packer-cn/packer-plugin-sdk/shell-local/config.go

238 lines
6.7 KiB
Go
Raw Normal View History

//go:generate mapstructure-to-hcl2 -type Config
package shell_local
import (
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
packersdk "github.com/hashicorp/packer/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/packer-plugin-sdk/shell"
configHelper "github.com/hashicorp/packer/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer/packer-plugin-sdk/template/interpolate"
)
type Config struct {
shell.Provisioner `mapstructure:",squash"`
// ** DEPRECATED: USE INLINE INSTEAD **
// ** Only Present for backwards compatibility **
// Command is the command to execute
Command string
// The command used to execute the script. The '{{ .Path }}' variable
// should be used to specify where the script goes, {{ .Vars }}
// can be used to inject the environment_vars into the environment.
ExecuteCommand []string `mapstructure:"execute_command"`
// The shebang value used when running inline scripts.
InlineShebang string `mapstructure:"inline_shebang"`
// An array of multiple Runtime OSs to run on.
OnlyOn []string `mapstructure:"only_on"`
// The file extension to use for the file generated from the inline commands
TempfileExtension string `mapstructure:"tempfile_extension"`
// End dedupe with postprocessor
UseLinuxPathing bool `mapstructure:"use_linux_pathing"`
// used to track the data sent to shell-local from the builder
// GeneratedData
ctx interpolate.Context
generatedData map[string]interface{}
}
func Decode(config *Config, raws ...interface{}) error {
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
err := configHelper.Decode(config, &configHelper.DecodeOpts{
Interpolate: true,
InterpolateContext: &config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"execute_command",
},
},
}, raws...)
if err != nil {
return fmt.Errorf("Error decoding config: %s", err)
// return fmt.Errorf("Error decoding config: %s, config is %#v, and raws is %#v", err, config, raws)
}
2018-02-28 17:35:42 -05:00
return nil
}
func Validate(config *Config) error {
var errs *packersdk.MultiError
if runtime.GOOS == "windows" {
if len(config.ExecuteCommand) == 0 {
config.ExecuteCommand = []string{
"cmd",
"/V",
"/C",
"{{.Vars}}",
"call",
"{{.Script}}",
}
}
if len(config.TempfileExtension) == 0 {
config.TempfileExtension = ".cmd"
}
} else {
if config.InlineShebang == "" {
config.InlineShebang = "/bin/sh -e"
}
if len(config.ExecuteCommand) == 0 {
config.ExecuteCommand = []string{
"/bin/sh",
"-c",
"{{.Vars}} {{.Script}}",
}
}
}
// Clean up input
if config.Inline != nil && len(config.Inline) == 0 {
config.Inline = make([]string, 0)
}
if config.Scripts == nil {
config.Scripts = make([]string, 0)
}
if config.Vars == nil {
config.Vars = make([]string, 0)
}
// Verify that the user has given us a command to run
2018-02-28 17:35:42 -05:00
if config.Command == "" && len(config.Inline) == 0 &&
len(config.Scripts) == 0 && config.Script == "" {
errs = packersdk.MultiErrorAppend(errs,
errors.New("Command, Inline, Script and Scripts options cannot all be empty."))
}
// Check that user hasn't given us too many commands to run
tooManyOptionsErr := errors.New("You may only specify one of the " +
"following options: Command, Inline, Script or Scripts. Please" +
" consolidate these options in your config.")
if config.Command != "" {
if len(config.Inline) != 0 || len(config.Scripts) != 0 || config.Script != "" {
errs = packersdk.MultiErrorAppend(errs, tooManyOptionsErr)
} else {
config.Inline = []string{config.Command}
}
}
if config.Script != "" {
if len(config.Scripts) > 0 || len(config.Inline) > 0 {
errs = packersdk.MultiErrorAppend(errs, tooManyOptionsErr)
} else {
config.Scripts = []string{config.Script}
}
}
if len(config.Scripts) > 0 && config.Inline != nil {
errs = packersdk.MultiErrorAppend(errs, tooManyOptionsErr)
}
// Check that all scripts we need to run exist locally
for _, path := range config.Scripts {
if _, err := os.Stat(path); err != nil {
errs = packersdk.MultiErrorAppend(errs,
fmt.Errorf("Bad script '%s': %s", path, err))
}
}
// Check for properly formatted go os types
2018-10-22 17:34:50 -04:00
supportedSyslist := []string{"darwin", "freebsd", "linux", "openbsd", "solaris", "windows"}
if len(config.OnlyOn) > 0 {
for _, provided_os := range config.OnlyOn {
2018-10-19 01:52:20 -04:00
supported_os := false
2018-10-22 17:34:50 -04:00
for _, go_os := range supportedSyslist {
if provided_os == go_os {
supported_os = true
break
}
2018-10-19 01:52:20 -04:00
}
if supported_os != true {
return fmt.Errorf("Invalid OS specified in only_on: '%s'\n"+
2018-10-22 17:34:50 -04:00
"Supported OS names: %s", provided_os, strings.Join(supportedSyslist, ", "))
}
}
}
if config.UseLinuxPathing {
for index, script := range config.Scripts {
scriptAbsPath, err := filepath.Abs(script)
if err != nil {
return fmt.Errorf("Error converting %s to absolute path: %s", script, err.Error())
}
converted, err := ConvertToLinuxPath(scriptAbsPath)
if err != nil {
return err
}
config.Scripts[index] = converted
}
// Interoperability issues with WSL makes creating and running tempfiles
// via golang's os package basically impossible.
if len(config.Inline) > 0 {
errs = packersdk.MultiErrorAppend(errs,
fmt.Errorf("Packer is unable to use the Command and Inline "+
"features with the Windows Linux Subsystem. Please use "+
"the Script or Scripts options instead"))
}
}
2019-01-09 14:16:48 -05:00
2018-03-12 14:25:39 -04:00
if config.EnvVarFormat == "" {
if (runtime.GOOS == "windows") && !config.UseLinuxPathing {
config.EnvVarFormat = "set %s=%s && "
2018-03-12 14:25:39 -04:00
} else {
config.EnvVarFormat = "%s='%s' "
}
}
// drop unnecessary "." in extension; we add this later.
if config.TempfileExtension != "" {
if strings.HasPrefix(config.TempfileExtension, ".") {
config.TempfileExtension = config.TempfileExtension[1:]
}
}
// Do a check for bad environment variables, such as '=foo', 'foobar'
for _, kv := range config.Vars {
vs := strings.SplitN(kv, "=", 2)
if len(vs) != 2 || vs[0] == "" {
errs = packersdk.MultiErrorAppend(errs,
fmt.Errorf("Environment variable not in format 'key=value': %s", kv))
}
}
if errs != nil && len(errs.Errors) > 0 {
return errs
}
return nil
}
// C:/path/to/your/file becomes /mnt/c/path/to/your/file
func ConvertToLinuxPath(winAbsPath string) (string, error) {
// get absolute path of script, and morph it into the bash path
winAbsPath = strings.Replace(winAbsPath, "\\", "/", -1)
splitPath := strings.SplitN(winAbsPath, ":/", 2)
if len(splitPath) == 2 {
winBashPath := fmt.Sprintf("/mnt/%s/%s", strings.ToLower(splitPath[0]), splitPath[1])
return winBashPath, nil
} else {
err := fmt.Errorf("There was an error splitting your absolute path; expected "+
"to find a drive following the format ':/' but did not: absolute "+
"path: %s", winAbsPath)
return "", err
}
}