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