mirror of
https://github.com/rancher/os.git
synced 2025-09-05 08:42:38 +00:00
Refactor configuration
This commit is contained in:
@@ -51,7 +51,7 @@ func disable(c *cli.Context) {
|
||||
}
|
||||
|
||||
if changed {
|
||||
if err = cfg.Save(); err != nil {
|
||||
if err = cfg.Set("enabled_addons", cfg.EnabledAddons); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@ func enable(c *cli.Context) {
|
||||
}
|
||||
|
||||
if changed {
|
||||
if err = cfg.Save(); err != nil {
|
||||
if err = cfg.Set("enabled_addons", cfg.EnabledAddons); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
@@ -12,7 +12,6 @@ import (
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/rancherio/os/config"
|
||||
"github.com/rancherio/os/docker"
|
||||
)
|
||||
|
||||
func configSubcommands() []cli.Command {
|
||||
@@ -30,7 +29,7 @@ func configSubcommands() []cli.Command {
|
||||
{
|
||||
Name: "import",
|
||||
Usage: "import configuration from standard in or a file",
|
||||
Action: configImport,
|
||||
Action: runImport,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "input, i",
|
||||
@@ -40,43 +39,40 @@ func configSubcommands() []cli.Command {
|
||||
},
|
||||
{
|
||||
Name: "export",
|
||||
Usage: "dump full configuration",
|
||||
Usage: "export configuration",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "output, o",
|
||||
Usage: "File to which to save",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "full",
|
||||
Usage: "Include full configuration, not just writable fields",
|
||||
Name: "private, p",
|
||||
Usage: "Include private information such as keys",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "full, f",
|
||||
Usage: "Include full configuration, including internal and default settings",
|
||||
},
|
||||
},
|
||||
Action: configSave,
|
||||
Action: export,
|
||||
},
|
||||
{
|
||||
Name: "merge",
|
||||
Usage: "merge configuration from stdin",
|
||||
Action: merge,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getConfigData() (map[interface{}]interface{}, error) {
|
||||
cfg, err := config.LoadConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
content, err := cfg.Dump()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
data := make(map[interface{}]interface{})
|
||||
err = yaml.Unmarshal([]byte(content), data)
|
||||
|
||||
return data, err
|
||||
}
|
||||
|
||||
func configImport(c *cli.Context) {
|
||||
var input io.Reader
|
||||
func runImport(c *cli.Context) {
|
||||
var input io.ReadCloser
|
||||
var err error
|
||||
input = os.Stdin
|
||||
cfg, err := config.LoadConfig()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
inputFile := c.String("input")
|
||||
if inputFile != "" {
|
||||
@@ -84,6 +80,7 @@ func configImport(c *cli.Context) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer input.Close()
|
||||
}
|
||||
|
||||
bytes, err := ioutil.ReadAll(input)
|
||||
@@ -91,38 +88,12 @@ func configImport(c *cli.Context) {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = mergeConfig(bytes)
|
||||
err = cfg.Import(bytes)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func mergeConfig(bytes []byte) error {
|
||||
var newConfig config.Config
|
||||
|
||||
err := yaml.Unmarshal(bytes, &newConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg, err := config.LoadConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = cfg.Merge(newConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cfg.Save()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func configSet(c *cli.Context) {
|
||||
key := c.Args().Get(0)
|
||||
value := c.Args().Get(1)
|
||||
@@ -130,15 +101,12 @@ func configSet(c *cli.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := getConfigData()
|
||||
getOrSetVal(key, data, value)
|
||||
|
||||
bytes, err := yaml.Marshal(data)
|
||||
cfg, err := config.LoadConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = mergeConfig(bytes)
|
||||
err = cfg.Set(key, value)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -150,12 +118,15 @@ func configGet(c *cli.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := getConfigData()
|
||||
cfg, err := config.LoadConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
val := getOrSetVal(arg, data, nil)
|
||||
val, err := cfg.Get(arg)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
printYaml := false
|
||||
switch val.(type) {
|
||||
@@ -183,6 +154,7 @@ func getOrSetVal(args string, data map[interface{}]interface{}, value interface{
|
||||
val, ok := data[part]
|
||||
last := i+1 == len(parts)
|
||||
|
||||
// Reached end, set the value
|
||||
if last && value != nil {
|
||||
if s, ok := value.(string); ok {
|
||||
value = config.DummyMarshall(s)
|
||||
@@ -192,6 +164,14 @@ func getOrSetVal(args string, data map[interface{}]interface{}, value interface{
|
||||
return value
|
||||
}
|
||||
|
||||
// Missing intermediate key, create key
|
||||
if !last && value != nil && !ok {
|
||||
newData := map[interface{}]interface{}{}
|
||||
data[part] = newData
|
||||
data = newData
|
||||
continue
|
||||
}
|
||||
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
@@ -211,26 +191,29 @@ func getOrSetVal(args string, data map[interface{}]interface{}, value interface{
|
||||
return ""
|
||||
}
|
||||
|
||||
func configSave(c *cli.Context) {
|
||||
func merge(c *cli.Context) {
|
||||
bytes, err := ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
cfg, err := config.LoadConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
//TODO: why doesn't this work
|
||||
for _, c := range cfg.SystemContainers {
|
||||
container := docker.NewContainer("", &c)
|
||||
if container.Err != nil {
|
||||
log.Fatalf("Failed to parse [%s] : %v", c.Cmd, container.Err)
|
||||
err = cfg.Merge(bytes)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if !c.Bool("full") {
|
||||
cfg.ClearReadOnly()
|
||||
func export(c *cli.Context) {
|
||||
content, err := config.Dump(c.Bool("private"), c.Bool("full"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
content, err := cfg.Dump()
|
||||
|
||||
output := c.String("output")
|
||||
if output == "" {
|
||||
fmt.Println(content)
|
||||
@@ -240,5 +223,4 @@ func configSave(c *cli.Context) {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
338
config/config.go
338
config/config.go
@@ -3,8 +3,6 @@ package config
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
@@ -12,90 +10,10 @@ import (
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
VERSION = "0.0.1"
|
||||
CONSOLE_CONTAINER = "console"
|
||||
DOCKER_BIN = "/usr/bin/docker"
|
||||
DOCKER_SYSTEM_HOST = "unix:///var/run/system-docker.sock"
|
||||
DOCKER_HOST = "unix:///var/run/docker.sock"
|
||||
IMAGES_PATH = "/"
|
||||
IMAGES_PATTERN = "images*.tar"
|
||||
SYS_INIT = "/sbin/init-sys"
|
||||
USER_INIT = "/sbin/init-user"
|
||||
MODULES_ARCHIVE = "/modules.tar"
|
||||
DEBUG = false
|
||||
)
|
||||
|
||||
var (
|
||||
ConfigFile = "/var/lib/rancher/conf/rancher.yml"
|
||||
)
|
||||
|
||||
type InitFunc func(*Config) error
|
||||
|
||||
type ContainerConfig struct {
|
||||
Id string `yaml:"id,omitempty"`
|
||||
Cmd string `yaml:"run,omitempty"`
|
||||
MigrateVolumes bool `yaml:"migrate_volumes,omitempty"`
|
||||
ReloadConfig bool `yaml:"reload_config,omitempty"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Debug bool `yaml:"debug,omitempty"`
|
||||
Disable []string `yaml:"disable,omitempty"`
|
||||
Dns []string `yaml:"dns,flow,omitempty"`
|
||||
Rescue bool `yaml:"rescue,omitempty"`
|
||||
RescueContainer *ContainerConfig `yaml:"rescue_container,omitempty"`
|
||||
State ConfigState `yaml:"state,omitempty"`
|
||||
Userdocker UserDockerInfo `yaml:"userdocker,omitempty"`
|
||||
OsUpgradeChannel string `yaml:"os_upgrade_channel,omitempty"`
|
||||
SystemContainers []ContainerConfig `yaml:"system_containers,omitempty"`
|
||||
SystemDockerArgs []string `yaml:"system_docker_args,flow,omitempty"`
|
||||
Modules []string `yaml:"modules,omitempty"`
|
||||
CloudInit CloudInit `yaml:"cloud_init"`
|
||||
SshInfo SshInfo `yaml:"ssh"`
|
||||
EnabledAddons []string `yaml:"enabledAddons,omitempty"`
|
||||
Addons map[string]Config `yaml:"addons,omitempty"`
|
||||
Network NetworkConfig `yaml:"network,omitempty"`
|
||||
}
|
||||
|
||||
type NetworkConfig struct {
|
||||
Interfaces []InterfaceConfig `yaml:"interfaces"`
|
||||
PostRun *ContainerConfig `yaml:"post_run"`
|
||||
}
|
||||
|
||||
type InterfaceConfig struct {
|
||||
Match string `yaml:"match"`
|
||||
DHCP bool `yaml:"dhcp"`
|
||||
Address string `yaml:"address,omitempty"`
|
||||
Gateway string `yaml:"gateway,omitempty"`
|
||||
MTU int `yaml:"mtu,omitempty"`
|
||||
}
|
||||
|
||||
type UserDockerInfo struct {
|
||||
UseTLS bool `yaml:"use_tls"`
|
||||
TLSServerCert string `yaml:"tls_server_cert"`
|
||||
TLSServerKey string `yaml:"tls_server_key"`
|
||||
TLSCACert string `yaml:"tls_ca_cert"`
|
||||
}
|
||||
|
||||
type SshInfo struct {
|
||||
Keys map[string]string
|
||||
}
|
||||
|
||||
type ConfigState struct {
|
||||
FsType string `yaml:"fstype"`
|
||||
Dev string `yaml:"dev"`
|
||||
Required bool `yaml:"required"`
|
||||
}
|
||||
|
||||
type CloudInit struct {
|
||||
Datasources []string `yaml:"datasources,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Config) PrivilegedMerge(newConfig Config) (bool, error) {
|
||||
reboot, err := c.Merge(newConfig)
|
||||
func (c *Config) privilegedMerge(newConfig Config) error {
|
||||
err := c.overlay(newConfig)
|
||||
if err != nil {
|
||||
return reboot, err
|
||||
return err
|
||||
}
|
||||
|
||||
toAppend := make([]ContainerConfig, 0, 5)
|
||||
@@ -115,48 +33,56 @@ func (c *Config) PrivilegedMerge(newConfig Config) (bool, error) {
|
||||
|
||||
c.SystemContainers = append(c.SystemContainers, toAppend...)
|
||||
|
||||
return reboot, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) Merge(newConfig Config) (bool, error) {
|
||||
//Efficient? Nope, but computers are fast
|
||||
newConfig.ClearReadOnly()
|
||||
content, err := newConfig.Dump()
|
||||
if err != nil {
|
||||
return false, err
|
||||
func (c *Config) overlay(newConfig Config) error {
|
||||
newConfig.clearReadOnly()
|
||||
return util.Convert(&newConfig, c)
|
||||
}
|
||||
|
||||
log.Debugf("Input \n%s", string(content))
|
||||
|
||||
err = yaml.Unmarshal([]byte(content), c)
|
||||
return true, err
|
||||
func (c *Config) clearReadOnly() {
|
||||
c.BootstrapContainers = make([]ContainerConfig, 0)
|
||||
c.SystemContainers = make([]ContainerConfig, 0)
|
||||
}
|
||||
|
||||
func (c *Config) ClearReadOnly() {
|
||||
c.SystemContainers = []ContainerConfig{}
|
||||
c.RescueContainer = nil
|
||||
c.Rescue = false
|
||||
c.Debug = false
|
||||
c.Addons = map[string]Config{}
|
||||
func clearReadOnly(data map[interface{}]interface{}) map[interface{}]interface{} {
|
||||
newData := make(map[interface{}]interface{})
|
||||
for k, v := range data {
|
||||
newData[k] = v
|
||||
}
|
||||
|
||||
func (c *Config) Dump() (string, error) {
|
||||
content, err := yaml.Marshal(c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return string(content), err
|
||||
}
|
||||
delete(newData, "system_container")
|
||||
delete(newData, "bootstrap_container")
|
||||
|
||||
return newData
|
||||
}
|
||||
|
||||
func (c Config) Save() error {
|
||||
c.ClearReadOnly()
|
||||
content, err := c.Dump()
|
||||
func (c *Config) Import(bytes []byte) error {
|
||||
data, err := readConfig(bytes, PrivateConfigFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(ConfigFile, []byte(content), 400)
|
||||
if err = saveToDisk(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Reload()
|
||||
}
|
||||
|
||||
func (c *Config) Merge(bytes []byte) error {
|
||||
data, err := readSavedConfig(bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = saveToDisk(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Reload()
|
||||
}
|
||||
|
||||
func LoadConfig() (*Config, error) {
|
||||
@@ -172,17 +98,6 @@ func LoadConfig() (*Config, error) {
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func FilterGlobalConfig(input []string) []string {
|
||||
result := make([]string, 0, len(input))
|
||||
for _, value := range input {
|
||||
if !strings.HasPrefix(value, "--rancher") {
|
||||
result = append(result, value)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *Config) readArgs() error {
|
||||
log.Debug("Reading config args")
|
||||
parts := make([]string, len(os.Args))
|
||||
@@ -209,32 +124,13 @@ func (c *Config) readArgs() error {
|
||||
return c.merge(cmdLineObj)
|
||||
}
|
||||
|
||||
func (c *Config) merge(values map[string]interface{}) error {
|
||||
// Lazy way to assign values to *Config
|
||||
override, err := yaml.Marshal(values)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var newConfig Config
|
||||
err = yaml.Unmarshal(override, &newConfig)
|
||||
if err != nil {
|
||||
return nil
|
||||
func (c *Config) merge(values map[interface{}]interface{}) error {
|
||||
values = clearReadOnly(values)
|
||||
return util.Convert(values, c)
|
||||
}
|
||||
|
||||
_, err = c.Merge(newConfig)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Config) readFile() error {
|
||||
content, err := ioutil.ReadFile(ConfigFile)
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data := make(map[string]interface{})
|
||||
err = yaml.Unmarshal(content, data)
|
||||
func (c *Config) readFiles() error {
|
||||
data, err := readSavedConfig(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -259,75 +155,49 @@ func (c *Config) readCmdline() error {
|
||||
return c.merge(cmdLineObj)
|
||||
}
|
||||
|
||||
func DummyMarshall(value string) interface{} {
|
||||
if strings.HasPrefix(value, "[") && strings.HasSuffix(value, "]") {
|
||||
return strings.Split(value[1:len(value)-1], ",")
|
||||
func Dump(private, full bool) (string, error) {
|
||||
files := []string{ConfigFile}
|
||||
if private {
|
||||
files = append(files, PrivateConfigFile)
|
||||
}
|
||||
|
||||
if value == "true" {
|
||||
return true
|
||||
} else if value == "false" {
|
||||
return false
|
||||
} else if ok, _ := regexp.MatchString("^[0-9]+$", value); ok {
|
||||
i, err := strconv.Atoi(value)
|
||||
var c Config
|
||||
|
||||
if full {
|
||||
c = *NewConfig()
|
||||
}
|
||||
|
||||
data, err := readConfig(nil, files...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return i
|
||||
return "", err
|
||||
}
|
||||
|
||||
return value
|
||||
err = c.merge(data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
func parseCmdline(cmdLine string) map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
|
||||
outer:
|
||||
for _, part := range strings.Split(cmdLine, " ") {
|
||||
if !strings.HasPrefix(part, "rancher.") {
|
||||
continue
|
||||
err = c.readGlobals()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var value string
|
||||
kv := strings.SplitN(part, "=", 2)
|
||||
|
||||
if len(kv) == 1 {
|
||||
value = "true"
|
||||
} else {
|
||||
value = kv[1]
|
||||
bytes, err := yaml.Marshal(c)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
current := result
|
||||
keys := strings.Split(kv[0], ".")[1:]
|
||||
for i, key := range keys {
|
||||
if i == len(keys)-1 {
|
||||
current[key] = DummyMarshall(value)
|
||||
} else {
|
||||
if obj, ok := current[key]; ok {
|
||||
if newCurrent, ok := obj.(map[string]interface{}); ok {
|
||||
current = newCurrent
|
||||
} else {
|
||||
continue outer
|
||||
}
|
||||
} else {
|
||||
newCurrent := make(map[string]interface{})
|
||||
current[key] = newCurrent
|
||||
current = newCurrent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("Input obj %s", result)
|
||||
return result
|
||||
func (c *Config) readGlobals() error {
|
||||
return util.ShortCircuit(
|
||||
c.readCmdline,
|
||||
c.readArgs,
|
||||
c.mergeAddons,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Config) Reload() error {
|
||||
return util.ShortCircuit(
|
||||
c.readFile,
|
||||
c.readCmdline,
|
||||
c.readArgs,
|
||||
c.mergeAddons,
|
||||
c.readFiles,
|
||||
c.readGlobals,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -335,24 +205,62 @@ func (c *Config) mergeAddons() error {
|
||||
for _, addon := range c.EnabledAddons {
|
||||
if newConfig, ok := c.Addons[addon]; ok {
|
||||
log.Debugf("Enabling addon %s", addon)
|
||||
_, err := c.PrivilegedMerge(newConfig)
|
||||
if err := c.privilegedMerge(newConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) Get(key string) (interface{}, error) {
|
||||
data := make(map[interface{}]interface{})
|
||||
err := util.Convert(c, &data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return getOrSetVal(key, data, nil), nil
|
||||
}
|
||||
|
||||
//func (c *Config) SetBytes(bytes []byte) error {
|
||||
// content, err := readConfigFile()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// data := make(map[interface{}]interface{})
|
||||
// err = yaml.Unmarshal(content, &data)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// err = yaml.Unmarshal(bytes, &data)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// content, err = yaml.Marshal(data)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// return ioutil.WriteFile(ConfigFile, content, 400)
|
||||
//}
|
||||
|
||||
func (c *Config) Set(key string, value interface{}) error {
|
||||
data, err := readSavedConfig(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
getOrSetVal(key, data, value)
|
||||
|
||||
func RunInitFuncs(cfg *Config, initFuncs []InitFunc) error {
|
||||
for i, initFunc := range initFuncs {
|
||||
log.Debugf("[%d/%d] Starting", i+1, len(initFuncs))
|
||||
if err := initFunc(cfg); err != nil {
|
||||
log.Errorf("Failed [%d/%d] %d%%", i+1, len(initFuncs), ((i + 1) * 100 / len(initFuncs)))
|
||||
err = saveToDisk(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("[%d/%d] Done %d%%", i+1, len(initFuncs), ((i + 1) * 100 / len(initFuncs)))
|
||||
}
|
||||
return nil
|
||||
|
||||
return c.Reload()
|
||||
}
|
||||
|
@@ -1,10 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.google.com/p/rog-go/deepdiff"
|
||||
)
|
||||
import "testing"
|
||||
import "reflect"
|
||||
|
||||
func TestParseCmdline(t *testing.T) {
|
||||
expected := map[string]interface{}{
|
||||
@@ -23,8 +20,76 @@ func TestParseCmdline(t *testing.T) {
|
||||
|
||||
actual := parseCmdline("a b rancher.rescue rancher.keyArray=[1,2] rancher.key1=value1 c rancher.key2=value2 rancher.obj1.key3=3value rancher.obj1.obj2.key4 rancher.key5=5")
|
||||
|
||||
ok, err := deepdiff.DeepDiff(actual, expected)
|
||||
if !ok || err != nil {
|
||||
t.Fatal(err)
|
||||
ok := reflect.DeepEqual(actual, expected)
|
||||
if !ok {
|
||||
t.Fatalf("%v != %v", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
data := map[interface{}]interface{}{
|
||||
"key": "value",
|
||||
"key2": map[interface{}]interface{}{
|
||||
"subkey": "subvalue",
|
||||
"subnum": 42,
|
||||
},
|
||||
}
|
||||
|
||||
tests := map[string]interface{}{
|
||||
"key": "value",
|
||||
"key2.subkey": "subvalue",
|
||||
"key2.subnum": 42,
|
||||
"key2.subkey2": "",
|
||||
"foo": "",
|
||||
}
|
||||
|
||||
for k, v := range tests {
|
||||
if getOrSetVal(k, data, nil) != v {
|
||||
t.Fatalf("Expected %v, got %v, for key %s", v, getOrSetVal(k, data, nil), k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSet(t *testing.T) {
|
||||
data := map[interface{}]interface{}{
|
||||
"key": "value",
|
||||
"key2": map[interface{}]interface{}{
|
||||
"subkey": "subvalue",
|
||||
"subnum": 42,
|
||||
},
|
||||
}
|
||||
|
||||
expected := map[interface{}]interface{}{
|
||||
"key": "value2",
|
||||
"key2": map[interface{}]interface{}{
|
||||
"subkey": "subvalue2",
|
||||
"subkey2": "value",
|
||||
"subkey3": 43,
|
||||
"subnum": 42,
|
||||
},
|
||||
"key3": map[interface{}]interface{}{
|
||||
"subkey3": 44,
|
||||
},
|
||||
"key4": "value4",
|
||||
}
|
||||
|
||||
tests := map[string]interface{}{
|
||||
"key": "value2",
|
||||
"key2.subkey": "subvalue2",
|
||||
"key2.subkey2": "value",
|
||||
"key2.subkey3": 43,
|
||||
"key3.subkey3": 44,
|
||||
"key4": "value4",
|
||||
}
|
||||
|
||||
for k, v := range tests {
|
||||
getOrSetVal(k, data, v)
|
||||
if getOrSetVal(k, data, nil) != v {
|
||||
t.Fatalf("Expected %v, got %v, for key %s", v, getOrSetVal(k, data, nil), k)
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(data, expected) {
|
||||
t.Fatalf("Expected %v, got %v", expected, data)
|
||||
}
|
||||
}
|
||||
|
116
config/data_funcs.go
Normal file
116
config/data_funcs.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getOrSetVal(args string, data map[interface{}]interface{}, value interface{}) interface{} {
|
||||
parts := strings.Split(args, ".")
|
||||
|
||||
for i, part := range parts {
|
||||
val, ok := data[part]
|
||||
last := i+1 == len(parts)
|
||||
|
||||
// Reached end, set the value
|
||||
if last && value != nil {
|
||||
if s, ok := value.(string); ok {
|
||||
value = DummyMarshall(s)
|
||||
}
|
||||
|
||||
data[part] = value
|
||||
return value
|
||||
}
|
||||
|
||||
// Missing intermediate key, create key
|
||||
if !last && value != nil && !ok {
|
||||
newData := map[interface{}]interface{}{}
|
||||
data[part] = newData
|
||||
data = newData
|
||||
continue
|
||||
}
|
||||
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
if last {
|
||||
return val
|
||||
}
|
||||
|
||||
newData, ok := val.(map[interface{}]interface{})
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
data = newData
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func DummyMarshall(value string) interface{} {
|
||||
if strings.HasPrefix(value, "[") && strings.HasSuffix(value, "]") {
|
||||
return strings.Split(value[1:len(value)-1], ",")
|
||||
}
|
||||
|
||||
if value == "true" {
|
||||
return true
|
||||
} else if value == "false" {
|
||||
return false
|
||||
} else if ok, _ := regexp.MatchString("^[0-9]+$", value); ok {
|
||||
i, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
func parseCmdline(cmdLine string) map[interface{}]interface{} {
|
||||
result := make(map[interface{}]interface{})
|
||||
|
||||
outer:
|
||||
for _, part := range strings.Split(cmdLine, " ") {
|
||||
if !strings.HasPrefix(part, "rancher.") {
|
||||
continue
|
||||
}
|
||||
|
||||
var value string
|
||||
kv := strings.SplitN(part, "=", 2)
|
||||
|
||||
if len(kv) == 1 {
|
||||
value = "true"
|
||||
} else {
|
||||
value = kv[1]
|
||||
}
|
||||
|
||||
current := result
|
||||
keys := strings.Split(kv[0], ".")[1:]
|
||||
for i, key := range keys {
|
||||
if i == len(keys)-1 {
|
||||
current[key] = DummyMarshall(value)
|
||||
} else {
|
||||
if obj, ok := current[key]; ok {
|
||||
if newCurrent, ok := obj.(map[interface{}]interface{}); ok {
|
||||
current = newCurrent
|
||||
} else {
|
||||
continue outer
|
||||
}
|
||||
} else {
|
||||
newCurrent := make(map[interface{}]interface{})
|
||||
current[key] = newCurrent
|
||||
current = newCurrent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("Input obj %v", result)
|
||||
return result
|
||||
}
|
98
config/disk.go
Normal file
98
config/disk.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/rancherio/os/util"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func writeToFile(data interface{}, filename string) error {
|
||||
content, err := yaml.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(filename, content, 400)
|
||||
}
|
||||
|
||||
func saveToDisk(data map[interface{}]interface{}) error {
|
||||
config := make(map[interface{}]interface{})
|
||||
private := make(map[interface{}]interface{})
|
||||
|
||||
for k, v := range data {
|
||||
if k == "ssh" {
|
||||
private[k] = v
|
||||
} else if k == "userdocker" {
|
||||
var userDockerConfig UserDockerConfig
|
||||
var userDockerConfigPrivate UserDockerConfig
|
||||
err := util.Convert(v, &userDockerConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userDockerConfigPrivate.TLSCACert = userDockerConfig.TLSCACert
|
||||
userDockerConfigPrivate.TLSServerKey = userDockerConfig.TLSServerKey
|
||||
userDockerConfigPrivate.TLSServerCert = userDockerConfig.TLSServerCert
|
||||
|
||||
userDockerConfig.TLSCACert = ""
|
||||
userDockerConfig.TLSServerKey = ""
|
||||
userDockerConfig.TLSServerCert = ""
|
||||
|
||||
config[k] = userDockerConfig
|
||||
private[k] = userDockerConfigPrivate
|
||||
} else {
|
||||
config[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
err := writeToFile(config, ConfigFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return writeToFile(private, PrivateConfigFile)
|
||||
}
|
||||
|
||||
func readSavedConfig(bytes []byte) (map[interface{}]interface{}, error) {
|
||||
return readConfig(bytes, ConfigFile, PrivateConfigFile)
|
||||
}
|
||||
|
||||
func readConfig(bytes []byte, files ...string) (map[interface{}]interface{}, error) {
|
||||
data := make(map[interface{}]interface{})
|
||||
for _, conf := range files {
|
||||
content, err := readConfigFile(conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(content, &data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if bytes != nil && len(bytes) > 0 {
|
||||
if err := yaml.Unmarshal(bytes, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func readConfigFile(file string) ([]byte, error) {
|
||||
content, err := ioutil.ReadFile(file)
|
||||
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = nil
|
||||
content = []byte{}
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return content, err
|
||||
}
|
32
config/init.go
Normal file
32
config/init.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
type InitFunc func(*Config) error
|
||||
|
||||
func RunInitFuncs(cfg *Config, initFuncs []InitFunc) error {
|
||||
for i, initFunc := range initFuncs {
|
||||
log.Debugf("[%d/%d] Starting", i+1, len(initFuncs))
|
||||
if err := initFunc(cfg); err != nil {
|
||||
log.Errorf("Failed [%d/%d] %d%%", i+1, len(initFuncs), ((i + 1) * 100 / len(initFuncs)))
|
||||
return err
|
||||
}
|
||||
log.Debugf("[%d/%d] Done %d%%", i+1, len(initFuncs), ((i + 1) * 100 / len(initFuncs)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func FilterGlobalConfig(input []string) []string {
|
||||
result := make([]string, 0, len(input))
|
||||
for _, value := range input {
|
||||
if !strings.HasPrefix(value, "--rancher") {
|
||||
result = append(result, value)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
86
config/types.go
Normal file
86
config/types.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package config
|
||||
|
||||
const (
|
||||
VERSION = "0.0.1"
|
||||
CONSOLE_CONTAINER = "console"
|
||||
DOCKER_BIN = "/usr/bin/docker"
|
||||
DOCKER_SYSTEM_HOST = "unix:///var/run/system-docker.sock"
|
||||
DOCKER_HOST = "unix:///var/run/docker.sock"
|
||||
IMAGES_PATH = "/"
|
||||
IMAGES_PATTERN = "images*.tar"
|
||||
SYS_INIT = "/sbin/init-sys"
|
||||
USER_INIT = "/sbin/init-user"
|
||||
MODULES_ARCHIVE = "/modules.tar"
|
||||
DEBUG = false
|
||||
)
|
||||
|
||||
var (
|
||||
ConfigFile = "/var/lib/rancher/conf/rancher.yml"
|
||||
PrivateConfigFile = "/var/lib/rancher/conf/rancher-private.yml"
|
||||
)
|
||||
|
||||
type ContainerConfig struct {
|
||||
Id string `yaml:"id,omitempty"`
|
||||
Cmd string `yaml:"run,omitempty"`
|
||||
MigrateVolumes bool `yaml:"migrate_volumes,omitempty"`
|
||||
ReloadConfig bool `yaml:"reload_config,omitempty"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Debug bool `yaml:"debug,omitempty"`
|
||||
Disable []string `yaml:"disable,omitempty"`
|
||||
Dns []string `yaml:"dns,flow,omitempty"`
|
||||
//Rescue bool `yaml:"rescue,omitempty"`
|
||||
//RescueContainer *ContainerConfig `yaml:"rescue_container,omitempty"`
|
||||
State ConfigState `yaml:"state,omitempty"`
|
||||
Userdocker UserDockerConfig `yaml:"userdocker,omitempty"`
|
||||
UpgradeConfig UpgradeConfig `yaml:"upgrade,omitempty"`
|
||||
BootstrapContainers []ContainerConfig `yaml:"bootstrap_containers,omitempty"`
|
||||
SystemContainers []ContainerConfig `yaml:"system_containers,omitempty"`
|
||||
UserContainers []ContainerConfig `yaml:"user_containers,omitempty"`
|
||||
SystemDockerArgs []string `yaml:"system_docker_args,flow,omitempty"`
|
||||
Modules []string `yaml:"modules,omitempty"`
|
||||
CloudInit CloudInit `yaml:"cloud_init,omitempty"`
|
||||
SshConfig SshConfig `yaml:"ssh,omitempty"`
|
||||
EnabledAddons []string `yaml:"enabled_addons,omitempty"`
|
||||
Addons map[string]Config `yaml:"addons,omitempty"`
|
||||
Network NetworkConfig `yaml:"network,omitempty"`
|
||||
}
|
||||
|
||||
type UpgradeConfig struct {
|
||||
Url string `yaml:"url,omitempty"`
|
||||
}
|
||||
|
||||
type NetworkConfig struct {
|
||||
Interfaces []InterfaceConfig `yaml:"interfaces,omitempty"`
|
||||
PostRun *ContainerConfig `yaml:"post_run,omitempty"`
|
||||
}
|
||||
|
||||
type InterfaceConfig struct {
|
||||
Match string `yaml:"match,omitempty"`
|
||||
DHCP bool `yaml:"dhcp,omitempty"`
|
||||
Address string `yaml:"address,omitempty"`
|
||||
Gateway string `yaml:"gateway,omitempty"`
|
||||
MTU int `yaml:"mtu,omitempty"`
|
||||
}
|
||||
|
||||
type UserDockerConfig struct {
|
||||
UseTLS bool `yaml:"use_tls,omitempty"`
|
||||
TLSServerCert string `yaml:"tls_server_cert,omitempty"`
|
||||
TLSServerKey string `yaml:"tls_server_key,omitempty"`
|
||||
TLSCACert string `yaml:"tls_ca_cert,omitempty"`
|
||||
}
|
||||
|
||||
type SshConfig struct {
|
||||
Keys map[string]string `yaml:"keys,omitempty"`
|
||||
}
|
||||
|
||||
type ConfigState struct {
|
||||
FsType string `yaml:"fstype,omitempty"`
|
||||
Dev string `yaml:"dev,omitempty"`
|
||||
Required bool `yaml:"required,omitempty"`
|
||||
}
|
||||
|
||||
type CloudInit struct {
|
||||
Datasources []string `yaml:"datasources,omitempty"`
|
||||
}
|
@@ -277,7 +277,7 @@ func RunInit() error {
|
||||
}
|
||||
|
||||
if cfg.Debug {
|
||||
cfgString, _ := cfg.Dump()
|
||||
cfgString, _ := config.Dump(false, true)
|
||||
if cfgString != "" {
|
||||
log.Debugf("Config: %s", cfgString)
|
||||
}
|
||||
|
10
util/util.go
10
util/util.go
@@ -10,6 +10,7 @@ import (
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/docker/pkg/mount"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -154,3 +155,12 @@ func RandSeq(n int) string {
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func Convert(from, to interface{}) error {
|
||||
bytes, err := yaml.Marshal(from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return yaml.Unmarshal(bytes, to)
|
||||
}
|
||||
|
Reference in New Issue
Block a user