packer-cn/provisioner/salt-masterless/provisioner.go

487 lines
15 KiB
Go
Raw Normal View History

//go:generate mapstructure-to-hcl2 -type Config
2013-07-22 00:20:39 -04:00
// This package implements a provisioner for Packer that executes a
// saltstack state within the remote machine
package saltmasterless
2013-07-22 00:20:39 -04:00
import (
"bytes"
"context"
"errors"
2013-07-22 00:20:39 -04:00
"fmt"
"os"
"path/filepath"
2017-12-12 13:36:38 -05:00
"strings"
2015-05-27 17:50:20 -04:00
build using HCL2 (#8423) This follows #8232 which added the code to generate the code required to parse HCL files for each packer component. All old config files of packer will keep on working the same. Packer takes one argument. When a directory is passed, all files in the folder with a name ending with “.pkr.hcl” or “.pkr.json” will be parsed using the HCL2 format. When a file ending with “.pkr.hcl” or “.pkr.json” is passed it will be parsed using the HCL2 format. For every other case; the old packer style will be used. ## 1. the hcl2template pkg can create a packer.Build from a set of HCL (v2) files I had to make the packer.coreBuild (which is our one and only packer.Build ) a public struct with public fields ## 2. Components interfaces get a new ConfigSpec Method to read a file from an HCL file. This is a breaking change for packer plugins. a packer component can be a: builder/provisioner/post-processor each component interface now gets a `ConfigSpec() hcldec.ObjectSpec` which allows packer to tell what is the layout of the hcl2 config meant to configure that specific component. This ObjectSpec is sent through the wire (RPC) and a cty.Value is now sent through the already existing configuration entrypoints: Provisioner.Prepare(raws ...interface{}) error Builder.Prepare(raws ...interface{}) ([]string, error) PostProcessor.Configure(raws ...interface{}) error close #1768 Example hcl files: ```hcl // file amazon-ebs-kms-key/run.pkr.hcl build { sources = [ "source.amazon-ebs.first", ] provisioner "shell" { inline = [ "sleep 5" ] } post-processor "shell-local" { inline = [ "sleep 5" ] } } // amazon-ebs-kms-key/source.pkr.hcl source "amazon-ebs" "first" { ami_name = "hcl2-test" region = "us-east-1" instance_type = "t2.micro" kms_key_id = "c729958f-c6ba-44cd-ab39-35ab68ce0a6c" encrypt_boot = true source_ami_filter { filters { virtualization-type = "hvm" name = "amzn-ami-hvm-????.??.?.????????-x86_64-gp2" root-device-type = "ebs" } most_recent = true owners = ["amazon"] } launch_block_device_mappings { device_name = "/dev/xvda" volume_size = 20 volume_type = "gp2" delete_on_termination = "true" } launch_block_device_mappings { device_name = "/dev/xvdf" volume_size = 500 volume_type = "gp2" delete_on_termination = true encrypted = true } ami_regions = ["eu-central-1"] run_tags { Name = "packer-solr-something" stack-name = "DevOps Tools" } communicator = "ssh" ssh_pty = true ssh_username = "ec2-user" associate_public_ip_address = true } ```
2019-12-17 05:25:56 -05:00
"github.com/hashicorp/hcl/v2/hcldec"
2017-04-04 16:39:01 -04:00
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
2017-12-12 13:36:38 -05:00
"github.com/hashicorp/packer/provisioner"
2017-04-04 16:39:01 -04:00
"github.com/hashicorp/packer/template/interpolate"
2013-07-22 00:20:39 -04:00
)
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 ""
2017-12-12 13:36:38 -05:00
// The Guest OS Type (unix or windows)
GuestOSType string `mapstructure:"guest_os_type"`
2015-05-27 17:50:20 -04:00
ctx interpolate.Context
2013-07-22 00:20:39 -04:00
}
type Provisioner struct {
2017-12-12 13:36:38 -05:00
config Config
guestOSTypeConfig guestOSTypeConfig
guestCommands *provisioner.GuestCommands
}
type guestOSTypeConfig struct {
tempDir string
stateRoot string
pillarRoot string
configDir string
bootstrapFetchCmd string
bootstrapRunCmd string
}
var guestOSTypeConfigs = map[string]guestOSTypeConfig{
provisioner.UnixOSType: {
configDir: "/etc/salt",
2018-03-13 19:11:11 -04:00
tempDir: "/tmp/salt",
stateRoot: "/srv/salt",
pillarRoot: "/srv/pillar",
2017-12-12 13:36:38 -05:00
bootstrapFetchCmd: "curl -L https://bootstrap.saltstack.com -o /tmp/install_salt.sh || wget -O /tmp/install_salt.sh https://bootstrap.saltstack.com",
bootstrapRunCmd: "sh /tmp/install_salt.sh",
},
provisioner.WindowsOSType: {
configDir: "C:/salt/conf",
tempDir: "C:/Windows/Temp/salt/",
2017-12-12 16:39:13 -05:00
stateRoot: "C:/salt/state",
pillarRoot: "C:/salt/pillar/",
bootstrapFetchCmd: "powershell Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/saltstack/salt-bootstrap/stable/bootstrap-salt.ps1' -OutFile 'C:/Windows/Temp/bootstrap-salt.ps1'",
2017-12-12 13:36:38 -05:00
bootstrapRunCmd: "Powershell C:/Windows/Temp/bootstrap-salt.ps1",
},
2013-07-22 00:20:39 -04:00
}
build using HCL2 (#8423) This follows #8232 which added the code to generate the code required to parse HCL files for each packer component. All old config files of packer will keep on working the same. Packer takes one argument. When a directory is passed, all files in the folder with a name ending with “.pkr.hcl” or “.pkr.json” will be parsed using the HCL2 format. When a file ending with “.pkr.hcl” or “.pkr.json” is passed it will be parsed using the HCL2 format. For every other case; the old packer style will be used. ## 1. the hcl2template pkg can create a packer.Build from a set of HCL (v2) files I had to make the packer.coreBuild (which is our one and only packer.Build ) a public struct with public fields ## 2. Components interfaces get a new ConfigSpec Method to read a file from an HCL file. This is a breaking change for packer plugins. a packer component can be a: builder/provisioner/post-processor each component interface now gets a `ConfigSpec() hcldec.ObjectSpec` which allows packer to tell what is the layout of the hcl2 config meant to configure that specific component. This ObjectSpec is sent through the wire (RPC) and a cty.Value is now sent through the already existing configuration entrypoints: Provisioner.Prepare(raws ...interface{}) error Builder.Prepare(raws ...interface{}) ([]string, error) PostProcessor.Configure(raws ...interface{}) error close #1768 Example hcl files: ```hcl // file amazon-ebs-kms-key/run.pkr.hcl build { sources = [ "source.amazon-ebs.first", ] provisioner "shell" { inline = [ "sleep 5" ] } post-processor "shell-local" { inline = [ "sleep 5" ] } } // amazon-ebs-kms-key/source.pkr.hcl source "amazon-ebs" "first" { ami_name = "hcl2-test" region = "us-east-1" instance_type = "t2.micro" kms_key_id = "c729958f-c6ba-44cd-ab39-35ab68ce0a6c" encrypt_boot = true source_ami_filter { filters { virtualization-type = "hvm" name = "amzn-ami-hvm-????.??.?.????????-x86_64-gp2" root-device-type = "ebs" } most_recent = true owners = ["amazon"] } launch_block_device_mappings { device_name = "/dev/xvda" volume_size = 20 volume_type = "gp2" delete_on_termination = "true" } launch_block_device_mappings { device_name = "/dev/xvdf" volume_size = 500 volume_type = "gp2" delete_on_termination = true encrypted = true } ami_regions = ["eu-central-1"] run_tags { Name = "packer-solr-something" stack-name = "DevOps Tools" } communicator = "ssh" ssh_pty = true ssh_username = "ec2-user" associate_public_ip_address = true } ```
2019-12-17 05:25:56 -05:00
func (p *Provisioner) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() }
2013-07-22 00:20:39 -04:00
func (p *Provisioner) Prepare(raws ...interface{}) error {
2015-05-27 17:50:20 -04:00
err := config.Decode(&p.config, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &p.config.ctx,
2015-05-27 17:50:20 -04:00
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{},
},
}, raws...)
if err != nil {
return err
}
2017-12-12 13:36:38 -05:00
if p.config.GuestOSType == "" {
p.config.GuestOSType = provisioner.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)
2017-12-12 13:36:38 -05:00
}
2019-06-20 13:39:51 -04:00
p.guestCommands, err = provisioner.NewGuestCommands(p.config.GuestOSType, !p.config.DisableSudo)
2017-12-12 13:36:38 -05:00
if err != nil {
return fmt.Errorf("Invalid guest_os_type: \"%s\"", p.config.GuestOSType)
}
if p.config.TempConfigDir == "" {
2017-12-12 13:36:38 -05:00
p.config.TempConfigDir = p.guestOSTypeConfig.tempDir
2013-07-22 00:20:39 -04:00
}
2015-05-27 17:50:20 -04:00
var errs *packer.MultiError
// require a salt state tree
err = validateDirConfig(p.config.LocalStateTree, "local_state_tree", true)
if err != nil {
errs = packer.MultiErrorAppend(errs, err)
2013-08-11 19:17:59 -04:00
}
err = validateDirConfig(p.config.LocalPillarRoots, "local_pillar_roots", false)
if err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
err = validateFileConfig(p.config.MinionConfig, "minion_config", false)
if err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
if p.config.MinionConfig != "" && (p.config.RemoteStateTree != "" || p.config.RemotePillarRoots != "") {
errs = packer.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 = packer.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")
2016-10-11 17:43:50 -04:00
} 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=")
2017-12-12 13:36:38 -05:00
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=")
2017-12-12 13:36:38 -05:00
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
2013-07-22 00:20:39 -04:00
}
return nil
}
func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator, _ map[string]interface{}) error {
2013-07-22 00:20:39 -04:00
var err error
var src, dst string
2013-07-22 00:20:39 -04:00
ui.Say("Provisioning with Salt...")
if !p.config.SkipBootstrap {
2013-07-27 21:12:18 -04:00
cmd := &packer.RemoteCmd{
// Fallback on wget if curl failed for any reason (such as not being installed)
2017-12-12 13:36:38 -05:00
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 = &packer.RemoteCmd{
2017-12-12 17:11:29 -05:00
Command: fmt.Sprintf("%s %s", p.sudo(p.guestOSTypeConfig.bootstrapRunCmd), p.config.BootstrapArgs),
2013-07-27 21:12:18 -04:00
}
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)
2013-07-22 00:20:39 -04:00
}
}
2015-07-27 07:00:57 -04:00
ui.Message(fmt.Sprintf("Creating remote temporary directory: %s", p.config.TempConfigDir))
if err := p.createDir(ui, comm, p.config.TempConfigDir); err != nil {
2015-07-27 07:00:57 -04:00
return fmt.Errorf("Error creating remote temporary directory: %s", err)
}
2013-08-27 18:47:11 -04:00
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 {
2013-08-27 18:47:11 -04:00
return fmt.Errorf("Error uploading local minion config file to remote: %s", err)
}
// move minion config into /etc/salt
2017-12-12 13:36:38 -05:00
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"))
2017-12-12 13:36:38 -05:00
dst = filepath.ToSlash(filepath.Join(p.guestOSTypeConfig.configDir, "minion"))
if err = p.moveFile(ui, comm, dst, src); err != nil {
2017-12-12 13:36:38 -05:00
return fmt.Errorf("Unable to move %s/minion to %s/minion: %s", p.config.TempConfigDir, p.guestOSTypeConfig.configDir, err)
2013-08-27 18:47:11 -04:00
}
}
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
2017-12-12 13:36:38 -05:00
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"))
2017-12-12 13:36:38 -05:00
dst = filepath.ToSlash(filepath.Join(p.guestOSTypeConfig.configDir, "grains"))
if err = p.moveFile(ui, comm, dst, src); err != nil {
2017-12-12 13:36:38 -05:00
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 {
2017-12-12 13:36:38 -05:00
dst = p.guestOSTypeConfig.stateRoot
}
2017-12-12 16:39:13 -05:00
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)
}
}
2017-12-12 16:39:13 -05:00
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)
}
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 {
2017-12-12 13:36:38 -05:00
dst = p.guestOSTypeConfig.pillarRoot
}
2017-12-12 18:01:00 -05:00
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)
}
}
2017-12-12 18:01:00 -05:00
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)
}
}
ui.Message(fmt.Sprintf("Running: salt-call --local %s", p.config.CmdArgs))
cmd := &packer.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 {
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 == provisioner.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 (p *Provisioner) uploadFile(ui packer.Ui, comm packer.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)
}
p.moveFile(ui, comm, dst, temp_dst)
return nil
}
2017-12-12 18:01:00 -05:00
func (p *Provisioner) moveFile(ui packer.Ui, comm packer.Communicator, dst string, src string) error {
ctx := context.TODO()
ui.Message(fmt.Sprintf("Moving %s to %s", src, dst))
cmd := &packer.RemoteCmd{
2018-03-13 19:11:11 -04:00
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())
}
2015-07-27 07:00:57 -04:00
return fmt.Errorf("Unable to move %s to %s: %s", src, dst, err)
}
return nil
}
func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
ui.Message(fmt.Sprintf("Creating directory: %s", dir))
cmd := &packer.RemoteCmd{
2017-12-12 13:36:38 -05:00
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
}
2017-12-12 16:39:13 -05:00
func (p *Provisioner) statPath(ui packer.Ui, comm packer.Communicator, path string) error {
ctx := context.TODO()
2017-12-12 16:39:13 -05:00
ui.Message(fmt.Sprintf("Verifying Path: %s", path))
cmd := &packer.RemoteCmd{
Command: p.guestCommands.StatPath(path),
}
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
2017-12-12 16:39:13 -05:00
return err
}
if cmd.ExitStatus() != 0 {
2017-12-12 16:39:13 -05:00
return fmt.Errorf("Non-zero exit status.")
}
return nil
}
func (p *Provisioner) removeDir(ui packer.Ui, comm packer.Communicator, dir string) error {
ctx := context.TODO()
ui.Message(fmt.Sprintf("Removing directory: %s", dir))
cmd := &packer.RemoteCmd{
2017-12-12 13:36:38 -05:00
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 packer.Ui, comm packer.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)
}