github.com/dacamp/packer@v0.10.2/provisioner/chef-client/provisioner.go (about)

     1  // This package implements a provisioner for Packer that uses
     2  // Chef to provision the remote machine, specifically with chef-client (that is,
     3  // with a Chef server).
     4  package chefclient
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/json"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  
    15  	"github.com/mitchellh/packer/common"
    16  	"github.com/mitchellh/packer/common/uuid"
    17  	"github.com/mitchellh/packer/helper/config"
    18  	"github.com/mitchellh/packer/packer"
    19  	"github.com/mitchellh/packer/provisioner"
    20  	"github.com/mitchellh/packer/template/interpolate"
    21  )
    22  
    23  type guestOSTypeConfig struct {
    24  	executeCommand string
    25  	installCommand string
    26  	stagingDir     string
    27  }
    28  
    29  var guestOSTypeConfigs = map[string]guestOSTypeConfig{
    30  	provisioner.UnixOSType: guestOSTypeConfig{
    31  		executeCommand: "{{if .Sudo}}sudo {{end}}chef-client --no-color -c {{.ConfigPath}} -j {{.JsonPath}}",
    32  		installCommand: "curl -L https://www.chef.io/chef/install.sh | {{if .Sudo}}sudo {{end}}bash",
    33  		stagingDir:     "/tmp/packer-chef-client",
    34  	},
    35  	provisioner.WindowsOSType: guestOSTypeConfig{
    36  		executeCommand: "c:/opscode/chef/bin/chef-client.bat --no-color -c {{.ConfigPath}} -j {{.JsonPath}}",
    37  		installCommand: "powershell.exe -Command \"(New-Object System.Net.WebClient).DownloadFile('http://chef.io/chef/install.msi', 'C:\\Windows\\Temp\\chef.msi');Start-Process 'msiexec' -ArgumentList '/qb /i C:\\Windows\\Temp\\chef.msi' -NoNewWindow -Wait\"",
    38  		stagingDir:     "C:/Windows/Temp/packer-chef-client",
    39  	},
    40  }
    41  
    42  type Config struct {
    43  	common.PackerConfig `mapstructure:",squash"`
    44  
    45  	Json map[string]interface{}
    46  
    47  	ChefEnvironment            string   `mapstructure:"chef_environment"`
    48  	ClientKey                  string   `mapstructure:"client_key"`
    49  	ConfigTemplate             string   `mapstructure:"config_template"`
    50  	EncryptedDataBagSecretPath string   `mapstructure:"encrypted_data_bag_secret_path"`
    51  	ExecuteCommand             string   `mapstructure:"execute_command"`
    52  	GuestOSType                string   `mapstructure:"guest_os_type"`
    53  	InstallCommand             string   `mapstructure:"install_command"`
    54  	NodeName                   string   `mapstructure:"node_name"`
    55  	PreventSudo                bool     `mapstructure:"prevent_sudo"`
    56  	RunList                    []string `mapstructure:"run_list"`
    57  	ServerUrl                  string   `mapstructure:"server_url"`
    58  	SkipCleanClient            bool     `mapstructure:"skip_clean_client"`
    59  	SkipCleanNode              bool     `mapstructure:"skip_clean_node"`
    60  	SkipInstall                bool     `mapstructure:"skip_install"`
    61  	SslVerifyMode              string   `mapstructure:"ssl_verify_mode"`
    62  	StagingDir                 string   `mapstructure:"staging_directory"`
    63  	ValidationClientName       string   `mapstructure:"validation_client_name"`
    64  	ValidationKeyPath          string   `mapstructure:"validation_key_path"`
    65  
    66  	ctx interpolate.Context
    67  }
    68  
    69  type Provisioner struct {
    70  	config            Config
    71  	guestOSTypeConfig guestOSTypeConfig
    72  	guestCommands     *provisioner.GuestCommands
    73  }
    74  
    75  type ConfigTemplate struct {
    76  	ChefEnvironment            string
    77  	ClientKey                  string
    78  	EncryptedDataBagSecretPath string
    79  	NodeName                   string
    80  	ServerUrl                  string
    81  	SslVerifyMode              string
    82  	ValidationClientName       string
    83  	ValidationKeyPath          string
    84  }
    85  
    86  type ExecuteTemplate struct {
    87  	ConfigPath string
    88  	JsonPath   string
    89  	Sudo       bool
    90  }
    91  
    92  type InstallChefTemplate struct {
    93  	Sudo bool
    94  }
    95  
    96  func (p *Provisioner) Prepare(raws ...interface{}) error {
    97  	err := config.Decode(&p.config, &config.DecodeOpts{
    98  		Interpolate:        true,
    99  		InterpolateContext: &p.config.ctx,
   100  		InterpolateFilter: &interpolate.RenderFilter{
   101  			Exclude: []string{
   102  				"execute_command",
   103  				"install_command",
   104  			},
   105  		},
   106  	}, raws...)
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	if p.config.GuestOSType == "" {
   112  		p.config.GuestOSType = provisioner.DefaultOSType
   113  	}
   114  	p.config.GuestOSType = strings.ToLower(p.config.GuestOSType)
   115  
   116  	var ok bool
   117  	p.guestOSTypeConfig, ok = guestOSTypeConfigs[p.config.GuestOSType]
   118  	if !ok {
   119  		return fmt.Errorf("Invalid guest_os_type: \"%s\"", p.config.GuestOSType)
   120  	}
   121  
   122  	p.guestCommands, err = provisioner.NewGuestCommands(p.config.GuestOSType, !p.config.PreventSudo)
   123  	if err != nil {
   124  		return fmt.Errorf("Invalid guest_os_type: \"%s\"", p.config.GuestOSType)
   125  	}
   126  
   127  	if p.config.ExecuteCommand == "" {
   128  		p.config.ExecuteCommand = p.guestOSTypeConfig.executeCommand
   129  	}
   130  
   131  	if p.config.InstallCommand == "" {
   132  		p.config.InstallCommand = p.guestOSTypeConfig.installCommand
   133  	}
   134  
   135  	if p.config.RunList == nil {
   136  		p.config.RunList = make([]string, 0)
   137  	}
   138  
   139  	if p.config.StagingDir == "" {
   140  		p.config.StagingDir = p.guestOSTypeConfig.stagingDir
   141  	}
   142  
   143  	var errs *packer.MultiError
   144  	if p.config.ConfigTemplate != "" {
   145  		fi, err := os.Stat(p.config.ConfigTemplate)
   146  		if err != nil {
   147  			errs = packer.MultiErrorAppend(
   148  				errs, fmt.Errorf("Bad config template path: %s", err))
   149  		} else if fi.IsDir() {
   150  			errs = packer.MultiErrorAppend(
   151  				errs, fmt.Errorf("Config template path must be a file: %s", err))
   152  		}
   153  	}
   154  
   155  	if p.config.EncryptedDataBagSecretPath != "" {
   156  		pFileInfo, err := os.Stat(p.config.EncryptedDataBagSecretPath)
   157  
   158  		if err != nil || pFileInfo.IsDir() {
   159  			errs = packer.MultiErrorAppend(
   160  				errs, fmt.Errorf("Bad encrypted data bag secret '%s': %s", p.config.EncryptedDataBagSecretPath, err))
   161  		}
   162  	}
   163  
   164  	if p.config.ServerUrl == "" {
   165  		errs = packer.MultiErrorAppend(
   166  			errs, fmt.Errorf("server_url must be set"))
   167  	}
   168  
   169  	if p.config.EncryptedDataBagSecretPath != "" {
   170  		pFileInfo, err := os.Stat(p.config.EncryptedDataBagSecretPath)
   171  
   172  		if err != nil || pFileInfo.IsDir() {
   173  			errs = packer.MultiErrorAppend(
   174  				errs, fmt.Errorf("Bad encrypted data bag secret '%s': %s", p.config.EncryptedDataBagSecretPath, err))
   175  		}
   176  	}
   177  
   178  	jsonValid := true
   179  	for k, v := range p.config.Json {
   180  		p.config.Json[k], err = p.deepJsonFix(k, v)
   181  		if err != nil {
   182  			errs = packer.MultiErrorAppend(
   183  				errs, fmt.Errorf("Error processing JSON: %s", err))
   184  			jsonValid = false
   185  		}
   186  	}
   187  
   188  	if jsonValid {
   189  		// Process the user variables within the JSON and set the JSON.
   190  		// Do this early so that we can validate and show errors.
   191  		p.config.Json, err = p.processJsonUserVars()
   192  		if err != nil {
   193  			errs = packer.MultiErrorAppend(
   194  				errs, fmt.Errorf("Error processing user variables in JSON: %s", err))
   195  		}
   196  	}
   197  
   198  	if errs != nil && len(errs.Errors) > 0 {
   199  		return errs
   200  	}
   201  
   202  	return nil
   203  }
   204  
   205  func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
   206  
   207  	nodeName := p.config.NodeName
   208  	if nodeName == "" {
   209  		nodeName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
   210  	}
   211  	remoteValidationKeyPath := ""
   212  	serverUrl := p.config.ServerUrl
   213  
   214  	if !p.config.SkipInstall {
   215  		if err := p.installChef(ui, comm); err != nil {
   216  			return fmt.Errorf("Error installing Chef: %s", err)
   217  		}
   218  	}
   219  
   220  	if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
   221  		return fmt.Errorf("Error creating staging directory: %s", err)
   222  	}
   223  
   224  	if p.config.ClientKey == "" {
   225  		p.config.ClientKey = fmt.Sprintf("%s/client.pem", p.config.StagingDir)
   226  	}
   227  
   228  	encryptedDataBagSecretPath := ""
   229  	if p.config.EncryptedDataBagSecretPath != "" {
   230  		encryptedDataBagSecretPath = fmt.Sprintf("%s/encrypted_data_bag_secret", p.config.StagingDir)
   231  		if err := p.uploadFile(ui,
   232  			comm,
   233  			encryptedDataBagSecretPath,
   234  			p.config.EncryptedDataBagSecretPath); err != nil {
   235  			return fmt.Errorf("Error uploading encrypted data bag secret: %s", err)
   236  		}
   237  	}
   238  
   239  	if p.config.ValidationKeyPath != "" {
   240  		remoteValidationKeyPath = fmt.Sprintf("%s/validation.pem", p.config.StagingDir)
   241  		if err := p.uploadFile(ui, comm, remoteValidationKeyPath, p.config.ValidationKeyPath); err != nil {
   242  			return fmt.Errorf("Error copying validation key: %s", err)
   243  		}
   244  	}
   245  
   246  	configPath, err := p.createConfig(
   247  		ui,
   248  		comm,
   249  		nodeName,
   250  		serverUrl,
   251  		p.config.ClientKey,
   252  		encryptedDataBagSecretPath,
   253  		remoteValidationKeyPath,
   254  		p.config.ValidationClientName,
   255  		p.config.ChefEnvironment,
   256  		p.config.SslVerifyMode)
   257  	if err != nil {
   258  		return fmt.Errorf("Error creating Chef config file: %s", err)
   259  	}
   260  
   261  	jsonPath, err := p.createJson(ui, comm)
   262  	if err != nil {
   263  		return fmt.Errorf("Error creating JSON attributes: %s", err)
   264  	}
   265  
   266  	err = p.executeChef(ui, comm, configPath, jsonPath)
   267  
   268  	knifeConfigPath, err2 := p.createKnifeConfig(
   269  		ui, comm, nodeName, serverUrl, p.config.ClientKey, p.config.SslVerifyMode)
   270  	if err2 != nil {
   271  		return fmt.Errorf("Error creating knife config on node: %s", err2)
   272  	}
   273  	if !p.config.SkipCleanNode {
   274  		if err2 := p.cleanNode(ui, comm, nodeName, knifeConfigPath); err2 != nil {
   275  			return fmt.Errorf("Error cleaning up chef node: %s", err2)
   276  		}
   277  	}
   278  
   279  	if !p.config.SkipCleanClient {
   280  		if err2 := p.cleanClient(ui, comm, nodeName, knifeConfigPath); err2 != nil {
   281  			return fmt.Errorf("Error cleaning up chef client: %s", err2)
   282  		}
   283  	}
   284  
   285  	if err != nil {
   286  		return fmt.Errorf("Error executing Chef: %s", err)
   287  	}
   288  
   289  	if err := p.removeDir(ui, comm, p.config.StagingDir); err != nil {
   290  		return fmt.Errorf("Error removing %s: %s", p.config.StagingDir, err)
   291  	}
   292  
   293  	return nil
   294  }
   295  
   296  func (p *Provisioner) Cancel() {
   297  	// Just hard quit. It isn't a big deal if what we're doing keeps
   298  	// running on the other side.
   299  	os.Exit(0)
   300  }
   301  
   302  func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, dst string, src string) error {
   303  	if err := p.createDir(ui, comm, dst); err != nil {
   304  		return err
   305  	}
   306  
   307  	// Make sure there is a trailing "/" so that the directory isn't
   308  	// created on the other side.
   309  	if src[len(src)-1] != '/' {
   310  		src = src + "/"
   311  	}
   312  
   313  	return comm.UploadDir(dst, src, nil)
   314  }
   315  
   316  func (p *Provisioner) uploadFile(ui packer.Ui, comm packer.Communicator, remotePath string, localPath string) error {
   317  	ui.Message(fmt.Sprintf("Uploading %s...", localPath))
   318  
   319  	f, err := os.Open(localPath)
   320  	if err != nil {
   321  		return err
   322  	}
   323  	defer f.Close()
   324  
   325  	return comm.Upload(remotePath, f, nil)
   326  }
   327  
   328  func (p *Provisioner) createConfig(
   329  	ui packer.Ui,
   330  	comm packer.Communicator,
   331  	nodeName string,
   332  	serverUrl string,
   333  	clientKey string,
   334  	encryptedDataBagSecretPath,
   335  	remoteKeyPath string,
   336  	validationClientName string,
   337  	chefEnvironment string,
   338  	sslVerifyMode string) (string, error) {
   339  
   340  	ui.Message("Creating configuration file 'client.rb'")
   341  
   342  	// Read the template
   343  	tpl := DefaultConfigTemplate
   344  	if p.config.ConfigTemplate != "" {
   345  		f, err := os.Open(p.config.ConfigTemplate)
   346  		if err != nil {
   347  			return "", err
   348  		}
   349  		defer f.Close()
   350  
   351  		tplBytes, err := ioutil.ReadAll(f)
   352  		if err != nil {
   353  			return "", err
   354  		}
   355  
   356  		tpl = string(tplBytes)
   357  	}
   358  
   359  	ctx := p.config.ctx
   360  	ctx.Data = &ConfigTemplate{
   361  		NodeName:                   nodeName,
   362  		ServerUrl:                  serverUrl,
   363  		ClientKey:                  clientKey,
   364  		ValidationKeyPath:          remoteKeyPath,
   365  		ValidationClientName:       validationClientName,
   366  		ChefEnvironment:            chefEnvironment,
   367  		SslVerifyMode:              sslVerifyMode,
   368  		EncryptedDataBagSecretPath: encryptedDataBagSecretPath,
   369  	}
   370  	configString, err := interpolate.Render(tpl, &ctx)
   371  	if err != nil {
   372  		return "", err
   373  	}
   374  
   375  	remotePath := filepath.ToSlash(filepath.Join(p.config.StagingDir, "client.rb"))
   376  	if err := comm.Upload(remotePath, bytes.NewReader([]byte(configString)), nil); err != nil {
   377  		return "", err
   378  	}
   379  
   380  	return remotePath, nil
   381  }
   382  
   383  func (p *Provisioner) createKnifeConfig(ui packer.Ui, comm packer.Communicator, nodeName string, serverUrl string, clientKey string, sslVerifyMode string) (string, error) {
   384  	ui.Message("Creating configuration file 'knife.rb'")
   385  
   386  	// Read the template
   387  	tpl := DefaultKnifeTemplate
   388  
   389  	ctx := p.config.ctx
   390  	ctx.Data = &ConfigTemplate{
   391  		NodeName:      nodeName,
   392  		ServerUrl:     serverUrl,
   393  		ClientKey:     clientKey,
   394  		SslVerifyMode: sslVerifyMode,
   395  	}
   396  	configString, err := interpolate.Render(tpl, &ctx)
   397  	if err != nil {
   398  		return "", err
   399  	}
   400  
   401  	remotePath := filepath.ToSlash(filepath.Join(p.config.StagingDir, "knife.rb"))
   402  	if err := comm.Upload(remotePath, bytes.NewReader([]byte(configString)), nil); err != nil {
   403  		return "", err
   404  	}
   405  
   406  	return remotePath, nil
   407  }
   408  
   409  func (p *Provisioner) createJson(ui packer.Ui, comm packer.Communicator) (string, error) {
   410  	ui.Message("Creating JSON attribute file")
   411  
   412  	jsonData := make(map[string]interface{})
   413  
   414  	// Copy the configured JSON
   415  	for k, v := range p.config.Json {
   416  		jsonData[k] = v
   417  	}
   418  
   419  	// Set the run list if it was specified
   420  	if len(p.config.RunList) > 0 {
   421  		jsonData["run_list"] = p.config.RunList
   422  	}
   423  
   424  	jsonBytes, err := json.MarshalIndent(jsonData, "", "  ")
   425  	if err != nil {
   426  		return "", err
   427  	}
   428  
   429  	// Upload the bytes
   430  	remotePath := filepath.ToSlash(filepath.Join(p.config.StagingDir, "first-boot.json"))
   431  	if err := comm.Upload(remotePath, bytes.NewReader(jsonBytes), nil); err != nil {
   432  		return "", err
   433  	}
   434  
   435  	return remotePath, nil
   436  }
   437  
   438  func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
   439  	ui.Message(fmt.Sprintf("Creating directory: %s", dir))
   440  
   441  	cmd := &packer.RemoteCmd{Command: p.guestCommands.CreateDir(dir)}
   442  	if err := cmd.StartWithUi(comm, ui); err != nil {
   443  		return err
   444  	}
   445  	if cmd.ExitStatus != 0 {
   446  		return fmt.Errorf("Non-zero exit status. See output above for more info.")
   447  	}
   448  
   449  	// Chmod the directory to 0777 just so that we can access it as our user
   450  	cmd = &packer.RemoteCmd{Command: p.guestCommands.Chmod(dir, "0777")}
   451  	if err := cmd.StartWithUi(comm, ui); err != nil {
   452  		return err
   453  	}
   454  	if cmd.ExitStatus != 0 {
   455  		return fmt.Errorf("Non-zero exit status. See output above for more info.")
   456  	}
   457  
   458  	return nil
   459  }
   460  
   461  func (p *Provisioner) cleanNode(ui packer.Ui, comm packer.Communicator, node string, knifeConfigPath string) error {
   462  	ui.Say("Cleaning up chef node...")
   463  	args := []string{"node", "delete", node}
   464  	if err := p.knifeExec(ui, comm, node, knifeConfigPath, args); err != nil {
   465  		return fmt.Errorf("Failed to cleanup node: %s", err)
   466  	}
   467  
   468  	return nil
   469  }
   470  
   471  func (p *Provisioner) cleanClient(ui packer.Ui, comm packer.Communicator, node string, knifeConfigPath string) error {
   472  	ui.Say("Cleaning up chef client...")
   473  	args := []string{"client", "delete", node}
   474  	if err := p.knifeExec(ui, comm, node, knifeConfigPath, args); err != nil {
   475  		return fmt.Errorf("Failed to cleanup client: %s", err)
   476  	}
   477  
   478  	return nil
   479  }
   480  
   481  func (p *Provisioner) knifeExec(ui packer.Ui, comm packer.Communicator, node string, knifeConfigPath string, args []string) error {
   482  	flags := []string{
   483  		"-y",
   484  		"-c", knifeConfigPath,
   485  	}
   486  
   487  	cmdText := fmt.Sprintf(
   488  		"knife %s %s", strings.Join(args, " "), strings.Join(flags, " "))
   489  	if !p.config.PreventSudo {
   490  		cmdText = "sudo " + cmdText
   491  	}
   492  
   493  	cmd := &packer.RemoteCmd{Command: cmdText}
   494  	if err := cmd.StartWithUi(comm, ui); err != nil {
   495  		return err
   496  	}
   497  	if cmd.ExitStatus != 0 {
   498  		return fmt.Errorf(
   499  			"Non-zero exit status. See output above for more info.\n\n"+
   500  				"Command: %s",
   501  			cmdText)
   502  	}
   503  
   504  	return nil
   505  }
   506  
   507  func (p *Provisioner) removeDir(ui packer.Ui, comm packer.Communicator, dir string) error {
   508  	ui.Message(fmt.Sprintf("Removing directory: %s", dir))
   509  
   510  	cmd := &packer.RemoteCmd{Command: p.guestCommands.RemoveDir(dir)}
   511  	if err := cmd.StartWithUi(comm, ui); err != nil {
   512  		return err
   513  	}
   514  
   515  	return nil
   516  }
   517  
   518  func (p *Provisioner) executeChef(ui packer.Ui, comm packer.Communicator, config string, json string) error {
   519  	p.config.ctx.Data = &ExecuteTemplate{
   520  		ConfigPath: config,
   521  		JsonPath:   json,
   522  		Sudo:       !p.config.PreventSudo,
   523  	}
   524  	command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx)
   525  	if err != nil {
   526  		return err
   527  	}
   528  
   529  	ui.Message(fmt.Sprintf("Executing Chef: %s", command))
   530  
   531  	cmd := &packer.RemoteCmd{
   532  		Command: command,
   533  	}
   534  
   535  	if err := cmd.StartWithUi(comm, ui); err != nil {
   536  		return err
   537  	}
   538  
   539  	if cmd.ExitStatus != 0 {
   540  		return fmt.Errorf("Non-zero exit status: %d", cmd.ExitStatus)
   541  	}
   542  
   543  	return nil
   544  }
   545  
   546  func (p *Provisioner) installChef(ui packer.Ui, comm packer.Communicator) error {
   547  	ui.Message("Installing Chef...")
   548  
   549  	p.config.ctx.Data = &InstallChefTemplate{
   550  		Sudo: !p.config.PreventSudo,
   551  	}
   552  	command, err := interpolate.Render(p.config.InstallCommand, &p.config.ctx)
   553  	if err != nil {
   554  		return err
   555  	}
   556  
   557  	ui.Message(command)
   558  
   559  	cmd := &packer.RemoteCmd{Command: command}
   560  	if err := cmd.StartWithUi(comm, ui); err != nil {
   561  		return err
   562  	}
   563  
   564  	if cmd.ExitStatus != 0 {
   565  		return fmt.Errorf(
   566  			"Install script exited with non-zero exit status %d", cmd.ExitStatus)
   567  	}
   568  
   569  	return nil
   570  }
   571  
   572  func (p *Provisioner) deepJsonFix(key string, current interface{}) (interface{}, error) {
   573  	if current == nil {
   574  		return nil, nil
   575  	}
   576  
   577  	switch c := current.(type) {
   578  	case []interface{}:
   579  		val := make([]interface{}, len(c))
   580  		for i, v := range c {
   581  			var err error
   582  			val[i], err = p.deepJsonFix(fmt.Sprintf("%s[%d]", key, i), v)
   583  			if err != nil {
   584  				return nil, err
   585  			}
   586  		}
   587  
   588  		return val, nil
   589  	case []uint8:
   590  		return string(c), nil
   591  	case map[interface{}]interface{}:
   592  		val := make(map[string]interface{})
   593  		for k, v := range c {
   594  			ks, ok := k.(string)
   595  			if !ok {
   596  				return nil, fmt.Errorf("%s: key is not string", key)
   597  			}
   598  
   599  			var err error
   600  			val[ks], err = p.deepJsonFix(
   601  				fmt.Sprintf("%s.%s", key, ks), v)
   602  			if err != nil {
   603  				return nil, err
   604  			}
   605  		}
   606  
   607  		return val, nil
   608  	default:
   609  		return current, nil
   610  	}
   611  }
   612  
   613  func (p *Provisioner) processJsonUserVars() (map[string]interface{}, error) {
   614  	jsonBytes, err := json.Marshal(p.config.Json)
   615  	if err != nil {
   616  		// This really shouldn't happen since we literally just unmarshalled
   617  		panic(err)
   618  	}
   619  
   620  	// Copy the user variables so that we can restore them later, and
   621  	// make sure we make the quotes JSON-friendly in the user variables.
   622  	originalUserVars := make(map[string]string)
   623  	for k, v := range p.config.ctx.UserVariables {
   624  		originalUserVars[k] = v
   625  	}
   626  
   627  	// Make sure we reset them no matter what
   628  	defer func() {
   629  		p.config.ctx.UserVariables = originalUserVars
   630  	}()
   631  
   632  	// Make the current user variables JSON string safe.
   633  	for k, v := range p.config.ctx.UserVariables {
   634  		v = strings.Replace(v, `\`, `\\`, -1)
   635  		v = strings.Replace(v, `"`, `\"`, -1)
   636  		p.config.ctx.UserVariables[k] = v
   637  	}
   638  
   639  	// Process the bytes with the template processor
   640  	p.config.ctx.Data = nil
   641  	jsonBytesProcessed, err := interpolate.Render(string(jsonBytes), &p.config.ctx)
   642  	if err != nil {
   643  		return nil, err
   644  	}
   645  
   646  	var result map[string]interface{}
   647  	if err := json.Unmarshal([]byte(jsonBytesProcessed), &result); err != nil {
   648  		return nil, err
   649  	}
   650  
   651  	return result, nil
   652  }
   653  
   654  var DefaultConfigTemplate = `
   655  log_level        :info
   656  log_location     STDOUT
   657  chef_server_url  "{{.ServerUrl}}"
   658  client_key       "{{.ClientKey}}"
   659  {{if ne .EncryptedDataBagSecretPath ""}}
   660  encrypted_data_bag_secret "{{.EncryptedDataBagSecretPath}}"
   661  {{end}}
   662  {{if ne .ValidationClientName ""}}
   663  validation_client_name "{{.ValidationClientName}}"
   664  {{else}}
   665  validation_client_name "chef-validator"
   666  {{end}}
   667  {{if ne .ValidationKeyPath ""}}
   668  validation_key "{{.ValidationKeyPath}}"
   669  {{end}}
   670  node_name "{{.NodeName}}"
   671  {{if ne .ChefEnvironment ""}}
   672  environment "{{.ChefEnvironment}}"
   673  {{end}}
   674  {{if ne .SslVerifyMode ""}}
   675  ssl_verify_mode :{{.SslVerifyMode}}
   676  {{end}}
   677  `
   678  
   679  var DefaultKnifeTemplate = `
   680  log_level        :info
   681  log_location     STDOUT
   682  chef_server_url  "{{.ServerUrl}}"
   683  client_key       "{{.ClientKey}}"
   684  node_name "{{.NodeName}}"
   685  {{if ne .SslVerifyMode ""}}
   686  ssl_verify_mode :{{.SslVerifyMode}}
   687  {{end}}
   688  `