github.com/rahart/packer@v0.12.2-0.20161229105310-282bb6ad370f/provisioner/puppet-server/provisioner.go (about) 1 // This package implements a provisioner for Packer that executes 2 // Puppet on the remote machine connecting to a Puppet master. 3 package puppetserver 4 5 import ( 6 "fmt" 7 "os" 8 "strings" 9 10 "github.com/mitchellh/packer/common" 11 "github.com/mitchellh/packer/helper/config" 12 "github.com/mitchellh/packer/packer" 13 "github.com/mitchellh/packer/template/interpolate" 14 ) 15 16 type Config struct { 17 common.PackerConfig `mapstructure:",squash"` 18 ctx interpolate.Context 19 20 // The command used to execute Puppet. 21 ExecuteCommand string `mapstructure:"execute_command"` 22 23 // Additional facts to set when executing Puppet 24 Facter map[string]string 25 26 // A path to the client certificate 27 ClientCertPath string `mapstructure:"client_cert_path"` 28 29 // A path to a directory containing the client private keys 30 ClientPrivateKeyPath string `mapstructure:"client_private_key_path"` 31 32 // The hostname of the Puppet node. 33 PuppetNode string `mapstructure:"puppet_node"` 34 35 // The hostname of the Puppet server. 36 PuppetServer string `mapstructure:"puppet_server"` 37 38 // Additional options to be passed to `puppet agent`. 39 Options string `mapstructure:"options"` 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_dir"` 47 48 // The directory that contains the puppet binary. 49 // E.g. if it can't be found on the standard path. 50 PuppetBinDir string `mapstructure:"puppet_bin_dir"` 51 52 // If true, packer will ignore all exit-codes from a puppet run 53 IgnoreExitCodes bool `mapstructure:"ignore_exit_codes"` 54 } 55 56 type Provisioner struct { 57 config Config 58 } 59 60 type ExecuteTemplate struct { 61 FacterVars string 62 ClientCertPath string 63 ClientPrivateKeyPath string 64 PuppetNode string 65 PuppetServer string 66 Options string 67 PuppetBinDir string 68 Sudo bool 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 if p.config.ExecuteCommand == "" { 86 p.config.ExecuteCommand = p.commandTemplate() 87 } 88 89 if p.config.StagingDir == "" { 90 p.config.StagingDir = "/tmp/packer-puppet-server" 91 } 92 93 if p.config.Facter == nil { 94 p.config.Facter = make(map[string]string) 95 } 96 p.config.Facter["packer_build_name"] = p.config.PackerBuildName 97 p.config.Facter["packer_builder_type"] = p.config.PackerBuilderType 98 99 var errs *packer.MultiError 100 if p.config.ClientCertPath != "" { 101 info, err := os.Stat(p.config.ClientCertPath) 102 if err != nil { 103 errs = packer.MultiErrorAppend(errs, 104 fmt.Errorf("client_cert_dir is invalid: %s", err)) 105 } else if !info.IsDir() { 106 errs = packer.MultiErrorAppend(errs, 107 fmt.Errorf("client_cert_dir must point to a directory")) 108 } 109 } 110 111 if p.config.ClientPrivateKeyPath != "" { 112 info, err := os.Stat(p.config.ClientPrivateKeyPath) 113 if err != nil { 114 errs = packer.MultiErrorAppend(errs, 115 fmt.Errorf("client_private_key_dir is invalid: %s", err)) 116 } else if !info.IsDir() { 117 errs = packer.MultiErrorAppend(errs, 118 fmt.Errorf("client_private_key_dir must point to a directory")) 119 } 120 } 121 122 if errs != nil && len(errs.Errors) > 0 { 123 return errs 124 } 125 126 return nil 127 } 128 129 func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { 130 ui.Say("Provisioning with Puppet...") 131 ui.Message("Creating Puppet staging directory...") 132 if err := p.createDir(ui, comm, p.config.StagingDir); err != nil { 133 return fmt.Errorf("Error creating staging directory: %s", err) 134 } 135 136 // Upload client cert dir if set 137 remoteClientCertPath := "" 138 if p.config.ClientCertPath != "" { 139 ui.Message(fmt.Sprintf( 140 "Uploading client cert from: %s", p.config.ClientCertPath)) 141 remoteClientCertPath = fmt.Sprintf("%s/certs", p.config.StagingDir) 142 err := p.uploadDirectory(ui, comm, remoteClientCertPath, p.config.ClientCertPath) 143 if err != nil { 144 return fmt.Errorf("Error uploading client cert: %s", err) 145 } 146 } 147 148 // Upload client cert dir if set 149 remoteClientPrivateKeyPath := "" 150 if p.config.ClientPrivateKeyPath != "" { 151 ui.Message(fmt.Sprintf( 152 "Uploading client private keys from: %s", p.config.ClientPrivateKeyPath)) 153 remoteClientPrivateKeyPath = fmt.Sprintf("%s/private_keys", p.config.StagingDir) 154 err := p.uploadDirectory(ui, comm, remoteClientPrivateKeyPath, p.config.ClientPrivateKeyPath) 155 if err != nil { 156 return fmt.Errorf("Error uploading client private keys: %s", err) 157 } 158 } 159 160 // Compile the facter variables 161 facterVars := make([]string, 0, len(p.config.Facter)) 162 for k, v := range p.config.Facter { 163 facterVars = append(facterVars, fmt.Sprintf("FACTER_%s='%s'", k, v)) 164 } 165 166 // Execute Puppet 167 p.config.ctx.Data = &ExecuteTemplate{ 168 FacterVars: strings.Join(facterVars, " "), 169 ClientCertPath: remoteClientCertPath, 170 ClientPrivateKeyPath: remoteClientPrivateKeyPath, 171 PuppetNode: p.config.PuppetNode, 172 PuppetServer: p.config.PuppetServer, 173 Options: p.config.Options, 174 PuppetBinDir: p.config.PuppetBinDir, 175 Sudo: !p.config.PreventSudo, 176 } 177 command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx) 178 if err != nil { 179 return err 180 } 181 182 cmd := &packer.RemoteCmd{ 183 Command: command, 184 } 185 186 ui.Message(fmt.Sprintf("Running Puppet: %s", command)) 187 if err := cmd.StartWithUi(comm, ui); err != nil { 188 return err 189 } 190 191 if cmd.ExitStatus != 0 && cmd.ExitStatus != 2 && !p.config.IgnoreExitCodes { 192 return fmt.Errorf("Puppet exited with a non-zero exit status: %d", cmd.ExitStatus) 193 } 194 195 return nil 196 } 197 198 func (p *Provisioner) Cancel() { 199 // Just hard quit. It isn't a big deal if what we're doing keeps 200 // running on the other side. 201 os.Exit(0) 202 } 203 204 func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error { 205 cmd := &packer.RemoteCmd{ 206 Command: fmt.Sprintf("mkdir -p '%s'", dir), 207 } 208 209 if err := cmd.StartWithUi(comm, ui); err != nil { 210 return err 211 } 212 213 if cmd.ExitStatus != 0 { 214 return fmt.Errorf("Non-zero exit status.") 215 } 216 217 return nil 218 } 219 220 func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, dst string, src string) error { 221 if err := p.createDir(ui, comm, dst); err != nil { 222 return err 223 } 224 225 // Make sure there is a trailing "/" so that the directory isn't 226 // created on the other side. 227 if src[len(src)-1] != '/' { 228 src = src + "/" 229 } 230 231 return comm.UploadDir(dst, src, nil) 232 } 233 234 func (p *Provisioner) commandTemplate() string { 235 return "{{.FacterVars}} {{if .Sudo}} sudo -E {{end}}" + 236 "{{if ne .PuppetBinDir \"\"}}{{.PuppetBinDir}}/{{end}}puppet agent " + 237 "--onetime --no-daemonize " + 238 "{{if ne .PuppetServer \"\"}}--server='{{.PuppetServer}}' {{end}}" + 239 "{{if ne .Options \"\"}}{{.Options}} {{end}}" + 240 "{{if ne .PuppetNode \"\"}}--certname={{.PuppetNode}} {{end}}" + 241 "{{if ne .ClientCertPath \"\"}}--certdir='{{.ClientCertPath}}' {{end}}" + 242 "{{if ne .ClientPrivateKeyPath \"\"}}--privatekeydir='{{.ClientPrivateKeyPath}}' {{end}}" + 243 "--detailed-exitcodes" 244 }