github.com/daniellockard/packer@v0.7.6-0.20141210173435-5a9390934716/provisioner/ansible-local/provisioner.go (about) 1 package ansiblelocal 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 "strings" 9 10 "github.com/mitchellh/packer/common" 11 "github.com/mitchellh/packer/packer" 12 ) 13 14 const DefaultStagingDir = "/tmp/packer-provisioner-ansible-local" 15 16 type Config struct { 17 common.PackerConfig `mapstructure:",squash"` 18 tpl *packer.ConfigTemplate 19 20 // The command to run ansible 21 Command string 22 23 // Extra options to pass to the ansible command 24 ExtraArguments []string `mapstructure:"extra_arguments"` 25 26 // Path to group_vars directory 27 GroupVars string `mapstructure:"group_vars"` 28 29 // Path to host_vars directory 30 HostVars string `mapstructure:"host_vars"` 31 32 // The playbook dir to upload. 33 PlaybookDir string `mapstructure:"playbook_dir"` 34 35 // The main playbook file to execute. 36 PlaybookFile string `mapstructure:"playbook_file"` 37 38 // An array of local paths of playbook files to upload. 39 PlaybookPaths []string `mapstructure:"playbook_paths"` 40 41 // An array of local paths of roles to upload. 42 RolePaths []string `mapstructure:"role_paths"` 43 44 // The directory where files will be uploaded. Packer requires write 45 // permissions in this directory. 46 StagingDir string `mapstructure:"staging_directory"` 47 48 // The optional inventory file 49 InventoryFile string `mapstructure:"inventory_file"` 50 } 51 52 type Provisioner struct { 53 config Config 54 } 55 56 func (p *Provisioner) Prepare(raws ...interface{}) error { 57 md, err := common.DecodeConfig(&p.config, raws...) 58 if err != nil { 59 return err 60 } 61 62 p.config.tpl, err = packer.NewConfigTemplate() 63 if err != nil { 64 return err 65 } 66 67 p.config.tpl.UserVars = p.config.PackerUserVars 68 69 // Accumulate any errors 70 errs := common.CheckUnusedConfig(md) 71 72 // Defaults 73 if p.config.Command == "" { 74 p.config.Command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 ansible-playbook" 75 } 76 77 if p.config.StagingDir == "" { 78 p.config.StagingDir = DefaultStagingDir 79 } 80 81 // Templates 82 templates := map[string]*string{ 83 "command": &p.config.Command, 84 "group_vars": &p.config.GroupVars, 85 "host_vars": &p.config.HostVars, 86 "playbook_file": &p.config.PlaybookFile, 87 "playbook_dir": &p.config.PlaybookDir, 88 "staging_dir": &p.config.StagingDir, 89 "inventory_file": &p.config.InventoryFile, 90 } 91 92 for n, ptr := range templates { 93 var err error 94 *ptr, err = p.config.tpl.Process(*ptr, nil) 95 if err != nil { 96 errs = packer.MultiErrorAppend( 97 errs, fmt.Errorf("Error processing %s: %s", n, err)) 98 } 99 } 100 101 sliceTemplates := map[string][]string{ 102 "extra_arguments": p.config.ExtraArguments, 103 "playbook_paths": p.config.PlaybookPaths, 104 "role_paths": p.config.RolePaths, 105 } 106 107 for n, slice := range sliceTemplates { 108 for i, elem := range slice { 109 var err error 110 slice[i], err = p.config.tpl.Process(elem, nil) 111 if err != nil { 112 errs = packer.MultiErrorAppend( 113 errs, fmt.Errorf("Error processing %s[%d]: %s", n, i, err)) 114 } 115 } 116 } 117 118 // Validation 119 err = validateFileConfig(p.config.PlaybookFile, "playbook_file", true) 120 if err != nil { 121 errs = packer.MultiErrorAppend(errs, err) 122 } 123 124 // Check that the inventory file exists, if configured 125 if len(p.config.InventoryFile) > 0 { 126 err = validateFileConfig(p.config.InventoryFile, "inventory_file", true) 127 if err != nil { 128 errs = packer.MultiErrorAppend(errs, err) 129 } 130 } 131 132 // Check that the playbook_dir directory exists, if configured 133 if len(p.config.PlaybookDir) > 0 { 134 if err := validateDirConfig(p.config.PlaybookDir, "playbook_dir"); err != nil { 135 errs = packer.MultiErrorAppend(errs, err) 136 } 137 } 138 139 // Check that the group_vars directory exists, if configured 140 if len(p.config.GroupVars) > 0 { 141 if err := validateDirConfig(p.config.GroupVars, "group_vars"); err != nil { 142 errs = packer.MultiErrorAppend(errs, err) 143 } 144 } 145 146 // Check that the host_vars directory exists, if configured 147 if len(p.config.HostVars) > 0 { 148 if err := validateDirConfig(p.config.HostVars, "host_vars"); err != nil { 149 errs = packer.MultiErrorAppend(errs, err) 150 } 151 } 152 153 for _, path := range p.config.PlaybookPaths { 154 err := validateDirConfig(path, "playbook_paths") 155 if err != nil { 156 errs = packer.MultiErrorAppend(errs, err) 157 } 158 } 159 for _, path := range p.config.RolePaths { 160 if err := validateDirConfig(path, "role_paths"); err != nil { 161 errs = packer.MultiErrorAppend(errs, err) 162 } 163 } 164 165 if errs != nil && len(errs.Errors) > 0 { 166 return errs 167 } 168 return nil 169 } 170 171 func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { 172 ui.Say("Provisioning with Ansible...") 173 174 if len(p.config.PlaybookDir) > 0 { 175 ui.Message("Uploading Playbook directory to Ansible staging directory...") 176 if err := p.uploadDir(ui, comm, p.config.StagingDir, p.config.PlaybookDir); err != nil { 177 return fmt.Errorf("Error uploading playbook_dir directory: %s", err) 178 } 179 } else { 180 ui.Message("Creating Ansible staging directory...") 181 if err := p.createDir(ui, comm, p.config.StagingDir); err != nil { 182 return fmt.Errorf("Error creating staging directory: %s", err) 183 } 184 } 185 186 ui.Message("Uploading main Playbook file...") 187 src := p.config.PlaybookFile 188 dst := filepath.ToSlash(filepath.Join(p.config.StagingDir, filepath.Base(src))) 189 if err := p.uploadFile(ui, comm, dst, src); err != nil { 190 return fmt.Errorf("Error uploading main playbook: %s", err) 191 } 192 193 if len(p.config.InventoryFile) == 0 { 194 tf, err := ioutil.TempFile("", "packer-provisioner-ansible-local") 195 if err != nil { 196 return fmt.Errorf("Error preparing inventory file: %s", err) 197 } 198 defer os.Remove(tf.Name()) 199 _, err = tf.Write([]byte("127.0.0.1")) 200 if err != nil { 201 tf.Close() 202 return fmt.Errorf("Error preparing inventory file: %s", err) 203 } 204 tf.Close() 205 p.config.InventoryFile = tf.Name() 206 defer func() { 207 p.config.InventoryFile = "" 208 }() 209 } 210 211 ui.Message("Uploading inventory file...") 212 src = p.config.InventoryFile 213 dst = filepath.ToSlash(filepath.Join(p.config.StagingDir, filepath.Base(src))) 214 if err := p.uploadFile(ui, comm, dst, src); err != nil { 215 return fmt.Errorf("Error uploading inventory file: %s", err) 216 } 217 218 if len(p.config.GroupVars) > 0 { 219 ui.Message("Uploading group_vars directory...") 220 src := p.config.GroupVars 221 dst := filepath.ToSlash(filepath.Join(p.config.StagingDir, "group_vars")) 222 if err := p.uploadDir(ui, comm, dst, src); err != nil { 223 return fmt.Errorf("Error uploading group_vars directory: %s", err) 224 } 225 } 226 227 if len(p.config.HostVars) > 0 { 228 ui.Message("Uploading host_vars directory...") 229 src := p.config.HostVars 230 dst := filepath.ToSlash(filepath.Join(p.config.StagingDir, "host_vars")) 231 if err := p.uploadDir(ui, comm, dst, src); err != nil { 232 return fmt.Errorf("Error uploading host_vars directory: %s", err) 233 } 234 } 235 236 if len(p.config.RolePaths) > 0 { 237 ui.Message("Uploading role directories...") 238 for _, src := range p.config.RolePaths { 239 dst := filepath.ToSlash(filepath.Join(p.config.StagingDir, "roles", filepath.Base(src))) 240 if err := p.uploadDir(ui, comm, dst, src); err != nil { 241 return fmt.Errorf("Error uploading roles: %s", err) 242 } 243 } 244 } 245 246 if len(p.config.PlaybookPaths) > 0 { 247 ui.Message("Uploading additional Playbooks...") 248 playbookDir := filepath.ToSlash(filepath.Join(p.config.StagingDir, "playbooks")) 249 if err := p.createDir(ui, comm, playbookDir); err != nil { 250 return fmt.Errorf("Error creating playbooks directory: %s", err) 251 } 252 for _, src := range p.config.PlaybookPaths { 253 dst := filepath.ToSlash(filepath.Join(playbookDir, filepath.Base(src))) 254 if err := p.uploadDir(ui, comm, dst, src); err != nil { 255 return fmt.Errorf("Error uploading playbooks: %s", err) 256 } 257 } 258 } 259 260 if err := p.executeAnsible(ui, comm); err != nil { 261 return fmt.Errorf("Error executing Ansible: %s", err) 262 } 263 return nil 264 } 265 266 func (p *Provisioner) Cancel() { 267 // Just hard quit. It isn't a big deal if what we're doing keeps 268 // running on the other side. 269 os.Exit(0) 270 } 271 272 func (p *Provisioner) executeAnsible(ui packer.Ui, comm packer.Communicator) error { 273 playbook := filepath.ToSlash(filepath.Join(p.config.StagingDir, filepath.Base(p.config.PlaybookFile))) 274 inventory := filepath.ToSlash(filepath.Join(p.config.StagingDir, filepath.Base(p.config.InventoryFile))) 275 276 extraArgs := "" 277 if len(p.config.ExtraArguments) > 0 { 278 extraArgs = " " + strings.Join(p.config.ExtraArguments, " ") 279 } 280 281 command := fmt.Sprintf("cd %s && %s %s%s -c local -i %s", 282 p.config.StagingDir, p.config.Command, playbook, extraArgs, inventory) 283 ui.Message(fmt.Sprintf("Executing Ansible: %s", command)) 284 cmd := &packer.RemoteCmd{ 285 Command: command, 286 } 287 if err := cmd.StartWithUi(comm, ui); err != nil { 288 return err 289 } 290 if cmd.ExitStatus != 0 { 291 if cmd.ExitStatus == 127 { 292 return fmt.Errorf("%s could not be found. Verify that it is available on the\n"+ 293 "PATH after connecting to the machine.", 294 p.config.Command) 295 } 296 297 return fmt.Errorf("Non-zero exit status: %d", cmd.ExitStatus) 298 } 299 return nil 300 } 301 302 func validateDirConfig(path string, config string) error { 303 info, err := os.Stat(path) 304 if err != nil { 305 return fmt.Errorf("%s: %s is invalid: %s", config, path, err) 306 } else if !info.IsDir() { 307 return fmt.Errorf("%s: %s must point to a directory", config, path) 308 } 309 return nil 310 } 311 312 func validateFileConfig(name string, config string, req bool) error { 313 if req { 314 if name == "" { 315 return fmt.Errorf("%s must be specified.", config) 316 } 317 } 318 info, err := os.Stat(name) 319 if err != nil { 320 return fmt.Errorf("%s: %s is invalid: %s", config, name, err) 321 } else if info.IsDir() { 322 return fmt.Errorf("%s: %s must point to a file", config, name) 323 } 324 return nil 325 } 326 327 func (p *Provisioner) uploadFile(ui packer.Ui, comm packer.Communicator, dst, src string) error { 328 f, err := os.Open(src) 329 if err != nil { 330 return fmt.Errorf("Error opening: %s", err) 331 } 332 defer f.Close() 333 334 if err = comm.Upload(dst, f, nil); err != nil { 335 return fmt.Errorf("Error uploading %s: %s", src, err) 336 } 337 return nil 338 } 339 340 func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error { 341 ui.Message(fmt.Sprintf("Creating directory: %s", dir)) 342 cmd := &packer.RemoteCmd{ 343 Command: fmt.Sprintf("mkdir -p '%s'", dir), 344 } 345 if err := cmd.StartWithUi(comm, ui); err != nil { 346 return err 347 } 348 if cmd.ExitStatus != 0 { 349 return fmt.Errorf("Non-zero exit status.") 350 } 351 return nil 352 } 353 354 func (p *Provisioner) uploadDir(ui packer.Ui, comm packer.Communicator, dst, src string) error { 355 if err := p.createDir(ui, comm, dst); err != nil { 356 return err 357 } 358 359 // Make sure there is a trailing "/" so that the directory isn't 360 // created on the other side. 361 if src[len(src)-1] != '/' { 362 src = src + "/" 363 } 364 return comm.UploadDir(dst, src, nil) 365 }