github.phpd.cn/hashicorp/packer@v1.3.2/common/shell-local/config.go (about)

     1  package shell_local
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"runtime"
     9  	"strings"
    10  
    11  	"github.com/hashicorp/packer/common"
    12  	configHelper "github.com/hashicorp/packer/helper/config"
    13  	"github.com/hashicorp/packer/packer"
    14  	"github.com/hashicorp/packer/template/interpolate"
    15  )
    16  
    17  type Config struct {
    18  	common.PackerConfig `mapstructure:",squash"`
    19  
    20  	// ** DEPRECATED: USE INLINE INSTEAD **
    21  	// ** Only Present for backwards compatibiltiy **
    22  	// Command is the command to execute
    23  	Command string
    24  
    25  	// An inline script to execute. Multiple strings are all executed
    26  	// in the context of a single shell.
    27  	Inline []string
    28  
    29  	// The shebang value used when running inline scripts.
    30  	InlineShebang string `mapstructure:"inline_shebang"`
    31  
    32  	// An array of multiple Runtime OSs to run on.
    33  	OnlyOn []string `mapstructure:"only_on"`
    34  
    35  	// The file extension to use for the file generated from the inline commands
    36  	TempfileExtension string `mapstructure:"tempfile_extension"`
    37  
    38  	// The local path of the shell script to upload and execute.
    39  	Script string
    40  
    41  	// An array of multiple scripts to run.
    42  	Scripts []string
    43  
    44  	// An array of environment variables that will be injected before
    45  	// your command(s) are executed.
    46  	Vars []string `mapstructure:"environment_vars"`
    47  
    48  	EnvVarFormat string `mapstructure:"env_var_format"`
    49  	// End dedupe with postprocessor
    50  
    51  	// The command used to execute the script. The '{{ .Path }}' variable
    52  	// should be used to specify where the script goes, {{ .Vars }}
    53  	// can be used to inject the environment_vars into the environment.
    54  	ExecuteCommand []string `mapstructure:"execute_command"`
    55  
    56  	UseLinuxPathing bool `mapstructure:"use_linux_pathing"`
    57  
    58  	Ctx interpolate.Context
    59  }
    60  
    61  func Decode(config *Config, raws ...interface{}) error {
    62  	//Create passthrough for winrm password so we can fill it in once we know it
    63  	config.Ctx.Data = &EnvVarsTemplate{
    64  		WinRMPassword: `{{.WinRMPassword}}`,
    65  	}
    66  
    67  	err := configHelper.Decode(&config, &configHelper.DecodeOpts{
    68  		Interpolate:        true,
    69  		InterpolateContext: &config.Ctx,
    70  		InterpolateFilter: &interpolate.RenderFilter{
    71  			Exclude: []string{
    72  				"execute_command",
    73  			},
    74  		},
    75  	}, raws...)
    76  	if err != nil {
    77  		return fmt.Errorf("Error decoding config: %s, config is %#v, and raws is %#v", err, config, raws)
    78  	}
    79  
    80  	return nil
    81  }
    82  
    83  func Validate(config *Config) error {
    84  	var errs *packer.MultiError
    85  
    86  	if runtime.GOOS == "windows" {
    87  		if len(config.ExecuteCommand) == 0 {
    88  			config.ExecuteCommand = []string{
    89  				"cmd",
    90  				"/V",
    91  				"/C",
    92  				"{{.Vars}}",
    93  				"call",
    94  				"{{.Script}}",
    95  			}
    96  		}
    97  		if len(config.TempfileExtension) == 0 {
    98  			config.TempfileExtension = ".cmd"
    99  		}
   100  	} else {
   101  		if config.InlineShebang == "" {
   102  			config.InlineShebang = "/bin/sh -e"
   103  		}
   104  		if len(config.ExecuteCommand) == 0 {
   105  			config.ExecuteCommand = []string{
   106  				"/bin/sh",
   107  				"-c",
   108  				"{{.Vars}} {{.Script}}",
   109  			}
   110  		}
   111  	}
   112  
   113  	// Clean up input
   114  	if config.Inline != nil && len(config.Inline) == 0 {
   115  		config.Inline = make([]string, 0)
   116  	}
   117  
   118  	if config.Scripts == nil {
   119  		config.Scripts = make([]string, 0)
   120  	}
   121  
   122  	if config.Vars == nil {
   123  		config.Vars = make([]string, 0)
   124  	}
   125  
   126  	// Verify that the user has given us a command to run
   127  	if config.Command == "" && len(config.Inline) == 0 &&
   128  		len(config.Scripts) == 0 && config.Script == "" {
   129  		errs = packer.MultiErrorAppend(errs,
   130  			errors.New("Command, Inline, Script and Scripts options cannot all be empty."))
   131  	}
   132  
   133  	// Check that user hasn't given us too many commands to run
   134  	tooManyOptionsErr := errors.New("You may only specify one of the " +
   135  		"following options: Command, Inline, Script or Scripts. Please" +
   136  		" consolidate these options in your config.")
   137  
   138  	if config.Command != "" {
   139  		if len(config.Inline) != 0 || len(config.Scripts) != 0 || config.Script != "" {
   140  			errs = packer.MultiErrorAppend(errs, tooManyOptionsErr)
   141  		} else {
   142  			config.Inline = []string{config.Command}
   143  		}
   144  	}
   145  
   146  	if config.Script != "" {
   147  		if len(config.Scripts) > 0 || len(config.Inline) > 0 {
   148  			errs = packer.MultiErrorAppend(errs, tooManyOptionsErr)
   149  		} else {
   150  			config.Scripts = []string{config.Script}
   151  		}
   152  	}
   153  
   154  	if len(config.Scripts) > 0 && config.Inline != nil {
   155  		errs = packer.MultiErrorAppend(errs, tooManyOptionsErr)
   156  	}
   157  
   158  	// Check that all scripts we need to run exist locally
   159  	for _, path := range config.Scripts {
   160  		if _, err := os.Stat(path); err != nil {
   161  			errs = packer.MultiErrorAppend(errs,
   162  				fmt.Errorf("Bad script '%s': %s", path, err))
   163  		}
   164  	}
   165  
   166  	// Check for properly formatted go os types
   167  	supportedSyslist := []string{"darwin", "freebsd", "linux", "openbsd", "solaris", "windows"}
   168  	if len(config.OnlyOn) > 0 {
   169  		for _, provided_os := range config.OnlyOn {
   170  			supported_os := false
   171  			for _, go_os := range supportedSyslist {
   172  				if provided_os == go_os {
   173  					supported_os = true
   174  					break
   175  				}
   176  			}
   177  			if supported_os != true {
   178  				return fmt.Errorf("Invalid OS specified in only_on: '%s'\n"+
   179  					"Supported OS names: %s", provided_os, strings.Join(supportedSyslist, ", "))
   180  			}
   181  		}
   182  	}
   183  
   184  	if config.UseLinuxPathing {
   185  		for index, script := range config.Scripts {
   186  			scriptAbsPath, err := filepath.Abs(script)
   187  			if err != nil {
   188  				return fmt.Errorf("Error converting %s to absolute path: %s", script, err.Error())
   189  			}
   190  			converted, err := ConvertToLinuxPath(scriptAbsPath)
   191  			if err != nil {
   192  				return err
   193  			}
   194  			config.Scripts[index] = converted
   195  		}
   196  		// Interoperability issues with WSL makes creating and running tempfiles
   197  		// via golang's os package basically impossible.
   198  		if len(config.Inline) > 0 {
   199  			errs = packer.MultiErrorAppend(errs,
   200  				fmt.Errorf("Packer is unable to use the Command and Inline "+
   201  					"features with the Windows Linux Subsystem. Please use "+
   202  					"the Script or Scripts options instead"))
   203  		}
   204  	}
   205  	// This is currently undocumented and not a feature users are expected to
   206  	// interact with.
   207  	if config.EnvVarFormat == "" {
   208  		if (runtime.GOOS == "windows") && !config.UseLinuxPathing {
   209  			config.EnvVarFormat = "set %s=%s && "
   210  		} else {
   211  			config.EnvVarFormat = "%s='%s' "
   212  		}
   213  	}
   214  
   215  	// drop unnecessary "." in extension; we add this later.
   216  	if config.TempfileExtension != "" {
   217  		if strings.HasPrefix(config.TempfileExtension, ".") {
   218  			config.TempfileExtension = config.TempfileExtension[1:]
   219  		}
   220  	}
   221  
   222  	// Do a check for bad environment variables, such as '=foo', 'foobar'
   223  	for _, kv := range config.Vars {
   224  		vs := strings.SplitN(kv, "=", 2)
   225  		if len(vs) != 2 || vs[0] == "" {
   226  			errs = packer.MultiErrorAppend(errs,
   227  				fmt.Errorf("Environment variable not in format 'key=value': %s", kv))
   228  		}
   229  	}
   230  
   231  	if errs != nil && len(errs.Errors) > 0 {
   232  		return errs
   233  	}
   234  
   235  	return nil
   236  }
   237  
   238  // C:/path/to/your/file becomes /mnt/c/path/to/your/file
   239  func ConvertToLinuxPath(winAbsPath string) (string, error) {
   240  	// get absolute path of script, and morph it into the bash path
   241  	winAbsPath = strings.Replace(winAbsPath, "\\", "/", -1)
   242  	splitPath := strings.SplitN(winAbsPath, ":/", 2)
   243  	if len(splitPath) == 2 {
   244  		winBashPath := fmt.Sprintf("/mnt/%s/%s", strings.ToLower(splitPath[0]), splitPath[1])
   245  		return winBashPath, nil
   246  	} else {
   247  		err := fmt.Errorf("There was an error splitting your absolute path; expected "+
   248  			"to find a drive following the format ':/' but did not: absolute "+
   249  			"path: %s", winAbsPath)
   250  		return "", err
   251  	}
   252  }