<commit_before>// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
	"bytes"
	"errors"
	"reflect"

	"github.com/coreos/ignition/config/types"
	"github.com/coreos/ignition/config/v1"
	"github.com/coreos/ignition/config/v2_0"
	"github.com/coreos/ignition/config/v2_1"
	"github.com/coreos/ignition/config/validate"
	astjson "github.com/coreos/ignition/config/validate/astjson"
	"github.com/coreos/ignition/config/validate/report"

	json "github.com/ajeddeloh/go-json"
	"github.com/coreos/go-semver/semver"
	"go4.org/errorutil"
)

var (
	ErrCloudConfig           = errors.New("not a config (found coreos-cloudconfig)")
	ErrEmpty                 = errors.New("not a config (empty)")
	ErrScript                = errors.New("not a config (found coreos-cloudinit script)")
	ErrDeprecated            = errors.New("config format deprecated")
	ErrInvalid               = errors.New("config is not valid")
	ErrUnknownVersion        = errors.New("unsupported config version")
	ErrVersionIndeterminable = errors.New("unable to determine version")
)

// Parse parses the raw config into a types.Config struct and generates a report of any
// errors, warnings, info, and deprecations it encountered
func Parse(rawConfig []byte) (types.Config, report.Report, error) {
	if isEmpty(rawConfig) {
		return types.Config{}, report.Report{}, ErrEmpty
	} else if isCloudConfig(rawConfig) {
		return types.Config{}, report.Report{}, ErrCloudConfig
	} else if isScript(rawConfig) {
		return types.Config{}, report.Report{}, ErrScript
	}

	version, err := Version(rawConfig)
	if err != nil && err != ErrVersionIndeterminable {
		// If we can't determine the version, its probably invalid json and we want to fall through
		// to handle that in ParseFromLatest, since it will generate the highlight string and report
		// line and column.
		return types.Config{}, report.ReportFromError(err, report.EntryError), err
	}
	switch version {
	case semver.Version{Major: 1}:
		config, err := ParseFromV1(rawConfig)
		if err != nil {
			return types.Config{}, report.ReportFromError(err, report.EntryError), err
		}

		return config, report.ReportFromError(ErrDeprecated, report.EntryDeprecated), nil
	case types.MaxVersion:
		return ParseFromLatest(rawConfig)
	case semver.Version{Major: 2, Minor: 1}:
		return ParseFromV2_1(rawConfig)
	case semver.Version{Major: 2, Minor: 0}:
		return ParseFromV2_0(rawConfig)
	default:
		// It's not empty, it's not a cloud config, and it's not a script, but
		// we can't determine the version of it (or it's 0.0.0, hooray golang
		// zero values). We know it's not valid, but try to parse it anyway with
		// ParseFromLatest to generate any parse errors.
		config, rep, err := ParseFromLatest(rawConfig)
		if err != nil || rep.IsFatal() {
			return config, rep, err
		}
		// Somehow parsing succeeded without any errors or fatal reports. This
		// should be an impossible state, but since we're in this part of the
		// switch statement we know at the very least the version is invalid, so
		// return that.
		return types.Config{}, report.Report{}, ErrUnknownVersion
	}
}

func ParseFromLatest(rawConfig []byte) (types.Config, report.Report, error) {
	var err error
	var config types.Config

	// These errors are fatal and the config should not be further validated
	if err = json.Unmarshal(rawConfig, &config); err == nil {
		versionReport := config.Ignition.Validate()
		if versionReport.IsFatal() {
			return types.Config{}, versionReport, ErrInvalid
		}
	}

	// Handle json syntax and type errors first, since they are fatal but have offset info
	if serr, ok := err.(*json.SyntaxError); ok {
		line, col, highlight := errorutil.HighlightBytePosition(bytes.NewReader(rawConfig), serr.Offset)
		return types.Config{},
			report.Report{
				Entries: []report.Entry{{
					Kind:      report.EntryError,
					Message:   serr.Error(),
					Line:      line,
					Column:    col,
					Highlight: highlight,
				}},
			},
			ErrInvalid
	}

	if terr, ok := err.(*json.UnmarshalTypeError); ok {
		line, col, highlight := errorutil.HighlightBytePosition(bytes.NewReader(rawConfig), terr.Offset)
		return types.Config{},
			report.Report{
				Entries: []report.Entry{{
					Kind:      report.EntryError,
					Message:   terr.Error(),
					Line:      line,
					Column:    col,
					Highlight: highlight,
				}},
			},
			ErrInvalid
	}

	// Handle other fatal errors (i.e. invalid version)
	if err != nil {
		return types.Config{}, report.ReportFromError(err, report.EntryError), err
	}

	// Unmarshal again to a json.Node to get offset information for building a report
	var ast json.Node
	var r report.Report
	configValue := reflect.ValueOf(config)
	if err := json.Unmarshal(rawConfig, &ast); err != nil {
		r.Add(report.Entry{
			Kind:    report.EntryWarning,
			Message: "Ignition could not unmarshal your config for reporting line numbers. This should never happen. Please file a bug.",
		})
		r.Merge(validate.ValidateWithoutSource(configValue))
	} else {
		r.Merge(validate.Validate(configValue, astjson.FromJsonRoot(ast), bytes.NewReader(rawConfig), true))
	}

	if r.IsFatal() {
		return types.Config{}, r, ErrInvalid
	}

	return config, r, nil
}

func ParseFromV1(rawConfig []byte) (types.Config, error) {
	config, err := v1.Parse(rawConfig)
	if err != nil {
		return types.Config{}, err
	}

	return TranslateFromV1(config), nil
}

func ParseFromV2_0(rawConfig []byte) (types.Config, report.Report, error) {
	cfg, report, err := v2_0.Parse(rawConfig)
	if err != nil {
		return types.Config{}, report, err
	}

	return TranslateFromV2_0(cfg), report, err
}

func ParseFromV2_1(rawConfig []byte) (types.Config, report.Report, error) {
	cfg, report, err := v2_1.Parse(rawConfig)
	if err != nil {
		return types.Config{}, report, err
	}

	return TranslateFromV2_1(cfg), report, err
}

func Version(rawConfig []byte) (semver.Version, error) {
	var composite struct {
		Version  *int `json:"ignitionVersion"`
		Ignition struct {
			Version *string `json:"version"`
		} `json:"ignition"`
	}

	if json.Unmarshal(rawConfig, &composite) == nil {
		if composite.Ignition.Version != nil {
			v, err := types.Ignition{Version: *composite.Ignition.Version}.Semver()
			if err != nil {
				return semver.Version{}, err
			}
			return *v, nil
		} else if composite.Version != nil {
			return semver.Version{Major: int64(*composite.Version)}, nil
		}
	}

	return semver.Version{}, ErrVersionIndeterminable
}

func isEmpty(userdata []byte) bool {
	return len(userdata) == 0
}
<commit_msg>config/config: validate V1 configs<commit_after>// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
	"bytes"
	"errors"
	"reflect"

	"github.com/coreos/ignition/config/types"
	"github.com/coreos/ignition/config/v1"
	"github.com/coreos/ignition/config/v2_0"
	"github.com/coreos/ignition/config/v2_1"
	"github.com/coreos/ignition/config/validate"
	astjson "github.com/coreos/ignition/config/validate/astjson"
	"github.com/coreos/ignition/config/validate/report"

	json "github.com/ajeddeloh/go-json"
	"github.com/coreos/go-semver/semver"
	"go4.org/errorutil"
)

var (
	ErrCloudConfig           = errors.New("not a config (found coreos-cloudconfig)")
	ErrEmpty                 = errors.New("not a config (empty)")
	ErrScript                = errors.New("not a config (found coreos-cloudinit script)")
	ErrDeprecated            = errors.New("config format deprecated")
	ErrInvalid               = errors.New("config is not valid")
	ErrUnknownVersion        = errors.New("unsupported config version")
	ErrVersionIndeterminable = errors.New("unable to determine version")
)

// Parse parses the raw config into a types.Config struct and generates a report of any
// errors, warnings, info, and deprecations it encountered
func Parse(rawConfig []byte) (types.Config, report.Report, error) {
	if isEmpty(rawConfig) {
		return types.Config{}, report.Report{}, ErrEmpty
	} else if isCloudConfig(rawConfig) {
		return types.Config{}, report.Report{}, ErrCloudConfig
	} else if isScript(rawConfig) {
		return types.Config{}, report.Report{}, ErrScript
	}

	version, err := Version(rawConfig)
	if err != nil && err != ErrVersionIndeterminable {
		// If we can't determine the version, its probably invalid json and we want to fall through
		// to handle that in ParseFromLatest, since it will generate the highlight string and report
		// line and column.
		return types.Config{}, report.ReportFromError(err, report.EntryError), err
	}
	switch version {
	case semver.Version{Major: 1}:
		config, rpt, err := ParseFromV1(rawConfig)
		if err != nil {
			return types.Config{}, report.ReportFromError(err, report.EntryError), err
		}
		rpt.Merge(report.ReportFromError(ErrDeprecated, report.EntryDeprecated))

		return config, rpt, nil
	case types.MaxVersion:
		return ParseFromLatest(rawConfig)
	case semver.Version{Major: 2, Minor: 1}:
		return ParseFromV2_1(rawConfig)
	case semver.Version{Major: 2, Minor: 0}:
		return ParseFromV2_0(rawConfig)
	default:
		// It's not empty, it's not a cloud config, and it's not a script, but
		// we can't determine the version of it (or it's 0.0.0, hooray golang
		// zero values). We know it's not valid, but try to parse it anyway with
		// ParseFromLatest to generate any parse errors.
		config, rep, err := ParseFromLatest(rawConfig)
		if err != nil || rep.IsFatal() {
			return config, rep, err
		}
		// Somehow parsing succeeded without any errors or fatal reports. This
		// should be an impossible state, but since we're in this part of the
		// switch statement we know at the very least the version is invalid, so
		// return that.
		return types.Config{}, report.Report{}, ErrUnknownVersion
	}
}

func ParseFromLatest(rawConfig []byte) (types.Config, report.Report, error) {
	var err error
	var config types.Config

	// These errors are fatal and the config should not be further validated
	if err = json.Unmarshal(rawConfig, &config); err == nil {
		versionReport := config.Ignition.Validate()
		if versionReport.IsFatal() {
			return types.Config{}, versionReport, ErrInvalid
		}
	}

	// Handle json syntax and type errors first, since they are fatal but have offset info
	if serr, ok := err.(*json.SyntaxError); ok {
		line, col, highlight := errorutil.HighlightBytePosition(bytes.NewReader(rawConfig), serr.Offset)
		return types.Config{},
			report.Report{
				Entries: []report.Entry{{
					Kind:      report.EntryError,
					Message:   serr.Error(),
					Line:      line,
					Column:    col,
					Highlight: highlight,
				}},
			},
			ErrInvalid
	}

	if terr, ok := err.(*json.UnmarshalTypeError); ok {
		line, col, highlight := errorutil.HighlightBytePosition(bytes.NewReader(rawConfig), terr.Offset)
		return types.Config{},
			report.Report{
				Entries: []report.Entry{{
					Kind:      report.EntryError,
					Message:   terr.Error(),
					Line:      line,
					Column:    col,
					Highlight: highlight,
				}},
			},
			ErrInvalid
	}

	// Handle other fatal errors (i.e. invalid version)
	if err != nil {
		return types.Config{}, report.ReportFromError(err, report.EntryError), err
	}

	// Unmarshal again to a json.Node to get offset information for building a report
	var ast json.Node
	var r report.Report
	configValue := reflect.ValueOf(config)
	if err := json.Unmarshal(rawConfig, &ast); err != nil {
		r.Add(report.Entry{
			Kind:    report.EntryWarning,
			Message: "Ignition could not unmarshal your config for reporting line numbers. This should never happen. Please file a bug.",
		})
		r.Merge(validate.ValidateWithoutSource(configValue))
	} else {
		r.Merge(validate.Validate(configValue, astjson.FromJsonRoot(ast), bytes.NewReader(rawConfig), true))
	}

	if r.IsFatal() {
		return types.Config{}, r, ErrInvalid
	}

	return config, r, nil
}

func ParseFromV1(rawConfig []byte) (types.Config, report.Report, error) {
	config, err := v1.Parse(rawConfig)
	if err != nil {
		return types.Config{}, report.Report{}, err
	}

	convertedConfig := TranslateFromV1(config)

	rpt := validate.ValidateWithoutSource(reflect.ValueOf(convertedConfig))
	if rpt.IsFatal() {
		return types.Config{}, rpt, ErrInvalid
	}
	return convertedConfig, rpt, nil

}

func ParseFromV2_0(rawConfig []byte) (types.Config, report.Report, error) {
	cfg, report, err := v2_0.Parse(rawConfig)
	if err != nil {
		return types.Config{}, report, err
	}

	return TranslateFromV2_0(cfg), report, err
}

func ParseFromV2_1(rawConfig []byte) (types.Config, report.Report, error) {
	cfg, report, err := v2_1.Parse(rawConfig)
	if err != nil {
		return types.Config{}, report, err
	}

	return TranslateFromV2_1(cfg), report, err
}

func Version(rawConfig []byte) (semver.Version, error) {
	var composite struct {
		Version  *int `json:"ignitionVersion"`
		Ignition struct {
			Version *string `json:"version"`
		} `json:"ignition"`
	}

	if json.Unmarshal(rawConfig, &composite) == nil {
		if composite.Ignition.Version != nil {
			v, err := types.Ignition{Version: *composite.Ignition.Version}.Semver()
			if err != nil {
				return semver.Version{}, err
			}
			return *v, nil
		} else if composite.Version != nil {
			return semver.Version{Major: int64(*composite.Version)}, nil
		}
	}

	return semver.Version{}, ErrVersionIndeterminable
}

func isEmpty(userdata []byte) bool {
	return len(userdata) == 0
}
<|endoftext|>


<commit_before>package config

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	yaml "gopkg.in/yaml.v2"

	"github.com/fatih/set"
	zglob "github.com/mattn/go-zglob"
	"github.com/mohae/deepcopy"
	"github.com/synchro-food/filelint/lib"
)

type Config struct {
	File    File     `yaml:"files"`
	Targets []Target `yaml:"targets"`
}

func NewConfig(configFile string) (*Config, error) {
	conf, err := NewDefaultConfig()
	if err != nil {
		return nil, err
	}

	userConfig := &Config{}
	src, err := ioutil.ReadFile(configFile)
	if err != nil {
		return nil, err
	}

	if err := yaml.Unmarshal(src, &userConfig); err != nil {
		return nil, err
	}

	conf.Merge(userConfig)

	return conf, nil
}

func NewDefaultConfig() (*Config, error) {
	conf := &Config{}
	src, err := configDefaultYmlBytes()
	if err != nil {
		return nil, err
	}

	if err := yaml.Unmarshal(src, &conf); err != nil {
		return nil, err
	}

	return conf, nil
}

func (src *Config) Merge(dst *Config) {
	if len(dst.File.Include) > 0 {
		src.File.Include = dst.File.Include
	}
	src.File.Exclude = append(src.File.Exclude, dst.File.Exclude...)
	src.Targets = append(src.Targets, dst.Targets...)
}

func (cfg *Config) MatchedRule(file string) RuleMap {
	rm := make(RuleMap)

	for _, t := range cfg.Targets {
		if match(file, t.Patterns) {
			rm = rm.Merge(t.Rule)
		}
	}

	return rm
}

func match(file string, patterns []string) bool {
	if len(patterns) == 0 {
		return true
	}

	for _, pattern := range patterns {
		ok, err := zglob.Match(pattern, file)
		if err != nil {
			return false
		}
		if ok {
			return true
		}
	}

	return false
}

type File struct {
	Include []string `yaml:"include"`
	Exclude []string `yaml:"exclude"`
}

func (f File) FindTargets() ([]string, error) {
	f.addGlobSignIfDir()

	inSet, err := expandGlob(f.Include)
	if err != nil {
		return nil, err
	}

	exSet, _ := expandGlob(f.Exclude)
	if err != nil {
		return nil, err
	}

	fileSet := set.Difference(inSet, exSet)
	files := set.StringSlice(fileSet)
	files = lib.FindTextFiles(files)

	return files, nil
}

func (f File) addGlobSignIfDir() {
	f.Include = addGlobSignIfDir(f.Include...)
	f.Exclude = addGlobSignIfDir(f.Exclude...)
}

func expandGlob(files []string) (*set.Set, error) {
	fileSet := set.New()

	for _, f := range files {
		expanded, err := zglob.Glob(f)
		if err != nil {
			return nil, err
		}

		for _, ex := range expanded {
			fileSet.Add(ex)
		}
	}

	return fileSet, nil
}

func addGlobSignIfDir(files ...string) (dst []string) {
	dst = make([]string, 0, len(files))

	for _, f := range files {
		if !strings.Contains(f, "*") {
			info, err := os.Stat(f)
			if err != nil {
				continue
			}

			if info.IsDir() {
				f = filepath.Join(f, "**", "*")
			}
		}

		dst = append(dst, f)
	}

	return dst
}

type Target struct {
	Patterns []string `yaml:"patterns"`
	Rule     RuleMap  `yaml:"rules"`
}

type RuleMap map[string]map[string]interface{}

func (rm RuleMap) Merge(dst RuleMap) (ret RuleMap) {
	ret = deepcopy.Copy(rm).(RuleMap)

	for ruleName, options := range dst {
		if _, ok := ret[ruleName]; ok {
			for optionKey, value := range options {
				ret[ruleName][optionKey] = value
			}
		} else {
			ret[ruleName] = options
		}
	}

	return ret
}

var (
	fileName   = ".filelint.yml"
	searchPath = "."
)

func SearchConfigFile() (f string, ok bool) {
	if f := filepath.Join(searchPath, fileName); lib.IsExist(f) {
		return f, true
	}

	if gitRoot, err := lib.FindGitRootPath(searchPath); err != nil {
		if err != nil && err != lib.ErrNotGitRepository {
			return "", false
		}
		if f := filepath.Join(gitRoot, fileName); lib.IsExist(f) {
			return f, true
		}
	}

	if f := filepath.Join(lib.GetHomeDir(), fileName); lib.IsExist(f) {
		return f, true
	}

	return "", false
}
<commit_msg>Fix: File#FindTargets() don't extract files in directories<commit_after>package config

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	yaml "gopkg.in/yaml.v2"

	"github.com/fatih/set"
	zglob "github.com/mattn/go-zglob"
	"github.com/mohae/deepcopy"
	"github.com/synchro-food/filelint/lib"
)

type Config struct {
	File    File     `yaml:"files"`
	Targets []Target `yaml:"targets"`
}

func NewConfig(configFile string) (*Config, error) {
	conf, err := NewDefaultConfig()
	if err != nil {
		return nil, err
	}

	userConfig := &Config{}
	src, err := ioutil.ReadFile(configFile)
	if err != nil {
		return nil, err
	}

	if err := yaml.Unmarshal(src, &userConfig); err != nil {
		return nil, err
	}

	conf.Merge(userConfig)

	return conf, nil
}

func NewDefaultConfig() (*Config, error) {
	conf := &Config{}
	src, err := configDefaultYmlBytes()
	if err != nil {
		return nil, err
	}

	if err := yaml.Unmarshal(src, &conf); err != nil {
		return nil, err
	}

	return conf, nil
}

func (src *Config) Merge(dst *Config) {
	if len(dst.File.Include) > 0 {
		src.File.Include = dst.File.Include
	}
	src.File.Exclude = append(src.File.Exclude, dst.File.Exclude...)
	src.Targets = append(src.Targets, dst.Targets...)
}

func (cfg *Config) MatchedRule(file string) RuleMap {
	rm := make(RuleMap)

	for _, t := range cfg.Targets {
		if match(file, t.Patterns) {
			rm = rm.Merge(t.Rule)
		}
	}

	return rm
}

func match(file string, patterns []string) bool {
	if len(patterns) == 0 {
		return true
	}

	for _, pattern := range patterns {
		ok, err := zglob.Match(pattern, file)
		if err != nil {
			return false
		}
		if ok {
			return true
		}
	}

	return false
}

type File struct {
	Include []string `yaml:"include"`
	Exclude []string `yaml:"exclude"`
}

func (f File) FindTargets() ([]string, error) {
	f.Include = addGlobSignIfDir(f.Include...)
	f.Exclude = addGlobSignIfDir(f.Exclude...)

	inSet, err := expandGlob(f.Include)
	if err != nil {
		return nil, err
	}

	exSet, _ := expandGlob(f.Exclude)
	if err != nil {
		return nil, err
	}

	fileSet := set.Difference(inSet, exSet)
	files := set.StringSlice(fileSet)
	files = lib.FindTextFiles(files)

	return files, nil
}

func expandGlob(files []string) (*set.Set, error) {
	fileSet := set.New()

	for _, f := range files {
		expanded, err := zglob.Glob(f)
		if err != nil {
			return nil, err
		}

		for _, ex := range expanded {
			fileSet.Add(ex)
		}
	}

	return fileSet, nil
}

func addGlobSignIfDir(files ...string) (dst []string) {
	dst = make([]string, 0, len(files))

	for _, f := range files {
		if !strings.Contains(f, "*") {
			info, err := os.Stat(f)
			if err != nil {
				continue
			}

			if info.IsDir() {
				f = filepath.Join(f, "**", "*")
			}
		}

		dst = append(dst, f)
	}

	return dst
}

type Target struct {
	Patterns []string `yaml:"patterns"`
	Rule     RuleMap  `yaml:"rules"`
}

type RuleMap map[string]map[string]interface{}

func (rm RuleMap) Merge(dst RuleMap) (ret RuleMap) {
	ret = deepcopy.Copy(rm).(RuleMap)

	for ruleName, options := range dst {
		if _, ok := ret[ruleName]; ok {
			for optionKey, value := range options {
				ret[ruleName][optionKey] = value
			}
		} else {
			ret[ruleName] = options
		}
	}

	return ret
}

var (
	fileName   = ".filelint.yml"
	searchPath = "."
)

func SearchConfigFile() (f string, ok bool) {
	if f := filepath.Join(searchPath, fileName); lib.IsExist(f) {
		return f, true
	}

	if gitRoot, err := lib.FindGitRootPath(searchPath); err != nil {
		if err != nil && err != lib.ErrNotGitRepository {
			return "", false
		}
		if f := filepath.Join(gitRoot, fileName); lib.IsExist(f) {
			return f, true
		}
	}

	if f := filepath.Join(lib.GetHomeDir(), fileName); lib.IsExist(f) {
		return f, true
	}

	return "", false
}
<|endoftext|>


<commit_before>package config

import (
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"errors"
	"flag"
	"github.com/Supernomad/quantum/ecdh"
	"github.com/go-playground/log"
	"gopkg.in/yaml.v2"
	"io/ioutil"
	"os"
	"path"
	"strconv"
	"strings"
	"time"
)

// Config handles marshalling user supplied configuration data
type Config struct {
	InterfaceName string
	MachineID     string

	PrivateIP string
	PublicIP  string

	PublicAddress string

	PrivateKey []byte
	PublicKey  []byte

	ListenAddress string
	ListenPort    int

	Prefix   string
	ConfFile string
	DataDir  string
	LogDir   string

	SyncInterval    time.Duration
	RefreshInterval time.Duration

	TLSEnabled bool
	TLSCert    string
	TLSKey     string
	TLSCA      string

	Datastore   string
	endpoints   string
	Endpoints   []string
	AuthEnabled bool
	Username    string
	Password    string

	fileData map[string]string
}

func (cfg *Config) handleDefaultString(name, def string) string {
	env := "QUANTUM_" + strings.ToUpper(strings.Replace(name, "-", "_", 10))
	output := os.Getenv(env)
	if output == "" {
		if output, ok := cfg.fileData[name]; ok {
			return output
		}
		return def
	}
	return output
}

func (cfg *Config) handleDefaultInt(name string, def int) int {
	str := strconv.Itoa(def)
	output, err := strconv.Atoi(cfg.handleDefaultString(name, str))
	if err != nil {
		panic(err)
	}
	return output
}

func (cfg *Config) handleDefaultDuration(name string, def time.Duration) time.Duration {
	str := def.String()
	output, err := time.ParseDuration(cfg.handleDefaultString(name, str))
	if err != nil {
		panic(err)
	}
	return output
}

func (cfg *Config) handleCli() error {
	flag.StringVar(&cfg.ConfFile, "conf-file", cfg.handleDefaultString("conf-file", ""), "The json or yaml file to load configuration data from.")
	flag.Parse()

	err := cfg.handleFile()
	if err != nil {
		return err
	}

	flag.StringVar(&cfg.InterfaceName, "interface-name", cfg.handleDefaultString("interface-name", "quantum%d"), "The name for the TUN interface that will be used for forwarding. Use %d to have the OS pick an available interface name.")

	flag.StringVar(&cfg.PrivateIP, "private-ip", cfg.handleDefaultString("private-ip", ""), "The private ip address of this node.")
	flag.StringVar(&cfg.PublicIP, "public-ip", cfg.handleDefaultString("public-ip", ""), "The public ip address of this node.")

	flag.StringVar(&cfg.ListenAddress, "listen-address", cfg.handleDefaultString("listen-address", "0.0.0.0"), "The ip address to listen on for forwarded packets.")
	flag.IntVar(&cfg.ListenPort, "listen-port", cfg.handleDefaultInt("listen-port", 1099), "The ip port to listen on for forwarded packets.")

	flag.StringVar(&cfg.Prefix, "prefix", cfg.handleDefaultString("prefix", "quantum"), "The etcd key that quantum information is stored under.")

	flag.StringVar(&cfg.DataDir, "data-dir", cfg.handleDefaultString("data-dir", "/var/lib/quantum"), "The data directory for quantum to use for persistent state.")
	flag.StringVar(&cfg.LogDir, "log-dir", cfg.handleDefaultString("log-dir", ""), "The log directory to write logs to, if this is ommited logs are written to stdout/stderr.")

	flag.DurationVar(&cfg.SyncInterval, "sync-interval", cfg.handleDefaultDuration("sync-interval", 30), "The backend sync interval")
	flag.DurationVar(&cfg.RefreshInterval, "refresh-interval", cfg.handleDefaultDuration("refresh-interval", 60), "The backend lease refresh interval.")

	flag.StringVar(&cfg.TLSCert, "tls-cert", cfg.handleDefaultString("tls-cert", ""), "The client certificate to use for authentication with the backend datastore.")
	flag.StringVar(&cfg.TLSKey, "tls-key", cfg.handleDefaultString("tls-key", ""), "The client key to use for authentication with the backend datastore.")
	flag.StringVar(&cfg.TLSCA, "tls-ca-cert", cfg.handleDefaultString("tls-ca-cert", ""), "The CA certificate to authenticate the backend datastore.")

	flag.StringVar(&cfg.Datastore, "datastore", cfg.handleDefaultString("datastore", "etcd"), "The datastore backend to use, either consul or etcd")

	flag.StringVar(&cfg.endpoints, "endpoints", cfg.handleDefaultString("endpoints", "127.0.0.1:2379"), "A comma delimited list of datastore endpoints to use.")
	flag.StringVar(&cfg.Username, "username", cfg.handleDefaultString("username", ""), "The datastore username to use for authentication.")
	flag.StringVar(&cfg.Password, "password", cfg.handleDefaultString("password", ""), "The datastore password to use for authentication.")

	flag.Parse()
	return nil
}

func (cfg *Config) handleComputed() {
	cfg.Endpoints = strings.Split(cfg.endpoints, ",")

	pubkey, privkey := ecdh.GenerateECKeyPair()
	cfg.PublicKey = pubkey
	cfg.PrivateKey = privkey

	if (cfg.TLSCert != "" && cfg.TLSKey != "") || cfg.TLSCA != "" {
		cfg.TLSEnabled = true
	}

	if cfg.Username != "" {
		cfg.AuthEnabled = true
	}

	os.MkdirAll(cfg.DataDir, os.ModeDir)
	machineID := make([]byte, 32)
	machineIDPath := path.Join(cfg.DataDir, "machine-id")
	if _, err := os.Stat(machineIDPath); os.IsNotExist(err) {
		rand.Read(machineID)
		ioutil.WriteFile(machineIDPath, machineID, os.ModePerm)
	} else {
		buf, _ := ioutil.ReadFile(machineIDPath)
		machineID = buf
	}
	cfg.MachineID = hex.EncodeToString(machineID)

	cfg.PublicAddress = cfg.PublicIP + ":" + strconv.Itoa(cfg.ListenPort)
}

func (cfg *Config) handleFile() error {
	if cfg.ConfFile != "" {
		buf, err := ioutil.ReadFile(cfg.ConfFile)
		if err != nil {
			return err
		}

		data := make(map[string]string)
		ext := path.Ext(cfg.ConfFile)
		switch ext {
		case ".json":
			err = json.Unmarshal(buf, &data)
		case ".yaml":
			err = yaml.Unmarshal(buf, &data)
		case ".yml":
			err = yaml.Unmarshal(buf, &data)
		default:
			return errors.New("The configuration file is not in a supported format.")
		}

		cfg.fileData = data
	} else {
		cfg.fileData = make(map[string]string)
	}

	return nil
}

// New generates a new config object
func New() (*Config, error) {
	cfg := &Config{}
	err := cfg.handleCli()
	if err != nil {
		log.Error("Error handling config:", err)
		return nil, err
	}
	cfg.handleComputed()
	return cfg, nil
}
<commit_msg>Fixes for the config errors<commit_after>package config

import (
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"errors"
	"flag"
	"github.com/Supernomad/quantum/ecdh"
	"github.com/go-playground/log"
	"gopkg.in/yaml.v2"
	"io/ioutil"
	"os"
	"path"
	"strconv"
	"strings"
	"time"
)

// Config handles marshalling user supplied configuration data
type Config struct {
	InterfaceName string
	MachineID     string

	PrivateIP string
	PublicIP  string

	PublicAddress string

	PrivateKey []byte
	PublicKey  []byte

	ListenAddress string
	ListenPort    int

	Prefix   string
	ConfFile string
	DataDir  string
	LogDir   string

	SyncInterval    time.Duration
	RefreshInterval time.Duration

	TLSEnabled bool
	TLSCert    string
	TLSKey     string
	TLSCA      string

	Datastore   string
	endpoints   string
	Endpoints   []string
	AuthEnabled bool
	Username    string
	Password    string

	fileData map[string]string
}

func (cfg *Config) handleDefaultString(name, def string) string {
	env := "QUANTUM_" + strings.ToUpper(strings.Replace(name, "-", "_", 10))
	output := os.Getenv(env)
	if output == "" {
		return def
	}
	return output
}

func (cfg *Config) handleDefaultInt(name string, def int) int {
	str := strconv.Itoa(def)
	output, err := strconv.Atoi(cfg.handleDefaultString(name, str))
	if err != nil {
		panic(err)
	}
	return output
}

func (cfg *Config) handleDefaultDuration(name string, def time.Duration) time.Duration {
	str := def.String()
	output, err := time.ParseDuration(cfg.handleDefaultString(name, str))
	if err != nil {
		panic(err)
	}
	return output
}

func (cfg *Config) handleCli() {
	flag.StringVar(&cfg.ConfFile, "conf-file", cfg.handleDefaultString("conf-file", ""), "The json or yaml file to load configuration data from.")

	flag.StringVar(&cfg.InterfaceName, "interface-name", cfg.handleDefaultString("interface-name", "quantum%d"), "The name for the TUN interface that will be used for forwarding. Use %d to have the OS pick an available interface name.")

	flag.StringVar(&cfg.PrivateIP, "private-ip", cfg.handleDefaultString("private-ip", ""), "The private ip address of this node.")
	flag.StringVar(&cfg.PublicIP, "public-ip", cfg.handleDefaultString("public-ip", ""), "The public ip address of this node.")

	flag.StringVar(&cfg.ListenAddress, "listen-address", cfg.handleDefaultString("listen-address", "0.0.0.0"), "The ip address to listen on for forwarded packets.")
	flag.IntVar(&cfg.ListenPort, "listen-port", cfg.handleDefaultInt("listen-port", 1099), "The ip port to listen on for forwarded packets.")

	flag.StringVar(&cfg.Prefix, "prefix", cfg.handleDefaultString("prefix", "quantum"), "The etcd key that quantum information is stored under.")
	flag.StringVar(&cfg.DataDir, "data-dir", cfg.handleDefaultString("data-dir", "/var/lib/quantum"), "The data directory for quantum to use for persistent state.")
	flag.StringVar(&cfg.LogDir, "log-dir", cfg.handleDefaultString("log-dir", ""), "The log directory to write logs to, if this is ommited logs are written to stdout/stderr.")

	flag.DurationVar(&cfg.SyncInterval, "sync-interval", cfg.handleDefaultDuration("sync-interval", 30), "The backend sync interval")
	flag.DurationVar(&cfg.RefreshInterval, "refresh-interval", cfg.handleDefaultDuration("refresh-interval", 60), "The backend lease refresh interval.")

	flag.StringVar(&cfg.TLSCert, "tls-cert", cfg.handleDefaultString("tls-cert", ""), "The client certificate to use for authentication with the backend datastore.")
	flag.StringVar(&cfg.TLSKey, "tls-key", cfg.handleDefaultString("tls-key", ""), "The client key to use for authentication with the backend datastore.")
	flag.StringVar(&cfg.TLSCA, "tls-ca-cert", cfg.handleDefaultString("tls-ca-cert", ""), "The CA certificate to authenticate the backend datastore.")

	flag.StringVar(&cfg.Datastore, "datastore", cfg.handleDefaultString("datastore", "etcd"), "The datastore backend to use, either consul or etcd")
	flag.StringVar(&cfg.endpoints, "endpoints", cfg.handleDefaultString("endpoints", "127.0.0.1:2379"), "A comma delimited list of datastore endpoints to use.")
	flag.StringVar(&cfg.Username, "username", cfg.handleDefaultString("username", ""), "The datastore username to use for authentication.")
	flag.StringVar(&cfg.Password, "password", cfg.handleDefaultString("password", ""), "The datastore password to use for authentication.")

	flag.Parse()
}

func (cfg *Config) handleComputed() {
	cfg.Endpoints = strings.Split(cfg.endpoints, ",")

	pubkey, privkey := ecdh.GenerateECKeyPair()
	cfg.PublicKey = pubkey
	cfg.PrivateKey = privkey

	if (cfg.TLSCert != "" && cfg.TLSKey != "") || cfg.TLSCA != "" {
		cfg.TLSEnabled = true
	}

	if cfg.Username != "" {
		cfg.AuthEnabled = true
	}

	os.MkdirAll(cfg.DataDir, os.ModeDir)
	machineID := make([]byte, 32)
	machineIDPath := path.Join(cfg.DataDir, "machine-id")
	if _, err := os.Stat(machineIDPath); os.IsNotExist(err) {
		rand.Read(machineID)
		ioutil.WriteFile(machineIDPath, machineID, os.ModePerm)
	} else {
		buf, _ := ioutil.ReadFile(machineIDPath)
		machineID = buf
	}
	cfg.MachineID = hex.EncodeToString(machineID)

	cfg.PublicAddress = cfg.PublicIP + ":" + strconv.Itoa(cfg.ListenPort)
}

func (cfg *Config) parseFileData(data map[string]string) error {
	for k, v := range data {
		switch k {
		case "conf-file":
			cfg.ConfFile = v
		case "interface-name":
			cfg.InterfaceName = v
		case "private-ip":
			cfg.PrivateIP = v
		case "public-ip":
			cfg.PublicIP = v
		case "listen-address":
			cfg.ListenAddress = v
		case "listen-port":
			i, err := strconv.Atoi(v)
			if err != nil {
				return err
			}
			cfg.ListenPort = i
		case "prefix":
			cfg.Prefix = v
		case "data-dir":
			cfg.DataDir = v
		case "log-dir":
			cfg.LogDir = v
		case "sync-interval":
			dur, err := time.ParseDuration(v)
			if err != nil {
				return err
			}
			cfg.SyncInterval = dur
		case "refresh-interval":
			dur, err := time.ParseDuration(v)
			if err != nil {
				return err
			}
			cfg.RefreshInterval = dur
		case "tls-cert":
			cfg.TLSCert = v
		case "tls-key":
			cfg.TLSKey = v
		case "tls-ca-cert":
			cfg.TLSCA = v
		case "datastore":
			cfg.Datastore = v
		case "endpoints":
			cfg.endpoints = v
		case "username":
			cfg.Username = v
		case "password":
			cfg.Password = v
		}
	}
	return nil
}

func (cfg *Config) handleFile() error {
	if cfg.ConfFile != "" {
		buf, err := ioutil.ReadFile(cfg.ConfFile)
		if err != nil {
			return err
		}

		data := make(map[string]string)
		ext := path.Ext(cfg.ConfFile)
		switch {
		case ".json" == ext:
			err = json.Unmarshal(buf, &data)
		case ".yaml" == ext || ".yml" == ext:
			err = yaml.Unmarshal(buf, &data)
		default:
			return errors.New("The configuration file is not in a supported format.")
		}

		return cfg.parseFileData(data)
	}
	return nil
}

// New generates a new config object
func New() (*Config, error) {
	cfg := &Config{}
	cfg.handleCli()
	err := cfg.handleFile()
	if err != nil {
		log.Error("Error handling config:", err)
		return nil, err
	}
	cfg.handleComputed()
	return cfg, nil
}
<|endoftext|>


<commit_before>package config

import (
	"encoding/json"
	"io/ioutil"
	"os"
	"regexp"
	"strconv"
	"strings"

	log "github.com/Sirupsen/logrus"
	"github.com/rancherio/os/util"
)

type InitFunc func(*Config) error

type ContainerConfig struct {
	Id  string   `json:"id,omitempty"`
	Cmd []string `json:"run,omitempty"`
	//Config     *runconfig.Config     `json:"-"`
	//HostConfig *runconfig.HostConfig `json:"-"`
}

type Config struct {
	//BootstrapContainers []ContainerConfig `json:"bootstrapContainers,omitempty"`
	ConsoleContainer string            `json:"consoleContainer,omitempty"`
	Debug            bool              `json:"debug,omitempty"`
	Disable          []string          `json:"disable,omitempty"`
	DockerEndpoint   string            `json:"dockerEndpoint,omitempty"`
	Dns              []string          `json:"dns,omitempty"`
	ImagesPath       string            `json:"ImagesPath,omitempty"`
	ImagesPattern    string            `json:"ImagesPattern,omitempty"`
	ModulesArchive   string            `json:"modulesArchive,omitempty"`
	Rescue           bool              `json:"rescue,omitempty"`
	RescueContainer  ContainerConfig   `json:"rescueContainer,omitempty"`
	StateDevFSType   string            `json:"stateDeviceFsType,omitempty"`
	StateDev         string            `json:"stateDevice,omitempty"`
	StateRequired    bool              `json:"stateRequired,omitempty"`
	SysInit          string            `json:"sysInit,omitempty"`
	SystemContainers []ContainerConfig `json:"systemContainers,omitempty"`
	SystemDockerArgs []string          `json:"systemDockerArgs,omitempty"`
	UserContainers   []ContainerConfig `json:"userContainser,omitempty"`
	UserInit         string            `json:"userInit,omitempty"`
	Modules          []string          `json:"modules,omitempty"`
	Respawn          []string          `json:"respawn,omitempty"`
}

func (c *Config) Dump() string {
	content, err := json.MarshalIndent(c, "", "  ")
	if err == nil {
		return string(content)
	} else {
		return err.Error()
	}
}

func LoadConfig() (*Config, error) {
	cfg := NewConfig()
	if err := cfg.Reload(); err != nil {
		return nil, err
	}

	if cfg.Debug {
		log.SetLevel(log.DebugLevel)
	}

	return cfg, nil
}

func NewConfig() *Config {
	return &Config{
		ConsoleContainer: "console",
		Debug:            true,
		DockerEndpoint:   "unix:/var/run/docker.sock",
		Dns: []string{
			"8.8.8.8",
			"8.8.4.4",
		},
		ImagesPath:       "/",
		ImagesPattern:    "images*.tar",
		StateRequired:    false,
		StateDev:         "/dev/sda",
		StateDevFSType:   "ext4",
		SysInit:          "/sbin/init-sys",
		SystemDockerArgs: []string{"docker", "-d", "-s", "overlay", "-b", "none"},
		UserInit:         "/sbin/init-user",
		Modules:          []string{},
		ModulesArchive:   "/modules.tar",
		SystemContainers: []ContainerConfig{
			{
				Cmd: []string{
					"--name", "system-state",
					"--net", "none",
					"--read-only",
					"state",
				},
			},
			{
				Cmd: []string{
					"--name", "udev",
					"--net", "none",
					"--privileged",
					"--rm",
					"--volume", "/dev:/host/dev",
					"--volume", "/lib/modules:/lib/modules:ro",
					"udev",
				},
			},
			{
				Cmd: []string{
					"--name", "network",
					"--cap-add", "NET_ADMIN",
					"--net", "host",
					"--rm",
					"network",
				},
			},
			{
				Cmd: []string{
					"--name", "userdocker",
					"-d",
					"--restart", "always",
					"--pid", "host",
					"--net", "host",
					"--privileged",
					"--volume", "/lib/modules:/lib/modules:ro",
					"--volume", "/usr/bin/docker:/usr/bin/docker:ro",
					"--volumes-from", "system-state",
					"userdocker",
				},
			},
			{
				Cmd: []string{
					"--name", "console",
					"-d",
					"--rm",
					"--privileged",
					//"--volume", "/:/host:ro",
					"--volume", "/lib/modules:/lib/modules:ro",
					"--volume", "/usr/bin/docker:/usr/bin/docker:ro",
					"--volume", "/init:/usr/bin/system-docker:ro",
					"--volume", "/init:/usr/bin/respawn:ro",
					"--volume", "/var/run/docker.sock:/var/run/system-docker.sock:ro",
					"--volumes-from", "system-state",
					"--net", "host",
					"--pid", "host",
					"console",
				},
			},
		},
		RescueContainer: ContainerConfig{
			Cmd: []string{
				"--name", "rescue",
				"-d",
				"--rm",
				"--privileged",
				//"--volume", "/:/host",
				"--volume", "/lib/modules:/lib/modules:ro",
				"--volume", "/usr/bin/docker:/usr/bin/docker:ro",
				"--volume", "/init:/usr/bin/system-docker:ro",
				"--volume", "/init:/usr/bin/respawn:ro",
				"--volume", "/var/run/docker.sock:/var/run/system-docker.sock:ro",
				"--net", "host",
				"--pid", "host",
				"rescue",
			},
		},
	}
}

func (c *Config) readArgs() error {
	log.Debug("Reading config args")
	cmdLine := strings.Join(os.Args[1:], " ")
	if len(cmdLine) == 0 {
		return nil
	}

	log.Debugf("Config Args %s", cmdLine)

	cmdLineObj := parseCmdline(strings.TrimSpace(cmdLine))

	return c.merge(cmdLineObj)
}

func (c *Config) merge(values map[string]interface{}) error {
	// Lazy way to assign values to *Config
	override, err := json.Marshal(values)
	if err != nil {
		return err
	}
	return json.Unmarshal(override, c)
}

func (c *Config) readCmdline() error {
	log.Debug("Reading config cmdline")
	cmdLine, err := ioutil.ReadFile("/proc/cmdline")
	if err != nil {
		return err
	}

	if len(cmdLine) == 0 {
		return nil
	}

	log.Debugf("Config cmdline %s", cmdLine)

	cmdLineObj := parseCmdline(strings.TrimSpace(string(cmdLine)))
	return c.merge(cmdLineObj)
}

func dummyMarshall(value string) interface{} {
	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[string]interface{} {
	result := make(map[string]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 {
				if strings.HasPrefix(value, "[") && strings.HasSuffix(value, "]") {
					current[key] = strings.Split(value[1:len(value)-1], ",")
				} else {
					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) Reload() error {
	return util.ShortCircuit(
		c.readCmdline,
		c.readArgs,
	)
}

func (c *Config) GetContainerById(id string) *ContainerConfig {
	for _, c := range c.SystemContainers {
		if c.Id == id {
			return &c
		}
	}

	return nil
}

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
}
<commit_msg>Fix typo in configuration name<commit_after>package config

import (
	"encoding/json"
	"io/ioutil"
	"os"
	"regexp"
	"strconv"
	"strings"

	log "github.com/Sirupsen/logrus"
	"github.com/rancherio/os/util"
)

type InitFunc func(*Config) error

type ContainerConfig struct {
	Id  string   `json:"id,omitempty"`
	Cmd []string `json:"run,omitempty"`
	//Config     *runconfig.Config     `json:"-"`
	//HostConfig *runconfig.HostConfig `json:"-"`
}

type Config struct {
	//BootstrapContainers []ContainerConfig `json:"bootstrapContainers,omitempty"`
	ConsoleContainer string            `json:"consoleContainer,omitempty"`
	Debug            bool              `json:"debug,omitempty"`
	Disable          []string          `json:"disable,omitempty"`
	DockerEndpoint   string            `json:"dockerEndpoint,omitempty"`
	Dns              []string          `json:"dns,omitempty"`
	ImagesPath       string            `json:"ImagesPath,omitempty"`
	ImagesPattern    string            `json:"ImagesPattern,omitempty"`
	ModulesArchive   string            `json:"modulesArchive,omitempty"`
	Rescue           bool              `json:"rescue,omitempty"`
	RescueContainer  ContainerConfig   `json:"rescueContainer,omitempty"`
	StateDevFSType   string            `json:"stateDevFsType,omitempty"`
	StateDev         string            `json:"stateDev,omitempty"`
	StateRequired    bool              `json:"stateRequired,omitempty"`
	SysInit          string            `json:"sysInit,omitempty"`
	SystemContainers []ContainerConfig `json:"systemContainers,omitempty"`
	SystemDockerArgs []string          `json:"systemDockerArgs,omitempty"`
	UserContainers   []ContainerConfig `json:"userContainser,omitempty"`
	UserInit         string            `json:"userInit,omitempty"`
	Modules          []string          `json:"modules,omitempty"`
	Respawn          []string          `json:"respawn,omitempty"`
}

func (c *Config) Dump() string {
	content, err := json.MarshalIndent(c, "", "  ")
	if err == nil {
		return string(content)
	} else {
		return err.Error()
	}
}

func LoadConfig() (*Config, error) {
	cfg := NewConfig()
	if err := cfg.Reload(); err != nil {
		return nil, err
	}

	if cfg.Debug {
		log.SetLevel(log.DebugLevel)
	}

	return cfg, nil
}

func NewConfig() *Config {
	return &Config{
		ConsoleContainer: "console",
		Debug:            true,
		DockerEndpoint:   "unix:/var/run/docker.sock",
		Dns: []string{
			"8.8.8.8",
			"8.8.4.4",
		},
		ImagesPath:       "/",
		ImagesPattern:    "images*.tar",
		StateRequired:    false,
		StateDev:         "/dev/sda",
		StateDevFSType:   "ext4",
		SysInit:          "/sbin/init-sys",
		SystemDockerArgs: []string{"docker", "-d", "-s", "overlay", "-b", "none"},
		UserInit:         "/sbin/init-user",
		Modules:          []string{},
		ModulesArchive:   "/modules.tar",
		SystemContainers: []ContainerConfig{
			{
				Cmd: []string{
					"--name", "system-state",
					"--net", "none",
					"--read-only",
					"state",
				},
			},
			{
				Cmd: []string{
					"--name", "udev",
					"--net", "none",
					"--privileged",
					"--rm",
					"--volume", "/dev:/host/dev",
					"--volume", "/lib/modules:/lib/modules:ro",
					"udev",
				},
			},
			{
				Cmd: []string{
					"--name", "network",
					"--cap-add", "NET_ADMIN",
					"--net", "host",
					"--rm",
					"network",
				},
			},
			{
				Cmd: []string{
					"--name", "userdocker",
					"-d",
					"--restart", "always",
					"--pid", "host",
					"--net", "host",
					"--privileged",
					"--volume", "/lib/modules:/lib/modules:ro",
					"--volume", "/usr/bin/docker:/usr/bin/docker:ro",
					"--volumes-from", "system-state",
					"userdocker",
				},
			},
			{
				Cmd: []string{
					"--name", "console",
					"-d",
					"--rm",
					"--privileged",
					//"--volume", "/:/host:ro",
					"--volume", "/lib/modules:/lib/modules:ro",
					"--volume", "/usr/bin/docker:/usr/bin/docker:ro",
					"--volume", "/init:/usr/bin/system-docker:ro",
					"--volume", "/init:/usr/bin/respawn:ro",
					"--volume", "/var/run/docker.sock:/var/run/system-docker.sock:ro",
					"--volumes-from", "system-state",
					"--net", "host",
					"--pid", "host",
					"console",
				},
			},
		},
		RescueContainer: ContainerConfig{
			Cmd: []string{
				"--name", "rescue",
				"-d",
				"--rm",
				"--privileged",
				//"--volume", "/:/host",
				"--volume", "/lib/modules:/lib/modules:ro",
				"--volume", "/usr/bin/docker:/usr/bin/docker:ro",
				"--volume", "/init:/usr/bin/system-docker:ro",
				"--volume", "/init:/usr/bin/respawn:ro",
				"--volume", "/var/run/docker.sock:/var/run/system-docker.sock:ro",
				"--net", "host",
				"--pid", "host",
				"rescue",
			},
		},
	}
}

func (c *Config) readArgs() error {
	log.Debug("Reading config args")
	cmdLine := strings.Join(os.Args[1:], " ")
	if len(cmdLine) == 0 {
		return nil
	}

	log.Debugf("Config Args %s", cmdLine)

	cmdLineObj := parseCmdline(strings.TrimSpace(cmdLine))

	return c.merge(cmdLineObj)
}

func (c *Config) merge(values map[string]interface{}) error {
	// Lazy way to assign values to *Config
	override, err := json.Marshal(values)
	if err != nil {
		return err
	}
	return json.Unmarshal(override, c)
}

func (c *Config) readCmdline() error {
	log.Debug("Reading config cmdline")
	cmdLine, err := ioutil.ReadFile("/proc/cmdline")
	if err != nil {
		return err
	}

	if len(cmdLine) == 0 {
		return nil
	}

	log.Debugf("Config cmdline %s", cmdLine)

	cmdLineObj := parseCmdline(strings.TrimSpace(string(cmdLine)))
	return c.merge(cmdLineObj)
}

func dummyMarshall(value string) interface{} {
	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[string]interface{} {
	result := make(map[string]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 {
				if strings.HasPrefix(value, "[") && strings.HasSuffix(value, "]") {
					current[key] = strings.Split(value[1:len(value)-1], ",")
				} else {
					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) Reload() error {
	return util.ShortCircuit(
		c.readCmdline,
		c.readArgs,
	)
}

func (c *Config) GetContainerById(id string) *ContainerConfig {
	for _, c := range c.SystemContainers {
		if c.Id == id {
			return &c
		}
	}

	return nil
}

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
}
<|endoftext|>


<commit_before>package config

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/user"
)

type iobeamConfig struct {
	Name string `json:"profile"`
}

const (
	// CLIVersion is the version of the CLI.
	CLIVersion = "0.4.0"
	// DefaultApiServer is the default iobeam server.
	DefaultApiServer = "https://api.iobeam.com"

	pathSeparator   = string(os.PathSeparator)
	dotDirName      = ".iobeam"
	defaultConfig   = "profile"
	profileFileName = "profile.config"
)

// InitConfig sets up the default config.
func InitConfig() (*iobeamConfig, error) {
	c := &iobeamConfig{
		Name: "default",
	}
	err := c.save()
	if err != nil {
		return nil, err
	}
	return c, nil
}

func getDotDir() string {
	user, err := user.Current()

	if err != nil {
		// We cannot gracefully use the temp directory with profiles.
		panic(err)
	}

	return user.HomeDir + pathSeparator + dotDirName
}

func defaultConfigPath() string {
	return getDotDir() + pathSeparator + defaultConfig
}

func makeAllOnPath(path string) error {
	err := os.MkdirAll(path, 0700)
	if err != nil {
		return err
	}
	return nil
}

func saveJson(path string, obj interface{}) error {
	_ = os.Remove(path) // error only if it does not exist, ignore.

	file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0600)
	if err != nil {
		return err
	}

	defer file.Close()
	return json.NewEncoder(file).Encode(obj)
}

func readJson(path string, obj interface{}) error {
	file, err := os.Open(path)

	if err != nil {
		return err
	}

	defer file.Close()
	return json.NewDecoder(file).Decode(obj)
}

// save writes the config to disk in the user's .iobeam directory.
func (c *iobeamConfig) save() error {
	err := makeAllOnPath(getDotDir())
	if err != nil {
		return err
	}
	return saveJson(defaultConfigPath(), c)
}

func (c *iobeamConfig) read(path string) error {
	return readJson(path, c)
}

func readConfig(path string) (*iobeamConfig, error) {
	c := new(iobeamConfig)

	err := c.read(path)
	if err != nil {
		return nil, err
	}

	return c, err
}

func ReadDefaultConfig() (*iobeamConfig, error) {
	return readConfig(defaultConfigPath())
}

// Profile represents a CLI profile, which is similar to a workspace
// that tracks active user, project, and other metadata.
type Profile struct {
	Name            string `json:"-"`
	Server          string `json:"server"`
	ActiveProject   uint64 `json:"active_project"`
	ActiveUser      uint64 `json:"active_user"`
	ActiveUserEmail string `json:"activer_user_email"`
	// TODO: Don't export active fields.
}

// InitProfile creates a new profile on the system named 'name'.
func InitProfile(name string) (*Profile, error) {
	return InitProfileWithServer(name, DefaultApiServer)
}

// InitProfileWithServer creates a new profile on the system named
// 'name' and uses 'server' for the API server.
func InitProfileWithServer(name, server string) (*Profile, error) {
	p := &Profile{
		Name:          name,
		Server:        server,
		ActiveProject: 0,
		ActiveUser:    0,
	}
	err := p.save()
	if err != nil {
		return nil, err
	}
	return p, nil
}

func baseProfilePath(name string) string {
	return getDotDir() + pathSeparator + name
}

func profilePath(name string) string {
	// e.x. ~user/.iobeam/default/profile.config
	return baseProfilePath(name) + pathSeparator + profileFileName
}

func (p *Profile) save() error {
	err := makeAllOnPath(p.GetDir())
	if err != nil {
		return err
	}
	return saveJson(profilePath(p.Name), p)
}

func (p *Profile) read() error {
	return readJson(p.GetFile(), p)
}

// GetDir returns the path to where p's data is stored.
func (p *Profile) GetDir() string {
	return baseProfilePath(p.Name)
}

// GetFile returns the path to where p's metadata is stored.
func (p *Profile) GetFile() string {
	return profilePath(p.Name)
}

// UpdateActiveUser changes the active user id and email of p.
func (p *Profile) UpdateActiveUser(uid uint64, email string) error {
	p.ActiveUser = uid
	p.ActiveUserEmail = email
	return p.save()
}

// UpdateActiveProject changes the active project id of p.
func (p *Profile) UpdateActiveProject(pid uint64) error {
	p.ActiveProject = pid
	return p.save()
}

// ReadProfile attempts to read and create a *Profile object.
func ReadProfile(name string) (*Profile, error) {
	p := new(Profile)
	p.Name = name

	err := p.read()
	if err != nil {
		return nil, err
	}

	return p, nil
}

// GetProfileList returns a list of available profiles.
func GetProfileList() ([]string, error) {
	files, err := ioutil.ReadDir(getDotDir())
	if err != nil {
		return nil, err
	}

	var list []string
	for _, f := range files {
		if f.IsDir() {
			list = append(list, f.Name())
		}
	}

	return list, nil
}

// SwitchProfile attempts to change the active profile.
func SwitchProfile(name string) error {
	path := baseProfilePath(name)
	_, err := os.Stat(path)
	if os.IsNotExist(err) {
		return fmt.Errorf("Profile '%s' does not exist", name)
	}

	if err == nil {
		c := &iobeamConfig{
			Name: name,
		}
		err = c.save()
	}
	return err
}

// DeleteProfile removes a profile from the system.
func DeleteProfile(name string) error {
	path := baseProfilePath(name)
	_, err := os.Stat(path)
	if err == nil {
		return os.RemoveAll(path)
	} else if os.IsNotExist(err) {
		return fmt.Errorf("Profile '%s' does not exist", name)
	} else {
		return err
	}
}
<commit_msg>release: v0.5.0<commit_after>package config

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/user"
)

type iobeamConfig struct {
	Name string `json:"profile"`
}

const (
	// CLIVersion is the version of the CLI.
	CLIVersion = "0.5.0"
	// DefaultApiServer is the default iobeam server.
	DefaultApiServer = "https://api.iobeam.com"

	pathSeparator   = string(os.PathSeparator)
	dotDirName      = ".iobeam"
	defaultConfig   = "profile"
	profileFileName = "profile.config"
)

// InitConfig sets up the default config.
func InitConfig() (*iobeamConfig, error) {
	c := &iobeamConfig{
		Name: "default",
	}
	err := c.save()
	if err != nil {
		return nil, err
	}
	return c, nil
}

func getDotDir() string {
	user, err := user.Current()

	if err != nil {
		// We cannot gracefully use the temp directory with profiles.
		panic(err)
	}

	return user.HomeDir + pathSeparator + dotDirName
}

func defaultConfigPath() string {
	return getDotDir() + pathSeparator + defaultConfig
}

func makeAllOnPath(path string) error {
	err := os.MkdirAll(path, 0700)
	if err != nil {
		return err
	}
	return nil
}

func saveJson(path string, obj interface{}) error {
	_ = os.Remove(path) // error only if it does not exist, ignore.

	file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0600)
	if err != nil {
		return err
	}

	defer file.Close()
	return json.NewEncoder(file).Encode(obj)
}

func readJson(path string, obj interface{}) error {
	file, err := os.Open(path)

	if err != nil {
		return err
	}

	defer file.Close()
	return json.NewDecoder(file).Decode(obj)
}

// save writes the config to disk in the user's .iobeam directory.
func (c *iobeamConfig) save() error {
	err := makeAllOnPath(getDotDir())
	if err != nil {
		return err
	}
	return saveJson(defaultConfigPath(), c)
}

func (c *iobeamConfig) read(path string) error {
	return readJson(path, c)
}

func readConfig(path string) (*iobeamConfig, error) {
	c := new(iobeamConfig)

	err := c.read(path)
	if err != nil {
		return nil, err
	}

	return c, err
}

func ReadDefaultConfig() (*iobeamConfig, error) {
	return readConfig(defaultConfigPath())
}

// Profile represents a CLI profile, which is similar to a workspace
// that tracks active user, project, and other metadata.
type Profile struct {
	Name            string `json:"-"`
	Server          string `json:"server"`
	ActiveProject   uint64 `json:"active_project"`
	ActiveUser      uint64 `json:"active_user"`
	ActiveUserEmail string `json:"activer_user_email"`
	// TODO: Don't export active fields.
}

// InitProfile creates a new profile on the system named 'name'.
func InitProfile(name string) (*Profile, error) {
	return InitProfileWithServer(name, DefaultApiServer)
}

// InitProfileWithServer creates a new profile on the system named
// 'name' and uses 'server' for the API server.
func InitProfileWithServer(name, server string) (*Profile, error) {
	p := &Profile{
		Name:          name,
		Server:        server,
		ActiveProject: 0,
		ActiveUser:    0,
	}
	err := p.save()
	if err != nil {
		return nil, err
	}
	return p, nil
}

func baseProfilePath(name string) string {
	return getDotDir() + pathSeparator + name
}

func profilePath(name string) string {
	// e.x. ~user/.iobeam/default/profile.config
	return baseProfilePath(name) + pathSeparator + profileFileName
}

func (p *Profile) save() error {
	err := makeAllOnPath(p.GetDir())
	if err != nil {
		return err
	}
	return saveJson(profilePath(p.Name), p)
}

func (p *Profile) read() error {
	return readJson(p.GetFile(), p)
}

// GetDir returns the path to where p's data is stored.
func (p *Profile) GetDir() string {
	return baseProfilePath(p.Name)
}

// GetFile returns the path to where p's metadata is stored.
func (p *Profile) GetFile() string {
	return profilePath(p.Name)
}

// UpdateActiveUser changes the active user id and email of p.
func (p *Profile) UpdateActiveUser(uid uint64, email string) error {
	p.ActiveUser = uid
	p.ActiveUserEmail = email
	return p.save()
}

// UpdateActiveProject changes the active project id of p.
func (p *Profile) UpdateActiveProject(pid uint64) error {
	p.ActiveProject = pid
	return p.save()
}

// ReadProfile attempts to read and create a *Profile object.
func ReadProfile(name string) (*Profile, error) {
	p := new(Profile)
	p.Name = name

	err := p.read()
	if err != nil {
		return nil, err
	}

	return p, nil
}

// GetProfileList returns a list of available profiles.
func GetProfileList() ([]string, error) {
	files, err := ioutil.ReadDir(getDotDir())
	if err != nil {
		return nil, err
	}

	var list []string
	for _, f := range files {
		if f.IsDir() {
			list = append(list, f.Name())
		}
	}

	return list, nil
}

// SwitchProfile attempts to change the active profile.
func SwitchProfile(name string) error {
	path := baseProfilePath(name)
	_, err := os.Stat(path)
	if os.IsNotExist(err) {
		return fmt.Errorf("Profile '%s' does not exist", name)
	}

	if err == nil {
		c := &iobeamConfig{
			Name: name,
		}
		err = c.save()
	}
	return err
}

// DeleteProfile removes a profile from the system.
func DeleteProfile(name string) error {
	path := baseProfilePath(name)
	_, err := os.Stat(path)
	if err == nil {
		return os.RemoveAll(path)
	} else if os.IsNotExist(err) {
		return fmt.Errorf("Profile '%s' does not exist", name)
	} else {
		return err
	}
}
<|endoftext|>


<commit_before>package config

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/davecgh/go-spew/spew"
	"github.com/ninjasphere/go-ninja/logger"
	"gopkg.in/alecthomas/kingpin.v1"
)

var log = logger.GetLogger("config")

var config map[string]interface{}

func init() {
	MustRefresh()

	if Bool(false, "dumpConfig") {
		spew.Dump(GetAll(false))
	}
}

func GetAll(flatten bool) map[string]interface{} {
	if flatten {
		return config
	} else {
		return unflatten(config)
	}
}

var serial string

func Serial() string {
	if serial == "" {
		cmd := exec.Command("sphere-serial", os.Args[1:]...)

		var out bytes.Buffer
		cmd.Stdout = &out

		err := cmd.Run()
		if err != nil {
			log.Fatalf("Failed to get sphere serial (sphere-serial must be in the PATH) error:%s", err)
		}

		serial = out.String()
	}

	return serial
}

func IsPaired() bool {
	return /*HasString("sphereNetworkKey") && */ HasString("token") && HasString("userId")
}

func String(def string, path ...string) string {
	val := get(path...)
	if val == nil {
		return def
	}
	return val.(string)
}

// MustString returns the string property at the path
func MustString(path ...string) string {
	return mustGet(path...).(string)
}

// MustDuration returns the string property at the path, as a time.Duration
func MustDuration(path ...string) time.Duration {
	s := MustString(path...)
	d, err := time.ParseDuration(s)
	if err != nil {
		log.Fatalf("Failed to parse duration '%s': %s", s, err)
	}
	return d
}

// MustStringArray returns the string array property at the path
func MustStringArray(path ...string) []string {
	a := mustGet(path...).([]interface{})
	b := make([]string, len(a))
	for i := range a {
		b[i] = a[i].(string)
	}
	return b
}

// Int returns the integer property at the path, with a default
func Int(def int, path ...string) int {
	val := get(path...)
	if val == nil {
		return def
	}
	return int(val.(float64))
}

// MustInt returns the string property at the path
func MustInt(path ...string) int {
	return int(mustGet(path...).(float64))
}

func MustFloat(path ...string) float64 {
	return mustGet(path...).(float64)
}

// Bool returns the boolean property at the path, with a default
func Bool(def bool, path ...string) bool {
	val := get(path...)
	if val == nil {
		return def
	}
	return val.(bool)
}

// MustBool returns the boolean property at the path
func MustBool(path ...string) bool {
	return mustGet(path...).(bool)
}

var hey = "what's up buddy?"

func HasString(path ...string) bool {
	val, ok := config[strings.Join(path, ".")]
	if !ok {
		return false
	}

	_, ok = val.(string)
	return ok
}

func mustGet(path ...string) interface{} {
	val, ok := config[strings.Join(path, ".")]
	if !ok {
		log.Fatalf("expected value for %v but found nothing", path)
	}
	return val
}

func get(path ...string) interface{} {
	val, ok := config[strings.Join(path, ".")]
	if !ok {
		return nil
	}
	return val
}

func MustRefresh() {

	flat := make(map[string]interface{})

	// cli overrides
	addArgs(flat)

	// load environments (no value args) from cli args
	environments := []string{"default"}
	for name, value := range flat {
		if value == nil {
			environments = append(environments, name)
		} else {
			if ok, boolValue := value.(bool); ok {
				if boolValue {
					environments = append(environments, name)
				}
			}
		}
	}

	log.Infof("Environments: %s", strings.Join(environments, ", "))

	flat["env"] = environments

	userHome := getUserHome()

	// env vars (if starting with "sphere_")
	addEnv(flat)

	// anything that can be parsed as a number, is a number
	parseNumbers(flat)

	installDir := "/opt/ninjablocks"
	if val, ok := flat["installDirectory"]; ok {
		installDir = val.(string)
	}

	flat["installDirectory"] = installDir

	if _, err := os.Stat(installDir); err != nil {
		log.Fatalf("Couldn't load sphere install directory. Override with env var sphere_installDirectory. error:%s", err)
	}

	// credentials file
	addFile("/data/etc/opt/ninja/credentials.json", flat)

	// home directory environment(s) config
	for i := len(environments) - 1; i >= 0; i-- {
		addFile(filepath.Join(userHome, ".sphere", environments[i]+".json"), flat)
	}

	// current directory environment(s) config
	for i := len(environments) - 1; i >= 0; i-- {
		addFile(filepath.Join(".", "config", environments[i]+".json"), flat)
	}

	// common environment(s) config
	for i := len(environments) - 1; i >= 0; i-- {
		addFile(filepath.Join(installDir, "config", environments[i]+".json"), flat)
	}

	// common credentials config
	addFile(filepath.Join(installDir, "config", "credentials.json"), flat)

	//log.Debugf("Loaded config: %v", flat)

	config = flat
}

func addEnv(config map[string]interface{}) {
	for _, v := range os.Environ() {
		split := strings.Split(v, "=")

		name, value := split[0], split[1]

		if strings.HasPrefix(name, "sphere_") {
			name = strings.TrimPrefix(name, "sphere_")
			name = strings.Replace(name, "_", ".", 0)

			if _, ok := config[name]; !ok {
				config[name] = value
			}
		}
	}
}

func addArgs(config map[string]interface{}) {

	parser := kingpin.Tokenize(os.Args[1:]).Tokens

	for token, parser := parser.Peek(), parser.Next(); token.Type != kingpin.TokenEOL; token, parser = parser.Peek(), parser.Next() {

		if token.IsFlag() {
			var value interface{}
			name := token.Value

			next := parser.Peek()
			if next.Type == kingpin.TokenArg {
				if next.Value == "false" {
					value = false
				} else if next.Value == "true" {
					value = true
				} else {
					value = next.Value
				}
			} else {
				value = true
				// It's an environment indicator... like --cloud-production
			}

			config[name] = value
		}

	}

}

func addFile(path string, config map[string]interface{}) error {
	//log.Debugf("Loading config file: %s", path)

	file, e := ioutil.ReadFile(path)
	if e != nil {
		return fmt.Errorf("Failed to load file: %s error: %s", path, e)
	}

	content := make(map[string]interface{})
	e = json.Unmarshal(file, &content)
	if e != nil {
		return fmt.Errorf("Failed to read file: %s error: %s", path, e)
	}

	//spew.Dump(path, content)

	flatten(content, nil, config)
	return nil
}

func flatten(input interface{}, lpath []string, flattened map[string]interface{}) {
	if lpath == nil {
		lpath = []string{}
	}

	if reflect.ValueOf(input).Kind() == reflect.Map {
		for rkey, value := range input.(map[string]interface{}) {
			flatten(value, append(lpath, rkey), flattened)
		}
	} else {
		if _, ok := flattened[strings.Join(lpath, ".")]; !ok {
			flattened[strings.Join(lpath, ".")] = input
		}
	}
}

func unflatten(flat map[string]interface{}) map[string]interface{} {
	out := make(map[string]interface{})
	obj := out

	for key, val := range flat {

		obj = out
		keys := strings.Split(key, ".")

		for i, k := range keys {
			if i == len(keys)-1 {
				obj[k] = val
			} else {
				next, ok := obj[k]
				if !ok {
					next = make(map[string]interface{})
				}
				obj[k] = next
				obj = next.(map[string]interface{})
			}
		}

	}

	return out
}

func parseNumbers(config map[string]interface{}) {
	for name, val := range config {
		stringVal, ok := val.(string)
		if ok {
			floatVal, err := strconv.ParseFloat(stringVal, 64)
			if err == nil {
				config[name] = floatVal
			}
		}
	}
}

func getUserHome() string {
	usr, err := user.Current()
	if err != nil {
		return "/root"
	}
	return usr.HomeDir
}
<commit_msg>Load mesh info into config<commit_after>package config

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/davecgh/go-spew/spew"
	"github.com/ninjasphere/go-ninja/logger"
	"gopkg.in/alecthomas/kingpin.v1"
)

var log = logger.GetLogger("config")

var config map[string]interface{}

func init() {
	MustRefresh()

	if Bool(false, "dumpConfig") {
		spew.Dump(GetAll(false))
	}
}

func GetAll(flatten bool) map[string]interface{} {
	if flatten {
		return config
	} else {
		return unflatten(config)
	}
}

var serial string

func Serial() string {
	if serial == "" {
		cmd := exec.Command("sphere-serial", os.Args[1:]...)

		var out bytes.Buffer
		cmd.Stdout = &out

		err := cmd.Run()
		if err != nil {
			log.Fatalf("Failed to get sphere serial (sphere-serial must be in the PATH) error:%s", err)
		}

		serial = out.String()
	}

	return serial
}

func IsPaired() bool {
	return /*HasString("sphereNetworkKey") && */ HasString("token") && HasString("userId")
}

func String(def string, path ...string) string {
	val := get(path...)
	if val == nil {
		return def
	}
	return val.(string)
}

// MustString returns the string property at the path
func MustString(path ...string) string {
	return mustGet(path...).(string)
}

// MustDuration returns the string property at the path, as a time.Duration
func MustDuration(path ...string) time.Duration {
	s := MustString(path...)
	d, err := time.ParseDuration(s)
	if err != nil {
		log.Fatalf("Failed to parse duration '%s': %s", s, err)
	}
	return d
}

// MustStringArray returns the string array property at the path
func MustStringArray(path ...string) []string {
	a := mustGet(path...).([]interface{})
	b := make([]string, len(a))
	for i := range a {
		b[i] = a[i].(string)
	}
	return b
}

// Int returns the integer property at the path, with a default
func Int(def int, path ...string) int {
	val := get(path...)
	if val == nil {
		return def
	}
	return int(val.(float64))
}

// MustInt returns the string property at the path
func MustInt(path ...string) int {
	return int(mustGet(path...).(float64))
}

func MustFloat(path ...string) float64 {
	return mustGet(path...).(float64)
}

// Bool returns the boolean property at the path, with a default
func Bool(def bool, path ...string) bool {
	val := get(path...)
	if val == nil {
		return def
	}
	return val.(bool)
}

// MustBool returns the boolean property at the path
func MustBool(path ...string) bool {
	return mustGet(path...).(bool)
}

var hey = "what's up buddy?"

func HasString(path ...string) bool {
	val, ok := config[strings.Join(path, ".")]
	if !ok {
		return false
	}

	_, ok = val.(string)
	return ok
}

func mustGet(path ...string) interface{} {
	val, ok := config[strings.Join(path, ".")]
	if !ok {
		log.Fatalf("expected value for %v but found nothing", path)
	}
	return val
}

func get(path ...string) interface{} {
	val, ok := config[strings.Join(path, ".")]
	if !ok {
		return nil
	}
	return val
}

func MustRefresh() {

	flat := make(map[string]interface{})

	// cli overrides
	addArgs(flat)

	// load environments (no value args) from cli args
	environments := []string{"default"}
	for name, value := range flat {
		if value == nil {
			environments = append(environments, name)
		} else {
			if ok, boolValue := value.(bool); ok {
				if boolValue {
					environments = append(environments, name)
				}
			}
		}
	}

	log.Infof("Environments: %s", strings.Join(environments, ", "))

	flat["env"] = environments

	userHome := getUserHome()

	// env vars (if starting with "sphere_")
	addEnv(flat)

	// anything that can be parsed as a number, is a number
	parseNumbers(flat)

	installDir := "/opt/ninjablocks"
	if val, ok := flat["installDirectory"]; ok {
		installDir = val.(string)
	}

	flat["installDirectory"] = installDir

	if _, err := os.Stat(installDir); err != nil {
		log.Fatalf("Couldn't load sphere install directory. Override with env var sphere_installDirectory. error:%s", err)
	}

	// credentials file
	addFile("/data/etc/opt/ninja/credentials.json", flat)

	// mesh file
	addFile("/data/etc/opt/ninja/mesh.json", flat)

	// home directory environment(s) config
	for i := len(environments) - 1; i >= 0; i-- {
		addFile(filepath.Join(userHome, ".sphere", environments[i]+".json"), flat)
	}

	// current directory environment(s) config
	for i := len(environments) - 1; i >= 0; i-- {
		addFile(filepath.Join(".", "config", environments[i]+".json"), flat)
	}

	// common environment(s) config
	for i := len(environments) - 1; i >= 0; i-- {
		addFile(filepath.Join(installDir, "config", environments[i]+".json"), flat)
	}

	// common credentials config
	addFile(filepath.Join(installDir, "config", "credentials.json"), flat)

	//log.Debugf("Loaded config: %v", flat)

	config = flat
}

func addEnv(config map[string]interface{}) {
	for _, v := range os.Environ() {
		split := strings.Split(v, "=")

		name, value := split[0], split[1]

		if strings.HasPrefix(name, "sphere_") {
			name = strings.TrimPrefix(name, "sphere_")
			name = strings.Replace(name, "_", ".", 0)

			if _, ok := config[name]; !ok {
				config[name] = value
			}
		}
	}
}

func addArgs(config map[string]interface{}) {

	parser := kingpin.Tokenize(os.Args[1:]).Tokens

	for token, parser := parser.Peek(), parser.Next(); token.Type != kingpin.TokenEOL; token, parser = parser.Peek(), parser.Next() {

		if token.IsFlag() {
			var value interface{}
			name := token.Value

			next := parser.Peek()
			if next.Type == kingpin.TokenArg {
				if next.Value == "false" {
					value = false
				} else if next.Value == "true" {
					value = true
				} else {
					value = next.Value
				}
			} else {
				value = true
				// It's an environment indicator... like --cloud-production
			}

			config[name] = value
		}

	}

}

func addFile(path string, config map[string]interface{}) error {
	//log.Debugf("Loading config file: %s", path)

	file, e := ioutil.ReadFile(path)
	if e != nil {
		return fmt.Errorf("Failed to load file: %s error: %s", path, e)
	}

	content := make(map[string]interface{})
	e = json.Unmarshal(file, &content)
	if e != nil {
		return fmt.Errorf("Failed to read file: %s error: %s", path, e)
	}

	//spew.Dump(path, content)

	flatten(content, nil, config)
	return nil
}

func flatten(input interface{}, lpath []string, flattened map[string]interface{}) {
	if lpath == nil {
		lpath = []string{}
	}

	if reflect.ValueOf(input).Kind() == reflect.Map {
		for rkey, value := range input.(map[string]interface{}) {
			flatten(value, append(lpath, rkey), flattened)
		}
	} else {
		if _, ok := flattened[strings.Join(lpath, ".")]; !ok {
			flattened[strings.Join(lpath, ".")] = input
		}
	}
}

func unflatten(flat map[string]interface{}) map[string]interface{} {
	out := make(map[string]interface{})
	obj := out

	for key, val := range flat {

		obj = out
		keys := strings.Split(key, ".")

		for i, k := range keys {
			if i == len(keys)-1 {
				obj[k] = val
			} else {
				next, ok := obj[k]
				if !ok {
					next = make(map[string]interface{})
				}
				obj[k] = next
				obj = next.(map[string]interface{})
			}
		}

	}

	return out
}

func parseNumbers(config map[string]interface{}) {
	for name, val := range config {
		stringVal, ok := val.(string)
		if ok {
			floatVal, err := strconv.ParseFloat(stringVal, 64)
			if err == nil {
				config[name] = floatVal
			}
		}
	}
}

func getUserHome() string {
	usr, err := user.Current()
	if err != nil {
		return "/root"
	}
	return usr.HomeDir
}
<|endoftext|>


<commit_before>// Package config collects together all configuration settings
// NOTE: Subject to change, do not rely on this package from outside git-lfs source
package config

import (
	"errors"
	"fmt"
	"reflect"
	"sync"

	"github.com/git-lfs/git-lfs/lfsapi"
	"github.com/git-lfs/git-lfs/tools"
)

var (
	Config                 = New()
	ShowConfigWarnings     = false
	defaultRemote          = "origin"
	gitConfigWarningPrefix = "lfs."
)

// FetchPruneConfig collects together the config options that control fetching and pruning
type FetchPruneConfig struct {
	// The number of days prior to current date for which (local) refs other than HEAD
	// will be fetched with --recent (default 7, 0 = only fetch HEAD)
	FetchRecentRefsDays int `git:"lfs.fetchrecentrefsdays"`
	// Makes the FetchRecentRefsDays option apply to remote refs from fetch source as well (default true)
	FetchRecentRefsIncludeRemotes bool `git:"lfs.fetchrecentremoterefs"`
	// number of days prior to latest commit on a ref that we'll fetch previous
	// LFS changes too (default 0 = only fetch at ref)
	FetchRecentCommitsDays int `git:"lfs.fetchrecentcommitsdays"`
	// Whether to always fetch recent even without --recent
	FetchRecentAlways bool `git:"lfs.fetchrecentalways"`
	// Number of days added to FetchRecent*; data outside combined window will be
	// deleted when prune is run. (default 3)
	PruneOffsetDays int `git:"lfs.pruneoffsetdays"`
	// Always verify with remote before pruning
	PruneVerifyRemoteAlways bool `git:"lfs.pruneverifyremotealways"`
	// Name of remote to check for unpushed and verify checks
	PruneRemoteName string `git:"lfs.pruneremotetocheck"`
}

type Configuration struct {
	// Os provides a `*Environment` used to access to the system's
	// environment through os.Getenv. It is the point of entry for all
	// system environment configuration.
	Os Environment

	// Git provides a `*Environment` used to access to the various levels of
	// `.gitconfig`'s. It is the point of entry for all Git environment
	// configuration.
	Git Environment

	CurrentRemote string

	loading        sync.Mutex // guards initialization of gitConfig and remotes
	remotes        []string
	extensions     map[string]Extension
	endpointFinder lfsapi.EndpointFinder
	endpointMu     sync.Mutex
}

func New() *Configuration {
	c := &Configuration{Os: EnvironmentOf(NewOsFetcher())}
	c.Git = &gitEnvironment{config: c}
	initConfig(c)
	return c
}

// Values is a convenience type used to call the NewFromValues function. It
// specifies `Git` and `Env` maps to use as mock values, instead of calling out
// to real `.gitconfig`s and the `os.Getenv` function.
type Values struct {
	// Git and Os are the stand-in maps used to provide values for their
	// respective environments.
	Git, Os map[string]string
}

// NewFrom returns a new `*config.Configuration` that reads both its Git
// and Enviornment-level values from the ones provided instead of the actual
// `.gitconfig` file or `os.Getenv`, respectively.
//
// This method should only be used during testing.
func NewFrom(v Values) *Configuration {
	c := &Configuration{
		Os:  EnvironmentOf(mapFetcher(v.Os)),
		Git: EnvironmentOf(mapFetcher(v.Git)),
	}
	initConfig(c)
	return c
}

func initConfig(c *Configuration) {
	c.CurrentRemote = defaultRemote
}

// Unmarshal unmarshals the *Configuration in context into all of `v`'s fields,
// according to the following rules:
//
// Values are marshaled according to the given key and environment, as follows:
//	type T struct {
//		Field string `git:"key"`
//		Other string `os:"key"`
//	}
//
// If an unknown environment is given, an error will be returned. If there is no
// method supporting conversion into a field's type, an error will be returned.
// If no value is associated with the given key and environment, the field will
// // only be modified if there is a config value present matching the given
// key. If the field is already set to a non-zero value of that field's type,
// then it will be left alone.
//
// Otherwise, the field will be set to the value of calling the
// appropriately-typed method on the specified environment.
func (c *Configuration) Unmarshal(v interface{}) error {
	into := reflect.ValueOf(v)
	if into.Kind() != reflect.Ptr {
		return fmt.Errorf("lfs/config: unable to parse non-pointer type of %T", v)
	}
	into = into.Elem()

	for i := 0; i < into.Type().NumField(); i++ {
		field := into.Field(i)
		sfield := into.Type().Field(i)

		key, env, err := c.parseTag(sfield.Tag)
		if err != nil {
			return err
		}

		if env == nil {
			continue
		}

		var val interface{}
		switch sfield.Type.Kind() {
		case reflect.String:
			var ok bool

			val, ok = env.Get(key)
			if !ok {
				val = field.String()
			}
		case reflect.Int:
			val = env.Int(key, int(field.Int()))
		case reflect.Bool:
			val = env.Bool(key, field.Bool())
		default:
			return fmt.Errorf(
				"lfs/config: unsupported target type for field %q: %v",
				sfield.Name, sfield.Type.String())
		}

		if val != nil {
			into.Field(i).Set(reflect.ValueOf(val))
		}
	}

	return nil
}

// parseTag returns the key, environment, and optional error assosciated with a
// given tag. It will return the XOR of either the `git` or `os` tag. That is to
// say, a field tagged with EITHER `git` OR `os` is valid, but pone tagged with
// both is not.
//
// If neither field was found, then a nil environment will be returned.
func (c *Configuration) parseTag(tag reflect.StructTag) (key string, env Environment, err error) {
	git, os := tag.Get("git"), tag.Get("os")

	if len(git) != 0 && len(os) != 0 {
		return "", nil, errors.New("lfs/config: ambiguous tags")
	}

	if len(git) != 0 {
		return git, c.Git, nil
	}
	if len(os) != 0 {
		return os, c.Os, nil
	}

	return
}

// BasicTransfersOnly returns whether to only allow "basic" HTTP transfers.
// Default is false, including if the lfs.basictransfersonly is invalid
func (c *Configuration) BasicTransfersOnly() bool {
	return c.Git.Bool("lfs.basictransfersonly", false)
}

// TusTransfersAllowed returns whether to only use "tus.io" HTTP transfers.
// Default is false, including if the lfs.tustransfers is invalid
func (c *Configuration) TusTransfersAllowed() bool {
	return c.Git.Bool("lfs.tustransfers", false)
}

func (c *Configuration) FetchIncludePaths() []string {
	patterns, _ := c.Git.Get("lfs.fetchinclude")
	return tools.CleanPaths(patterns, ",")
}

func (c *Configuration) FetchExcludePaths() []string {
	patterns, _ := c.Git.Get("lfs.fetchexclude")
	return tools.CleanPaths(patterns, ",")
}

func (c *Configuration) Remotes() []string {
	c.loadGitConfig()

	return c.remotes
}

func (c *Configuration) endpointConfig() lfsapi.EndpointFinder {
	c.endpointMu.Lock()
	defer c.endpointMu.Unlock()

	if c.endpointFinder == nil {
		c.endpointFinder = lfsapi.NewEndpointFinder(c.Git)
	}

	return c.endpointFinder
}

func (c *Configuration) Extensions() map[string]Extension {
	c.loadGitConfig()
	return c.extensions
}

// SortedExtensions gets the list of extensions ordered by Priority
func (c *Configuration) SortedExtensions() ([]Extension, error) {
	return SortExtensions(c.Extensions())
}

func (c *Configuration) FetchPruneConfig() FetchPruneConfig {
	f := &FetchPruneConfig{
		FetchRecentRefsDays:           7,
		FetchRecentRefsIncludeRemotes: true,
		PruneOffsetDays:               3,
		PruneRemoteName:               "origin",
	}

	if err := c.Unmarshal(f); err != nil {
		panic(err.Error())
	}
	return *f
}

func (c *Configuration) SkipDownloadErrors() bool {
	return c.Os.Bool("GIT_LFS_SKIP_DOWNLOAD_ERRORS", false) || c.Git.Bool("lfs.skipdownloaderrors", false)
}

func (c *Configuration) SetLockableFilesReadOnly() bool {
	return c.Os.Bool("GIT_LFS_SET_LOCKABLE_READONLY", true) && c.Git.Bool("lfs.setlockablereadonly", true)
}

// loadGitConfig is a temporary measure to support legacy behavior dependent on
// accessing properties set by ReadGitConfig, namely:
//  - `c.extensions`
//  - `c.uniqRemotes`
//  - `c.gitConfig`
//
// Since the *gitEnvironment is responsible for setting these values on the
// (*config.Configuration) instance, we must call that method, if it exists.
//
// loadGitConfig returns a bool returning whether or not `loadGitConfig` was
// called AND the method did not return early.
func (c *Configuration) loadGitConfig() bool {
	if g, ok := c.Git.(*gitEnvironment); ok {
		return g.loadGitConfig()
	}

	return false
}

// CurrentCommitter returns the name/email that would be used to author a commit
// with this configuration. In particular, the "user.name" and "user.email"
// configuration values are used
func (c *Configuration) CurrentCommitter() (name, email string) {
	name, _ = c.Git.Get("user.name")
	email, _ = c.Git.Get("user.email")
	return
}
<commit_msg>config/config: remove endpointConfig() function<commit_after>// Package config collects together all configuration settings
// NOTE: Subject to change, do not rely on this package from outside git-lfs source
package config

import (
	"errors"
	"fmt"
	"reflect"
	"sync"

	"github.com/git-lfs/git-lfs/tools"
)

var (
	Config                 = New()
	ShowConfigWarnings     = false
	defaultRemote          = "origin"
	gitConfigWarningPrefix = "lfs."
)

// FetchPruneConfig collects together the config options that control fetching and pruning
type FetchPruneConfig struct {
	// The number of days prior to current date for which (local) refs other than HEAD
	// will be fetched with --recent (default 7, 0 = only fetch HEAD)
	FetchRecentRefsDays int `git:"lfs.fetchrecentrefsdays"`
	// Makes the FetchRecentRefsDays option apply to remote refs from fetch source as well (default true)
	FetchRecentRefsIncludeRemotes bool `git:"lfs.fetchrecentremoterefs"`
	// number of days prior to latest commit on a ref that we'll fetch previous
	// LFS changes too (default 0 = only fetch at ref)
	FetchRecentCommitsDays int `git:"lfs.fetchrecentcommitsdays"`
	// Whether to always fetch recent even without --recent
	FetchRecentAlways bool `git:"lfs.fetchrecentalways"`
	// Number of days added to FetchRecent*; data outside combined window will be
	// deleted when prune is run. (default 3)
	PruneOffsetDays int `git:"lfs.pruneoffsetdays"`
	// Always verify with remote before pruning
	PruneVerifyRemoteAlways bool `git:"lfs.pruneverifyremotealways"`
	// Name of remote to check for unpushed and verify checks
	PruneRemoteName string `git:"lfs.pruneremotetocheck"`
}

type Configuration struct {
	// Os provides a `*Environment` used to access to the system's
	// environment through os.Getenv. It is the point of entry for all
	// system environment configuration.
	Os Environment

	// Git provides a `*Environment` used to access to the various levels of
	// `.gitconfig`'s. It is the point of entry for all Git environment
	// configuration.
	Git Environment

	CurrentRemote string

	loading    sync.Mutex // guards initialization of gitConfig and remotes
	remotes    []string
	extensions map[string]Extension
}

func New() *Configuration {
	c := &Configuration{Os: EnvironmentOf(NewOsFetcher())}
	c.Git = &gitEnvironment{config: c}
	initConfig(c)
	return c
}

// Values is a convenience type used to call the NewFromValues function. It
// specifies `Git` and `Env` maps to use as mock values, instead of calling out
// to real `.gitconfig`s and the `os.Getenv` function.
type Values struct {
	// Git and Os are the stand-in maps used to provide values for their
	// respective environments.
	Git, Os map[string]string
}

// NewFrom returns a new `*config.Configuration` that reads both its Git
// and Enviornment-level values from the ones provided instead of the actual
// `.gitconfig` file or `os.Getenv`, respectively.
//
// This method should only be used during testing.
func NewFrom(v Values) *Configuration {
	c := &Configuration{
		Os:  EnvironmentOf(mapFetcher(v.Os)),
		Git: EnvironmentOf(mapFetcher(v.Git)),
	}
	initConfig(c)
	return c
}

func initConfig(c *Configuration) {
	c.CurrentRemote = defaultRemote
}

// Unmarshal unmarshals the *Configuration in context into all of `v`'s fields,
// according to the following rules:
//
// Values are marshaled according to the given key and environment, as follows:
//	type T struct {
//		Field string `git:"key"`
//		Other string `os:"key"`
//	}
//
// If an unknown environment is given, an error will be returned. If there is no
// method supporting conversion into a field's type, an error will be returned.
// If no value is associated with the given key and environment, the field will
// // only be modified if there is a config value present matching the given
// key. If the field is already set to a non-zero value of that field's type,
// then it will be left alone.
//
// Otherwise, the field will be set to the value of calling the
// appropriately-typed method on the specified environment.
func (c *Configuration) Unmarshal(v interface{}) error {
	into := reflect.ValueOf(v)
	if into.Kind() != reflect.Ptr {
		return fmt.Errorf("lfs/config: unable to parse non-pointer type of %T", v)
	}
	into = into.Elem()

	for i := 0; i < into.Type().NumField(); i++ {
		field := into.Field(i)
		sfield := into.Type().Field(i)

		key, env, err := c.parseTag(sfield.Tag)
		if err != nil {
			return err
		}

		if env == nil {
			continue
		}

		var val interface{}
		switch sfield.Type.Kind() {
		case reflect.String:
			var ok bool

			val, ok = env.Get(key)
			if !ok {
				val = field.String()
			}
		case reflect.Int:
			val = env.Int(key, int(field.Int()))
		case reflect.Bool:
			val = env.Bool(key, field.Bool())
		default:
			return fmt.Errorf(
				"lfs/config: unsupported target type for field %q: %v",
				sfield.Name, sfield.Type.String())
		}

		if val != nil {
			into.Field(i).Set(reflect.ValueOf(val))
		}
	}

	return nil
}

// parseTag returns the key, environment, and optional error assosciated with a
// given tag. It will return the XOR of either the `git` or `os` tag. That is to
// say, a field tagged with EITHER `git` OR `os` is valid, but pone tagged with
// both is not.
//
// If neither field was found, then a nil environment will be returned.
func (c *Configuration) parseTag(tag reflect.StructTag) (key string, env Environment, err error) {
	git, os := tag.Get("git"), tag.Get("os")

	if len(git) != 0 && len(os) != 0 {
		return "", nil, errors.New("lfs/config: ambiguous tags")
	}

	if len(git) != 0 {
		return git, c.Git, nil
	}
	if len(os) != 0 {
		return os, c.Os, nil
	}

	return
}

// BasicTransfersOnly returns whether to only allow "basic" HTTP transfers.
// Default is false, including if the lfs.basictransfersonly is invalid
func (c *Configuration) BasicTransfersOnly() bool {
	return c.Git.Bool("lfs.basictransfersonly", false)
}

// TusTransfersAllowed returns whether to only use "tus.io" HTTP transfers.
// Default is false, including if the lfs.tustransfers is invalid
func (c *Configuration) TusTransfersAllowed() bool {
	return c.Git.Bool("lfs.tustransfers", false)
}

func (c *Configuration) FetchIncludePaths() []string {
	patterns, _ := c.Git.Get("lfs.fetchinclude")
	return tools.CleanPaths(patterns, ",")
}

func (c *Configuration) FetchExcludePaths() []string {
	patterns, _ := c.Git.Get("lfs.fetchexclude")
	return tools.CleanPaths(patterns, ",")
}

func (c *Configuration) Remotes() []string {
	c.loadGitConfig()

	return c.remotes
}

func (c *Configuration) Extensions() map[string]Extension {
	c.loadGitConfig()
	return c.extensions
}

// SortedExtensions gets the list of extensions ordered by Priority
func (c *Configuration) SortedExtensions() ([]Extension, error) {
	return SortExtensions(c.Extensions())
}

func (c *Configuration) FetchPruneConfig() FetchPruneConfig {
	f := &FetchPruneConfig{
		FetchRecentRefsDays:           7,
		FetchRecentRefsIncludeRemotes: true,
		PruneOffsetDays:               3,
		PruneRemoteName:               "origin",
	}

	if err := c.Unmarshal(f); err != nil {
		panic(err.Error())
	}
	return *f
}

func (c *Configuration) SkipDownloadErrors() bool {
	return c.Os.Bool("GIT_LFS_SKIP_DOWNLOAD_ERRORS", false) || c.Git.Bool("lfs.skipdownloaderrors", false)
}

func (c *Configuration) SetLockableFilesReadOnly() bool {
	return c.Os.Bool("GIT_LFS_SET_LOCKABLE_READONLY", true) && c.Git.Bool("lfs.setlockablereadonly", true)
}

// loadGitConfig is a temporary measure to support legacy behavior dependent on
// accessing properties set by ReadGitConfig, namely:
//  - `c.extensions`
//  - `c.uniqRemotes`
//  - `c.gitConfig`
//
// Since the *gitEnvironment is responsible for setting these values on the
// (*config.Configuration) instance, we must call that method, if it exists.
//
// loadGitConfig returns a bool returning whether or not `loadGitConfig` was
// called AND the method did not return early.
func (c *Configuration) loadGitConfig() bool {
	if g, ok := c.Git.(*gitEnvironment); ok {
		return g.loadGitConfig()
	}

	return false
}

// CurrentCommitter returns the name/email that would be used to author a commit
// with this configuration. In particular, the "user.name" and "user.email"
// configuration values are used
func (c *Configuration) CurrentCommitter() (name, email string) {
	name, _ = c.Git.Get("user.name")
	email, _ = c.Git.Get("user.email")
	return
}
<|endoftext|>