Merge pull request #22705 from kmott/habitat-provisioner-updates
Habitat provisioner updates
This commit is contained in:
commit
b40385772e
|
@ -0,0 +1,376 @@
|
||||||
|
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("mv %s %s && chown root:root %s && chmod 0600 %s", tempPath, destination, destination, 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 {
|
||||||
|
if err := comm.Upload(fmt.Sprintf("/tmp/user-%s.toml", service.getServiceNameChecksum()), userToml); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
command = p.linuxGetCommand(fmt.Sprintf("mv /tmp/user-%s.toml %s/user.toml", service.getServiceNameChecksum(), 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
|
||||||
|
}
|
|
@ -0,0 +1,348 @@
|
||||||
|
package habitat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/terraform/communicator"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const linuxDefaultSystemdUnitFileContents = `[Unit]
|
||||||
|
Description=Habitat Supervisor
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/bin/hab sup run --peer host1 --peer 1.2.3.4 --auto-update
|
||||||
|
Restart=on-failure
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target`
|
||||||
|
|
||||||
|
const linuxCustomSystemdUnitFileContents = `[Unit]
|
||||||
|
Description=Habitat Supervisor
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/bin/hab sup run --listen-ctl 192.168.0.1:8443 --listen-gossip 192.168.10.1:9443 --listen-http 192.168.20.1:8080 --peer host1 --peer host2 --peer 1.2.3.4 --peer 5.6.7.8 --peer foo.example.com
|
||||||
|
Restart=on-failure
|
||||||
|
Environment="HAB_SUP_GATEWAY_AUTH_TOKEN=ea7-beef"
|
||||||
|
Environment="HAB_AUTH_TOKEN=dead-beef"
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target`
|
||||||
|
|
||||||
|
func TestLinuxProvisioner_linuxInstallHabitat(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
Config map[string]interface{}
|
||||||
|
Commands map[string]bool
|
||||||
|
}{
|
||||||
|
"Installation with sudo": {
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"version": "0.79.1",
|
||||||
|
"auto_update": true,
|
||||||
|
"use_sudo": true,
|
||||||
|
},
|
||||||
|
|
||||||
|
Commands: map[string]bool{
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'curl --silent -L0 https://raw.githubusercontent.com/habitat-sh/habitat/master/components/hab/install.sh > install.sh'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'bash ./install.sh -v 0.79.1'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'hab install core/busybox'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'hab pkg exec core/busybox adduser -D -g \"\" hab'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'rm -f install.sh'": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Installation without sudo": {
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"version": "0.79.1",
|
||||||
|
"auto_update": true,
|
||||||
|
"use_sudo": false,
|
||||||
|
},
|
||||||
|
|
||||||
|
Commands: map[string]bool{
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true /bin/bash -c 'curl --silent -L0 https://raw.githubusercontent.com/habitat-sh/habitat/master/components/hab/install.sh > install.sh'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true /bin/bash -c 'bash ./install.sh -v 0.79.1'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true /bin/bash -c 'hab install core/busybox'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true /bin/bash -c 'hab pkg exec core/busybox adduser -D -g \"\" hab'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true /bin/bash -c 'rm -f install.sh'": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Installation with Habitat license acceptance": {
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"version": "0.81.0",
|
||||||
|
"accept_license": true,
|
||||||
|
"auto_update": true,
|
||||||
|
"use_sudo": true,
|
||||||
|
},
|
||||||
|
|
||||||
|
Commands: map[string]bool{
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'curl --silent -L0 https://raw.githubusercontent.com/habitat-sh/habitat/master/components/hab/install.sh > install.sh'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'bash ./install.sh -v 0.81.0'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'hab install core/busybox'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'hab pkg exec core/busybox adduser -D -g \"\" hab'": true,
|
||||||
|
"env HAB_LICENSE=accept sudo -E /bin/bash -c 'hab -V'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'rm -f install.sh'": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
o := new(terraform.MockUIOutput)
|
||||||
|
c := new(communicator.MockCommunicator)
|
||||||
|
|
||||||
|
for k, tc := range cases {
|
||||||
|
c.Commands = tc.Commands
|
||||||
|
|
||||||
|
p, err := decodeConfig(
|
||||||
|
schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.linuxInstallHabitat(o, c)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test %q failed: %v", k, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinuxProvisioner_linuxStartHabitat(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
Config map[string]interface{}
|
||||||
|
Commands map[string]bool
|
||||||
|
Uploads map[string]string
|
||||||
|
}{
|
||||||
|
"Start systemd Habitat with sudo": {
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"version": "0.79.1",
|
||||||
|
"auto_update": true,
|
||||||
|
"use_sudo": true,
|
||||||
|
"service_name": "hab-sup",
|
||||||
|
"peer": "--peer host1",
|
||||||
|
"peers": []interface{}{"1.2.3.4"},
|
||||||
|
},
|
||||||
|
|
||||||
|
Commands: map[string]bool{
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'hab install core/hab-sup/0.79.1'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'systemctl enable hab-sup && systemctl start hab-sup'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'mv /tmp/hab-sup.service /etc/systemd/system/hab-sup.service'": true,
|
||||||
|
},
|
||||||
|
|
||||||
|
Uploads: map[string]string{
|
||||||
|
"/tmp/hab-sup.service": linuxDefaultSystemdUnitFileContents,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Start systemd Habitat without sudo": {
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"version": "0.79.1",
|
||||||
|
"auto_update": true,
|
||||||
|
"use_sudo": false,
|
||||||
|
"service_name": "hab-sup",
|
||||||
|
"peer": "--peer host1",
|
||||||
|
"peers": []interface{}{"1.2.3.4"},
|
||||||
|
},
|
||||||
|
|
||||||
|
Commands: map[string]bool{
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true /bin/bash -c 'hab install core/hab-sup/0.79.1'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true /bin/bash -c 'systemctl enable hab-sup && systemctl start hab-sup'": true,
|
||||||
|
},
|
||||||
|
|
||||||
|
Uploads: map[string]string{
|
||||||
|
"/etc/systemd/system/hab-sup.service": linuxDefaultSystemdUnitFileContents,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Start unmanaged Habitat with sudo": {
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"version": "0.81.0",
|
||||||
|
"license": "accept-no-persist",
|
||||||
|
"auto_update": true,
|
||||||
|
"use_sudo": true,
|
||||||
|
"service_type": "unmanaged",
|
||||||
|
"peer": "--peer host1",
|
||||||
|
"peers": []interface{}{"1.2.3.4"},
|
||||||
|
},
|
||||||
|
|
||||||
|
Commands: map[string]bool{
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'hab install core/hab-sup/0.81.0'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'mkdir -p /hab/sup/default && chmod o+w /hab/sup/default'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c '(setsid hab sup run --peer host1 --peer 1.2.3.4 --auto-update > /hab/sup/default/sup.log 2>&1 <&1 &) ; sleep 1'": true,
|
||||||
|
},
|
||||||
|
|
||||||
|
Uploads: map[string]string{
|
||||||
|
"/etc/systemd/system/hab-sup.service": linuxDefaultSystemdUnitFileContents,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Start Habitat with custom config": {
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"version": "0.79.1",
|
||||||
|
"auto_update": false,
|
||||||
|
"use_sudo": true,
|
||||||
|
"service_name": "hab-sup",
|
||||||
|
"peer": "--peer host1 --peer host2",
|
||||||
|
"peers": []interface{}{"1.2.3.4", "5.6.7.8", "foo.example.com"},
|
||||||
|
"listen_ctl": "192.168.0.1:8443",
|
||||||
|
"listen_gossip": "192.168.10.1:9443",
|
||||||
|
"listen_http": "192.168.20.1:8080",
|
||||||
|
"builder_auth_token": "dead-beef",
|
||||||
|
"gateway_auth_token": "ea7-beef",
|
||||||
|
"ctl_secret": "bad-beef",
|
||||||
|
},
|
||||||
|
|
||||||
|
Commands: map[string]bool{
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true HAB_AUTH_TOKEN=dead-beef sudo -E /bin/bash -c 'hab install core/hab-sup/0.79.1'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true HAB_AUTH_TOKEN=dead-beef sudo -E /bin/bash -c 'systemctl enable hab-sup && systemctl start hab-sup'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true HAB_AUTH_TOKEN=dead-beef sudo -E /bin/bash -c 'mv /tmp/hab-sup.service /etc/systemd/system/hab-sup.service'": true,
|
||||||
|
},
|
||||||
|
|
||||||
|
Uploads: map[string]string{
|
||||||
|
"/tmp/hab-sup.service": linuxCustomSystemdUnitFileContents,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
o := new(terraform.MockUIOutput)
|
||||||
|
c := new(communicator.MockCommunicator)
|
||||||
|
|
||||||
|
for k, tc := range cases {
|
||||||
|
c.Commands = tc.Commands
|
||||||
|
c.Uploads = tc.Uploads
|
||||||
|
|
||||||
|
p, err := decodeConfig(
|
||||||
|
schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.linuxStartHabitat(o, c)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test %q failed: %v", k, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinuxProvisioner_linuxUploadRingKey(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
Config map[string]interface{}
|
||||||
|
Commands map[string]bool
|
||||||
|
}{
|
||||||
|
"Upload ring key": {
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"version": "0.79.1",
|
||||||
|
"auto_update": true,
|
||||||
|
"use_sudo": true,
|
||||||
|
"service_name": "hab-sup",
|
||||||
|
"peers": []interface{}{"1.2.3.4"},
|
||||||
|
"ring_key": "test-ring",
|
||||||
|
"ring_key_content": "dead-beef",
|
||||||
|
},
|
||||||
|
|
||||||
|
Commands: map[string]bool{
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'echo -e \"dead-beef\" | hab ring key import'": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
o := new(terraform.MockUIOutput)
|
||||||
|
c := new(communicator.MockCommunicator)
|
||||||
|
|
||||||
|
for k, tc := range cases {
|
||||||
|
c.Commands = tc.Commands
|
||||||
|
|
||||||
|
p, err := decodeConfig(
|
||||||
|
schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.linuxUploadRingKey(o, c)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test %q failed: %v", k, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinuxProvisioner_linuxStartHabitatService(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
Config map[string]interface{}
|
||||||
|
Commands map[string]bool
|
||||||
|
Uploads map[string]string
|
||||||
|
}{
|
||||||
|
"Start Habitat service with sudo": {
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"version": "0.79.1",
|
||||||
|
"auto_update": false,
|
||||||
|
"use_sudo": true,
|
||||||
|
"service_name": "hab-sup",
|
||||||
|
"peers": []interface{}{"1.2.3.4"},
|
||||||
|
"ring_key": "test-ring",
|
||||||
|
"ring_key_content": "dead-beef",
|
||||||
|
"service": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "core/foo",
|
||||||
|
"topology": "standalone",
|
||||||
|
"strategy": "none",
|
||||||
|
"channel": "stable",
|
||||||
|
"user_toml": "[config]\nlisten = 0.0.0.0:8080",
|
||||||
|
"bind": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"alias": "backend",
|
||||||
|
"service": "bar",
|
||||||
|
"group": "default",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "core/bar",
|
||||||
|
"topology": "standalone",
|
||||||
|
"strategy": "rolling",
|
||||||
|
"channel": "staging",
|
||||||
|
"user_toml": "[config]\nlisten = 0.0.0.0:443",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Commands: map[string]bool{
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'hab pkg install core/foo --channel stable'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'mkdir -p /hab/user/foo/config'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'mv /tmp/user-a5b83ec1b302d109f41852ae17379f75c36dff9bc598aae76b6f7c9cd425fd76.toml /hab/user/foo/config/user.toml'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'hab svc load core/foo --topology standalone --strategy none --channel stable --bind backend:bar.default'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'hab pkg install core/bar --channel staging'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'mkdir -p /hab/user/bar/config'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'mv /tmp/user-6466ae3283ae1bd4737b00367bc676c6465b25682169ea5f7da222f3f078a5bf.toml /hab/user/bar/config/user.toml'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'hab svc load core/bar --topology standalone --strategy rolling --channel staging'": true,
|
||||||
|
},
|
||||||
|
|
||||||
|
Uploads: map[string]string{
|
||||||
|
"/tmp/user-a5b83ec1b302d109f41852ae17379f75c36dff9bc598aae76b6f7c9cd425fd76.toml": "[config]\nlisten = 0.0.0.0:8080",
|
||||||
|
"/tmp/user-6466ae3283ae1bd4737b00367bc676c6465b25682169ea5f7da222f3f078a5bf.toml": "[config]\nlisten = 0.0.0.0:443",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
o := new(terraform.MockUIOutput)
|
||||||
|
c := new(communicator.MockCommunicator)
|
||||||
|
|
||||||
|
for k, tc := range cases {
|
||||||
|
c.Commands = tc.Commands
|
||||||
|
c.Uploads = tc.Uploads
|
||||||
|
|
||||||
|
p, err := decodeConfig(
|
||||||
|
schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs []error
|
||||||
|
for _, s := range p.Services {
|
||||||
|
err = p.linuxStartHabitatService(o, c, s)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
for _, e := range errs {
|
||||||
|
t.Logf("Test %q failed: %v", k, e)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,55 +1,38 @@
|
||||||
package habitat
|
package habitat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
|
||||||
|
|
||||||
version "github.com/hashicorp/go-version"
|
version "github.com/hashicorp/go-version"
|
||||||
"github.com/hashicorp/terraform/communicator"
|
"github.com/hashicorp/terraform/communicator"
|
||||||
"github.com/hashicorp/terraform/communicator/remote"
|
"github.com/hashicorp/terraform/communicator/remote"
|
||||||
|
"github.com/hashicorp/terraform/configs/hcl2shim"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform/helper/validation"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
linereader "github.com/mitchellh/go-linereader"
|
"github.com/mitchellh/go-linereader"
|
||||||
)
|
)
|
||||||
|
|
||||||
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 .BuilderAuthToken -}}
|
|
||||||
Environment="HAB_AUTH_TOKEN={{ .BuilderAuthToken }}"
|
|
||||||
{{ end -}}
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=default.target
|
|
||||||
`
|
|
||||||
|
|
||||||
var serviceTypes = map[string]bool{"unmanaged": true, "systemd": true}
|
|
||||||
var updateStrategies = map[string]bool{"at-once": true, "rolling": true, "none": true}
|
|
||||||
var topologies = map[string]bool{"leader": true, "standalone": true}
|
|
||||||
|
|
||||||
type provisionFn func(terraform.UIOutput, communicator.Communicator) error
|
|
||||||
|
|
||||||
type provisioner struct {
|
type provisioner struct {
|
||||||
Version string
|
Version string
|
||||||
|
AutoUpdate bool
|
||||||
|
HttpDisable bool
|
||||||
Services []Service
|
Services []Service
|
||||||
PermanentPeer bool
|
PermanentPeer bool
|
||||||
|
ListenCtl string
|
||||||
ListenGossip string
|
ListenGossip string
|
||||||
ListenHTTP string
|
ListenHTTP string
|
||||||
Peer string
|
Peer string
|
||||||
|
Peers []string
|
||||||
RingKey string
|
RingKey string
|
||||||
RingKeyContent string
|
RingKeyContent string
|
||||||
|
CtlSecret string
|
||||||
SkipInstall bool
|
SkipInstall bool
|
||||||
UseSudo bool
|
UseSudo bool
|
||||||
ServiceType string
|
ServiceType string
|
||||||
|
@ -57,13 +40,24 @@ type provisioner struct {
|
||||||
URL string
|
URL string
|
||||||
Channel string
|
Channel string
|
||||||
Events string
|
Events string
|
||||||
OverrideName string
|
|
||||||
Organization string
|
Organization string
|
||||||
|
GatewayAuthToken string
|
||||||
BuilderAuthToken string
|
BuilderAuthToken string
|
||||||
SupOptions string
|
SupOptions string
|
||||||
AcceptLicense bool
|
AcceptLicense bool
|
||||||
|
|
||||||
|
installHabitat provisionFn
|
||||||
|
startHabitat provisionFn
|
||||||
|
uploadRingKey provisionFn
|
||||||
|
uploadCtlSecret provisionFn
|
||||||
|
startHabitatService provisionServiceFn
|
||||||
|
|
||||||
|
osType string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type provisionFn func(terraform.UIOutput, communicator.Communicator) error
|
||||||
|
type provisionServiceFn func(terraform.UIOutput, communicator.Communicator, Service) error
|
||||||
|
|
||||||
func Provisioner() terraform.ResourceProvisioner {
|
func Provisioner() terraform.ResourceProvisioner {
|
||||||
return &schema.Provisioner{
|
return &schema.Provisioner{
|
||||||
Schema: map[string]*schema.Schema{
|
Schema: map[string]*schema.Schema{
|
||||||
|
@ -71,14 +65,30 @@ func Provisioner() terraform.ResourceProvisioner {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
|
"auto_update": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: false,
|
||||||
|
},
|
||||||
|
"http_disable": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: false,
|
||||||
|
},
|
||||||
"peer": &schema.Schema{
|
"peer": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
"service_type": &schema.Schema{
|
"peers": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeList,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Default: "systemd",
|
},
|
||||||
|
"service_type": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Default: "systemd",
|
||||||
|
ValidateFunc: validation.StringInSlice([]string{"systemd", "unmanaged"}, false),
|
||||||
},
|
},
|
||||||
"service_name": &schema.Schema{
|
"service_name": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
|
@ -99,6 +109,10 @@ func Provisioner() terraform.ResourceProvisioner {
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Default: false,
|
Default: false,
|
||||||
},
|
},
|
||||||
|
"listen_ctl": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
"listen_gossip": &schema.Schema{
|
"listen_gossip": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
@ -115,9 +129,25 @@ func Provisioner() terraform.ResourceProvisioner {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
|
"ctl_secret": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
"url": &schema.Schema{
|
"url": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) {
|
||||||
|
u, err := url.Parse(val.(string))
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("invalid URL specified for %q: %v", key, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme == "" {
|
||||||
|
errs = append(errs, fmt.Errorf("invalid URL specified for %q (scheme must be specified)", key))
|
||||||
|
}
|
||||||
|
|
||||||
|
return warns, errs
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"channel": &schema.Schema{
|
"channel": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
|
@ -127,11 +157,11 @@ func Provisioner() terraform.ResourceProvisioner {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
"override_name": &schema.Schema{
|
"organization": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
"organization": &schema.Schema{
|
"gateway_auth_token": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
|
@ -173,19 +203,20 @@ func Provisioner() terraform.ResourceProvisioner {
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
"topology": &schema.Schema{
|
"topology": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
ValidateFunc: validation.StringInSlice([]string{"leader", "standalone"}, false),
|
||||||
},
|
},
|
||||||
"user_toml": &schema.Schema{
|
"user_toml": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
"strategy": &schema.Schema{
|
"strategy": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
ValidateFunc: validation.StringInSlice([]string{"none", "rolling", "at-once"}, false),
|
||||||
},
|
},
|
||||||
"channel": &schema.Schema{
|
"channel": &schema.Schema{
|
||||||
|
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
|
@ -196,6 +227,18 @@ func Provisioner() terraform.ResourceProvisioner {
|
||||||
"url": &schema.Schema{
|
"url": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) {
|
||||||
|
u, err := url.Parse(val.(string))
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("invalid URL specified for %q: %v", key, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme == "" {
|
||||||
|
errs = append(errs, fmt.Errorf("invalid URL specified for %q (scheme must be specified)", key))
|
||||||
|
}
|
||||||
|
|
||||||
|
return warns, errs
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"application": &schema.Schema{
|
"application": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
|
@ -205,10 +248,6 @@ func Provisioner() terraform.ResourceProvisioner {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
"override_name": &schema.Schema{
|
|
||||||
Type: schema.TypeString,
|
|
||||||
Optional: true,
|
|
||||||
},
|
|
||||||
"service_key": &schema.Schema{
|
"service_key": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
@ -233,6 +272,30 @@ func applyFn(ctx context.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Automatically determine the OS type
|
||||||
|
switch t := s.Ephemeral.ConnInfo["type"]; t {
|
||||||
|
case "ssh", "":
|
||||||
|
p.osType = "linux"
|
||||||
|
case "winrm":
|
||||||
|
p.osType = "windows"
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported connection type: %s", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch p.osType {
|
||||||
|
case "linux":
|
||||||
|
p.installHabitat = p.linuxInstallHabitat
|
||||||
|
p.uploadRingKey = p.linuxUploadRingKey
|
||||||
|
p.uploadCtlSecret = p.linuxUploadCtlSecret
|
||||||
|
p.startHabitat = p.linuxStartHabitat
|
||||||
|
p.startHabitatService = p.linuxStartHabitatService
|
||||||
|
case "windows":
|
||||||
|
return fmt.Errorf("windows is not supported yet for the habitat provisioner")
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported os type: %s", p.osType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a new communicator
|
||||||
comm, err := communicator.New(s)
|
comm, err := communicator.New(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -241,6 +304,7 @@ func applyFn(ctx context.Context) error {
|
||||||
retryCtx, cancel := context.WithTimeout(ctx, comm.Timeout())
|
retryCtx, cancel := context.WithTimeout(ctx, comm.Timeout())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
// Wait and retry until we establish the connection
|
||||||
err = communicator.Retry(retryCtx, func() error {
|
err = communicator.Retry(retryCtx, func() error {
|
||||||
return comm.Connect(o)
|
return comm.Connect(o)
|
||||||
})
|
})
|
||||||
|
@ -252,7 +316,7 @@ func applyFn(ctx context.Context) error {
|
||||||
|
|
||||||
if !p.SkipInstall {
|
if !p.SkipInstall {
|
||||||
o.Output("Installing habitat...")
|
o.Output("Installing habitat...")
|
||||||
if err := p.installHab(o, comm); err != nil {
|
if err := p.installHabitat(o, comm); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -264,15 +328,22 @@ func applyFn(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.CtlSecret != "" {
|
||||||
|
o.Output("Uploading ctl secret...")
|
||||||
|
if err := p.uploadCtlSecret(o, comm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
o.Output("Starting the habitat supervisor...")
|
o.Output("Starting the habitat supervisor...")
|
||||||
if err := p.startHab(o, comm); err != nil {
|
if err := p.startHabitat(o, comm); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.Services != nil {
|
if p.Services != nil {
|
||||||
for _, service := range p.Services {
|
for _, service := range p.Services {
|
||||||
o.Output("Starting service: " + service.Name)
|
o.Output("Starting service: " + service.Name)
|
||||||
if err := p.startHabService(o, comm, service); err != nil {
|
if err := p.startHabitatService(o, comm, service); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -282,17 +353,11 @@ func applyFn(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateFn(c *terraform.ResourceConfig) (ws []string, es []error) {
|
func validateFn(c *terraform.ResourceConfig) (ws []string, es []error) {
|
||||||
serviceType, ok := c.Get("service_type")
|
ringKeyContent, ok := c.Get("ring_key_content")
|
||||||
if ok {
|
if ok && ringKeyContent != "" && ringKeyContent != hcl2shim.UnknownVariableValue {
|
||||||
if !serviceTypes[serviceType.(string)] {
|
ringKey, ringOk := c.Get("ring_key")
|
||||||
es = append(es, errors.New(serviceType.(string)+" is not a valid service_type."))
|
if ringOk && ringKey == "" {
|
||||||
}
|
es = append(es, errors.New("if ring_key_content is specified, ring_key must be specified as well"))
|
||||||
}
|
|
||||||
|
|
||||||
builderURL, ok := c.Get("url")
|
|
||||||
if ok {
|
|
||||||
if _, err := url.ParseRequestURI(builderURL.(string)); err != nil {
|
|
||||||
es = append(es, errors.New(builderURL.(string)+" is not a valid URL."))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,30 +384,12 @@ func validateFn(c *terraform.ResourceConfig) (ws []string, es []error) {
|
||||||
// Validate service level configs
|
// Validate service level configs
|
||||||
services, ok := c.Get("service")
|
services, ok := c.Get("service")
|
||||||
if ok {
|
if ok {
|
||||||
for i, svc := range services.([]interface{}) {
|
data, dataOk := services.(string)
|
||||||
service, ok := svc.(map[string]interface{})
|
if dataOk {
|
||||||
if !ok {
|
es = append(es, fmt.Errorf("service '%v': must be a block", data))
|
||||||
es = append(es, fmt.Errorf("service %d: must be a block", i))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
strategy, ok := service["strategy"].(string)
|
|
||||||
if ok && !updateStrategies[strategy] {
|
|
||||||
es = append(es, errors.New(strategy+" is not a valid update strategy."))
|
|
||||||
}
|
|
||||||
|
|
||||||
topology, ok := service["topology"].(string)
|
|
||||||
if ok && !topologies[topology] {
|
|
||||||
es = append(es, errors.New(topology+" is not a valid topology"))
|
|
||||||
}
|
|
||||||
|
|
||||||
builderURL, ok := service["url"].(string)
|
|
||||||
if ok {
|
|
||||||
if _, err := url.ParseRequestURI(builderURL); err != nil {
|
|
||||||
es = append(es, errors.New(builderURL+" is not a valid URL."))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ws, es
|
return ws, es
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -358,20 +405,23 @@ type Service struct {
|
||||||
UserTOML string
|
UserTOML string
|
||||||
AppName string
|
AppName string
|
||||||
Environment string
|
Environment string
|
||||||
OverrideName string
|
|
||||||
ServiceGroupKey string
|
ServiceGroupKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) getPackageName(fullName string) string {
|
||||||
|
return strings.Split(fullName, "/")[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) getServiceNameChecksum() string {
|
||||||
|
return fmt.Sprintf("%x", sha256.Sum256([]byte(s.Name)))
|
||||||
|
}
|
||||||
|
|
||||||
type Bind struct {
|
type Bind struct {
|
||||||
Alias string
|
Alias string
|
||||||
Service string
|
Service string
|
||||||
Group string
|
Group string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) getPackageName(fullName string) string {
|
|
||||||
return strings.Split(fullName, "/")[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bind) toBindString() string {
|
func (b *Bind) toBindString() string {
|
||||||
return fmt.Sprintf("%s:%s.%s", b.Alias, b.Service, b.Group)
|
return fmt.Sprintf("%s:%s.%s", b.Alias, b.Service, b.Group)
|
||||||
}
|
}
|
||||||
|
@ -379,7 +429,10 @@ func (b *Bind) toBindString() string {
|
||||||
func decodeConfig(d *schema.ResourceData) (*provisioner, error) {
|
func decodeConfig(d *schema.ResourceData) (*provisioner, error) {
|
||||||
p := &provisioner{
|
p := &provisioner{
|
||||||
Version: d.Get("version").(string),
|
Version: d.Get("version").(string),
|
||||||
|
AutoUpdate: d.Get("auto_update").(bool),
|
||||||
|
HttpDisable: d.Get("http_disable").(bool),
|
||||||
Peer: d.Get("peer").(string),
|
Peer: d.Get("peer").(string),
|
||||||
|
Peers: getPeers(d.Get("peers").([]interface{})),
|
||||||
Services: getServices(d.Get("service").(*schema.Set).List()),
|
Services: getServices(d.Get("service").(*schema.Set).List()),
|
||||||
UseSudo: d.Get("use_sudo").(bool),
|
UseSudo: d.Get("use_sudo").(bool),
|
||||||
AcceptLicense: d.Get("accept_license").(bool),
|
AcceptLicense: d.Get("accept_license").(bool),
|
||||||
|
@ -387,20 +440,30 @@ func decodeConfig(d *schema.ResourceData) (*provisioner, error) {
|
||||||
ServiceName: d.Get("service_name").(string),
|
ServiceName: d.Get("service_name").(string),
|
||||||
RingKey: d.Get("ring_key").(string),
|
RingKey: d.Get("ring_key").(string),
|
||||||
RingKeyContent: d.Get("ring_key_content").(string),
|
RingKeyContent: d.Get("ring_key_content").(string),
|
||||||
|
CtlSecret: d.Get("ctl_secret").(string),
|
||||||
PermanentPeer: d.Get("permanent_peer").(bool),
|
PermanentPeer: d.Get("permanent_peer").(bool),
|
||||||
|
ListenCtl: d.Get("listen_ctl").(string),
|
||||||
ListenGossip: d.Get("listen_gossip").(string),
|
ListenGossip: d.Get("listen_gossip").(string),
|
||||||
ListenHTTP: d.Get("listen_http").(string),
|
ListenHTTP: d.Get("listen_http").(string),
|
||||||
URL: d.Get("url").(string),
|
URL: d.Get("url").(string),
|
||||||
Channel: d.Get("channel").(string),
|
Channel: d.Get("channel").(string),
|
||||||
Events: d.Get("events").(string),
|
Events: d.Get("events").(string),
|
||||||
OverrideName: d.Get("override_name").(string),
|
|
||||||
Organization: d.Get("organization").(string),
|
Organization: d.Get("organization").(string),
|
||||||
BuilderAuthToken: d.Get("builder_auth_token").(string),
|
BuilderAuthToken: d.Get("builder_auth_token").(string),
|
||||||
|
GatewayAuthToken: d.Get("gateway_auth_token").(string),
|
||||||
}
|
}
|
||||||
|
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getPeers(v []interface{}) []string {
|
||||||
|
peers := make([]string, 0, len(v))
|
||||||
|
for _, rawPeerData := range v {
|
||||||
|
peers = append(peers, rawPeerData.(string))
|
||||||
|
}
|
||||||
|
return peers
|
||||||
|
}
|
||||||
|
|
||||||
func getServices(v []interface{}) []Service {
|
func getServices(v []interface{}) []Service {
|
||||||
services := make([]Service, 0, len(v))
|
services := make([]Service, 0, len(v))
|
||||||
for _, rawServiceData := range v {
|
for _, rawServiceData := range v {
|
||||||
|
@ -413,7 +476,6 @@ func getServices(v []interface{}) []Service {
|
||||||
url := (serviceData["url"].(string))
|
url := (serviceData["url"].(string))
|
||||||
app := (serviceData["application"].(string))
|
app := (serviceData["application"].(string))
|
||||||
env := (serviceData["environment"].(string))
|
env := (serviceData["environment"].(string))
|
||||||
override := (serviceData["override_name"].(string))
|
|
||||||
userToml := (serviceData["user_toml"].(string))
|
userToml := (serviceData["user_toml"].(string))
|
||||||
serviceGroupKey := (serviceData["service_key"].(string))
|
serviceGroupKey := (serviceData["service_key"].(string))
|
||||||
var bindStrings []string
|
var bindStrings []string
|
||||||
|
@ -438,7 +500,6 @@ func getServices(v []interface{}) []Service {
|
||||||
Binds: binds,
|
Binds: binds,
|
||||||
AppName: app,
|
AppName: app,
|
||||||
Environment: env,
|
Environment: env,
|
||||||
OverrideName: override,
|
|
||||||
ServiceGroupKey: serviceGroupKey,
|
ServiceGroupKey: serviceGroupKey,
|
||||||
}
|
}
|
||||||
services = append(services, service)
|
services = append(services, service)
|
||||||
|
@ -463,331 +524,6 @@ func getBinds(v []interface{}) []Bind {
|
||||||
return binds
|
return binds
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *provisioner) uploadRingKey(o terraform.UIOutput, comm communicator.Communicator) error {
|
|
||||||
command := fmt.Sprintf("echo '%s' | hab ring key import", p.RingKeyContent)
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("echo '%s' | sudo hab ring key import", p.RingKeyContent)
|
|
||||||
}
|
|
||||||
return p.runCommand(o, comm, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *provisioner) installHab(o terraform.UIOutput, comm communicator.Communicator) error {
|
|
||||||
// Build the install command
|
|
||||||
command := fmt.Sprintf("curl -L0 %s > install.sh", installURL)
|
|
||||||
if err := p.runCommand(o, comm, command); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the install script
|
|
||||||
if p.Version == "" {
|
|
||||||
command = fmt.Sprintf("env HAB_NONINTERACTIVE=true bash ./install.sh ")
|
|
||||||
} else {
|
|
||||||
command = fmt.Sprintf("env HAB_NONINTERACTIVE=true bash ./install.sh -v %s", p.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("sudo %s", command)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.runCommand(o, comm, command); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accept the license
|
|
||||||
if p.AcceptLicense {
|
|
||||||
command = fmt.Sprintf("export HAB_LICENSE=accept; hab -V")
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("sudo HAB_LICENSE=accept hab -V")
|
|
||||||
}
|
|
||||||
if err := p.runCommand(o, comm, command); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.createHabUser(o, comm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.runCommand(o, comm, fmt.Sprintf("rm -f install.sh"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *provisioner) startHab(o terraform.UIOutput, comm communicator.Communicator) error {
|
|
||||||
// Install the supervisor first
|
|
||||||
var command string
|
|
||||||
if p.Version == "" {
|
|
||||||
command += fmt.Sprintf("hab install core/hab-sup")
|
|
||||||
} else {
|
|
||||||
command += fmt.Sprintf("hab install core/hab-sup/%s", p.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("sudo -E %s", command)
|
|
||||||
}
|
|
||||||
|
|
||||||
command = fmt.Sprintf("env HAB_NONINTERACTIVE=true %s", command)
|
|
||||||
|
|
||||||
if err := p.runCommand(o, comm, command); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build up sup options
|
|
||||||
options := ""
|
|
||||||
if p.PermanentPeer {
|
|
||||||
options += " -I"
|
|
||||||
}
|
|
||||||
|
|
||||||
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(" --peer %s", p.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.OverrideName != "" {
|
|
||||||
options += fmt.Sprintf(" --override-name %s", p.OverrideName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.Organization != "" {
|
|
||||||
options += fmt.Sprintf(" --org %s", p.Organization)
|
|
||||||
}
|
|
||||||
|
|
||||||
p.SupOptions = options
|
|
||||||
|
|
||||||
switch p.ServiceType {
|
|
||||||
case "unmanaged":
|
|
||||||
return p.startHabUnmanaged(o, comm, options)
|
|
||||||
case "systemd":
|
|
||||||
return p.startHabSystemd(o, comm, options)
|
|
||||||
default:
|
|
||||||
return errors.New("Unsupported service type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *provisioner) startHabUnmanaged(o terraform.UIOutput, comm communicator.Communicator, options string) error {
|
|
||||||
// Create the sup directory for the log file
|
|
||||||
var command string
|
|
||||||
var token string
|
|
||||||
if p.UseSudo {
|
|
||||||
command = "sudo mkdir -p /hab/sup/default && sudo chmod o+w /hab/sup/default"
|
|
||||||
} else {
|
|
||||||
command = "mkdir -p /hab/sup/default && chmod o+w /hab/sup/default"
|
|
||||||
}
|
|
||||||
if err := p.runCommand(o, comm, command); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.BuilderAuthToken != "" {
|
|
||||||
token = fmt.Sprintf("env HAB_AUTH_TOKEN=%s", p.BuilderAuthToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("(%s setsid sudo -E hab sup run %s > /hab/sup/default/sup.log 2>&1 &) ; sleep 1", token, options)
|
|
||||||
} else {
|
|
||||||
command = fmt.Sprintf("(%s setsid hab sup run %s > /hab/sup/default/sup.log 2>&1 <&1 &) ; sleep 1", token, options)
|
|
||||||
}
|
|
||||||
return p.runCommand(o, comm, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *provisioner) startHabSystemd(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 template: %s", "hab-supervisor.service", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var command string
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("sudo echo '%s' | sudo tee /etc/systemd/system/%s.service > /dev/null", &buf, p.ServiceName)
|
|
||||||
} else {
|
|
||||||
command = fmt.Sprintf("echo '%s' | tee /etc/systemd/system/%s.service > /dev/null", &buf, p.ServiceName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.runCommand(o, comm, command); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("sudo systemctl enable hab-supervisor && sudo systemctl start hab-supervisor")
|
|
||||||
} else {
|
|
||||||
command = fmt.Sprintf("systemctl enable hab-supervisor && systemctl start hab-supervisor")
|
|
||||||
}
|
|
||||||
return p.runCommand(o, comm, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *provisioner) createHabUser(o terraform.UIOutput, comm communicator.Communicator) error {
|
|
||||||
addUser := false
|
|
||||||
// Install busybox to get us the user tools we need
|
|
||||||
command := fmt.Sprintf("env HAB_NONINTERACTIVE=true hab install core/busybox")
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("sudo %s", command)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.runCommand(o, comm, command); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for existing hab user
|
|
||||||
command = fmt.Sprintf("hab pkg exec core/busybox id hab")
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("sudo %s", command)
|
|
||||||
}
|
|
||||||
if err := p.runCommand(o, comm, command); err != nil {
|
|
||||||
o.Output("No existing hab user detected, creating...")
|
|
||||||
addUser = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if addUser {
|
|
||||||
command = fmt.Sprintf("hab pkg exec core/busybox adduser -D -g \"\" hab")
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("sudo %s", command)
|
|
||||||
}
|
|
||||||
return p.runCommand(o, comm, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) installHabPackage(o terraform.UIOutput, comm communicator.Communicator, service Service) error {
|
|
||||||
var command string
|
|
||||||
options := ""
|
|
||||||
if service.Channel != "" {
|
|
||||||
options += fmt.Sprintf(" --channel %s", service.Channel)
|
|
||||||
}
|
|
||||||
|
|
||||||
if service.URL != "" {
|
|
||||||
options += fmt.Sprintf(" --url %s", service.URL)
|
|
||||||
}
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("env HAB_NONINTERACTIVE=true sudo -E hab pkg install %s %s", service.Name, options)
|
|
||||||
} else {
|
|
||||||
command = fmt.Sprintf("env HAB_NONINTERACTIVE=true hab pkg install %s %s", service.Name, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.BuilderAuthToken != "" {
|
|
||||||
command = fmt.Sprintf("env HAB_AUTH_TOKEN=%s %s", p.BuilderAuthToken, command)
|
|
||||||
}
|
|
||||||
return p.runCommand(o, comm, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *provisioner) startHabService(o terraform.UIOutput, comm communicator.Communicator, service Service) error {
|
|
||||||
var command string
|
|
||||||
if err := p.installHabPackage(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 != "" {
|
|
||||||
p.uploadServiceGroupKey(o, comm, service.ServiceGroupKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
options := ""
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
command = fmt.Sprintf("hab svc load %s %s", service.Name, options)
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("sudo -E %s", command)
|
|
||||||
}
|
|
||||||
if p.BuilderAuthToken != "" {
|
|
||||||
command = fmt.Sprintf("env HAB_AUTH_TOKEN=%s %s", p.BuilderAuthToken, command)
|
|
||||||
}
|
|
||||||
return p.runCommand(o, comm, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
command := fmt.Sprintf("sudo mv %s %s", tempPath, destPath)
|
|
||||||
return p.runCommand(o, comm, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
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/svc/%s", service.getPackageName(service.Name))
|
|
||||||
command := fmt.Sprintf("mkdir -p %s", destDir)
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("sudo %s", command)
|
|
||||||
}
|
|
||||||
if err := p.runCommand(o, comm, command); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
userToml := strings.NewReader(service.UserTOML)
|
|
||||||
|
|
||||||
if p.UseSudo {
|
|
||||||
if err := comm.Upload("/tmp/user.toml", userToml); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
command = fmt.Sprintf("sudo mv /tmp/user.toml %s", destDir)
|
|
||||||
return p.runCommand(o, comm, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
return comm.Upload(path.Join(destDir, "user.toml"), userToml)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *provisioner) copyOutput(o terraform.UIOutput, r io.Reader) {
|
func (p *provisioner) copyOutput(o terraform.UIOutput, r io.Reader) {
|
||||||
lr := linereader.New(r)
|
lr := linereader.New(r)
|
||||||
for line := range lr.Ch {
|
for line := range lr.Ch {
|
||||||
|
@ -811,7 +547,7 @@ func (p *provisioner) runCommand(o terraform.UIOutput, comm communicator.Communi
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := comm.Start(cmd); err != nil {
|
if err := comm.Start(cmd); err != nil {
|
||||||
return fmt.Errorf("Error executing command %q: %v", cmd.Command, err)
|
return fmt.Errorf("error executing command %q: %v", cmd.Command, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cmd.Wait(); err != nil {
|
if err := cmd.Wait(); err != nil {
|
||||||
|
@ -830,7 +566,7 @@ func getBindFromString(bind string) (Bind, error) {
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
if len(t) != 3 {
|
if len(t) != 3 {
|
||||||
return Bind{}, errors.New("Invalid bind specification: " + bind)
|
return Bind{}, errors.New("invalid bind specification: " + bind)
|
||||||
}
|
}
|
||||||
return Bind{Alias: t[0], Service: t[1], Group: t[2]}, nil
|
return Bind{Alias: t[0], Service: t[1], Group: t[2]}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ func TestProvisioner(t *testing.T) {
|
||||||
|
|
||||||
func TestResourceProvisioner_Validate_good(t *testing.T) {
|
func TestResourceProvisioner_Validate_good(t *testing.T) {
|
||||||
c := testConfig(t, map[string]interface{}{
|
c := testConfig(t, map[string]interface{}{
|
||||||
"peer": "1.2.3.4",
|
"peers": []interface{}{"1.2.3.4"},
|
||||||
"version": "0.32.0",
|
"version": "0.32.0",
|
||||||
"service_type": "systemd",
|
"service_type": "systemd",
|
||||||
"accept_license": false,
|
"accept_license": false,
|
||||||
|
@ -37,15 +37,16 @@ func TestResourceProvisioner_Validate_good(t *testing.T) {
|
||||||
func TestResourceProvisioner_Validate_bad(t *testing.T) {
|
func TestResourceProvisioner_Validate_bad(t *testing.T) {
|
||||||
c := testConfig(t, map[string]interface{}{
|
c := testConfig(t, map[string]interface{}{
|
||||||
"service_type": "invalidtype",
|
"service_type": "invalidtype",
|
||||||
|
"url": "badurl",
|
||||||
})
|
})
|
||||||
|
|
||||||
warn, errs := Provisioner().Validate(c)
|
warn, errs := Provisioner().Validate(c)
|
||||||
if len(warn) > 0 {
|
if len(warn) > 0 {
|
||||||
t.Fatalf("Warnings: %v", warn)
|
t.Fatalf("Warnings: %v", warn)
|
||||||
}
|
}
|
||||||
//Two errors, one for service_type, other for missing required accept_license argument
|
// 3 errors, bad service_type, bad url, missing accept_license
|
||||||
if len(errs) != 2 {
|
if len(errs) != 3 {
|
||||||
t.Fatalf("Should have one errors, got %d", len(errs))
|
t.Fatalf("Should have three errors, got %d", len(errs))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +68,21 @@ func TestResourceProvisioner_Validate_bad_service_config(t *testing.T) {
|
||||||
t.Fatalf("Warnings: %v", warn)
|
t.Fatalf("Warnings: %v", warn)
|
||||||
}
|
}
|
||||||
if len(errs) != 3 {
|
if len(errs) != 3 {
|
||||||
t.Fatalf("Should have three errors")
|
t.Fatalf("Should have three errors, got %d", len(errs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResourceProvisioner_Validate_bad_service_definition(t *testing.T) {
|
||||||
|
c := testConfig(t, map[string]interface{}{
|
||||||
|
"service": "core/vault",
|
||||||
|
})
|
||||||
|
|
||||||
|
warn, errs := Provisioner().Validate(c)
|
||||||
|
if len(warn) > 0 {
|
||||||
|
t.Fatalf("Warnings: %v", warn)
|
||||||
|
}
|
||||||
|
if len(errs) != 3 {
|
||||||
|
t.Fatalf("Should have three errors, got %d", len(errs))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ resource "aws_instance" "redis" {
|
||||||
count = 3
|
count = 3
|
||||||
|
|
||||||
provisioner "habitat" {
|
provisioner "habitat" {
|
||||||
peer = "${aws_instance.redis.0.private_ip}"
|
peers = [aws_instance.redis[0].private_ip]
|
||||||
use_sudo = true
|
use_sudo = true
|
||||||
service_type = "systemd"
|
service_type = "systemd"
|
||||||
accept_license = true
|
accept_license = true
|
||||||
|
@ -40,7 +40,7 @@ resource "aws_instance" "redis" {
|
||||||
service {
|
service {
|
||||||
name = "core/redis"
|
name = "core/redis"
|
||||||
topology = "leader"
|
topology = "leader"
|
||||||
user_toml = "${file("conf/redis.toml")}"
|
user_toml = file("conf/redis.toml")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,20 +54,25 @@ There are 2 configuration levels, `supervisor` and `service`. Configuration pla
|
||||||
### Supervisor Arguments
|
### Supervisor Arguments
|
||||||
* `accept_license (bool)` - (Required) Set to true to accept [Habitat end user license agreement](https://www.chef.io/end-user-license-agreement/)
|
* `accept_license (bool)` - (Required) Set to true to accept [Habitat end user license agreement](https://www.chef.io/end-user-license-agreement/)
|
||||||
* `version (string)` - (Optional) The Habitat version to install on the remote machine. If not specified, the latest available version is used.
|
* `version (string)` - (Optional) The Habitat version to install on the remote machine. If not specified, the latest available version is used.
|
||||||
* `use_sudo (bool)` - (Optional) Use `sudo` when executing remote commands. Required when the user specified in the `connection` block is not `root`. (Defaults to `true`)
|
* `auto_update (bool)` - (Optional) If set to `true`, the supervisor will auto-update itself as soon as new releases are available on the specified `channel`.
|
||||||
|
* `http_disable (bool)` - (Optional) If set to `true`, disables the supervisor HTTP listener entirely.
|
||||||
|
* `peer (string)` - (Optional, deprecated) IP addresses or FQDN's for other Habitat supervisors to peer with, like: `--peer 1.2.3.4 --peer 5.6.7.8`. (Defaults to none)
|
||||||
|
* `peers (array)` - (Optional) A list of IP or FQDN's of other supervisor instance(s) to peer with. (Defaults to none)
|
||||||
* `service_type (string)` - (Optional) Method used to run the Habitat supervisor. Valid options are `unmanaged` and `systemd`. (Defaults to `systemd`)
|
* `service_type (string)` - (Optional) Method used to run the Habitat supervisor. Valid options are `unmanaged` and `systemd`. (Defaults to `systemd`)
|
||||||
* `service_name (string)` - (Optional) The name of the Habitat supervisor service, if using an init system such as `systemd`. (Defaults to `hab-supervisor`)
|
* `service_name (string)` - (Optional) The name of the Habitat supervisor service, if using an init system such as `systemd`. (Defaults to `hab-supervisor`)
|
||||||
* `peer (string)` - (Optional) IP or FQDN of a supervisor instance to peer with. (Defaults to none)
|
* `use_sudo (bool)` - (Optional) Use `sudo` when executing remote commands. Required when the user specified in the `connection` block is not `root`. (Defaults to `true`)
|
||||||
* `permanent_peer (bool)` - (Optional) Marks this supervisor as a permanent peer. (Defaults to false)
|
* `permanent_peer (bool)` - (Optional) Marks this supervisor as a permanent peer. (Defaults to false)
|
||||||
|
* `listen_ctl (string)` - (Optional) The listen address for the countrol gateway system (Defaults to 127.0.0.1:9632)
|
||||||
* `listen_gossip (string)` - (Optional) The listen address for the gossip system (Defaults to 0.0.0.0:9638)
|
* `listen_gossip (string)` - (Optional) The listen address for the gossip system (Defaults to 0.0.0.0:9638)
|
||||||
* `listen_http (string)` - (Optional) The listen address for the HTTP gateway (Defaults to 0.0.0.0:9631)
|
* `listen_http (string)` - (Optional) The listen address for the HTTP gateway (Defaults to 0.0.0.0:9631)
|
||||||
* `ring_key (string)` - (Optional) The name of the ring key for encrypting gossip ring communication (Defaults to no encryption)
|
* `ring_key (string)` - (Optional) The name of the ring key for encrypting gossip ring communication (Defaults to no encryption)
|
||||||
* `ring_key_content (string)` - (Optional) The key content. Only needed if using ring encryption and want the provisioner to take care of uploading and importing it. Easiest to source from a file (eg `ring_key_content = "${file("conf/foo-123456789.sym.key")}"`) (Defaults to none)
|
* `ring_key_content (string)` - (Optional) The key content. Only needed if using ring encryption and want the provisioner to take care of uploading and importing it. Easiest to source from a file (eg `ring_key_content = "${file("conf/foo-123456789.sym.key")}"`) (Defaults to none)
|
||||||
|
* `ctl_secret (string)` - (Optional) Specify a secret to use (from `hab sup secret generate`) for control gateway communication between hab client(s) and the supervisor. (Defaults to none)
|
||||||
* `url (string)` - (Optional) The URL of a Builder service to download packages and receive updates from. (Defaults to https://bldr.habitat.sh)
|
* `url (string)` - (Optional) The URL of a Builder service to download packages and receive updates from. (Defaults to https://bldr.habitat.sh)
|
||||||
* `channel (string)` - (Optional) The release channel in the Builder service to use. (Defaults to `stable`)
|
* `channel (string)` - (Optional) The release channel in the Builder service to use. (Defaults to `stable`)
|
||||||
* `events (string)` - (Optional) Name of the service group running a Habitat EventSrv to forward Supervisor and service event data to. (Defaults to none)
|
* `events (string)` - (Optional) Name of the service group running a Habitat EventSrv to forward Supervisor and service event data to. (Defaults to none)
|
||||||
* `override_name (string)` - (Optional) The name of the Supervisor (Defaults to `default`)
|
|
||||||
* `organization (string)` - (Optional) The organization that the Supervisor and it's subsequent services are part of. (Defaults to `default`)
|
* `organization (string)` - (Optional) The organization that the Supervisor and it's subsequent services are part of. (Defaults to `default`)
|
||||||
|
* `gateway_auth_token (string)` - (Optional) The http gateway authorization token (Defaults to none)
|
||||||
* `builder_auth_token (string)` - (Optional) The builder authorization token when using a private origin. (Defaults to none)
|
* `builder_auth_token (string)` - (Optional) The builder authorization token when using a private origin. (Defaults to none)
|
||||||
|
|
||||||
### Service Arguments
|
### Service Arguments
|
||||||
|
@ -84,11 +89,10 @@ bind {
|
||||||
```
|
```
|
||||||
* `topology (string)` - (Optional) Topology to start service in. Possible values `standalone` or `leader`. (Defaults to `standalone`)
|
* `topology (string)` - (Optional) Topology to start service in. Possible values `standalone` or `leader`. (Defaults to `standalone`)
|
||||||
* `strategy (string)` - (Optional) Update strategy to use. Possible values `at-once`, `rolling` or `none`. (Defaults to `none`)
|
* `strategy (string)` - (Optional) Update strategy to use. Possible values `at-once`, `rolling` or `none`. (Defaults to `none`)
|
||||||
* `user_toml (string)` - (Optional) TOML formatted user configuration for the service. Easiest to source from a file (eg `user_toml = "${file("conf/redis.toml")}")`. (Defaults to none)
|
* `user_toml (string)` - (Optional) TOML formatted user configuration for the service. Easiest to source from a file (eg `user_toml = "${file("conf/redis.toml")}"`). (Defaults to none)
|
||||||
* `channel (string)` - (Optional) The release channel in the Builder service to use. (Defaults to `stable`)
|
* `channel (string)` - (Optional) The release channel in the Builder service to use. (Defaults to `stable`)
|
||||||
* `group (string)` - (Optional) The service group to join. (Defaults to `default`)
|
* `group (string)` - (Optional) The service group to join. (Defaults to `default`)
|
||||||
* `url (string)` - (Optional) The URL of a Builder service to download packages and receive updates from. (Defaults to https://bldr.habitat.sh)
|
* `url (string)` - (Optional) The URL of a Builder service to download packages and receive updates from. (Defaults to https://bldr.habitat.sh)
|
||||||
* `application (string)` - (Optional) The application name. (Defaults to none)
|
* `application (string)` - (Optional) The application name. (Defaults to none)
|
||||||
* `environment (string)` - (Optional) The environment name. (Defaults to none)
|
* `environment (string)` - (Optional) The environment name. (Defaults to none)
|
||||||
* `override_name (string)` - (Optional) The name for the state directory if there is more than one Supervisor running. (Defaults to `default`)
|
* `service_key (string)` - (Optional) The key content of a service private key, if using service group encryption. Easiest to source from a file (eg `service_key = "${file("conf/redis.default@org-123456789.box.key")}"`) (Defaults to none)
|
||||||
* `service_key (string)` - (Optional) The key content of a service private key, if using service group encryption. Easiest to source from a file (eg `service_key = "${file("conf/redis.default@org-123456789.box.key")}"`) (Defaults to none)
|
|
||||||
|
|
Loading…
Reference in New Issue