github.com/amanya/packer@v0.12.1-0.20161117214323-902ac5ab2eb6/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  	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  	knifeConfigPath, err2 := p.createKnifeConfig(
   284  		ui, comm, nodeName, serverUrl, p.config.ClientKey, p.config.SslVerifyMode)
   285  	if err2 != nil {
   286  		return fmt.Errorf("Error creating knife config on node: %s", err2)
   287  	}
   288  	if !p.config.SkipCleanNode {
   289  		if err2 := p.cleanNode(ui, comm, nodeName, knifeConfigPath); err2 != nil {
   290  			return fmt.Errorf("Error cleaning up chef node: %s", err2)
   291  		}
   292  	}
   293  
   294  	if !p.config.SkipCleanClient {
   295  		if err2 := p.cleanClient(ui, comm, nodeName, knifeConfigPath); err2 != nil {
   296  			return fmt.Errorf("Error cleaning up chef client: %s", err2)
   297  		}
   298  	}
   299  
   300  	if err != nil {
   301  		return fmt.Errorf("Error executing Chef: %s", err)
   302  	}
   303  
   304  	if err := p.removeDir(ui, comm, p.config.StagingDir); err != nil {
   305  		return fmt.Errorf("Error removing %s: %s", p.config.StagingDir, err)
   306  	}
   307  
   308  	return nil
   309  }
   310  
   311  func (p *Provisioner) Cancel() {
   312  	// Just hard quit. It isn't a big deal if what we're doing keeps
   313  	// running on the other side.
   314  	os.Exit(0)
   315  }
   316  
   317  func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, dst string, src string) error {
   318  	if err := p.createDir(ui, comm, dst); err != nil {
   319  		return err
   320  	}
   321  
   322  	// Make sure there is a trailing "/" so that the directory isn't
   323  	// created on the other side.
   324  	if src[len(src)-1] != '/' {
   325  		src = src + "/"
   326  	}
   327  
   328  	return comm.UploadDir(dst, src, nil)
   329  }
   330  
   331  func (p *Provisioner) uploadFile(ui packer.Ui, comm packer.Communicator, remotePath string, localPath string) error {
   332  	ui.Message(fmt.Sprintf("Uploading %s...", localPath))
   333  
   334  	f, err := os.Open(localPath)
   335  	if err != nil {
   336  		return err
   337  	}
   338  	defer f.Close()
   339  
   340  	return comm.Upload(remotePath, f, nil)
   341  }
   342  
   343  func (p *Provisioner) createConfig(
   344  	ui packer.Ui,
   345  	comm packer.Communicator,
   346  	nodeName string,
   347  	serverUrl string,
   348  	clientKey string,
   349  	encryptedDataBagSecretPath,
   350  	remoteKeyPath string,
   351  	validationClientName string,
   352  	chefEnvironment string,
   353  	sslVerifyMode string) (string, error) {
   354  
   355  	ui.Message("Creating configuration file 'client.rb'")
   356  
   357  	// Read the template
   358  	tpl := DefaultConfigTemplate
   359  	if p.config.ConfigTemplate != "" {
   360  		f, err := os.Open(p.config.ConfigTemplate)
   361  		if err != nil {
   362  			return "", err
   363  		}
   364  		defer f.Close()
   365  
   366  		tplBytes, err := ioutil.ReadAll(f)
   367  		if err != nil {
   368  			return "", err
   369  		}
   370  
   371  		tpl = string(tplBytes)
   372  	}
   373  
   374  	ctx := p.config.ctx
   375  	ctx.Data = &ConfigTemplate{
   376  		NodeName:                   nodeName,
   377  		ServerUrl:                  serverUrl,
   378  		ClientKey:                  clientKey,
   379  		ValidationKeyPath:          remoteKeyPath,
   380  		ValidationClientName:       validationClientName,
   381  		ChefEnvironment:            chefEnvironment,
   382  		SslVerifyMode:              sslVerifyMode,
   383  		EncryptedDataBagSecretPath: encryptedDataBagSecretPath,
   384  	}
   385  	configString, err := interpolate.Render(tpl, &ctx)
   386  	if err != nil {
   387  		return "", err
   388  	}
   389  
   390  	remotePath := filepath.ToSlash(filepath.Join(p.config.StagingDir, "client.rb"))
   391  	if err := comm.Upload(remotePath, bytes.NewReader([]byte(configString)), nil); err != nil {
   392  		return "", err
   393  	}
   394  
   395  	return remotePath, nil
   396  }
   397  
   398  func (p *Provisioner) createKnifeConfig(ui packer.Ui, comm packer.Communicator, nodeName string, serverUrl string, clientKey string, sslVerifyMode string) (string, error) {
   399  	ui.Message("Creating configuration file 'knife.rb'")
   400  
   401  	// Read the template
   402  	tpl := DefaultKnifeTemplate
   403  
   404  	ctx := p.config.ctx
   405  	ctx.Data = &ConfigTemplate{
   406  		NodeName:      nodeName,
   407  		ServerUrl:     serverUrl,
   408  		ClientKey:     clientKey,
   409  		SslVerifyMode: sslVerifyMode,
   410  	}
   411  	configString, err := interpolate.Render(tpl, &ctx)
   412  	if err != nil {
   413  		return "", err
   414  	}
   415  
   416  	remotePath := filepath.ToSlash(filepath.Join(p.config.StagingDir, "knife.rb"))
   417  	if err := comm.Upload(remotePath, bytes.NewReader([]byte(configString)), nil); err != nil {
   418  		return "", err
   419  	}
   420  
   421  	return remotePath, nil
   422  }
   423  
   424  func (p *Provisioner) createJson(ui packer.Ui, comm packer.Communicator) (string, error) {
   425  	ui.Message("Creating JSON attribute file")
   426  
   427  	jsonData := make(map[string]interface{})
   428  
   429  	// Copy the configured JSON
   430  	for k, v := range p.config.Json {
   431  		jsonData[k] = v
   432  	}
   433  
   434  	// Set the run list if it was specified
   435  	if len(p.config.RunList) > 0 {
   436  		jsonData["run_list"] = p.config.RunList
   437  	}
   438  
   439  	jsonBytes, err := json.MarshalIndent(jsonData, "", "  ")
   440  	if err != nil {
   441  		return "", err
   442  	}
   443  
   444  	// Upload the bytes
   445  	remotePath := filepath.ToSlash(filepath.Join(p.config.StagingDir, "first-boot.json"))
   446  	if err := comm.Upload(remotePath, bytes.NewReader(jsonBytes), nil); err != nil {
   447  		return "", err
   448  	}
   449  
   450  	return remotePath, nil
   451  }
   452  
   453  func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
   454  	ui.Message(fmt.Sprintf("Creating directory: %s", dir))
   455  
   456  	cmd := &packer.RemoteCmd{Command: p.guestCommands.CreateDir(dir)}
   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  	// Chmod the directory to 0777 just so that we can access it as our user
   465  	cmd = &packer.RemoteCmd{Command: p.guestCommands.Chmod(dir, "0777")}
   466  	if err := cmd.StartWithUi(comm, ui); err != nil {
   467  		return err
   468  	}
   469  	if cmd.ExitStatus != 0 {
   470  		return fmt.Errorf("Non-zero exit status. See output above for more info.")
   471  	}
   472  
   473  	return nil
   474  }
   475  
   476  func (p *Provisioner) cleanNode(ui packer.Ui, comm packer.Communicator, node string, knifeConfigPath string) error {
   477  	ui.Say("Cleaning up chef node...")
   478  	args := []string{"node", "delete", node}
   479  	if err := p.knifeExec(ui, comm, node, knifeConfigPath, args); err != nil {
   480  		return fmt.Errorf("Failed to cleanup node: %s", err)
   481  	}
   482  
   483  	return nil
   484  }
   485  
   486  func (p *Provisioner) cleanClient(ui packer.Ui, comm packer.Communicator, node string, knifeConfigPath string) error {
   487  	ui.Say("Cleaning up chef client...")
   488  	args := []string{"client", "delete", node}
   489  	if err := p.knifeExec(ui, comm, node, knifeConfigPath, args); err != nil {
   490  		return fmt.Errorf("Failed to cleanup client: %s", err)
   491  	}
   492  
   493  	return nil
   494  }
   495  
   496  func (p *Provisioner) knifeExec(ui packer.Ui, comm packer.Communicator, node string, knifeConfigPath string, args []string) error {
   497  	flags := []string{
   498  		"-y",
   499  		"-c", knifeConfigPath,
   500  	}
   501  
   502  	p.config.ctx.Data = &KnifeTemplate{
   503  		Sudo:  !p.config.PreventSudo,
   504  		Flags: strings.Join(flags, " "),
   505  		Args:  strings.Join(args, " "),
   506  	}
   507  
   508  	command, err := interpolate.Render(p.config.KnifeCommand, &p.config.ctx)
   509  	if err != nil {
   510  		return err
   511  	}
   512  
   513  	cmd := &packer.RemoteCmd{Command: command}
   514  	if err := cmd.StartWithUi(comm, ui); err != nil {
   515  		return err
   516  	}
   517  	if cmd.ExitStatus != 0 {
   518  		return fmt.Errorf(
   519  			"Non-zero exit status. See output above for more info.\n\n"+
   520  				"Command: %s",
   521  			command)
   522  	}
   523  
   524  	return nil
   525  }
   526  
   527  func (p *Provisioner) removeDir(ui packer.Ui, comm packer.Communicator, dir string) error {
   528  	ui.Message(fmt.Sprintf("Removing directory: %s", dir))
   529  
   530  	cmd := &packer.RemoteCmd{Command: p.guestCommands.RemoveDir(dir)}
   531  	if err := cmd.StartWithUi(comm, ui); err != nil {
   532  		return err
   533  	}
   534  
   535  	return nil
   536  }
   537  
   538  func (p *Provisioner) executeChef(ui packer.Ui, comm packer.Communicator, config string, json string) error {
   539  	p.config.ctx.Data = &ExecuteTemplate{
   540  		ConfigPath: config,
   541  		JsonPath:   json,
   542  		Sudo:       !p.config.PreventSudo,
   543  	}
   544  	command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx)
   545  	if err != nil {
   546  		return err
   547  	}
   548  
   549  	ui.Message(fmt.Sprintf("Executing Chef: %s", command))
   550  
   551  	cmd := &packer.RemoteCmd{
   552  		Command: command,
   553  	}
   554  
   555  	if err := cmd.StartWithUi(comm, ui); err != nil {
   556  		return err
   557  	}
   558  
   559  	if cmd.ExitStatus != 0 {
   560  		return fmt.Errorf("Non-zero exit status: %d", cmd.ExitStatus)
   561  	}
   562  
   563  	return nil
   564  }
   565  
   566  func (p *Provisioner) installChef(ui packer.Ui, comm packer.Communicator) error {
   567  	ui.Message("Installing Chef...")
   568  
   569  	p.config.ctx.Data = &InstallChefTemplate{
   570  		Sudo: !p.config.PreventSudo,
   571  	}
   572  	command, err := interpolate.Render(p.config.InstallCommand, &p.config.ctx)
   573  	if err != nil {
   574  		return err
   575  	}
   576  
   577  	ui.Message(command)
   578  
   579  	cmd := &packer.RemoteCmd{Command: command}
   580  	if err := cmd.StartWithUi(comm, ui); err != nil {
   581  		return err
   582  	}
   583  
   584  	if cmd.ExitStatus != 0 {
   585  		return fmt.Errorf(
   586  			"Install script exited with non-zero exit status %d", cmd.ExitStatus)
   587  	}
   588  
   589  	return nil
   590  }
   591  
   592  func (p *Provisioner) deepJsonFix(key string, current interface{}) (interface{}, error) {
   593  	if current == nil {
   594  		return nil, nil
   595  	}
   596  
   597  	switch c := current.(type) {
   598  	case []interface{}:
   599  		val := make([]interface{}, len(c))
   600  		for i, v := range c {
   601  			var err error
   602  			val[i], err = p.deepJsonFix(fmt.Sprintf("%s[%d]", key, i), v)
   603  			if err != nil {
   604  				return nil, err
   605  			}
   606  		}
   607  
   608  		return val, nil
   609  	case []uint8:
   610  		return string(c), nil
   611  	case map[interface{}]interface{}:
   612  		val := make(map[string]interface{})
   613  		for k, v := range c {
   614  			ks, ok := k.(string)
   615  			if !ok {
   616  				return nil, fmt.Errorf("%s: key is not string", key)
   617  			}
   618  
   619  			var err error
   620  			val[ks], err = p.deepJsonFix(
   621  				fmt.Sprintf("%s.%s", key, ks), v)
   622  			if err != nil {
   623  				return nil, err
   624  			}
   625  		}
   626  
   627  		return val, nil
   628  	default:
   629  		return current, nil
   630  	}
   631  }
   632  
   633  func (p *Provisioner) processJsonUserVars() (map[string]interface{}, error) {
   634  	jsonBytes, err := json.Marshal(p.config.Json)
   635  	if err != nil {
   636  		// This really shouldn't happen since we literally just unmarshalled
   637  		panic(err)
   638  	}
   639  
   640  	// Copy the user variables so that we can restore them later, and
   641  	// make sure we make the quotes JSON-friendly in the user variables.
   642  	originalUserVars := make(map[string]string)
   643  	for k, v := range p.config.ctx.UserVariables {
   644  		originalUserVars[k] = v
   645  	}
   646  
   647  	// Make sure we reset them no matter what
   648  	defer func() {
   649  		p.config.ctx.UserVariables = originalUserVars
   650  	}()
   651  
   652  	// Make the current user variables JSON string safe.
   653  	for k, v := range p.config.ctx.UserVariables {
   654  		v = strings.Replace(v, `\`, `\\`, -1)
   655  		v = strings.Replace(v, `"`, `\"`, -1)
   656  		p.config.ctx.UserVariables[k] = v
   657  	}
   658  
   659  	// Process the bytes with the template processor
   660  	p.config.ctx.Data = nil
   661  	jsonBytesProcessed, err := interpolate.Render(string(jsonBytes), &p.config.ctx)
   662  	if err != nil {
   663  		return nil, err
   664  	}
   665  
   666  	var result map[string]interface{}
   667  	if err := json.Unmarshal([]byte(jsonBytesProcessed), &result); err != nil {
   668  		return nil, err
   669  	}
   670  
   671  	return result, nil
   672  }
   673  
   674  var DefaultConfigTemplate = `
   675  log_level        :info
   676  log_location     STDOUT
   677  chef_server_url  "{{.ServerUrl}}"
   678  client_key       "{{.ClientKey}}"
   679  {{if ne .EncryptedDataBagSecretPath ""}}
   680  encrypted_data_bag_secret "{{.EncryptedDataBagSecretPath}}"
   681  {{end}}
   682  {{if ne .ValidationClientName ""}}
   683  validation_client_name "{{.ValidationClientName}}"
   684  {{else}}
   685  validation_client_name "chef-validator"
   686  {{end}}
   687  {{if ne .ValidationKeyPath ""}}
   688  validation_key "{{.ValidationKeyPath}}"
   689  {{end}}
   690  node_name "{{.NodeName}}"
   691  {{if ne .ChefEnvironment ""}}
   692  environment "{{.ChefEnvironment}}"
   693  {{end}}
   694  {{if ne .SslVerifyMode ""}}
   695  ssl_verify_mode :{{.SslVerifyMode}}
   696  {{end}}
   697  `
   698  
   699  var DefaultKnifeTemplate = `
   700  log_level        :info
   701  log_location     STDOUT
   702  chef_server_url  "{{.ServerUrl}}"
   703  client_key       "{{.ClientKey}}"
   704  node_name "{{.NodeName}}"
   705  {{if ne .SslVerifyMode ""}}
   706  ssl_verify_mode :{{.SslVerifyMode}}
   707  {{end}}
   708  `