github.com/supr/packer@v0.3.10-0.20131015195147-7b09e24ac3c1/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 "path/filepath" 12 "strings" 13 ) 14 15 const DefaultTempConfigDir = "/tmp/salt" 16 17 type Config struct { 18 common.PackerConfig `mapstructure:",squash"` 19 20 // If true, run the salt-bootstrap script 21 SkipBootstrap bool `mapstructure:"skip_bootstrap"` 22 BootstrapArgs string `mapstructure:"bootstrap_args"` 23 24 // Local path to the minion config 25 MinionConfig string `mapstructure:"minion_config"` 26 27 // Local path to the salt state tree 28 LocalStateTree string `mapstructure:"local_state_tree"` 29 30 // Local path to the salt pillar roots 31 LocalPillarRoots string `mapstructure:"local_pillar_roots"` 32 33 // Where files will be copied before moving to the /srv/salt directory 34 TempConfigDir string `mapstructure:"temp_config_dir"` 35 36 tpl *packer.ConfigTemplate 37 } 38 39 type Provisioner struct { 40 config Config 41 } 42 43 func (p *Provisioner) Prepare(raws ...interface{}) error { 44 md, err := common.DecodeConfig(&p.config, raws...) 45 if err != nil { 46 return err 47 } 48 49 p.config.tpl, err = packer.NewConfigTemplate() 50 if err != nil { 51 return err 52 } 53 p.config.tpl.UserVars = p.config.PackerUserVars 54 55 if p.config.TempConfigDir == "" { 56 p.config.TempConfigDir = DefaultTempConfigDir 57 } 58 59 // Accumulate any errors 60 errs := common.CheckUnusedConfig(md) 61 62 templates := map[string]*string{ 63 "bootstrap_args": &p.config.BootstrapArgs, 64 "minion_config": &p.config.MinionConfig, 65 "local_state_tree": &p.config.LocalStateTree, 66 "local_pillar_roots": &p.config.LocalPillarRoots, 67 "temp_config_dir": &p.config.TempConfigDir, 68 } 69 70 for n, ptr := range templates { 71 var err error 72 *ptr, err = p.config.tpl.Process(*ptr, nil) 73 if err != nil { 74 errs = packer.MultiErrorAppend( 75 errs, fmt.Errorf("Error processing %s: %s", n, err)) 76 } 77 } 78 79 if p.config.LocalStateTree != "" { 80 if _, err := os.Stat(p.config.LocalStateTree); err != nil { 81 errs = packer.MultiErrorAppend(errs, 82 errors.New("local_state_tree must exist and be accessible")) 83 } 84 } 85 86 if p.config.LocalPillarRoots != "" { 87 if _, err := os.Stat(p.config.LocalPillarRoots); err != nil { 88 errs = packer.MultiErrorAppend(errs, 89 errors.New("local_pillar_roots must exist and be accessible")) 90 } 91 } 92 93 if p.config.MinionConfig != "" { 94 if _, err := os.Stat(p.config.MinionConfig); err != nil { 95 errs = packer.MultiErrorAppend(errs, 96 errors.New("minion_config must exist and be accessible")) 97 } 98 } 99 100 if errs != nil && len(errs.Errors) > 0 { 101 return errs 102 } 103 104 return nil 105 } 106 107 func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { 108 var err error 109 110 ui.Say("Provisioning with Salt...") 111 if !p.config.SkipBootstrap { 112 cmd := &packer.RemoteCmd{ 113 Command: fmt.Sprintf("wget -O - http://bootstrap.saltstack.org | sudo sh -s %s", p.config.BootstrapArgs), 114 } 115 ui.Message(fmt.Sprintf("Installing Salt with command %s", cmd)) 116 if err = cmd.StartWithUi(comm, ui); err != nil { 117 return fmt.Errorf("Unable to install Salt: %d", err) 118 } 119 } 120 121 ui.Message(fmt.Sprintf("Creating remote directory: %s", p.config.TempConfigDir)) 122 cmd := &packer.RemoteCmd{Command: fmt.Sprintf("mkdir -p %s", p.config.TempConfigDir)} 123 if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 { 124 if err == nil { 125 err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus) 126 } 127 128 return fmt.Errorf("Error creating remote salt state directory: %s", err) 129 } 130 131 if p.config.MinionConfig != "" { 132 ui.Message(fmt.Sprintf("Uploading minion config: %s", p.config.MinionConfig)) 133 if err = uploadMinionConfig(comm, fmt.Sprintf("%s/minion", p.config.TempConfigDir), p.config.MinionConfig); err != nil { 134 return fmt.Errorf("Error uploading local minion config file to remote: %s", err) 135 } 136 137 ui.Message(fmt.Sprintf("Moving %s/minion to /etc/salt/minion", p.config.TempConfigDir)) 138 cmd = &packer.RemoteCmd{Command: fmt.Sprintf("sudo mv %s/minion /etc/salt/minion", p.config.TempConfigDir)} 139 if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 { 140 if err == nil { 141 err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus) 142 } 143 144 return fmt.Errorf("Unable to move %s/minion to /etc/salt/minion: %d", p.config.TempConfigDir, err) 145 } 146 } 147 148 ui.Message(fmt.Sprintf("Uploading local state tree: %s", p.config.LocalStateTree)) 149 if err = UploadLocalDirectory(p.config.LocalStateTree, fmt.Sprintf("%s/states", p.config.TempConfigDir), comm, ui); err != nil { 150 return fmt.Errorf("Error uploading local state tree to remote: %s", err) 151 } 152 153 ui.Message(fmt.Sprintf("Moving %s to /srv/salt", p.config.TempConfigDir)) 154 cmd = &packer.RemoteCmd{Command: fmt.Sprintf("sudo mv %s/states /srv/salt/", p.config.TempConfigDir)} 155 if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 { 156 if err == nil { 157 err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus) 158 } 159 160 return fmt.Errorf("Unable to move %s/states to /srv/salt: %d", p.config.TempConfigDir, err) 161 } 162 163 if p.config.LocalPillarRoots != "" { 164 ui.Message(fmt.Sprintf("Creating remote pillar directory: %s/pillar", p.config.TempConfigDir)) 165 cmd := &packer.RemoteCmd{Command: fmt.Sprintf("mkdir -p %s/pillar", p.config.TempConfigDir)} 166 if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 { 167 if err == nil { 168 err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus) 169 } 170 171 return fmt.Errorf("Error creating remote pillar directory: %s", err) 172 } 173 174 ui.Message(fmt.Sprintf("Uploading local pillar roots: %s", p.config.LocalPillarRoots)) 175 if err = UploadLocalDirectory(p.config.LocalPillarRoots, fmt.Sprintf("%s/pillar", p.config.TempConfigDir), comm, ui); err != nil { 176 return fmt.Errorf("Error uploading local pillar roots to remote: %s", err) 177 } 178 179 ui.Message(fmt.Sprintf("Moving %s/pillar to /srv/pillar", p.config.TempConfigDir)) 180 cmd = &packer.RemoteCmd{Command: fmt.Sprintf("sudo mv %s/pillar /srv/pillar", p.config.TempConfigDir)} 181 if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 { 182 if err == nil { 183 err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus) 184 } 185 186 return fmt.Errorf("Unable to move %s/pillar to /srv/pillar: %d", p.config.TempConfigDir, err) 187 } 188 } 189 190 ui.Message("Running highstate") 191 cmd = &packer.RemoteCmd{Command: "sudo salt-call --local state.highstate -l info"} 192 if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 { 193 if err == nil { 194 err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus) 195 } 196 197 return fmt.Errorf("Error executing highstate: %s", err) 198 } 199 200 return nil 201 } 202 203 func (p *Provisioner) Cancel() { 204 // Just hard quit. It isn't a big deal if what we're doing keeps 205 // running on the other side. 206 os.Exit(0) 207 } 208 209 func UploadLocalDirectory(localDir string, remoteDir string, comm packer.Communicator, ui packer.Ui) (err error) { 210 visitPath := func(localPath string, f os.FileInfo, err error) (err2 error) { 211 localRelPath := strings.Replace(localPath, localDir, "", 1) 212 localRelPath = strings.Replace(localRelPath, "\\", "/", -1) 213 remotePath := filepath.Join(remoteDir, localRelPath) 214 if f.IsDir() && f.Name() == ".git" { 215 return filepath.SkipDir 216 } 217 if f.IsDir() { 218 // Make remote directory 219 cmd := &packer.RemoteCmd{Command: fmt.Sprintf("mkdir -p %s", remotePath)} 220 if err = cmd.StartWithUi(comm, ui); err != nil { 221 return err 222 } 223 } else { 224 // Upload file to existing directory 225 file, err := os.Open(localPath) 226 if err != nil { 227 return fmt.Errorf("Error opening file: %s", err) 228 } 229 defer file.Close() 230 231 ui.Message(fmt.Sprintf("Uploading file %s: %s", localPath, remotePath)) 232 if err = comm.Upload(remotePath, file); err != nil { 233 return fmt.Errorf("Error uploading file: %s", err) 234 } 235 } 236 return 237 } 238 239 err = filepath.Walk(localDir, visitPath) 240 if err != nil { 241 return fmt.Errorf("Error uploading local directory %s: %s", localDir, err) 242 } 243 244 return nil 245 } 246 247 func uploadMinionConfig(comm packer.Communicator, dst string, src string) error { 248 f, err := os.Open(src) 249 if err != nil { 250 return fmt.Errorf("Error opening minion config: %s", err) 251 } 252 defer f.Close() 253 254 if err = comm.Upload(dst, f); err != nil { 255 return fmt.Errorf("Error uploading minion config: %s", err) 256 } 257 258 return nil 259 }