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