github.com/jerryclinesmith/packer@v0.3.7/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 "staging_dir": &p.config.StagingDir, 86 } 87 88 for n, ptr := range templates { 89 var err error 90 *ptr, err = p.config.tpl.Process(*ptr, nil) 91 if err != nil { 92 errs = packer.MultiErrorAppend( 93 errs, fmt.Errorf("Error processing %s: %s", n, err)) 94 } 95 } 96 97 sliceTemplates := map[string][]string{ 98 "module_paths": p.config.ModulePaths, 99 } 100 101 for n, slice := range sliceTemplates { 102 for i, elem := range slice { 103 var err error 104 slice[i], err = p.config.tpl.Process(elem, nil) 105 if err != nil { 106 errs = packer.MultiErrorAppend( 107 errs, fmt.Errorf("Error processing %s[%d]: %s", n, i, err)) 108 } 109 } 110 } 111 112 validates := map[string]*string{ 113 "execute_command": &p.config.ExecuteCommand, 114 } 115 116 for n, ptr := range validates { 117 if err := p.config.tpl.Validate(*ptr); err != nil { 118 errs = packer.MultiErrorAppend( 119 errs, fmt.Errorf("Error parsing %s: %s", n, err)) 120 } 121 } 122 123 newFacts := make(map[string]string) 124 for k, v := range p.config.Facter { 125 k, err := p.config.tpl.Process(k, nil) 126 if err != nil { 127 errs = packer.MultiErrorAppend(errs, 128 fmt.Errorf("Error processing facter key %s: %s", k, err)) 129 continue 130 } 131 132 v, err := p.config.tpl.Process(v, nil) 133 if err != nil { 134 errs = packer.MultiErrorAppend(errs, 135 fmt.Errorf("Error processing facter value '%s': %s", v, err)) 136 continue 137 } 138 139 newFacts[k] = v 140 } 141 142 p.config.Facter = newFacts 143 144 // Validation 145 if p.config.HieraConfigPath != "" { 146 info, err := os.Stat(p.config.ManifestFile) 147 if err != nil { 148 errs = packer.MultiErrorAppend(errs, 149 fmt.Errorf("hiera_config_path is invalid: %s", err)) 150 } else if info.IsDir() { 151 errs = packer.MultiErrorAppend(errs, 152 fmt.Errorf("hiera_config_path must point to a file")) 153 } 154 } 155 156 if p.config.ManifestFile == "" { 157 errs = packer.MultiErrorAppend(errs, 158 fmt.Errorf("A manifest_file must be specified.")) 159 } else { 160 info, err := os.Stat(p.config.ManifestFile) 161 if err != nil { 162 errs = packer.MultiErrorAppend(errs, 163 fmt.Errorf("manifest_file is invalid: %s", err)) 164 } else if info.IsDir() { 165 errs = packer.MultiErrorAppend(errs, 166 fmt.Errorf("manifest_file must point to a file")) 167 } 168 } 169 170 for i, path := range p.config.ModulePaths { 171 info, err := os.Stat(path) 172 if err != nil { 173 errs = packer.MultiErrorAppend(errs, 174 fmt.Errorf("module_path[%d] is invalid: %s", i, err)) 175 } else if !info.IsDir() { 176 errs = packer.MultiErrorAppend(errs, 177 fmt.Errorf("module_path[%d] must point to a directory")) 178 } 179 } 180 181 if errs != nil && len(errs.Errors) > 0 { 182 return errs 183 } 184 185 return nil 186 } 187 188 func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { 189 ui.Say("Provisioning with Puppet...") 190 ui.Message("Creating Puppet staging directory...") 191 if err := p.createDir(ui, comm, p.config.StagingDir); err != nil { 192 return fmt.Errorf("Error creating staging directory: %s", err) 193 } 194 195 // Upload hiera config if set 196 remoteHieraConfigPath := "" 197 if p.config.HieraConfigPath != "" { 198 var err error 199 remoteHieraConfigPath, err = p.uploadHieraConfig(ui, comm) 200 if err != nil { 201 return fmt.Errorf("Error uploading hiera config: %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 ui.Message("Uploading manifests...") 219 remoteManifestFile, err := p.uploadManifests(ui, comm) 220 if err != nil { 221 return fmt.Errorf("Error uploading manifests: %s", err) 222 } 223 224 // Compile the facter variables 225 facterVars := make([]string, 0, len(p.config.Facter)) 226 for k, v := range p.config.Facter { 227 facterVars = append(facterVars, fmt.Sprintf("FACTER_%s='%s'", k, v)) 228 } 229 230 // Execute Puppet 231 command, err := p.config.tpl.Process(p.config.ExecuteCommand, &ExecuteTemplate{ 232 FacterVars: strings.Join(facterVars, " "), 233 HasHieraConfigPath: remoteHieraConfigPath != "", 234 HieraConfigPath: remoteHieraConfigPath, 235 ManifestFile: remoteManifestFile, 236 ModulePath: strings.Join(modulePaths, ":"), 237 Sudo: !p.config.PreventSudo, 238 }) 239 if err != nil { 240 return err 241 } 242 243 cmd := &packer.RemoteCmd{ 244 Command: command, 245 } 246 247 ui.Message(fmt.Sprintf("Running Puppet: %s", command)) 248 if err := cmd.StartWithUi(comm, ui); err != nil { 249 return err 250 } 251 252 if cmd.ExitStatus != 0 { 253 return fmt.Errorf("Puppet exited with a non-zero exit status: %d", cmd.ExitStatus) 254 } 255 256 return nil 257 } 258 259 func (p *Provisioner) Cancel() { 260 // Just hard quit. It isn't a big deal if what we're doing keeps 261 // running on the other side. 262 os.Exit(0) 263 } 264 265 func (p *Provisioner) uploadHieraConfig(ui packer.Ui, comm packer.Communicator) (string, error) { 266 ui.Message("Uploading hiera configuration...") 267 f, err := os.Open(p.config.HieraConfigPath) 268 if err != nil { 269 return "", err 270 } 271 defer f.Close() 272 273 path := fmt.Sprintf("%s/hiera.yaml", p.config.StagingDir) 274 if err := comm.Upload(path, f); err != nil { 275 return "", err 276 } 277 278 return path, nil 279 } 280 281 func (p *Provisioner) uploadManifests(ui packer.Ui, comm packer.Communicator) (string, error) { 282 // Create the remote manifests directory... 283 ui.Message("Uploading manifests...") 284 remoteManifestsPath := fmt.Sprintf("%s/manifests", p.config.StagingDir) 285 if err := p.createDir(ui, comm, remoteManifestsPath); err != nil { 286 return "", fmt.Errorf("Error creating manifests directory: %s", err) 287 } 288 289 // Upload the main manifest 290 f, err := os.Open(p.config.ManifestFile) 291 if err != nil { 292 return "", err 293 } 294 defer f.Close() 295 296 manifestFilename := filepath.Base(p.config.ManifestFile) 297 remoteManifestFile := fmt.Sprintf("%s/%s", remoteManifestsPath, manifestFilename) 298 if err := comm.Upload(remoteManifestFile, f); err != nil { 299 return "", err 300 } 301 302 return remoteManifestFile, nil 303 } 304 305 func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error { 306 cmd := &packer.RemoteCmd{ 307 Command: fmt.Sprintf("mkdir -p '%s'", dir), 308 } 309 310 if err := cmd.StartWithUi(comm, ui); err != nil { 311 return err 312 } 313 314 if cmd.ExitStatus != 0 { 315 return fmt.Errorf("Non-zero exit status.") 316 } 317 318 return nil 319 } 320 321 func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, dst string, src string) error { 322 if err := p.createDir(ui, comm, dst); err != nil { 323 return err 324 } 325 326 // Make sure there is a trailing "/" so that the directory isn't 327 // created on the other side. 328 if src[len(src)-1] != '/' { 329 src = src + "/" 330 } 331 332 return comm.UploadDir(dst, src, nil) 333 }