github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/builtin/provisioners/habitat/linux_provisioner.go (about)

     1  package habitat
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"github.com/hashicorp/terraform/communicator"
     8  	"github.com/hashicorp/terraform/terraform"
     9  	"path"
    10  	"path/filepath"
    11  	"strings"
    12  	"text/template"
    13  )
    14  
    15  const installURL = "https://raw.githubusercontent.com/habitat-sh/habitat/master/components/hab/install.sh"
    16  const systemdUnit = `[Unit]
    17  Description=Habitat Supervisor
    18  
    19  [Service]
    20  ExecStart=/bin/hab sup run{{ .SupOptions }}
    21  Restart=on-failure
    22  {{ if .GatewayAuthToken -}}
    23  Environment="HAB_SUP_GATEWAY_AUTH_TOKEN={{ .GatewayAuthToken }}"
    24  {{ end -}}
    25  {{ if .BuilderAuthToken -}}
    26  Environment="HAB_AUTH_TOKEN={{ .BuilderAuthToken }}"
    27  {{ end -}}
    28  
    29  [Install]
    30  WantedBy=default.target
    31  `
    32  
    33  func (p *provisioner) linuxInstallHabitat(o terraform.UIOutput, comm communicator.Communicator) error {
    34  	// Download the hab installer
    35  	if err := p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("curl --silent -L0 %s > install.sh", installURL))); err != nil {
    36  		return err
    37  	}
    38  
    39  	// Run the install script
    40  	var command string
    41  	if p.Version == "" {
    42  		command = fmt.Sprintf("bash ./install.sh ")
    43  	} else {
    44  		command = fmt.Sprintf("bash ./install.sh -v %s", p.Version)
    45  	}
    46  
    47  	if err := p.runCommand(o, comm, p.linuxGetCommand(command)); err != nil {
    48  		return err
    49  	}
    50  
    51  	// Accept the license
    52  	if p.AcceptLicense {
    53  		var cmd string
    54  
    55  		if p.UseSudo == true {
    56  			cmd = "env HAB_LICENSE=accept sudo -E /bin/bash -c 'hab -V'"
    57  		} else {
    58  			cmd = "env HAB_LICENSE=accept /bin/bash -c 'hab -V'"
    59  		}
    60  
    61  		if err := p.runCommand(o, comm, cmd); err != nil {
    62  			return err
    63  		}
    64  	}
    65  
    66  	// Create the hab user
    67  	if err := p.createHabUser(o, comm); err != nil {
    68  		return err
    69  	}
    70  
    71  	// Cleanup the installer
    72  	return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("rm -f install.sh")))
    73  }
    74  
    75  func (p *provisioner) createHabUser(o terraform.UIOutput, comm communicator.Communicator) error {
    76  	var addUser bool
    77  
    78  	// Install busybox to get us the user tools we need
    79  	if err := p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("hab install core/busybox"))); err != nil {
    80  		return err
    81  	}
    82  
    83  	// Check for existing hab user
    84  	if err := p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("hab pkg exec core/busybox id hab"))); err != nil {
    85  		o.Output("No existing hab user detected, creating...")
    86  		addUser = true
    87  	}
    88  
    89  	if addUser {
    90  		return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("hab pkg exec core/busybox adduser -D -g \"\" hab")))
    91  	}
    92  
    93  	return nil
    94  }
    95  
    96  func (p *provisioner) linuxStartHabitat(o terraform.UIOutput, comm communicator.Communicator) error {
    97  	// Install the supervisor first
    98  	var command string
    99  	if p.Version == "" {
   100  		command += p.linuxGetCommand(fmt.Sprintf("hab install core/hab-sup"))
   101  	} else {
   102  		command += p.linuxGetCommand(fmt.Sprintf("hab install core/hab-sup/%s", p.Version))
   103  	}
   104  
   105  	if err := p.runCommand(o, comm, command); err != nil {
   106  		return err
   107  	}
   108  
   109  	// Build up supervisor options
   110  	options := ""
   111  	if p.PermanentPeer {
   112  		options += " --permanent-peer"
   113  	}
   114  
   115  	if p.ListenCtl != "" {
   116  		options += fmt.Sprintf(" --listen-ctl %s", p.ListenCtl)
   117  	}
   118  
   119  	if p.ListenGossip != "" {
   120  		options += fmt.Sprintf(" --listen-gossip %s", p.ListenGossip)
   121  	}
   122  
   123  	if p.ListenHTTP != "" {
   124  		options += fmt.Sprintf(" --listen-http %s", p.ListenHTTP)
   125  	}
   126  
   127  	if p.Peer != "" {
   128  		options += fmt.Sprintf(" %s", p.Peer)
   129  	}
   130  
   131  	if len(p.Peers) > 0 {
   132  		if len(p.Peers) == 1 {
   133  			options += fmt.Sprintf(" --peer %s", p.Peers[0])
   134  		} else {
   135  			options += fmt.Sprintf(" --peer %s", strings.Join(p.Peers, " --peer "))
   136  		}
   137  	}
   138  
   139  	if p.RingKey != "" {
   140  		options += fmt.Sprintf(" --ring %s", p.RingKey)
   141  	}
   142  
   143  	if p.URL != "" {
   144  		options += fmt.Sprintf(" --url %s", p.URL)
   145  	}
   146  
   147  	if p.Channel != "" {
   148  		options += fmt.Sprintf(" --channel %s", p.Channel)
   149  	}
   150  
   151  	if p.Events != "" {
   152  		options += fmt.Sprintf(" --events %s", p.Events)
   153  	}
   154  
   155  	if p.Organization != "" {
   156  		options += fmt.Sprintf(" --org %s", p.Organization)
   157  	}
   158  
   159  	if p.HttpDisable == true {
   160  		options += fmt.Sprintf(" --http-disable")
   161  	}
   162  
   163  	if p.AutoUpdate == true {
   164  		options += fmt.Sprintf(" --auto-update")
   165  	}
   166  
   167  	p.SupOptions = options
   168  
   169  	// Start hab depending on service type
   170  	switch p.ServiceType {
   171  	case "unmanaged":
   172  		return p.linuxStartHabitatUnmanaged(o, comm, options)
   173  	case "systemd":
   174  		return p.linuxStartHabitatSystemd(o, comm, options)
   175  	default:
   176  		return errors.New("unsupported service type")
   177  	}
   178  }
   179  
   180  // This func is a little different than the others since we need to expose HAB_AUTH_TOKEN to a shell
   181  // sub-process that's actually running the supervisor.
   182  func (p *provisioner) linuxStartHabitatUnmanaged(o terraform.UIOutput, comm communicator.Communicator, options string) error {
   183  	var token string
   184  
   185  	// Create the sup directory for the log file
   186  	if err := p.runCommand(o, comm, p.linuxGetCommand("mkdir -p /hab/sup/default && chmod o+w /hab/sup/default")); err != nil {
   187  		return err
   188  	}
   189  
   190  	// Set HAB_AUTH_TOKEN if provided
   191  	if p.BuilderAuthToken != "" {
   192  		token = fmt.Sprintf("env HAB_AUTH_TOKEN=%s ", p.BuilderAuthToken)
   193  	}
   194  
   195  	return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("(%ssetsid hab sup run%s > /hab/sup/default/sup.log 2>&1 <&1 &) ; sleep 1", token, options)))
   196  }
   197  
   198  func (p *provisioner) linuxStartHabitatSystemd(o terraform.UIOutput, comm communicator.Communicator, options string) error {
   199  	// Create a new template and parse the client config into it
   200  	unitString := template.Must(template.New("hab-supervisor.service").Parse(systemdUnit))
   201  
   202  	var buf bytes.Buffer
   203  	err := unitString.Execute(&buf, p)
   204  	if err != nil {
   205  		return fmt.Errorf("error executing %s.service template: %s", p.ServiceName, err)
   206  	}
   207  
   208  	if err := p.linuxUploadSystemdUnit(o, comm, &buf); err != nil {
   209  		return err
   210  	}
   211  
   212  	return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("systemctl enable %s && systemctl start %s", p.ServiceName, p.ServiceName)))
   213  }
   214  
   215  func (p *provisioner) linuxUploadSystemdUnit(o terraform.UIOutput, comm communicator.Communicator, contents *bytes.Buffer) error {
   216  	destination := fmt.Sprintf("/etc/systemd/system/%s.service", p.ServiceName)
   217  
   218  	if p.UseSudo {
   219  		tempPath := fmt.Sprintf("/tmp/%s.service", p.ServiceName)
   220  		if err := comm.Upload(tempPath, contents); err != nil {
   221  			return err
   222  		}
   223  
   224  		return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("mv %s %s", tempPath, destination)))
   225  	}
   226  
   227  	return comm.Upload(destination, contents)
   228  }
   229  
   230  func (p *provisioner) linuxUploadRingKey(o terraform.UIOutput, comm communicator.Communicator) error {
   231  	return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf(`echo -e "%s" | hab ring key import`, p.RingKeyContent)))
   232  }
   233  
   234  func (p *provisioner) linuxUploadCtlSecret(o terraform.UIOutput, comm communicator.Communicator) error {
   235  	destination := fmt.Sprintf("/hab/sup/default/CTL_SECRET")
   236  	// Create the destination directory
   237  	err := p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("mkdir -p %s", filepath.Dir(destination))))
   238  	if err != nil {
   239  		return err
   240  	}
   241  
   242  	keyContent := strings.NewReader(p.CtlSecret)
   243  	if p.UseSudo {
   244  		tempPath := fmt.Sprintf("/tmp/CTL_SECRET")
   245  		if err := comm.Upload(tempPath, keyContent); err != nil {
   246  			return err
   247  		}
   248  
   249  		return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("mv %s %s && chown root:root %s && chmod 0600 %s", tempPath, destination, destination, destination)))
   250  	}
   251  
   252  	return comm.Upload(destination, keyContent)
   253  }
   254  
   255  //
   256  // Habitat Services
   257  //
   258  func (p *provisioner) linuxStartHabitatService(o terraform.UIOutput, comm communicator.Communicator, service Service) error {
   259  	var options string
   260  
   261  	if err := p.linuxInstallHabitatPackage(o, comm, service); err != nil {
   262  		return err
   263  	}
   264  	if err := p.uploadUserTOML(o, comm, service); err != nil {
   265  		return err
   266  	}
   267  
   268  	// Upload service group key
   269  	if service.ServiceGroupKey != "" {
   270  		err := p.uploadServiceGroupKey(o, comm, service.ServiceGroupKey)
   271  		if err != nil {
   272  			return err
   273  		}
   274  	}
   275  
   276  	if service.Topology != "" {
   277  		options += fmt.Sprintf(" --topology %s", service.Topology)
   278  	}
   279  
   280  	if service.Strategy != "" {
   281  		options += fmt.Sprintf(" --strategy %s", service.Strategy)
   282  	}
   283  
   284  	if service.Channel != "" {
   285  		options += fmt.Sprintf(" --channel %s", service.Channel)
   286  	}
   287  
   288  	if service.URL != "" {
   289  		options += fmt.Sprintf(" --url %s", service.URL)
   290  	}
   291  
   292  	if service.Group != "" {
   293  		options += fmt.Sprintf(" --group %s", service.Group)
   294  	}
   295  
   296  	for _, bind := range service.Binds {
   297  		options += fmt.Sprintf(" --bind %s", bind.toBindString())
   298  	}
   299  
   300  	return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("hab svc load %s %s", service.Name, options)))
   301  }
   302  
   303  // In the future we'll remove the dedicated install once the synchronous load feature in hab-sup is
   304  // available. Until then we install here to provide output and a noisy failure mechanism because
   305  // if you install with the pkg load, it occurs asynchronously and fails quietly.
   306  func (p *provisioner) linuxInstallHabitatPackage(o terraform.UIOutput, comm communicator.Communicator, service Service) error {
   307  	var options string
   308  
   309  	if service.Channel != "" {
   310  		options += fmt.Sprintf(" --channel %s", service.Channel)
   311  	}
   312  
   313  	if service.URL != "" {
   314  		options += fmt.Sprintf(" --url %s", service.URL)
   315  	}
   316  
   317  	return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("hab pkg install %s %s", service.Name, options)))
   318  }
   319  
   320  func (p *provisioner) uploadServiceGroupKey(o terraform.UIOutput, comm communicator.Communicator, key string) error {
   321  	keyName := strings.Split(key, "\n")[1]
   322  	o.Output("Uploading service group key: " + keyName)
   323  	keyFileName := fmt.Sprintf("%s.box.key", keyName)
   324  	destPath := path.Join("/hab/cache/keys", keyFileName)
   325  	keyContent := strings.NewReader(key)
   326  	if p.UseSudo {
   327  		tempPath := path.Join("/tmp", keyFileName)
   328  		if err := comm.Upload(tempPath, keyContent); err != nil {
   329  			return err
   330  		}
   331  
   332  		return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("mv %s %s", tempPath, destPath)))
   333  	}
   334  
   335  	return comm.Upload(destPath, keyContent)
   336  }
   337  
   338  func (p *provisioner) uploadUserTOML(o terraform.UIOutput, comm communicator.Communicator, service Service) error {
   339  	// Create the hab svc directory to lay down the user.toml before loading the service
   340  	o.Output("Uploading user.toml for service: " + service.Name)
   341  	destDir := fmt.Sprintf("/hab/user/%s/config", service.getPackageName(service.Name))
   342  	command := p.linuxGetCommand(fmt.Sprintf("mkdir -p %s", destDir))
   343  	if err := p.runCommand(o, comm, command); err != nil {
   344  		return err
   345  	}
   346  
   347  	userToml := strings.NewReader(service.UserTOML)
   348  
   349  	if p.UseSudo {
   350  		if err := comm.Upload(fmt.Sprintf("/tmp/user-%s.toml", service.getServiceNameChecksum()), userToml); err != nil {
   351  			return err
   352  		}
   353  		command = p.linuxGetCommand(fmt.Sprintf("mv /tmp/user-%s.toml %s/user.toml", service.getServiceNameChecksum(), destDir))
   354  		return p.runCommand(o, comm, command)
   355  	}
   356  
   357  	return comm.Upload(path.Join(destDir, "user.toml"), userToml)
   358  }
   359  
   360  func (p *provisioner) linuxGetCommand(command string) string {
   361  	// Always set HAB_NONINTERACTIVE & HAB_NOCOLORING
   362  	env := fmt.Sprintf("env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true")
   363  
   364  	// Set builder auth token
   365  	if p.BuilderAuthToken != "" {
   366  		env += fmt.Sprintf(" HAB_AUTH_TOKEN=%s", p.BuilderAuthToken)
   367  	}
   368  
   369  	if p.UseSudo {
   370  		command = fmt.Sprintf("%s sudo -E /bin/bash -c '%s'", env, command)
   371  	} else {
   372  		command = fmt.Sprintf("%s /bin/bash -c '%s'", env, command)
   373  	}
   374  
   375  	return command
   376  }