github.com/amanya/packer@v0.12.1-0.20161117214323-902ac5ab2eb6/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 var errs *packer.MultiError 94 if p.config.ClientCertPath != "" { 95 info, err := os.Stat(p.config.ClientCertPath) 96 if err != nil { 97 errs = packer.MultiErrorAppend(errs, 98 fmt.Errorf("client_cert_dir is invalid: %s", err)) 99 } else if !info.IsDir() { 100 errs = packer.MultiErrorAppend(errs, 101 fmt.Errorf("client_cert_dir must point to a directory")) 102 } 103 } 104 105 if p.config.ClientPrivateKeyPath != "" { 106 info, err := os.Stat(p.config.ClientPrivateKeyPath) 107 if err != nil { 108 errs = packer.MultiErrorAppend(errs, 109 fmt.Errorf("client_private_key_dir is invalid: %s", err)) 110 } else if !info.IsDir() { 111 errs = packer.MultiErrorAppend(errs, 112 fmt.Errorf("client_private_key_dir must point to a directory")) 113 } 114 } 115 116 if errs != nil && len(errs.Errors) > 0 { 117 return errs 118 } 119 120 return nil 121 } 122 123 func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { 124 ui.Say("Provisioning with Puppet...") 125 ui.Message("Creating Puppet staging directory...") 126 if err := p.createDir(ui, comm, p.config.StagingDir); err != nil { 127 return fmt.Errorf("Error creating staging directory: %s", err) 128 } 129 130 // Upload client cert dir if set 131 remoteClientCertPath := "" 132 if p.config.ClientCertPath != "" { 133 ui.Message(fmt.Sprintf( 134 "Uploading client cert from: %s", p.config.ClientCertPath)) 135 remoteClientCertPath = fmt.Sprintf("%s/certs", p.config.StagingDir) 136 err := p.uploadDirectory(ui, comm, remoteClientCertPath, p.config.ClientCertPath) 137 if err != nil { 138 return fmt.Errorf("Error uploading client cert: %s", err) 139 } 140 } 141 142 // Upload client cert dir if set 143 remoteClientPrivateKeyPath := "" 144 if p.config.ClientPrivateKeyPath != "" { 145 ui.Message(fmt.Sprintf( 146 "Uploading client private keys from: %s", p.config.ClientPrivateKeyPath)) 147 remoteClientPrivateKeyPath = fmt.Sprintf("%s/private_keys", p.config.StagingDir) 148 err := p.uploadDirectory(ui, comm, remoteClientPrivateKeyPath, p.config.ClientPrivateKeyPath) 149 if err != nil { 150 return fmt.Errorf("Error uploading client private keys: %s", err) 151 } 152 } 153 154 // Compile the facter variables 155 facterVars := make([]string, 0, len(p.config.Facter)) 156 for k, v := range p.config.Facter { 157 facterVars = append(facterVars, fmt.Sprintf("FACTER_%s='%s'", k, v)) 158 } 159 160 // Execute Puppet 161 p.config.ctx.Data = &ExecuteTemplate{ 162 FacterVars: strings.Join(facterVars, " "), 163 ClientCertPath: remoteClientCertPath, 164 ClientPrivateKeyPath: remoteClientPrivateKeyPath, 165 PuppetNode: p.config.PuppetNode, 166 PuppetServer: p.config.PuppetServer, 167 Options: p.config.Options, 168 PuppetBinDir: p.config.PuppetBinDir, 169 Sudo: !p.config.PreventSudo, 170 } 171 command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx) 172 if err != nil { 173 return err 174 } 175 176 cmd := &packer.RemoteCmd{ 177 Command: command, 178 } 179 180 ui.Message(fmt.Sprintf("Running Puppet: %s", command)) 181 if err := cmd.StartWithUi(comm, ui); err != nil { 182 return err 183 } 184 185 if cmd.ExitStatus != 0 && cmd.ExitStatus != 2 && !p.config.IgnoreExitCodes { 186 return fmt.Errorf("Puppet exited with a non-zero exit status: %d", cmd.ExitStatus) 187 } 188 189 return nil 190 } 191 192 func (p *Provisioner) Cancel() { 193 // Just hard quit. It isn't a big deal if what we're doing keeps 194 // running on the other side. 195 os.Exit(0) 196 } 197 198 func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error { 199 cmd := &packer.RemoteCmd{ 200 Command: fmt.Sprintf("mkdir -p '%s'", dir), 201 } 202 203 if err := cmd.StartWithUi(comm, ui); err != nil { 204 return err 205 } 206 207 if cmd.ExitStatus != 0 { 208 return fmt.Errorf("Non-zero exit status.") 209 } 210 211 return nil 212 } 213 214 func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, dst string, src string) error { 215 if err := p.createDir(ui, comm, dst); err != nil { 216 return err 217 } 218 219 // Make sure there is a trailing "/" so that the directory isn't 220 // created on the other side. 221 if src[len(src)-1] != '/' { 222 src = src + "/" 223 } 224 225 return comm.UploadDir(dst, src, nil) 226 } 227 228 func (p *Provisioner) commandTemplate() string { 229 return "{{.FacterVars}} {{if .Sudo}} sudo -E {{end}}" + 230 "{{if ne .PuppetBinDir \"\"}}{{.PuppetBinDir}}/{{end}}puppet agent " + 231 "--onetime --no-daemonize " + 232 "{{if ne .PuppetServer \"\"}}--server='{{.PuppetServer}}' {{end}}" + 233 "{{if ne .Options \"\"}}{{.Options}} {{end}}" + 234 "{{if ne .PuppetNode \"\"}}--certname={{.PuppetNode}} {{end}}" + 235 "{{if ne .ClientCertPath \"\"}}--certdir='{{.ClientCertPath}}' {{end}}" + 236 "{{if ne .ClientPrivateKeyPath \"\"}}--privatekeydir='{{.ClientPrivateKeyPath}}' {{end}}" + 237 "--detailed-exitcodes" 238 }