github.com/kjmkznr/terraform@v0.5.2-0.20180216194316-1d0f5fdac99e/builtin/provisioners/habitat/resource_provisioner.go (about)

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