terraform/builtin/provisioners/habitat/linux_provisioner.go

378 lines
11 KiB
Go
Raw Normal View History

package habitat
import (
"bytes"
"errors"
"fmt"
"github.com/hashicorp/terraform/communicator"
"github.com/hashicorp/terraform/terraform"
"path"
"path/filepath"
"strings"
"text/template"
)
const installURL = "https://raw.githubusercontent.com/habitat-sh/habitat/master/components/hab/install.sh"
const systemdUnit = `[Unit]
Description=Habitat Supervisor
[Service]
ExecStart=/bin/hab sup run{{ .SupOptions }}
Restart=on-failure
{{ if .GatewayAuthToken -}}
Environment="HAB_SUP_GATEWAY_AUTH_TOKEN={{ .GatewayAuthToken }}"
{{ end -}}
{{ if .BuilderAuthToken -}}
Environment="HAB_AUTH_TOKEN={{ .BuilderAuthToken }}"
{{ end -}}
[Install]
WantedBy=default.target
`
func (p *provisioner) linuxInstallHabitat(o terraform.UIOutput, comm communicator.Communicator) error {
// Download the hab installer
if err := p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("curl --silent -L0 %s > install.sh", installURL))); err != nil {
return err
}
// Run the install script
var command string
if p.Version == "" {
command = fmt.Sprintf("bash ./install.sh ")
} else {
command = fmt.Sprintf("bash ./install.sh -v %s", p.Version)
}
if err := p.runCommand(o, comm, p.linuxGetCommand(command)); err != nil {
return err
}
// Accept the license
if p.AcceptLicense {
var cmd string
if p.UseSudo == true {
cmd = "env HAB_LICENSE=accept sudo -E /bin/bash -c 'hab -V'"
} else {
cmd = "env HAB_LICENSE=accept /bin/bash -c 'hab -V'"
}
if err := p.runCommand(o, comm, cmd); err != nil {
return err
}
}
// Create the hab user
if err := p.createHabUser(o, comm); err != nil {
return err
}
// Cleanup the installer
return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("rm -f install.sh")))
}
func (p *provisioner) createHabUser(o terraform.UIOutput, comm communicator.Communicator) error {
var addUser bool
// Install busybox to get us the user tools we need
if err := p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("hab install core/busybox"))); err != nil {
return err
}
// Check for existing hab user
if err := p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("hab pkg exec core/busybox id hab"))); err != nil {
o.Output("No existing hab user detected, creating...")
addUser = true
}
if addUser {
return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("hab pkg exec core/busybox adduser -D -g \"\" hab")))
}
return nil
}
func (p *provisioner) linuxStartHabitat(o terraform.UIOutput, comm communicator.Communicator) error {
// Install the supervisor first
var command string
if p.Version == "" {
command += p.linuxGetCommand(fmt.Sprintf("hab install core/hab-sup"))
} else {
command += p.linuxGetCommand(fmt.Sprintf("hab install core/hab-sup/%s", p.Version))
}
if err := p.runCommand(o, comm, command); err != nil {
return err
}
// Build up supervisor options
options := ""
if p.PermanentPeer {
options += " --permanent-peer"
}
if p.ListenCtl != "" {
options += fmt.Sprintf(" --listen-ctl %s", p.ListenCtl)
}
if p.ListenGossip != "" {
options += fmt.Sprintf(" --listen-gossip %s", p.ListenGossip)
}
if p.ListenHTTP != "" {
options += fmt.Sprintf(" --listen-http %s", p.ListenHTTP)
}
if p.Peer != "" {
options += fmt.Sprintf(" %s", p.Peer)
}
if len(p.Peers) > 0 {
if len(p.Peers) == 1 {
options += fmt.Sprintf(" --peer %s", p.Peers[0])
} else {
options += fmt.Sprintf(" --peer %s", strings.Join(p.Peers, " --peer "))
}
}
if p.RingKey != "" {
options += fmt.Sprintf(" --ring %s", p.RingKey)
}
if p.URL != "" {
options += fmt.Sprintf(" --url %s", p.URL)
}
if p.Channel != "" {
options += fmt.Sprintf(" --channel %s", p.Channel)
}
if p.Events != "" {
options += fmt.Sprintf(" --events %s", p.Events)
}
if p.Organization != "" {
options += fmt.Sprintf(" --org %s", p.Organization)
}
if p.HttpDisable == true {
options += fmt.Sprintf(" --http-disable")
}
if p.AutoUpdate == true {
options += fmt.Sprintf(" --auto-update")
}
p.SupOptions = options
// Start hab depending on service type
switch p.ServiceType {
case "unmanaged":
return p.linuxStartHabitatUnmanaged(o, comm, options)
case "systemd":
return p.linuxStartHabitatSystemd(o, comm, options)
default:
return errors.New("unsupported service type")
}
}
// This func is a little different than the others since we need to expose HAB_AUTH_TOKEN to a shell
// sub-process that's actually running the supervisor.
func (p *provisioner) linuxStartHabitatUnmanaged(o terraform.UIOutput, comm communicator.Communicator, options string) error {
var token string
// Create the sup directory for the log file
if err := p.runCommand(o, comm, p.linuxGetCommand("mkdir -p /hab/sup/default && chmod o+w /hab/sup/default")); err != nil {
return err
}
// Set HAB_AUTH_TOKEN if provided
if p.BuilderAuthToken != "" {
token = fmt.Sprintf("env HAB_AUTH_TOKEN=%s ", p.BuilderAuthToken)
}
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)))
}
func (p *provisioner) linuxStartHabitatSystemd(o terraform.UIOutput, comm communicator.Communicator, options string) error {
// Create a new template and parse the client config into it
unitString := template.Must(template.New("hab-supervisor.service").Parse(systemdUnit))
var buf bytes.Buffer
err := unitString.Execute(&buf, p)
if err != nil {
return fmt.Errorf("error executing %s.service template: %s", p.ServiceName, err)
}
if err := p.linuxUploadSystemdUnit(o, comm, &buf); err != nil {
return err
}
return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("systemctl enable %s && systemctl start %s", p.ServiceName, p.ServiceName)))
}
func (p *provisioner) linuxUploadSystemdUnit(o terraform.UIOutput, comm communicator.Communicator, contents *bytes.Buffer) error {
destination := fmt.Sprintf("/etc/systemd/system/%s.service", p.ServiceName)
if p.UseSudo {
tempPath := fmt.Sprintf("/tmp/%s.service", p.ServiceName)
if err := comm.Upload(tempPath, contents); err != nil {
return err
}
return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("mv %s %s", tempPath, destination)))
}
return comm.Upload(destination, contents)
}
func (p *provisioner) linuxUploadRingKey(o terraform.UIOutput, comm communicator.Communicator) error {
return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf(`echo -e "%s" | hab ring key import`, p.RingKeyContent)))
}
func (p *provisioner) linuxUploadCtlSecret(o terraform.UIOutput, comm communicator.Communicator) error {
destination := fmt.Sprintf("/hab/sup/default/CTL_SECRET")
// Create the destination directory
err := p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("mkdir -p %s", filepath.Dir(destination))))
if err != nil {
return err
}
keyContent := strings.NewReader(p.CtlSecret)
if p.UseSudo {
tempPath := fmt.Sprintf("/tmp/CTL_SECRET")
if err := comm.Upload(tempPath, keyContent); err != nil {
return err
}
return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("chown root:root %s && chmod 0600 %s && mv %s %s", tempPath, tempPath, tempPath, destination)))
}
return comm.Upload(destination, keyContent)
}
//
// Habitat Services
//
func (p *provisioner) linuxStartHabitatService(o terraform.UIOutput, comm communicator.Communicator, service Service) error {
var options string
if err := p.linuxInstallHabitatPackage(o, comm, service); err != nil {
return err
}
if err := p.uploadUserTOML(o, comm, service); err != nil {
return err
}
// Upload service group key
if service.ServiceGroupKey != "" {
err := p.uploadServiceGroupKey(o, comm, service.ServiceGroupKey)
if err != nil {
return err
}
}
if service.Topology != "" {
options += fmt.Sprintf(" --topology %s", service.Topology)
}
if service.Strategy != "" {
options += fmt.Sprintf(" --strategy %s", service.Strategy)
}
if service.Channel != "" {
options += fmt.Sprintf(" --channel %s", service.Channel)
}
if service.URL != "" {
options += fmt.Sprintf(" --url %s", service.URL)
}
if service.Group != "" {
options += fmt.Sprintf(" --group %s", service.Group)
}
for _, bind := range service.Binds {
options += fmt.Sprintf(" --bind %s", bind.toBindString())
}
return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("hab svc load %s %s", service.Name, options)))
}
// In the future we'll remove the dedicated install once the synchronous load feature in hab-sup is
// available. Until then we install here to provide output and a noisy failure mechanism because
// if you install with the pkg load, it occurs asynchronously and fails quietly.
func (p *provisioner) linuxInstallHabitatPackage(o terraform.UIOutput, comm communicator.Communicator, service Service) error {
var options string
if service.Channel != "" {
options += fmt.Sprintf(" --channel %s", service.Channel)
}
if service.URL != "" {
options += fmt.Sprintf(" --url %s", service.URL)
}
return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("hab pkg install %s %s", service.Name, options)))
}
func (p *provisioner) uploadServiceGroupKey(o terraform.UIOutput, comm communicator.Communicator, key string) error {
keyName := strings.Split(key, "\n")[1]
o.Output("Uploading service group key: " + keyName)
keyFileName := fmt.Sprintf("%s.box.key", keyName)
destPath := path.Join("/hab/cache/keys", keyFileName)
keyContent := strings.NewReader(key)
if p.UseSudo {
tempPath := path.Join("/tmp", keyFileName)
if err := comm.Upload(tempPath, keyContent); err != nil {
return err
}
return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("mv %s %s", tempPath, destPath)))
}
return comm.Upload(destPath, keyContent)
}
func (p *provisioner) uploadUserTOML(o terraform.UIOutput, comm communicator.Communicator, service Service) error {
// Create the hab svc directory to lay down the user.toml before loading the service
o.Output("Uploading user.toml for service: " + service.Name)
destDir := fmt.Sprintf("/hab/user/%s/config", service.getPackageName(service.Name))
command := p.linuxGetCommand(fmt.Sprintf("mkdir -p %s", destDir))
if err := p.runCommand(o, comm, command); err != nil {
return err
}
userToml := strings.NewReader(service.UserTOML)
if p.UseSudo {
checksum := service.getServiceNameChecksum()
if err := comm.Upload(fmt.Sprintf("/tmp/user-%s.toml", checksum), userToml); err != nil {
return err
}
command = p.linuxGetCommand(fmt.Sprintf("chmod o-r /tmp/user-%s.toml && mv /tmp/user-%s.toml %s/user.toml", checksum, checksum, destDir))
return p.runCommand(o, comm, command)
}
return comm.Upload(path.Join(destDir, "user.toml"), userToml)
}
func (p *provisioner) linuxGetCommand(command string) string {
// Always set HAB_NONINTERACTIVE & HAB_NOCOLORING
env := fmt.Sprintf("env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true")
// Set builder auth token
if p.BuilderAuthToken != "" {
env += fmt.Sprintf(" HAB_AUTH_TOKEN=%s", p.BuilderAuthToken)
}
if p.UseSudo {
command = fmt.Sprintf("%s sudo -E /bin/bash -c '%s'", env, command)
} else {
command = fmt.Sprintf("%s /bin/bash -c '%s'", env, command)
}
return command
}