Merge pull request #5252 from c22/packer_4391_rework

#4391 rework (see #4426)
This commit is contained in:
Matthew Hooker 2017-08-31 13:46:48 -07:00 committed by GitHub
commit 54920422ea
4 changed files with 226 additions and 87 deletions

View File

@ -1,4 +1,4 @@
// This package implements a provisioner for Packer that executes
// Package puppetmasterless implements a provisioner for Packer that executes
// Puppet on the remote machine, configured to apply a local manifest
// versus connecting to a Puppet master.
package puppetmasterless
@ -12,6 +12,7 @@ import (
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/provisioner"
"github.com/hashicorp/packer/template/interpolate"
)
@ -61,10 +62,51 @@ type Config struct {
// If true, packer will ignore all exit-codes from a puppet run
IgnoreExitCodes bool `mapstructure:"ignore_exit_codes"`
// The Guest OS Type (unix or windows)
GuestOSType string `mapstructure:"guest_os_type"`
}
type guestOSTypeConfig struct {
stagingDir string
executeCommand string
facterVarsFmt string
modulePathJoiner string
}
var guestOSTypeConfigs = map[string]guestOSTypeConfig{
provisioner.UnixOSType: {
stagingDir: "/tmp/packer-puppet-masterless",
executeCommand: "cd {{.WorkingDir}} && " +
"{{.FacterVars}} {{if .Sudo}} sudo -E {{end}}" +
"puppet apply --verbose --modulepath='{{.ModulePath}}' " +
"{{if ne .HieraConfigPath \"\"}}--hiera_config='{{.HieraConfigPath}}' {{end}}" +
"{{if ne .ManifestDir \"\"}}--manifestdir='{{.ManifestDir}}' {{end}}" +
"--detailed-exitcodes " +
"{{if ne .ExtraArguments \"\"}}{{.ExtraArguments}} {{end}}" +
"{{.ManifestFile}}",
facterVarsFmt: "FACTER_%s='%s'",
modulePathJoiner: ":",
},
provisioner.WindowsOSType: {
stagingDir: "C:/Windows/Temp/packer-puppet-masterless",
executeCommand: "cd {{.WorkingDir}} && " +
"{{.FacterVars}} && " +
"puppet apply --verbose --modulepath='{{.ModulePath}}' " +
"{{if ne .HieraConfigPath \"\"}}--hiera_config='{{.HieraConfigPath}}' {{end}}" +
"{{if ne .ManifestDir \"\"}}--manifestdir='{{.ManifestDir}}' {{end}}" +
"--detailed-exitcodes " +
"{{if ne .ExtraArguments \"\"}}{{.ExtraArguments}} {{end}}" +
"{{.ManifestFile}}",
facterVarsFmt: "SET \"FACTER_%s=%s\" &",
modulePathJoiner: ";",
},
}
type Provisioner struct {
config Config
config Config
guestOSTypeConfig guestOSTypeConfig
guestCommands *provisioner.GuestCommands
}
type ExecuteTemplate struct {
@ -94,20 +136,32 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
}
// Set some defaults
if p.config.GuestOSType == "" {
p.config.GuestOSType = provisioner.DefaultOSType
}
p.config.GuestOSType = strings.ToLower(p.config.GuestOSType)
var ok bool
p.guestOSTypeConfig, ok = guestOSTypeConfigs[p.config.GuestOSType]
if !ok {
return fmt.Errorf("Invalid guest_os_type: \"%s\"", p.config.GuestOSType)
}
p.guestCommands, err = provisioner.NewGuestCommands(p.config.GuestOSType, !p.config.PreventSudo)
if err != nil {
return fmt.Errorf("Invalid guest_os_type: \"%s\"", p.config.GuestOSType)
}
if p.config.ExecuteCommand == "" {
p.config.ExecuteCommand = "cd {{.WorkingDir}} && " +
"{{.FacterVars}} {{if .Sudo}} sudo -E {{end}}" +
"{{if ne .PuppetBinDir \"\"}}{{.PuppetBinDir}}/{{end}}puppet apply " +
"--verbose --modulepath='{{.ModulePath}}' " +
"{{if ne .HieraConfigPath \"\"}}--hiera_config='{{.HieraConfigPath}}' {{end}}" +
"{{if ne .ManifestDir \"\"}}--manifestdir='{{.ManifestDir}}' {{end}}" +
"--detailed-exitcodes " +
"{{if ne .ExtraArguments \"\"}}{{.ExtraArguments}} {{end}}" +
"{{.ManifestFile}}"
p.config.ExecuteCommand = p.guestOSTypeConfig.executeCommand
}
if p.config.ExecuteCommand == "" {
p.config.ExecuteCommand = p.guestOSTypeConfig.executeCommand
}
if p.config.StagingDir == "" {
p.config.StagingDir = "/tmp/packer-puppet-masterless"
p.config.StagingDir = p.guestOSTypeConfig.stagingDir
}
if p.config.WorkingDir == "" {
@ -223,7 +277,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
// Compile the facter variables
facterVars := make([]string, 0, len(p.config.Facter))
for k, v := range p.config.Facter {
facterVars = append(facterVars, fmt.Sprintf("FACTER_%s='%s'", k, v))
facterVars = append(facterVars, fmt.Sprintf(p.guestOSTypeConfig.facterVarsFmt, k, v))
}
// Execute Puppet
@ -232,7 +286,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
HieraConfigPath: remoteHieraConfigPath,
ManifestDir: remoteManifestDir,
ManifestFile: remoteManifestFile,
ModulePath: strings.Join(modulePaths, ":"),
ModulePath: strings.Join(modulePaths, p.guestOSTypeConfig.modulePathJoiner),
PuppetBinDir: p.config.PuppetBinDir,
Sudo: !p.config.PreventSudo,
WorkingDir: p.config.WorkingDir,
@ -249,7 +303,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
ui.Message(fmt.Sprintf("Running Puppet: %s", command))
if err := cmd.StartWithUi(comm, ui); err != nil {
return err
return fmt.Errorf("Got an error starting command: %s", err)
}
if cmd.ExitStatus != 0 && cmd.ExitStatus != 2 && !p.config.IgnoreExitCodes {
@ -314,30 +368,29 @@ func (p *Provisioner) uploadManifests(ui packer.Ui, comm packer.Communicator) (s
return "", fmt.Errorf("Error uploading manifest dir: %s", err)
}
return remoteManifestDir, nil
} else {
// Otherwise manifest_file is a file and we'll upload it
ui.Message(fmt.Sprintf(
"Uploading manifest file from: %s", p.config.ManifestFile))
f, err := os.Open(p.config.ManifestFile)
if err != nil {
return "", err
}
defer f.Close()
manifestFilename := filepath.Base(p.config.ManifestFile)
remoteManifestFile := fmt.Sprintf("%s/%s", remoteManifestsPath, manifestFilename)
if err := comm.Upload(remoteManifestFile, f, nil); err != nil {
return "", err
}
return remoteManifestFile, nil
}
// Otherwise manifest_file is a file and we'll upload it
ui.Message(fmt.Sprintf(
"Uploading manifest file from: %s", p.config.ManifestFile))
f, err := os.Open(p.config.ManifestFile)
if err != nil {
return "", err
}
defer f.Close()
manifestFilename := filepath.Base(p.config.ManifestFile)
remoteManifestFile := fmt.Sprintf("%s/%s", remoteManifestsPath, manifestFilename)
if err := comm.Upload(remoteManifestFile, f, nil); err != nil {
return "", err
}
return remoteManifestFile, nil
}
func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
cmd := &packer.RemoteCmd{
Command: fmt.Sprintf("mkdir -p '%s'", dir),
}
ui.Message(fmt.Sprintf("Creating directory: %s", dir))
cmd := &packer.RemoteCmd{Command: p.guestCommands.CreateDir(dir)}
if err := cmd.StartWithUi(comm, ui); err != nil {
return err

View File

@ -1,4 +1,4 @@
// This package implements a provisioner for Packer that executes
// Package puppetserver implements a provisioner for Packer that executes
// Puppet on the remote machine connecting to a Puppet master.
package puppetserver
@ -10,9 +10,45 @@ import (
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/provisioner"
"github.com/hashicorp/packer/template/interpolate"
)
type guestOSTypeConfig struct {
executeCommand string
facterVarsFmt string
stagingDir string
}
var guestOSTypeConfigs = map[string]guestOSTypeConfig{
provisioner.UnixOSType: {
executeCommand: "{{.FacterVars}} {{if .Sudo}}sudo -E {{end}}" +
"{{if ne .PuppetBinDir \"\"}}{{.PuppetBinDir}}/{{end}}puppet agent " +
"--onetime --no-daemonize " +
"{{if ne .PuppetServer \"\"}}--server='{{.PuppetServer}}' {{end}}" +
"{{if ne .Options \"\"}}{{.Options}} {{end}}" +
"{{if ne .PuppetNode \"\"}}--certname={{.PuppetNode}} {{end}}" +
"{{if ne .ClientCertPath \"\"}}--certdir='{{.ClientCertPath}}' {{end}}" +
"{{if ne .ClientPrivateKeyPath \"\"}}--privatekeydir='{{.ClientPrivateKeyPath}}' {{end}}" +
"--detailed-exitcodes",
facterVarsFmt: "FACTER_%s='%s'",
stagingDir: "/tmp/packer-puppet-server",
},
provisioner.WindowsOSType: {
executeCommand: "{{.FacterVars}} " +
"{{if ne .PuppetBinDir \"\"}}{{.PuppetBinDir}}/{{end}}puppet agent " +
"--onetime --no-daemonize " +
"{{if ne .PuppetServer \"\"}}--server='{{.PuppetServer}}' {{end}}" +
"{{if ne .Options \"\"}}{{.Options}} {{end}}" +
"{{if ne .PuppetNode \"\"}}--certname={{.PuppetNode}} {{end}}" +
"{{if ne .ClientCertPath \"\"}}--certdir='{{.ClientCertPath}}' {{end}}" +
"{{if ne .ClientPrivateKeyPath \"\"}}--privatekeydir='{{.ClientPrivateKeyPath}}' {{end}}" +
"--detailed-exitcodes",
facterVarsFmt: "SET \"FACTER_%s=%s\" &",
stagingDir: "C:/Windows/Temp/packer-puppet-server",
},
}
type Config struct {
common.PackerConfig `mapstructure:",squash"`
ctx interpolate.Context
@ -20,6 +56,9 @@ type Config struct {
// The command used to execute Puppet.
ExecuteCommand string `mapstructure:"execute_command"`
// The Guest OS Type (unix or windows)
GuestOSType string `mapstructure:"guest_os_type"`
// Additional facts to set when executing Puppet
Facter map[string]string
@ -54,7 +93,9 @@ type Config struct {
}
type Provisioner struct {
config Config
config Config
guestOSTypeConfig guestOSTypeConfig
guestCommands *provisioner.GuestCommands
}
type ExecuteTemplate struct {
@ -82,12 +123,28 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
return err
}
if p.config.GuestOSType == "" {
p.config.GuestOSType = provisioner.DefaultOSType
}
p.config.GuestOSType = strings.ToLower(p.config.GuestOSType)
var ok bool
p.guestOSTypeConfig, ok = guestOSTypeConfigs[p.config.GuestOSType]
if !ok {
return fmt.Errorf("Invalid guest_os_type: \"%s\"", p.config.GuestOSType)
}
p.guestCommands, err = provisioner.NewGuestCommands(p.config.GuestOSType, !p.config.PreventSudo)
if err != nil {
return fmt.Errorf("Invalid guest_os_type: \"%s\"", p.config.GuestOSType)
}
if p.config.ExecuteCommand == "" {
p.config.ExecuteCommand = p.commandTemplate()
p.config.ExecuteCommand = p.guestOSTypeConfig.executeCommand
}
if p.config.StagingDir == "" {
p.config.StagingDir = "/tmp/packer-puppet-server"
p.config.StagingDir = p.guestOSTypeConfig.stagingDir
}
if p.config.Facter == nil {
@ -160,7 +217,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
// Compile the facter variables
facterVars := make([]string, 0, len(p.config.Facter))
for k, v := range p.config.Facter {
facterVars = append(facterVars, fmt.Sprintf("FACTER_%s='%s'", k, v))
facterVars = append(facterVars, fmt.Sprintf(p.guestOSTypeConfig.facterVarsFmt, k, v))
}
// Execute Puppet
@ -202,16 +259,23 @@ func (p *Provisioner) Cancel() {
}
func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
cmd := &packer.RemoteCmd{
Command: fmt.Sprintf("mkdir -p '%s'", dir),
}
ui.Message(fmt.Sprintf("Creating directory: %s", dir))
cmd := &packer.RemoteCmd{Command: p.guestCommands.CreateDir(dir)}
if err := cmd.StartWithUi(comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
return fmt.Errorf("Non-zero exit status.")
return fmt.Errorf("Non-zero exit status. See output above for more info.")
}
// Chmod the directory to 0777 just so that we can access it as our user
cmd = &packer.RemoteCmd{Command: p.guestCommands.Chmod(dir, "0777")}
if err := cmd.StartWithUi(comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
return fmt.Errorf("Non-zero exit status. See output above for more info.")
}
return nil
@ -230,15 +294,3 @@ func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, ds
return comm.UploadDir(dst, src, nil)
}
func (p *Provisioner) commandTemplate() string {
return "{{.FacterVars}} {{if .Sudo}} sudo -E {{end}}" +
"{{if ne .PuppetBinDir \"\"}}{{.PuppetBinDir}}/{{end}}puppet agent " +
"--onetime --no-daemonize " +
"{{if ne .PuppetServer \"\"}}--server='{{.PuppetServer}}' {{end}}" +
"{{if ne .Options \"\"}}{{.Options}} {{end}}" +
"{{if ne .PuppetNode \"\"}}--certname={{.PuppetNode}} {{end}}" +
"{{if ne .ClientCertPath \"\"}}--certdir='{{.ClientCertPath}}' {{end}}" +
"{{if ne .ClientPrivateKeyPath \"\"}}--privatekeydir='{{.ClientPrivateKeyPath}}' {{end}}" +
"--detailed-exitcodes"
}

View File

@ -59,6 +59,10 @@ Optional parameters:
variables](/docs/templates/engine.html) available. See
below for more information.
- `guest_os_type` (string) - The target guest OS type, either "unix" or
"windows". Setting this to "windows" will cause the provisioner to use
Windows friendly paths and commands. By default, this is "unix".
- `extra_arguments` (array of strings) - This is an array of additional options to
pass to the puppet command when executing puppet. This allows for
customization of the `execute_command` without having to completely replace
@ -99,12 +103,13 @@ multiple manifests you should use `manifest_file` instead.
executed to run Puppet are executed with `sudo`. If this is true, then the
sudo will be omitted.
- `staging_directory` (string) - This is the directory where all the
configuration of Puppet by Packer will be placed. By default this
is "/tmp/packer-puppet-masterless". This directory doesn't need to exist but
must have proper permissions so that the SSH user that Packer uses is able
to create directories and write into this folder. If the permissions are not
correct, use a shell provisioner prior to this to configure it properly.
- `staging_directory` (string) - This is the directory where all the configuration
of Puppet by Packer will be placed. By default this is "/tmp/packer-puppet-masterless"
when guest OS type is unix and "C:/Windows/Temp/packer-puppet-masterless" when windows.
This directory doesn't need to exist but must have proper permissions so that the SSH
user that Packer uses is able to create directories and write into this folder.
If the permissions are not correct, use a shell provisioner prior to this to configure
it properly.
- `working_directory` (string) - This is the directory from which the puppet
command will be run. When using hiera with a relative path, this option
@ -117,17 +122,28 @@ multiple manifests you should use `manifest_file` instead.
By default, Packer uses the following command (broken across multiple lines for
readability) to execute Puppet:
``` liquid
cd {{.WorkingDir}} && \
{{.FacterVars}}{{if .Sudo}} sudo -E {{end}} \
{{if ne .PuppetBinDir \"\"}}{{.PuppetBinDir}}{{end}}puppet apply \
--verbose \
--modulepath='{{.ModulePath}}' \
{{if ne .HieraConfigPath ""}}--hiera_config='{{.HieraConfigPath}}' {{end}} \
{{if ne .ManifestDir ""}}--manifestdir='{{.ManifestDir}}' {{end}} \
--detailed-exitcodes \
{{if ne .ExtraArguments ""}}{{.ExtraArguments}} {{end}} \
{{.ManifestFile}}
```
cd {{.WorkingDir}} &&
{{.FacterVars}} {{if .Sudo}} sudo -E {{end}}
puppet apply --verbose --modulepath='{{.ModulePath}}'
{{if ne .HieraConfigPath ""}}--hiera_config='{{.HieraConfigPath}}' {{end}}
{{if ne .ManifestDir ""}}--manifestdir='{{.ManifestDir}}' {{end}}
--detailed-exitcodes
{{if ne .ExtraArguments ""}}{{.ExtraArguments}} {{end}}
{{.ManifestFile}}
```
The following command is used if guest OS type is windows:
```
cd {{.WorkingDir}} &&
{{.FacterVars}} &&
puppet apply --verbose --modulepath='{{.ModulePath}}'
{{if ne .HieraConfigPath ""}}--hiera_config='{{.HieraConfigPath}}' {{end}}
{{if ne .ManifestDir ""}}--manifestdir='{{.ManifestDir}}' {{end}}
--detailed-exitcodes
{{if ne .ExtraArguments ""}}{{.ExtraArguments}} {{end}}
{{.ManifestFile}}
```
This command can be customized using the `execute_command` configuration. As you

View File

@ -81,20 +81,38 @@ listed below:
or `%PATH%` environment variable, but some builders (notably, the Docker one) do
not run profile-setup scripts, therefore the path is usually empty.
- `execute_command` (string) - This is optional. The command used to execute Puppet. This has
various [configuration template
variables](/docs/templates/engine.html) available. See
below for more information. By default, Packer uses the following command:
- `guest_os_type` (string) - The target guest OS type, either "unix" or
"windows". Setting this to "windows" will cause the provisioner to use
Windows friendly paths and commands. By default, this is "unix".
``` liquid
{{.FacterVars}} {{if .Sudo}} sudo -E {{end}} \
{{if ne .PuppetBinDir \"\"}}{{.PuppetBinDir}}/{{end}}puppet agent --onetime --no-daemonize \
{{if ne .PuppetServer \"\"}}--server='{{.PuppetServer}}' {{end}} \
{{if ne .Options \"\"}}{{.Options}} {{end}} \
{{if ne .PuppetNode \"\"}}--certname={{.PuppetNode}} {{end}} \
{{if ne .ClientCertPath \"\"}}--certdir='{{.ClientCertPath}}' {{end}} \
{{if ne .ClientPrivateKeyPath \"\"}}--privatekeydir='{{.ClientPrivateKeyPath}}' \
{{end}} --detailed-exitcodes
- `execute_command` (string) - This is optional. The command used to execute Puppet. This has
various [configuration template variables](/docs/templates/engine.html) available. By default,
Packer uses the following command (broken across multiple lines for readability) to execute Puppet:
```
{{.FacterVars}} {{if .Sudo}}sudo -E {{end}}
{{if ne .PuppetBinDir ""}}{{.PuppetBinDir}}/{{end}}puppet agent
--onetime --no-daemonize
{{if ne .PuppetServer ""}}--server='{{.PuppetServer}}' {{end}}
{{if ne .Options ""}}{{.Options}} {{end}}
{{if ne .PuppetNode ""}}--certname={{.PuppetNode}} {{end}}
{{if ne .ClientCertPath ""}}--certdir='{{.ClientCertPath}}' {{end}}
{{if ne .ClientPrivateKeyPath ""}}--privatekeydir='{{.ClientPrivateKeyPath}}' {{end}}
--detailed-exitcodes
```
The following command is used if guest OS type is windows:
```
{{.FacterVars}}
{{if ne .PuppetBinDir ""}}{{.PuppetBinDir}}/{{end}}puppet agent
--onetime --no-daemonize
{{if ne .PuppetServer ""}}--server='{{.PuppetServer}}' {{end}}
{{if ne .Options ""}}{{.Options}} {{end}}
{{if ne .PuppetNode ""}}--certname={{.PuppetNode}} {{end}}
{{if ne .ClientCertPath ""}}--certdir='{{.ClientCertPath}}' {{end}}
{{if ne .ClientPrivateKeyPath ""}}--privatekeydir='{{.ClientPrivateKeyPath}}' {{end}}
--detailed-exitcodes
```
## Default Facts