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