github.com/dacamp/packer@v0.10.2/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 57 type Provisioner struct { 58 config Config 59 } 60 61 func (p *Provisioner) Prepare(raws ...interface{}) error { 62 err := config.Decode(&p.config, &config.DecodeOpts{ 63 Interpolate: true, 64 InterpolateContext: &p.config.ctx, 65 InterpolateFilter: &interpolate.RenderFilter{ 66 Exclude: []string{}, 67 }, 68 }, raws...) 69 if err != nil { 70 return err 71 } 72 73 // Defaults 74 if p.config.Command == "" { 75 p.config.Command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 ansible-playbook" 76 } 77 78 if p.config.StagingDir == "" { 79 p.config.StagingDir = DefaultStagingDir 80 } 81 82 // Validation 83 var errs *packer.MultiError 84 err = validateFileConfig(p.config.PlaybookFile, "playbook_file", true) 85 if err != nil { 86 errs = packer.MultiErrorAppend(errs, err) 87 } 88 89 // Check that the inventory file exists, if configured 90 if len(p.config.InventoryFile) > 0 { 91 err = validateFileConfig(p.config.InventoryFile, "inventory_file", true) 92 if err != nil { 93 errs = packer.MultiErrorAppend(errs, err) 94 } 95 } 96 97 // Check that the playbook_dir directory exists, if configured 98 if len(p.config.PlaybookDir) > 0 { 99 if err := validateDirConfig(p.config.PlaybookDir, "playbook_dir"); err != nil { 100 errs = packer.MultiErrorAppend(errs, err) 101 } 102 } 103 104 // Check that the group_vars directory exists, if configured 105 if len(p.config.GroupVars) > 0 { 106 if err := validateDirConfig(p.config.GroupVars, "group_vars"); err != nil { 107 errs = packer.MultiErrorAppend(errs, err) 108 } 109 } 110 111 // Check that the host_vars directory exists, if configured 112 if len(p.config.HostVars) > 0 { 113 if err := validateDirConfig(p.config.HostVars, "host_vars"); err != nil { 114 errs = packer.MultiErrorAppend(errs, err) 115 } 116 } 117 118 for _, path := range p.config.PlaybookPaths { 119 err := validateDirConfig(path, "playbook_paths") 120 if err != nil { 121 errs = packer.MultiErrorAppend(errs, err) 122 } 123 } 124 for _, path := range p.config.RolePaths { 125 if err := validateDirConfig(path, "role_paths"); err != nil { 126 errs = packer.MultiErrorAppend(errs, err) 127 } 128 } 129 130 if errs != nil && len(errs.Errors) > 0 { 131 return errs 132 } 133 return nil 134 } 135 136 func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { 137 ui.Say("Provisioning with Ansible...") 138 139 if len(p.config.PlaybookDir) > 0 { 140 ui.Message("Uploading Playbook directory to Ansible staging directory...") 141 if err := p.uploadDir(ui, comm, p.config.StagingDir, p.config.PlaybookDir); err != nil { 142 return fmt.Errorf("Error uploading playbook_dir directory: %s", err) 143 } 144 } else { 145 ui.Message("Creating Ansible staging directory...") 146 if err := p.createDir(ui, comm, p.config.StagingDir); err != nil { 147 return fmt.Errorf("Error creating staging directory: %s", err) 148 } 149 } 150 151 ui.Message("Uploading main Playbook file...") 152 src := p.config.PlaybookFile 153 dst := filepath.ToSlash(filepath.Join(p.config.StagingDir, filepath.Base(src))) 154 if err := p.uploadFile(ui, comm, dst, src); err != nil { 155 return fmt.Errorf("Error uploading main playbook: %s", err) 156 } 157 158 if len(p.config.InventoryFile) == 0 { 159 tf, err := ioutil.TempFile("", "packer-provisioner-ansible-local") 160 if err != nil { 161 return fmt.Errorf("Error preparing inventory file: %s", err) 162 } 163 defer os.Remove(tf.Name()) 164 if len(p.config.InventoryGroups) != 0 { 165 content := "" 166 for _, group := range p.config.InventoryGroups { 167 content += fmt.Sprintf("[%s]\n127.0.0.1\n", group) 168 } 169 _, err = tf.Write([]byte(content)) 170 } else { 171 _, err = tf.Write([]byte("127.0.0.1")) 172 } 173 if err != nil { 174 tf.Close() 175 return fmt.Errorf("Error preparing inventory file: %s", err) 176 } 177 tf.Close() 178 p.config.InventoryFile = tf.Name() 179 defer func() { 180 p.config.InventoryFile = "" 181 }() 182 } 183 184 ui.Message("Uploading inventory file...") 185 src = p.config.InventoryFile 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 inventory file: %s", err) 189 } 190 191 if len(p.config.GroupVars) > 0 { 192 ui.Message("Uploading group_vars directory...") 193 src := p.config.GroupVars 194 dst := filepath.ToSlash(filepath.Join(p.config.StagingDir, "group_vars")) 195 if err := p.uploadDir(ui, comm, dst, src); err != nil { 196 return fmt.Errorf("Error uploading group_vars directory: %s", err) 197 } 198 } 199 200 if len(p.config.HostVars) > 0 { 201 ui.Message("Uploading host_vars directory...") 202 src := p.config.HostVars 203 dst := filepath.ToSlash(filepath.Join(p.config.StagingDir, "host_vars")) 204 if err := p.uploadDir(ui, comm, dst, src); err != nil { 205 return fmt.Errorf("Error uploading host_vars directory: %s", err) 206 } 207 } 208 209 if len(p.config.RolePaths) > 0 { 210 ui.Message("Uploading role directories...") 211 for _, src := range p.config.RolePaths { 212 dst := filepath.ToSlash(filepath.Join(p.config.StagingDir, "roles", filepath.Base(src))) 213 if err := p.uploadDir(ui, comm, dst, src); err != nil { 214 return fmt.Errorf("Error uploading roles: %s", err) 215 } 216 } 217 } 218 219 if len(p.config.PlaybookPaths) > 0 { 220 ui.Message("Uploading additional Playbooks...") 221 playbookDir := filepath.ToSlash(filepath.Join(p.config.StagingDir, "playbooks")) 222 if err := p.createDir(ui, comm, playbookDir); err != nil { 223 return fmt.Errorf("Error creating playbooks directory: %s", err) 224 } 225 for _, src := range p.config.PlaybookPaths { 226 dst := filepath.ToSlash(filepath.Join(playbookDir, filepath.Base(src))) 227 if err := p.uploadDir(ui, comm, dst, src); err != nil { 228 return fmt.Errorf("Error uploading playbooks: %s", err) 229 } 230 } 231 } 232 233 if err := p.executeAnsible(ui, comm); err != nil { 234 return fmt.Errorf("Error executing Ansible: %s", err) 235 } 236 return nil 237 } 238 239 func (p *Provisioner) Cancel() { 240 // Just hard quit. It isn't a big deal if what we're doing keeps 241 // running on the other side. 242 os.Exit(0) 243 } 244 245 func (p *Provisioner) executeAnsible(ui packer.Ui, comm packer.Communicator) error { 246 playbook := filepath.ToSlash(filepath.Join(p.config.StagingDir, filepath.Base(p.config.PlaybookFile))) 247 inventory := filepath.ToSlash(filepath.Join(p.config.StagingDir, filepath.Base(p.config.InventoryFile))) 248 249 extraArgs := "" 250 if len(p.config.ExtraArguments) > 0 { 251 extraArgs = " " + strings.Join(p.config.ExtraArguments, " ") 252 } 253 254 command := fmt.Sprintf("cd %s && %s %s%s -c local -i %s", 255 p.config.StagingDir, p.config.Command, playbook, extraArgs, inventory) 256 ui.Message(fmt.Sprintf("Executing Ansible: %s", command)) 257 cmd := &packer.RemoteCmd{ 258 Command: command, 259 } 260 if err := cmd.StartWithUi(comm, ui); err != nil { 261 return err 262 } 263 if cmd.ExitStatus != 0 { 264 if cmd.ExitStatus == 127 { 265 return fmt.Errorf("%s could not be found. Verify that it is available on the\n"+ 266 "PATH after connecting to the machine.", 267 p.config.Command) 268 } 269 270 return fmt.Errorf("Non-zero exit status: %d", cmd.ExitStatus) 271 } 272 return nil 273 } 274 275 func validateDirConfig(path string, config string) error { 276 info, err := os.Stat(path) 277 if err != nil { 278 return fmt.Errorf("%s: %s is invalid: %s", config, path, err) 279 } else if !info.IsDir() { 280 return fmt.Errorf("%s: %s must point to a directory", config, path) 281 } 282 return nil 283 } 284 285 func validateFileConfig(name string, config string, req bool) error { 286 if req { 287 if name == "" { 288 return fmt.Errorf("%s must be specified.", config) 289 } 290 } 291 info, err := os.Stat(name) 292 if err != nil { 293 return fmt.Errorf("%s: %s is invalid: %s", config, name, err) 294 } else if info.IsDir() { 295 return fmt.Errorf("%s: %s must point to a file", config, name) 296 } 297 return nil 298 } 299 300 func (p *Provisioner) uploadFile(ui packer.Ui, comm packer.Communicator, dst, src string) error { 301 f, err := os.Open(src) 302 if err != nil { 303 return fmt.Errorf("Error opening: %s", err) 304 } 305 defer f.Close() 306 307 if err = comm.Upload(dst, f, nil); err != nil { 308 return fmt.Errorf("Error uploading %s: %s", src, err) 309 } 310 return nil 311 } 312 313 func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error { 314 ui.Message(fmt.Sprintf("Creating directory: %s", dir)) 315 cmd := &packer.RemoteCmd{ 316 Command: fmt.Sprintf("mkdir -p '%s'", dir), 317 } 318 if err := cmd.StartWithUi(comm, ui); err != nil { 319 return err 320 } 321 if cmd.ExitStatus != 0 { 322 return fmt.Errorf("Non-zero exit status.") 323 } 324 return nil 325 } 326 327 func (p *Provisioner) uploadDir(ui packer.Ui, comm packer.Communicator, dst, src string) error { 328 if err := p.createDir(ui, comm, dst); err != nil { 329 return err 330 } 331 332 // Make sure there is a trailing "/" so that the directory isn't 333 // created on the other side. 334 if src[len(src)-1] != '/' { 335 src = src + "/" 336 } 337 return comm.UploadDir(dst, src, nil) 338 }