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