diff --git a/config.go b/config.go index 0e48c96eb..3c0362825 100644 --- a/config.go +++ b/config.go @@ -1,9 +1,10 @@ package main import ( - "github.com/BurntSushi/toml" + "encoding/json" "github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer/plugin" + "io" "log" "os/exec" ) @@ -11,15 +12,20 @@ import ( // This is the default, built-in configuration that ships with // Packer. const defaultConfig = ` -[builders] -amazon-ebs = "packer-builder-amazon-ebs" -vmware = "packer-builder-vmware" +{ + "builders": { + "amazon-ebs": "packer-builder-amazon-ebs", + "vmware": "packer-builder-vmware" + }, -[commands] -build = "packer-command-build" + "commands": { + "build": "packer-command-build" + }, -[provisioners] -shell = "packer-provisioner-shell" + "provisioners": { + "shell": "packer-provisioner-shell" + } +} ` type config struct { @@ -28,44 +34,11 @@ type config struct { Provisioners map[string]string } -// Merge the configurations. Anything in the "new" configuration takes -// precedence over the "old" configuration. -func mergeConfig(a, b *config) *config { - configs := []*config{a, b} - result := newConfig() - - for _, config := range configs { - for k, v := range config.Builders { - result.Builders[k] = v - } - - for k, v := range config.Commands { - result.Commands[k] = v - } - - for k, v := range config.Provisioners { - result.Provisioners[k] = v - } - } - - return result -} - -// Creates and initializes a new config struct. -func newConfig() *config { - result := new(config) - result.Builders = make(map[string]string) - result.Commands = make(map[string]string) - result.Provisioners = make(map[string]string) - return result -} - -// Parses a configuration file and returns a proper configuration -// struct. -func parseConfig(data string) (result *config, err error) { - result = new(config) - _, err = toml.Decode(data, &result) - return +// Decodes configuration in JSON format from the given io.Reader into +// the config object pointed to. +func decodeConfig(r io.Reader, c *config) error { + decoder := json.NewDecoder(r) + return decoder.Decode(c) } // Returns an array of defined command names. @@ -77,6 +50,8 @@ func (c *config) CommandNames() (result []string) { return } +// This is a proper packer.BuilderFunc that can be used to load packer.Builder +// implementations from the defined plugins. func (c *config) LoadBuilder(name string) (packer.Builder, error) { log.Printf("Loading builder: %s\n", name) bin, ok := c.Builders[name] @@ -101,11 +76,15 @@ func (c *config) LoadCommand(name string) (packer.Command, error) { return plugin.Command(exec.Command(commandBin)) } +// This is a proper implementation of packer.HookFunc that can be used +// to load packer.Hook implementations from the defined plugins. func (c *config) LoadHook(name string) (packer.Hook, error) { log.Printf("Loading hook: %s\n", name) return plugin.Hook(exec.Command(name)) } +// This is a proper packer.ProvisionerFunc that can be used to load +// packer.Provisioner implementations from defined plugins. func (c *config) LoadProvisioner(name string) (packer.Provisioner, error) { log.Printf("Loading provisioner: %s\n", name) provBin, ok := c.Provisioners[name] diff --git a/config_test.go b/config_test.go deleted file mode 100644 index 53e0b9d4a..000000000 --- a/config_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package main - -import ( - "cgl.tideland.biz/asserts" - "testing" -) - -func TestConfig_MergeConfig(t *testing.T) { - assert := asserts.NewTestingAsserts(t, true) - - aString := ` - [commands] - a = "1" - b = "1" - ` - - bString := ` - [commands] - a = "1" - b = "2" - c = "3" - ` - - a, _ := parseConfig(aString) - b, _ := parseConfig(bString) - result := mergeConfig(a, b) - - assert.Equal(result.Commands["a"], "1", "a should be 1") - assert.Equal(result.Commands["b"], "2", "a should be 2") - assert.Equal(result.Commands["c"], "3", "a should be 3") -} - -func TestConfig_ParseConfig_Bad(t *testing.T) { - assert := asserts.NewTestingAsserts(t, true) - - data := ` - [commands] - foo = bar - ` - - _, err := parseConfig(data) - assert.NotNil(err, "should have an error") -} - -func TestConfig_ParseConfig_DefaultConfig(t *testing.T) { - assert := asserts.NewTestingAsserts(t, true) - - _, err := parseConfig(defaultConfig) - assert.Nil(err, "should be able to parse the default config") -} - -func TestConfig_ParseConfig_Good(t *testing.T) { - assert := asserts.NewTestingAsserts(t, true) - - data := ` - [commands] - foo = "bar" - ` - - c, err := parseConfig(data) - assert.Nil(err, "should not have an error") - assert.Equal(c.CommandNames(), []string{"foo"}, "should have correct command names") - assert.Equal(c.Commands["foo"], "bar", "should have the command") -} diff --git a/packer.go b/packer.go index 6ecc42ee1..cb2c481e1 100644 --- a/packer.go +++ b/packer.go @@ -2,6 +2,7 @@ package main import ( + "bytes" "fmt" "github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer/plugin" @@ -9,47 +10,10 @@ import ( "log" "os" "os/user" - "path" + "path/filepath" "runtime" ) -func loadGlobalConfig() (result *config, err error) { - mustExist := true - p := os.Getenv("PACKER_CONFIG") - if p == "" { - var u *user.User - u, err = user.Current() - if err != nil { - return - } - - p = path.Join(u.HomeDir, ".packerrc") - mustExist = false - } - - log.Printf("Loading packer config: %s\n", p) - contents, err := ioutil.ReadFile(p) - if err != nil && !mustExist { - // Don't report an error if it is okay if the file is missing - perr, ok := err.(*os.PathError) - if ok && perr.Op == "open" { - log.Printf("Packer config didn't exist. Ignoring: %s\n", p) - err = nil - } - } - - if err != nil { - return - } - - result, err = parseConfig(string(contents)) - if err != nil { - return - } - - return -} - func main() { if os.Getenv("PACKER_LOG") == "" { // If we don't have logging explicitly enabled, then disable it @@ -64,25 +28,16 @@ func main() { runtime.GOMAXPROCS(runtime.NumCPU()) } + config, err := loadConfig() + if err != nil { + fmt.Fprintf(os.Stderr, "Error loading configuration: \n\n%s\n", err) + os.Exit(1) + } + + log.Printf("Packer config: %+v", config) + defer plugin.CleanupClients() - homeConfig, err := loadGlobalConfig() - if err != nil { - fmt.Fprintf(os.Stderr, "Error loading global Packer configuration: \n\n%s\n", err) - os.Exit(1) - } - - config, err := parseConfig(defaultConfig) - if err != nil { - fmt.Fprintf(os.Stderr, "Error parsing global Packer configuration: \n\n%s\n", err) - os.Exit(1) - } - - if homeConfig != nil { - log.Println("Merging default config with home config...") - config = mergeConfig(config, homeConfig) - } - envConfig := packer.DefaultEnvironmentConfig() envConfig.Commands = config.CommandNames() envConfig.Components.Builder = config.LoadBuilder @@ -107,3 +62,44 @@ func main() { plugin.CleanupClients() os.Exit(exitCode) } + +func loadConfig() (*config, error) { + var config config + if err := decodeConfig(bytes.NewBufferString(defaultConfig), &config); err != nil { + return nil, err + } + + mustExist := true + configFile := os.Getenv("PACKER_CONFIG") + if configFile == "" { + u, err := user.Current() + if err != nil { + return nil, err + } + + configFile = filepath.Join(u.HomeDir, ".packerrc") + mustExist = false + } + + log.Printf("Attempting to open config file: %s", configFile) + f, err := os.Open(configFile) + if err != nil { + if !os.IsNotExist(err) { + return nil, err + } + + if mustExist { + return nil, err + } + + log.Println("File doesn't exist, but doesn't need to. Ignoring.") + return &config, nil + } + defer f.Close() + + if err := decodeConfig(f, &config); err != nil { + return nil, err + } + + return &config, nil +}