github.com/mmcquillan/packer@v1.1.1-0.20171009221028-c85cf0483a5d/provisioner/puppet-server/provisioner.go (about) 1 // Package puppetserver 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/hashicorp/packer/common" 11 "github.com/hashicorp/packer/helper/config" 12 "github.com/hashicorp/packer/packer" 13 "github.com/hashicorp/packer/provisioner" 14 "github.com/hashicorp/packer/template/interpolate" 15 ) 16 17 type guestOSTypeConfig struct { 18 executeCommand string 19 facterVarsFmt string 20 facterVarsJoiner string 21 stagingDir string 22 } 23 24 var guestOSTypeConfigs = map[string]guestOSTypeConfig{ 25 provisioner.UnixOSType: { 26 executeCommand: "{{.FacterVars}} {{if .Sudo}}sudo -E {{end}}" + 27 "{{if ne .PuppetBinDir \"\"}}{{.PuppetBinDir}}/{{end}}puppet agent " + 28 "--onetime --no-daemonize " + 29 "{{if ne .PuppetServer \"\"}}--server='{{.PuppetServer}}' {{end}}" + 30 "{{if ne .Options \"\"}}{{.Options}} {{end}}" + 31 "{{if ne .PuppetNode \"\"}}--certname={{.PuppetNode}} {{end}}" + 32 "{{if ne .ClientCertPath \"\"}}--certdir='{{.ClientCertPath}}' {{end}}" + 33 "{{if ne .ClientPrivateKeyPath \"\"}}--privatekeydir='{{.ClientPrivateKeyPath}}' {{end}}" + 34 "--detailed-exitcodes", 35 facterVarsFmt: "FACTER_%s='%s'", 36 facterVarsJoiner: " ", 37 stagingDir: "/tmp/packer-puppet-server", 38 }, 39 provisioner.WindowsOSType: { 40 executeCommand: "{{.FacterVars}} " + 41 "{{if ne .PuppetBinDir \"\"}}{{.PuppetBinDir}}/{{end}}puppet agent " + 42 "--onetime --no-daemonize " + 43 "{{if ne .PuppetServer \"\"}}--server='{{.PuppetServer}}' {{end}}" + 44 "{{if ne .Options \"\"}}{{.Options}} {{end}}" + 45 "{{if ne .PuppetNode \"\"}}--certname={{.PuppetNode}} {{end}}" + 46 "{{if ne .ClientCertPath \"\"}}--certdir='{{.ClientCertPath}}' {{end}}" + 47 "{{if ne .ClientPrivateKeyPath \"\"}}--privatekeydir='{{.ClientPrivateKeyPath}}' {{end}}" + 48 "--detailed-exitcodes", 49 facterVarsFmt: "SET \"FACTER_%s=%s\"", 50 facterVarsJoiner: " & ", 51 stagingDir: "C:/Windows/Temp/packer-puppet-server", 52 }, 53 } 54 55 type Config struct { 56 common.PackerConfig `mapstructure:",squash"` 57 ctx interpolate.Context 58 59 // The command used to execute Puppet. 60 ExecuteCommand string `mapstructure:"execute_command"` 61 62 // The Guest OS Type (unix or windows) 63 GuestOSType string `mapstructure:"guest_os_type"` 64 65 // Additional facts to set when executing Puppet 66 Facter map[string]string 67 68 // A path to the client certificate 69 ClientCertPath string `mapstructure:"client_cert_path"` 70 71 // A path to a directory containing the client private keys 72 ClientPrivateKeyPath string `mapstructure:"client_private_key_path"` 73 74 // The hostname of the Puppet node. 75 PuppetNode string `mapstructure:"puppet_node"` 76 77 // The hostname of the Puppet server. 78 PuppetServer string `mapstructure:"puppet_server"` 79 80 // Additional options to be passed to `puppet agent`. 81 Options string `mapstructure:"options"` 82 83 // If true, `sudo` will NOT be used to execute Puppet. 84 PreventSudo bool `mapstructure:"prevent_sudo"` 85 86 // The directory where files will be uploaded. Packer requires write 87 // permissions in this directory. 88 StagingDir string `mapstructure:"staging_dir"` 89 90 // The directory that contains the puppet binary. 91 // E.g. if it can't be found on the standard path. 92 PuppetBinDir string `mapstructure:"puppet_bin_dir"` 93 94 // If true, packer will ignore all exit-codes from a puppet run 95 IgnoreExitCodes bool `mapstructure:"ignore_exit_codes"` 96 } 97 98 type Provisioner struct { 99 config Config 100 guestOSTypeConfig guestOSTypeConfig 101 guestCommands *provisioner.GuestCommands 102 } 103 104 type ExecuteTemplate struct { 105 FacterVars string 106 ClientCertPath string 107 ClientPrivateKeyPath string 108 PuppetNode string 109 PuppetServer string 110 Options string 111 PuppetBinDir string 112 Sudo bool 113 } 114 115 func (p *Provisioner) Prepare(raws ...interface{}) error { 116 err := config.Decode(&p.config, &config.DecodeOpts{ 117 Interpolate: true, 118 InterpolateContext: &p.config.ctx, 119 InterpolateFilter: &interpolate.RenderFilter{ 120 Exclude: []string{ 121 "execute_command", 122 }, 123 }, 124 }, raws...) 125 if err != nil { 126 return err 127 } 128 129 if p.config.GuestOSType == "" { 130 p.config.GuestOSType = provisioner.DefaultOSType 131 } 132 p.config.GuestOSType = strings.ToLower(p.config.GuestOSType) 133 134 var ok bool 135 p.guestOSTypeConfig, ok = guestOSTypeConfigs[p.config.GuestOSType] 136 if !ok { 137 return fmt.Errorf("Invalid guest_os_type: \"%s\"", p.config.GuestOSType) 138 } 139 140 p.guestCommands, err = provisioner.NewGuestCommands(p.config.GuestOSType, !p.config.PreventSudo) 141 if err != nil { 142 return fmt.Errorf("Invalid guest_os_type: \"%s\"", p.config.GuestOSType) 143 } 144 145 if p.config.ExecuteCommand == "" { 146 p.config.ExecuteCommand = p.guestOSTypeConfig.executeCommand 147 } 148 149 if p.config.StagingDir == "" { 150 p.config.StagingDir = p.guestOSTypeConfig.stagingDir 151 } 152 153 if p.config.Facter == nil { 154 p.config.Facter = make(map[string]string) 155 } 156 p.config.Facter["packer_build_name"] = p.config.PackerBuildName 157 p.config.Facter["packer_builder_type"] = p.config.PackerBuilderType 158 159 var errs *packer.MultiError 160 if p.config.ClientCertPath != "" { 161 info, err := os.Stat(p.config.ClientCertPath) 162 if err != nil { 163 errs = packer.MultiErrorAppend(errs, 164 fmt.Errorf("client_cert_dir is invalid: %s", err)) 165 } else if !info.IsDir() { 166 errs = packer.MultiErrorAppend(errs, 167 fmt.Errorf("client_cert_dir must point to a directory")) 168 } 169 } 170 171 if p.config.ClientPrivateKeyPath != "" { 172 info, err := os.Stat(p.config.ClientPrivateKeyPath) 173 if err != nil { 174 errs = packer.MultiErrorAppend(errs, 175 fmt.Errorf("client_private_key_dir is invalid: %s", err)) 176 } else if !info.IsDir() { 177 errs = packer.MultiErrorAppend(errs, 178 fmt.Errorf("client_private_key_dir must point to a directory")) 179 } 180 } 181 182 if errs != nil && len(errs.Errors) > 0 { 183 return errs 184 } 185 186 return nil 187 } 188 189 func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { 190 ui.Say("Provisioning with Puppet...") 191 ui.Message("Creating Puppet staging directory...") 192 if err := p.createDir(ui, comm, p.config.StagingDir); err != nil { 193 return fmt.Errorf("Error creating staging directory: %s", err) 194 } 195 196 // Upload client cert dir if set 197 remoteClientCertPath := "" 198 if p.config.ClientCertPath != "" { 199 ui.Message(fmt.Sprintf( 200 "Uploading client cert from: %s", p.config.ClientCertPath)) 201 remoteClientCertPath = fmt.Sprintf("%s/certs", p.config.StagingDir) 202 err := p.uploadDirectory(ui, comm, remoteClientCertPath, p.config.ClientCertPath) 203 if err != nil { 204 return fmt.Errorf("Error uploading client cert: %s", err) 205 } 206 } 207 208 // Upload client cert dir if set 209 remoteClientPrivateKeyPath := "" 210 if p.config.ClientPrivateKeyPath != "" { 211 ui.Message(fmt.Sprintf( 212 "Uploading client private keys from: %s", p.config.ClientPrivateKeyPath)) 213 remoteClientPrivateKeyPath = fmt.Sprintf("%s/private_keys", p.config.StagingDir) 214 err := p.uploadDirectory(ui, comm, remoteClientPrivateKeyPath, p.config.ClientPrivateKeyPath) 215 if err != nil { 216 return fmt.Errorf("Error uploading client private keys: %s", err) 217 } 218 } 219 220 // Compile the facter variables 221 facterVars := make([]string, 0, len(p.config.Facter)) 222 for k, v := range p.config.Facter { 223 facterVars = append(facterVars, fmt.Sprintf(p.guestOSTypeConfig.facterVarsFmt, k, v)) 224 } 225 226 // Execute Puppet 227 p.config.ctx.Data = &ExecuteTemplate{ 228 FacterVars: strings.Join(facterVars, p.guestOSTypeConfig.facterVarsJoiner), 229 ClientCertPath: remoteClientCertPath, 230 ClientPrivateKeyPath: remoteClientPrivateKeyPath, 231 PuppetNode: p.config.PuppetNode, 232 PuppetServer: p.config.PuppetServer, 233 Options: p.config.Options, 234 PuppetBinDir: p.config.PuppetBinDir, 235 Sudo: !p.config.PreventSudo, 236 } 237 command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx) 238 if err != nil { 239 return err 240 } 241 242 cmd := &packer.RemoteCmd{ 243 Command: command, 244 } 245 246 ui.Message(fmt.Sprintf("Running Puppet: %s", command)) 247 if err := cmd.StartWithUi(comm, ui); err != nil { 248 return err 249 } 250 251 if cmd.ExitStatus != 0 && cmd.ExitStatus != 2 && !p.config.IgnoreExitCodes { 252 return fmt.Errorf("Puppet exited with a non-zero exit status: %d", cmd.ExitStatus) 253 } 254 255 return nil 256 } 257 258 func (p *Provisioner) Cancel() { 259 // Just hard quit. It isn't a big deal if what we're doing keeps 260 // running on the other side. 261 os.Exit(0) 262 } 263 264 func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error { 265 ui.Message(fmt.Sprintf("Creating directory: %s", dir)) 266 267 cmd := &packer.RemoteCmd{Command: p.guestCommands.CreateDir(dir)} 268 if err := cmd.StartWithUi(comm, ui); err != nil { 269 return err 270 } 271 if cmd.ExitStatus != 0 { 272 return fmt.Errorf("Non-zero exit status. See output above for more info.") 273 } 274 275 // Chmod the directory to 0777 just so that we can access it as our user 276 cmd = &packer.RemoteCmd{Command: p.guestCommands.Chmod(dir, "0777")} 277 if err := cmd.StartWithUi(comm, ui); err != nil { 278 return err 279 } 280 if cmd.ExitStatus != 0 { 281 return fmt.Errorf("Non-zero exit status. See output above for more info.") 282 } 283 284 return nil 285 } 286 287 func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, dst string, src string) error { 288 if err := p.createDir(ui, comm, dst); err != nil { 289 return err 290 } 291 292 // Make sure there is a trailing "/" so that the directory isn't 293 // created on the other side. 294 if src[len(src)-1] != '/' { 295 src = src + "/" 296 } 297 298 return comm.UploadDir(dst, src, nil) 299 }