* Remove Azure plugin components and docs * Add Azure plugin to vendored_plugins * Updates Azure to use remote plugin docs * Revendor packer-plugin-azure at v0.0.2 * Update to new version of the Packer plugin SDK v0.2.1 * Update to the official version of go-getter@v2.0.0 * Update salt provisioner to use new go-getter API * Update vendored plugins to working versions This changes fixes an issue with the go.sum for the Azure plugin. It also revendors the plugins for puppet, chef, and ansible as v0.0.1 of those plugins where not usable.
607 lines
20 KiB
Go
607 lines
20 KiB
Go
//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)
|
|
}
|