Remove Chef components and docs
This commit is contained in:
parent
2da9c21733
commit
30bcf44c2c
|
@ -50,8 +50,6 @@ import (
|
|||
yandeximportpostprocessor "github.com/hashicorp/packer/post-processor/yandex-import"
|
||||
azuredtlartifactprovisioner "github.com/hashicorp/packer/provisioner/azure-dtlartifact"
|
||||
breakpointprovisioner "github.com/hashicorp/packer/provisioner/breakpoint"
|
||||
chefclientprovisioner "github.com/hashicorp/packer/provisioner/chef-client"
|
||||
chefsoloprovisioner "github.com/hashicorp/packer/provisioner/chef-solo"
|
||||
convergeprovisioner "github.com/hashicorp/packer/provisioner/converge"
|
||||
fileprovisioner "github.com/hashicorp/packer/provisioner/file"
|
||||
inspecprovisioner "github.com/hashicorp/packer/provisioner/inspec"
|
||||
|
@ -100,8 +98,6 @@ var Builders = map[string]packersdk.Builder{
|
|||
var Provisioners = map[string]packersdk.Provisioner{
|
||||
"azure-dtlartifact": new(azuredtlartifactprovisioner.Provisioner),
|
||||
"breakpoint": new(breakpointprovisioner.Provisioner),
|
||||
"chef-client": new(chefclientprovisioner.Provisioner),
|
||||
"chef-solo": new(chefsoloprovisioner.Provisioner),
|
||||
"converge": new(convergeprovisioner.Provisioner),
|
||||
"file": new(fileprovisioner.Provisioner),
|
||||
"inspec": new(inspecprovisioner.Provisioner),
|
||||
|
|
|
@ -1,782 +0,0 @@
|
|||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||
|
||||
// This package implements a provisioner for Packer that uses
|
||||
// Chef to provision the remote machine, specifically with chef-client (that is,
|
||||
// with a Chef server).
|
||||
package chefclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/hashicorp/packer-plugin-sdk/common"
|
||||
"github.com/hashicorp/packer-plugin-sdk/guestexec"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer-plugin-sdk/pathing"
|
||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
||||
"github.com/hashicorp/packer-plugin-sdk/uuid"
|
||||
)
|
||||
|
||||
type guestOSTypeConfig struct {
|
||||
executeCommand string
|
||||
installCommand string
|
||||
knifeCommand string
|
||||
stagingDir string
|
||||
}
|
||||
|
||||
var guestOSTypeConfigs = map[string]guestOSTypeConfig{
|
||||
guestexec.UnixOSType: {
|
||||
executeCommand: "{{if .Sudo}}sudo {{end}}chef-client --no-color -c {{.ConfigPath}} -j {{.JsonPath}}",
|
||||
installCommand: "curl -L https://omnitruck.chef.io/install.sh | {{if .Sudo}}sudo {{end}}bash -s --{{if .Version}} -v {{.Version}}{{end}}",
|
||||
knifeCommand: "{{if .Sudo}}sudo {{end}}knife {{.Args}} {{.Flags}}",
|
||||
stagingDir: "/tmp/packer-chef-client",
|
||||
},
|
||||
guestexec.WindowsOSType: {
|
||||
executeCommand: "c:/opscode/chef/bin/chef-client.bat --no-color -c {{.ConfigPath}} -j {{.JsonPath}}",
|
||||
installCommand: "powershell.exe -Command \". { iwr -useb https://omnitruck.chef.io/install.ps1 } | iex; Install-Project{{if .Version}} -version {{.Version}}{{end}}\"",
|
||||
knifeCommand: "c:/opscode/chef/bin/knife.bat {{.Args}} {{.Flags}}",
|
||||
stagingDir: "C:/Windows/Temp/packer-chef-client",
|
||||
},
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
Json map[string]interface{}
|
||||
|
||||
ChefEnvironment string `mapstructure:"chef_environment"`
|
||||
ChefLicense string `mapstructure:"chef_license"`
|
||||
ClientKey string `mapstructure:"client_key"`
|
||||
ConfigTemplate string `mapstructure:"config_template"`
|
||||
ElevatedUser string `mapstructure:"elevated_user"`
|
||||
ElevatedPassword string `mapstructure:"elevated_password"`
|
||||
EncryptedDataBagSecretPath string `mapstructure:"encrypted_data_bag_secret_path"`
|
||||
ExecuteCommand string `mapstructure:"execute_command"`
|
||||
GuestOSType string `mapstructure:"guest_os_type"`
|
||||
InstallCommand string `mapstructure:"install_command"`
|
||||
KnifeCommand string `mapstructure:"knife_command"`
|
||||
NodeName string `mapstructure:"node_name"`
|
||||
PolicyGroup string `mapstructure:"policy_group"`
|
||||
PolicyName string `mapstructure:"policy_name"`
|
||||
PreventSudo bool `mapstructure:"prevent_sudo"`
|
||||
RunList []string `mapstructure:"run_list"`
|
||||
ServerUrl string `mapstructure:"server_url"`
|
||||
SkipCleanClient bool `mapstructure:"skip_clean_client"`
|
||||
SkipCleanNode bool `mapstructure:"skip_clean_node"`
|
||||
SkipCleanStagingDirectory bool `mapstructure:"skip_clean_staging_directory"`
|
||||
SkipInstall bool `mapstructure:"skip_install"`
|
||||
SslVerifyMode string `mapstructure:"ssl_verify_mode"`
|
||||
TrustedCertsDir string `mapstructure:"trusted_certs_dir"`
|
||||
StagingDir string `mapstructure:"staging_directory"`
|
||||
ValidationClientName string `mapstructure:"validation_client_name"`
|
||||
ValidationKeyPath string `mapstructure:"validation_key_path"`
|
||||
Version string `mapstructure:"version"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
type Provisioner struct {
|
||||
config Config
|
||||
communicator packersdk.Communicator
|
||||
guestOSTypeConfig guestOSTypeConfig
|
||||
guestCommands *guestexec.GuestCommands
|
||||
generatedData map[string]interface{}
|
||||
}
|
||||
|
||||
type ConfigTemplate struct {
|
||||
ChefEnvironment string
|
||||
ChefLicense string
|
||||
ClientKey string
|
||||
EncryptedDataBagSecretPath string
|
||||
NodeName string
|
||||
PolicyGroup string
|
||||
PolicyName string
|
||||
ServerUrl string
|
||||
SslVerifyMode string
|
||||
TrustedCertsDir string
|
||||
ValidationClientName string
|
||||
ValidationKeyPath string
|
||||
}
|
||||
|
||||
type ExecuteTemplate struct {
|
||||
ConfigPath string
|
||||
JsonPath string
|
||||
Sudo bool
|
||||
}
|
||||
|
||||
type InstallChefTemplate struct {
|
||||
Sudo bool
|
||||
Version string
|
||||
}
|
||||
|
||||
type KnifeTemplate struct {
|
||||
Sudo bool
|
||||
Flags string
|
||||
Args string
|
||||
}
|
||||
|
||||
func (p *Provisioner) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() }
|
||||
|
||||
func (p *Provisioner) Prepare(raws ...interface{}) error {
|
||||
err := config.Decode(&p.config, &config.DecodeOpts{
|
||||
PluginType: "chef-client",
|
||||
Interpolate: true,
|
||||
InterpolateContext: &p.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{
|
||||
"execute_command",
|
||||
"install_command",
|
||||
"knife_command",
|
||||
},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.config.GuestOSType == "" {
|
||||
p.config.GuestOSType = guestexec.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 = guestexec.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.guestOSTypeConfig.executeCommand
|
||||
}
|
||||
|
||||
if p.config.InstallCommand == "" {
|
||||
p.config.InstallCommand = p.guestOSTypeConfig.installCommand
|
||||
}
|
||||
|
||||
if p.config.RunList == nil {
|
||||
p.config.RunList = make([]string, 0)
|
||||
}
|
||||
|
||||
if p.config.StagingDir == "" {
|
||||
p.config.StagingDir = p.guestOSTypeConfig.stagingDir
|
||||
}
|
||||
|
||||
if p.config.KnifeCommand == "" {
|
||||
p.config.KnifeCommand = p.guestOSTypeConfig.knifeCommand
|
||||
}
|
||||
|
||||
var errs *packersdk.MultiError
|
||||
if p.config.ConfigTemplate != "" {
|
||||
fi, err := os.Stat(p.config.ConfigTemplate)
|
||||
if err != nil {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Bad config template path: %s", err))
|
||||
} else if fi.IsDir() {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Config template path must be a file: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.ServerUrl == "" {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, fmt.Errorf("server_url must be set"))
|
||||
}
|
||||
|
||||
if p.config.SkipInstall == false && p.config.InstallCommand == p.guestOSTypeConfig.installCommand {
|
||||
if p.config.ChefLicense == "" {
|
||||
p.config.ChefLicense = "accept-silent"
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.EncryptedDataBagSecretPath != "" {
|
||||
pFileInfo, err := os.Stat(p.config.EncryptedDataBagSecretPath)
|
||||
|
||||
if err != nil || pFileInfo.IsDir() {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Bad encrypted data bag secret '%s': %s", p.config.EncryptedDataBagSecretPath, err))
|
||||
}
|
||||
}
|
||||
|
||||
if (p.config.PolicyName != "") != (p.config.PolicyGroup != "") {
|
||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("If either policy_name or policy_group are set, they must both be set."))
|
||||
}
|
||||
|
||||
jsonValid := true
|
||||
for k, v := range p.config.Json {
|
||||
p.config.Json[k], err = p.deepJsonFix(k, v)
|
||||
if err != nil {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Error processing JSON: %s", err))
|
||||
jsonValid = false
|
||||
}
|
||||
}
|
||||
|
||||
if jsonValid {
|
||||
// Process the user variables within the JSON and set the JSON.
|
||||
// Do this early so that we can validate and show errors.
|
||||
p.config.Json, err = p.processJsonUserVars()
|
||||
if err != nil {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Error processing user variables in JSON: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) Provision(ctx context.Context, ui packersdk.Ui, comm packersdk.Communicator, generatedData map[string]interface{}) error {
|
||||
p.generatedData = generatedData
|
||||
p.communicator = comm
|
||||
|
||||
nodeName := p.config.NodeName
|
||||
if nodeName == "" {
|
||||
nodeName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
||||
}
|
||||
remoteValidationKeyPath := ""
|
||||
serverUrl := p.config.ServerUrl
|
||||
|
||||
if !p.config.SkipInstall {
|
||||
if err := p.installChef(ui, comm, p.config.Version); err != nil {
|
||||
return fmt.Errorf("Error installing Chef: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
|
||||
return fmt.Errorf("Error creating staging directory: %s", err)
|
||||
}
|
||||
|
||||
if p.config.ClientKey == "" {
|
||||
p.config.ClientKey = fmt.Sprintf("%s/client.pem", p.config.StagingDir)
|
||||
}
|
||||
|
||||
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 {
|
||||
return fmt.Errorf("Error uploading encrypted data bag secret: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.ValidationKeyPath != "" {
|
||||
path, err := pathing.ExpandUser(p.config.ValidationKeyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error while expanding a tilde in the validation key: %s", err)
|
||||
}
|
||||
remoteValidationKeyPath = fmt.Sprintf("%s/validation.pem", p.config.StagingDir)
|
||||
if err := p.uploadFile(ui, comm, remoteValidationKeyPath, path); err != nil {
|
||||
return fmt.Errorf("Error copying validation key: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
configPath, err := p.createConfig(
|
||||
ui,
|
||||
comm,
|
||||
nodeName,
|
||||
serverUrl,
|
||||
p.config.ClientKey,
|
||||
p.config.ChefLicense,
|
||||
encryptedDataBagSecretPath,
|
||||
remoteValidationKeyPath,
|
||||
p.config.ValidationClientName,
|
||||
p.config.ChefEnvironment,
|
||||
p.config.PolicyGroup,
|
||||
p.config.PolicyName,
|
||||
p.config.SslVerifyMode,
|
||||
p.config.TrustedCertsDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating Chef config file: %s", err)
|
||||
}
|
||||
|
||||
jsonPath, err := p.createJson(ui, comm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating JSON attributes: %s", err)
|
||||
}
|
||||
|
||||
err = p.executeChef(ui, comm, configPath, jsonPath)
|
||||
|
||||
if !(p.config.SkipCleanNode && p.config.SkipCleanClient) {
|
||||
|
||||
knifeConfigPath, knifeErr := p.createKnifeConfig(
|
||||
ui, comm, nodeName, serverUrl, p.config.ClientKey, p.config.SslVerifyMode, p.config.TrustedCertsDir)
|
||||
|
||||
if knifeErr != nil {
|
||||
return fmt.Errorf("Error creating knife config on node: %s", knifeErr)
|
||||
}
|
||||
|
||||
if !p.config.SkipCleanNode {
|
||||
if err := p.cleanNode(ui, comm, nodeName, knifeConfigPath); err != nil {
|
||||
return fmt.Errorf("Error cleaning up chef node: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if !p.config.SkipCleanClient {
|
||||
if err := p.cleanClient(ui, comm, nodeName, knifeConfigPath); err != nil {
|
||||
return fmt.Errorf("Error cleaning up chef client: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error executing Chef: %s", err)
|
||||
}
|
||||
|
||||
if !p.config.SkipCleanStagingDirectory {
|
||||
if err := p.removeDir(ui, comm, p.config.StagingDir); err != nil {
|
||||
return fmt.Errorf("Error removing %s: %s", p.config.StagingDir, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) uploadFile(ui packersdk.Ui, comm packersdk.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(remotePath, f, nil)
|
||||
}
|
||||
|
||||
func (p *Provisioner) createConfig(
|
||||
ui packersdk.Ui,
|
||||
comm packersdk.Communicator,
|
||||
nodeName string,
|
||||
serverUrl string,
|
||||
clientKey string,
|
||||
chefLicense string,
|
||||
encryptedDataBagSecretPath,
|
||||
remoteKeyPath string,
|
||||
validationClientName string,
|
||||
chefEnvironment string,
|
||||
policyGroup string,
|
||||
policyName string,
|
||||
sslVerifyMode string,
|
||||
trustedCertsDir string) (string, error) {
|
||||
|
||||
ui.Message("Creating configuration file 'client.rb'")
|
||||
|
||||
// Read the template
|
||||
tpl := DefaultConfigTemplate
|
||||
if p.config.ConfigTemplate != "" {
|
||||
f, err := os.Open(p.config.ConfigTemplate)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
tplBytes, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tpl = string(tplBytes)
|
||||
}
|
||||
|
||||
ictx := p.config.ctx
|
||||
ictx.Data = &ConfigTemplate{
|
||||
NodeName: nodeName,
|
||||
ServerUrl: serverUrl,
|
||||
ClientKey: clientKey,
|
||||
ChefLicense: chefLicense,
|
||||
ValidationKeyPath: remoteKeyPath,
|
||||
ValidationClientName: validationClientName,
|
||||
ChefEnvironment: chefEnvironment,
|
||||
PolicyGroup: policyGroup,
|
||||
PolicyName: policyName,
|
||||
SslVerifyMode: sslVerifyMode,
|
||||
TrustedCertsDir: trustedCertsDir,
|
||||
EncryptedDataBagSecretPath: encryptedDataBagSecretPath,
|
||||
}
|
||||
configString, err := interpolate.Render(tpl, &ictx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
remotePath := filepath.ToSlash(filepath.Join(p.config.StagingDir, "client.rb"))
|
||||
if err := comm.Upload(remotePath, bytes.NewReader([]byte(configString)), nil); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return remotePath, nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) createKnifeConfig(ui packersdk.Ui, comm packersdk.Communicator, nodeName string, serverUrl string, clientKey string, sslVerifyMode string, trustedCertsDir string) (string, error) {
|
||||
ui.Message("Creating configuration file 'knife.rb'")
|
||||
|
||||
// Read the template
|
||||
tpl := DefaultKnifeTemplate
|
||||
|
||||
ictx := p.config.ctx
|
||||
ictx.Data = &ConfigTemplate{
|
||||
NodeName: nodeName,
|
||||
ServerUrl: serverUrl,
|
||||
ClientKey: clientKey,
|
||||
SslVerifyMode: sslVerifyMode,
|
||||
TrustedCertsDir: trustedCertsDir,
|
||||
}
|
||||
configString, err := interpolate.Render(tpl, &ictx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
remotePath := filepath.ToSlash(filepath.Join(p.config.StagingDir, "knife.rb"))
|
||||
if err := comm.Upload(remotePath, bytes.NewReader([]byte(configString)), nil); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return remotePath, nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) createJson(ui packersdk.Ui, comm packersdk.Communicator) (string, error) {
|
||||
ui.Message("Creating JSON attribute file")
|
||||
|
||||
jsonData := make(map[string]interface{})
|
||||
|
||||
// Copy the configured JSON
|
||||
for k, v := range p.config.Json {
|
||||
jsonData[k] = v
|
||||
}
|
||||
|
||||
// Set the run list if it was specified
|
||||
if len(p.config.RunList) > 0 {
|
||||
jsonData["run_list"] = p.config.RunList
|
||||
}
|
||||
|
||||
jsonBytes, err := json.MarshalIndent(jsonData, "", " ")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Upload the bytes
|
||||
remotePath := filepath.ToSlash(filepath.Join(p.config.StagingDir, "first-boot.json"))
|
||||
if err := comm.Upload(remotePath, bytes.NewReader(jsonBytes), nil); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return remotePath, nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) createDir(ui packersdk.Ui, comm packersdk.Communicator, dir string) error {
|
||||
ctx := context.TODO()
|
||||
ui.Message(fmt.Sprintf("Creating directory: %s", dir))
|
||||
|
||||
cmd := &packersdk.RemoteCmd{Command: p.guestCommands.CreateDir(dir)}
|
||||
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
|
||||
return err
|
||||
}
|
||||
if cmd.ExitStatus() != 0 {
|
||||
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 = &packersdk.RemoteCmd{Command: p.guestCommands.Chmod(dir, "0777")}
|
||||
if err := cmd.RunWithUi(ctx, 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
|
||||
}
|
||||
|
||||
func (p *Provisioner) cleanNode(ui packersdk.Ui, comm packersdk.Communicator, node string, knifeConfigPath string) error {
|
||||
ui.Say("Cleaning up chef node...")
|
||||
args := []string{"node", "delete", node}
|
||||
if err := p.knifeExec(ui, comm, node, knifeConfigPath, args); err != nil {
|
||||
return fmt.Errorf("Failed to cleanup node: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) cleanClient(ui packersdk.Ui, comm packersdk.Communicator, node string, knifeConfigPath string) error {
|
||||
ui.Say("Cleaning up chef client...")
|
||||
args := []string{"client", "delete", node}
|
||||
if err := p.knifeExec(ui, comm, node, knifeConfigPath, args); err != nil {
|
||||
return fmt.Errorf("Failed to cleanup client: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) knifeExec(ui packersdk.Ui, comm packersdk.Communicator, node string, knifeConfigPath string, args []string) error {
|
||||
flags := []string{
|
||||
"-y",
|
||||
"-c", knifeConfigPath,
|
||||
}
|
||||
ctx := context.TODO()
|
||||
|
||||
p.config.ctx.Data = &KnifeTemplate{
|
||||
Sudo: !p.config.PreventSudo,
|
||||
Flags: strings.Join(flags, " "),
|
||||
Args: strings.Join(args, " "),
|
||||
}
|
||||
|
||||
command, err := interpolate.Render(p.config.KnifeCommand, &p.config.ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := &packersdk.RemoteCmd{Command: command}
|
||||
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
|
||||
return err
|
||||
}
|
||||
if cmd.ExitStatus() != 0 {
|
||||
return fmt.Errorf(
|
||||
"Non-zero exit status. See output above for more info.\n\n"+
|
||||
"Command: %s",
|
||||
command)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) removeDir(ui packersdk.Ui, comm packersdk.Communicator, dir string) error {
|
||||
ui.Message(fmt.Sprintf("Removing directory: %s", dir))
|
||||
ctx := context.TODO()
|
||||
|
||||
cmd := &packersdk.RemoteCmd{Command: p.guestCommands.RemoveDir(dir)}
|
||||
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) executeChef(ui packersdk.Ui, comm packersdk.Communicator, config string, json string) error {
|
||||
p.config.ctx.Data = &ExecuteTemplate{
|
||||
ConfigPath: config,
|
||||
JsonPath: json,
|
||||
Sudo: !p.config.PreventSudo,
|
||||
}
|
||||
ctx := context.TODO()
|
||||
|
||||
command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.config.ElevatedUser != "" {
|
||||
command, err = guestexec.GenerateElevatedRunner(command, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Executing Chef: %s", command))
|
||||
|
||||
cmd := &packersdk.RemoteCmd{
|
||||
Command: command,
|
||||
}
|
||||
|
||||
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cmd.ExitStatus() != 0 {
|
||||
return fmt.Errorf("Non-zero exit status: %d", cmd.ExitStatus())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) installChef(ui packersdk.Ui, comm packersdk.Communicator, version string) error {
|
||||
ui.Message("Installing Chef...")
|
||||
ctx := context.TODO()
|
||||
|
||||
p.config.ctx.Data = &InstallChefTemplate{
|
||||
Sudo: !p.config.PreventSudo,
|
||||
Version: version,
|
||||
}
|
||||
command, err := interpolate.Render(p.config.InstallCommand, &p.config.ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ui.Message(command)
|
||||
|
||||
cmd := &packersdk.RemoteCmd{Command: command}
|
||||
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cmd.ExitStatus() != 0 {
|
||||
return fmt.Errorf(
|
||||
"Install script exited with non-zero exit status %d", cmd.ExitStatus())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) deepJsonFix(key string, current interface{}) (interface{}, error) {
|
||||
if current == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch c := current.(type) {
|
||||
case []interface{}:
|
||||
val := make([]interface{}, len(c))
|
||||
for i, v := range c {
|
||||
var err error
|
||||
val[i], err = p.deepJsonFix(fmt.Sprintf("%s[%d]", key, i), v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return val, nil
|
||||
case []uint8:
|
||||
return string(c), nil
|
||||
case map[interface{}]interface{}:
|
||||
val := make(map[string]interface{})
|
||||
for k, v := range c {
|
||||
ks, ok := k.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s: key is not string", key)
|
||||
}
|
||||
|
||||
var err error
|
||||
val[ks], err = p.deepJsonFix(
|
||||
fmt.Sprintf("%s.%s", key, ks), v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return val, nil
|
||||
default:
|
||||
return current, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provisioner) processJsonUserVars() (map[string]interface{}, error) {
|
||||
jsonBytes, err := json.Marshal(p.config.Json)
|
||||
if err != nil {
|
||||
// This really shouldn't happen since we literally just unmarshalled
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Copy the user variables so that we can restore them later, and
|
||||
// make sure we make the quotes JSON-friendly in the user variables.
|
||||
originalUserVars := make(map[string]string)
|
||||
for k, v := range p.config.ctx.UserVariables {
|
||||
originalUserVars[k] = v
|
||||
}
|
||||
|
||||
// Make sure we reset them no matter what
|
||||
defer func() {
|
||||
p.config.ctx.UserVariables = originalUserVars
|
||||
}()
|
||||
|
||||
// Make the current user variables JSON string safe.
|
||||
for k, v := range p.config.ctx.UserVariables {
|
||||
v = strings.Replace(v, `\`, `\\`, -1)
|
||||
v = strings.Replace(v, `"`, `\"`, -1)
|
||||
p.config.ctx.UserVariables[k] = v
|
||||
}
|
||||
|
||||
// Process the bytes with the template processor
|
||||
p.config.ctx.Data = nil
|
||||
jsonBytesProcessed, err := interpolate.Render(string(jsonBytes), &p.config.ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(jsonBytesProcessed), &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) Communicator() packersdk.Communicator {
|
||||
return p.communicator
|
||||
}
|
||||
|
||||
func (p *Provisioner) ElevatedUser() string {
|
||||
return p.config.ElevatedUser
|
||||
}
|
||||
|
||||
func (p *Provisioner) ElevatedPassword() string {
|
||||
// Replace ElevatedPassword for winrm users who used this feature
|
||||
p.config.ctx.Data = p.generatedData
|
||||
|
||||
elevatedPassword, _ := interpolate.Render(p.config.ElevatedPassword, &p.config.ctx)
|
||||
|
||||
return elevatedPassword
|
||||
}
|
||||
|
||||
var DefaultConfigTemplate = `
|
||||
log_level :info
|
||||
log_location STDOUT
|
||||
chef_server_url "{{.ServerUrl}}"
|
||||
client_key "{{.ClientKey}}"
|
||||
chef_license "{{.ChefLicense}}"
|
||||
{{if ne .EncryptedDataBagSecretPath ""}}
|
||||
encrypted_data_bag_secret "{{.EncryptedDataBagSecretPath}}"
|
||||
{{end}}
|
||||
{{if ne .ValidationClientName ""}}
|
||||
validation_client_name "{{.ValidationClientName}}"
|
||||
{{else}}
|
||||
validation_client_name "chef-validator"
|
||||
{{end}}
|
||||
{{if ne .ValidationKeyPath ""}}
|
||||
validation_key "{{.ValidationKeyPath}}"
|
||||
{{end}}
|
||||
node_name "{{.NodeName}}"
|
||||
{{if ne .ChefEnvironment ""}}
|
||||
environment "{{.ChefEnvironment}}"
|
||||
{{end}}
|
||||
{{if ne .PolicyGroup ""}}
|
||||
policy_group "{{.PolicyGroup}}"
|
||||
{{end}}
|
||||
{{if ne .PolicyName ""}}
|
||||
policy_name "{{.PolicyName}}"
|
||||
{{end}}
|
||||
{{if ne .SslVerifyMode ""}}
|
||||
ssl_verify_mode :{{.SslVerifyMode}}
|
||||
{{end}}
|
||||
{{if ne .TrustedCertsDir ""}}
|
||||
trusted_certs_dir "{{.TrustedCertsDir}}"
|
||||
{{end}}
|
||||
`
|
||||
|
||||
var DefaultKnifeTemplate = `
|
||||
log_level :info
|
||||
log_location STDOUT
|
||||
chef_server_url "{{.ServerUrl}}"
|
||||
client_key "{{.ClientKey}}"
|
||||
node_name "{{.NodeName}}"
|
||||
{{if ne .SslVerifyMode ""}}
|
||||
ssl_verify_mode :{{.SslVerifyMode}}
|
||||
{{end}}
|
||||
{{if ne .TrustedCertsDir ""}}
|
||||
trusted_certs_dir "{{.TrustedCertsDir}}"
|
||||
{{end}}
|
||||
`
|
|
@ -1,101 +0,0 @@
|
|||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
|
||||
package chefclient
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// FlatConfig is an auto-generated flat version of Config.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatConfig struct {
|
||||
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
|
||||
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
|
||||
PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
|
||||
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
|
||||
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
|
||||
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
|
||||
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
|
||||
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
|
||||
Json map[string]interface{} `cty:"json" hcl:"json"`
|
||||
ChefEnvironment *string `mapstructure:"chef_environment" cty:"chef_environment" hcl:"chef_environment"`
|
||||
ChefLicense *string `mapstructure:"chef_license" cty:"chef_license" hcl:"chef_license"`
|
||||
ClientKey *string `mapstructure:"client_key" cty:"client_key" hcl:"client_key"`
|
||||
ConfigTemplate *string `mapstructure:"config_template" cty:"config_template" hcl:"config_template"`
|
||||
ElevatedUser *string `mapstructure:"elevated_user" cty:"elevated_user" hcl:"elevated_user"`
|
||||
ElevatedPassword *string `mapstructure:"elevated_password" cty:"elevated_password" hcl:"elevated_password"`
|
||||
EncryptedDataBagSecretPath *string `mapstructure:"encrypted_data_bag_secret_path" cty:"encrypted_data_bag_secret_path" hcl:"encrypted_data_bag_secret_path"`
|
||||
ExecuteCommand *string `mapstructure:"execute_command" cty:"execute_command" hcl:"execute_command"`
|
||||
GuestOSType *string `mapstructure:"guest_os_type" cty:"guest_os_type" hcl:"guest_os_type"`
|
||||
InstallCommand *string `mapstructure:"install_command" cty:"install_command" hcl:"install_command"`
|
||||
KnifeCommand *string `mapstructure:"knife_command" cty:"knife_command" hcl:"knife_command"`
|
||||
NodeName *string `mapstructure:"node_name" cty:"node_name" hcl:"node_name"`
|
||||
PolicyGroup *string `mapstructure:"policy_group" cty:"policy_group" hcl:"policy_group"`
|
||||
PolicyName *string `mapstructure:"policy_name" cty:"policy_name" hcl:"policy_name"`
|
||||
PreventSudo *bool `mapstructure:"prevent_sudo" cty:"prevent_sudo" hcl:"prevent_sudo"`
|
||||
RunList []string `mapstructure:"run_list" cty:"run_list" hcl:"run_list"`
|
||||
ServerUrl *string `mapstructure:"server_url" cty:"server_url" hcl:"server_url"`
|
||||
SkipCleanClient *bool `mapstructure:"skip_clean_client" cty:"skip_clean_client" hcl:"skip_clean_client"`
|
||||
SkipCleanNode *bool `mapstructure:"skip_clean_node" cty:"skip_clean_node" hcl:"skip_clean_node"`
|
||||
SkipCleanStagingDirectory *bool `mapstructure:"skip_clean_staging_directory" cty:"skip_clean_staging_directory" hcl:"skip_clean_staging_directory"`
|
||||
SkipInstall *bool `mapstructure:"skip_install" cty:"skip_install" hcl:"skip_install"`
|
||||
SslVerifyMode *string `mapstructure:"ssl_verify_mode" cty:"ssl_verify_mode" hcl:"ssl_verify_mode"`
|
||||
TrustedCertsDir *string `mapstructure:"trusted_certs_dir" cty:"trusted_certs_dir" hcl:"trusted_certs_dir"`
|
||||
StagingDir *string `mapstructure:"staging_directory" cty:"staging_directory" hcl:"staging_directory"`
|
||||
ValidationClientName *string `mapstructure:"validation_client_name" cty:"validation_client_name" hcl:"validation_client_name"`
|
||||
ValidationKeyPath *string `mapstructure:"validation_key_path" cty:"validation_key_path" hcl:"validation_key_path"`
|
||||
Version *string `mapstructure:"version" cty:"version" hcl:"version"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatConfig.
|
||||
// FlatConfig is an auto-generated flat version of Config.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatConfig)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a Config.
|
||||
// This spec is used by HCL to read the fields of Config.
|
||||
// The decoded values from this spec will then be applied to a FlatConfig.
|
||||
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
|
||||
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
|
||||
"packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false},
|
||||
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
|
||||
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
|
||||
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
|
||||
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
|
||||
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
|
||||
"json": &hcldec.AttrSpec{Name: "json", Type: cty.Map(cty.String), Required: false},
|
||||
"chef_environment": &hcldec.AttrSpec{Name: "chef_environment", Type: cty.String, Required: false},
|
||||
"chef_license": &hcldec.AttrSpec{Name: "chef_license", Type: cty.String, Required: false},
|
||||
"client_key": &hcldec.AttrSpec{Name: "client_key", Type: cty.String, Required: false},
|
||||
"config_template": &hcldec.AttrSpec{Name: "config_template", Type: cty.String, Required: false},
|
||||
"elevated_user": &hcldec.AttrSpec{Name: "elevated_user", Type: cty.String, Required: false},
|
||||
"elevated_password": &hcldec.AttrSpec{Name: "elevated_password", Type: cty.String, Required: false},
|
||||
"encrypted_data_bag_secret_path": &hcldec.AttrSpec{Name: "encrypted_data_bag_secret_path", Type: cty.String, Required: false},
|
||||
"execute_command": &hcldec.AttrSpec{Name: "execute_command", Type: cty.String, Required: false},
|
||||
"guest_os_type": &hcldec.AttrSpec{Name: "guest_os_type", Type: cty.String, Required: false},
|
||||
"install_command": &hcldec.AttrSpec{Name: "install_command", Type: cty.String, Required: false},
|
||||
"knife_command": &hcldec.AttrSpec{Name: "knife_command", Type: cty.String, Required: false},
|
||||
"node_name": &hcldec.AttrSpec{Name: "node_name", Type: cty.String, Required: false},
|
||||
"policy_group": &hcldec.AttrSpec{Name: "policy_group", Type: cty.String, Required: false},
|
||||
"policy_name": &hcldec.AttrSpec{Name: "policy_name", Type: cty.String, Required: false},
|
||||
"prevent_sudo": &hcldec.AttrSpec{Name: "prevent_sudo", Type: cty.Bool, Required: false},
|
||||
"run_list": &hcldec.AttrSpec{Name: "run_list", Type: cty.List(cty.String), Required: false},
|
||||
"server_url": &hcldec.AttrSpec{Name: "server_url", Type: cty.String, Required: false},
|
||||
"skip_clean_client": &hcldec.AttrSpec{Name: "skip_clean_client", Type: cty.Bool, Required: false},
|
||||
"skip_clean_node": &hcldec.AttrSpec{Name: "skip_clean_node", Type: cty.Bool, Required: false},
|
||||
"skip_clean_staging_directory": &hcldec.AttrSpec{Name: "skip_clean_staging_directory", Type: cty.Bool, Required: false},
|
||||
"skip_install": &hcldec.AttrSpec{Name: "skip_install", Type: cty.Bool, Required: false},
|
||||
"ssl_verify_mode": &hcldec.AttrSpec{Name: "ssl_verify_mode", Type: cty.String, Required: false},
|
||||
"trusted_certs_dir": &hcldec.AttrSpec{Name: "trusted_certs_dir", Type: cty.String, Required: false},
|
||||
"staging_directory": &hcldec.AttrSpec{Name: "staging_directory", Type: cty.String, Required: false},
|
||||
"validation_client_name": &hcldec.AttrSpec{Name: "validation_client_name", Type: cty.String, Required: false},
|
||||
"validation_key_path": &hcldec.AttrSpec{Name: "validation_key_path", Type: cty.String, Required: false},
|
||||
"version": &hcldec.AttrSpec{Name: "version", Type: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
|
@ -1,322 +0,0 @@
|
|||
package chefclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"server_url": "foo",
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisioner_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Provisioner{}
|
||||
if _, ok := raw.(packersdk.Provisioner); !ok {
|
||||
t.Fatalf("must be a Provisioner")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_chefEnvironment(t *testing.T) {
|
||||
var p Provisioner
|
||||
|
||||
config := testConfig()
|
||||
config["chef_environment"] = "some-env"
|
||||
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if p.config.ChefEnvironment != "some-env" {
|
||||
t.Fatalf("unexpected: %#v", p.config.ChefEnvironment)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_configTemplate(t *testing.T) {
|
||||
var err error
|
||||
var p Provisioner
|
||||
|
||||
// Test no config template
|
||||
config := testConfig()
|
||||
delete(config, "config_template")
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Test with a file
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
config = testConfig()
|
||||
config["config_template"] = tf.Name()
|
||||
p = Provisioner{}
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Test with a directory
|
||||
td, err := ioutil.TempDir("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(td)
|
||||
|
||||
config = testConfig()
|
||||
config["config_template"] = td
|
||||
p = Provisioner{}
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have err")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_commands(t *testing.T) {
|
||||
commands := []string{
|
||||
"execute_command",
|
||||
"install_command",
|
||||
"knife_command",
|
||||
}
|
||||
|
||||
for _, command := range commands {
|
||||
var p Provisioner
|
||||
|
||||
// Test not set
|
||||
config := testConfig()
|
||||
delete(config, command)
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Test invalid template
|
||||
config = testConfig()
|
||||
config[command] = "{{if NOPE}}"
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
|
||||
// Test good template
|
||||
config = testConfig()
|
||||
config[command] = "{{.Foo}}"
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_serverUrl(t *testing.T) {
|
||||
var p Provisioner
|
||||
|
||||
// Test not set
|
||||
config := testConfig()
|
||||
delete(config, "server_url")
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
|
||||
// Test set
|
||||
config = testConfig()
|
||||
config["server_url"] = "foo"
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
func TestProvisionerPrepare_chefLicense(t *testing.T) {
|
||||
var p Provisioner
|
||||
|
||||
// Test not set
|
||||
config := testConfig()
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
|
||||
if p.config.ChefLicense != "accept-silent" {
|
||||
t.Fatalf("unexpected: %#v", p.config.ChefLicense)
|
||||
}
|
||||
|
||||
// Test set
|
||||
config = testConfig()
|
||||
config["chef_license"] = "accept"
|
||||
p = Provisioner{}
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if p.config.ChefLicense != "accept" {
|
||||
t.Fatalf("unexpected: %#v", p.config.ChefLicense)
|
||||
}
|
||||
|
||||
// Test set skipInstall true
|
||||
config = testConfig()
|
||||
config["skip_install"] = true
|
||||
p = Provisioner{}
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if p.config.ChefLicense != "" {
|
||||
t.Fatalf("unexpected: %#v", "empty string")
|
||||
}
|
||||
|
||||
// Test set installCommand true
|
||||
config = testConfig()
|
||||
config["install_command"] = "install chef"
|
||||
p = Provisioner{}
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if p.config.ChefLicense != "" {
|
||||
t.Fatalf("unexpected: %#v", "empty string")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_encryptedDataBagSecretPath(t *testing.T) {
|
||||
var err error
|
||||
var p Provisioner
|
||||
|
||||
// Test no config template
|
||||
config := testConfig()
|
||||
delete(config, "encrypted_data_bag_secret_path")
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Test with a file
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
config = testConfig()
|
||||
config["encrypted_data_bag_secret_path"] = tf.Name()
|
||||
p = Provisioner{}
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Test with a directory
|
||||
td, err := ioutil.TempDir("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(td)
|
||||
|
||||
config = testConfig()
|
||||
config["encrypted_data_bag_secret_path"] = td
|
||||
p = Provisioner{}
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have err")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisioner_createDir(t *testing.T) {
|
||||
for _, sudo := range []bool{true, false} {
|
||||
config := testConfig()
|
||||
config["prevent_sudo"] = !sudo
|
||||
|
||||
p := &Provisioner{}
|
||||
comm := &packersdk.MockCommunicator{}
|
||||
ui := &packersdk.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
}
|
||||
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if err := p.createDir(ui, comm, "/tmp/foo"); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
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) {
|
||||
for _, sudo := range []bool{true, false} {
|
||||
config := testConfig()
|
||||
config["prevent_sudo"] = !sudo
|
||||
|
||||
p := &Provisioner{}
|
||||
comm := &packersdk.MockCommunicator{}
|
||||
ui := &packersdk.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
}
|
||||
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if err := p.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 sudo && !strings.HasPrefix(comm.StartCmd.Command, "sudo") {
|
||||
t.Fatalf("removeDir should use sudo, got: \"%s\"", comm.StartCmd.Command)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_policy(t *testing.T) {
|
||||
var p Provisioner
|
||||
|
||||
var policyTests = []struct {
|
||||
name string
|
||||
group string
|
||||
success bool
|
||||
}{
|
||||
{"", "", true},
|
||||
{"a", "b", true},
|
||||
{"a", "", false},
|
||||
{"", "a", false},
|
||||
}
|
||||
for _, tt := range policyTests {
|
||||
config := testConfig()
|
||||
config["policy_name"] = tt.name
|
||||
config["policy_group"] = tt.group
|
||||
err := p.Prepare(config)
|
||||
if (err == nil) != tt.success {
|
||||
t.Fatalf("wasn't expecting %+v to fail: %s", tt, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package version
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/packer-plugin-sdk/version"
|
||||
packerVersion "github.com/hashicorp/packer/version"
|
||||
)
|
||||
|
||||
var ChefClientPluginVersion *version.PluginVersion
|
||||
|
||||
func init() {
|
||||
ChefClientPluginVersion = version.InitializePluginVersion(
|
||||
packerVersion.Version, packerVersion.VersionPrerelease)
|
||||
}
|
|
@ -1,611 +0,0 @@
|
|||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||
|
||||
// This package implements a provisioner for Packer that uses
|
||||
// Chef to provision the remote machine, specifically with chef-solo (that is,
|
||||
// without a Chef server).
|
||||
package chefsolo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/hashicorp/packer-plugin-sdk/common"
|
||||
"github.com/hashicorp/packer-plugin-sdk/guestexec"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
||||
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
|
||||
)
|
||||
|
||||
type guestOSTypeConfig struct {
|
||||
executeCommand string
|
||||
installCommand string
|
||||
stagingDir string
|
||||
}
|
||||
|
||||
var guestOSTypeConfigs = map[string]guestOSTypeConfig{
|
||||
guestexec.UnixOSType: {
|
||||
executeCommand: "{{if .Sudo}}sudo {{end}}chef-solo --no-color -c {{.ConfigPath}} -j {{.JsonPath}}",
|
||||
installCommand: "curl -L https://omnitruck.chef.io/install.sh | {{if .Sudo}}sudo {{end}}bash -s --{{if .Version}} -v {{.Version}}{{end}}",
|
||||
stagingDir: "/tmp/packer-chef-solo",
|
||||
},
|
||||
guestexec.WindowsOSType: {
|
||||
executeCommand: "c:/opscode/chef/bin/chef-solo.bat --no-color -c {{.ConfigPath}} -j {{.JsonPath}}",
|
||||
installCommand: "powershell.exe -Command \". { iwr -useb https://omnitruck.chef.io/install.ps1 } | iex; Install-Project{{if .Version}} -version {{.Version}}{{end}}\"",
|
||||
stagingDir: "C:/Windows/Temp/packer-chef-solo",
|
||||
},
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
ChefEnvironment string `mapstructure:"chef_environment"`
|
||||
ChefLicense string `mapstructure:"chef_license"`
|
||||
ConfigTemplate string `mapstructure:"config_template"`
|
||||
CookbookPaths []string `mapstructure:"cookbook_paths"`
|
||||
RolesPath string `mapstructure:"roles_path"`
|
||||
DataBagsPath string `mapstructure:"data_bags_path"`
|
||||
EncryptedDataBagSecretPath string `mapstructure:"encrypted_data_bag_secret_path"`
|
||||
EnvironmentsPath string `mapstructure:"environments_path"`
|
||||
ExecuteCommand string `mapstructure:"execute_command"`
|
||||
InstallCommand string `mapstructure:"install_command"`
|
||||
RemoteCookbookPaths []string `mapstructure:"remote_cookbook_paths"`
|
||||
// HCL cannot be decoded into an interface so for HCL templates you must use the JsonString option,
|
||||
// To be used with https://www.packer.io/docs/templates/hcl_templates/functions/encoding/jsonencode
|
||||
// ref: https://github.com/hashicorp/hcl/issues/291#issuecomment-496347585
|
||||
JsonString string `mapstructure:"json_string"`
|
||||
// For JSON templates we keep the map[string]interface{}
|
||||
Json map[string]interface{} `mapstructure:"json" mapstructure-to-hcl2:",skip"`
|
||||
PreventSudo bool `mapstructure:"prevent_sudo"`
|
||||
RunList []string `mapstructure:"run_list"`
|
||||
SkipInstall bool `mapstructure:"skip_install"`
|
||||
StagingDir string `mapstructure:"staging_directory"`
|
||||
GuestOSType string `mapstructure:"guest_os_type"`
|
||||
Version string `mapstructure:"version"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
type Provisioner struct {
|
||||
config Config
|
||||
guestOSTypeConfig guestOSTypeConfig
|
||||
guestCommands *guestexec.GuestCommands
|
||||
}
|
||||
|
||||
type ConfigTemplate struct {
|
||||
CookbookPaths string
|
||||
DataBagsPath string
|
||||
EncryptedDataBagSecretPath string
|
||||
RolesPath string
|
||||
EnvironmentsPath string
|
||||
ChefEnvironment string
|
||||
ChefLicense string
|
||||
|
||||
// Templates don't support boolean statements until Go 1.2. In the
|
||||
// mean time, we do this.
|
||||
// TODO(mitchellh): Remove when Go 1.2 is released
|
||||
HasDataBagsPath bool
|
||||
HasEncryptedDataBagSecretPath bool
|
||||
HasRolesPath bool
|
||||
HasEnvironmentsPath bool
|
||||
}
|
||||
|
||||
type ExecuteTemplate struct {
|
||||
ConfigPath string
|
||||
JsonPath string
|
||||
Sudo bool
|
||||
}
|
||||
|
||||
type InstallChefTemplate struct {
|
||||
Sudo bool
|
||||
Version string
|
||||
}
|
||||
|
||||
func (p *Provisioner) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() }
|
||||
|
||||
func (p *Provisioner) Prepare(raws ...interface{}) error {
|
||||
err := config.Decode(&p.config, &config.DecodeOpts{
|
||||
PluginType: "chef-solo",
|
||||
Interpolate: true,
|
||||
InterpolateContext: &p.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{
|
||||
"execute_command",
|
||||
"install_command",
|
||||
},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.config.JsonString != "" {
|
||||
if err := json.Unmarshal([]byte(p.config.JsonString), &p.config.Json); err != nil {
|
||||
return fmt.Errorf("Failed to unmarshal 'json_string': %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.GuestOSType == "" {
|
||||
p.config.GuestOSType = guestexec.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 = guestexec.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.guestOSTypeConfig.executeCommand
|
||||
}
|
||||
|
||||
if p.config.InstallCommand == "" {
|
||||
p.config.InstallCommand = p.guestOSTypeConfig.installCommand
|
||||
}
|
||||
|
||||
if p.config.RunList == nil {
|
||||
p.config.RunList = make([]string, 0)
|
||||
}
|
||||
|
||||
if p.config.StagingDir == "" {
|
||||
p.config.StagingDir = p.guestOSTypeConfig.stagingDir
|
||||
}
|
||||
|
||||
if p.config.SkipInstall == false && p.config.InstallCommand == p.guestOSTypeConfig.installCommand {
|
||||
if p.config.ChefLicense == "" {
|
||||
p.config.ChefLicense = "accept-silent"
|
||||
}
|
||||
}
|
||||
|
||||
var errs *packersdk.MultiError
|
||||
if p.config.ConfigTemplate != "" {
|
||||
fi, err := os.Stat(p.config.ConfigTemplate)
|
||||
if err != nil {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Bad config template path: %s", err))
|
||||
} else if fi.IsDir() {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Config template path must be a file: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
for _, path := range p.config.CookbookPaths {
|
||||
pFileInfo, err := os.Stat(path)
|
||||
|
||||
if err != nil || !pFileInfo.IsDir() {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Bad cookbook path '%s': %s", path, err))
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.RolesPath != "" {
|
||||
pFileInfo, err := os.Stat(p.config.RolesPath)
|
||||
|
||||
if err != nil || !pFileInfo.IsDir() {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Bad roles path '%s': %s", p.config.RolesPath, err))
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.DataBagsPath != "" {
|
||||
pFileInfo, err := os.Stat(p.config.DataBagsPath)
|
||||
|
||||
if err != nil || !pFileInfo.IsDir() {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Bad data bags path '%s': %s", p.config.DataBagsPath, err))
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.EncryptedDataBagSecretPath != "" {
|
||||
pFileInfo, err := os.Stat(p.config.EncryptedDataBagSecretPath)
|
||||
|
||||
if err != nil || pFileInfo.IsDir() {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Bad encrypted data bag secret '%s': %s", p.config.EncryptedDataBagSecretPath, err))
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.EnvironmentsPath != "" {
|
||||
pFileInfo, err := os.Stat(p.config.EnvironmentsPath)
|
||||
|
||||
if err != nil || !pFileInfo.IsDir() {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Bad environments path '%s': %s", p.config.EnvironmentsPath, err))
|
||||
}
|
||||
}
|
||||
|
||||
jsonValid := true
|
||||
for k, v := range p.config.Json {
|
||||
p.config.Json[k], err = p.deepJsonFix(k, v)
|
||||
if err != nil {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Error processing JSON: %s", err))
|
||||
jsonValid = false
|
||||
}
|
||||
}
|
||||
|
||||
if jsonValid {
|
||||
// Process the user variables within the JSON and set the JSON.
|
||||
// Do this early so that we can validate and show errors.
|
||||
p.config.Json, err = p.processJsonUserVars()
|
||||
if err != nil {
|
||||
errs = packersdk.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Error processing user variables in JSON: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) Provision(ctx context.Context, ui packersdk.Ui, comm packersdk.Communicator, _ map[string]interface{}) error {
|
||||
ui.Say("Provisioning with chef-solo")
|
||||
|
||||
if !p.config.SkipInstall {
|
||||
if err := p.installChef(ui, comm, p.config.Version); err != nil {
|
||||
return fmt.Errorf("Error installing Chef: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
|
||||
return fmt.Errorf("Error creating staging directory: %s", err)
|
||||
}
|
||||
|
||||
cookbookPaths := make([]string, 0, len(p.config.CookbookPaths))
|
||||
for i, path := range p.config.CookbookPaths {
|
||||
targetPath := fmt.Sprintf("%s/cookbooks-%d", p.config.StagingDir, i)
|
||||
if err := p.uploadDirectory(ui, comm, targetPath, path); err != nil {
|
||||
return fmt.Errorf("Error uploading cookbooks: %s", err)
|
||||
}
|
||||
|
||||
cookbookPaths = append(cookbookPaths, targetPath)
|
||||
}
|
||||
|
||||
rolesPath := ""
|
||||
if p.config.RolesPath != "" {
|
||||
rolesPath = fmt.Sprintf("%s/roles", p.config.StagingDir)
|
||||
if err := p.uploadDirectory(ui, comm, rolesPath, p.config.RolesPath); err != nil {
|
||||
return fmt.Errorf("Error uploading roles: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
dataBagsPath := ""
|
||||
if p.config.DataBagsPath != "" {
|
||||
dataBagsPath = fmt.Sprintf("%s/data_bags", p.config.StagingDir)
|
||||
if err := p.uploadDirectory(ui, comm, dataBagsPath, p.config.DataBagsPath); err != nil {
|
||||
return fmt.Errorf("Error uploading data bags: %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 {
|
||||
return fmt.Errorf("Error uploading encrypted data bag secret: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
environmentsPath := ""
|
||||
if p.config.EnvironmentsPath != "" {
|
||||
environmentsPath = fmt.Sprintf("%s/environments", p.config.StagingDir)
|
||||
if err := p.uploadDirectory(ui, comm, environmentsPath, p.config.EnvironmentsPath); err != nil {
|
||||
return fmt.Errorf("Error uploading environments: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
configPath, err := p.createConfig(ui, comm, cookbookPaths, rolesPath, dataBagsPath, encryptedDataBagSecretPath, environmentsPath, p.config.ChefEnvironment, p.config.ChefLicense)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating Chef config file: %s", err)
|
||||
}
|
||||
|
||||
jsonPath, err := p.createJson(ui, comm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating JSON attributes: %s", err)
|
||||
}
|
||||
|
||||
if err := p.executeChef(ui, comm, configPath, jsonPath); err != nil {
|
||||
return fmt.Errorf("Error executing Chef: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) uploadDirectory(ui packersdk.Ui, comm packersdk.Communicator, dst string, src string) error {
|
||||
if err := p.createDir(ui, comm, dst); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Make sure there is a trailing "/" so that the directory isn't
|
||||
// created on the other side.
|
||||
if src[len(src)-1] != '/' {
|
||||
src = src + "/"
|
||||
}
|
||||
|
||||
return comm.UploadDir(dst, src, nil)
|
||||
}
|
||||
|
||||
func (p *Provisioner) uploadFile(ui packersdk.Ui, comm packersdk.Communicator, dst string, src string) error {
|
||||
f, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return comm.Upload(dst, f, nil)
|
||||
}
|
||||
|
||||
func (p *Provisioner) createConfig(ui packersdk.Ui, comm packersdk.Communicator, localCookbooks []string, rolesPath string, dataBagsPath string, encryptedDataBagSecretPath string, environmentsPath string, chefEnvironment string, chefLicense string) (string, error) {
|
||||
ui.Message("Creating configuration file 'solo.rb'")
|
||||
|
||||
cookbook_paths := make([]string, len(p.config.RemoteCookbookPaths)+len(localCookbooks))
|
||||
for i, path := range p.config.RemoteCookbookPaths {
|
||||
cookbook_paths[i] = fmt.Sprintf(`"%s"`, path)
|
||||
}
|
||||
|
||||
for i, path := range localCookbooks {
|
||||
i = len(p.config.RemoteCookbookPaths) + i
|
||||
cookbook_paths[i] = fmt.Sprintf(`"%s"`, path)
|
||||
}
|
||||
|
||||
// Read the template
|
||||
tpl := DefaultConfigTemplate
|
||||
if p.config.ConfigTemplate != "" {
|
||||
f, err := os.Open(p.config.ConfigTemplate)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
tplBytes, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tpl = string(tplBytes)
|
||||
}
|
||||
|
||||
p.config.ctx.Data = &ConfigTemplate{
|
||||
CookbookPaths: strings.Join(cookbook_paths, ","),
|
||||
RolesPath: rolesPath,
|
||||
DataBagsPath: dataBagsPath,
|
||||
EncryptedDataBagSecretPath: encryptedDataBagSecretPath,
|
||||
EnvironmentsPath: environmentsPath,
|
||||
HasRolesPath: rolesPath != "",
|
||||
HasDataBagsPath: dataBagsPath != "",
|
||||
HasEncryptedDataBagSecretPath: encryptedDataBagSecretPath != "",
|
||||
HasEnvironmentsPath: environmentsPath != "",
|
||||
ChefEnvironment: chefEnvironment,
|
||||
ChefLicense: chefLicense,
|
||||
}
|
||||
configString, err := interpolate.Render(tpl, &p.config.ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
remotePath := filepath.ToSlash(filepath.Join(p.config.StagingDir, "solo.rb"))
|
||||
if err := comm.Upload(remotePath, bytes.NewReader([]byte(configString)), nil); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return remotePath, nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) createJson(ui packersdk.Ui, comm packersdk.Communicator) (string, error) {
|
||||
ui.Message("Creating JSON attribute file")
|
||||
|
||||
jsonData := make(map[string]interface{})
|
||||
// Copy the configured JSON
|
||||
for k, v := range p.config.Json {
|
||||
jsonData[k] = v
|
||||
}
|
||||
// Set the run list if it was specified
|
||||
if len(p.config.RunList) > 0 {
|
||||
jsonData["run_list"] = p.config.RunList
|
||||
}
|
||||
|
||||
jsonBytes, err := json.MarshalIndent(jsonData, "", " ")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Upload the bytes
|
||||
remotePath := filepath.ToSlash(filepath.Join(p.config.StagingDir, "node.json"))
|
||||
if err := comm.Upload(remotePath, bytes.NewReader(jsonBytes), nil); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return remotePath, nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) createDir(ui packersdk.Ui, comm packersdk.Communicator, dir string) error {
|
||||
ui.Message(fmt.Sprintf("Creating directory: %s", dir))
|
||||
ctx := context.TODO()
|
||||
|
||||
cmd := &packersdk.RemoteCmd{Command: p.guestCommands.CreateDir(dir)}
|
||||
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
|
||||
return err
|
||||
}
|
||||
if cmd.ExitStatus() != 0 {
|
||||
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 = &packersdk.RemoteCmd{Command: p.guestCommands.Chmod(dir, "0777")}
|
||||
if err := cmd.RunWithUi(ctx, 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
|
||||
}
|
||||
|
||||
func (p *Provisioner) executeChef(ui packersdk.Ui, comm packersdk.Communicator, config string, json string) error {
|
||||
p.config.ctx.Data = &ExecuteTemplate{
|
||||
ConfigPath: config,
|
||||
JsonPath: json,
|
||||
Sudo: !p.config.PreventSudo,
|
||||
}
|
||||
command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Executing Chef: %s", command))
|
||||
|
||||
cmd := &packersdk.RemoteCmd{
|
||||
Command: command,
|
||||
}
|
||||
ctx := context.TODO()
|
||||
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cmd.ExitStatus() != 0 {
|
||||
return fmt.Errorf("Non-zero exit status: %d", cmd.ExitStatus())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) installChef(ui packersdk.Ui, comm packersdk.Communicator, version string) error {
|
||||
ui.Message("Installing Chef...")
|
||||
ctx := context.TODO()
|
||||
|
||||
p.config.ctx.Data = &InstallChefTemplate{
|
||||
Sudo: !p.config.PreventSudo,
|
||||
Version: version,
|
||||
}
|
||||
command, err := interpolate.Render(p.config.InstallCommand, &p.config.ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := &packersdk.RemoteCmd{Command: command}
|
||||
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cmd.ExitStatus() != 0 {
|
||||
return fmt.Errorf(
|
||||
"Install script exited with non-zero exit status %d", cmd.ExitStatus())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) deepJsonFix(key string, current interface{}) (interface{}, error) {
|
||||
if current == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch c := current.(type) {
|
||||
case []interface{}:
|
||||
val := make([]interface{}, len(c))
|
||||
for i, v := range c {
|
||||
var err error
|
||||
val[i], err = p.deepJsonFix(fmt.Sprintf("%s[%d]", key, i), v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return val, nil
|
||||
case []uint8:
|
||||
return string(c), nil
|
||||
case map[interface{}]interface{}:
|
||||
val := make(map[string]interface{})
|
||||
for k, v := range c {
|
||||
ks, ok := k.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s: key is not string", key)
|
||||
}
|
||||
|
||||
var err error
|
||||
val[ks], err = p.deepJsonFix(
|
||||
fmt.Sprintf("%s.%s", key, ks), v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return val, nil
|
||||
default:
|
||||
return current, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provisioner) processJsonUserVars() (map[string]interface{}, error) {
|
||||
jsonBytes, err := json.Marshal(p.config.Json)
|
||||
if err != nil {
|
||||
// This really shouldn't happen since we literally just unmarshalled
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Copy the user variables so that we can restore them later, and
|
||||
// make sure we make the quotes JSON-friendly in the user variables.
|
||||
originalUserVars := make(map[string]string)
|
||||
for k, v := range p.config.ctx.UserVariables {
|
||||
originalUserVars[k] = v
|
||||
}
|
||||
|
||||
// Make sure we reset them no matter what
|
||||
defer func() {
|
||||
p.config.ctx.UserVariables = originalUserVars
|
||||
}()
|
||||
|
||||
// Make the current user variables JSON string safe.
|
||||
for k, v := range p.config.ctx.UserVariables {
|
||||
v = strings.Replace(v, `\`, `\\`, -1)
|
||||
v = strings.Replace(v, `"`, `\"`, -1)
|
||||
p.config.ctx.UserVariables[k] = v
|
||||
}
|
||||
|
||||
// Process the bytes with the template processor
|
||||
p.config.ctx.Data = nil
|
||||
jsonBytesProcessed, err := interpolate.Render(string(jsonBytes), &p.config.ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(jsonBytesProcessed), &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
var DefaultConfigTemplate = `
|
||||
chef_license "{{.ChefLicense}}"
|
||||
cookbook_path [{{.CookbookPaths}}]
|
||||
{{if .HasRolesPath}}
|
||||
role_path "{{.RolesPath}}"
|
||||
{{end}}
|
||||
{{if .HasDataBagsPath}}
|
||||
data_bag_path "{{.DataBagsPath}}"
|
||||
{{end}}
|
||||
{{if .HasEncryptedDataBagSecretPath}}
|
||||
encrypted_data_bag_secret "{{.EncryptedDataBagSecretPath}}"
|
||||
{{end}}
|
||||
{{if .HasEnvironmentsPath}}
|
||||
environment_path "{{.EnvironmentsPath}}"
|
||||
environment "{{.ChefEnvironment}}"
|
||||
{{end}}
|
||||
`
|
|
@ -1,81 +0,0 @@
|
|||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
|
||||
package chefsolo
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// FlatConfig is an auto-generated flat version of Config.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatConfig struct {
|
||||
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
|
||||
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
|
||||
PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
|
||||
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
|
||||
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
|
||||
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
|
||||
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
|
||||
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
|
||||
ChefEnvironment *string `mapstructure:"chef_environment" cty:"chef_environment" hcl:"chef_environment"`
|
||||
ChefLicense *string `mapstructure:"chef_license" cty:"chef_license" hcl:"chef_license"`
|
||||
ConfigTemplate *string `mapstructure:"config_template" cty:"config_template" hcl:"config_template"`
|
||||
CookbookPaths []string `mapstructure:"cookbook_paths" cty:"cookbook_paths" hcl:"cookbook_paths"`
|
||||
RolesPath *string `mapstructure:"roles_path" cty:"roles_path" hcl:"roles_path"`
|
||||
DataBagsPath *string `mapstructure:"data_bags_path" cty:"data_bags_path" hcl:"data_bags_path"`
|
||||
EncryptedDataBagSecretPath *string `mapstructure:"encrypted_data_bag_secret_path" cty:"encrypted_data_bag_secret_path" hcl:"encrypted_data_bag_secret_path"`
|
||||
EnvironmentsPath *string `mapstructure:"environments_path" cty:"environments_path" hcl:"environments_path"`
|
||||
ExecuteCommand *string `mapstructure:"execute_command" cty:"execute_command" hcl:"execute_command"`
|
||||
InstallCommand *string `mapstructure:"install_command" cty:"install_command" hcl:"install_command"`
|
||||
RemoteCookbookPaths []string `mapstructure:"remote_cookbook_paths" cty:"remote_cookbook_paths" hcl:"remote_cookbook_paths"`
|
||||
JsonString *string `mapstructure:"json_string" cty:"json_string" hcl:"json_string"`
|
||||
PreventSudo *bool `mapstructure:"prevent_sudo" cty:"prevent_sudo" hcl:"prevent_sudo"`
|
||||
RunList []string `mapstructure:"run_list" cty:"run_list" hcl:"run_list"`
|
||||
SkipInstall *bool `mapstructure:"skip_install" cty:"skip_install" hcl:"skip_install"`
|
||||
StagingDir *string `mapstructure:"staging_directory" cty:"staging_directory" hcl:"staging_directory"`
|
||||
GuestOSType *string `mapstructure:"guest_os_type" cty:"guest_os_type" hcl:"guest_os_type"`
|
||||
Version *string `mapstructure:"version" cty:"version" hcl:"version"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatConfig.
|
||||
// FlatConfig is an auto-generated flat version of Config.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatConfig)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a Config.
|
||||
// This spec is used by HCL to read the fields of Config.
|
||||
// The decoded values from this spec will then be applied to a FlatConfig.
|
||||
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
|
||||
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
|
||||
"packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false},
|
||||
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
|
||||
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
|
||||
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
|
||||
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
|
||||
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
|
||||
"chef_environment": &hcldec.AttrSpec{Name: "chef_environment", Type: cty.String, Required: false},
|
||||
"chef_license": &hcldec.AttrSpec{Name: "chef_license", Type: cty.String, Required: false},
|
||||
"config_template": &hcldec.AttrSpec{Name: "config_template", Type: cty.String, Required: false},
|
||||
"cookbook_paths": &hcldec.AttrSpec{Name: "cookbook_paths", Type: cty.List(cty.String), Required: false},
|
||||
"roles_path": &hcldec.AttrSpec{Name: "roles_path", Type: cty.String, Required: false},
|
||||
"data_bags_path": &hcldec.AttrSpec{Name: "data_bags_path", Type: cty.String, Required: false},
|
||||
"encrypted_data_bag_secret_path": &hcldec.AttrSpec{Name: "encrypted_data_bag_secret_path", Type: cty.String, Required: false},
|
||||
"environments_path": &hcldec.AttrSpec{Name: "environments_path", Type: cty.String, Required: false},
|
||||
"execute_command": &hcldec.AttrSpec{Name: "execute_command", Type: cty.String, Required: false},
|
||||
"install_command": &hcldec.AttrSpec{Name: "install_command", Type: cty.String, Required: false},
|
||||
"remote_cookbook_paths": &hcldec.AttrSpec{Name: "remote_cookbook_paths", Type: cty.List(cty.String), Required: false},
|
||||
"json_string": &hcldec.AttrSpec{Name: "json_string", Type: cty.String, Required: false},
|
||||
"prevent_sudo": &hcldec.AttrSpec{Name: "prevent_sudo", Type: cty.Bool, Required: false},
|
||||
"run_list": &hcldec.AttrSpec{Name: "run_list", Type: cty.List(cty.String), Required: false},
|
||||
"skip_install": &hcldec.AttrSpec{Name: "skip_install", Type: cty.Bool, Required: false},
|
||||
"staging_directory": &hcldec.AttrSpec{Name: "staging_directory", Type: cty.String, Required: false},
|
||||
"guest_os_type": &hcldec.AttrSpec{Name: "guest_os_type", Type: cty.String, Required: false},
|
||||
"version": &hcldec.AttrSpec{Name: "version", Type: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
|
@ -1,390 +0,0 @@
|
|||
package chefsolo
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/common"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
|
||||
func TestProvisioner_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Provisioner{}
|
||||
if _, ok := raw.(packersdk.Provisioner); !ok {
|
||||
t.Fatalf("must be a Provisioner")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_chefEnvironment(t *testing.T) {
|
||||
var p Provisioner
|
||||
|
||||
config := testConfig()
|
||||
config["chef_environment"] = "some-env"
|
||||
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if p.config.ChefEnvironment != "some-env" {
|
||||
t.Fatalf("unexpected: %#v", p.config.ChefEnvironment)
|
||||
}
|
||||
}
|
||||
func TestProvisionerPrepare_chefLicense(t *testing.T) {
|
||||
var p Provisioner
|
||||
|
||||
// Test not set
|
||||
config := testConfig()
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
|
||||
if p.config.ChefLicense != "accept-silent" {
|
||||
t.Fatalf("unexpected: %#v", p.config.ChefLicense)
|
||||
}
|
||||
|
||||
// Test set
|
||||
config = testConfig()
|
||||
config["chef_license"] = "accept"
|
||||
p = Provisioner{}
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if p.config.ChefLicense != "accept" {
|
||||
t.Fatalf("unexpected: %#v", p.config.ChefLicense)
|
||||
}
|
||||
|
||||
// Test set skipInstall true
|
||||
config = testConfig()
|
||||
config["skip_install"] = true
|
||||
p = Provisioner{}
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if p.config.ChefLicense != "" {
|
||||
t.Fatalf("unexpected: %#v", "empty string")
|
||||
}
|
||||
|
||||
// Test set installCommand true
|
||||
config = testConfig()
|
||||
config["install_command"] = "install chef"
|
||||
p = Provisioner{}
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if p.config.ChefLicense != "" {
|
||||
t.Fatalf("unexpected: %#v", "empty string")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_configTemplate(t *testing.T) {
|
||||
var err error
|
||||
var p Provisioner
|
||||
|
||||
// Test no config template
|
||||
config := testConfig()
|
||||
delete(config, "config_template")
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Test with a file
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
config = testConfig()
|
||||
config["config_template"] = tf.Name()
|
||||
p = Provisioner{}
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Test with a directory
|
||||
td, err := ioutil.TempDir("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(td)
|
||||
|
||||
config = testConfig()
|
||||
config["config_template"] = td
|
||||
p = Provisioner{}
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have err")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_cookbookPaths(t *testing.T) {
|
||||
var p Provisioner
|
||||
|
||||
path1, err := ioutil.TempDir("", "cookbooks_one")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
path2, err := ioutil.TempDir("", "cookbooks_two")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
rolesPath, err := ioutil.TempDir("", "roles")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
dataBagsPath, err := ioutil.TempDir("", "data_bags")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
defer os.Remove(path1)
|
||||
defer os.Remove(path2)
|
||||
defer os.Remove(rolesPath)
|
||||
defer os.Remove(dataBagsPath)
|
||||
|
||||
config := testConfig()
|
||||
config["cookbook_paths"] = []string{path1, path2}
|
||||
config["roles_path"] = rolesPath
|
||||
config["data_bags_path"] = dataBagsPath
|
||||
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(p.config.CookbookPaths) != 2 {
|
||||
t.Fatalf("unexpected: %#v", p.config.CookbookPaths)
|
||||
}
|
||||
|
||||
if p.config.CookbookPaths[0] != path1 || p.config.CookbookPaths[1] != path2 {
|
||||
t.Fatalf("unexpected: %#v", p.config.CookbookPaths)
|
||||
}
|
||||
|
||||
if p.config.RolesPath != rolesPath {
|
||||
t.Fatalf("unexpected: %#v", p.config.RolesPath)
|
||||
}
|
||||
|
||||
if p.config.DataBagsPath != dataBagsPath {
|
||||
t.Fatalf("unexpected: %#v", p.config.DataBagsPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_dataBagsPath(t *testing.T) {
|
||||
var p Provisioner
|
||||
|
||||
dataBagsPath, err := ioutil.TempDir("", "data_bags")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(dataBagsPath)
|
||||
|
||||
config := testConfig()
|
||||
config["data_bags_path"] = dataBagsPath
|
||||
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if p.config.DataBagsPath != dataBagsPath {
|
||||
t.Fatalf("unexpected: %#v", p.config.DataBagsPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_encryptedDataBagSecretPath(t *testing.T) {
|
||||
var err error
|
||||
var p Provisioner
|
||||
|
||||
// Test no config template
|
||||
config := testConfig()
|
||||
delete(config, "encrypted_data_bag_secret_path")
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Test with a file
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
config = testConfig()
|
||||
config["encrypted_data_bag_secret_path"] = tf.Name()
|
||||
p = Provisioner{}
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Test with a directory
|
||||
td, err := ioutil.TempDir("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(td)
|
||||
|
||||
config = testConfig()
|
||||
config["encrypted_data_bag_secret_path"] = td
|
||||
p = Provisioner{}
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have err")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_environmentsPath(t *testing.T) {
|
||||
var p Provisioner
|
||||
|
||||
environmentsPath, err := ioutil.TempDir("", "environments")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(environmentsPath)
|
||||
|
||||
config := testConfig()
|
||||
config["environments_path"] = environmentsPath
|
||||
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if p.config.EnvironmentsPath != environmentsPath {
|
||||
t.Fatalf("unexpected: %#v", p.config.EnvironmentsPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_rolesPath(t *testing.T) {
|
||||
var p Provisioner
|
||||
|
||||
rolesPath, err := ioutil.TempDir("", "roles")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(rolesPath)
|
||||
|
||||
config := testConfig()
|
||||
config["roles_path"] = rolesPath
|
||||
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if p.config.RolesPath != rolesPath {
|
||||
t.Fatalf("unexpected: %#v", p.config.RolesPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_json(t *testing.T) {
|
||||
config := testConfig()
|
||||
config["json"] = map[string]interface{}{
|
||||
"foo": "{{ user `foo` }}",
|
||||
}
|
||||
|
||||
config[common.UserVariablesConfigKey] = map[string]string{
|
||||
"foo": `"bar\baz"`,
|
||||
}
|
||||
|
||||
var p Provisioner
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if p.config.Json["foo"] != `"bar\baz"` {
|
||||
t.Fatalf("bad: %#v", p.config.Json)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_jsonNested(t *testing.T) {
|
||||
config := testConfig()
|
||||
config["json"] = map[string]interface{}{
|
||||
"foo": map[interface{}]interface{}{
|
||||
"bar": []uint8("baz"),
|
||||
},
|
||||
|
||||
"bar": []interface{}{
|
||||
"foo",
|
||||
|
||||
map[interface{}]interface{}{
|
||||
"bar": "baz",
|
||||
},
|
||||
},
|
||||
|
||||
"bFalse": false,
|
||||
"bTrue": true,
|
||||
"bNil": nil,
|
||||
"bStr": []uint8("bar"),
|
||||
|
||||
"bInt": 1,
|
||||
"bFloat": 4.5,
|
||||
}
|
||||
|
||||
var p Provisioner
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
fooMap := p.config.Json["foo"].(map[string]interface{})
|
||||
if fooMap["bar"] != "baz" {
|
||||
t.Fatalf("nope: %#v", fooMap["bar"])
|
||||
}
|
||||
if p.config.Json["bStr"] != "bar" {
|
||||
t.Fatalf("nope: %#v", fooMap["bar"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_jsonstring(t *testing.T) {
|
||||
config := testConfig()
|
||||
config["json_string"] = `{
|
||||
"foo": {
|
||||
"bar": "baz"
|
||||
},
|
||||
"bar": {
|
||||
"bar": "baz"
|
||||
},
|
||||
"bFalse": false,
|
||||
"bTrue": true,
|
||||
"bStr": "bar",
|
||||
"bNil": null,
|
||||
"bInt": 1,
|
||||
"bFloat": 4.5
|
||||
}`
|
||||
|
||||
var p Provisioner
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
fooMap := p.config.Json["foo"].(map[string]interface{})
|
||||
if fooMap["bar"] != "baz" {
|
||||
t.Fatalf("nope: %#v", fooMap["bar"])
|
||||
}
|
||||
if p.config.Json["bStr"] != "bar" {
|
||||
t.Fatalf("nope: %#v", fooMap["bar"])
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package version
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/packer-plugin-sdk/version"
|
||||
packerVersion "github.com/hashicorp/packer/version"
|
||||
)
|
||||
|
||||
var ChefSoloPluginVersion *version.PluginVersion
|
||||
|
||||
func init() {
|
||||
ChefSoloPluginVersion = version.InitializePluginVersion(
|
||||
packerVersion.Version, packerVersion.VersionPrerelease)
|
||||
}
|
|
@ -1,430 +0,0 @@
|
|||
---
|
||||
description: >
|
||||
The chef-client Packer provisioner installs and configures software on
|
||||
machines
|
||||
|
||||
built by Packer using chef-client. Packer configures a Chef client to talk to
|
||||
a
|
||||
|
||||
remote Chef Server to provision the machine.
|
||||
page_title: Chef Client - Provisioners
|
||||
---
|
||||
|
||||
# Chef Client Provisioner
|
||||
|
||||
@include 'provisioners/unmaintained-plugin.mdx'
|
||||
|
||||
Type: `chef-client`
|
||||
|
||||
The Chef Client Packer provisioner installs and configures software on machines
|
||||
built by Packer using [chef-client](https://docs.chef.io/chef_client.html).
|
||||
Packer configures a Chef client to talk to a remote Chef Server to provision
|
||||
the machine.
|
||||
|
||||
The provisioner will even install Chef onto your machine if it isn't already
|
||||
installed, using the official Chef installers provided by Chef.
|
||||
|
||||
## Basic Example
|
||||
|
||||
The example below is fully functional. It will install Chef onto the remote
|
||||
machine and run Chef client.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "chef-client",
|
||||
"server_url": "https://mychefserver.com/"
|
||||
}
|
||||
```
|
||||
|
||||
Note: to properly clean up the Chef node and client the machine on which packer
|
||||
is running must have knife on the path and configured globally, i.e,
|
||||
`~/.chef/knife.rb` must be present and configured for the target chef server
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
The reference of available configuration options is listed below. No
|
||||
configuration is actually required.
|
||||
|
||||
- `chef_environment` (string) - The name of the chef_environment sent to the
|
||||
Chef server. By default this is empty and will not use an environment.
|
||||
|
||||
- `chef_license` (string) - As of Chef v15, Chef requires users to accept a
|
||||
license. Defaults to `accept-silent` when `skip_install` is false and
|
||||
`install_command` is unset. Possible values are `accept`,
|
||||
`accept-silent` and `accept-no-persist`. For details see [Accepting the
|
||||
Chef License](https://docs.chef.io/chef_license_accept.html).
|
||||
|
||||
This is a [template engine](/docs/templates/legacy_json_templates/engine.html). Therefore, you
|
||||
may use user variables and template functions in this field.
|
||||
|
||||
- `config_template` (string) - Path to a template that will be used for the
|
||||
Chef configuration file. By default Packer only sets configuration it needs
|
||||
to match the settings set in the provisioner configuration. If you need to
|
||||
set configurations that the Packer provisioner doesn't support, then you
|
||||
should use a custom configuration template. See the dedicated "Chef
|
||||
Configuration" section below for more details.
|
||||
|
||||
- `elevated_user` and `elevated_password` (string) - If specified, Chef will
|
||||
be run with elevated privileges using the given Windows user. See the
|
||||
[powershell](/docs/provisioners/powershell) provisioner for the full
|
||||
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.
|
||||
|
||||
- `execute_command` (string) - The command used to execute Chef. This has
|
||||
various [configuration template variables](/docs/templates/legacy_json_templates/engine)
|
||||
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/legacy_json_templates/engine)
|
||||
available. See below for more information.
|
||||
|
||||
- `json` (object) - An arbitrary mapping of JSON that will be available as
|
||||
node attributes while running Chef.
|
||||
|
||||
- `knife_command` (string) - The command used to run Knife during node
|
||||
clean-up. This has various [configuration template
|
||||
variables](/docs/templates/legacy_json_templates/engine) available. See below for more
|
||||
information.
|
||||
|
||||
- `node_name` (string) - The name of the node to register with the Chef
|
||||
Server. This is optional and by default is `packer-{{uuid}}`.
|
||||
|
||||
- `policy_group` (string) - The name of a policy group that exists on the
|
||||
Chef server. `policy_name` must also be specified.
|
||||
|
||||
- `policy_name` (string) - The name of a policy, as identified by the name
|
||||
setting in a `Policyfile.rb` file. `policy_group` must also be specified.
|
||||
|
||||
- `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. This has no effect when guest_os_type is
|
||||
windows.
|
||||
|
||||
- `run_list` (array of strings) - The [run
|
||||
list](https://docs.chef.io/run_lists) for Chef.
|
||||
By default this is empty, and will use the run list sent down by the Chef
|
||||
Server.
|
||||
|
||||
- `server_url` (string) - The URL to the Chef server. This is required.
|
||||
|
||||
- `skip_clean_client` (boolean) - If true, Packer won't remove the client
|
||||
from the Chef server after it is done running. By default, this is false.
|
||||
|
||||
- `skip_clean_node` (boolean) - If true, Packer won't remove the node from
|
||||
the Chef server after it is done running. By default, this is false.
|
||||
|
||||
- `skip_clean_staging_directory` (boolean) - If true, Packer won't remove the
|
||||
Chef staging directory from the machine after it is done running. By
|
||||
default, this is false.
|
||||
|
||||
- `skip_install` (boolean) - If true, Chef will not automatically be
|
||||
installed on the machine using the Chef omnibus installers.
|
||||
|
||||
- `ssl_verify_mode` (string) - Set to "verify_none" to skip validation of
|
||||
SSL certificates. If not set, this defaults to "verify_peer" which
|
||||
validates all SSL certifications.
|
||||
|
||||
- `trusted_certs_dir` (string) - This is a directory that contains additional
|
||||
SSL certificates to trust. Any certificates in this directory will be added
|
||||
to whatever CA bundle ruby is using. Use this to add self-signed certs for
|
||||
your Chef Server or local HTTP file servers.
|
||||
|
||||
- `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` 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`.
|
||||
|
||||
- `validation_client_name` (string) - Name of the validation client. If not
|
||||
set, this won't be set in the configuration and the default that Chef uses
|
||||
will be used.
|
||||
|
||||
- `validation_key_path` (string) - Path to the validation key for
|
||||
communicating with the Chef Server. This will be uploaded to the remote
|
||||
machine. If this is NOT set, then it is your responsibility via other means
|
||||
(shell provisioner, etc.) to get a validation key to where Chef expects it.
|
||||
|
||||
- `version` (string) - The version of Chef to be installed. By default this
|
||||
is empty which will install the latest version of Chef.
|
||||
|
||||
@include 'provisioners/common-config.mdx'
|
||||
|
||||
## Chef Configuration
|
||||
|
||||
By default, Packer uses a simple Chef configuration file in order to set the
|
||||
options specified for the provisioner. But Chef is a complex tool that supports
|
||||
many configuration options. Packer allows you to specify a custom configuration
|
||||
template if you'd like to set custom configurations.
|
||||
|
||||
The default value for the configuration template is:
|
||||
|
||||
```liquid
|
||||
log_level :info
|
||||
log_location STDOUT
|
||||
chef_server_url "{{.ServerUrl}}"
|
||||
client_key "{{.ClientKey}}"
|
||||
chef_license "{{.ChefLicense}}"
|
||||
{{if ne .EncryptedDataBagSecretPath ""}}
|
||||
encrypted_data_bag_secret "{{.EncryptedDataBagSecretPath}}"
|
||||
{{end}}
|
||||
{{if ne .ValidationClientName ""}}
|
||||
validation_client_name "{{.ValidationClientName}}"
|
||||
{{else}}
|
||||
validation_client_name "chef-validator"
|
||||
{{end}}
|
||||
{{if ne .ValidationKeyPath ""}}
|
||||
validation_key "{{.ValidationKeyPath}}"
|
||||
{{end}}
|
||||
node_name "{{.NodeName}}"
|
||||
{{if ne .ChefEnvironment ""}}
|
||||
environment "{{.ChefEnvironment}}"
|
||||
{{end}}
|
||||
{{if ne .PolicyGroup ""}}
|
||||
policy_group "{{.PolicyGroup}}"
|
||||
{{end}}
|
||||
{{if ne .PolicyName ""}}
|
||||
policy_name "{{.PolicyName}}"
|
||||
{{end}}
|
||||
{{if ne .SslVerifyMode ""}}
|
||||
ssl_verify_mode :{{.SslVerifyMode}}
|
||||
{{end}}
|
||||
{{if ne .TrustedCertsDir ""}}
|
||||
trusted_certs_dir :{{.TrustedCertsDir}}
|
||||
{{end}}
|
||||
```
|
||||
|
||||
This template is a [configuration template](/docs/templates/legacy_json_templates/engine) and
|
||||
has a set of variables available to use:
|
||||
|
||||
- `ChefEnvironment` - The Chef environment name.
|
||||
- `ChefLicense` - The Chef license acceptance value.
|
||||
- `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.
|
||||
- `TrustedCertsDir` - Path to dir with trusted certificates.
|
||||
- `ValidationClientName` - The name of the client used for validation.
|
||||
- `ValidationKeyPath` - Path to the validation key, if it is set.
|
||||
|
||||
## Execute Command
|
||||
|
||||
By default, Packer uses the following command (broken across multiple lines for
|
||||
readability) to execute Chef:
|
||||
|
||||
```liquid
|
||||
{{if .Sudo}}sudo {{end}}chef-client \
|
||||
--no-color \
|
||||
-c {{.ConfigPath}} \
|
||||
-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 propagate 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:
|
||||
|
||||
- `ConfigPath` - The path to the Chef configuration file.
|
||||
- `JsonPath` - The path to the JSON attributes file for the node.
|
||||
- `Sudo` - A boolean of whether to `sudo` the command or not, depending on
|
||||
the value of the `prevent_sudo` configuration.
|
||||
|
||||
## Install Command
|
||||
|
||||
By default, Packer uses the following command (broken across multiple lines for
|
||||
readability) to install Chef. This command can be customized if you want to
|
||||
install Chef in another way.
|
||||
|
||||
```text
|
||||
curl -L https://omnitruck.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.
|
||||
|
||||
## Knife Command
|
||||
|
||||
By default, Packer uses the following command (broken across multiple lines for
|
||||
readability) to execute Chef:
|
||||
|
||||
```liquid
|
||||
{{if .Sudo}}sudo {{end}}knife \
|
||||
{{.Args}} \
|
||||
{{.Flags}}
|
||||
```
|
||||
|
||||
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 propagate to running processes.
|
||||
|
||||
```liquid
|
||||
c:/opscode/chef/bin/knife.bat \
|
||||
{{.Args}} \
|
||||
{{.Flags}}
|
||||
```
|
||||
|
||||
This command can be customized using the `knife_command` configuration. As you
|
||||
can see from the default value above, the value of this configuration can
|
||||
contain various template variables, defined below:
|
||||
|
||||
- `Args` - The command arguments that are getting passed to the Knife
|
||||
command.
|
||||
- `Flags` - The command flags that are getting passed to the Knife command..
|
||||
- `Sudo` - A boolean of whether to `sudo` the command or not, depending on
|
||||
the value of the `prevent_sudo` configuration.
|
||||
|
||||
## Folder Permissions
|
||||
|
||||
!> The `chef-client` provisioner will chmod the directory with your Chef
|
||||
keys to 777. This is to ensure that Packer can upload and make use of that
|
||||
directory. However, once the machine is created, you usually don't want to keep
|
||||
these directories with those permissions. To change the permissions on the
|
||||
directories, append a shell provisioner after Chef to modify them.
|
||||
|
||||
## Examples
|
||||
|
||||
### Chef Client Local Mode - Simple
|
||||
|
||||
The following example shows how to run the `chef-client` provisioner in local
|
||||
mode.
|
||||
|
||||
**Packer variables**
|
||||
|
||||
Set the necessary Packer variables using environment variables or provide a
|
||||
[var file](/docs/templates/legacy_json_templates/user-variables).
|
||||
|
||||
```json
|
||||
"variables": {
|
||||
"chef_dir": "/tmp/packer-chef-client"
|
||||
}
|
||||
```
|
||||
|
||||
**Setup the** `chef-client` **provisioner**
|
||||
|
||||
Make sure we have the correct directories and permissions for the `chef-client`
|
||||
provisioner. You will need to bootstrap the Chef run by providing the necessary
|
||||
cookbooks using Berkshelf or some other means.
|
||||
|
||||
```json
|
||||
"provisioners": [
|
||||
...
|
||||
{ "type": "shell", "inline": [ "mkdir -p {{user `chef_dir`}}" ] },
|
||||
{ "type": "file", "source": "./roles", "destination": "{{user `chef_dir`}}" },
|
||||
{ "type": "file", "source": "./cookbooks", "destination": "{{user `chef_dir`}}" },
|
||||
{ "type": "file", "source": "./data_bags", "destination": "{{user `chef_dir`}}" },
|
||||
{ "type": "file", "source": "./environments", "destination": "{{user `chef_dir`}}" },
|
||||
{ "type": "file", "source": "./scripts/install_chef.sh", "destination": "{{user `chef_dir`}}/install_chef.sh" },
|
||||
{
|
||||
"type": "chef-client",
|
||||
"install_command": "sudo bash {{user `chef_dir`}}/install_chef.sh",
|
||||
"server_url": "http://localhost:8889",
|
||||
"config_template": "./config/client.rb.template",
|
||||
"run_list": [ "role[testing]" ],
|
||||
"skip_clean_node": true,
|
||||
"skip_clean_client": true
|
||||
}
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
And ./config/client.rb.template referenced by the above configuration:
|
||||
|
||||
```ruby
|
||||
log_level :info
|
||||
log_location STDOUT
|
||||
local_mode true
|
||||
chef_zero.enabled true
|
||||
ssl_verify_mode "verify_peer"
|
||||
role_path "{{user `chef_dir`}}/roles"
|
||||
data_bag_path "{{user `chef_dir`}}/data_bags"
|
||||
environment_path "{{user `chef_dir`}}/environments"
|
||||
cookbook_path [ "{{user `chef_dir`}}/cookbooks" ]
|
||||
```
|
||||
|
||||
### Chef Client Local Mode - Passing variables
|
||||
|
||||
The following example shows how to run the `chef-client` provisioner in local
|
||||
mode, while passing a `run_list` using a variable.
|
||||
|
||||
**Local environment variables**
|
||||
|
||||
# Machine's Chef directory
|
||||
export PACKER_CHEF_DIR=/var/chef-packer
|
||||
# Comma separated run_list
|
||||
export PACKER_CHEF_RUN_LIST="recipe[apt],recipe[nginx]"
|
||||
|
||||
**Packer variables**
|
||||
|
||||
Set the necessary Packer variables using environment variables or provide a
|
||||
[var file](/docs/templates/legacy_json_templates/user-variables).
|
||||
|
||||
```json
|
||||
"variables": {
|
||||
"chef_dir": "{{env `PACKER_CHEF_DIR`}}",
|
||||
"chef_run_list": "{{env `PACKER_CHEF_RUN_LIST`}}",
|
||||
"chef_client_config_tpl": "{{env `PACKER_CHEF_CLIENT_CONFIG_TPL`}}",
|
||||
"packer_chef_bootstrap_dir": "{{env `PACKER_CHEF_BOOTSTRAP_DIR`}}" ,
|
||||
"packer_uid": "{{env `PACKER_UID`}}",
|
||||
"packer_gid": "{{env `PACKER_GID`}}"
|
||||
}
|
||||
```
|
||||
|
||||
**Setup the** `chef-client` **provisioner**
|
||||
|
||||
Make sure we have the correct directories and permissions for the `chef-client`
|
||||
provisioner. You will need to bootstrap the Chef run by providing the necessary
|
||||
cookbooks using Berkshelf or some other means.
|
||||
|
||||
```json
|
||||
({
|
||||
"type": "file",
|
||||
"source": "{{user `packer_chef_bootstrap_dir`}}",
|
||||
"destination": "/tmp/bootstrap"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"inline": [
|
||||
"sudo mkdir -p {{user `chef_dir`}}",
|
||||
"sudo mkdir -p /tmp/packer-chef-client",
|
||||
"sudo chown {{user `packer_uid`}}.{{user `packer_gid`}} /tmp/packer-chef-client",
|
||||
"sudo sh /tmp/bootstrap/bootstrap.sh"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "chef-client",
|
||||
"server_url": "http://localhost:8889",
|
||||
"config_template": "{{user `chef_client_config_tpl`}}/client.rb.tpl",
|
||||
"skip_clean_node": true,
|
||||
"skip_clean_client": true,
|
||||
"run_list": "{{user `chef_run_list`}}"
|
||||
})
|
||||
```
|
|
@ -1,256 +0,0 @@
|
|||
---
|
||||
description: |
|
||||
The chef-solo Packer provisioner installs and configures software on machines
|
||||
built by Packer using chef-solo. Cookbooks can be uploaded from your local
|
||||
machine to the remote machine or remote paths can be used.
|
||||
page_title: Chef Solo - Provisioners
|
||||
---
|
||||
|
||||
# Chef Solo Provisioner
|
||||
|
||||
@include 'provisioners/unmaintained-plugin.mdx'
|
||||
|
||||
Type: `chef-solo`
|
||||
|
||||
The Chef solo Packer provisioner installs and configures software on machines
|
||||
built by Packer using [chef-solo](https://docs.chef.io/chef_solo.html).
|
||||
Cookbooks can be uploaded from your local machine to the remote machine or
|
||||
remote paths can be used.
|
||||
|
||||
The provisioner will even install Chef onto your machine if it isn't already
|
||||
installed, using the official Chef installers provided by Chef Inc.
|
||||
|
||||
## Basic Example
|
||||
|
||||
The example below is fully functional and expects cookbooks in the "cookbooks"
|
||||
directory relative to your working directory.
|
||||
|
||||
<Tabs>
|
||||
<Tab heading="HCL2">
|
||||
|
||||
```hcl
|
||||
provisioner "chef-solo" {
|
||||
cookbook_paths = ["cookbooks"]
|
||||
}
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab heading="JSON">
|
||||
|
||||
```json
|
||||
"provisioners":[{
|
||||
"type": "chef-solo",
|
||||
"cookbook_paths": ["cookbooks"]
|
||||
}]
|
||||
```
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
The reference of available configuration options is listed below. No
|
||||
configuration is actually required, but at least `run_list` is recommended.
|
||||
|
||||
- `chef_environment` (string) - The name of the `chef_environment` sent to
|
||||
the Chef server. By default this is empty and will not use an environment
|
||||
|
||||
- `chef_license` (string) - As of Chef v15, Chef requires users to accept a
|
||||
license. Defaults to `accept-silent` when `skip_install` is false and
|
||||
`install_command` is unset. Possible values are `accept`,
|
||||
`accept-silent` and `accept-no-persist`. For details see [Accepting the
|
||||
Chef License](https://docs.chef.io/chef_license_accept.html).
|
||||
|
||||
- `config_template` (string) - Path to a template that will be used for the
|
||||
Chef configuration file. By default Packer only sets configuration it needs
|
||||
to match the settings set in the provisioner configuration. If you need to
|
||||
set configurations that the Packer provisioner doesn't support, then you
|
||||
should use a custom configuration template. See the dedicated "Chef
|
||||
Configuration" section below for more details.
|
||||
|
||||
- `cookbook_paths` (array of strings) - This is an array of paths to
|
||||
"cookbooks" directories on your local filesystem. These will be uploaded to
|
||||
the remote machine in the directory specified by the `staging_directory`.
|
||||
By default, this is empty.
|
||||
|
||||
- `data_bags_path` (string) - The path to the "data_bags" directory on your
|
||||
local filesystem. These will be uploaded to the remote machine in the
|
||||
directory specified by the `staging_directory`. By default, this is empty.
|
||||
|
||||
- `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.
|
||||
|
||||
- `environments_path` (string) - The path to the "environments" directory on
|
||||
your local filesystem. These will be uploaded to the remote machine in the
|
||||
directory specified by the `staging_directory`. By default, this is empty.
|
||||
|
||||
- `execute_command` (string) - The command used to execute Chef. This has
|
||||
various [configuration template variables](/docs/templates/legacy_json_templates/engine)
|
||||
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/legacy_json_templates/engine)
|
||||
available. See below for more information.
|
||||
|
||||
- `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. 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
|
||||
provisioner or step. If specified, Chef will be configured to look for
|
||||
cookbooks here. By default, this is empty.
|
||||
|
||||
- `roles_path` (string) - The path to the "roles" directory on your local
|
||||
filesystem. These will be uploaded to the remote machine in the directory
|
||||
specified by the `staging_directory`. By default, this is empty.
|
||||
|
||||
- `run_list` (array of strings) - The [run
|
||||
list](https://docs.chef.io/run_lists.html) for Chef. By default this is
|
||||
empty.
|
||||
|
||||
- `skip_install` (boolean) - If true, Chef will not automatically be
|
||||
installed 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` 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.
|
||||
|
||||
- `version` (string) - The version of Chef to be installed. By default this
|
||||
is empty which will install the latest version of Chef.
|
||||
|
||||
@include 'provisioners/common-config.mdx'
|
||||
|
||||
##### Node Attribute Mapping
|
||||
|
||||
An arbitrary mapping of JSON that will be available as node attributes while running Chef.
|
||||
|
||||
<Tabs>
|
||||
<Tab heading="HCL2">
|
||||
|
||||
- `json_string` (string) - The JSON string can be encoded using the [jsonencode](/docs/templates/hcl_templates/functions/encoding/jsonencode)
|
||||
template function.
|
||||
|
||||
```hcl
|
||||
provisioner "chef-solo" {
|
||||
json_string = jsonencode({
|
||||
"a" = "b"
|
||||
"foo" = {
|
||||
"bar" = "val"
|
||||
"number" = 1
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab heading="JSON">
|
||||
|
||||
- `json` (object) - This option is only available to old-style JSON templates.
|
||||
|
||||
```json
|
||||
"provisioners":[{
|
||||
"type": "chef-solo",
|
||||
"json": {
|
||||
"a": "b",
|
||||
"foo": {
|
||||
"bar": "val",
|
||||
"number": 1
|
||||
}
|
||||
}
|
||||
}]
|
||||
```
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Chef Configuration
|
||||
|
||||
By default, Packer uses a simple Chef configuration file in order to set the
|
||||
options specified for the provisioner. But Chef is a complex tool that supports
|
||||
many configuration options. Packer allows you to specify a custom configuration
|
||||
template if you'd like to set custom configurations.
|
||||
|
||||
The default value for the configuration template is:
|
||||
|
||||
```liquid
|
||||
cookbook_path [{{.CookbookPaths}}]
|
||||
```
|
||||
|
||||
This template is a [configuration template](/docs/templates/legacy_json_templates/engine) and
|
||||
has a set of variables available to use:
|
||||
|
||||
- `ChefEnvironment` - The current enabled environment. Only non-empty if the
|
||||
environment path is set.
|
||||
- `ChefLicense` - The Chef license acceptance value.
|
||||
- `CookbookPaths` is the set of cookbook paths ready to embedded directly
|
||||
into a Ruby array to configure Chef.
|
||||
- `DataBagsPath` is the path to the data bags folder.
|
||||
- `EncryptedDataBagSecretPath` - The path to the encrypted data bag secret
|
||||
- `EnvironmentsPath` - The path to the environments folder.
|
||||
- `RolesPath` - The path to the roles folder.
|
||||
|
||||
## Execute Command
|
||||
|
||||
By default, Packer uses the following command (broken across multiple lines for
|
||||
readability) to execute Chef:
|
||||
|
||||
```liquid
|
||||
{{if .Sudo}}sudo {{end}}chef-solo \
|
||||
--no-color \
|
||||
-c {{.ConfigPath}} \
|
||||
-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 propagate 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:
|
||||
|
||||
- `ConfigPath` - The path to the Chef configuration file.
|
||||
- `JsonPath` - The path to the JSON attributes file for the node.
|
||||
- `Sudo` - A boolean of whether to `sudo` the command or not, depending on
|
||||
the value of the `prevent_sudo` configuration.
|
||||
|
||||
## Install Command
|
||||
|
||||
By default, Packer uses the following command (broken across multiple lines for
|
||||
readability) to install Chef. This command can be customized if you want to
|
||||
install Chef in another way.
|
||||
|
||||
```text
|
||||
curl -L https://omnitruck.chef.io/install.sh | \
|
||||
{{if .Sudo}}sudo{{end}} bash -s --{{if .Version}} -v {{.Version}}{{end}}
|
||||
```
|
||||
|
||||
When guest_os_type is set to "windows", Packer uses the following command to
|
||||
install the latest version of Chef:
|
||||
|
||||
```powershell
|
||||
powershell.exe -Command \". { iwr -useb https://omnitruck.chef.io/install.ps1 } | iex; install\"
|
||||
```
|
||||
|
||||
This command can be customized using the `install_command` configuration.
|
|
@ -828,14 +828,6 @@
|
|||
"title": "Breakpoint",
|
||||
"path": "provisioners/breakpoint"
|
||||
},
|
||||
{
|
||||
"title": "Chef Client",
|
||||
"path": "provisioners/chef-client"
|
||||
},
|
||||
{
|
||||
"title": "Chef Solo",
|
||||
"path": "provisioners/chef-solo"
|
||||
},
|
||||
{
|
||||
"title": "Converge",
|
||||
"path": "provisioners/converge"
|
||||
|
|
Loading…
Reference in New Issue