github.com/jzbruno/terraform@v0.10.3-0.20180104230435-18975d727047/builtin/provisioners/habitat/resource_provisioner.go (about)

     1  package habitat
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"log"
    10  	"net/url"
    11  	"path"
    12  	"strings"
    13  	"text/template"
    14  	"time"
    15  
    16  	"github.com/hashicorp/terraform/communicator"
    17  	"github.com/hashicorp/terraform/communicator/remote"
    18  	"github.com/hashicorp/terraform/helper/schema"
    19  	"github.com/hashicorp/terraform/terraform"
    20  	linereader "github.com/mitchellh/go-linereader"
    21  )
    22  
    23  const installURL = "https://raw.githubusercontent.com/habitat-sh/habitat/master/components/hab/install.sh"
    24  const systemdUnit = `
    25  [Unit]
    26  Description=Habitat Supervisor
    27  
    28  [Service]
    29  ExecStart=/bin/hab sup run {{ .SupOptions }}
    30  Restart=on-failure
    31  {{ if .BuilderAuthToken -}}
    32  Environment="HAB_AUTH_TOKEN={{ .BuilderAuthToken }}"
    33  {{ end -}}
    34  
    35  [Install]
    36  WantedBy=default.target
    37  `
    38  
    39  var serviceTypes = map[string]bool{"unmanaged": true, "systemd": true}
    40  var updateStrategies = map[string]bool{"at-once": true, "rolling": true, "none": true}
    41  var topologies = map[string]bool{"leader": true, "standalone": true}
    42  
    43  type provisionFn func(terraform.UIOutput, communicator.Communicator) error
    44  
    45  type provisioner struct {
    46  	Version          string
    47  	Services         []Service
    48  	PermanentPeer    bool
    49  	ListenGossip     string
    50  	ListenHTTP       string
    51  	Peer             string
    52  	RingKey          string
    53  	RingKeyContent   string
    54  	SkipInstall      bool
    55  	UseSudo          bool
    56  	ServiceType      string
    57  	URL              string
    58  	Channel          string
    59  	Events           string
    60  	OverrideName     string
    61  	Organization     string
    62  	BuilderAuthToken string
    63  	SupOptions       string
    64  }
    65  
    66  func Provisioner() terraform.ResourceProvisioner {
    67  	return &schema.Provisioner{
    68  		Schema: map[string]*schema.Schema{
    69  			"version": &schema.Schema{
    70  				Type:     schema.TypeString,
    71  				Optional: true,
    72  			},
    73  			"peer": &schema.Schema{
    74  				Type:     schema.TypeString,
    75  				Optional: true,
    76  			},
    77  			"service_type": &schema.Schema{
    78  				Type:     schema.TypeString,
    79  				Optional: true,
    80  				Default:  "systemd",
    81  			},
    82  			"use_sudo": &schema.Schema{
    83  				Type:     schema.TypeBool,
    84  				Optional: true,
    85  				Default:  true,
    86  			},
    87  			"permanent_peer": &schema.Schema{
    88  				Type:     schema.TypeBool,
    89  				Optional: true,
    90  				Default:  false,
    91  			},
    92  			"listen_gossip": &schema.Schema{
    93  				Type:     schema.TypeString,
    94  				Optional: true,
    95  			},
    96  			"listen_http": &schema.Schema{
    97  				Type:     schema.TypeString,
    98  				Optional: true,
    99  			},
   100  			"ring_key": &schema.Schema{
   101  				Type:     schema.TypeString,
   102  				Optional: true,
   103  			},
   104  			"ring_key_content": &schema.Schema{
   105  				Type:     schema.TypeString,
   106  				Optional: true,
   107  			},
   108  			"url": &schema.Schema{
   109  				Type:     schema.TypeString,
   110  				Optional: true,
   111  			},
   112  			"channel": &schema.Schema{
   113  				Type:     schema.TypeString,
   114  				Optional: true,
   115  			},
   116  			"events": &schema.Schema{
   117  				Type:     schema.TypeString,
   118  				Optional: true,
   119  			},
   120  			"override_name": &schema.Schema{
   121  				Type:     schema.TypeString,
   122  				Optional: true,
   123  			},
   124  			"organization": &schema.Schema{
   125  				Type:     schema.TypeString,
   126  				Optional: true,
   127  			},
   128  			"builder_auth_token": &schema.Schema{
   129  				Type:     schema.TypeString,
   130  				Optional: true,
   131  			},
   132  			"service": &schema.Schema{
   133  				Type: schema.TypeSet,
   134  				Elem: &schema.Resource{
   135  					Schema: map[string]*schema.Schema{
   136  						"name": &schema.Schema{
   137  							Type:     schema.TypeString,
   138  							Required: true,
   139  						},
   140  						"binds": &schema.Schema{
   141  							Type:     schema.TypeList,
   142  							Elem:     &schema.Schema{Type: schema.TypeString},
   143  							Optional: true,
   144  						},
   145  						"bind": &schema.Schema{
   146  							Type: schema.TypeSet,
   147  							Elem: &schema.Resource{
   148  								Schema: map[string]*schema.Schema{
   149  									"alias": &schema.Schema{
   150  										Type:     schema.TypeString,
   151  										Required: true,
   152  									},
   153  									"service": &schema.Schema{
   154  										Type:     schema.TypeString,
   155  										Required: true,
   156  									},
   157  									"group": &schema.Schema{
   158  										Type:     schema.TypeString,
   159  										Required: true,
   160  									},
   161  								},
   162  							},
   163  							Optional: true,
   164  						},
   165  						"topology": &schema.Schema{
   166  							Type:     schema.TypeString,
   167  							Optional: true,
   168  						},
   169  						"user_toml": &schema.Schema{
   170  							Type:     schema.TypeString,
   171  							Optional: true,
   172  						},
   173  						"strategy": &schema.Schema{
   174  							Type:     schema.TypeString,
   175  							Optional: true,
   176  						},
   177  						"channel": &schema.Schema{
   178  							Type:     schema.TypeString,
   179  							Optional: true,
   180  						},
   181  						"group": &schema.Schema{
   182  							Type:     schema.TypeString,
   183  							Optional: true,
   184  						},
   185  						"url": &schema.Schema{
   186  							Type:     schema.TypeString,
   187  							Optional: true,
   188  						},
   189  						"application": &schema.Schema{
   190  							Type:     schema.TypeString,
   191  							Optional: true,
   192  						},
   193  						"environment": &schema.Schema{
   194  							Type:     schema.TypeString,
   195  							Optional: true,
   196  						},
   197  						"override_name": &schema.Schema{
   198  							Type:     schema.TypeString,
   199  							Optional: true,
   200  						},
   201  						"service_key": &schema.Schema{
   202  							Type:     schema.TypeString,
   203  							Optional: true,
   204  						},
   205  					},
   206  				},
   207  				Optional: true,
   208  			},
   209  		},
   210  		ApplyFunc:    applyFn,
   211  		ValidateFunc: validateFn,
   212  	}
   213  }
   214  
   215  func applyFn(ctx context.Context) error {
   216  	o := ctx.Value(schema.ProvOutputKey).(terraform.UIOutput)
   217  	s := ctx.Value(schema.ProvRawStateKey).(*terraform.InstanceState)
   218  	d := ctx.Value(schema.ProvConfigDataKey).(*schema.ResourceData)
   219  
   220  	p, err := decodeConfig(d)
   221  	if err != nil {
   222  		return err
   223  	}
   224  
   225  	comm, err := communicator.New(s)
   226  	if err != nil {
   227  		return err
   228  	}
   229  
   230  	err = retryFunc(comm.Timeout(), func() error {
   231  		err = comm.Connect(o)
   232  		return err
   233  	})
   234  	if err != nil {
   235  		return err
   236  	}
   237  	defer comm.Disconnect()
   238  
   239  	if !p.SkipInstall {
   240  		o.Output("Installing habitat...")
   241  		if err := p.installHab(o, comm); err != nil {
   242  			return err
   243  		}
   244  	}
   245  
   246  	if p.RingKeyContent != "" {
   247  		o.Output("Uploading supervisor ring key...")
   248  		if err := p.uploadRingKey(o, comm); err != nil {
   249  			return err
   250  		}
   251  	}
   252  
   253  	o.Output("Starting the habitat supervisor...")
   254  	if err := p.startHab(o, comm); err != nil {
   255  		return err
   256  	}
   257  
   258  	if p.Services != nil {
   259  		for _, service := range p.Services {
   260  			o.Output("Starting service: " + service.Name)
   261  			if err := p.startHabService(o, comm, service); err != nil {
   262  				return err
   263  			}
   264  		}
   265  	}
   266  
   267  	return nil
   268  }
   269  
   270  func validateFn(c *terraform.ResourceConfig) (ws []string, es []error) {
   271  	serviceType, ok := c.Get("service_type")
   272  	if ok {
   273  		if !serviceTypes[serviceType.(string)] {
   274  			es = append(es, errors.New(serviceType.(string)+" is not a valid service_type."))
   275  		}
   276  	}
   277  
   278  	builderURL, ok := c.Get("url")
   279  	if ok {
   280  		if _, err := url.ParseRequestURI(builderURL.(string)); err != nil {
   281  			es = append(es, errors.New(builderURL.(string)+" is not a valid URL."))
   282  		}
   283  	}
   284  
   285  	// Validate service level configs
   286  	services, ok := c.Get("service")
   287  	if ok {
   288  		for _, service := range services.([]map[string]interface{}) {
   289  			strategy, ok := service["strategy"].(string)
   290  			if ok && !updateStrategies[strategy] {
   291  				es = append(es, errors.New(strategy+" is not a valid update strategy."))
   292  			}
   293  
   294  			topology, ok := service["topology"].(string)
   295  			if ok && !topologies[topology] {
   296  				es = append(es, errors.New(topology+" is not a valid topology"))
   297  			}
   298  
   299  			builderURL, ok := service["url"].(string)
   300  			if ok {
   301  				if _, err := url.ParseRequestURI(builderURL); err != nil {
   302  					es = append(es, errors.New(builderURL+" is not a valid URL."))
   303  				}
   304  			}
   305  		}
   306  	}
   307  	return ws, es
   308  }
   309  
   310  type Service struct {
   311  	Name            string
   312  	Strategy        string
   313  	Topology        string
   314  	Channel         string
   315  	Group           string
   316  	URL             string
   317  	Binds           []Bind
   318  	BindStrings     []string
   319  	UserTOML        string
   320  	AppName         string
   321  	Environment     string
   322  	OverrideName    string
   323  	ServiceGroupKey string
   324  }
   325  
   326  type Bind struct {
   327  	Alias   string
   328  	Service string
   329  	Group   string
   330  }
   331  
   332  func (s *Service) getPackageName(fullName string) string {
   333  	return strings.Split(fullName, "/")[1]
   334  }
   335  
   336  func (b *Bind) toBindString() string {
   337  	return fmt.Sprintf("%s:%s.%s", b.Alias, b.Service, b.Group)
   338  }
   339  
   340  func decodeConfig(d *schema.ResourceData) (*provisioner, error) {
   341  	p := &provisioner{
   342  		Version:          d.Get("version").(string),
   343  		Peer:             d.Get("peer").(string),
   344  		Services:         getServices(d.Get("service").(*schema.Set).List()),
   345  		UseSudo:          d.Get("use_sudo").(bool),
   346  		ServiceType:      d.Get("service_type").(string),
   347  		RingKey:          d.Get("ring_key").(string),
   348  		RingKeyContent:   d.Get("ring_key_content").(string),
   349  		PermanentPeer:    d.Get("permanent_peer").(bool),
   350  		ListenGossip:     d.Get("listen_gossip").(string),
   351  		ListenHTTP:       d.Get("listen_http").(string),
   352  		URL:              d.Get("url").(string),
   353  		Channel:          d.Get("channel").(string),
   354  		Events:           d.Get("events").(string),
   355  		OverrideName:     d.Get("override_name").(string),
   356  		Organization:     d.Get("organization").(string),
   357  		BuilderAuthToken: d.Get("builder_auth_token").(string),
   358  	}
   359  
   360  	return p, nil
   361  }
   362  
   363  func getServices(v []interface{}) []Service {
   364  	services := make([]Service, 0, len(v))
   365  	for _, rawServiceData := range v {
   366  		serviceData := rawServiceData.(map[string]interface{})
   367  		name := (serviceData["name"].(string))
   368  		strategy := (serviceData["strategy"].(string))
   369  		topology := (serviceData["topology"].(string))
   370  		channel := (serviceData["channel"].(string))
   371  		group := (serviceData["group"].(string))
   372  		url := (serviceData["url"].(string))
   373  		app := (serviceData["application"].(string))
   374  		env := (serviceData["environment"].(string))
   375  		override := (serviceData["override_name"].(string))
   376  		userToml := (serviceData["user_toml"].(string))
   377  		serviceGroupKey := (serviceData["service_key"].(string))
   378  		var bindStrings []string
   379  		binds := getBinds(serviceData["bind"].(*schema.Set).List())
   380  		for _, b := range serviceData["binds"].([]interface{}) {
   381  			bind, err := getBindFromString(b.(string))
   382  			if err != nil {
   383  				return nil
   384  			}
   385  			binds = append(binds, bind)
   386  		}
   387  
   388  		service := Service{
   389  			Name:            name,
   390  			Strategy:        strategy,
   391  			Topology:        topology,
   392  			Channel:         channel,
   393  			Group:           group,
   394  			URL:             url,
   395  			UserTOML:        userToml,
   396  			BindStrings:     bindStrings,
   397  			Binds:           binds,
   398  			AppName:         app,
   399  			Environment:     env,
   400  			OverrideName:    override,
   401  			ServiceGroupKey: serviceGroupKey,
   402  		}
   403  		services = append(services, service)
   404  	}
   405  	return services
   406  }
   407  
   408  func getBinds(v []interface{}) []Bind {
   409  	binds := make([]Bind, 0, len(v))
   410  	for _, rawBindData := range v {
   411  		bindData := rawBindData.(map[string]interface{})
   412  		alias := bindData["alias"].(string)
   413  		service := bindData["service"].(string)
   414  		group := bindData["group"].(string)
   415  		bind := Bind{
   416  			Alias:   alias,
   417  			Service: service,
   418  			Group:   group,
   419  		}
   420  		binds = append(binds, bind)
   421  	}
   422  	return binds
   423  }
   424  
   425  func (p *provisioner) uploadRingKey(o terraform.UIOutput, comm communicator.Communicator) error {
   426  	command := fmt.Sprintf("echo '%s' | hab ring key import", p.RingKeyContent)
   427  	if p.UseSudo {
   428  		command = fmt.Sprintf("echo '%s' | sudo hab ring key import", p.RingKeyContent)
   429  	}
   430  	return p.runCommand(o, comm, command)
   431  }
   432  
   433  func (p *provisioner) installHab(o terraform.UIOutput, comm communicator.Communicator) error {
   434  	// Build the install command
   435  	command := fmt.Sprintf("curl -L0 %s > install.sh", installURL)
   436  	if err := p.runCommand(o, comm, command); err != nil {
   437  		return err
   438  	}
   439  
   440  	// Run the install script
   441  	if p.Version == "" {
   442  		command = fmt.Sprintf("env HAB_NONINTERACTIVE=true bash ./install.sh ")
   443  	} else {
   444  		command = fmt.Sprintf("env HAB_NONINTERACTIVE=true bash ./install.sh -v %s", p.Version)
   445  	}
   446  
   447  	if p.UseSudo {
   448  		command = fmt.Sprintf("sudo %s", command)
   449  	}
   450  
   451  	if err := p.runCommand(o, comm, command); err != nil {
   452  		return err
   453  	}
   454  
   455  	if err := p.createHabUser(o, comm); err != nil {
   456  		return err
   457  	}
   458  
   459  	return p.runCommand(o, comm, fmt.Sprintf("rm -f install.sh"))
   460  }
   461  
   462  func (p *provisioner) startHab(o terraform.UIOutput, comm communicator.Communicator) error {
   463  	// Install the supervisor first
   464  	var command string
   465  	if p.Version == "" {
   466  		command += fmt.Sprintf("hab install core/hab-sup")
   467  	} else {
   468  		command += fmt.Sprintf("hab install core/hab-sup/%s", p.Version)
   469  	}
   470  
   471  	if p.UseSudo {
   472  		command = fmt.Sprintf("sudo -E %s", command)
   473  	}
   474  
   475  	command = fmt.Sprintf("env HAB_NONINTERACTIVE=true %s", command)
   476  
   477  	if err := p.runCommand(o, comm, command); err != nil {
   478  		return err
   479  	}
   480  
   481  	// Build up sup options
   482  	options := ""
   483  	if p.PermanentPeer {
   484  		options += " -I"
   485  	}
   486  
   487  	if p.ListenGossip != "" {
   488  		options += fmt.Sprintf(" --listen-gossip %s", p.ListenGossip)
   489  	}
   490  
   491  	if p.ListenHTTP != "" {
   492  		options += fmt.Sprintf(" --listen-http %s", p.ListenHTTP)
   493  	}
   494  
   495  	if p.Peer != "" {
   496  		options += fmt.Sprintf(" --peer %s", p.Peer)
   497  	}
   498  
   499  	if p.RingKey != "" {
   500  		options += fmt.Sprintf(" --ring %s", p.RingKey)
   501  	}
   502  
   503  	if p.URL != "" {
   504  		options += fmt.Sprintf(" --url %s", p.URL)
   505  	}
   506  
   507  	if p.Channel != "" {
   508  		options += fmt.Sprintf(" --channel %s", p.Channel)
   509  	}
   510  
   511  	if p.Events != "" {
   512  		options += fmt.Sprintf(" --events %s", p.Events)
   513  	}
   514  
   515  	if p.OverrideName != "" {
   516  		options += fmt.Sprintf(" --override-name %s", p.OverrideName)
   517  	}
   518  
   519  	if p.Organization != "" {
   520  		options += fmt.Sprintf(" --org %s", p.Organization)
   521  	}
   522  
   523  	p.SupOptions = options
   524  
   525  	switch p.ServiceType {
   526  	case "unmanaged":
   527  		return p.startHabUnmanaged(o, comm, options)
   528  	case "systemd":
   529  		return p.startHabSystemd(o, comm, options)
   530  	default:
   531  		return errors.New("Unsupported service type")
   532  	}
   533  }
   534  
   535  func (p *provisioner) startHabUnmanaged(o terraform.UIOutput, comm communicator.Communicator, options string) error {
   536  	// Create the sup directory for the log file
   537  	var command string
   538  	var token string
   539  	if p.UseSudo {
   540  		command = "sudo mkdir -p /hab/sup/default && sudo chmod o+w /hab/sup/default"
   541  	} else {
   542  		command = "mkdir -p /hab/sup/default && chmod o+w /hab/sup/default"
   543  	}
   544  	if err := p.runCommand(o, comm, command); err != nil {
   545  		return err
   546  	}
   547  
   548  	if p.BuilderAuthToken != "" {
   549  		token = fmt.Sprintf("env HAB_AUTH_TOKEN=%s", p.BuilderAuthToken)
   550  	}
   551  
   552  	if p.UseSudo {
   553  		command = fmt.Sprintf("(%s setsid sudo -E hab sup run %s > /hab/sup/default/sup.log 2>&1 &) ; sleep 1", token, options)
   554  	} else {
   555  		command = fmt.Sprintf("(%s setsid hab sup run %s > /hab/sup/default/sup.log 2>&1 <&1 &) ; sleep 1", token, options)
   556  	}
   557  	return p.runCommand(o, comm, command)
   558  }
   559  
   560  func (p *provisioner) startHabSystemd(o terraform.UIOutput, comm communicator.Communicator, options string) error {
   561  	// Create a new template and parse the client config into it
   562  	unitString := template.Must(template.New("hab-supervisor.service").Parse(systemdUnit))
   563  
   564  	var buf bytes.Buffer
   565  	err := unitString.Execute(&buf, p)
   566  	if err != nil {
   567  		return fmt.Errorf("Error executing %s template: %s", "hab-supervisor.service", err)
   568  	}
   569  
   570  	var command string
   571  	if p.UseSudo {
   572  		command = fmt.Sprintf("sudo echo '%s' | sudo tee /etc/systemd/system/hab-supervisor.service > /dev/null", &buf)
   573  	} else {
   574  		command = fmt.Sprintf("echo '%s' | tee /etc/systemd/system/hab-supervisor.service > /dev/null", &buf)
   575  	}
   576  
   577  	if err := p.runCommand(o, comm, command); err != nil {
   578  		return err
   579  	}
   580  
   581  	if p.UseSudo {
   582  		command = fmt.Sprintf("sudo systemctl start hab-supervisor")
   583  	} else {
   584  		command = fmt.Sprintf("systemctl start hab-supervisor")
   585  	}
   586  	return p.runCommand(o, comm, command)
   587  }
   588  
   589  func (p *provisioner) createHabUser(o terraform.UIOutput, comm communicator.Communicator) error {
   590  	// Create the hab user
   591  	command := fmt.Sprintf("env HAB_NONINTERACTIVE=true hab install core/busybox")
   592  	if p.UseSudo {
   593  		command = fmt.Sprintf("sudo %s", command)
   594  	}
   595  	if err := p.runCommand(o, comm, command); err != nil {
   596  		return err
   597  	}
   598  
   599  	command = fmt.Sprintf("hab pkg exec core/busybox adduser -D -g \"\" hab")
   600  	if p.UseSudo {
   601  		command = fmt.Sprintf("sudo %s", command)
   602  	}
   603  	return p.runCommand(o, comm, command)
   604  }
   605  
   606  func (p *provisioner) startHabService(o terraform.UIOutput, comm communicator.Communicator, service Service) error {
   607  	var command string
   608  	if p.UseSudo {
   609  		command = fmt.Sprintf("env HAB_NONINTERACTIVE=true sudo -E hab pkg install %s", service.Name)
   610  	} else {
   611  		command = fmt.Sprintf("env HAB_NONINTERACTIVE=true hab pkg install %s", service.Name)
   612  	}
   613  
   614  	if p.BuilderAuthToken != "" {
   615  		command = fmt.Sprintf("env HAB_AUTH_TOKEN=%s %s", p.BuilderAuthToken, command)
   616  	}
   617  
   618  	if err := p.runCommand(o, comm, command); err != nil {
   619  		return err
   620  	}
   621  
   622  	if err := p.uploadUserTOML(o, comm, service); err != nil {
   623  		return err
   624  	}
   625  
   626  	// Upload service group key
   627  	if service.ServiceGroupKey != "" {
   628  		p.uploadServiceGroupKey(o, comm, service.ServiceGroupKey)
   629  	}
   630  
   631  	options := ""
   632  	if service.Topology != "" {
   633  		options += fmt.Sprintf(" --topology %s", service.Topology)
   634  	}
   635  
   636  	if service.Strategy != "" {
   637  		options += fmt.Sprintf(" --strategy %s", service.Strategy)
   638  	}
   639  
   640  	if service.Channel != "" {
   641  		options += fmt.Sprintf(" --channel %s", service.Channel)
   642  	}
   643  
   644  	if service.URL != "" {
   645  		options += fmt.Sprintf("--url %s", service.URL)
   646  	}
   647  
   648  	if service.Group != "" {
   649  		options += fmt.Sprintf(" --group %s", service.Group)
   650  	}
   651  
   652  	for _, bind := range service.Binds {
   653  		options += fmt.Sprintf(" --bind %s", bind.toBindString())
   654  	}
   655  	command = fmt.Sprintf("hab svc load %s %s", service.Name, options)
   656  	if p.UseSudo {
   657  		command = fmt.Sprintf("sudo -E %s", command)
   658  	}
   659  	if p.BuilderAuthToken != "" {
   660  		command = fmt.Sprintf("env HAB_AUTH_TOKEN=%s %s", p.BuilderAuthToken, command)
   661  	}
   662  	return p.runCommand(o, comm, command)
   663  }
   664  
   665  func (p *provisioner) uploadServiceGroupKey(o terraform.UIOutput, comm communicator.Communicator, key string) error {
   666  	keyName := strings.Split(key, "\n")[1]
   667  	o.Output("Uploading service group key: " + keyName)
   668  	keyFileName := fmt.Sprintf("%s.box.key", keyName)
   669  	destPath := path.Join("/hab/cache/keys", keyFileName)
   670  	keyContent := strings.NewReader(key)
   671  	if p.UseSudo {
   672  		tempPath := path.Join("/tmp", keyFileName)
   673  		if err := comm.Upload(tempPath, keyContent); err != nil {
   674  			return err
   675  		}
   676  		command := fmt.Sprintf("sudo mv %s %s", tempPath, destPath)
   677  		return p.runCommand(o, comm, command)
   678  	}
   679  
   680  	return comm.Upload(destPath, keyContent)
   681  }
   682  
   683  func (p *provisioner) uploadUserTOML(o terraform.UIOutput, comm communicator.Communicator, service Service) error {
   684  	// Create the hab svc directory to lay down the user.toml before loading the service
   685  	o.Output("Uploading user.toml for service: " + service.Name)
   686  	destDir := fmt.Sprintf("/hab/svc/%s", service.getPackageName(service.Name))
   687  	command := fmt.Sprintf("mkdir -p %s", destDir)
   688  	if p.UseSudo {
   689  		command = fmt.Sprintf("sudo %s", command)
   690  	}
   691  	if err := p.runCommand(o, comm, command); err != nil {
   692  		return err
   693  	}
   694  
   695  	userToml := strings.NewReader(service.UserTOML)
   696  
   697  	if p.UseSudo {
   698  		if err := comm.Upload("/tmp/user.toml", userToml); err != nil {
   699  			return err
   700  		}
   701  		command = fmt.Sprintf("sudo mv /tmp/user.toml %s", destDir)
   702  		return p.runCommand(o, comm, command)
   703  	}
   704  
   705  	return comm.Upload(path.Join(destDir, "user.toml"), userToml)
   706  
   707  }
   708  
   709  func retryFunc(timeout time.Duration, f func() error) error {
   710  	finish := time.After(timeout)
   711  
   712  	for {
   713  		err := f()
   714  		if err == nil {
   715  			return nil
   716  		}
   717  		log.Printf("Retryable error: %v", err)
   718  
   719  		select {
   720  		case <-finish:
   721  			return err
   722  		case <-time.After(3 * time.Second):
   723  		}
   724  	}
   725  }
   726  
   727  func (p *provisioner) copyOutput(o terraform.UIOutput, r io.Reader, doneCh chan<- struct{}) {
   728  	defer close(doneCh)
   729  	lr := linereader.New(r)
   730  	for line := range lr.Ch {
   731  		o.Output(line)
   732  	}
   733  }
   734  
   735  func (p *provisioner) runCommand(o terraform.UIOutput, comm communicator.Communicator, command string) error {
   736  	outR, outW := io.Pipe()
   737  	errR, errW := io.Pipe()
   738  	var err error
   739  
   740  	outDoneCh := make(chan struct{})
   741  	errDoneCh := make(chan struct{})
   742  	go p.copyOutput(o, outR, outDoneCh)
   743  	go p.copyOutput(o, errR, errDoneCh)
   744  
   745  	cmd := &remote.Cmd{
   746  		Command: command,
   747  		Stdout:  outW,
   748  		Stderr:  errW,
   749  	}
   750  
   751  	if err = comm.Start(cmd); err != nil {
   752  		return fmt.Errorf("Error executing command %q: %v", cmd.Command, err)
   753  	}
   754  
   755  	cmd.Wait()
   756  	if cmd.ExitStatus != 0 {
   757  		err = fmt.Errorf(
   758  			"Command %q exited with non-zero exit status: %d", cmd.Command, cmd.ExitStatus)
   759  	}
   760  
   761  	outW.Close()
   762  	errW.Close()
   763  	<-outDoneCh
   764  	<-errDoneCh
   765  
   766  	return err
   767  }
   768  
   769  func getBindFromString(bind string) (Bind, error) {
   770  	t := strings.FieldsFunc(bind, func(d rune) bool {
   771  		switch d {
   772  		case ':', '.':
   773  			return true
   774  		}
   775  		return false
   776  	})
   777  	if len(t) != 3 {
   778  		return Bind{}, errors.New("Invalid bind specification: " + bind)
   779  	}
   780  	return Bind{Alias: t[0], Service: t[1], Group: t[2]}, nil
   781  }