github.com/kimor79/packer@v0.8.7-0.20151221212622-d507b18eb4cf/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 // The directory from which the command will be executed. 52 // Packer requires the directory to exist when running puppet. 53 WorkingDir string `mapstructure:"working_directory"` 54 } 55 56 type Provisioner struct { 57 config Config 58 } 59 60 type ExecuteTemplate struct { 61 WorkingDir string 62 FacterVars string 63 HieraConfigPath string 64 ModulePath string 65 ManifestFile string 66 ManifestDir string 67 Sudo bool 68 ExtraArguments string 69 } 70 71 func (p *Provisioner) Prepare(raws ...interface{}) error { 72 err := config.Decode(&p.config, &config.DecodeOpts{ 73 Interpolate: true, 74 InterpolateContext: &p.config.ctx, 75 InterpolateFilter: &interpolate.RenderFilter{ 76 Exclude: []string{ 77 "execute_command", 78 }, 79 }, 80 }, raws...) 81 if err != nil { 82 return err 83 } 84 85 // Set some defaults 86 if p.config.ExecuteCommand == "" { 87 p.config.ExecuteCommand = "cd {{.WorkingDir}} && " + 88 "{{.FacterVars}} {{if .Sudo}} sudo -E {{end}}" + 89 "puppet apply --verbose --modulepath='{{.ModulePath}}' " + 90 "{{if ne .HieraConfigPath \"\"}}--hiera_config='{{.HieraConfigPath}}' {{end}}" + 91 "{{if ne .ManifestDir \"\"}}--manifestdir='{{.ManifestDir}}' {{end}}" + 92 "--detailed-exitcodes " + 93 "{{if ne .ExtraArguments \"\"}}{{.ExtraArguments}} {{end}}" + 94 "{{.ManifestFile}}" 95 } 96 97 if p.config.StagingDir == "" { 98 p.config.StagingDir = "/tmp/packer-puppet-masterless" 99 } 100 101 if p.config.WorkingDir == "" { 102 p.config.WorkingDir = p.config.StagingDir 103 } 104 105 if p.config.Facter == nil { 106 p.config.Facter = make(map[string]string) 107 } 108 p.config.Facter["packer_build_name"] = p.config.PackerBuildName 109 p.config.Facter["packer_builder_type"] = p.config.PackerBuilderType 110 111 // Validation 112 var errs *packer.MultiError 113 if p.config.HieraConfigPath != "" { 114 info, err := os.Stat(p.config.HieraConfigPath) 115 if err != nil { 116 errs = packer.MultiErrorAppend(errs, 117 fmt.Errorf("hiera_config_path is invalid: %s", err)) 118 } else if info.IsDir() { 119 errs = packer.MultiErrorAppend(errs, 120 fmt.Errorf("hiera_config_path must point to a file")) 121 } 122 } 123 124 if p.config.ManifestDir != "" { 125 info, err := os.Stat(p.config.ManifestDir) 126 if err != nil { 127 errs = packer.MultiErrorAppend(errs, 128 fmt.Errorf("manifest_dir is invalid: %s", err)) 129 } else if !info.IsDir() { 130 errs = packer.MultiErrorAppend(errs, 131 fmt.Errorf("manifest_dir must point to a directory")) 132 } 133 } 134 135 if p.config.ManifestFile == "" { 136 errs = packer.MultiErrorAppend(errs, 137 fmt.Errorf("A manifest_file must be specified.")) 138 } else { 139 _, err := os.Stat(p.config.ManifestFile) 140 if err != nil { 141 errs = packer.MultiErrorAppend(errs, 142 fmt.Errorf("manifest_file is invalid: %s", err)) 143 } 144 } 145 146 for i, path := range p.config.ModulePaths { 147 info, err := os.Stat(path) 148 if err != nil { 149 errs = packer.MultiErrorAppend(errs, 150 fmt.Errorf("module_path[%d] is invalid: %s", i, err)) 151 } else if !info.IsDir() { 152 errs = packer.MultiErrorAppend(errs, 153 fmt.Errorf("module_path[%d] must point to a directory", i)) 154 } 155 } 156 157 if errs != nil && len(errs.Errors) > 0 { 158 return errs 159 } 160 161 return nil 162 } 163 164 func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { 165 ui.Say("Provisioning with Puppet...") 166 ui.Message("Creating Puppet staging directory...") 167 if err := p.createDir(ui, comm, p.config.StagingDir); err != nil { 168 return fmt.Errorf("Error creating staging directory: %s", err) 169 } 170 171 // Upload hiera config if set 172 remoteHieraConfigPath := "" 173 if p.config.HieraConfigPath != "" { 174 var err error 175 remoteHieraConfigPath, err = p.uploadHieraConfig(ui, comm) 176 if err != nil { 177 return fmt.Errorf("Error uploading hiera config: %s", err) 178 } 179 } 180 181 // Upload manifest dir if set 182 remoteManifestDir := "" 183 if p.config.ManifestDir != "" { 184 ui.Message(fmt.Sprintf( 185 "Uploading manifest directory from: %s", p.config.ManifestDir)) 186 remoteManifestDir = fmt.Sprintf("%s/manifests", p.config.StagingDir) 187 err := p.uploadDirectory(ui, comm, remoteManifestDir, p.config.ManifestDir) 188 if err != nil { 189 return fmt.Errorf("Error uploading manifest dir: %s", err) 190 } 191 } 192 193 // Upload all modules 194 modulePaths := make([]string, 0, len(p.config.ModulePaths)) 195 for i, path := range p.config.ModulePaths { 196 ui.Message(fmt.Sprintf("Uploading local modules from: %s", path)) 197 targetPath := fmt.Sprintf("%s/module-%d", p.config.StagingDir, i) 198 if err := p.uploadDirectory(ui, comm, targetPath, path); err != nil { 199 return fmt.Errorf("Error uploading modules: %s", err) 200 } 201 202 modulePaths = append(modulePaths, targetPath) 203 } 204 205 // Upload manifests 206 remoteManifestFile, err := p.uploadManifests(ui, comm) 207 if err != nil { 208 return fmt.Errorf("Error uploading manifests: %s", err) 209 } 210 211 // Compile the facter variables 212 facterVars := make([]string, 0, len(p.config.Facter)) 213 for k, v := range p.config.Facter { 214 facterVars = append(facterVars, fmt.Sprintf("FACTER_%s='%s'", k, v)) 215 } 216 217 // Execute Puppet 218 p.config.ctx.Data = &ExecuteTemplate{ 219 FacterVars: strings.Join(facterVars, " "), 220 HieraConfigPath: remoteHieraConfigPath, 221 ManifestDir: remoteManifestDir, 222 ManifestFile: remoteManifestFile, 223 ModulePath: strings.Join(modulePaths, ":"), 224 Sudo: !p.config.PreventSudo, 225 WorkingDir: p.config.WorkingDir, 226 ExtraArguments: strings.Join(p.config.ExtraArguments, " "), 227 } 228 command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx) 229 if err != nil { 230 return err 231 } 232 233 cmd := &packer.RemoteCmd{ 234 Command: command, 235 } 236 237 ui.Message(fmt.Sprintf("Running Puppet: %s", command)) 238 if err := cmd.StartWithUi(comm, ui); err != nil { 239 return err 240 } 241 242 if cmd.ExitStatus != 0 && cmd.ExitStatus != 2 { 243 return fmt.Errorf("Puppet exited with a non-zero exit status: %d", cmd.ExitStatus) 244 } 245 246 return nil 247 } 248 249 func (p *Provisioner) Cancel() { 250 // Just hard quit. It isn't a big deal if what we're doing keeps 251 // running on the other side. 252 os.Exit(0) 253 } 254 255 func (p *Provisioner) uploadHieraConfig(ui packer.Ui, comm packer.Communicator) (string, error) { 256 ui.Message("Uploading hiera configuration...") 257 f, err := os.Open(p.config.HieraConfigPath) 258 if err != nil { 259 return "", err 260 } 261 defer f.Close() 262 263 path := fmt.Sprintf("%s/hiera.yaml", p.config.StagingDir) 264 if err := comm.Upload(path, f, nil); err != nil { 265 return "", err 266 } 267 268 return path, nil 269 } 270 271 func (p *Provisioner) uploadManifests(ui packer.Ui, comm packer.Communicator) (string, error) { 272 // Create the remote manifests directory... 273 ui.Message("Uploading manifests...") 274 remoteManifestsPath := fmt.Sprintf("%s/manifests", p.config.StagingDir) 275 if err := p.createDir(ui, comm, remoteManifestsPath); err != nil { 276 return "", fmt.Errorf("Error creating manifests directory: %s", err) 277 } 278 279 // NOTE! manifest_file may either be a directory or a file, as puppet apply 280 // now accepts either one. 281 282 fi, err := os.Stat(p.config.ManifestFile) 283 if err != nil { 284 return "", fmt.Errorf("Error inspecting manifest file: %s", err) 285 } 286 287 if fi.IsDir() { 288 // If manifest_file is a directory we'll upload the whole thing 289 ui.Message(fmt.Sprintf( 290 "Uploading manifest directory from: %s", p.config.ManifestFile)) 291 292 remoteManifestDir := fmt.Sprintf("%s/manifests", p.config.StagingDir) 293 err := p.uploadDirectory(ui, comm, remoteManifestDir, p.config.ManifestFile) 294 if err != nil { 295 return "", fmt.Errorf("Error uploading manifest dir: %s", err) 296 } 297 return remoteManifestDir, nil 298 } else { 299 // Otherwise manifest_file is a file and we'll upload it 300 ui.Message(fmt.Sprintf( 301 "Uploading manifest file from: %s", p.config.ManifestFile)) 302 303 f, err := os.Open(p.config.ManifestFile) 304 if err != nil { 305 return "", err 306 } 307 defer f.Close() 308 309 manifestFilename := filepath.Base(p.config.ManifestFile) 310 remoteManifestFile := fmt.Sprintf("%s/%s", remoteManifestsPath, manifestFilename) 311 if err := comm.Upload(remoteManifestFile, f, nil); err != nil { 312 return "", err 313 } 314 return remoteManifestFile, nil 315 } 316 } 317 318 func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error { 319 cmd := &packer.RemoteCmd{ 320 Command: fmt.Sprintf("mkdir -p '%s'", dir), 321 } 322 323 if err := cmd.StartWithUi(comm, ui); err != nil { 324 return err 325 } 326 327 if cmd.ExitStatus != 0 { 328 return fmt.Errorf("Non-zero exit status.") 329 } 330 331 return nil 332 } 333 334 func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, dst string, src string) error { 335 if err := p.createDir(ui, comm, dst); err != nil { 336 return err 337 } 338 339 // Make sure there is a trailing "/" so that the directory isn't 340 // created on the other side. 341 if src[len(src)-1] != '/' { 342 src = src + "/" 343 } 344 345 return comm.UploadDir(dst, src, nil) 346 }