github.com/askholme/packer@v0.7.2-0.20140924152349-70d9566a6852/provisioner/puppet-masterless/provisioner.go (about) 1 // This package implements a provisioner for Packer that executes 2 // Puppet on the remote machine, configured to apply a local manifest 3 // versus connecting to a Puppet master. 4 package puppetmasterless 5 6 import ( 7 "fmt" 8 "github.com/mitchellh/packer/common" 9 "github.com/mitchellh/packer/packer" 10 "os" 11 "path/filepath" 12 "strings" 13 ) 14 15 type Config struct { 16 common.PackerConfig `mapstructure:",squash"` 17 tpl *packer.ConfigTemplate 18 19 // The command used to execute Puppet. 20 ExecuteCommand string `mapstructure:"execute_command"` 21 22 // Additional facts to set when executing Puppet 23 Facter map[string]string 24 25 // Path to a hiera configuration file to upload and use. 26 HieraConfigPath string `mapstructure:"hiera_config_path"` 27 28 // An array of local paths of modules to upload. 29 ModulePaths []string `mapstructure:"module_paths"` 30 31 // The main manifest file to apply to kick off the entire thing. 32 ManifestFile string `mapstructure:"manifest_file"` 33 34 // A directory of manifest files that will be uploaded to the remote 35 // machine. 36 ManifestDir string `mapstructure:"manifest_dir"` 37 38 // If true, `sudo` will NOT be used to execute Puppet. 39 PreventSudo bool `mapstructure:"prevent_sudo"` 40 41 // The directory where files will be uploaded. Packer requires write 42 // permissions in this directory. 43 StagingDir string `mapstructure:"staging_directory"` 44 } 45 46 type Provisioner struct { 47 config Config 48 } 49 50 type ExecuteTemplate struct { 51 FacterVars string 52 HieraConfigPath string 53 ModulePath string 54 ManifestFile string 55 ManifestDir string 56 Sudo bool 57 } 58 59 func (p *Provisioner) Prepare(raws ...interface{}) error { 60 md, err := common.DecodeConfig(&p.config, raws...) 61 if err != nil { 62 return err 63 } 64 65 p.config.tpl, err = packer.NewConfigTemplate() 66 if err != nil { 67 return err 68 } 69 p.config.tpl.UserVars = p.config.PackerUserVars 70 71 // Accumulate any errors 72 errs := common.CheckUnusedConfig(md) 73 74 // Set some defaults 75 if p.config.ExecuteCommand == "" { 76 p.config.ExecuteCommand = "{{.FacterVars}} {{if .Sudo}} sudo -E {{end}}" + 77 "puppet apply --verbose --modulepath='{{.ModulePath}}' " + 78 "{{if ne .HieraConfigPath \"\"}}--hiera_config='{{.HieraConfigPath}}' {{end}}" + 79 "{{if ne .ManifestDir \"\"}}--manifestdir='{{.ManifestDir}}' {{end}}" + 80 "--detailed-exitcodes " + 81 "{{.ManifestFile}}" 82 } 83 84 if p.config.StagingDir == "" { 85 p.config.StagingDir = "/tmp/packer-puppet-masterless" 86 } 87 88 // Templates 89 templates := map[string]*string{ 90 "hiera_config_path": &p.config.HieraConfigPath, 91 "manifest_file": &p.config.ManifestFile, 92 "manifest_dir": &p.config.ManifestDir, 93 "staging_dir": &p.config.StagingDir, 94 } 95 96 for n, ptr := range templates { 97 var err error 98 *ptr, err = p.config.tpl.Process(*ptr, nil) 99 if err != nil { 100 errs = packer.MultiErrorAppend( 101 errs, fmt.Errorf("Error processing %s: %s", n, err)) 102 } 103 } 104 105 sliceTemplates := map[string][]string{ 106 "module_paths": p.config.ModulePaths, 107 } 108 109 for n, slice := range sliceTemplates { 110 for i, elem := range slice { 111 var err error 112 slice[i], err = p.config.tpl.Process(elem, nil) 113 if err != nil { 114 errs = packer.MultiErrorAppend( 115 errs, fmt.Errorf("Error processing %s[%d]: %s", n, i, err)) 116 } 117 } 118 } 119 120 validates := map[string]*string{ 121 "execute_command": &p.config.ExecuteCommand, 122 } 123 124 for n, ptr := range validates { 125 if err := p.config.tpl.Validate(*ptr); err != nil { 126 errs = packer.MultiErrorAppend( 127 errs, fmt.Errorf("Error parsing %s: %s", n, err)) 128 } 129 } 130 131 newFacts := make(map[string]string) 132 for k, v := range p.config.Facter { 133 k, err := p.config.tpl.Process(k, nil) 134 if err != nil { 135 errs = packer.MultiErrorAppend(errs, 136 fmt.Errorf("Error processing facter key %s: %s", k, err)) 137 continue 138 } 139 140 v, err := p.config.tpl.Process(v, nil) 141 if err != nil { 142 errs = packer.MultiErrorAppend(errs, 143 fmt.Errorf("Error processing facter value '%s': %s", v, err)) 144 continue 145 } 146 147 newFacts[k] = v 148 } 149 150 p.config.Facter = newFacts 151 152 // Validation 153 if p.config.HieraConfigPath != "" { 154 info, err := os.Stat(p.config.HieraConfigPath) 155 if err != nil { 156 errs = packer.MultiErrorAppend(errs, 157 fmt.Errorf("hiera_config_path is invalid: %s", err)) 158 } else if info.IsDir() { 159 errs = packer.MultiErrorAppend(errs, 160 fmt.Errorf("hiera_config_path must point to a file")) 161 } 162 } 163 164 if p.config.ManifestDir != "" { 165 info, err := os.Stat(p.config.ManifestDir) 166 if err != nil { 167 errs = packer.MultiErrorAppend(errs, 168 fmt.Errorf("manifest_dir is invalid: %s", err)) 169 } else if !info.IsDir() { 170 errs = packer.MultiErrorAppend(errs, 171 fmt.Errorf("manifest_dir must point to a directory")) 172 } 173 } 174 175 if p.config.ManifestFile == "" { 176 errs = packer.MultiErrorAppend(errs, 177 fmt.Errorf("A manifest_file must be specified.")) 178 } else { 179 info, err := os.Stat(p.config.ManifestFile) 180 if err != nil { 181 errs = packer.MultiErrorAppend(errs, 182 fmt.Errorf("manifest_file is invalid: %s", err)) 183 } else if info.IsDir() { 184 errs = packer.MultiErrorAppend(errs, 185 fmt.Errorf("manifest_file must point to a file")) 186 } 187 } 188 189 for i, path := range p.config.ModulePaths { 190 info, err := os.Stat(path) 191 if err != nil { 192 errs = packer.MultiErrorAppend(errs, 193 fmt.Errorf("module_path[%d] is invalid: %s", i, err)) 194 } else if !info.IsDir() { 195 errs = packer.MultiErrorAppend(errs, 196 fmt.Errorf("module_path[%d] must point to a directory")) 197 } 198 } 199 200 if errs != nil && len(errs.Errors) > 0 { 201 return errs 202 } 203 204 return nil 205 } 206 207 func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { 208 ui.Say("Provisioning with Puppet...") 209 ui.Message("Creating Puppet staging directory...") 210 if err := p.createDir(ui, comm, p.config.StagingDir); err != nil { 211 return fmt.Errorf("Error creating staging directory: %s", err) 212 } 213 214 // Upload hiera config if set 215 remoteHieraConfigPath := "" 216 if p.config.HieraConfigPath != "" { 217 var err error 218 remoteHieraConfigPath, err = p.uploadHieraConfig(ui, comm) 219 if err != nil { 220 return fmt.Errorf("Error uploading hiera config: %s", err) 221 } 222 } 223 224 // Upload manifest dir if set 225 remoteManifestDir := "" 226 if p.config.ManifestDir != "" { 227 ui.Message(fmt.Sprintf( 228 "Uploading manifest directory from: %s", p.config.ManifestDir)) 229 remoteManifestDir = fmt.Sprintf("%s/manifests", p.config.StagingDir) 230 err := p.uploadDirectory(ui, comm, remoteManifestDir, p.config.ManifestDir) 231 if err != nil { 232 return fmt.Errorf("Error uploading manifest dir: %s", err) 233 } 234 } 235 236 // Upload all modules 237 modulePaths := make([]string, 0, len(p.config.ModulePaths)) 238 for i, path := range p.config.ModulePaths { 239 ui.Message(fmt.Sprintf("Uploading local modules from: %s", path)) 240 targetPath := fmt.Sprintf("%s/module-%d", p.config.StagingDir, i) 241 if err := p.uploadDirectory(ui, comm, targetPath, path); err != nil { 242 return fmt.Errorf("Error uploading modules: %s", err) 243 } 244 245 modulePaths = append(modulePaths, targetPath) 246 } 247 248 // Upload manifests 249 remoteManifestFile, err := p.uploadManifests(ui, comm) 250 if err != nil { 251 return fmt.Errorf("Error uploading manifests: %s", err) 252 } 253 254 // Compile the facter variables 255 facterVars := make([]string, 0, len(p.config.Facter)) 256 for k, v := range p.config.Facter { 257 facterVars = append(facterVars, fmt.Sprintf("FACTER_%s='%s'", k, v)) 258 } 259 260 // Execute Puppet 261 command, err := p.config.tpl.Process(p.config.ExecuteCommand, &ExecuteTemplate{ 262 FacterVars: strings.Join(facterVars, " "), 263 HieraConfigPath: remoteHieraConfigPath, 264 ManifestDir: remoteManifestDir, 265 ManifestFile: remoteManifestFile, 266 ModulePath: strings.Join(modulePaths, ":"), 267 Sudo: !p.config.PreventSudo, 268 }) 269 if err != nil { 270 return err 271 } 272 273 cmd := &packer.RemoteCmd{ 274 Command: command, 275 } 276 277 ui.Message(fmt.Sprintf("Running Puppet: %s", command)) 278 if err := cmd.StartWithUi(comm, ui); err != nil { 279 return err 280 } 281 282 if cmd.ExitStatus != 0 && cmd.ExitStatus != 2 { 283 return fmt.Errorf("Puppet exited with a non-zero exit status: %d", cmd.ExitStatus) 284 } 285 286 return nil 287 } 288 289 func (p *Provisioner) Cancel() { 290 // Just hard quit. It isn't a big deal if what we're doing keeps 291 // running on the other side. 292 os.Exit(0) 293 } 294 295 func (p *Provisioner) uploadHieraConfig(ui packer.Ui, comm packer.Communicator) (string, error) { 296 ui.Message("Uploading hiera configuration...") 297 f, err := os.Open(p.config.HieraConfigPath) 298 if err != nil { 299 return "", err 300 } 301 defer f.Close() 302 303 path := fmt.Sprintf("%s/hiera.yaml", p.config.StagingDir) 304 if err := comm.Upload(path, f, nil); err != nil { 305 return "", err 306 } 307 308 return path, nil 309 } 310 311 func (p *Provisioner) uploadManifests(ui packer.Ui, comm packer.Communicator) (string, error) { 312 // Create the remote manifests directory... 313 ui.Message("Uploading manifests...") 314 remoteManifestsPath := fmt.Sprintf("%s/manifests", p.config.StagingDir) 315 if err := p.createDir(ui, comm, remoteManifestsPath); err != nil { 316 return "", fmt.Errorf("Error creating manifests directory: %s", err) 317 } 318 319 // Upload the main manifest 320 f, err := os.Open(p.config.ManifestFile) 321 if err != nil { 322 return "", err 323 } 324 defer f.Close() 325 326 manifestFilename := filepath.Base(p.config.ManifestFile) 327 remoteManifestFile := fmt.Sprintf("%s/%s", remoteManifestsPath, manifestFilename) 328 if err := comm.Upload(remoteManifestFile, f, nil); err != nil { 329 return "", err 330 } 331 332 return remoteManifestFile, nil 333 } 334 335 func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error { 336 cmd := &packer.RemoteCmd{ 337 Command: fmt.Sprintf("mkdir -p '%s'", dir), 338 } 339 340 if err := cmd.StartWithUi(comm, ui); err != nil { 341 return err 342 } 343 344 if cmd.ExitStatus != 0 { 345 return fmt.Errorf("Non-zero exit status.") 346 } 347 348 return nil 349 } 350 351 func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, dst string, src string) error { 352 if err := p.createDir(ui, comm, dst); err != nil { 353 return err 354 } 355 356 // Make sure there is a trailing "/" so that the directory isn't 357 // created on the other side. 358 if src[len(src)-1] != '/' { 359 src = src + "/" 360 } 361 362 return comm.UploadDir(dst, src, nil) 363 }