github.com/chentex/terraform@v0.11.2-0.20171208003256-252e8145842e/builtin/provisioners/habitat/resource_provisioner.go (about)

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