github.com/ns1/terraform@v0.7.10-0.20161109153551-8949419bef40/builtin/provisioners/chef/resource_provisioner.go (about)

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