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