diff --git a/builtin/provisioners/habitat/resource_provisioner.go b/builtin/provisioners/habitat/resource_provisioner.go index 2ce35d250..fcb8ddb18 100644 --- a/builtin/provisioners/habitat/resource_provisioner.go +++ b/builtin/provisioners/habitat/resource_provisioner.go @@ -11,6 +11,7 @@ import ( "strings" "text/template" + version "github.com/hashicorp/go-version" "github.com/hashicorp/terraform/communicator" "github.com/hashicorp/terraform/communicator/remote" "github.com/hashicorp/terraform/helper/schema" @@ -60,6 +61,7 @@ type provisioner struct { Organization string BuilderAuthToken string SupOptions string + AcceptLicense bool } func Provisioner() terraform.ResourceProvisioner { @@ -88,6 +90,10 @@ func Provisioner() terraform.ResourceProvisioner { Optional: true, Default: true, }, + "accept_license": &schema.Schema{ + Type: schema.TypeBool, + Required: true, + }, "permanent_peer": &schema.Schema{ Type: schema.TypeBool, Optional: true, @@ -290,6 +296,26 @@ func validateFn(c *terraform.ResourceConfig) (ws []string, es []error) { } } + v, ok := c.Get("version") + if ok && v != nil && strings.TrimSpace(v.(string)) != "" { + if _, err := version.NewVersion(v.(string)); err != nil { + es = append(es, errors.New(v.(string)+" is not a valid version.")) + } + } + + acceptLicense, ok := c.Get("accept_license") + if ok && !acceptLicense.(bool) { + if v != nil && strings.TrimSpace(v.(string)) != "" { + versionOld, _ := version.NewVersion("0.79.0") + versionRequired, _ := version.NewVersion(v.(string)) + if versionRequired.GreaterThan(versionOld) { + es = append(es, errors.New("Habitat end user license agreement needs to be accepted, set the accept_license argument to true to accept")) + } + } else { // blank means latest version + es = append(es, errors.New("Habitat end user license agreement needs to be accepted, set the accept_license argument to true to accept")) + } + } + // Validate service level configs services, ok := c.Get("service") if ok { @@ -299,7 +325,6 @@ func validateFn(c *terraform.ResourceConfig) (ws []string, es []error) { 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.")) @@ -357,6 +382,7 @@ func decodeConfig(d *schema.ResourceData) (*provisioner, error) { Peer: d.Get("peer").(string), Services: getServices(d.Get("service").(*schema.Set).List()), UseSudo: d.Get("use_sudo").(bool), + AcceptLicense: d.Get("accept_license").(bool), ServiceType: d.Get("service_type").(string), ServiceName: d.Get("service_name").(string), RingKey: d.Get("ring_key").(string), @@ -467,6 +493,17 @@ func (p *provisioner) installHab(o terraform.UIOutput, comm communicator.Communi 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 } @@ -608,6 +645,7 @@ func (p *provisioner) createHabUser(o terraform.UIOutput, comm communicator.Comm if p.UseSudo { command = fmt.Sprintf("sudo %s", command) } + if err := p.runCommand(o, comm, command); err != nil { return err } diff --git a/builtin/provisioners/habitat/resource_provisioner_test.go b/builtin/provisioners/habitat/resource_provisioner_test.go index 5add40cbf..d18a39b5c 100644 --- a/builtin/provisioners/habitat/resource_provisioner_test.go +++ b/builtin/provisioners/habitat/resource_provisioner_test.go @@ -19,9 +19,10 @@ func TestProvisioner(t *testing.T) { func TestResourceProvisioner_Validate_good(t *testing.T) { c := testConfig(t, map[string]interface{}{ - "peer": "1.2.3.4", - "version": "0.32.0", - "service_type": "systemd", + "peer": "1.2.3.4", + "version": "0.32.0", + "service_type": "systemd", + "accept_license": false, }) warn, errs := Provisioner().Validate(c) @@ -42,13 +43,15 @@ func TestResourceProvisioner_Validate_bad(t *testing.T) { if len(warn) > 0 { t.Fatalf("Warnings: %v", warn) } - if len(errs) != 1 { - t.Fatalf("Should have one error") + //Two errors, one for service_type, other for missing required accept_license argument + if len(errs) != 2 { + t.Fatalf("Should have one errors, got %d", len(errs)) } } func TestResourceProvisioner_Validate_bad_service_config(t *testing.T) { c := testConfig(t, map[string]interface{}{ + "accept_license": true, "service": []interface{}{ map[string]interface{}{ "name": "core/foo", diff --git a/website/docs/provisioners/chef.html.markdown b/website/docs/provisioners/chef.html.markdown index 67c34b59a..3dbb5b70e 100644 --- a/website/docs/provisioners/chef.html.markdown +++ b/website/docs/provisioners/chef.html.markdown @@ -19,6 +19,8 @@ The `chef` provisioner has some prerequisites for specific connection types: * For `ssh` type connections, `cURL` must be available on the remote host. * For `winrm` connections, `PowerShell 2.0` must be available on the remote host. +[Chef end user license agreement](https://www.chef.io/end-user-license-agreement/) must be accepted by setting `chef_license` to `accept` in `client_options` argument unless you are installing an old version of Chef client. + Without these prerequisites, your provisioning execution will fail. ## Example usage @@ -43,6 +45,7 @@ resource "aws_instance" "web" { EOF environment = "_default" + client_options = ["chef_license 'accept'"] run_list = ["cookbook::recipe"] node_name = "webserver1" secret_key = "${file("../encrypted_data_bag_secret")}" diff --git a/website/docs/provisioners/habitat.html.markdown b/website/docs/provisioners/habitat.html.markdown index f96239b8e..df95f04f2 100644 --- a/website/docs/provisioners/habitat.html.markdown +++ b/website/docs/provisioners/habitat.html.markdown @@ -30,6 +30,7 @@ resource "aws_instance" "redis" { peer = "${aws_instance.redis.0.private_ip}" use_sudo = true service_type = "systemd" + accept_license = true service { name = "core/redis" @@ -46,6 +47,7 @@ resource "aws_instance" "redis" { There are 2 configuration levels, `supervisor` and `service`. Configuration placed directly within the `provisioner` block are supervisor configurations, and a provisioner can define zero or more services to run, and each service will have a `service` block within the `provisioner`. A `service` block can also contain zero or more `bind` blocks to create service group bindings. ### Supervisor Arguments +* `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. * `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`) * `service_type (string)` - (Optional) Method used to run the Habitat supervisor. Valid options are `unmanaged` and `systemd`. (Defaults to `systemd`) @@ -84,4 +86,4 @@ bind { * `application (string)` - (Optional) The application 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) \ No newline at end of file