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

     1  package shell_local
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"log"
     8  	"os"
     9  	"runtime"
    10  	"sort"
    11  	"strings"
    12  
    13  	"github.com/hashicorp/packer/common"
    14  	commonhelper "github.com/hashicorp/packer/helper/common"
    15  	"github.com/hashicorp/packer/packer"
    16  	"github.com/hashicorp/packer/template/interpolate"
    17  )
    18  
    19  type ExecuteCommandTemplate struct {
    20  	Vars          string
    21  	Script        string
    22  	Command       string
    23  	WinRMPassword string
    24  }
    25  
    26  type EnvVarsTemplate struct {
    27  	WinRMPassword string
    28  }
    29  
    30  func Run(ui packer.Ui, config *Config) (bool, error) {
    31  	// Check if shell-local can even execute against this runtime OS
    32  	if len(config.OnlyOn) > 0 {
    33  		runCommand := false
    34  		for _, os := range config.OnlyOn {
    35  			if os == runtime.GOOS {
    36  				runCommand = true
    37  				break
    38  			}
    39  		}
    40  		if !runCommand {
    41  			ui.Say(fmt.Sprintf("Skipping shell-local due to runtime OS"))
    42  			log.Printf("[INFO] (shell-local): skipping shell-local due to missing runtime OS")
    43  			return true, nil
    44  		}
    45  	}
    46  
    47  	scripts := make([]string, len(config.Scripts))
    48  	if len(config.Scripts) > 0 {
    49  		copy(scripts, config.Scripts)
    50  	} else if config.Inline != nil {
    51  		// If we have an inline script, then turn that into a temporary
    52  		// shell script and use that.
    53  		tempScriptFileName, err := createInlineScriptFile(config)
    54  		if err != nil {
    55  			return false, err
    56  		}
    57  		scripts = append(scripts, tempScriptFileName)
    58  
    59  		// figure out what extension the file should have, and rename it.
    60  		if config.TempfileExtension != "" {
    61  			os.Rename(tempScriptFileName, fmt.Sprintf("%s.%s", tempScriptFileName, config.TempfileExtension))
    62  			tempScriptFileName = fmt.Sprintf("%s.%s", tempScriptFileName, config.TempfileExtension)
    63  		}
    64  		defer os.Remove(tempScriptFileName)
    65  	}
    66  
    67  	// Create environment variables to set before executing the command
    68  	flattenedEnvVars, err := createFlattenedEnvVars(config)
    69  	if err != nil {
    70  		return false, err
    71  	}
    72  
    73  	for _, script := range scripts {
    74  		interpolatedCmds, err := createInterpolatedCommands(config, script, flattenedEnvVars)
    75  		if err != nil {
    76  			return false, err
    77  		}
    78  		ui.Say(fmt.Sprintf("Running local shell script: %s", script))
    79  
    80  		comm := &Communicator{
    81  			ExecuteCommand: interpolatedCmds,
    82  		}
    83  
    84  		// The remoteCmd generated here isn't actually run, but it allows us to
    85  		// use the same interafce for the shell-local communicator as we use for
    86  		// the other communicators; ultimately, this command is just used for
    87  		// buffers and for reading the final exit status.
    88  		flattenedCmd := strings.Join(interpolatedCmds, " ")
    89  		cmd := &packer.RemoteCmd{Command: flattenedCmd}
    90  		log.Printf("[INFO] (shell-local): starting local command: %s", flattenedCmd)
    91  		if err := cmd.StartWithUi(comm, ui); err != nil {
    92  			return false, fmt.Errorf(
    93  				"Error executing script: %s\n\n"+
    94  					"Please see output above for more information.",
    95  				script)
    96  		}
    97  		if cmd.ExitStatus != 0 {
    98  			return false, fmt.Errorf(
    99  				"Erroneous exit code %d while executing script: %s\n\n"+
   100  					"Please see output above for more information.",
   101  				cmd.ExitStatus,
   102  				script)
   103  		}
   104  	}
   105  
   106  	return true, nil
   107  }
   108  
   109  func createInlineScriptFile(config *Config) (string, error) {
   110  	tf, err := ioutil.TempFile("", "packer-shell")
   111  	if err != nil {
   112  		return "", fmt.Errorf("Error preparing shell script: %s", err)
   113  	}
   114  	defer tf.Close()
   115  	// Write our contents to it
   116  	writer := bufio.NewWriter(tf)
   117  	if config.InlineShebang != "" {
   118  		shebang := fmt.Sprintf("#!%s\n", config.InlineShebang)
   119  		log.Printf("[INFO] (shell-local): Prepending inline script with %s", shebang)
   120  		writer.WriteString(shebang)
   121  	}
   122  
   123  	// generate context so you can interpolate the command
   124  	config.Ctx.Data = &EnvVarsTemplate{
   125  		WinRMPassword: getWinRMPassword(config.PackerBuildName),
   126  	}
   127  
   128  	for _, command := range config.Inline {
   129  		// interpolate command to check for template variables.
   130  		command, err := interpolate.Render(command, &config.Ctx)
   131  		if err != nil {
   132  			return "", err
   133  		}
   134  
   135  		if _, err := writer.WriteString(command + "\n"); err != nil {
   136  			return "", fmt.Errorf("Error preparing shell script: %s", err)
   137  		}
   138  	}
   139  
   140  	if err := writer.Flush(); err != nil {
   141  		return "", fmt.Errorf("Error preparing shell script: %s", err)
   142  	}
   143  
   144  	err = os.Chmod(tf.Name(), 0700)
   145  	if err != nil {
   146  		log.Printf("[ERROR] (shell-local): error modifying permissions of temp script file: %s", err.Error())
   147  	}
   148  	return tf.Name(), nil
   149  }
   150  
   151  // Generates the final command to send to the communicator, using either the
   152  // user-provided ExecuteCommand or defaulting to something that makes sense for
   153  // the host OS
   154  func createInterpolatedCommands(config *Config, script string, flattenedEnvVars string) ([]string, error) {
   155  	config.Ctx.Data = &ExecuteCommandTemplate{
   156  		Vars:          flattenedEnvVars,
   157  		Script:        script,
   158  		Command:       script,
   159  		WinRMPassword: getWinRMPassword(config.PackerBuildName),
   160  	}
   161  
   162  	interpolatedCmds := make([]string, len(config.ExecuteCommand))
   163  	for i, cmd := range config.ExecuteCommand {
   164  		interpolatedCmd, err := interpolate.Render(cmd, &config.Ctx)
   165  		if err != nil {
   166  			return nil, fmt.Errorf("Error processing command: %s", err)
   167  		}
   168  		interpolatedCmds[i] = interpolatedCmd
   169  	}
   170  	return interpolatedCmds, nil
   171  }
   172  
   173  func createFlattenedEnvVars(config *Config) (string, error) {
   174  	flattened := ""
   175  	envVars := make(map[string]string)
   176  
   177  	// Always available Packer provided env vars
   178  	envVars["PACKER_BUILD_NAME"] = fmt.Sprintf("%s", config.PackerBuildName)
   179  	envVars["PACKER_BUILDER_TYPE"] = fmt.Sprintf("%s", config.PackerBuilderType)
   180  
   181  	// expose PACKER_HTTP_ADDR
   182  	httpAddr := common.GetHTTPAddr()
   183  	if httpAddr != "" {
   184  		envVars["PACKER_HTTP_ADDR"] = fmt.Sprintf("%s", httpAddr)
   185  	}
   186  
   187  	// interpolate environment variables
   188  	config.Ctx.Data = &EnvVarsTemplate{
   189  		WinRMPassword: getWinRMPassword(config.PackerBuildName),
   190  	}
   191  	// Split vars into key/value components
   192  	for _, envVar := range config.Vars {
   193  		envVar, err := interpolate.Render(envVar, &config.Ctx)
   194  		if err != nil {
   195  			return "", err
   196  		}
   197  		// Split vars into key/value components
   198  		keyValue := strings.SplitN(envVar, "=", 2)
   199  		// Store pair, replacing any single quotes in value so they parse
   200  		// correctly with required environment variable format
   201  		envVars[keyValue[0]] = strings.Replace(keyValue[1], "'", `'"'"'`, -1)
   202  	}
   203  
   204  	// Create a list of env var keys in sorted order
   205  	var keys []string
   206  	for k := range envVars {
   207  		keys = append(keys, k)
   208  	}
   209  	sort.Strings(keys)
   210  
   211  	for _, key := range keys {
   212  		flattened += fmt.Sprintf(config.EnvVarFormat, key, envVars[key])
   213  	}
   214  	return flattened, nil
   215  }
   216  
   217  func getWinRMPassword(buildName string) string {
   218  	winRMPass, _ := commonhelper.RetrieveSharedState("winrm_password", buildName)
   219  	packer.LogSecretFilter.Set(winRMPass)
   220  	return winRMPass
   221  }