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