mirror of https://github.com/hashicorp/packer
move salt-masterless provisioner out (#11229)
* move salt-masterless provisioner out * go get github.com/hashicorp/packer-plugin-saltpull/11238/head
parent
eb39a3120e
commit
acb01cb800
@ -1,606 +0,0 @@
|
||||
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
|
||||
|
||||
// This package implements a provisioner for Packer that executes a
|
||||
// saltstack state within the remote machine
|
||||
package saltmasterless
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-getter/v2"
|
||||
"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 Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
// If true, run the salt-bootstrap script
|
||||
SkipBootstrap bool `mapstructure:"skip_bootstrap"`
|
||||
BootstrapArgs string `mapstructure:"bootstrap_args"`
|
||||
|
||||
DisableSudo bool `mapstructure:"disable_sudo"`
|
||||
|
||||
// Custom state to run instead of highstate
|
||||
CustomState string `mapstructure:"custom_state"`
|
||||
|
||||
// Local path to the minion config
|
||||
MinionConfig string `mapstructure:"minion_config"`
|
||||
|
||||
// Local path to the minion grains
|
||||
GrainsFile string `mapstructure:"grains_file"`
|
||||
|
||||
// Local path to the salt state tree
|
||||
LocalStateTree string `mapstructure:"local_state_tree"`
|
||||
|
||||
// Local path to the salt pillar roots
|
||||
LocalPillarRoots string `mapstructure:"local_pillar_roots"`
|
||||
|
||||
// Remote path to the salt state tree
|
||||
RemoteStateTree string `mapstructure:"remote_state_tree"`
|
||||
|
||||
// Remote path to the salt pillar roots
|
||||
RemotePillarRoots string `mapstructure:"remote_pillar_roots"`
|
||||
|
||||
// Where files will be copied before moving to the /srv/salt directory
|
||||
TempConfigDir string `mapstructure:"temp_config_dir"`
|
||||
|
||||
// Don't exit packer if salt-call returns an error code
|
||||
NoExitOnFailure bool `mapstructure:"no_exit_on_failure"`
|
||||
|
||||
// Set the logging level for the salt-call run
|
||||
LogLevel string `mapstructure:"log_level"`
|
||||
|
||||
// Arguments to pass to salt-call
|
||||
SaltCallArgs string `mapstructure:"salt_call_args"`
|
||||
|
||||
// Directory containing salt-call
|
||||
SaltBinDir string `mapstructure:"salt_bin_dir"`
|
||||
|
||||
// Command line args passed onto salt-call
|
||||
CmdArgs string `mapstructure-to-hcl2:",skip"`
|
||||
|
||||
// The Guest OS Type (unix or windows)
|
||||
GuestOSType string `mapstructure:"guest_os_type"`
|
||||
|
||||
// An array of private or community git source formulas
|
||||
Formulas []string `mapstructure:"formulas"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
type Provisioner struct {
|
||||
config Config
|
||||
guestOSTypeConfig guestOSTypeConfig
|
||||
guestCommands *guestexec.GuestCommands
|
||||
}
|
||||
|
||||
type guestOSTypeConfig struct {
|
||||
tempDir string
|
||||
stateRoot string
|
||||
pillarRoot string
|
||||
configDir string
|
||||
bootstrapFetchCmd string
|
||||
bootstrapRunCmd string
|
||||
}
|
||||
|
||||
var guestOSTypeConfigs = map[string]guestOSTypeConfig{
|
||||
guestexec.UnixOSType: {
|
||||
configDir: "/etc/salt",
|
||||
tempDir: "/tmp/salt",
|
||||
stateRoot: "/srv/salt",
|
||||
pillarRoot: "/srv/pillar",
|
||||
bootstrapFetchCmd: "curl -L https://bootstrap.saltproject.io -o /tmp/install_salt.sh || wget -O /tmp/install_salt.sh https://bootstrap.saltproject.io",
|
||||
bootstrapRunCmd: "sh /tmp/install_salt.sh",
|
||||
},
|
||||
guestexec.WindowsOSType: {
|
||||
configDir: "C:/salt/conf",
|
||||
tempDir: "C:/Windows/Temp/salt/",
|
||||
stateRoot: "C:/salt/state",
|
||||
pillarRoot: "C:/salt/pillar/",
|
||||
bootstrapFetchCmd: "powershell -Command \"[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]'Tls,Tls11,Tls12'; Invoke-WebRequest -Uri 'https://winbootstrap.saltproject.io' -OutFile 'C:/Windows/Temp/bootstrap-salt.ps1'\"",
|
||||
bootstrapRunCmd: "Powershell C:/Windows/Temp/bootstrap-salt.ps1",
|
||||
},
|
||||
}
|
||||
|
||||
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: "salt-masterless",
|
||||
Interpolate: true,
|
||||
InterpolateContext: &p.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.config.GuestOSType == "" {
|
||||
p.config.GuestOSType = guestexec.DefaultOSType
|
||||
} else {
|
||||
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.DisableSudo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Invalid guest_os_type: \"%s\"", p.config.GuestOSType)
|
||||
}
|
||||
|
||||
if p.config.TempConfigDir == "" {
|
||||
p.config.TempConfigDir = p.guestOSTypeConfig.tempDir
|
||||
}
|
||||
|
||||
var errs *packersdk.MultiError
|
||||
|
||||
// require a salt state tree
|
||||
err = validateDirConfig(p.config.LocalStateTree, "local_state_tree", true)
|
||||
if err != nil {
|
||||
errs = packersdk.MultiErrorAppend(errs, err)
|
||||
}
|
||||
|
||||
if p.config.Formulas != nil && len(p.config.Formulas) > 0 {
|
||||
|
||||
validURLs := hasValidFormulaURLs(p.config.Formulas)
|
||||
if !validURLs {
|
||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("Invalid formula URL. Please verify the git URLs also contain a '//' subdir"))
|
||||
}
|
||||
}
|
||||
|
||||
err = validateDirConfig(p.config.LocalPillarRoots, "local_pillar_roots", false)
|
||||
if err != nil {
|
||||
errs = packersdk.MultiErrorAppend(errs, err)
|
||||
}
|
||||
|
||||
err = validateFileConfig(p.config.MinionConfig, "minion_config", false)
|
||||
if err != nil {
|
||||
errs = packersdk.MultiErrorAppend(errs, err)
|
||||
}
|
||||
|
||||
if p.config.MinionConfig != "" && (p.config.RemoteStateTree != "" || p.config.RemotePillarRoots != "") {
|
||||
errs = packersdk.MultiErrorAppend(errs,
|
||||
errors.New("remote_state_tree and remote_pillar_roots only apply when minion_config is not used"))
|
||||
}
|
||||
|
||||
err = validateFileConfig(p.config.GrainsFile, "grains_file", false)
|
||||
if err != nil {
|
||||
errs = packersdk.MultiErrorAppend(errs, err)
|
||||
}
|
||||
|
||||
// build the command line args to pass onto salt
|
||||
var cmd_args bytes.Buffer
|
||||
|
||||
if p.config.CustomState == "" {
|
||||
cmd_args.WriteString(" state.highstate")
|
||||
} else {
|
||||
cmd_args.WriteString(" state.sls ")
|
||||
cmd_args.WriteString(p.config.CustomState)
|
||||
}
|
||||
|
||||
if p.config.MinionConfig == "" {
|
||||
// pass --file-root and --pillar-root if no minion_config is supplied
|
||||
if p.config.RemoteStateTree != "" {
|
||||
cmd_args.WriteString(" --file-root=")
|
||||
cmd_args.WriteString(p.config.RemoteStateTree)
|
||||
} else {
|
||||
cmd_args.WriteString(" --file-root=")
|
||||
cmd_args.WriteString(p.guestOSTypeConfig.stateRoot)
|
||||
}
|
||||
if p.config.RemotePillarRoots != "" {
|
||||
cmd_args.WriteString(" --pillar-root=")
|
||||
cmd_args.WriteString(p.config.RemotePillarRoots)
|
||||
} else {
|
||||
cmd_args.WriteString(" --pillar-root=")
|
||||
cmd_args.WriteString(p.guestOSTypeConfig.pillarRoot)
|
||||
}
|
||||
}
|
||||
|
||||
if !p.config.NoExitOnFailure {
|
||||
cmd_args.WriteString(" --retcode-passthrough")
|
||||
}
|
||||
|
||||
if p.config.LogLevel == "" {
|
||||
cmd_args.WriteString(" -l info")
|
||||
} else {
|
||||
cmd_args.WriteString(" -l ")
|
||||
cmd_args.WriteString(p.config.LogLevel)
|
||||
}
|
||||
|
||||
if p.config.SaltCallArgs != "" {
|
||||
cmd_args.WriteString(" ")
|
||||
cmd_args.WriteString(p.config.SaltCallArgs)
|
||||
}
|
||||
|
||||
p.config.CmdArgs = cmd_args.String()
|
||||
|
||||
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 {
|
||||
var err error
|
||||
var src, dst string
|
||||
var formulas []string
|
||||
|
||||
if p.config.Formulas != nil && len(p.config.Formulas) > 0 {
|
||||
ui.Say("Downloading Salt formulas...")
|
||||
client := new(getter.Client)
|
||||
for _, i := range p.config.Formulas {
|
||||
req := getter.Request{
|
||||
Src: i,
|
||||
}
|
||||
// Use //subdirectory name when creating in local_state_tree directory
|
||||
state := strings.Split(i, "//")
|
||||
last := state[len(state)-1]
|
||||
path := filepath.Join(p.config.LocalStateTree, last)
|
||||
formulas = append(formulas, path)
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
ui.Message(fmt.Sprintf("%s => %s", i, path))
|
||||
if err = os.Mkdir(path, 0755); err != nil {
|
||||
return fmt.Errorf("Unable to create Salt state directory: %s", err)
|
||||
}
|
||||
req.Dst = path
|
||||
req.GetMode = getter.ModeAny
|
||||
if _, err := client.Get(ctx, &req); err != nil {
|
||||
return fmt.Errorf("Unable to download Salt formula from %s: %s", i, err)
|
||||
}
|
||||
} else {
|
||||
ui.Message(fmt.Sprintf("Found existing formula at: %s", path))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ui.Say("Provisioning with Salt...")
|
||||
if !p.config.SkipBootstrap {
|
||||
cmd := &packersdk.RemoteCmd{
|
||||
// Fallback on wget if curl failed for any reason (such as not being installed)
|
||||
Command: fmt.Sprintf(p.guestOSTypeConfig.bootstrapFetchCmd),
|
||||
}
|
||||
ui.Message(fmt.Sprintf("Downloading saltstack bootstrap to /tmp/install_salt.sh"))
|
||||
if err = cmd.RunWithUi(ctx, comm, ui); err != nil {
|
||||
return fmt.Errorf("Unable to download Salt: %s", err)
|
||||
}
|
||||
cmd = &packersdk.RemoteCmd{
|
||||
Command: fmt.Sprintf("%s %s", p.sudo(p.guestOSTypeConfig.bootstrapRunCmd), p.config.BootstrapArgs),
|
||||
}
|
||||
ui.Message(fmt.Sprintf("Installing Salt with command %s", cmd.Command))
|
||||
if err = cmd.RunWithUi(ctx, comm, ui); err != nil {
|
||||
return fmt.Errorf("Unable to install Salt: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Creating remote temporary directory: %s", p.config.TempConfigDir))
|
||||
if err := p.createDir(ui, comm, p.config.TempConfigDir); err != nil {
|
||||
return fmt.Errorf("Error creating remote temporary directory: %s", err)
|
||||
}
|
||||
|
||||
if p.config.MinionConfig != "" {
|
||||
ui.Message(fmt.Sprintf("Uploading minion config: %s", p.config.MinionConfig))
|
||||
src = p.config.MinionConfig
|
||||
dst = filepath.ToSlash(filepath.Join(p.config.TempConfigDir, "minion"))
|
||||
if err = p.uploadFile(ui, comm, dst, src); err != nil {
|
||||
return fmt.Errorf("Error uploading local minion config file to remote: %s", err)
|
||||
}
|
||||
|
||||
// move minion config into /etc/salt
|
||||
ui.Message(fmt.Sprintf("Make sure directory %s exists", p.guestOSTypeConfig.configDir))
|
||||
if err := p.createDir(ui, comm, p.guestOSTypeConfig.configDir); err != nil {
|
||||
return fmt.Errorf("Error creating remote salt configuration directory: %s", err)
|
||||
}
|
||||
src = filepath.ToSlash(filepath.Join(p.config.TempConfigDir, "minion"))
|
||||
dst = filepath.ToSlash(filepath.Join(p.guestOSTypeConfig.configDir, "minion"))
|
||||
if err = p.moveFile(ui, comm, dst, src); err != nil {
|
||||
return fmt.Errorf("Unable to move %s/minion to %s/minion: %s", p.config.TempConfigDir, p.guestOSTypeConfig.configDir, err)
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.GrainsFile != "" {
|
||||
ui.Message(fmt.Sprintf("Uploading grains file: %s", p.config.GrainsFile))
|
||||
src = p.config.GrainsFile
|
||||
dst = filepath.ToSlash(filepath.Join(p.config.TempConfigDir, "grains"))
|
||||
if err = p.uploadFile(ui, comm, dst, src); err != nil {
|
||||
return fmt.Errorf("Error uploading local grains file to remote: %s", err)
|
||||
}
|
||||
|
||||
// move grains file into /etc/salt
|
||||
ui.Message(fmt.Sprintf("Make sure directory %s exists", p.guestOSTypeConfig.configDir))
|
||||
if err := p.createDir(ui, comm, p.guestOSTypeConfig.configDir); err != nil {
|
||||
return fmt.Errorf("Error creating remote salt configuration directory: %s", err)
|
||||
}
|
||||
src = filepath.ToSlash(filepath.Join(p.config.TempConfigDir, "grains"))
|
||||
dst = filepath.ToSlash(filepath.Join(p.guestOSTypeConfig.configDir, "grains"))
|
||||
if err = p.moveFile(ui, comm, dst, src); err != nil {
|
||||
return fmt.Errorf("Unable to move %s/grains to %s/grains: %s", p.config.TempConfigDir, p.guestOSTypeConfig.configDir, err)
|
||||
}
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Uploading local state tree: %s", p.config.LocalStateTree))
|
||||
src = p.config.LocalStateTree
|
||||
dst = filepath.ToSlash(filepath.Join(p.config.TempConfigDir, "states"))
|
||||
if err = p.uploadDir(ui, comm, dst, src, []string{".git"}); err != nil {
|
||||
return fmt.Errorf("Error uploading local state tree to remote: %s", err)
|
||||
}
|
||||
|
||||
// move state tree from temporary directory
|
||||
src = filepath.ToSlash(filepath.Join(p.config.TempConfigDir, "states"))
|
||||
if p.config.RemoteStateTree != "" {
|
||||
dst = p.config.RemoteStateTree
|
||||
} else {
|
||||
dst = p.guestOSTypeConfig.stateRoot
|
||||
}
|
||||
|
||||
if err = p.statPath(ui, comm, dst); err != nil {
|
||||
if err = p.removeDir(ui, comm, dst); err != nil {
|
||||
return fmt.Errorf("Unable to clear salt tree: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = p.moveFile(ui, comm, dst, src); err != nil {
|
||||
return fmt.Errorf("Unable to move %s/states to %s: %s", p.config.TempConfigDir, dst, err)
|
||||
}
|
||||
|
||||
// Remove the local Salt formulas if present
|
||||
if p.config.Formulas != nil {
|
||||
for _, f := range formulas {
|
||||
if _, err := os.Stat(f); !os.IsNotExist(err) && f != p.config.LocalStateTree {
|
||||
ui.Message(fmt.Sprintf("Removing Salt formula: %s", f))
|
||||
defer os.RemoveAll(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.LocalPillarRoots != "" {
|
||||
ui.Message(fmt.Sprintf("Uploading local pillar roots: %s", p.config.LocalPillarRoots))
|
||||
src = p.config.LocalPillarRoots
|
||||
dst = filepath.ToSlash(filepath.Join(p.config.TempConfigDir, "pillar"))
|
||||
if err = p.uploadDir(ui, comm, dst, src, []string{".git"}); err != nil {
|
||||
return fmt.Errorf("Error uploading local pillar roots to remote: %s", err)
|
||||
}
|
||||
|
||||
// move pillar root from temporary directory
|
||||
src = filepath.ToSlash(filepath.Join(p.config.TempConfigDir, "pillar"))
|
||||
if p.config.RemotePillarRoots != "" {
|
||||
dst = p.config.RemotePillarRoots
|
||||
} else {
|
||||
dst = p.guestOSTypeConfig.pillarRoot
|
||||
}
|
||||
|
||||
if err = p.statPath(ui, comm, dst); err != nil {
|
||||
if err = p.removeDir(ui, comm, dst); err != nil {
|
||||
return fmt.Errorf("Unable to clear pillar root: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = p.moveFile(ui, comm, dst, src); err != nil {
|
||||
return fmt.Errorf("Unable to move %s/pillar to %s: %s", p.config.TempConfigDir, dst, err)
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.GuestOSType == guestexec.WindowsOSType {
|
||||
ui.Message("Downloading Git for Windows")
|
||||
cmd1 := &packersdk.RemoteCmd{Command: fmt.Sprintf("powershell [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri https://github.com/git-for-windows/git/releases/download/v2.28.0.windows.1/Git-2.28.0-64-bit.exe -OutFile $env:TEMP/Git.exe")}
|
||||
if err = cmd1.RunWithUi(ctx, comm, ui); (err != nil || cmd1.ExitStatus() != 0) && !p.config.NoExitOnFailure {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("Bad exit status: %d", cmd1.ExitStatus())
|
||||
}
|
||||
|
||||
return fmt.Errorf("Unable to Download Git for Windows: %s", err)
|
||||
}
|
||||
|
||||
ui.Message("Installing Git for Windows")
|
||||
cmd2 := &packersdk.RemoteCmd{Command: fmt.Sprintf("powershell Start-Process -FilePath $env:TEMP/Git.exe /SILENT -Wait")}
|
||||
if err = cmd2.RunWithUi(ctx, comm, ui); (err != nil || cmd2.ExitStatus() != 0) && !p.config.NoExitOnFailure {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("Bad exit status: %d", cmd2.ExitStatus())
|
||||
}
|
||||
|
||||
return fmt.Errorf("Unable to Install Git for Windows: %s", err)
|
||||
}
|
||||
|
||||
ui.Message("Cleaning Up After Git for Windows")
|
||||
cmd3 := &packersdk.RemoteCmd{Command: fmt.Sprintf("powershell Remove-Item $env:TEMP/Git.exe")}
|
||||
if err = cmd3.RunWithUi(ctx, comm, ui); (err != nil || cmd3.ExitStatus() != 0) && !p.config.NoExitOnFailure {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("Bad exit status: %d", cmd3.ExitStatus())
|
||||
}
|
||||
|
||||
return fmt.Errorf("Unable to Clean-up After Git for Windows: %s", err)
|
||||
}
|
||||
|
||||
ui.Message("Running salt-call --local winrepo.update_git_repos")
|
||||
cmd4 := &packersdk.RemoteCmd{Command: fmt.Sprintf("%s --local winrepo.update_git_repos", filepath.Join(p.config.SaltBinDir, "salt-call"))}
|
||||
if err = cmd4.RunWithUi(ctx, comm, ui); (err != nil || cmd4.ExitStatus() != 0) && !p.config.NoExitOnFailure {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("Bad exit status: %d", cmd4.ExitStatus())
|
||||
}
|
||||
|
||||
return fmt.Errorf("Error executing salt-call --local winrepo.update_git_repos: %s", err)
|
||||
}
|
||||
|
||||
ui.Message("Running salt-call --local pkg.refresh_db")
|
||||
cmd5 := &packersdk.RemoteCmd{Command: fmt.Sprintf("%s --local pkg.refresh_db", filepath.Join(p.config.SaltBinDir, "salt-call"))}
|
||||
if err = cmd5.RunWithUi(ctx, comm, ui); (err != nil || cmd5.ExitStatus() != 0) && !p.config.NoExitOnFailure {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("Bad exit status: %d", cmd5.ExitStatus())
|
||||
}
|
||||
|
||||
return fmt.Errorf("Error executing salt-call --local pkg.refresh_db: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Running: salt-call --local %s", p.config.CmdArgs))
|
||||
cmd := &packersdk.RemoteCmd{Command: p.sudo(fmt.Sprintf("%s --local %s", filepath.Join(p.config.SaltBinDir, "salt-call"), p.config.CmdArgs))}
|
||||
if err = cmd.RunWithUi(ctx, comm, ui); (err != nil || cmd.ExitStatus() != 0) && !p.config.NoExitOnFailure {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus())
|
||||
}
|
||||
|
||||
return fmt.Errorf("Error executing salt-call: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Prepends sudo to supplied command if config says to
|
||||
func (p *Provisioner) sudo(cmd string) string {
|
||||
if p.config.DisableSudo || (p.config.GuestOSType == guestexec.WindowsOSType) {
|
||||
return cmd
|
||||
}
|
||||
|
||||
return "sudo " + cmd
|
||||
}
|
||||
|
||||
func validateDirConfig(path string, name string, required bool) error {
|
||||
if required && path == "" {
|
||||
return fmt.Errorf("%s cannot be empty", name)
|
||||
} else if required == false && path == "" {
|
||||
return nil
|
||||
}
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: path '%s' is invalid: %s", name, path, err)
|
||||
} else if !info.IsDir() {
|
||||
return fmt.Errorf("%s: path '%s' must point to a directory", name, path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateFileConfig(path string, name string, required bool) error {
|
||||
if required == true && path == "" {
|
||||
return fmt.Errorf("%s cannot be empty", name)
|
||||
} else if required == false && path == "" {
|
||||
return nil
|
||||
}
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: path '%s' is invalid: %s", name, path, err)
|
||||
} else if info.IsDir() {
|
||||
return fmt.Errorf("%s: path '%s' must point to a file", name, path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasValidFormulaURLs(s []string) bool {
|
||||
re := regexp.MustCompile(`^(.*).git\/\/[a-zA-Z0-9-_]+(\?.*)?$`)
|
||||
|
||||
for _, u := range s {
|
||||
if !re.MatchString(u) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *Provisioner) uploadFile(ui packersdk.Ui, comm packersdk.Communicator, dst, src string) error {
|
||||
f, err := os.Open(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error opening: %s", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, temp_dst := filepath.Split(dst)
|
||||
|
||||
if err = comm.Upload(temp_dst, f, nil); err != nil {
|
||||
return fmt.Errorf("Error uploading %s: %s", src, err)
|
||||
}
|
||||
|
||||
err = p.moveFile(ui, comm, dst, temp_dst)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error moving file to destination: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) moveFile(ui packersdk.Ui, comm packersdk.Communicator, dst string, src string) error {
|
||||
ctx := context.TODO()
|
||||
|
||||
ui.Message(fmt.Sprintf("Moving %s to %s", src, dst))
|
||||
cmd := &packersdk.RemoteCmd{
|
||||
Command: p.sudo(p.guestCommands.MovePath(src, dst)),
|
||||
}
|
||||
if err := cmd.RunWithUi(ctx, comm, ui); err != nil || cmd.ExitStatus() != 0 {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus())
|
||||
}
|
||||
|
||||
return fmt.Errorf("Unable to move %s to %s: %s", src, dst, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) createDir(ui packersdk.Ui, comm packersdk.Communicator, dir string) error {
|
||||
ui.Message(fmt.Sprintf("Creating directory: %s", dir))
|
||||
cmd := &packersdk.RemoteCmd{
|
||||
Command: p.guestCommands.CreateDir(dir),
|
||||
}
|
||||
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.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) statPath(ui packersdk.Ui, comm packersdk.Communicator, path string) error {
|
||||
ctx := context.TODO()
|
||||
ui.Message(fmt.Sprintf("Verifying Path: %s", path))
|
||||
cmd := &packersdk.RemoteCmd{
|
||||
Command: p.guestCommands.StatPath(path),
|
||||
}
|
||||
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
|
||||
return err
|
||||
}
|
||||
if cmd.ExitStatus() != 0 {
|
||||
return fmt.Errorf("Non-zero exit status.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) removeDir(ui packersdk.Ui, comm packersdk.Communicator, dir string) error {
|
||||
ctx := context.TODO()
|
||||
ui.Message(fmt.Sprintf("Removing directory: %s", dir))
|
||||
cmd := &packersdk.RemoteCmd{
|
||||
Command: p.guestCommands.RemoveDir(dir),
|
||||
}
|
||||
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
|
||||
return err
|
||||
}
|
||||
if cmd.ExitStatus() != 0 {
|
||||
return fmt.Errorf("Non-zero exit status.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) uploadDir(ui packersdk.Ui, comm packersdk.Communicator, dst, src string, ignore []string) error {
|
||||
_, temp_dst := filepath.Split(dst)
|
||||
if err := comm.UploadDir(temp_dst, src, ignore); err != nil {
|
||||
return err
|
||||
}
|
||||
return p.moveFile(ui, comm, dst, temp_dst)
|
||||
}
|
||||
@ -1,79 +0,0 @@
|
||||
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
|
||||
|
||||
package saltmasterless
|
||||
|
||||
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"`
|
||||
SkipBootstrap *bool `mapstructure:"skip_bootstrap" cty:"skip_bootstrap" hcl:"skip_bootstrap"`
|
||||
BootstrapArgs *string `mapstructure:"bootstrap_args" cty:"bootstrap_args" hcl:"bootstrap_args"`
|
||||
DisableSudo *bool `mapstructure:"disable_sudo" cty:"disable_sudo" hcl:"disable_sudo"`
|
||||
CustomState *string `mapstructure:"custom_state" cty:"custom_state" hcl:"custom_state"`
|
||||
MinionConfig *string `mapstructure:"minion_config" cty:"minion_config" hcl:"minion_config"`
|
||||
GrainsFile *string `mapstructure:"grains_file" cty:"grains_file" hcl:"grains_file"`
|
||||
LocalStateTree *string `mapstructure:"local_state_tree" cty:"local_state_tree" hcl:"local_state_tree"`
|
||||
LocalPillarRoots *string `mapstructure:"local_pillar_roots" cty:"local_pillar_roots" hcl:"local_pillar_roots"`
|
||||
RemoteStateTree *string `mapstructure:"remote_state_tree" cty:"remote_state_tree" hcl:"remote_state_tree"`
|
||||
RemotePillarRoots *string `mapstructure:"remote_pillar_roots" cty:"remote_pillar_roots" hcl:"remote_pillar_roots"`
|
||||
TempConfigDir *string `mapstructure:"temp_config_dir" cty:"temp_config_dir" hcl:"temp_config_dir"`
|
||||
NoExitOnFailure *bool `mapstructure:"no_exit_on_failure" cty:"no_exit_on_failure" hcl:"no_exit_on_failure"`
|
||||
LogLevel *string `mapstructure:"log_level" cty:"log_level" hcl:"log_level"`
|
||||
SaltCallArgs *string `mapstructure:"salt_call_args" cty:"salt_call_args" hcl:"salt_call_args"`
|
||||
SaltBinDir *string `mapstructure:"salt_bin_dir" cty:"salt_bin_dir" hcl:"salt_bin_dir"`
|
||||
GuestOSType *string `mapstructure:"guest_os_type" cty:"guest_os_type" hcl:"guest_os_type"`
|
||||
Formulas []string `mapstructure:"formulas" cty:"formulas" hcl:"formulas"`
|
||||
}
|
||||
|
||||
// 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},
|
||||
"skip_bootstrap": &hcldec.AttrSpec{Name: "skip_bootstrap", Type: cty.Bool, Required: false},
|
||||
"bootstrap_args": &hcldec.AttrSpec{Name: "bootstrap_args", Type: cty.String, Required: false},
|
||||
"disable_sudo": &hcldec.AttrSpec{Name: "disable_sudo", Type: cty.Bool, Required: false},
|
||||
"custom_state": &hcldec.AttrSpec{Name: "custom_state", Type: cty.String, Required: false},
|
||||
"minion_config": &hcldec.AttrSpec{Name: "minion_config", Type: cty.String, Required: false},
|
||||
"grains_file": &hcldec.AttrSpec{Name: "grains_file", Type: cty.String, Required: false},
|
||||
"local_state_tree": &hcldec.AttrSpec{Name: "local_state_tree", Type: cty.String, Required: false},
|
||||
"local_pillar_roots": &hcldec.AttrSpec{Name: "local_pillar_roots", Type: cty.String, Required: false},
|
||||
"remote_state_tree": &hcldec.AttrSpec{Name: "remote_state_tree", Type: cty.String, Required: false},
|
||||
"remote_pillar_roots": &hcldec.AttrSpec{Name: "remote_pillar_roots", Type: cty.String, Required: false},
|
||||
"temp_config_dir": &hcldec.AttrSpec{Name: "temp_config_dir", Type: cty.String, Required: false},
|
||||
"no_exit_on_failure": &hcldec.AttrSpec{Name: "no_exit_on_failure", Type: cty.Bool, Required: false},
|
||||
"log_level": &hcldec.AttrSpec{Name: "log_level", Type: cty.String, Required: false},
|
||||
"salt_call_args": &hcldec.AttrSpec{Name: "salt_call_args", Type: cty.String, Required: false},
|
||||
"salt_bin_dir": &hcldec.AttrSpec{Name: "salt_bin_dir", Type: cty.String, Required: false},
|
||||
"guest_os_type": &hcldec.AttrSpec{Name: "guest_os_type", Type: cty.String, Required: false},
|
||||
"formulas": &hcldec.AttrSpec{Name: "formulas", Type: cty.List(cty.String), Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
@ -1,358 +0,0 @@
|
||||
package saltmasterless
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"local_state_tree": os.TempDir(),
|
||||
}
|
||||
}
|
||||
|
||||
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_Defaults(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if p.config.TempConfigDir != p.guestOSTypeConfig.tempDir {
|
||||
t.Errorf("unexpected temp config dir: %s", p.config.TempConfigDir)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_InvalidKey(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
// Add a random key
|
||||
config["i_should_not_be_valid"] = true
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_CustomState(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(p.config.CmdArgs, "state.") {
|
||||
t.Fatal("a state should be specified in CmdArgs")
|
||||
}
|
||||
|
||||
config["custom_state"] = "birb"
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(p.config.CmdArgs, "state.sls birb") {
|
||||
t.Fatal("birb state should be specified in CmdArgs")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_MinionConfig(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
config["minion_config"] = "/i/dont/exist/i/think"
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
tf, err := ioutil.TempFile("", "minion")
|
||||
if err != nil {
|
||||
t.Fatalf("error tempfile: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
config["minion_config"] = tf.Name()
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_MinionConfig_RemoteStateTree(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
config["minion_config"] = "/i/dont/exist/i/think"
|
||||
config["remote_state_tree"] = "/i/dont/exist/remote_state_tree"
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("minion_config and remote_state_tree should cause error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_MinionConfig_RemotePillarRoots(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
config["minion_config"] = "/i/dont/exist/i/think"
|
||||
config["remote_pillar_roots"] = "/i/dont/exist/remote_pillar_roots"
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("minion_config and remote_pillar_roots should cause error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_GrainsFile(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
config["grains_file"] = "/i/dont/exist/i/think"
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
tf, err := ioutil.TempFile("", "grains")
|
||||
if err != nil {
|
||||
t.Fatalf("error tempfile: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
config["grains_file"] = tf.Name()
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_LocalStateTree(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
config["local_state_tree"] = "/i/dont/exist/i/think"
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
config["local_state_tree"] = os.TempDir()
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_LocalPillarRoots(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
config["local_pillar_roots"] = "/i/dont/exist/i/think"
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
config["local_pillar_roots"] = os.TempDir()
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerSudo(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
withSudo := p.sudo("echo hello")
|
||||
if withSudo != "sudo echo hello" {
|
||||
t.Fatalf("sudo command not generated correctly")
|
||||
}
|
||||
|
||||
config["disable_sudo"] = true
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
withoutSudo := p.sudo("echo hello")
|
||||
if withoutSudo != "echo hello" {
|
||||
t.Fatalf("sudo-less command not generated correctly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_RemoteStateTree(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
config["remote_state_tree"] = "/remote_state_tree"
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(p.config.CmdArgs, "--file-root=/remote_state_tree") {
|
||||
t.Fatal("--file-root should be set in CmdArgs")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_RemotePillarRoots(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
config["remote_pillar_roots"] = "/remote_pillar_roots"
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(p.config.CmdArgs, "--pillar-root=/remote_pillar_roots") {
|
||||
t.Fatal("--pillar-root should be set in CmdArgs")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_RemoteStateTree_Default(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
// no minion_config, no remote_state_tree
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(p.config.CmdArgs, "--file-root=/srv/salt") {
|
||||
t.Fatal("--file-root should be set in CmdArgs")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_RemotePillarRoots_Default(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
// no minion_config, no remote_pillar_roots
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(p.config.CmdArgs, "--pillar-root=/srv/pillar") {
|
||||
t.Fatal("--pillar-root should be set in CmdArgs")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_NoExitOnFailure(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(p.config.CmdArgs, "--retcode-passthrough") {
|
||||
t.Fatal("--retcode-passthrough should be set in CmdArgs")
|
||||
}
|
||||
|
||||
config["no_exit_on_failure"] = true
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if strings.Contains(p.config.CmdArgs, "--retcode-passthrough") {
|
||||
t.Fatal("--retcode-passthrough should not be set in CmdArgs")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_LogLevel(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(p.config.CmdArgs, "-l info") {
|
||||
t.Fatal("-l info should be set in CmdArgs")
|
||||
}
|
||||
|
||||
config["log_level"] = "debug"
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(p.config.CmdArgs, "-l debug") {
|
||||
t.Fatal("-l debug should be set in CmdArgs")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_GuestOSType(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
config["guest_os_type"] = "Windows"
|
||||
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if p.config.GuestOSType != "windows" {
|
||||
t.Fatalf("GuestOSType should be 'windows'")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_BadFormulaURL(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
config["formulas"] = []string{
|
||||
"git::https://github.com/org/some-formula.git//",
|
||||
}
|
||||
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected invalid formula URL: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_ValidFormulaURLs(t *testing.T) {
|
||||
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
config["formulas"] = []string{
|
||||
"git::https://github.com/org/some-formula.git//example",
|
||||
"git@github.com:org/some-formula.git//example",
|
||||
"git::https://github.com/org/some-formula.git//example?ref=example",
|
||||
}
|
||||
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error in formula URLs: %s", err)
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/packer-plugin-sdk/version"
|
||||
packerVersion "github.com/hashicorp/packer/version"
|
||||
)
|
||||
|
||||
var SaltPluginVersion *version.PluginVersion
|
||||
|
||||
func init() {
|
||||
SaltPluginVersion = version.InitializePluginVersion(
|
||||
packerVersion.Version, packerVersion.VersionPrerelease)
|
||||
}
|
||||
@ -1,121 +0,0 @@
|
||||
---
|
||||
description: |
|
||||
The salt-masterless Packer provisioner provisions machines built by Packer
|
||||
using Salt states, without connecting to a Salt master.
|
||||
page_title: Salt Masterless - Provisioners
|
||||
---
|
||||
|
||||
# Salt Masterless Provisioner
|
||||
|
||||
@include 'provisioners/unmaintained-plugin.mdx'
|
||||
|
||||
Type: `salt-masterless`
|
||||
|
||||
The `salt-masterless` Packer provisioner provisions machines built by Packer
|
||||
using [Salt](https://saltproject.io/) states, without connecting to a Salt
|
||||
master.
|
||||
|
||||
## Basic Example
|
||||
|
||||
The example below is fully functional.
|
||||
|
||||
<Tabs>
|
||||
<Tab heading="HCL2">
|
||||
|
||||
```hcl
|
||||
provisioner "salt-masterless" {
|
||||
local_state_tree = "/Users/me/salt"
|
||||
}
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab heading="JSON">
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "salt-masterless",
|
||||
"local_state_tree": "/Users/me/salt"
|
||||
}
|
||||
```
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
The reference of available configuration options is listed below. The only
|
||||
required element is "local_state_tree".
|
||||
|
||||
Required:
|
||||
|
||||
- `local_state_tree` (string) - The path to your local [state
|
||||
tree](http://docs.saltproject.io/en/latest/ref/states/highstate.html#the-salt-state-tree).
|
||||
This will be uploaded to the `remote_state_tree` on the remote.
|
||||
|
||||
Optional:
|
||||
|
||||
- `bootstrap_args` (string) - Arguments to send to the bootstrap script.
|
||||
Usage is somewhat documented on
|
||||
[github](https://github.com/saltstack/salt-bootstrap), but the [script
|
||||
itself](https://github.com/saltstack/salt-bootstrap/blob/develop/bootstrap-salt.sh)
|
||||
has more detailed usage instructions. By default, no arguments are sent to
|
||||
the script.
|
||||
|
||||
- `disable_sudo` (boolean) - By default, the bootstrap install command is
|
||||
prefixed with `sudo`. When using a Docker builder, you will likely want to
|
||||
pass `true` since `sudo` is often not pre-installed.
|
||||
|
||||
- `remote_pillar_roots` (string) - The path to your remote [pillar
|
||||
roots](http://docs.saltproject.io/en/latest/ref/configuration/master.html#pillar-configuration).
|
||||
default: `/srv/pillar`. This option cannot be used with `minion_config`.
|
||||
|
||||
- `remote_state_tree` (string) - The path to your remote [state
|
||||
tree](http://docs.saltproject.io/en/latest/ref/states/highstate.html#the-salt-state-tree).
|
||||
default: `/srv/salt`. This option cannot be used with `minion_config`.
|
||||
|
||||
- `local_pillar_roots` (string) - The path to your local [pillar
|
||||
roots](http://docs.saltproject.io/en/latest/ref/configuration/master.html#pillar-configuration).
|
||||
This will be uploaded to the `remote_pillar_roots` on the remote.
|
||||
|
||||
- `custom_state` (string) - A state to be run instead of `state.highstate`.
|
||||
Defaults to `state.highstate` if unspecified.
|
||||
|
||||
- `minion_config` (string) - The path to your local [minion config
|
||||
file](http://docs.saltproject.io/en/latest/ref/configuration/minion.html). This will
|
||||
be uploaded to the `/etc/salt` on the remote. This option overrides the
|
||||
`remote_state_tree` or `remote_pillar_roots` options.
|
||||
|
||||
- `grains_file` (string) - The path to your local [grains
|
||||
file](https://docs.saltproject.io/en/latest/topics/grains). This will be
|
||||
uploaded to `/etc/salt/grains` on the remote.
|
||||
|
||||
- `skip_bootstrap` (boolean) - By default the salt provisioner runs [salt
|
||||
bootstrap](https://github.com/saltstack/salt-bootstrap) to install salt.
|
||||
Set this to true to skip this step.
|
||||
|
||||
- `temp_config_dir` (string) - Where your local state tree will be copied
|
||||
before moving to the `/srv/salt` directory. Default is `/tmp/salt`.
|
||||
|
||||
- `no_exit_on_failure` (boolean) - Packer will exit if the `salt-call`
|
||||
command fails. Set this option to true to ignore Salt failures.
|
||||
|
||||
- `log_level` (string) - Set the logging level for the `salt-call` run.
|
||||
|
||||
- `salt_call_args` (string) - Additional arguments to pass directly to
|
||||
`salt-call`. See
|
||||
[salt-call](https://docs.saltproject.io/en/latest/ref/cli/salt-call.html)
|
||||
documentation for more information. By default no additional arguments
|
||||
(besides the ones Packer generates) are passed to `salt-call`.
|
||||
|
||||
- `salt_bin_dir` (string) - Path to the `salt-call` executable. Useful if it
|
||||
is not on the PATH.
|
||||
|
||||
- `guest_os_type` (string) - The target guest OS type, either "unix" or
|
||||
"windows".
|
||||
|
||||
- `formulas` (array of strings) - An array of git source formulas to be downloaded to the local
|
||||
state tree prior to moving to the remote state tree. Note: `//directory` must be included in
|
||||
the URL to download the appropriate formula directory. Example:
|
||||
`git::https://github.com/saltstack-formulas/vault-formula.git//vault?ref=v1.2.3`
|
||||
|
||||
@include 'provisioners/common-config.mdx'
|
||||
Loading…
Reference in new issue