Merge branch 'chef-provisioner-windows' of https://github.com/sneal/packer into f-chef-provisioner-windows
This commit is contained in:
commit
87532b1b00
|
@ -16,19 +16,41 @@ import (
|
|||
"github.com/mitchellh/packer/common/uuid"
|
||||
"github.com/mitchellh/packer/helper/config"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/provisioner"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
type guestOSTypeConfig struct {
|
||||
executeCommand string
|
||||
installCommand string
|
||||
stagingDir string
|
||||
}
|
||||
|
||||
var guestOSTypeConfigs = map[string]guestOSTypeConfig{
|
||||
provisioner.UnixOSType: guestOSTypeConfig{
|
||||
executeCommand: "{{if .Sudo}}sudo {{end}}chef-client --no-color -c {{.ConfigPath}} -j {{.JsonPath}}",
|
||||
installCommand: "curl -L https://www.chef.io/chef/install.sh | {{if .Sudo}}sudo {{end}}bash",
|
||||
stagingDir: "/tmp/packer-chef-client",
|
||||
},
|
||||
provisioner.WindowsOSType: guestOSTypeConfig{
|
||||
executeCommand: "c:/opscode/chef/bin/chef-client.bat --no-color -c {{.ConfigPath}} -j {{.JsonPath}}",
|
||||
installCommand: "powershell.exe -Command \"(New-Object System.Net.WebClient).DownloadFile('http://chef.io/chef/install.msi', 'C:\\Windows\\Temp\\chef.msi');Start-Process 'msiexec' -ArgumentList '/qb /i C:\\Windows\\Temp\\chef.msi' -NoNewWindow -Wait\"",
|
||||
stagingDir: "C:/Windows/Temp/packer-chef-client",
|
||||
},
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
ChefEnvironment string `mapstructure:"chef_environment"`
|
||||
EncryptedDataBagSecretPath string `mapstructure:"encrypted_data_bag_secret_path"`
|
||||
SslVerifyMode string `mapstructure:"ssl_verify_mode"`
|
||||
ConfigTemplate string `mapstructure:"config_template"`
|
||||
ExecuteCommand string `mapstructure:"execute_command"`
|
||||
InstallCommand string `mapstructure:"install_command"`
|
||||
Json map[string]interface{}
|
||||
Json map[string]interface{}
|
||||
|
||||
ChefEnvironment string `mapstructure:"chef_environment"`
|
||||
ClientKey string `mapstructure:"client_key"`
|
||||
ConfigTemplate string `mapstructure:"config_template"`
|
||||
EncryptedDataBagSecretPath string `mapstructure:"encrypted_data_bag_secret_path"`
|
||||
ExecuteCommand string `mapstructure:"execute_command"`
|
||||
GuestOSType string `mapstructure:"guest_os_type"`
|
||||
InstallCommand string `mapstructure:"install_command"`
|
||||
NodeName string `mapstructure:"node_name"`
|
||||
PreventSudo bool `mapstructure:"prevent_sudo"`
|
||||
RunList []string `mapstructure:"run_list"`
|
||||
|
@ -36,28 +58,29 @@ type Config struct {
|
|||
SkipCleanClient bool `mapstructure:"skip_clean_client"`
|
||||
SkipCleanNode bool `mapstructure:"skip_clean_node"`
|
||||
SkipInstall bool `mapstructure:"skip_install"`
|
||||
SslVerifyMode string `mapstructure:"ssl_verify_mode"`
|
||||
StagingDir string `mapstructure:"staging_directory"`
|
||||
ClientKey string `mapstructure:"client_key"`
|
||||
ValidationKeyPath string `mapstructure:"validation_key_path"`
|
||||
ValidationClientName string `mapstructure:"validation_client_name"`
|
||||
ValidationKeyPath string `mapstructure:"validation_key_path"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
type Provisioner struct {
|
||||
config Config
|
||||
config Config
|
||||
guestOSTypeConfig guestOSTypeConfig
|
||||
guestCommands *provisioner.GuestCommands
|
||||
}
|
||||
|
||||
type ConfigTemplate struct {
|
||||
NodeName string
|
||||
ServerUrl string
|
||||
ClientKey string
|
||||
ValidationKeyPath string
|
||||
ValidationClientName string
|
||||
EncryptedDataBagSecretPath string
|
||||
ChefEnvironment string
|
||||
SslVerifyMode string
|
||||
HasEncryptedDataBagSecretPath bool
|
||||
ChefEnvironment string
|
||||
ClientKey string
|
||||
EncryptedDataBagSecretPath string
|
||||
NodeName string
|
||||
ServerUrl string
|
||||
SslVerifyMode string
|
||||
ValidationClientName string
|
||||
ValidationKeyPath string
|
||||
}
|
||||
|
||||
type ExecuteTemplate struct {
|
||||
|
@ -85,15 +108,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 = "{{if .Sudo}}sudo {{end}}chef-client " +
|
||||
"--no-color -c {{.ConfigPath}} -j {{.JsonPath}}"
|
||||
p.config.ExecuteCommand = p.guestOSTypeConfig.executeCommand
|
||||
}
|
||||
|
||||
if p.config.InstallCommand == "" {
|
||||
p.config.InstallCommand = "curl -L " +
|
||||
"https://www.chef.io/chef/install.sh | " +
|
||||
"{{if .Sudo}}sudo {{end}}bash"
|
||||
p.config.InstallCommand = p.guestOSTypeConfig.installCommand
|
||||
}
|
||||
|
||||
if p.config.RunList == nil {
|
||||
|
@ -101,7 +137,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
|
|||
}
|
||||
|
||||
if p.config.StagingDir == "" {
|
||||
p.config.StagingDir = "/tmp/packer-chef-client"
|
||||
p.config.StagingDir = p.guestOSTypeConfig.stagingDir
|
||||
}
|
||||
|
||||
var errs *packer.MultiError
|
||||
|
@ -116,6 +152,15 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
|
|||
}
|
||||
}
|
||||
|
||||
if p.config.EncryptedDataBagSecretPath != "" {
|
||||
pFileInfo, err := os.Stat(p.config.EncryptedDataBagSecretPath)
|
||||
|
||||
if err != nil || pFileInfo.IsDir() {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Bad encrypted data bag secret '%s': %s", p.config.EncryptedDataBagSecretPath, err))
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.ServerUrl == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("server_url must be set"))
|
||||
|
@ -180,27 +225,38 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
|||
p.config.ClientKey = fmt.Sprintf("%s/client.pem", p.config.StagingDir)
|
||||
}
|
||||
|
||||
if p.config.ValidationKeyPath != "" {
|
||||
remoteValidationKeyPath = fmt.Sprintf("%s/validation.pem", p.config.StagingDir)
|
||||
if err := p.copyValidationKey(ui, comm, remoteValidationKeyPath); err != nil {
|
||||
return fmt.Errorf("Error copying validation key: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
encryptedDataBagSecretPath := ""
|
||||
if p.config.EncryptedDataBagSecretPath != "" {
|
||||
encryptedDataBagSecretPath = fmt.Sprintf("%s/encrypted_data_bag_secret", p.config.StagingDir)
|
||||
if err := p.uploadFile(ui, comm, encryptedDataBagSecretPath, p.config.EncryptedDataBagSecretPath); err != nil {
|
||||
if err := p.uploadFile(ui,
|
||||
comm,
|
||||
encryptedDataBagSecretPath,
|
||||
p.config.EncryptedDataBagSecretPath); err != nil {
|
||||
return fmt.Errorf("Error uploading encrypted data bag secret: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
configPath, err := p.createConfig(
|
||||
ui, comm, nodeName, serverUrl, p.config.ClientKey, remoteValidationKeyPath, p.config.ValidationClientName, encryptedDataBagSecretPath, p.config.ChefEnvironment, p.config.SslVerifyMode)
|
||||
ui,
|
||||
comm,
|
||||
nodeName,
|
||||
serverUrl,
|
||||
p.config.ClientKey,
|
||||
encryptedDataBagSecretPath,
|
||||
remoteValidationKeyPath,
|
||||
p.config.ValidationClientName,
|
||||
p.config.ChefEnvironment,
|
||||
p.config.SslVerifyMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating Chef config file: %s", err)
|
||||
}
|
||||
|
||||
if p.config.ValidationKeyPath != "" {
|
||||
remoteValidationKeyPath = fmt.Sprintf("%s/validation.pem", p.config.StagingDir)
|
||||
if err := p.uploadFile(ui, comm, remoteValidationKeyPath, p.config.ValidationKeyPath); err != nil {
|
||||
return fmt.Errorf("Error copying validation key: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
jsonPath, err := p.createJson(ui, comm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating JSON attributes: %s", err)
|
||||
|
@ -230,7 +286,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
|||
}
|
||||
|
||||
if err := p.removeDir(ui, comm, p.config.StagingDir); err != nil {
|
||||
return fmt.Errorf("Error removing /etc/chef directory: %s", err)
|
||||
return fmt.Errorf("Error removing %s: %s", p.config.StagingDir, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -256,17 +312,30 @@ func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, ds
|
|||
return comm.UploadDir(dst, src, nil)
|
||||
}
|
||||
|
||||
func (p *Provisioner) uploadFile(ui packer.Ui, comm packer.Communicator, dst string, src string) error {
|
||||
f, err := os.Open(src)
|
||||
func (p *Provisioner) uploadFile(ui packer.Ui, comm packer.Communicator, remotePath string, localPath string) error {
|
||||
ui.Message(fmt.Sprintf("Uploading %s...", localPath))
|
||||
|
||||
f, err := os.Open(localPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return comm.Upload(dst, f, nil)
|
||||
return comm.Upload(remotePath, f, nil)
|
||||
}
|
||||
|
||||
func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, nodeName string, serverUrl string, clientKey string, remoteKeyPath string, validationClientName string, encryptedDataBagSecretPath string, chefEnvironment string, sslVerifyMode string) (string, error) {
|
||||
func (p *Provisioner) createConfig(
|
||||
ui packer.Ui,
|
||||
comm packer.Communicator,
|
||||
nodeName string,
|
||||
serverUrl string,
|
||||
clientKey string,
|
||||
encryptedDataBagSecretPath,
|
||||
remoteKeyPath string,
|
||||
validationClientName string,
|
||||
chefEnvironment string,
|
||||
sslVerifyMode string) (string, error) {
|
||||
|
||||
ui.Message("Creating configuration file 'client.rb'")
|
||||
|
||||
// Read the template
|
||||
|
@ -288,15 +357,14 @@ func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, nodeN
|
|||
|
||||
ctx := p.config.ctx
|
||||
ctx.Data = &ConfigTemplate{
|
||||
NodeName: nodeName,
|
||||
ServerUrl: serverUrl,
|
||||
ClientKey: clientKey,
|
||||
ValidationKeyPath: remoteKeyPath,
|
||||
ValidationClientName: validationClientName,
|
||||
ChefEnvironment: chefEnvironment,
|
||||
SslVerifyMode: sslVerifyMode,
|
||||
EncryptedDataBagSecretPath: encryptedDataBagSecretPath,
|
||||
HasEncryptedDataBagSecretPath: encryptedDataBagSecretPath != "",
|
||||
NodeName: nodeName,
|
||||
ServerUrl: serverUrl,
|
||||
ClientKey: clientKey,
|
||||
ValidationKeyPath: remoteKeyPath,
|
||||
ValidationClientName: validationClientName,
|
||||
ChefEnvironment: chefEnvironment,
|
||||
SslVerifyMode: sslVerifyMode,
|
||||
EncryptedDataBagSecretPath: encryptedDataBagSecretPath,
|
||||
}
|
||||
configString, err := interpolate.Render(tpl, &ctx)
|
||||
if err != nil {
|
||||
|
@ -369,12 +437,7 @@ func (p *Provisioner) createJson(ui packer.Ui, comm packer.Communicator) (string
|
|||
func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
|
||||
ui.Message(fmt.Sprintf("Creating directory: %s", dir))
|
||||
|
||||
mkdirCmd := fmt.Sprintf("mkdir -p '%s'", dir)
|
||||
if !p.config.PreventSudo {
|
||||
mkdirCmd = "sudo " + mkdirCmd
|
||||
}
|
||||
|
||||
cmd := &packer.RemoteCmd{Command: mkdirCmd}
|
||||
cmd := &packer.RemoteCmd{Command: p.guestCommands.CreateDir(dir)}
|
||||
if err := cmd.StartWithUi(comm, ui); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -383,11 +446,7 @@ func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir stri
|
|||
}
|
||||
|
||||
// Chmod the directory to 0777 just so that we can access it as our user
|
||||
mkdirCmd = fmt.Sprintf("chmod 0777 '%s'", dir)
|
||||
if !p.config.PreventSudo {
|
||||
mkdirCmd = "sudo " + mkdirCmd
|
||||
}
|
||||
cmd = &packer.RemoteCmd{Command: mkdirCmd}
|
||||
cmd = &packer.RemoteCmd{Command: p.guestCommands.Chmod(dir, "0777")}
|
||||
if err := cmd.StartWithUi(comm, ui); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -447,15 +506,7 @@ func (p *Provisioner) knifeExec(ui packer.Ui, comm packer.Communicator, node str
|
|||
func (p *Provisioner) removeDir(ui packer.Ui, comm packer.Communicator, dir string) error {
|
||||
ui.Message(fmt.Sprintf("Removing directory: %s", dir))
|
||||
|
||||
rmCmd := fmt.Sprintf("rm -rf '%s'", dir)
|
||||
if !p.config.PreventSudo {
|
||||
rmCmd = "sudo " + rmCmd
|
||||
}
|
||||
|
||||
cmd := &packer.RemoteCmd{
|
||||
Command: rmCmd,
|
||||
}
|
||||
|
||||
cmd := &packer.RemoteCmd{Command: p.guestCommands.RemoveDir(dir)}
|
||||
if err := cmd.StartWithUi(comm, ui); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -502,6 +553,8 @@ func (p *Provisioner) installChef(ui packer.Ui, comm packer.Communicator) error
|
|||
return err
|
||||
}
|
||||
|
||||
ui.Message(command)
|
||||
|
||||
cmd := &packer.RemoteCmd{Command: command}
|
||||
if err := cmd.StartWithUi(comm, ui); err != nil {
|
||||
return err
|
||||
|
@ -515,23 +568,6 @@ func (p *Provisioner) installChef(ui packer.Ui, comm packer.Communicator) error
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) copyValidationKey(ui packer.Ui, comm packer.Communicator, remotePath string) error {
|
||||
ui.Message("Uploading validation key...")
|
||||
|
||||
// First upload the validation key to a writable location
|
||||
f, err := os.Open(p.config.ValidationKeyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := comm.Upload(remotePath, f, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) deepJsonFix(key string, current interface{}) (interface{}, error) {
|
||||
if current == nil {
|
||||
return nil, nil
|
||||
|
@ -619,7 +655,7 @@ log_level :info
|
|||
log_location STDOUT
|
||||
chef_server_url "{{.ServerUrl}}"
|
||||
client_key "{{.ClientKey}}"
|
||||
{{if .HasEncryptedDataBagSecretPath}}
|
||||
{{if ne .EncryptedDataBagSecretPath ""}}
|
||||
encrypted_data_bag_secret "{{.EncryptedDataBagSecretPath}}"
|
||||
{{end}}
|
||||
{{if ne .ValidationClientName ""}}
|
||||
|
|
|
@ -182,53 +182,63 @@ func TestProvisionerPrepare_encryptedDataBagSecretPath(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestProvisioner_createDir(t *testing.T) {
|
||||
p1 := &Provisioner{config: Config{PreventSudo: true}}
|
||||
p2 := &Provisioner{config: Config{PreventSudo: false}}
|
||||
comm := &packer.MockCommunicator{}
|
||||
ui := &packer.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
}
|
||||
for _, sudo := range []bool{true, false} {
|
||||
config := testConfig()
|
||||
config["prevent_sudo"] = !sudo
|
||||
|
||||
if err := p1.createDir(ui, comm, "/tmp/foo"); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
p := &Provisioner{}
|
||||
comm := &packer.MockCommunicator{}
|
||||
ui := &packer.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
}
|
||||
|
||||
if strings.HasPrefix(comm.StartCmd.Command, "sudo") {
|
||||
t.Fatalf("createDir should not use sudo, got: \"%s\"", comm.StartCmd.Command)
|
||||
}
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if err := p2.createDir(ui, comm, "/tmp/foo"); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if err := p.createDir(ui, comm, "/tmp/foo"); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(comm.StartCmd.Command, "sudo") {
|
||||
t.Fatalf("createDir should use sudo, got: \"%s\"", comm.StartCmd.Command)
|
||||
if !sudo && strings.HasPrefix(comm.StartCmd.Command, "sudo") {
|
||||
t.Fatalf("createDir should not use sudo, got: \"%s\"", comm.StartCmd.Command)
|
||||
}
|
||||
|
||||
if sudo && !strings.HasPrefix(comm.StartCmd.Command, "sudo") {
|
||||
t.Fatalf("createDir should use sudo, got: \"%s\"", comm.StartCmd.Command)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisioner_removeDir(t *testing.T) {
|
||||
p1 := &Provisioner{config: Config{PreventSudo: true}}
|
||||
p2 := &Provisioner{config: Config{PreventSudo: false}}
|
||||
comm := &packer.MockCommunicator{}
|
||||
ui := &packer.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
}
|
||||
for _, sudo := range []bool{true, false} {
|
||||
config := testConfig()
|
||||
config["prevent_sudo"] = !sudo
|
||||
|
||||
p := &Provisioner{}
|
||||
comm := &packer.MockCommunicator{}
|
||||
ui := &packer.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
}
|
||||
|
||||
if err := p1.removeDir(ui, comm, "/tmp/foo"); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(comm.StartCmd.Command, "sudo") {
|
||||
t.Fatalf("removeDir should not use sudo, got: \"%s\"", comm.StartCmd.Command)
|
||||
}
|
||||
if err := p.removeDir(ui, comm, "/tmp/foo"); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if err := p2.removeDir(ui, comm, "/tmp/foo"); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if !sudo && strings.HasPrefix(comm.StartCmd.Command, "sudo") {
|
||||
t.Fatalf("removeDir should not use sudo, got: \"%s\"", comm.StartCmd.Command)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(comm.StartCmd.Command, "sudo") {
|
||||
t.Fatalf("removeDir should use sudo, got: \"%s\"", comm.StartCmd.Command)
|
||||
if sudo && !strings.HasPrefix(comm.StartCmd.Command, "sudo") {
|
||||
t.Fatalf("removeDir should use sudo, got: \"%s\"", comm.StartCmd.Command)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,9 +15,29 @@ import (
|
|||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/helper/config"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/provisioner"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
type guestOSTypeConfig struct {
|
||||
executeCommand string
|
||||
installCommand string
|
||||
stagingDir string
|
||||
}
|
||||
|
||||
var guestOSTypeConfigs = map[string]guestOSTypeConfig{
|
||||
provisioner.UnixOSType: guestOSTypeConfig{
|
||||
executeCommand: "{{if .Sudo}}sudo {{end}}chef-solo --no-color -c {{.ConfigPath}} -j {{.JsonPath}}",
|
||||
installCommand: "curl -L https://www.chef.io/chef/install.sh | {{if .Sudo}}sudo {{end}}bash",
|
||||
stagingDir: "/tmp/packer-chef-client",
|
||||
},
|
||||
provisioner.WindowsOSType: guestOSTypeConfig{
|
||||
executeCommand: "c:/opscode/chef/bin/chef-solo.bat --no-color -c {{.ConfigPath}} -j {{.JsonPath}}",
|
||||
installCommand: "powershell.exe -Command \"(New-Object System.Net.WebClient).DownloadFile('http://chef.io/chef/install.msi', 'C:\\Windows\\Temp\\chef.msi');Start-Process 'msiexec' -ArgumentList '/qb /i C:\\Windows\\Temp\\chef.msi' -NoNewWindow -Wait\"",
|
||||
stagingDir: "C:/Windows/Temp/packer-chef-client",
|
||||
},
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
|
@ -36,12 +56,15 @@ type Config struct {
|
|||
RunList []string `mapstructure:"run_list"`
|
||||
SkipInstall bool `mapstructure:"skip_install"`
|
||||
StagingDir string `mapstructure:"staging_directory"`
|
||||
GuestOSType string `mapstructure:"guest_os_type"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
type Provisioner struct {
|
||||
config Config
|
||||
config Config
|
||||
guestOSTypeConfig guestOSTypeConfig
|
||||
guestCommands *provisioner.GuestCommands
|
||||
}
|
||||
|
||||
type ConfigTemplate struct {
|
||||
|
@ -86,12 +109,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 = "{{if .Sudo}}sudo {{end}}chef-solo --no-color -c {{.ConfigPath}} -j {{.JsonPath}}"
|
||||
p.config.ExecuteCommand = p.guestOSTypeConfig.executeCommand
|
||||
}
|
||||
|
||||
if p.config.InstallCommand == "" {
|
||||
p.config.InstallCommand = "curl -L https://www.chef.io/chef/install.sh | {{if .Sudo}}sudo {{end}}bash"
|
||||
p.config.InstallCommand = p.guestOSTypeConfig.installCommand
|
||||
}
|
||||
|
||||
if p.config.RunList == nil {
|
||||
|
@ -99,7 +138,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
|
|||
}
|
||||
|
||||
if p.config.StagingDir == "" {
|
||||
p.config.StagingDir = "/tmp/packer-chef-solo"
|
||||
p.config.StagingDir = p.guestOSTypeConfig.stagingDir
|
||||
}
|
||||
|
||||
var errs *packer.MultiError
|
||||
|
@ -374,16 +413,13 @@ func (p *Provisioner) createJson(ui packer.Ui, comm packer.Communicator) (string
|
|||
|
||||
func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
|
||||
ui.Message(fmt.Sprintf("Creating directory: %s", dir))
|
||||
cmd := &packer.RemoteCmd{
|
||||
Command: fmt.Sprintf("mkdir -p '%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.")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
package provisioner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const UnixOSType = "unix"
|
||||
const WindowsOSType = "windows"
|
||||
const DefaultOSType = UnixOSType
|
||||
|
||||
type guestOSTypeCommand struct {
|
||||
chmod string
|
||||
mkdir string
|
||||
removeDir string
|
||||
}
|
||||
|
||||
var guestOSTypeCommands = map[string]guestOSTypeCommand{
|
||||
UnixOSType: guestOSTypeCommand{
|
||||
chmod: "chmod %s '%s'",
|
||||
mkdir: "mkdir -p '%s'",
|
||||
removeDir: "rm -rf '%s'",
|
||||
},
|
||||
WindowsOSType: guestOSTypeCommand{
|
||||
chmod: "echo 'skipping chmod %s %s'", // no-op
|
||||
mkdir: "powershell.exe -Command \"New-Item -ItemType directory -Force -ErrorAction SilentlyContinue -Path %s\"",
|
||||
removeDir: "powershell.exe -Command \"rm %s -recurse -force\"",
|
||||
},
|
||||
}
|
||||
|
||||
type GuestCommands struct {
|
||||
GuestOSType string
|
||||
Sudo bool
|
||||
}
|
||||
|
||||
func NewGuestCommands(osType string, sudo bool) (*GuestCommands, error) {
|
||||
_, ok := guestOSTypeCommands[osType]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Invalid osType: \"%s\"", osType)
|
||||
}
|
||||
return &GuestCommands{GuestOSType: osType, Sudo: sudo}, nil
|
||||
}
|
||||
|
||||
func (g *GuestCommands) Chmod(path string, mode string) string {
|
||||
return g.sudo(fmt.Sprintf(g.commands().chmod, mode, g.escapePath(path)))
|
||||
}
|
||||
|
||||
func (g *GuestCommands) CreateDir(path string) string {
|
||||
return g.sudo(fmt.Sprintf(g.commands().mkdir, g.escapePath(path)))
|
||||
}
|
||||
|
||||
func (g *GuestCommands) RemoveDir(path string) string {
|
||||
return g.sudo(fmt.Sprintf(g.commands().removeDir, g.escapePath(path)))
|
||||
}
|
||||
|
||||
func (g *GuestCommands) commands() guestOSTypeCommand {
|
||||
return guestOSTypeCommands[g.GuestOSType]
|
||||
}
|
||||
|
||||
func (g *GuestCommands) escapePath(path string) string {
|
||||
if g.GuestOSType == WindowsOSType {
|
||||
return strings.Replace(path, " ", "` ", -1)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func (g *GuestCommands) sudo(cmd string) string {
|
||||
if g.GuestOSType == UnixOSType && g.Sudo {
|
||||
return "sudo " + cmd
|
||||
}
|
||||
return cmd
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
package provisioner
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewGuestCommands(t *testing.T) {
|
||||
_, err := NewGuestCommands("Amiga", true)
|
||||
if err == nil {
|
||||
t.Fatalf("Should have returned an err for unsupported OS type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateDir(t *testing.T) {
|
||||
// *nix OS
|
||||
guestCmd, err := NewGuestCommands(UnixOSType, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create new GuestCommands for OS: %s", UnixOSType)
|
||||
}
|
||||
cmd := guestCmd.CreateDir("/tmp/tempdir")
|
||||
if cmd != "mkdir -p '/tmp/tempdir'" {
|
||||
t.Fatalf("Unexpected Unix create dir cmd: %s", cmd)
|
||||
}
|
||||
|
||||
// *nix OS w/sudo
|
||||
guestCmd, err = NewGuestCommands(UnixOSType, true)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create new sudo GuestCommands for OS: %s", UnixOSType)
|
||||
}
|
||||
cmd = guestCmd.CreateDir("/tmp/tempdir")
|
||||
if cmd != "sudo mkdir -p '/tmp/tempdir'" {
|
||||
t.Fatalf("Unexpected Unix sudo create dir cmd: %s", cmd)
|
||||
}
|
||||
|
||||
// Windows OS
|
||||
guestCmd, err = NewGuestCommands(WindowsOSType, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create new GuestCommands for OS: %s", WindowsOSType)
|
||||
}
|
||||
cmd = guestCmd.CreateDir("C:\\Windows\\Temp\\tempdir")
|
||||
if cmd != "powershell.exe -Command \"New-Item -ItemType directory -Force -ErrorAction SilentlyContinue -Path C:\\Windows\\Temp\\tempdir\"" {
|
||||
t.Fatalf("Unexpected Windows create dir cmd: %s", cmd)
|
||||
}
|
||||
|
||||
// Windows OS w/ space in path
|
||||
cmd = guestCmd.CreateDir("C:\\Windows\\Temp\\temp dir")
|
||||
if cmd != "powershell.exe -Command \"New-Item -ItemType directory -Force -ErrorAction SilentlyContinue -Path C:\\Windows\\Temp\\temp` dir\"" {
|
||||
t.Fatalf("Unexpected Windows create dir cmd: %s", cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChmod(t *testing.T) {
|
||||
// *nix
|
||||
guestCmd, err := NewGuestCommands(UnixOSType, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create new GuestCommands for OS: %s", UnixOSType)
|
||||
}
|
||||
cmd := guestCmd.Chmod("/usr/local/bin/script.sh", "0666")
|
||||
if cmd != "chmod 0666 '/usr/local/bin/script.sh'" {
|
||||
t.Fatalf("Unexpected Unix chmod 0666 cmd: %s", cmd)
|
||||
}
|
||||
|
||||
// sudo *nix
|
||||
guestCmd, err = NewGuestCommands(UnixOSType, true)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create new sudo GuestCommands for OS: %s", UnixOSType)
|
||||
}
|
||||
cmd = guestCmd.Chmod("/usr/local/bin/script.sh", "+x")
|
||||
if cmd != "sudo chmod +x '/usr/local/bin/script.sh'" {
|
||||
t.Fatalf("Unexpected Unix chmod +x cmd: %s", cmd)
|
||||
}
|
||||
|
||||
// Windows
|
||||
guestCmd, err = NewGuestCommands(WindowsOSType, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create new GuestCommands for OS: %s", WindowsOSType)
|
||||
}
|
||||
cmd = guestCmd.Chmod("C:\\Program Files\\SomeApp\\someapp.exe", "+x")
|
||||
if cmd != "echo 'skipping chmod +x C:\\Program` Files\\SomeApp\\someapp.exe'" {
|
||||
t.Fatalf("Unexpected Windows chmod +x cmd: %s", cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveDir(t *testing.T) {
|
||||
// *nix
|
||||
guestCmd, err := NewGuestCommands(UnixOSType, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create new GuestCommands for OS: %s", UnixOSType)
|
||||
}
|
||||
cmd := guestCmd.RemoveDir("/tmp/somedir")
|
||||
if cmd != "rm -rf '/tmp/somedir'" {
|
||||
t.Fatalf("Unexpected Unix remove dir cmd: %s", cmd)
|
||||
}
|
||||
|
||||
// sudo *nix
|
||||
guestCmd, err = NewGuestCommands(UnixOSType, true)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create new sudo GuestCommands for OS: %s", UnixOSType)
|
||||
}
|
||||
cmd = guestCmd.RemoveDir("/tmp/somedir")
|
||||
if cmd != "sudo rm -rf '/tmp/somedir'" {
|
||||
t.Fatalf("Unexpected Unix sudo remove dir cmd: %s", cmd)
|
||||
}
|
||||
|
||||
// Windows OS
|
||||
guestCmd, err = NewGuestCommands(WindowsOSType, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create new GuestCommands for OS: %s", WindowsOSType)
|
||||
}
|
||||
cmd = guestCmd.RemoveDir("C:\\Temp\\SomeDir")
|
||||
if cmd != "powershell.exe -Command \"rm C:\\Temp\\SomeDir -recurse -force\"" {
|
||||
t.Fatalf("Unexpected Windows remove dir cmd: %s", cmd)
|
||||
}
|
||||
|
||||
// Windows OS w/ space in path
|
||||
cmd = guestCmd.RemoveDir("C:\\Temp\\Some Dir")
|
||||
if cmd != "powershell.exe -Command \"rm C:\\Temp\\Some` Dir -recurse -force\"" {
|
||||
t.Fatalf("Unexpected Windows remove dir cmd: %s", cmd)
|
||||
}
|
||||
}
|
|
@ -51,14 +51,18 @@ configuration is actually required.
|
|||
Configuration" section below for more details.
|
||||
|
||||
- `encrypted_data_bag_secret_path` (string) - The path to the file containing
|
||||
the secret for encrypted data bags. By default, this is empty, so no secret
|
||||
will be available.
|
||||
the secret for encrypted data bags. By default, this is empty, so no
|
||||
secret will be available.
|
||||
|
||||
- `execute_command` (string) - The command used to execute Chef. This has
|
||||
various [configuration template
|
||||
variables](/docs/templates/configuration-templates.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".
|
||||
|
||||
- `install_command` (string) - The command used to install Chef. This has
|
||||
various [configuration template
|
||||
variables](/docs/templates/configuration-templates.html) available. See
|
||||
|
@ -72,7 +76,8 @@ configuration is actually required.
|
|||
|
||||
- `prevent_sudo` (boolean) - By default, the configured commands that are
|
||||
executed to install and run Chef are executed with `sudo`. If this is true,
|
||||
then the sudo will be omitted.
|
||||
then the sudo will be omitted. This has no effect when guest_os_type is
|
||||
windows.
|
||||
|
||||
- `run_list` (array of strings) - The [run
|
||||
list](http://docs.chef.io/essentials_node_object_run_lists.html) for Chef.
|
||||
|
@ -91,11 +96,12 @@ configuration is actually required.
|
|||
on the machine using the Chef omnibus installers.
|
||||
|
||||
- `staging_directory` (string) - This is the directory where all the
|
||||
configuration of Chef by Packer will be placed. By default this
|
||||
is "/tmp/packer-chef-client". 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.
|
||||
configuration of Chef by Packer will be placed. By default this is
|
||||
"/tmp/packer-chef-client" when guest_os_type unix and
|
||||
"$env:TEMP/packer-chef-client" when windows. This directory doesn't need to
|
||||
exist but must have proper permissions so that the user that Packer uses is
|
||||
able to create directories and write into this folder. By default the
|
||||
provisioner will create and chmod 0777 this directory.
|
||||
|
||||
- `client_key` (string) - Path to client key. If not set, this defaults to a
|
||||
file named client.pem in `staging_directory`.
|
||||
|
@ -123,6 +129,10 @@ The default value for the configuration template is:
|
|||
log_level :info
|
||||
log_location STDOUT
|
||||
chef_server_url "{{.ServerUrl}}"
|
||||
client_key "{{.ClientKey}}"
|
||||
{{if ne .EncryptedDataBagSecretPath ""}}
|
||||
encrypted_data_bag_secret "{{.EncryptedDataBagSecretPath}}"
|
||||
{{end}}
|
||||
{{if ne .ValidationClientName ""}}
|
||||
validation_client_name "{{.ValidationClientName}}"
|
||||
{{else}}
|
||||
|
@ -131,8 +141,12 @@ validation_client_name "chef-validator"
|
|||
{{if ne .ValidationKeyPath ""}}
|
||||
validation_key "{{.ValidationKeyPath}}"
|
||||
{{end}}
|
||||
{{if ne .NodeName ""}}
|
||||
node_name "{{.NodeName}}"
|
||||
{{if ne .ChefEnvironment ""}}
|
||||
environment "{{.ChefEnvironment}}"
|
||||
{{end}}
|
||||
{{if ne .SslVerifyMode ""}}
|
||||
ssl_verify_mode :{{.SslVerifyMode}}
|
||||
{{end}}
|
||||
```
|
||||
|
||||
|
@ -140,9 +154,13 @@ This template is a [configuration
|
|||
template](/docs/templates/configuration-templates.html) and has a set of
|
||||
variables available to use:
|
||||
|
||||
- `EncryptedDataBagSecretPath` - The path to the encrypted data bag secret
|
||||
- `ChefEnvironment` - The Chef environment name.
|
||||
- `EncryptedDataBagSecretPath` - The path to the secret key file to decrypt
|
||||
encrypted data bags.
|
||||
- `NodeName` - The node name set in the configuration.
|
||||
- `ServerUrl` - The URL of the Chef Server set in the configuration.
|
||||
- `SslVerifyMode` - Whether Chef SSL verify mode is on or off.
|
||||
- `ValidationClientName` - The name of the client used for validation.
|
||||
- `ValidationKeyPath` - Path to the validation key, if it is set.
|
||||
|
||||
## Execute Command
|
||||
|
@ -157,6 +175,17 @@ readability) to execute Chef:
|
|||
-j {{.JsonPath}}
|
||||
```
|
||||
|
||||
When guest_os_type is set to "windows", Packer uses the following command to
|
||||
execute Chef. The full path to Chef is required because the PATH environment
|
||||
variable changes don't immediately propogate to running processes.
|
||||
|
||||
``` {.liquid}
|
||||
c:/opscode/chef/bin/chef-client.bat \
|
||||
--no-color \
|
||||
-c {{.ConfigPath}} \
|
||||
-j {{.JsonPath}}
|
||||
```
|
||||
|
||||
This command can be customized using the `execute_command` configuration. As you
|
||||
can see from the default value above, the value of this configuration can
|
||||
contain various template variables, defined below:
|
||||
|
@ -177,6 +206,13 @@ curl -L https://www.chef.io/chef/install.sh | \
|
|||
{{if .Sudo}}sudo{{end}} bash
|
||||
```
|
||||
|
||||
When guest_os_type is set to "windows", Packer uses the following command to
|
||||
install the latest version of Chef:
|
||||
|
||||
``` {.text}
|
||||
powershell.exe -Command "(New-Object System.Net.WebClient).DownloadFile('http://chef.io/chef/install.msi', 'C:\\Windows\\Temp\\chef.msi');Start-Process 'msiexec' -ArgumentList '/qb /i C:\\Windows\\Temp\\chef.msi' -NoNewWindow -Wait"
|
||||
```
|
||||
|
||||
This command can be customized using the `install_command` configuration.
|
||||
|
||||
## Folder Permissions
|
||||
|
|
|
@ -68,6 +68,10 @@ configuration is actually required, but at least `run_list` is recommended.
|
|||
variables](/docs/templates/configuration-templates.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".
|
||||
|
||||
- `install_command` (string) - The command used to install Chef. This has
|
||||
various [configuration template
|
||||
variables](/docs/templates/configuration-templates.html) available. See
|
||||
|
@ -78,7 +82,8 @@ configuration is actually required, but at least `run_list` is recommended.
|
|||
|
||||
- `prevent_sudo` (boolean) - By default, the configured commands that are
|
||||
executed to install and run Chef are executed with `sudo`. If this is true,
|
||||
then the sudo will be omitted.
|
||||
then the sudo will be omitted. This has no effect when guest_os_type is
|
||||
windows.
|
||||
|
||||
- `remote_cookbook_paths` (array of strings) - A list of paths on the remote
|
||||
machine where cookbooks will already exist. These may exist from a previous
|
||||
|
@ -97,11 +102,13 @@ configuration is actually required, but at least `run_list` is recommended.
|
|||
on the machine using the Chef omnibus installers.
|
||||
|
||||
- `staging_directory` (string) - This is the directory where all the
|
||||
configuration of Chef by Packer will be placed. By default this
|
||||
is "/tmp/packer-chef-solo". 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.
|
||||
configuration of Chef by Packer will be placed. By default this is
|
||||
"/tmp/packer-chef-solo" when guest_os_type unix and
|
||||
"$env:TEMP/packer-chef-solo" when windows. This directory doesn't need to
|
||||
exist but must have proper permissions so that the 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.
|
||||
|
||||
## Chef Configuration
|
||||
|
||||
|
@ -141,6 +148,17 @@ readability) to execute Chef:
|
|||
-j {{.JsonPath}}
|
||||
```
|
||||
|
||||
When guest_os_type is set to "windows", Packer uses the following command to
|
||||
execute Chef. The full path to Chef is required because the PATH environment
|
||||
variable changes don't immediately propogate to running processes.
|
||||
|
||||
``` {.liquid}
|
||||
c:/opscode/chef/bin/chef-solo.bat \
|
||||
--no-color \
|
||||
-c {{.ConfigPath}} \
|
||||
-j {{.JsonPath}}
|
||||
```
|
||||
|
||||
This command can be customized using the `execute_command` configuration. As you
|
||||
can see from the default value above, the value of this configuration can
|
||||
contain various template variables, defined below:
|
||||
|
@ -161,4 +179,11 @@ curl -L https://www.chef.io/chef/install.sh | \
|
|||
{{if .Sudo}}sudo{{end}} bash
|
||||
```
|
||||
|
||||
When guest_os_type is set to "windows", Packer uses the following command to
|
||||
install the latest version of Chef:
|
||||
|
||||
``` {.text}
|
||||
powershell.exe -Command "(New-Object System.Net.WebClient).DownloadFile('http://chef.io/chef/install.msi', 'C:\\Windows\\Temp\\chef.msi');Start-Process 'msiexec' -ArgumentList '/qb /i C:\\Windows\\Temp\\chef.msi' -NoNewWindow -Wait"
|
||||
```
|
||||
|
||||
This command can be customized using the `install_command` configuration.
|
||||
|
|
Loading…
Reference in New Issue