github.com/handlerbot/terraform@v0.10.0-beta1.0.20180726153736-26b68d98f9cb/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  
   183  							Type:     schema.TypeString,
   184  							Optional: true,
   185  						},
   186  						"group": &schema.Schema{
   187  							Type:     schema.TypeString,
   188  							Optional: true,
   189  						},
   190  						"url": &schema.Schema{
   191  							Type:     schema.TypeString,
   192  							Optional: true,
   193  						},
   194  						"application": &schema.Schema{
   195  							Type:     schema.TypeString,
   196  							Optional: true,
   197  						},
   198  						"environment": &schema.Schema{
   199  							Type:     schema.TypeString,
   200  							Optional: true,
   201  						},
   202  						"override_name": &schema.Schema{
   203  							Type:     schema.TypeString,
   204  							Optional: true,
   205  						},
   206  						"service_key": &schema.Schema{
   207  							Type:     schema.TypeString,
   208  							Optional: true,
   209  						},
   210  					},
   211  				},
   212  				Optional: true,
   213  			},
   214  		},
   215  		ApplyFunc:    applyFn,
   216  		ValidateFunc: validateFn,
   217  	}
   218  }
   219  
   220  func applyFn(ctx context.Context) error {
   221  	o := ctx.Value(schema.ProvOutputKey).(terraform.UIOutput)
   222  	s := ctx.Value(schema.ProvRawStateKey).(*terraform.InstanceState)
   223  	d := ctx.Value(schema.ProvConfigDataKey).(*schema.ResourceData)
   224  
   225  	p, err := decodeConfig(d)
   226  	if err != nil {
   227  		return err
   228  	}
   229  
   230  	comm, err := communicator.New(s)
   231  	if err != nil {
   232  		return err
   233  	}
   234  
   235  	retryCtx, cancel := context.WithTimeout(ctx, comm.Timeout())
   236  	defer cancel()
   237  
   238  	err = communicator.Retry(retryCtx, func() error {
   239  		return comm.Connect(o)
   240  	})
   241  
   242  	if err != nil {
   243  		return err
   244  	}
   245  	defer comm.Disconnect()
   246  
   247  	if !p.SkipInstall {
   248  		o.Output("Installing habitat...")
   249  		if err := p.installHab(o, comm); err != nil {
   250  			return err
   251  		}
   252  	}
   253  
   254  	if p.RingKeyContent != "" {
   255  		o.Output("Uploading supervisor ring key...")
   256  		if err := p.uploadRingKey(o, comm); err != nil {
   257  			return err
   258  		}
   259  	}
   260  
   261  	o.Output("Starting the habitat supervisor...")
   262  	if err := p.startHab(o, comm); err != nil {
   263  		return err
   264  	}
   265  
   266  	if p.Services != nil {
   267  		for _, service := range p.Services {
   268  			o.Output("Starting service: " + service.Name)
   269  			if err := p.startHabService(o, comm, service); err != nil {
   270  				return err
   271  			}
   272  		}
   273  	}
   274  
   275  	return nil
   276  }
   277  
   278  func validateFn(c *terraform.ResourceConfig) (ws []string, es []error) {
   279  	serviceType, ok := c.Get("service_type")
   280  	if ok {
   281  		if !serviceTypes[serviceType.(string)] {
   282  			es = append(es, errors.New(serviceType.(string)+" is not a valid service_type."))
   283  		}
   284  	}
   285  
   286  	builderURL, ok := c.Get("url")
   287  	if ok {
   288  		if _, err := url.ParseRequestURI(builderURL.(string)); err != nil {
   289  			es = append(es, errors.New(builderURL.(string)+" is not a valid URL."))
   290  		}
   291  	}
   292  
   293  	// Validate service level configs
   294  	services, ok := c.Get("service")
   295  	if ok {
   296  		for _, service := range services.([]map[string]interface{}) {
   297  			strategy, ok := service["strategy"].(string)
   298  			if ok && !updateStrategies[strategy] {
   299  				es = append(es, errors.New(strategy+" is not a valid update strategy."))
   300  			}
   301  
   302  			topology, ok := service["topology"].(string)
   303  			if ok && !topologies[topology] {
   304  				es = append(es, errors.New(topology+" is not a valid topology"))
   305  			}
   306  
   307  			builderURL, ok := service["url"].(string)
   308  			if ok {
   309  				if _, err := url.ParseRequestURI(builderURL); err != nil {
   310  					es = append(es, errors.New(builderURL+" is not a valid URL."))
   311  				}
   312  			}
   313  		}
   314  	}
   315  	return ws, es
   316  }
   317  
   318  type Service struct {
   319  	Name            string
   320  	Strategy        string
   321  	Topology        string
   322  	Channel         string
   323  	Group           string
   324  	URL             string
   325  	Binds           []Bind
   326  	BindStrings     []string
   327  	UserTOML        string
   328  	AppName         string
   329  	Environment     string
   330  	OverrideName    string
   331  	ServiceGroupKey string
   332  }
   333  
   334  type Bind struct {
   335  	Alias   string
   336  	Service string
   337  	Group   string
   338  }
   339  
   340  func (s *Service) getPackageName(fullName string) string {
   341  	return strings.Split(fullName, "/")[1]
   342  }
   343  
   344  func (b *Bind) toBindString() string {
   345  	return fmt.Sprintf("%s:%s.%s", b.Alias, b.Service, b.Group)
   346  }
   347  
   348  func decodeConfig(d *schema.ResourceData) (*provisioner, error) {
   349  	p := &provisioner{
   350  		Version:          d.Get("version").(string),
   351  		Peer:             d.Get("peer").(string),
   352  		Services:         getServices(d.Get("service").(*schema.Set).List()),
   353  		UseSudo:          d.Get("use_sudo").(bool),
   354  		ServiceType:      d.Get("service_type").(string),
   355  		ServiceName:      d.Get("service_name").(string),
   356  		RingKey:          d.Get("ring_key").(string),
   357  		RingKeyContent:   d.Get("ring_key_content").(string),
   358  		PermanentPeer:    d.Get("permanent_peer").(bool),
   359  		ListenGossip:     d.Get("listen_gossip").(string),
   360  		ListenHTTP:       d.Get("listen_http").(string),
   361  		URL:              d.Get("url").(string),
   362  		Channel:          d.Get("channel").(string),
   363  		Events:           d.Get("events").(string),
   364  		OverrideName:     d.Get("override_name").(string),
   365  		Organization:     d.Get("organization").(string),
   366  		BuilderAuthToken: d.Get("builder_auth_token").(string),
   367  	}
   368  
   369  	return p, nil
   370  }
   371  
   372  func getServices(v []interface{}) []Service {
   373  	services := make([]Service, 0, len(v))
   374  	for _, rawServiceData := range v {
   375  		serviceData := rawServiceData.(map[string]interface{})
   376  		name := (serviceData["name"].(string))
   377  		strategy := (serviceData["strategy"].(string))
   378  		topology := (serviceData["topology"].(string))
   379  		channel := (serviceData["channel"].(string))
   380  		group := (serviceData["group"].(string))
   381  		url := (serviceData["url"].(string))
   382  		app := (serviceData["application"].(string))
   383  		env := (serviceData["environment"].(string))
   384  		override := (serviceData["override_name"].(string))
   385  		userToml := (serviceData["user_toml"].(string))
   386  		serviceGroupKey := (serviceData["service_key"].(string))
   387  		var bindStrings []string
   388  		binds := getBinds(serviceData["bind"].(*schema.Set).List())
   389  		for _, b := range serviceData["binds"].([]interface{}) {
   390  			bind, err := getBindFromString(b.(string))
   391  			if err != nil {
   392  				return nil
   393  			}
   394  			binds = append(binds, bind)
   395  		}
   396  
   397  		service := Service{
   398  			Name:            name,
   399  			Strategy:        strategy,
   400  			Topology:        topology,
   401  			Channel:         channel,
   402  			Group:           group,
   403  			URL:             url,
   404  			UserTOML:        userToml,
   405  			BindStrings:     bindStrings,
   406  			Binds:           binds,
   407  			AppName:         app,
   408  			Environment:     env,
   409  			OverrideName:    override,
   410  			ServiceGroupKey: serviceGroupKey,
   411  		}
   412  		services = append(services, service)
   413  	}
   414  	return services
   415  }
   416  
   417  func getBinds(v []interface{}) []Bind {
   418  	binds := make([]Bind, 0, len(v))
   419  	for _, rawBindData := range v {
   420  		bindData := rawBindData.(map[string]interface{})
   421  		alias := bindData["alias"].(string)
   422  		service := bindData["service"].(string)
   423  		group := bindData["group"].(string)
   424  		bind := Bind{
   425  			Alias:   alias,
   426  			Service: service,
   427  			Group:   group,
   428  		}
   429  		binds = append(binds, bind)
   430  	}
   431  	return binds
   432  }
   433  
   434  func (p *provisioner) uploadRingKey(o terraform.UIOutput, comm communicator.Communicator) error {
   435  	command := fmt.Sprintf("echo '%s' | hab ring key import", p.RingKeyContent)
   436  	if p.UseSudo {
   437  		command = fmt.Sprintf("echo '%s' | sudo hab ring key import", p.RingKeyContent)
   438  	}
   439  	return p.runCommand(o, comm, command)
   440  }
   441  
   442  func (p *provisioner) installHab(o terraform.UIOutput, comm communicator.Communicator) error {
   443  	// Build the install command
   444  	command := fmt.Sprintf("curl -L0 %s > install.sh", installURL)
   445  	if err := p.runCommand(o, comm, command); err != nil {
   446  		return err
   447  	}
   448  
   449  	// Run the install script
   450  	if p.Version == "" {
   451  		command = fmt.Sprintf("env HAB_NONINTERACTIVE=true bash ./install.sh ")
   452  	} else {
   453  		command = fmt.Sprintf("env HAB_NONINTERACTIVE=true bash ./install.sh -v %s", p.Version)
   454  	}
   455  
   456  	if p.UseSudo {
   457  		command = fmt.Sprintf("sudo %s", command)
   458  	}
   459  
   460  	if err := p.runCommand(o, comm, command); err != nil {
   461  		return err
   462  	}
   463  
   464  	if err := p.createHabUser(o, comm); err != nil {
   465  		return err
   466  	}
   467  
   468  	return p.runCommand(o, comm, fmt.Sprintf("rm -f install.sh"))
   469  }
   470  
   471  func (p *provisioner) startHab(o terraform.UIOutput, comm communicator.Communicator) error {
   472  	// Install the supervisor first
   473  	var command string
   474  	if p.Version == "" {
   475  		command += fmt.Sprintf("hab install core/hab-sup")
   476  	} else {
   477  		command += fmt.Sprintf("hab install core/hab-sup/%s", p.Version)
   478  	}
   479  
   480  	if p.UseSudo {
   481  		command = fmt.Sprintf("sudo -E %s", command)
   482  	}
   483  
   484  	command = fmt.Sprintf("env HAB_NONINTERACTIVE=true %s", command)
   485  
   486  	if err := p.runCommand(o, comm, command); err != nil {
   487  		return err
   488  	}
   489  
   490  	// Build up sup options
   491  	options := ""
   492  	if p.PermanentPeer {
   493  		options += " -I"
   494  	}
   495  
   496  	if p.ListenGossip != "" {
   497  		options += fmt.Sprintf(" --listen-gossip %s", p.ListenGossip)
   498  	}
   499  
   500  	if p.ListenHTTP != "" {
   501  		options += fmt.Sprintf(" --listen-http %s", p.ListenHTTP)
   502  	}
   503  
   504  	if p.Peer != "" {
   505  		options += fmt.Sprintf(" --peer %s", p.Peer)
   506  	}
   507  
   508  	if p.RingKey != "" {
   509  		options += fmt.Sprintf(" --ring %s", p.RingKey)
   510  	}
   511  
   512  	if p.URL != "" {
   513  		options += fmt.Sprintf(" --url %s", p.URL)
   514  	}
   515  
   516  	if p.Channel != "" {
   517  		options += fmt.Sprintf(" --channel %s", p.Channel)
   518  	}
   519  
   520  	if p.Events != "" {
   521  		options += fmt.Sprintf(" --events %s", p.Events)
   522  	}
   523  
   524  	if p.OverrideName != "" {
   525  		options += fmt.Sprintf(" --override-name %s", p.OverrideName)
   526  	}
   527  
   528  	if p.Organization != "" {
   529  		options += fmt.Sprintf(" --org %s", p.Organization)
   530  	}
   531  
   532  	p.SupOptions = options
   533  
   534  	switch p.ServiceType {
   535  	case "unmanaged":
   536  		return p.startHabUnmanaged(o, comm, options)
   537  	case "systemd":
   538  		return p.startHabSystemd(o, comm, options)
   539  	default:
   540  		return errors.New("Unsupported service type")
   541  	}
   542  }
   543  
   544  func (p *provisioner) startHabUnmanaged(o terraform.UIOutput, comm communicator.Communicator, options string) error {
   545  	// Create the sup directory for the log file
   546  	var command string
   547  	var token string
   548  	if p.UseSudo {
   549  		command = "sudo mkdir -p /hab/sup/default && sudo chmod o+w /hab/sup/default"
   550  	} else {
   551  		command = "mkdir -p /hab/sup/default && chmod o+w /hab/sup/default"
   552  	}
   553  	if err := p.runCommand(o, comm, command); err != nil {
   554  		return err
   555  	}
   556  
   557  	if p.BuilderAuthToken != "" {
   558  		token = fmt.Sprintf("env HAB_AUTH_TOKEN=%s", p.BuilderAuthToken)
   559  	}
   560  
   561  	if p.UseSudo {
   562  		command = fmt.Sprintf("(%s setsid sudo -E hab sup run %s > /hab/sup/default/sup.log 2>&1 &) ; sleep 1", token, options)
   563  	} else {
   564  		command = fmt.Sprintf("(%s setsid hab sup run %s > /hab/sup/default/sup.log 2>&1 <&1 &) ; sleep 1", token, options)
   565  	}
   566  	return p.runCommand(o, comm, command)
   567  }
   568  
   569  func (p *provisioner) startHabSystemd(o terraform.UIOutput, comm communicator.Communicator, options string) error {
   570  	// Create a new template and parse the client config into it
   571  	unitString := template.Must(template.New("hab-supervisor.service").Parse(systemdUnit))
   572  
   573  	var buf bytes.Buffer
   574  	err := unitString.Execute(&buf, p)
   575  	if err != nil {
   576  		return fmt.Errorf("Error executing %s template: %s", "hab-supervisor.service", err)
   577  	}
   578  
   579  	var command string
   580  	if p.UseSudo {
   581  		command = fmt.Sprintf("sudo echo '%s' | sudo tee /etc/systemd/system/%s.service > /dev/null", &buf, p.ServiceName)
   582  	} else {
   583  		command = fmt.Sprintf("echo '%s' | tee /etc/systemd/system/%s.service > /dev/null", &buf, p.ServiceName)
   584  	}
   585  
   586  	if err := p.runCommand(o, comm, command); err != nil {
   587  		return err
   588  	}
   589  
   590  	if p.UseSudo {
   591  		command = fmt.Sprintf("sudo systemctl enable hab-supervisor && sudo systemctl start hab-supervisor")
   592  	} else {
   593  		command = fmt.Sprintf("systemctl enable hab-supervisor && systemctl start hab-supervisor")
   594  	}
   595  	return p.runCommand(o, comm, command)
   596  }
   597  
   598  func (p *provisioner) createHabUser(o terraform.UIOutput, comm communicator.Communicator) error {
   599  	addUser := false
   600  	// Install busybox to get us the user tools we need
   601  	command := fmt.Sprintf("env HAB_NONINTERACTIVE=true hab install core/busybox")
   602  	if p.UseSudo {
   603  		command = fmt.Sprintf("sudo %s", command)
   604  	}
   605  	if err := p.runCommand(o, comm, command); err != nil {
   606  		return err
   607  	}
   608  
   609  	// Check for existing hab user
   610  	command = fmt.Sprintf("hab pkg exec core/busybox id hab")
   611  	if p.UseSudo {
   612  		command = fmt.Sprintf("sudo %s", command)
   613  	}
   614  	if err := p.runCommand(o, comm, command); err != nil {
   615  		o.Output("No existing hab user detected, creating...")
   616  		addUser = true
   617  	}
   618  
   619  	if addUser {
   620  		command = fmt.Sprintf("hab pkg exec core/busybox adduser -D -g \"\" hab")
   621  		if p.UseSudo {
   622  			command = fmt.Sprintf("sudo %s", command)
   623  		}
   624  		return p.runCommand(o, comm, command)
   625  	}
   626  
   627  	return nil
   628  }
   629  
   630  // In the future we'll remove the dedicated install once the synchronous load feature in hab-sup is
   631  // available. Until then we install here to provide output and a noisy failure mechanism because
   632  // if you install with the pkg load, it occurs asynchronously and fails quietly.
   633  func (p *provisioner) installHabPackage(o terraform.UIOutput, comm communicator.Communicator, service Service) error {
   634  	var command string
   635  	options := ""
   636  	if service.Channel != "" {
   637  		options += fmt.Sprintf(" --channel %s", service.Channel)
   638  	}
   639  
   640  	if service.URL != "" {
   641  		options += fmt.Sprintf(" --url %s", service.URL)
   642  	}
   643  	if p.UseSudo {
   644  		command = fmt.Sprintf("env HAB_NONINTERACTIVE=true sudo -E hab pkg install %s %s", service.Name, options)
   645  	} else {
   646  		command = fmt.Sprintf("env HAB_NONINTERACTIVE=true hab pkg install %s %s", service.Name, options)
   647  	}
   648  
   649  	if p.BuilderAuthToken != "" {
   650  		command = fmt.Sprintf("env HAB_AUTH_TOKEN=%s %s", p.BuilderAuthToken, command)
   651  	}
   652  	return p.runCommand(o, comm, command)
   653  }
   654  
   655  func (p *provisioner) startHabService(o terraform.UIOutput, comm communicator.Communicator, service Service) error {
   656  	var command string
   657  	if err := p.installHabPackage(o, comm, service); err != nil {
   658  		return err
   659  	}
   660  	if err := p.uploadUserTOML(o, comm, service); err != nil {
   661  		return err
   662  	}
   663  
   664  	// Upload service group key
   665  	if service.ServiceGroupKey != "" {
   666  		p.uploadServiceGroupKey(o, comm, service.ServiceGroupKey)
   667  	}
   668  
   669  	options := ""
   670  	if service.Topology != "" {
   671  		options += fmt.Sprintf(" --topology %s", service.Topology)
   672  	}
   673  
   674  	if service.Strategy != "" {
   675  		options += fmt.Sprintf(" --strategy %s", service.Strategy)
   676  	}
   677  
   678  	if service.Channel != "" {
   679  		options += fmt.Sprintf(" --channel %s", service.Channel)
   680  	}
   681  
   682  	if service.URL != "" {
   683  		options += fmt.Sprintf(" --url %s", service.URL)
   684  	}
   685  
   686  	if service.Group != "" {
   687  		options += fmt.Sprintf(" --group %s", service.Group)
   688  	}
   689  
   690  	for _, bind := range service.Binds {
   691  		options += fmt.Sprintf(" --bind %s", bind.toBindString())
   692  	}
   693  	command = fmt.Sprintf("hab svc load %s %s", service.Name, options)
   694  	if p.UseSudo {
   695  		command = fmt.Sprintf("sudo -E %s", command)
   696  	}
   697  	if p.BuilderAuthToken != "" {
   698  		command = fmt.Sprintf("env HAB_AUTH_TOKEN=%s %s", p.BuilderAuthToken, command)
   699  	}
   700  	return p.runCommand(o, comm, command)
   701  }
   702  
   703  func (p *provisioner) uploadServiceGroupKey(o terraform.UIOutput, comm communicator.Communicator, key string) error {
   704  	keyName := strings.Split(key, "\n")[1]
   705  	o.Output("Uploading service group key: " + keyName)
   706  	keyFileName := fmt.Sprintf("%s.box.key", keyName)
   707  	destPath := path.Join("/hab/cache/keys", keyFileName)
   708  	keyContent := strings.NewReader(key)
   709  	if p.UseSudo {
   710  		tempPath := path.Join("/tmp", keyFileName)
   711  		if err := comm.Upload(tempPath, keyContent); err != nil {
   712  			return err
   713  		}
   714  		command := fmt.Sprintf("sudo mv %s %s", tempPath, destPath)
   715  		return p.runCommand(o, comm, command)
   716  	}
   717  
   718  	return comm.Upload(destPath, keyContent)
   719  }
   720  
   721  func (p *provisioner) uploadUserTOML(o terraform.UIOutput, comm communicator.Communicator, service Service) error {
   722  	// Create the hab svc directory to lay down the user.toml before loading the service
   723  	o.Output("Uploading user.toml for service: " + service.Name)
   724  	destDir := fmt.Sprintf("/hab/svc/%s", service.getPackageName(service.Name))
   725  	command := fmt.Sprintf("mkdir -p %s", destDir)
   726  	if p.UseSudo {
   727  		command = fmt.Sprintf("sudo %s", command)
   728  	}
   729  	if err := p.runCommand(o, comm, command); err != nil {
   730  		return err
   731  	}
   732  
   733  	userToml := strings.NewReader(service.UserTOML)
   734  
   735  	if p.UseSudo {
   736  		if err := comm.Upload("/tmp/user.toml", userToml); err != nil {
   737  			return err
   738  		}
   739  		command = fmt.Sprintf("sudo mv /tmp/user.toml %s", destDir)
   740  		return p.runCommand(o, comm, command)
   741  	}
   742  
   743  	return comm.Upload(path.Join(destDir, "user.toml"), userToml)
   744  
   745  }
   746  
   747  func (p *provisioner) copyOutput(o terraform.UIOutput, r io.Reader) {
   748  	lr := linereader.New(r)
   749  	for line := range lr.Ch {
   750  		o.Output(line)
   751  	}
   752  }
   753  
   754  func (p *provisioner) runCommand(o terraform.UIOutput, comm communicator.Communicator, command string) error {
   755  	outR, outW := io.Pipe()
   756  	errR, errW := io.Pipe()
   757  
   758  	go p.copyOutput(o, outR)
   759  	go p.copyOutput(o, errR)
   760  	defer outW.Close()
   761  	defer errW.Close()
   762  
   763  	cmd := &remote.Cmd{
   764  		Command: command,
   765  		Stdout:  outW,
   766  		Stderr:  errW,
   767  	}
   768  
   769  	if err := comm.Start(cmd); err != nil {
   770  		return fmt.Errorf("Error executing command %q: %v", cmd.Command, err)
   771  	}
   772  
   773  	if err := cmd.Wait(); err != nil {
   774  		return err
   775  	}
   776  
   777  	return nil
   778  }
   779  
   780  func getBindFromString(bind string) (Bind, error) {
   781  	t := strings.FieldsFunc(bind, func(d rune) bool {
   782  		switch d {
   783  		case ':', '.':
   784  			return true
   785  		}
   786  		return false
   787  	})
   788  	if len(t) != 3 {
   789  		return Bind{}, errors.New("Invalid bind specification: " + bind)
   790  	}
   791  	return Bind{Alias: t[0], Service: t[1], Group: t[2]}, nil
   792  }