2015-05-11 05:21:03 +02:00
|
|
|
package aws
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
2017-03-28 11:29:20 +02:00
|
|
|
"os"
|
2015-05-11 05:21:03 +02:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2017-03-28 11:29:20 +02:00
|
|
|
"github.com/hashicorp/errwrap"
|
2015-05-11 05:21:03 +02:00
|
|
|
"github.com/hashicorp/terraform/helper/resource"
|
|
|
|
"github.com/hashicorp/terraform/helper/schema"
|
|
|
|
|
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
|
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
2017-03-28 11:29:20 +02:00
|
|
|
"github.com/aws/aws-sdk-go/aws/session"
|
2015-05-11 05:21:03 +02:00
|
|
|
"github.com/aws/aws-sdk-go/service/opsworks"
|
|
|
|
)
|
|
|
|
|
|
|
|
func resourceAwsOpsworksStack() *schema.Resource {
|
|
|
|
return &schema.Resource{
|
|
|
|
Create: resourceAwsOpsworksStackCreate,
|
|
|
|
Read: resourceAwsOpsworksStackRead,
|
|
|
|
Update: resourceAwsOpsworksStackUpdate,
|
|
|
|
Delete: resourceAwsOpsworksStackDelete,
|
2016-09-29 12:44:58 +02:00
|
|
|
Importer: &schema.ResourceImporter{
|
|
|
|
State: schema.ImportStatePassthrough,
|
|
|
|
},
|
2015-05-11 05:21:03 +02:00
|
|
|
|
|
|
|
Schema: map[string]*schema.Schema{
|
2017-03-15 15:17:53 +01:00
|
|
|
"agent_version": {
|
2016-05-05 23:56:44 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
2016-05-09 18:26:16 +02:00
|
|
|
Computed: true,
|
2016-05-05 23:56:44 +02:00
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"id": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"name": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"region": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
ForceNew: true,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"service_role_arn": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"default_instance_profile_arn": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"color": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"configuration_manager_name": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Default: "Chef",
|
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"configuration_manager_version": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
2017-03-22 21:26:47 +01:00
|
|
|
Default: "11.10",
|
2015-05-11 05:21:03 +02:00
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"manage_berkshelf": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
Default: false,
|
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"berkshelf_version": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Default: "3.2.0",
|
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"custom_cookbooks_source": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeList,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
Elem: &schema.Resource{
|
|
|
|
Schema: map[string]*schema.Schema{
|
2017-03-15 15:17:53 +01:00
|
|
|
"type": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"url": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"username": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"password": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"revision": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"ssh_key": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"custom_json": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"default_availability_zone": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"default_os": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Default: "Ubuntu 12.04 LTS",
|
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"default_root_device_type": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Default: "instance-store",
|
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"default_ssh_key_name": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"default_subnet_id": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
2017-03-22 21:26:47 +01:00
|
|
|
Computed: true,
|
2015-05-11 05:21:03 +02:00
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"hostname_theme": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Default: "Layer_Dependent",
|
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"use_custom_cookbooks": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
Default: false,
|
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"use_opsworks_security_groups": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
Default: true,
|
|
|
|
},
|
|
|
|
|
2017-03-15 15:17:53 +01:00
|
|
|
"vpc_id": {
|
2015-05-11 05:21:03 +02:00
|
|
|
Type: schema.TypeString,
|
|
|
|
ForceNew: true,
|
2017-03-22 21:26:47 +01:00
|
|
|
Computed: true,
|
2015-05-11 05:21:03 +02:00
|
|
|
Optional: true,
|
|
|
|
},
|
2017-03-28 11:29:20 +02:00
|
|
|
|
|
|
|
"stack_endpoint": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
2015-05-11 05:21:03 +02:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceAwsOpsworksStackValidate(d *schema.ResourceData) error {
|
|
|
|
cookbooksSourceCount := d.Get("custom_cookbooks_source.#").(int)
|
|
|
|
if cookbooksSourceCount > 1 {
|
|
|
|
return fmt.Errorf("Only one custom_cookbooks_source is permitted")
|
|
|
|
}
|
|
|
|
|
|
|
|
vpcId := d.Get("vpc_id").(string)
|
|
|
|
if vpcId != "" {
|
|
|
|
if d.Get("default_subnet_id").(string) == "" {
|
|
|
|
return fmt.Errorf("default_subnet_id must be set if vpc_id is set")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if d.Get("default_availability_zone").(string) == "" {
|
|
|
|
return fmt.Errorf("either vpc_id or default_availability_zone must be set")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceAwsOpsworksStackCustomCookbooksSource(d *schema.ResourceData) *opsworks.Source {
|
|
|
|
count := d.Get("custom_cookbooks_source.#").(int)
|
|
|
|
if count == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return &opsworks.Source{
|
|
|
|
Type: aws.String(d.Get("custom_cookbooks_source.0.type").(string)),
|
|
|
|
Url: aws.String(d.Get("custom_cookbooks_source.0.url").(string)),
|
|
|
|
Username: aws.String(d.Get("custom_cookbooks_source.0.username").(string)),
|
|
|
|
Password: aws.String(d.Get("custom_cookbooks_source.0.password").(string)),
|
|
|
|
Revision: aws.String(d.Get("custom_cookbooks_source.0.revision").(string)),
|
|
|
|
SshKey: aws.String(d.Get("custom_cookbooks_source.0.ssh_key").(string)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceAwsOpsworksSetStackCustomCookbooksSource(d *schema.ResourceData, v *opsworks.Source) {
|
|
|
|
nv := make([]interface{}, 0, 1)
|
2016-04-21 20:59:40 +02:00
|
|
|
if v != nil && v.Type != nil && *v.Type != "" {
|
2015-05-11 05:21:03 +02:00
|
|
|
m := make(map[string]interface{})
|
|
|
|
if v.Type != nil {
|
|
|
|
m["type"] = *v.Type
|
|
|
|
}
|
|
|
|
if v.Url != nil {
|
|
|
|
m["url"] = *v.Url
|
|
|
|
}
|
|
|
|
if v.Username != nil {
|
|
|
|
m["username"] = *v.Username
|
|
|
|
}
|
|
|
|
if v.Revision != nil {
|
|
|
|
m["revision"] = *v.Revision
|
|
|
|
}
|
2016-04-16 23:56:36 +02:00
|
|
|
// v.Password will, on read, contain the placeholder string
|
|
|
|
// "*****FILTERED*****", so we ignore it on read and let persist
|
|
|
|
// the value already in the state.
|
2015-05-11 05:21:03 +02:00
|
|
|
nv = append(nv, m)
|
|
|
|
}
|
|
|
|
|
|
|
|
err := d.Set("custom_cookbooks_source", nv)
|
|
|
|
if err != nil {
|
|
|
|
// should never happen
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceAwsOpsworksStackRead(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
client := meta.(*AWSClient).opsworksconn
|
2017-03-28 11:29:20 +02:00
|
|
|
var conErr error
|
|
|
|
if v := d.Get("stack_endpoint").(string); v != "" {
|
|
|
|
client, conErr = opsworksConnForRegion(v, meta)
|
|
|
|
if conErr != nil {
|
|
|
|
return conErr
|
|
|
|
}
|
|
|
|
}
|
2015-05-11 05:21:03 +02:00
|
|
|
|
|
|
|
req := &opsworks.DescribeStacksInput{
|
|
|
|
StackIds: []*string{
|
|
|
|
aws.String(d.Id()),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("[DEBUG] Reading OpsWorks stack: %s", d.Id())
|
|
|
|
|
2017-03-28 11:29:20 +02:00
|
|
|
// notFound represents the number of times we've called DescribeStacks looking
|
|
|
|
// for this Stack. If it's not found in the the default region we're in, we
|
|
|
|
// check us-east-1 in the event this stack was created with Terraform before
|
|
|
|
// version 0.9
|
|
|
|
// See https://github.com/hashicorp/terraform/issues/12842
|
|
|
|
var notFound int
|
|
|
|
var resp *opsworks.DescribeStacksOutput
|
|
|
|
var dErr error
|
|
|
|
|
|
|
|
for {
|
|
|
|
resp, dErr = client.DescribeStacks(req)
|
|
|
|
if dErr != nil {
|
|
|
|
if awserr, ok := dErr.(awserr.Error); ok {
|
|
|
|
if awserr.Code() == "ResourceNotFoundException" {
|
|
|
|
if notFound < 1 {
|
|
|
|
// If we haven't already, try us-east-1, legacy connection
|
|
|
|
notFound++
|
|
|
|
var connErr error
|
|
|
|
client, connErr = opsworksConnForRegion("us-east-1", meta)
|
|
|
|
if connErr != nil {
|
|
|
|
return connErr
|
|
|
|
}
|
|
|
|
// start again from the top of the FOR loop, but with a client
|
|
|
|
// configured to talk to us-east-1
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// We've tried both the original and us-east-1 endpoint, and the stack
|
|
|
|
// is still not found
|
|
|
|
log.Printf("[DEBUG] OpsWorks stack (%s) not found", d.Id())
|
|
|
|
d.SetId("")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// not ResoureNotFoundException, fall through to returning error
|
2015-05-11 05:21:03 +02:00
|
|
|
}
|
2017-03-28 11:29:20 +02:00
|
|
|
return dErr
|
2015-05-11 05:21:03 +02:00
|
|
|
}
|
2017-03-28 11:29:20 +02:00
|
|
|
// If the stack was found, set the stack_endpoint
|
|
|
|
if client.Config.Region != nil && *client.Config.Region != "" {
|
|
|
|
log.Printf("[DEBUG] Setting stack_endpoint for (%s) to (%s)", d.Id(), *client.Config.Region)
|
|
|
|
if err := d.Set("stack_endpoint", *client.Config.Region); err != nil {
|
|
|
|
log.Printf("[WARN] Error setting stack_endpoint: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
log.Printf("[DEBUG] Breaking stack endpoint search, found stack for (%s)", d.Id())
|
|
|
|
// Break the FOR loop
|
|
|
|
break
|
2015-05-11 05:21:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
stack := resp.Stacks[0]
|
2016-05-05 23:56:44 +02:00
|
|
|
d.Set("agent_version", stack.AgentVersion)
|
2015-05-11 05:21:03 +02:00
|
|
|
d.Set("name", stack.Name)
|
|
|
|
d.Set("region", stack.Region)
|
|
|
|
d.Set("default_instance_profile_arn", stack.DefaultInstanceProfileArn)
|
|
|
|
d.Set("service_role_arn", stack.ServiceRoleArn)
|
|
|
|
d.Set("default_availability_zone", stack.DefaultAvailabilityZone)
|
|
|
|
d.Set("default_os", stack.DefaultOs)
|
|
|
|
d.Set("default_root_device_type", stack.DefaultRootDeviceType)
|
|
|
|
d.Set("default_ssh_key_name", stack.DefaultSshKeyName)
|
|
|
|
d.Set("default_subnet_id", stack.DefaultSubnetId)
|
|
|
|
d.Set("hostname_theme", stack.HostnameTheme)
|
|
|
|
d.Set("use_custom_cookbooks", stack.UseCustomCookbooks)
|
2016-09-29 14:00:52 +02:00
|
|
|
if stack.CustomJson != nil {
|
|
|
|
d.Set("custom_json", stack.CustomJson)
|
|
|
|
}
|
2015-05-11 05:21:03 +02:00
|
|
|
d.Set("use_opsworks_security_groups", stack.UseOpsworksSecurityGroups)
|
|
|
|
d.Set("vpc_id", stack.VpcId)
|
|
|
|
if color, ok := stack.Attributes["Color"]; ok {
|
|
|
|
d.Set("color", color)
|
|
|
|
}
|
|
|
|
if stack.ConfigurationManager != nil {
|
|
|
|
d.Set("configuration_manager_name", stack.ConfigurationManager.Name)
|
|
|
|
d.Set("configuration_manager_version", stack.ConfigurationManager.Version)
|
|
|
|
}
|
|
|
|
if stack.ChefConfiguration != nil {
|
|
|
|
d.Set("berkshelf_version", stack.ChefConfiguration.BerkshelfVersion)
|
|
|
|
d.Set("manage_berkshelf", stack.ChefConfiguration.ManageBerkshelf)
|
|
|
|
}
|
|
|
|
resourceAwsOpsworksSetStackCustomCookbooksSource(d, stack.CustomCookbooksSource)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-03-28 11:29:20 +02:00
|
|
|
// opsworksConn will return a connection for the stack_endpoint in the
|
|
|
|
// configuration. Stacks can only be accessed or managed within the endpoint
|
|
|
|
// in which they are created, so we allow users to specify an original endpoint
|
|
|
|
// for Stacks created before multiple endpoints were offered (Terraform v0.9.0).
|
|
|
|
// See:
|
|
|
|
// - https://github.com/hashicorp/terraform/pull/12688
|
|
|
|
// - https://github.com/hashicorp/terraform/issues/12842
|
|
|
|
func opsworksConnForRegion(region string, meta interface{}) (*opsworks.OpsWorks, error) {
|
|
|
|
originalConn := meta.(*AWSClient).opsworksconn
|
|
|
|
|
|
|
|
// Regions are the same, no need to reconfigure
|
|
|
|
if originalConn.Config.Region != nil && *originalConn.Config.Region == region {
|
|
|
|
return originalConn, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set up base session
|
|
|
|
sess, err := session.NewSession(&originalConn.Config)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errwrap.Wrapf("Error creating AWS session: {{err}}", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
sess.Handlers.Build.PushBackNamed(addTerraformVersionToUserAgent)
|
|
|
|
|
|
|
|
if extraDebug := os.Getenv("TERRAFORM_AWS_AUTHFAILURE_DEBUG"); extraDebug != "" {
|
|
|
|
sess.Handlers.UnmarshalError.PushFrontNamed(debugAuthFailure)
|
|
|
|
}
|
|
|
|
|
|
|
|
newSession := sess.Copy(&aws.Config{Region: aws.String(region)})
|
|
|
|
newOpsworksconn := opsworks.New(newSession)
|
|
|
|
|
|
|
|
log.Printf("[DEBUG] Returning new OpsWorks client")
|
|
|
|
return newOpsworksconn, nil
|
|
|
|
}
|
|
|
|
|
2015-05-11 05:21:03 +02:00
|
|
|
func resourceAwsOpsworksStackCreate(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
client := meta.(*AWSClient).opsworksconn
|
|
|
|
|
|
|
|
err := resourceAwsOpsworksStackValidate(d)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
req := &opsworks.CreateStackInput{
|
|
|
|
DefaultInstanceProfileArn: aws.String(d.Get("default_instance_profile_arn").(string)),
|
2016-01-13 18:13:09 +01:00
|
|
|
Name: aws.String(d.Get("name").(string)),
|
|
|
|
Region: aws.String(d.Get("region").(string)),
|
|
|
|
ServiceRoleArn: aws.String(d.Get("service_role_arn").(string)),
|
2016-03-18 21:51:25 +01:00
|
|
|
DefaultOs: aws.String(d.Get("default_os").(string)),
|
2016-01-13 18:13:09 +01:00
|
|
|
UseOpsworksSecurityGroups: aws.Bool(d.Get("use_opsworks_security_groups").(bool)),
|
2015-05-11 05:21:03 +02:00
|
|
|
}
|
2016-04-19 20:59:03 +02:00
|
|
|
req.ConfigurationManager = &opsworks.StackConfigurationManager{
|
2016-04-19 21:41:26 +02:00
|
|
|
Name: aws.String(d.Get("configuration_manager_name").(string)),
|
|
|
|
Version: aws.String(d.Get("configuration_manager_version").(string)),
|
2016-04-19 20:59:03 +02:00
|
|
|
}
|
2015-05-11 05:21:03 +02:00
|
|
|
inVpc := false
|
|
|
|
if vpcId, ok := d.GetOk("vpc_id"); ok {
|
|
|
|
req.VpcId = aws.String(vpcId.(string))
|
|
|
|
inVpc = true
|
|
|
|
}
|
|
|
|
if defaultSubnetId, ok := d.GetOk("default_subnet_id"); ok {
|
|
|
|
req.DefaultSubnetId = aws.String(defaultSubnetId.(string))
|
|
|
|
}
|
|
|
|
if defaultAvailabilityZone, ok := d.GetOk("default_availability_zone"); ok {
|
|
|
|
req.DefaultAvailabilityZone = aws.String(defaultAvailabilityZone.(string))
|
|
|
|
}
|
2016-10-06 14:30:06 +02:00
|
|
|
if defaultRootDeviceType, ok := d.GetOk("default_root_device_type"); ok {
|
|
|
|
req.DefaultRootDeviceType = aws.String(defaultRootDeviceType.(string))
|
|
|
|
}
|
2015-05-11 05:21:03 +02:00
|
|
|
|
2016-01-06 18:19:42 +01:00
|
|
|
log.Printf("[DEBUG] Creating OpsWorks stack: %s", req)
|
2015-05-11 05:21:03 +02:00
|
|
|
|
|
|
|
var resp *opsworks.CreateStackOutput
|
2016-03-09 23:53:32 +01:00
|
|
|
err = resource.Retry(20*time.Minute, func() *resource.RetryError {
|
2015-05-11 05:21:03 +02:00
|
|
|
var cerr error
|
|
|
|
resp, cerr = client.CreateStack(req)
|
|
|
|
if cerr != nil {
|
|
|
|
if opserr, ok := cerr.(awserr.Error); ok {
|
|
|
|
// If Terraform is also managing the service IAM role,
|
|
|
|
// it may have just been created and not yet be
|
|
|
|
// propagated.
|
|
|
|
// AWS doesn't provide a machine-readable code for this
|
|
|
|
// specific error, so we're forced to do fragile message
|
|
|
|
// matching.
|
|
|
|
// The full error we're looking for looks something like
|
|
|
|
// the following:
|
|
|
|
// Service Role Arn: [...] is not yet propagated, please try again in a couple of minutes
|
2016-01-06 18:19:42 +01:00
|
|
|
propErr := "not yet propagated"
|
|
|
|
trustErr := "not the necessary trust relationship"
|
2016-04-06 21:57:14 +02:00
|
|
|
validateErr := "validate IAM role permission"
|
|
|
|
if opserr.Code() == "ValidationException" && (strings.Contains(opserr.Message(), trustErr) || strings.Contains(opserr.Message(), propErr) || strings.Contains(opserr.Message(), validateErr)) {
|
2015-05-11 05:21:03 +02:00
|
|
|
log.Printf("[INFO] Waiting for service IAM role to propagate")
|
2016-03-09 23:53:32 +01:00
|
|
|
return resource.RetryableError(cerr)
|
2015-05-11 05:21:03 +02:00
|
|
|
}
|
|
|
|
}
|
2016-03-09 23:53:32 +01:00
|
|
|
return resource.NonRetryableError(cerr)
|
2015-05-11 05:21:03 +02:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
stackId := *resp.StackId
|
|
|
|
d.SetId(stackId)
|
|
|
|
d.Set("id", stackId)
|
|
|
|
|
2016-01-13 18:13:09 +01:00
|
|
|
if inVpc && *req.UseOpsworksSecurityGroups {
|
2015-05-11 05:21:03 +02:00
|
|
|
// For VPC-based stacks, OpsWorks asynchronously creates some default
|
|
|
|
// security groups which must exist before layers can be created.
|
|
|
|
// Unfortunately it doesn't tell us what the ids of these are, so
|
|
|
|
// we can't actually check for them. Instead, we just wait a nominal
|
|
|
|
// amount of time for their creation to complete.
|
|
|
|
log.Print("[INFO] Waiting for OpsWorks built-in security groups to be created")
|
|
|
|
time.Sleep(30 * time.Second)
|
|
|
|
}
|
|
|
|
|
|
|
|
return resourceAwsOpsworksStackUpdate(d, meta)
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceAwsOpsworksStackUpdate(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
client := meta.(*AWSClient).opsworksconn
|
2017-03-28 11:29:20 +02:00
|
|
|
var conErr error
|
|
|
|
if v := d.Get("stack_endpoint").(string); v != "" {
|
|
|
|
client, conErr = opsworksConnForRegion(v, meta)
|
|
|
|
if conErr != nil {
|
|
|
|
return conErr
|
|
|
|
}
|
|
|
|
}
|
2015-05-11 05:21:03 +02:00
|
|
|
|
|
|
|
err := resourceAwsOpsworksStackValidate(d)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
req := &opsworks.UpdateStackInput{
|
|
|
|
CustomJson: aws.String(d.Get("custom_json").(string)),
|
|
|
|
DefaultInstanceProfileArn: aws.String(d.Get("default_instance_profile_arn").(string)),
|
|
|
|
DefaultRootDeviceType: aws.String(d.Get("default_root_device_type").(string)),
|
|
|
|
DefaultSshKeyName: aws.String(d.Get("default_ssh_key_name").(string)),
|
|
|
|
Name: aws.String(d.Get("name").(string)),
|
|
|
|
ServiceRoleArn: aws.String(d.Get("service_role_arn").(string)),
|
|
|
|
StackId: aws.String(d.Id()),
|
|
|
|
UseCustomCookbooks: aws.Bool(d.Get("use_custom_cookbooks").(bool)),
|
|
|
|
UseOpsworksSecurityGroups: aws.Bool(d.Get("use_opsworks_security_groups").(bool)),
|
|
|
|
Attributes: make(map[string]*string),
|
|
|
|
CustomCookbooksSource: resourceAwsOpsworksStackCustomCookbooksSource(d),
|
|
|
|
}
|
2016-05-05 23:56:44 +02:00
|
|
|
if v, ok := d.GetOk("agent_version"); ok {
|
|
|
|
req.AgentVersion = aws.String(v.(string))
|
|
|
|
}
|
2015-05-11 05:21:03 +02:00
|
|
|
if v, ok := d.GetOk("default_os"); ok {
|
|
|
|
req.DefaultOs = aws.String(v.(string))
|
|
|
|
}
|
|
|
|
if v, ok := d.GetOk("default_subnet_id"); ok {
|
|
|
|
req.DefaultSubnetId = aws.String(v.(string))
|
|
|
|
}
|
|
|
|
if v, ok := d.GetOk("default_availability_zone"); ok {
|
|
|
|
req.DefaultAvailabilityZone = aws.String(v.(string))
|
|
|
|
}
|
|
|
|
if v, ok := d.GetOk("hostname_theme"); ok {
|
|
|
|
req.HostnameTheme = aws.String(v.(string))
|
|
|
|
}
|
|
|
|
if v, ok := d.GetOk("color"); ok {
|
|
|
|
req.Attributes["Color"] = aws.String(v.(string))
|
|
|
|
}
|
2017-03-22 21:26:47 +01:00
|
|
|
|
2015-05-11 05:21:03 +02:00
|
|
|
req.ChefConfiguration = &opsworks.ChefConfiguration{
|
|
|
|
BerkshelfVersion: aws.String(d.Get("berkshelf_version").(string)),
|
|
|
|
ManageBerkshelf: aws.Bool(d.Get("manage_berkshelf").(bool)),
|
|
|
|
}
|
2017-03-22 21:26:47 +01:00
|
|
|
|
2015-05-11 05:21:03 +02:00
|
|
|
req.ConfigurationManager = &opsworks.StackConfigurationManager{
|
|
|
|
Name: aws.String(d.Get("configuration_manager_name").(string)),
|
|
|
|
Version: aws.String(d.Get("configuration_manager_version").(string)),
|
|
|
|
}
|
|
|
|
|
2016-01-06 18:19:42 +01:00
|
|
|
log.Printf("[DEBUG] Updating OpsWorks stack: %s", req)
|
2015-05-11 05:21:03 +02:00
|
|
|
|
|
|
|
_, err = client.UpdateStack(req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return resourceAwsOpsworksStackRead(d, meta)
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceAwsOpsworksStackDelete(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
client := meta.(*AWSClient).opsworksconn
|
2017-03-28 11:29:20 +02:00
|
|
|
var conErr error
|
|
|
|
if v := d.Get("stack_endpoint").(string); v != "" {
|
|
|
|
client, conErr = opsworksConnForRegion(v, meta)
|
|
|
|
if conErr != nil {
|
|
|
|
return conErr
|
|
|
|
}
|
|
|
|
}
|
2015-05-11 05:21:03 +02:00
|
|
|
|
|
|
|
req := &opsworks.DeleteStackInput{
|
|
|
|
StackId: aws.String(d.Id()),
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("[DEBUG] Deleting OpsWorks stack: %s", d.Id())
|
|
|
|
|
|
|
|
_, err := client.DeleteStack(req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// For a stack in a VPC, OpsWorks has created some default security groups
|
|
|
|
// in the VPC, which it will now delete.
|
|
|
|
// Unfortunately, the security groups are deleted asynchronously and there
|
|
|
|
// is no robust way for us to determine when it is done. The VPC itself
|
|
|
|
// isn't deletable until the security groups are cleaned up, so this could
|
|
|
|
// make 'terraform destroy' fail if the VPC is also managed and we don't
|
|
|
|
// wait for the security groups to be deleted.
|
|
|
|
// There is no robust way to check for this, so we'll just wait a
|
|
|
|
// nominal amount of time.
|
2016-01-13 18:13:09 +01:00
|
|
|
_, inVpc := d.GetOk("vpc_id")
|
|
|
|
_, useOpsworksDefaultSg := d.GetOk("use_opsworks_security_group")
|
2015-12-07 10:45:56 +01:00
|
|
|
|
2016-01-13 18:13:09 +01:00
|
|
|
if inVpc && useOpsworksDefaultSg {
|
2015-05-11 05:21:03 +02:00
|
|
|
log.Print("[INFO] Waiting for Opsworks built-in security groups to be deleted")
|
|
|
|
time.Sleep(30 * time.Second)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|