github.com/marksheahan/packer@v0.10.2-0.20160613200515-1acb2d6645a0/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 // If true, packer will ignore all exit-codes from a puppet run 49 IgnoreExitCodes bool `mapstructure:"ignore_exit_codes"` 50 } 51 52 type Provisioner struct { 53 config Config 54 } 55 56 type ExecuteTemplate struct { 57 FacterVars string 58 ClientCertPath string 59 ClientPrivateKeyPath string 60 PuppetNode string 61 PuppetServer string 62 Options string 63 Sudo bool 64 } 65 66 func (p *Provisioner) Prepare(raws ...interface{}) error { 67 err := config.Decode(&p.config, &config.DecodeOpts{ 68 Interpolate: true, 69 InterpolateContext: &p.config.ctx, 70 InterpolateFilter: &interpolate.RenderFilter{ 71 Exclude: []string{ 72 "execute_command", 73 }, 74 }, 75 }, raws...) 76 if err != nil { 77 return err 78 } 79 80 if p.config.ExecuteCommand == "" { 81 p.config.ExecuteCommand = p.commandTemplate() 82 } 83 84 if p.config.StagingDir == "" { 85 p.config.StagingDir = "/tmp/packer-puppet-server" 86 } 87 88 var errs *packer.MultiError 89 if p.config.ClientCertPath != "" { 90 info, err := os.Stat(p.config.ClientCertPath) 91 if err != nil { 92 errs = packer.MultiErrorAppend(errs, 93 fmt.Errorf("client_cert_dir is invalid: %s", err)) 94 } else if !info.IsDir() { 95 errs = packer.MultiErrorAppend(errs, 96 fmt.Errorf("client_cert_dir must point to a directory")) 97 } 98 } 99 100 if p.config.ClientPrivateKeyPath != "" { 101 info, err := os.Stat(p.config.ClientPrivateKeyPath) 102 if err != nil { 103 errs = packer.MultiErrorAppend(errs, 104 fmt.Errorf("client_private_key_dir is invalid: %s", err)) 105 } else if !info.IsDir() { 106 errs = packer.MultiErrorAppend(errs, 107 fmt.Errorf("client_private_key_dir must point to a directory")) 108 } 109 } 110 111 if errs != nil && len(errs.Errors) > 0 { 112 return errs 113 } 114 115 return nil 116 } 117 118 func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { 119 ui.Say("Provisioning with Puppet...") 120 ui.Message("Creating Puppet staging directory...") 121 if err := p.createDir(ui, comm, p.config.StagingDir); err != nil { 122 return fmt.Errorf("Error creating staging directory: %s", err) 123 } 124 125 // Upload client cert dir if set 126 remoteClientCertPath := "" 127 if p.config.ClientCertPath != "" { 128 ui.Message(fmt.Sprintf( 129 "Uploading client cert from: %s", p.config.ClientCertPath)) 130 remoteClientCertPath = fmt.Sprintf("%s/certs", p.config.StagingDir) 131 err := p.uploadDirectory(ui, comm, remoteClientCertPath, p.config.ClientCertPath) 132 if err != nil { 133 return fmt.Errorf("Error uploading client cert: %s", err) 134 } 135 } 136 137 // Upload client cert dir if set 138 remoteClientPrivateKeyPath := "" 139 if p.config.ClientPrivateKeyPath != "" { 140 ui.Message(fmt.Sprintf( 141 "Uploading client private keys from: %s", p.config.ClientPrivateKeyPath)) 142 remoteClientPrivateKeyPath = fmt.Sprintf("%s/private_keys", p.config.StagingDir) 143 err := p.uploadDirectory(ui, comm, remoteClientPrivateKeyPath, p.config.ClientPrivateKeyPath) 144 if err != nil { 145 return fmt.Errorf("Error uploading client private keys: %s", err) 146 } 147 } 148 149 // Compile the facter variables 150 facterVars := make([]string, 0, len(p.config.Facter)) 151 for k, v := range p.config.Facter { 152 facterVars = append(facterVars, fmt.Sprintf("FACTER_%s='%s'", k, v)) 153 } 154 155 // Execute Puppet 156 p.config.ctx.Data = &ExecuteTemplate{ 157 FacterVars: strings.Join(facterVars, " "), 158 ClientCertPath: remoteClientCertPath, 159 ClientPrivateKeyPath: remoteClientPrivateKeyPath, 160 PuppetNode: p.config.PuppetNode, 161 PuppetServer: p.config.PuppetServer, 162 Options: p.config.Options, 163 Sudo: !p.config.PreventSudo, 164 } 165 command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx) 166 if err != nil { 167 return err 168 } 169 170 cmd := &packer.RemoteCmd{ 171 Command: command, 172 } 173 174 ui.Message(fmt.Sprintf("Running Puppet: %s", command)) 175 if err := cmd.StartWithUi(comm, ui); err != nil { 176 return err 177 } 178 179 if cmd.ExitStatus != 0 && cmd.ExitStatus != 2 && !p.config.IgnoreExitCodes { 180 return fmt.Errorf("Puppet exited with a non-zero exit status: %d", cmd.ExitStatus) 181 } 182 183 return nil 184 } 185 186 func (p *Provisioner) Cancel() { 187 // Just hard quit. It isn't a big deal if what we're doing keeps 188 // running on the other side. 189 os.Exit(0) 190 } 191 192 func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error { 193 cmd := &packer.RemoteCmd{ 194 Command: fmt.Sprintf("mkdir -p '%s'", dir), 195 } 196 197 if err := cmd.StartWithUi(comm, ui); err != nil { 198 return err 199 } 200 201 if cmd.ExitStatus != 0 { 202 return fmt.Errorf("Non-zero exit status.") 203 } 204 205 return nil 206 } 207 208 func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, dst string, src string) error { 209 if err := p.createDir(ui, comm, dst); err != nil { 210 return err 211 } 212 213 // Make sure there is a trailing "/" so that the directory isn't 214 // created on the other side. 215 if src[len(src)-1] != '/' { 216 src = src + "/" 217 } 218 219 return comm.UploadDir(dst, src, nil) 220 } 221 222 func (p *Provisioner) commandTemplate() string { 223 return "{{.FacterVars}} {{if .Sudo}} sudo -E {{end}}" + 224 "puppet agent --onetime --no-daemonize " + 225 "{{if ne .PuppetServer \"\"}}--server='{{.PuppetServer}}' {{end}}" + 226 "{{if ne .Options \"\"}}{{.Options}} {{end}}" + 227 "{{if ne .PuppetNode \"\"}}--certname={{.PuppetNode}} {{end}}" + 228 "{{if ne .ClientCertPath \"\"}}--certdir='{{.ClientCertPath}}' {{end}}" + 229 "{{if ne .ClientPrivateKeyPath \"\"}}--privatekeydir='{{.ClientPrivateKeyPath}}' {{end}}" + 230 "--detailed-exitcodes" 231 }