1
0
mirror of https://github.com/rancher/os.git synced 2025-05-31 19:06:17 +00:00
os/cmd/control/config.go
2019-03-11 13:03:57 +08:00

311 lines
6.3 KiB
Go

package control
import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"sort"
"strings"
"text/template"
"github.com/rancher/os/config"
"github.com/rancher/os/pkg/log"
"github.com/rancher/os/pkg/util"
yaml "github.com/cloudfoundry-incubator/candiedyaml"
"github.com/codegangsta/cli"
"github.com/pkg/errors"
)
func configSubcommands() []cli.Command {
return []cli.Command{
{
Name: "get",
Usage: "get value",
Action: configGet,
},
{
Name: "set",
Usage: "set a value",
Action: configSet,
},
{
Name: "images",
Usage: "List Docker images for a configuration from a file",
Action: runImages,
Flags: []cli.Flag{
cli.StringFlag{
Name: "input, i",
Usage: "File from which to read config",
},
},
},
{
Name: "generate",
Usage: "Generate a configuration file from a template",
Action: runGenerate,
HideHelp: true,
},
{
Name: "export",
Usage: "export configuration",
Flags: []cli.Flag{
cli.StringFlag{
Name: "output, o",
Usage: "File to which to save",
},
cli.BoolFlag{
Name: "private, p",
Usage: "Include the generated private keys",
},
cli.BoolFlag{
Name: "full, f",
Usage: "Export full configuration, including internal and default settings",
},
},
Action: export,
},
{
Name: "merge",
Usage: "merge configuration from stdin",
Action: merge,
Flags: []cli.Flag{
cli.StringFlag{
Name: "input, i",
Usage: "File from which to read",
},
},
},
{
Name: "syslinux",
Usage: "edit Syslinux boot global.cfg",
Action: editSyslinux,
},
{
Name: "validate",
Usage: "validate configuration from stdin",
Action: validate,
Flags: []cli.Flag{
cli.StringFlag{
Name: "input, i",
Usage: "File from which to read",
},
},
},
}
}
func imagesFromConfig(cfg *config.CloudConfig) []string {
imagesMap := map[string]int{}
for _, service := range cfg.Rancher.BootstrapContainers {
imagesMap[service.Image] = 1
}
for _, service := range cfg.Rancher.Services {
imagesMap[service.Image] = 1
}
images := make([]string, len(imagesMap))
i := 0
for image := range imagesMap {
images[i] = image
i++
}
sort.Strings(images)
return images
}
func runImages(c *cli.Context) error {
configFile := c.String("input")
cfg, err := config.ReadConfig(nil, false, configFile)
if err != nil {
log.WithFields(log.Fields{"err": err, "file": configFile}).Fatalf("Could not read config from file")
}
images := imagesFromConfig(cfg)
fmt.Println(strings.Join(images, " "))
return nil
}
func runGenerate(c *cli.Context) error {
if err := genTpl(os.Stdin, os.Stdout); err != nil {
log.Fatalf("Failed to generate config, err: '%s'", err)
}
return nil
}
func genTpl(in io.Reader, out io.Writer) error {
bytes, err := ioutil.ReadAll(in)
if err != nil {
log.Fatal("Could not read from stdin")
}
tpl := template.Must(template.New("osconfig").Parse(string(bytes)))
return tpl.Execute(out, env2map(os.Environ()))
}
func env2map(env []string) map[string]string {
m := make(map[string]string, len(env))
for _, s := range env {
d := strings.Split(s, "=")
m[d[0]] = d[1]
}
return m
}
func editSyslinux(c *cli.Context) error {
// check whether is Raspberry Pi or not
bytes, err := ioutil.ReadFile("/proc/device-tree/model")
if err == nil && strings.Contains(strings.ToLower(string(bytes)), "raspberry") {
buf := bufio.NewWriter(os.Stdout)
fmt.Fprintln(buf, "raspberry pi can not use this command")
buf.Flush()
return errors.New("raspberry pi can not use this command")
}
if isExist := checkGlobalCfg(); !isExist {
buf := bufio.NewWriter(os.Stdout)
fmt.Fprintln(buf, "global.cfg can not be found")
buf.Flush()
return errors.New("global.cfg can not be found")
}
cmd := exec.Command("system-docker", "run", "--rm", "-it",
"-v", "/:/host",
"-w", "/host",
"--entrypoint=vi",
"rancher/os-console:"+config.Version,
"boot/global.cfg")
cmd.Stdout, cmd.Stderr, cmd.Stdin = os.Stdout, os.Stderr, os.Stdin
return cmd.Run()
}
func configSet(c *cli.Context) error {
if c.NArg() < 2 {
return nil
}
key := c.Args().Get(0)
value := c.Args().Get(1)
if key == "" {
return nil
}
err := config.Set(key, value)
if err != nil {
log.Fatal(err)
}
return nil
}
func configGet(c *cli.Context) error {
arg := c.Args().Get(0)
if arg == "" {
return nil
}
val, err := config.Get(arg)
if err != nil {
log.WithFields(log.Fields{"key": arg, "val": val, "err": err}).Fatal("config get: failed to retrieve value")
}
printYaml := false
switch val.(type) {
case []interface{}:
printYaml = true
case map[interface{}]interface{}:
printYaml = true
}
if printYaml {
bytes, err := yaml.Marshal(val)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(bytes))
} else {
fmt.Println(val)
}
return nil
}
func merge(c *cli.Context) error {
bytes, err := inputBytes(c)
if err != nil {
log.Fatal(err)
}
if err = config.Merge(bytes); err != nil {
log.Error(err)
validationErrors, err := config.ValidateBytes(bytes)
if err != nil {
log.Fatal(err)
}
for _, validationError := range validationErrors.Errors() {
log.Error(validationError)
}
log.Fatal("EXITING: Failed to parse configuration")
}
return nil
}
func export(c *cli.Context) error {
content, err := config.Export(c.Bool("private"), c.Bool("full"))
if err != nil {
log.Fatal(err)
}
output := c.String("output")
if output == "" {
fmt.Println(content)
} else {
err := util.WriteFileAtomic(output, []byte(content), 0400)
if err != nil {
log.Fatal(err)
}
}
return nil
}
func validate(c *cli.Context) error {
bytes, err := inputBytes(c)
if err != nil {
log.Fatal(err)
}
validationErrors, err := config.ValidateBytes(bytes)
if err != nil {
log.Fatal(err)
}
for _, validationError := range validationErrors.Errors() {
log.Error(validationError)
}
return nil
}
func inputBytes(c *cli.Context) ([]byte, error) {
input := os.Stdin
inputFile := c.String("input")
if inputFile != "" {
var err error
input, err = os.Open(inputFile)
if err != nil {
return nil, err
}
defer input.Close()
}
content, err := ioutil.ReadAll(input)
if err != nil {
return nil, err
}
if bytes.Contains(content, []byte{13, 10}) {
return nil, errors.New("file format shouldn't contain CRLF characters")
}
return content, nil
}