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