github.com/lymingtonprecision/terraform@v0.9.9-0.20170613092852-62acef9611a9/builtin/provisioners/chef/resource_provisioner.go (about)

     1  package chef
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"log"
    11  	"os"
    12  	"path"
    13  	"regexp"
    14  	"strings"
    15  	"sync"
    16  	"text/template"
    17  	"time"
    18  
    19  	"github.com/hashicorp/terraform/communicator"
    20  	"github.com/hashicorp/terraform/communicator/remote"
    21  	"github.com/hashicorp/terraform/helper/schema"
    22  	"github.com/hashicorp/terraform/terraform"
    23  	"github.com/mitchellh/go-homedir"
    24  	"github.com/mitchellh/go-linereader"
    25  )
    26  
    27  const (
    28  	clienrb         = "client.rb"
    29  	defaultEnv      = "_default"
    30  	firstBoot       = "first-boot.json"
    31  	logfileDir      = "logfiles"
    32  	linuxChefCmd    = "chef-client"
    33  	linuxConfDir    = "/etc/chef"
    34  	linuxNoOutput   = "> /dev/null 2>&1"
    35  	linuxGemCmd     = "/opt/chef/embedded/bin/gem"
    36  	linuxKnifeCmd   = "knife"
    37  	secretKey       = "encrypted_data_bag_secret"
    38  	windowsChefCmd  = "cmd /c chef-client"
    39  	windowsConfDir  = "C:/chef"
    40  	windowsNoOutput = "> nul 2>&1"
    41  	windowsGemCmd   = "C:/opscode/chef/embedded/bin/gem"
    42  	windowsKnifeCmd = "cmd /c knife"
    43  )
    44  
    45  const clientConf = `
    46  log_location            STDOUT
    47  chef_server_url         "{{ .ServerURL }}"
    48  node_name               "{{ .NodeName }}"
    49  {{ if .UsePolicyfile }}
    50  use_policyfile true
    51  policy_group 	 "{{ .PolicyGroup }}"
    52  policy_name 	 "{{ .PolicyName }}"
    53  {{ end -}}
    54  
    55  {{ if .HTTPProxy }}
    56  http_proxy          "{{ .HTTPProxy }}"
    57  ENV['http_proxy'] = "{{ .HTTPProxy }}"
    58  ENV['HTTP_PROXY'] = "{{ .HTTPProxy }}"
    59  {{ end -}}
    60  
    61  {{ if .HTTPSProxy }}
    62  https_proxy          "{{ .HTTPSProxy }}"
    63  ENV['https_proxy'] = "{{ .HTTPSProxy }}"
    64  ENV['HTTPS_PROXY'] = "{{ .HTTPSProxy }}"
    65  {{ end -}}
    66  
    67  {{ if .NOProxy }}
    68  no_proxy          "{{ join .NOProxy "," }}"
    69  ENV['no_proxy'] = "{{ join .NOProxy "," }}"
    70  {{ end -}}
    71  
    72  {{ if .SSLVerifyMode }}
    73  ssl_verify_mode  {{ .SSLVerifyMode }}
    74  {{- end -}}
    75  
    76  {{ if .DisableReporting }}
    77  enable_reporting false
    78  {{ end -}}
    79  
    80  {{ if .ClientOptions }}
    81  {{ join .ClientOptions "\n" }}
    82  {{ end }}
    83  `
    84  
    85  type provisionFn func(terraform.UIOutput, communicator.Communicator) error
    86  
    87  type provisioner struct {
    88  	Attributes            map[string]interface{}
    89  	ClientOptions         []string
    90  	DisableReporting      bool
    91  	Environment           string
    92  	FetchChefCertificates bool
    93  	LogToFile             bool
    94  	UsePolicyfile         bool
    95  	PolicyGroup           string
    96  	PolicyName            string
    97  	HTTPProxy             string
    98  	HTTPSProxy            string
    99  	NamedRunList          string
   100  	NOProxy               []string
   101  	NodeName              string
   102  	OhaiHints             []string
   103  	OSType                string
   104  	RecreateClient        bool
   105  	PreventSudo           bool
   106  	RunList               []string
   107  	SecretKey             string
   108  	ServerURL             string
   109  	SkipInstall           bool
   110  	SkipRegister          bool
   111  	SSLVerifyMode         string
   112  	UserName              string
   113  	UserKey               string
   114  	Vaults                map[string][]string
   115  	Version               string
   116  
   117  	cleanupUserKeyCmd     string
   118  	createConfigFiles     provisionFn
   119  	installChefClient     provisionFn
   120  	fetchChefCertificates provisionFn
   121  	generateClientKey     provisionFn
   122  	configureVaults       provisionFn
   123  	runChefClient         provisionFn
   124  	useSudo               bool
   125  }
   126  
   127  // Provisioner returns a Chef provisioner
   128  func Provisioner() terraform.ResourceProvisioner {
   129  	return &schema.Provisioner{
   130  		Schema: map[string]*schema.Schema{
   131  			"node_name": &schema.Schema{
   132  				Type:     schema.TypeString,
   133  				Required: true,
   134  			},
   135  			"server_url": &schema.Schema{
   136  				Type:     schema.TypeString,
   137  				Required: true,
   138  			},
   139  			"user_name": &schema.Schema{
   140  				Type:     schema.TypeString,
   141  				Required: true,
   142  			},
   143  			"user_key": &schema.Schema{
   144  				Type:     schema.TypeString,
   145  				Required: true,
   146  			},
   147  
   148  			"attributes_json": &schema.Schema{
   149  				Type:     schema.TypeString,
   150  				Optional: true,
   151  			},
   152  			"client_options": &schema.Schema{
   153  				Type:     schema.TypeList,
   154  				Elem:     &schema.Schema{Type: schema.TypeString},
   155  				Optional: true,
   156  			},
   157  			"disable_reporting": &schema.Schema{
   158  				Type:     schema.TypeBool,
   159  				Optional: true,
   160  			},
   161  			"environment": &schema.Schema{
   162  				Type:     schema.TypeString,
   163  				Optional: true,
   164  				Default:  defaultEnv,
   165  			},
   166  			"fetch_chef_certificates": &schema.Schema{
   167  				Type:     schema.TypeBool,
   168  				Optional: true,
   169  			},
   170  			"log_to_file": &schema.Schema{
   171  				Type:     schema.TypeBool,
   172  				Optional: true,
   173  			},
   174  			"use_policyfile": &schema.Schema{
   175  				Type:     schema.TypeBool,
   176  				Optional: true,
   177  			},
   178  			"policy_group": &schema.Schema{
   179  				Type:     schema.TypeString,
   180  				Optional: true,
   181  			},
   182  			"policy_name": &schema.Schema{
   183  				Type:     schema.TypeString,
   184  				Optional: true,
   185  			},
   186  			"http_proxy": &schema.Schema{
   187  				Type:     schema.TypeString,
   188  				Optional: true,
   189  			},
   190  			"https_proxy": &schema.Schema{
   191  				Type:     schema.TypeString,
   192  				Optional: true,
   193  			},
   194  			"no_proxy": &schema.Schema{
   195  				Type:     schema.TypeList,
   196  				Elem:     &schema.Schema{Type: schema.TypeString},
   197  				Optional: true,
   198  			},
   199  			"named_run_list": &schema.Schema{
   200  				Type:     schema.TypeString,
   201  				Optional: true,
   202  			},
   203  			"ohai_hints": &schema.Schema{
   204  				Type:     schema.TypeList,
   205  				Elem:     &schema.Schema{Type: schema.TypeString},
   206  				Optional: true,
   207  			},
   208  			"os_type": &schema.Schema{
   209  				Type:     schema.TypeString,
   210  				Optional: true,
   211  			},
   212  			"recreate_client": &schema.Schema{
   213  				Type:     schema.TypeBool,
   214  				Optional: true,
   215  			},
   216  			"prevent_sudo": &schema.Schema{
   217  				Type:     schema.TypeBool,
   218  				Optional: true,
   219  			},
   220  			"run_list": &schema.Schema{
   221  				Type:     schema.TypeList,
   222  				Elem:     &schema.Schema{Type: schema.TypeString},
   223  				Optional: true,
   224  			},
   225  			"secret_key": &schema.Schema{
   226  				Type:     schema.TypeString,
   227  				Optional: true,
   228  			},
   229  			"skip_install": &schema.Schema{
   230  				Type:     schema.TypeBool,
   231  				Optional: true,
   232  			},
   233  			"skip_register": &schema.Schema{
   234  				Type:     schema.TypeBool,
   235  				Optional: true,
   236  			},
   237  			"ssl_verify_mode": &schema.Schema{
   238  				Type:     schema.TypeString,
   239  				Optional: true,
   240  			},
   241  			"vault_json": &schema.Schema{
   242  				Type:     schema.TypeString,
   243  				Optional: true,
   244  			},
   245  			"version": &schema.Schema{
   246  				Type:     schema.TypeString,
   247  				Optional: true,
   248  			},
   249  		},
   250  
   251  		ApplyFunc:    applyFn,
   252  		ValidateFunc: validateFn,
   253  	}
   254  }
   255  
   256  // TODO: Support context cancelling (Provisioner Stop)
   257  func applyFn(ctx context.Context) error {
   258  	o := ctx.Value(schema.ProvOutputKey).(terraform.UIOutput)
   259  	d := ctx.Value(schema.ProvConfigDataKey).(*schema.ResourceData)
   260  
   261  	// Decode the raw config for this provisioner
   262  	p, err := decodeConfig(d)
   263  	if err != nil {
   264  		return err
   265  	}
   266  
   267  	if p.OSType == "" {
   268  		switch t := d.State().Ephemeral.ConnInfo["type"]; t {
   269  		case "ssh", "": // The default connection type is ssh, so if the type is empty assume ssh
   270  			p.OSType = "linux"
   271  		case "winrm":
   272  			p.OSType = "windows"
   273  		default:
   274  			return fmt.Errorf("Unsupported connection type: %s", t)
   275  		}
   276  	}
   277  
   278  	// Set some values based on the targeted OS
   279  	switch p.OSType {
   280  	case "linux":
   281  		p.cleanupUserKeyCmd = fmt.Sprintf("rm -f %s", path.Join(linuxConfDir, p.UserName+".pem"))
   282  		p.createConfigFiles = p.linuxCreateConfigFiles
   283  		p.installChefClient = p.linuxInstallChefClient
   284  		p.fetchChefCertificates = p.fetchChefCertificatesFunc(linuxKnifeCmd, linuxConfDir)
   285  		p.generateClientKey = p.generateClientKeyFunc(linuxKnifeCmd, linuxConfDir, linuxNoOutput)
   286  		p.configureVaults = p.configureVaultsFunc(linuxGemCmd, linuxKnifeCmd, linuxConfDir)
   287  		p.runChefClient = p.runChefClientFunc(linuxChefCmd, linuxConfDir)
   288  		p.useSudo = !p.PreventSudo && d.State().Ephemeral.ConnInfo["user"] != "root"
   289  	case "windows":
   290  		p.cleanupUserKeyCmd = fmt.Sprintf("cd %s && del /F /Q %s", windowsConfDir, p.UserName+".pem")
   291  		p.createConfigFiles = p.windowsCreateConfigFiles
   292  		p.installChefClient = p.windowsInstallChefClient
   293  		p.fetchChefCertificates = p.fetchChefCertificatesFunc(windowsKnifeCmd, windowsConfDir)
   294  		p.generateClientKey = p.generateClientKeyFunc(windowsKnifeCmd, windowsConfDir, windowsNoOutput)
   295  		p.configureVaults = p.configureVaultsFunc(windowsGemCmd, windowsKnifeCmd, windowsConfDir)
   296  		p.runChefClient = p.runChefClientFunc(windowsChefCmd, windowsConfDir)
   297  		p.useSudo = false
   298  	default:
   299  		return fmt.Errorf("Unsupported os type: %s", p.OSType)
   300  	}
   301  
   302  	// Get a new communicator
   303  	comm, err := communicator.New(d.State())
   304  	if err != nil {
   305  		return err
   306  	}
   307  
   308  	// Wait and retry until we establish the connection
   309  	err = retryFunc(comm.Timeout(), func() error {
   310  		return comm.Connect(o)
   311  	})
   312  	if err != nil {
   313  		return err
   314  	}
   315  	defer comm.Disconnect()
   316  
   317  	// Make sure we always delete the user key from the new node!
   318  	var once sync.Once
   319  	cleanupUserKey := func() {
   320  		o.Output("Cleanup user key...")
   321  		if err := p.runCommand(o, comm, p.cleanupUserKeyCmd); err != nil {
   322  			o.Output("WARNING: Failed to cleanup user key on new node: " + err.Error())
   323  		}
   324  	}
   325  	defer once.Do(cleanupUserKey)
   326  
   327  	if !p.SkipInstall {
   328  		if err := p.installChefClient(o, comm); err != nil {
   329  			return err
   330  		}
   331  	}
   332  
   333  	o.Output("Creating configuration files...")
   334  	if err := p.createConfigFiles(o, comm); err != nil {
   335  		return err
   336  	}
   337  
   338  	if !p.SkipRegister {
   339  		if p.FetchChefCertificates {
   340  			o.Output("Fetch Chef certificates...")
   341  			if err := p.fetchChefCertificates(o, comm); err != nil {
   342  				return err
   343  			}
   344  		}
   345  
   346  		o.Output("Generate the private key...")
   347  		if err := p.generateClientKey(o, comm); err != nil {
   348  			return err
   349  		}
   350  	}
   351  
   352  	if p.Vaults != nil {
   353  		o.Output("Configure Chef vaults...")
   354  		if err := p.configureVaults(o, comm); err != nil {
   355  			return err
   356  		}
   357  	}
   358  
   359  	// Cleanup the user key before we run Chef-Client to prevent issues
   360  	// with rights caused by changing settings during the run.
   361  	once.Do(cleanupUserKey)
   362  
   363  	o.Output("Starting initial Chef-Client run...")
   364  	if err := p.runChefClient(o, comm); err != nil {
   365  		return err
   366  	}
   367  
   368  	return nil
   369  }
   370  
   371  func validateFn(d *schema.ResourceData) (ws []string, es []error) {
   372  	p, err := decodeConfig(d)
   373  	if err != nil {
   374  		es = append(es, err)
   375  		return ws, es
   376  	}
   377  
   378  	if !p.UsePolicyfile && p.RunList == nil {
   379  		es = append(es, errors.New("Key not found: run_list"))
   380  	}
   381  	if p.UsePolicyfile && p.PolicyName == "" {
   382  		es = append(es, errors.New("Policyfile enabled but key not found: policy_name"))
   383  	}
   384  	if p.UsePolicyfile && p.PolicyGroup == "" {
   385  		es = append(es, errors.New("Policyfile enabled but key not found: policy_group"))
   386  	}
   387  
   388  	return ws, es
   389  }
   390  
   391  func (p *provisioner) deployConfigFiles(o terraform.UIOutput, comm communicator.Communicator, confDir string) error {
   392  	// Copy the user key to the new instance
   393  	pk := strings.NewReader(p.UserKey)
   394  	if err := comm.Upload(path.Join(confDir, p.UserName+".pem"), pk); err != nil {
   395  		return fmt.Errorf("Uploading user key failed: %v", err)
   396  	}
   397  
   398  	if p.SecretKey != "" {
   399  		// Copy the secret key to the new instance
   400  		s := strings.NewReader(p.SecretKey)
   401  		if err := comm.Upload(path.Join(confDir, secretKey), s); err != nil {
   402  			return fmt.Errorf("Uploading %s failed: %v", secretKey, err)
   403  		}
   404  	}
   405  
   406  	// Make sure the SSLVerifyMode value is written as a symbol
   407  	if p.SSLVerifyMode != "" && !strings.HasPrefix(p.SSLVerifyMode, ":") {
   408  		p.SSLVerifyMode = fmt.Sprintf(":%s", p.SSLVerifyMode)
   409  	}
   410  
   411  	// Make strings.Join available for use within the template
   412  	funcMap := template.FuncMap{
   413  		"join": strings.Join,
   414  	}
   415  
   416  	// Create a new template and parse the client config into it
   417  	t := template.Must(template.New(clienrb).Funcs(funcMap).Parse(clientConf))
   418  
   419  	var buf bytes.Buffer
   420  	err := t.Execute(&buf, p)
   421  	if err != nil {
   422  		return fmt.Errorf("Error executing %s template: %s", clienrb, err)
   423  	}
   424  
   425  	// Copy the client config to the new instance
   426  	if err = comm.Upload(path.Join(confDir, clienrb), &buf); err != nil {
   427  		return fmt.Errorf("Uploading %s failed: %v", clienrb, err)
   428  	}
   429  
   430  	// Create a map with first boot settings
   431  	fb := make(map[string]interface{})
   432  	if p.Attributes != nil {
   433  		fb = p.Attributes
   434  	}
   435  
   436  	// Check if the run_list was also in the attributes and if so log a warning
   437  	// that it will be overwritten with the value of the run_list argument.
   438  	if _, found := fb["run_list"]; found {
   439  		log.Printf("[WARNING] Found a 'run_list' specified in the configured attributes! " +
   440  			"This value will be overwritten by the value of the `run_list` argument!")
   441  	}
   442  
   443  	// Add the initial runlist to the first boot settings
   444  	if !p.UsePolicyfile {
   445  		fb["run_list"] = p.RunList
   446  	}
   447  
   448  	// Marshal the first boot settings to JSON
   449  	d, err := json.Marshal(fb)
   450  	if err != nil {
   451  		return fmt.Errorf("Failed to create %s data: %s", firstBoot, err)
   452  	}
   453  
   454  	// Copy the first-boot.json to the new instance
   455  	if err := comm.Upload(path.Join(confDir, firstBoot), bytes.NewReader(d)); err != nil {
   456  		return fmt.Errorf("Uploading %s failed: %v", firstBoot, err)
   457  	}
   458  
   459  	return nil
   460  }
   461  
   462  func (p *provisioner) deployOhaiHints(o terraform.UIOutput, comm communicator.Communicator, hintDir string) error {
   463  	for _, hint := range p.OhaiHints {
   464  		// Open the hint file
   465  		f, err := os.Open(hint)
   466  		if err != nil {
   467  			return err
   468  		}
   469  		defer f.Close()
   470  
   471  		// Copy the hint to the new instance
   472  		if err := comm.Upload(path.Join(hintDir, path.Base(hint)), f); err != nil {
   473  			return fmt.Errorf("Uploading %s failed: %v", path.Base(hint), err)
   474  		}
   475  	}
   476  
   477  	return nil
   478  }
   479  
   480  func (p *provisioner) fetchChefCertificatesFunc(
   481  	knifeCmd string,
   482  	confDir string) func(terraform.UIOutput, communicator.Communicator) error {
   483  	return func(o terraform.UIOutput, comm communicator.Communicator) error {
   484  		clientrb := path.Join(confDir, clienrb)
   485  		cmd := fmt.Sprintf("%s ssl fetch -c %s", knifeCmd, clientrb)
   486  
   487  		return p.runCommand(o, comm, cmd)
   488  	}
   489  }
   490  
   491  func (p *provisioner) generateClientKeyFunc(knifeCmd string, confDir string, noOutput string) provisionFn {
   492  	return func(o terraform.UIOutput, comm communicator.Communicator) error {
   493  		options := fmt.Sprintf("-c %s -u %s --key %s",
   494  			path.Join(confDir, clienrb),
   495  			p.UserName,
   496  			path.Join(confDir, p.UserName+".pem"),
   497  		)
   498  
   499  		// See if we already have a node object
   500  		getNodeCmd := fmt.Sprintf("%s node show %s %s %s", knifeCmd, p.NodeName, options, noOutput)
   501  		node := p.runCommand(o, comm, getNodeCmd) == nil
   502  
   503  		// See if we already have a client object
   504  		getClientCmd := fmt.Sprintf("%s client show %s %s %s", knifeCmd, p.NodeName, options, noOutput)
   505  		client := p.runCommand(o, comm, getClientCmd) == nil
   506  
   507  		// If we have a client, we can only continue if we are to recreate the client
   508  		if client && !p.RecreateClient {
   509  			return fmt.Errorf(
   510  				"Chef client %q already exists, set recreate_client=true to automatically recreate the client", p.NodeName)
   511  		}
   512  
   513  		// If the node exists, try to delete it
   514  		if node {
   515  			deleteNodeCmd := fmt.Sprintf("%s node delete %s -y %s",
   516  				knifeCmd,
   517  				p.NodeName,
   518  				options,
   519  			)
   520  			if err := p.runCommand(o, comm, deleteNodeCmd); err != nil {
   521  				return err
   522  			}
   523  		}
   524  
   525  		// If the client exists, try to delete it
   526  		if client {
   527  			deleteClientCmd := fmt.Sprintf("%s client delete %s -y %s",
   528  				knifeCmd,
   529  				p.NodeName,
   530  				options,
   531  			)
   532  			if err := p.runCommand(o, comm, deleteClientCmd); err != nil {
   533  				return err
   534  			}
   535  		}
   536  
   537  		// Create the new client object
   538  		createClientCmd := fmt.Sprintf("%s client create %s -d -f %s %s",
   539  			knifeCmd,
   540  			p.NodeName,
   541  			path.Join(confDir, "client.pem"),
   542  			options,
   543  		)
   544  
   545  		return p.runCommand(o, comm, createClientCmd)
   546  	}
   547  }
   548  
   549  func (p *provisioner) configureVaultsFunc(gemCmd string, knifeCmd string, confDir string) provisionFn {
   550  	return func(o terraform.UIOutput, comm communicator.Communicator) error {
   551  		if err := p.runCommand(o, comm, fmt.Sprintf("%s install chef-vault", gemCmd)); err != nil {
   552  			return err
   553  		}
   554  
   555  		options := fmt.Sprintf("-c %s -u %s --key %s",
   556  			path.Join(confDir, clienrb),
   557  			p.UserName,
   558  			path.Join(confDir, p.UserName+".pem"),
   559  		)
   560  
   561  		for vault, items := range p.Vaults {
   562  			for _, item := range items {
   563  				updateCmd := fmt.Sprintf("%s vault update %s %s -C %s -M client %s",
   564  					knifeCmd,
   565  					vault,
   566  					item,
   567  					p.NodeName,
   568  					options,
   569  				)
   570  				if err := p.runCommand(o, comm, updateCmd); err != nil {
   571  					return err
   572  				}
   573  			}
   574  		}
   575  
   576  		return nil
   577  	}
   578  }
   579  
   580  func (p *provisioner) runChefClientFunc(chefCmd string, confDir string) provisionFn {
   581  	return func(o terraform.UIOutput, comm communicator.Communicator) error {
   582  		fb := path.Join(confDir, firstBoot)
   583  		var cmd string
   584  
   585  		// Policyfiles do not support chef environments, so don't pass the `-E` flag.
   586  		switch {
   587  		case p.UsePolicyfile && p.NamedRunList == "":
   588  			cmd = fmt.Sprintf("%s -j %q", chefCmd, fb)
   589  		case p.UsePolicyfile && p.NamedRunList != "":
   590  			cmd = fmt.Sprintf("%s -j %q -n %q", chefCmd, fb, p.NamedRunList)
   591  		default:
   592  			cmd = fmt.Sprintf("%s -j %q -E %q", chefCmd, fb, p.Environment)
   593  		}
   594  
   595  		if p.LogToFile {
   596  			if err := os.MkdirAll(logfileDir, 0755); err != nil {
   597  				return fmt.Errorf("Error creating logfile directory %s: %v", logfileDir, err)
   598  			}
   599  
   600  			logFile := path.Join(logfileDir, p.NodeName)
   601  			f, err := os.Create(path.Join(logFile))
   602  			if err != nil {
   603  				return fmt.Errorf("Error creating logfile %s: %v", logFile, err)
   604  			}
   605  			f.Close()
   606  
   607  			o.Output("Writing Chef Client output to " + logFile)
   608  			o = p
   609  		}
   610  
   611  		return p.runCommand(o, comm, cmd)
   612  	}
   613  }
   614  
   615  // Output implementation of terraform.UIOutput interface
   616  func (p *provisioner) Output(output string) {
   617  	logFile := path.Join(logfileDir, p.NodeName)
   618  	f, err := os.OpenFile(logFile, os.O_APPEND|os.O_WRONLY, 0666)
   619  	if err != nil {
   620  		log.Printf("Error creating logfile %s: %v", logFile, err)
   621  		return
   622  	}
   623  	defer f.Close()
   624  
   625  	// These steps are needed to remove any ANSI escape codes used to colorize
   626  	// the output and to make sure we have proper line endings before writing
   627  	// the string to the logfile.
   628  	re := regexp.MustCompile(`\x1b\[[0-9;]+m`)
   629  	output = re.ReplaceAllString(output, "")
   630  	output = strings.Replace(output, "\r", "\n", -1)
   631  
   632  	if _, err := f.WriteString(output); err != nil {
   633  		log.Printf("Error writing output to logfile %s: %v", logFile, err)
   634  	}
   635  
   636  	if err := f.Sync(); err != nil {
   637  		log.Printf("Error saving logfile %s to disk: %v", logFile, err)
   638  	}
   639  }
   640  
   641  // runCommand is used to run already prepared commands
   642  func (p *provisioner) runCommand(o terraform.UIOutput, comm communicator.Communicator, command string) error {
   643  	// Unless prevented, prefix the command with sudo
   644  	if p.useSudo {
   645  		command = "sudo " + command
   646  	}
   647  
   648  	outR, outW := io.Pipe()
   649  	errR, errW := io.Pipe()
   650  	outDoneCh := make(chan struct{})
   651  	errDoneCh := make(chan struct{})
   652  	go p.copyOutput(o, outR, outDoneCh)
   653  	go p.copyOutput(o, errR, errDoneCh)
   654  
   655  	cmd := &remote.Cmd{
   656  		Command: command,
   657  		Stdout:  outW,
   658  		Stderr:  errW,
   659  	}
   660  
   661  	err := comm.Start(cmd)
   662  	if err != nil {
   663  		return fmt.Errorf("Error executing command %q: %v", cmd.Command, err)
   664  	}
   665  
   666  	cmd.Wait()
   667  	if cmd.ExitStatus != 0 {
   668  		err = fmt.Errorf(
   669  			"Command %q exited with non-zero exit status: %d", cmd.Command, cmd.ExitStatus)
   670  	}
   671  
   672  	// Wait for output to clean up
   673  	outW.Close()
   674  	errW.Close()
   675  	<-outDoneCh
   676  	<-errDoneCh
   677  
   678  	return err
   679  }
   680  
   681  func (p *provisioner) copyOutput(o terraform.UIOutput, r io.Reader, doneCh chan<- struct{}) {
   682  	defer close(doneCh)
   683  	lr := linereader.New(r)
   684  	for line := range lr.Ch {
   685  		o.Output(line)
   686  	}
   687  }
   688  
   689  // retryFunc is used to retry a function for a given duration
   690  func retryFunc(timeout time.Duration, f func() error) error {
   691  	finish := time.After(timeout)
   692  	for {
   693  		err := f()
   694  		if err == nil {
   695  			return nil
   696  		}
   697  		log.Printf("Retryable error: %v", err)
   698  
   699  		select {
   700  		case <-finish:
   701  			return err
   702  		case <-time.After(3 * time.Second):
   703  		}
   704  	}
   705  }
   706  
   707  func decodeConfig(d *schema.ResourceData) (*provisioner, error) {
   708  	p := &provisioner{
   709  		ClientOptions:         getStringList(d.Get("client_options")),
   710  		DisableReporting:      d.Get("disable_reporting").(bool),
   711  		Environment:           d.Get("environment").(string),
   712  		FetchChefCertificates: d.Get("fetch_chef_certificates").(bool),
   713  		LogToFile:             d.Get("log_to_file").(bool),
   714  		UsePolicyfile:         d.Get("use_policyfile").(bool),
   715  		PolicyGroup:           d.Get("policy_group").(string),
   716  		PolicyName:            d.Get("policy_name").(string),
   717  		HTTPProxy:             d.Get("http_proxy").(string),
   718  		HTTPSProxy:            d.Get("https_proxy").(string),
   719  		NOProxy:               getStringList(d.Get("no_proxy")),
   720  		NamedRunList:          d.Get("named_run_list").(string),
   721  		NodeName:              d.Get("node_name").(string),
   722  		OhaiHints:             getStringList(d.Get("ohai_hints")),
   723  		OSType:                d.Get("os_type").(string),
   724  		RecreateClient:        d.Get("recreate_client").(bool),
   725  		PreventSudo:           d.Get("prevent_sudo").(bool),
   726  		RunList:               getStringList(d.Get("run_list")),
   727  		SecretKey:             d.Get("secret_key").(string),
   728  		ServerURL:             d.Get("server_url").(string),
   729  		SkipInstall:           d.Get("skip_install").(bool),
   730  		SkipRegister:          d.Get("skip_register").(bool),
   731  		SSLVerifyMode:         d.Get("ssl_verify_mode").(string),
   732  		UserName:              d.Get("user_name").(string),
   733  		UserKey:               d.Get("user_key").(string),
   734  		Version:               d.Get("version").(string),
   735  	}
   736  
   737  	// Make sure the supplied URL has a trailing slash
   738  	p.ServerURL = strings.TrimSuffix(p.ServerURL, "/") + "/"
   739  
   740  	for i, hint := range p.OhaiHints {
   741  		hintPath, err := homedir.Expand(hint)
   742  		if err != nil {
   743  			return nil, fmt.Errorf("Error expanding the path %s: %v", hint, err)
   744  		}
   745  		p.OhaiHints[i] = hintPath
   746  	}
   747  
   748  	if attrs, ok := d.GetOk("attributes_json"); ok {
   749  		var m map[string]interface{}
   750  		if err := json.Unmarshal([]byte(attrs.(string)), &m); err != nil {
   751  			return nil, fmt.Errorf("Error parsing attributes_json: %v", err)
   752  		}
   753  		p.Attributes = m
   754  	}
   755  
   756  	if vaults, ok := d.GetOk("vault_json"); ok {
   757  		var m map[string]interface{}
   758  		if err := json.Unmarshal([]byte(vaults.(string)), &m); err != nil {
   759  			return nil, fmt.Errorf("Error parsing vault_json: %v", err)
   760  		}
   761  
   762  		v := make(map[string][]string)
   763  		for vault, items := range m {
   764  			switch items := items.(type) {
   765  			case []interface{}:
   766  				for _, item := range items {
   767  					if item, ok := item.(string); ok {
   768  						v[vault] = append(v[vault], item)
   769  					}
   770  				}
   771  			case interface{}:
   772  				if item, ok := items.(string); ok {
   773  					v[vault] = append(v[vault], item)
   774  				}
   775  			}
   776  		}
   777  
   778  		p.Vaults = v
   779  	}
   780  
   781  	return p, nil
   782  }
   783  
   784  func getStringList(v interface{}) []string {
   785  	if v == nil {
   786  		return nil
   787  	}
   788  	switch l := v.(type) {
   789  	case []string:
   790  		return l
   791  	case []interface{}:
   792  		arr := make([]string, len(l))
   793  		for i, x := range l {
   794  			arr[i] = x.(string)
   795  		}
   796  		return arr
   797  	default:
   798  		panic(fmt.Sprintf("Unsupported type: %T", v))
   799  	}
   800  }