diff --git a/provisioner/salt-masterless/provisioner.go b/provisioner/salt-masterless/provisioner.go index bbae5ecec..ce726630b 100644 --- a/provisioner/salt-masterless/provisioner.go +++ b/provisioner/salt-masterless/provisioner.go @@ -3,6 +3,8 @@ package saltmasterless import ( + "bytes" + "errors" "fmt" "os" "path/filepath" @@ -44,6 +46,9 @@ type Config struct { // Where files will be copied before moving to the /srv/salt directory TempConfigDir string `mapstructure:"temp_config_dir"` + // Command line args passed onto salt-call + CmdArgs string "" + ctx interpolate.Context } @@ -67,14 +72,6 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { p.config.TempConfigDir = DefaultTempConfigDir } - if p.config.RemoteStateTree == "" { - p.config.RemoteStateTree = DefaultStateTreeDir - } - - if p.config.RemotePillarRoots == "" { - p.config.RemotePillarRoots = DefaultPillarRootDir - } - var errs *packer.MultiError // require a salt state tree @@ -93,6 +90,34 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { errs = packer.MultiErrorAppend(errs, err) } + if p.config.MinionConfig != "" && (p.config.RemoteStateTree != "" || p.config.RemotePillarRoots != "") { + errs = packer.MultiErrorAppend(errs, + errors.New("minion_config option overrides remote_state_tree and remote_pillar_roots")) + } + + // build the command line args to pass onto salt + var cmd_args bytes.Buffer + + if p.config.MinionConfig == "" { + // pass --file-root and --pillar-root if no minion_config is supplied + if p.config.RemoteStateTree != "" { + cmd_args.WriteString(" --file-root=") + cmd_args.WriteString(p.config.RemoteStateTree) + } else { + cmd_args.WriteString(" --file-root=") + cmd_args.WriteString(DefaultStateTreeDir) + } + if p.config.RemotePillarRoots != "" { + cmd_args.WriteString(" --pillar-root=") + cmd_args.WriteString(p.config.RemotePillarRoots) + } else { + cmd_args.WriteString(" --pillar-root=") + cmd_args.WriteString(DefaultPillarRootDir) + } + } + + p.config.CmdArgs = cmd_args.String() + if errs != nil && len(errs.Errors) > 0 { return errs } @@ -156,7 +181,11 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { // move state tree from temporary directory src = filepath.ToSlash(filepath.Join(p.config.TempConfigDir, "states")) - dst = p.config.RemoteStateTree + if p.config.RemoteStateTree != "" { + dst = p.config.RemoteStateTree + } else { + dst = DefaultStateTreeDir + } if err = p.removeDir(ui, comm, dst); err != nil { return fmt.Errorf("Unable to clear salt tree: %s", err) } @@ -174,7 +203,11 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { // move pillar root from temporary directory src = filepath.ToSlash(filepath.Join(p.config.TempConfigDir, "pillar")) - dst = p.config.RemotePillarRoots + if p.config.RemotePillarRoots != "" { + dst = p.config.RemotePillarRoots + } else { + dst = DefaultPillarRootDir + } if err = p.removeDir(ui, comm, dst); err != nil { return fmt.Errorf("Unable to clear pillar root: %s", err) } @@ -184,7 +217,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { } ui.Message("Running highstate") - cmd := &packer.RemoteCmd{Command: fmt.Sprintf(p.sudo("salt-call --local state.highstate --file-root=%s --pillar-root=%s -l info --retcode-passthrough"), p.config.RemoteStateTree, p.config.RemotePillarRoots)} + cmd := &packer.RemoteCmd{Command: p.sudo(fmt.Sprintf("salt-call --local state.highstate -l info --retcode-passthrough %s", p.config.CmdArgs))} if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 { if err == nil { err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus) diff --git a/provisioner/salt-masterless/provisioner_test.go b/provisioner/salt-masterless/provisioner_test.go index 9c8320769..844f97c98 100644 --- a/provisioner/salt-masterless/provisioner_test.go +++ b/provisioner/salt-masterless/provisioner_test.go @@ -4,6 +4,7 @@ import ( "github.com/mitchellh/packer/packer" "io/ioutil" "os" + "strings" "testing" ) @@ -70,6 +71,30 @@ func TestProvisionerPrepare_MinionConfig(t *testing.T) { } } +func TestProvisionerPrepare_MinionConfig_RemoteStateTree(t *testing.T) { + var p Provisioner + config := testConfig() + + config["minion_config"] = "/i/dont/exist/i/think" + config["remote_state_tree"] = "/i/dont/exist/remote_state_tree" + err := p.Prepare(config) + if err == nil { + t.Fatal("minion_config and remote_state_tree should cause error") + } +} + +func TestProvisionerPrepare_MinionConfig_RemotePillarRoots(t *testing.T) { + var p Provisioner + config := testConfig() + + config["minion_config"] = "/i/dont/exist/i/think" + config["remote_pillar_roots"] = "/i/dont/exist/remote_pillar_roots" + err := p.Prepare(config) + if err == nil { + t.Fatal("minion_config and remote_pillar_roots should cause error") + } +} + func TestProvisionerPrepare_LocalStateTree(t *testing.T) { var p Provisioner config := testConfig() @@ -128,3 +153,63 @@ func TestProvisionerSudo(t *testing.T) { t.Fatalf("sudo-less command not generated correctly") } } + +func TestProvisionerPrepare_RemoteStateTree(t *testing.T) { + var p Provisioner + config := testConfig() + + config["remote_state_tree"] = "/remote_state_tree" + err := p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } + + if !strings.Contains(p.config.CmdArgs, "--file-root=/remote_state_tree") { + t.Fatal("--file-root should be set in CmdArgs") + } +} + +func TestProvisionerPrepare_RemotePillarRoots(t *testing.T) { + var p Provisioner + config := testConfig() + + config["remote_pillar_roots"] = "/remote_pillar_roots" + err := p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } + + if !strings.Contains(p.config.CmdArgs, "--pillar-root=/remote_pillar_roots") { + t.Fatal("--pillar-root should be set in CmdArgs") + } +} + +func TestProvisionerPrepare_RemoteStateTree_Default(t *testing.T) { + var p Provisioner + config := testConfig() + + // no minion_config, no remote_state_tree + err := p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } + + if !strings.Contains(p.config.CmdArgs, "--file-root=/srv/salt") { + t.Fatal("--file-root should be set in CmdArgs") + } +} + +func TestProvisionerPrepare_RemotePillarRoots_Default(t *testing.T) { + var p Provisioner + config := testConfig() + + // no minion_config, no remote_pillar_roots + err := p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } + + if !strings.Contains(p.config.CmdArgs, "--pillar-root=/srv/pillar") { + t.Fatal("--pillar-root should be set in CmdArgs") + } +} diff --git a/website/source/docs/provisioners/salt-masterless.html.markdown b/website/source/docs/provisioners/salt-masterless.html.markdown index 7e69f2351..f671ec696 100644 --- a/website/source/docs/provisioners/salt-masterless.html.markdown +++ b/website/source/docs/provisioners/salt-masterless.html.markdown @@ -43,11 +43,11 @@ Optional: - `remote_pillar_roots` (string) - The path to your remote [pillar roots](http://docs.saltstack.com/ref/configuration/master.html#pillar-configuration). - default: `/srv/pillar`. + default: `/srv/pillar`. This option cannot be used with `minion_config`. - `remote_state_tree` (string) - The path to your remote [state tree](http://docs.saltstack.com/ref/states/highstate.html#the-salt-state-tree). - default: `/srv/salt`. + default: `/srv/salt`. This option cannot be used with `minion_config`. - `local_pillar_roots` (string) - The path to your local [pillar roots](http://docs.saltstack.com/ref/configuration/master.html#pillar-configuration). @@ -59,7 +59,8 @@ Optional: - `minion_config` (string) - The path to your local [minion config file](http://docs.saltstack.com/ref/configuration/minion.html). This will be - uploaded to the `/etc/salt` on the remote. + uploaded to the `/etc/salt` on the remote. This option overrides the + `remote_state_tree` or `remote_pillar_roots` options. - `skip_bootstrap` (boolean) - By default the salt provisioner runs [salt bootstrap](https://github.com/saltstack/salt-bootstrap) to install salt. Set