github.com/askholme/packer@v0.7.2-0.20140924152349-70d9566a6852/provisioner/salt-masterless/provisioner.go (about) 1 // This package implements a provisioner for Packer that executes a 2 // saltstack highstate within the remote machine 3 package saltmasterless 4 5 import ( 6 "errors" 7 "fmt" 8 "github.com/mitchellh/packer/common" 9 "github.com/mitchellh/packer/packer" 10 "os" 11 ) 12 13 const DefaultTempConfigDir = "/tmp/salt" 14 15 type Config struct { 16 common.PackerConfig `mapstructure:",squash"` 17 18 // If true, run the salt-bootstrap script 19 SkipBootstrap bool `mapstructure:"skip_bootstrap"` 20 BootstrapArgs string `mapstructure:"bootstrap_args"` 21 22 // Local path to the minion config 23 MinionConfig string `mapstructure:"minion_config"` 24 25 // Local path to the salt state tree 26 LocalStateTree string `mapstructure:"local_state_tree"` 27 28 // Local path to the salt pillar roots 29 LocalPillarRoots string `mapstructure:"local_pillar_roots"` 30 31 // Where files will be copied before moving to the /srv/salt directory 32 TempConfigDir string `mapstructure:"temp_config_dir"` 33 34 tpl *packer.ConfigTemplate 35 } 36 37 type Provisioner struct { 38 config Config 39 } 40 41 func (p *Provisioner) Prepare(raws ...interface{}) error { 42 md, err := common.DecodeConfig(&p.config, raws...) 43 if err != nil { 44 return err 45 } 46 47 p.config.tpl, err = packer.NewConfigTemplate() 48 if err != nil { 49 return err 50 } 51 p.config.tpl.UserVars = p.config.PackerUserVars 52 53 if p.config.TempConfigDir == "" { 54 p.config.TempConfigDir = DefaultTempConfigDir 55 } 56 57 // Accumulate any errors 58 errs := common.CheckUnusedConfig(md) 59 60 templates := map[string]*string{ 61 "bootstrap_args": &p.config.BootstrapArgs, 62 "minion_config": &p.config.MinionConfig, 63 "local_state_tree": &p.config.LocalStateTree, 64 "local_pillar_roots": &p.config.LocalPillarRoots, 65 "temp_config_dir": &p.config.TempConfigDir, 66 } 67 68 for n, ptr := range templates { 69 var err error 70 *ptr, err = p.config.tpl.Process(*ptr, nil) 71 if err != nil { 72 errs = packer.MultiErrorAppend( 73 errs, fmt.Errorf("Error processing %s: %s", n, err)) 74 } 75 } 76 77 if p.config.LocalStateTree != "" { 78 if _, err := os.Stat(p.config.LocalStateTree); err != nil { 79 errs = packer.MultiErrorAppend(errs, 80 errors.New("local_state_tree must exist and be accessible")) 81 } 82 } 83 84 if p.config.LocalPillarRoots != "" { 85 if _, err := os.Stat(p.config.LocalPillarRoots); err != nil { 86 errs = packer.MultiErrorAppend(errs, 87 errors.New("local_pillar_roots must exist and be accessible")) 88 } 89 } 90 91 if p.config.MinionConfig != "" { 92 if _, err := os.Stat(p.config.MinionConfig); err != nil { 93 errs = packer.MultiErrorAppend(errs, 94 errors.New("minion_config must exist and be accessible")) 95 } 96 } 97 98 if errs != nil && len(errs.Errors) > 0 { 99 return errs 100 } 101 102 return nil 103 } 104 105 func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { 106 var err error 107 108 ui.Say("Provisioning with Salt...") 109 if !p.config.SkipBootstrap { 110 cmd := &packer.RemoteCmd{ 111 Command: fmt.Sprintf("wget -O - http://bootstrap.saltstack.org | sudo sh -s %s", p.config.BootstrapArgs), 112 } 113 ui.Message(fmt.Sprintf("Installing Salt with command %s", cmd)) 114 if err = cmd.StartWithUi(comm, ui); err != nil { 115 return fmt.Errorf("Unable to install Salt: %d", err) 116 } 117 } 118 119 ui.Message(fmt.Sprintf("Creating remote directory: %s", p.config.TempConfigDir)) 120 cmd := &packer.RemoteCmd{Command: fmt.Sprintf("mkdir -p %s", p.config.TempConfigDir)} 121 if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 { 122 if err == nil { 123 err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus) 124 } 125 126 return fmt.Errorf("Error creating remote salt state directory: %s", err) 127 } 128 129 if p.config.MinionConfig != "" { 130 ui.Message(fmt.Sprintf("Uploading minion config: %s", p.config.MinionConfig)) 131 if err = uploadMinionConfig(comm, fmt.Sprintf("%s/minion", p.config.TempConfigDir), p.config.MinionConfig); err != nil { 132 return fmt.Errorf("Error uploading local minion config file to remote: %s", err) 133 } 134 135 ui.Message(fmt.Sprintf("Moving %s/minion to /etc/salt/minion", p.config.TempConfigDir)) 136 cmd = &packer.RemoteCmd{Command: fmt.Sprintf("sudo mv %s/minion /etc/salt/minion", p.config.TempConfigDir)} 137 if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 { 138 if err == nil { 139 err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus) 140 } 141 142 return fmt.Errorf("Unable to move %s/minion to /etc/salt/minion: %d", p.config.TempConfigDir, err) 143 } 144 } 145 146 ui.Message(fmt.Sprintf("Uploading local state tree: %s", p.config.LocalStateTree)) 147 if err = comm.UploadDir(fmt.Sprintf("%s/states", p.config.TempConfigDir), 148 p.config.LocalStateTree, []string{".git"}); err != nil { 149 return fmt.Errorf("Error uploading local state tree to remote: %s", err) 150 } 151 152 ui.Message(fmt.Sprintf("Moving %s/states to /srv/salt", p.config.TempConfigDir)) 153 cmd = &packer.RemoteCmd{Command: fmt.Sprintf("sudo mv %s/states /srv/salt", p.config.TempConfigDir)} 154 if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 { 155 if err == nil { 156 err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus) 157 } 158 159 return fmt.Errorf("Unable to move %s/states to /srv/salt: %d", p.config.TempConfigDir, err) 160 } 161 162 if p.config.LocalPillarRoots != "" { 163 ui.Message(fmt.Sprintf("Uploading local pillar roots: %s", p.config.LocalPillarRoots)) 164 if err = comm.UploadDir(fmt.Sprintf("%s/pillar", p.config.TempConfigDir), 165 p.config.LocalPillarRoots, []string{".git"}); err != nil { 166 return fmt.Errorf("Error uploading local pillar roots to remote: %s", err) 167 } 168 169 ui.Message(fmt.Sprintf("Moving %s/pillar to /srv/pillar", p.config.TempConfigDir)) 170 cmd = &packer.RemoteCmd{Command: fmt.Sprintf("sudo mv %s/pillar /srv/pillar", p.config.TempConfigDir)} 171 if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 { 172 if err == nil { 173 err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus) 174 } 175 176 return fmt.Errorf("Unable to move %s/pillar to /srv/pillar: %d", p.config.TempConfigDir, err) 177 } 178 } 179 180 ui.Message("Running highstate") 181 cmd = &packer.RemoteCmd{Command: "sudo salt-call --local state.highstate -l info"} 182 if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 { 183 if err == nil { 184 err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus) 185 } 186 187 return fmt.Errorf("Error executing highstate: %s", err) 188 } 189 190 return nil 191 } 192 193 func (p *Provisioner) Cancel() { 194 // Just hard quit. It isn't a big deal if what we're doing keeps 195 // running on the other side. 196 os.Exit(0) 197 } 198 199 func uploadMinionConfig(comm packer.Communicator, dst string, src string) error { 200 f, err := os.Open(src) 201 if err != nil { 202 return fmt.Errorf("Error opening minion config: %s", err) 203 } 204 defer f.Close() 205 206 if err = comm.Upload(dst, f, nil); err != nil { 207 return fmt.Errorf("Error uploading minion config: %s", err) 208 } 209 210 return nil 211 }