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