From 60812045333b77d504d804d64600e26444a3790b Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Thu, 8 Feb 2018 07:55:10 +0000 Subject: [PATCH] New Resource: aws_cloud9_environment_ec2 --- aws/config.go | 3 + aws/provider.go | 1 + aws/resource_aws_cloud9_environment_ec2.go | 217 ++++++++++++++++ ...esource_aws_cloud9_environment_ec2_test.go | 237 ++++++++++++++++++ website/aws.erb | 9 + .../r/cloud9_environment_ec2.html.markdown | 39 +++ 6 files changed, 506 insertions(+) create mode 100644 aws/resource_aws_cloud9_environment_ec2.go create mode 100644 aws/resource_aws_cloud9_environment_ec2_test.go create mode 100644 website/docs/r/cloud9_environment_ec2.html.markdown diff --git a/aws/config.go b/aws/config.go index 5b28676dc52f..d87df2533678 100644 --- a/aws/config.go +++ b/aws/config.go @@ -21,6 +21,7 @@ import ( "github.com/aws/aws-sdk-go/service/athena" "github.com/aws/aws-sdk-go/service/autoscaling" "github.com/aws/aws-sdk-go/service/batch" + "github.com/aws/aws-sdk-go/service/cloud9" "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/aws/aws-sdk-go/service/cloudfront" "github.com/aws/aws-sdk-go/service/cloudtrail" @@ -138,6 +139,7 @@ type Config struct { type AWSClient struct { cfconn *cloudformation.CloudFormation + cloud9conn *cloud9.Cloud9 cloudfrontconn *cloudfront.CloudFront cloudtrailconn *cloudtrail.CloudTrail cloudwatchconn *cloudwatch.CloudWatch @@ -398,6 +400,7 @@ func (c *Config) Client() (interface{}, error) { client.apigateway = apigateway.New(awsApigatewaySess) client.appautoscalingconn = applicationautoscaling.New(sess) client.autoscalingconn = autoscaling.New(sess) + client.cloud9conn = cloud9.New(sess) client.cfconn = cloudformation.New(awsCfSess) client.cloudfrontconn = cloudfront.New(sess) client.cloudtrailconn = cloudtrail.New(sess) diff --git a/aws/provider.go b/aws/provider.go index 057409a680aa..ec87dd77eff6 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -278,6 +278,7 @@ func Provider() terraform.ResourceProvider { "aws_autoscaling_notification": resourceAwsAutoscalingNotification(), "aws_autoscaling_policy": resourceAwsAutoscalingPolicy(), "aws_autoscaling_schedule": resourceAwsAutoscalingSchedule(), + "aws_cloud9_environment_ec2": resourceAwsCloud9EnvironmentEc2(), "aws_cloudformation_stack": resourceAwsCloudFormationStack(), "aws_cloudfront_distribution": resourceAwsCloudFrontDistribution(), "aws_cloudfront_origin_access_identity": resourceAwsCloudFrontOriginAccessIdentity(), diff --git a/aws/resource_aws_cloud9_environment_ec2.go b/aws/resource_aws_cloud9_environment_ec2.go new file mode 100644 index 000000000000..af6c8e16d273 --- /dev/null +++ b/aws/resource_aws_cloud9_environment_ec2.go @@ -0,0 +1,217 @@ +package aws + +import ( + "fmt" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloud9" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsCloud9EnvironmentEc2() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsCloud9EnvironmentEc2Create, + Read: resourceAwsCloud9EnvironmentEc2Read, + Update: resourceAwsCloud9EnvironmentEc2Update, + Delete: resourceAwsCloud9EnvironmentEc2Delete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "instance_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "automatic_stop_time_minutes": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "owner_arn": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "subnet_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAwsCloud9EnvironmentEc2Create(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cloud9conn + + params := &cloud9.CreateEnvironmentEC2Input{ + InstanceType: aws.String(d.Get("instance_type").(string)), + Name: aws.String(d.Get("name").(string)), + ClientRequestToken: aws.String(resource.UniqueId()), + } + + if v, ok := d.GetOk("automatic_stop_time_minutes"); ok { + params.AutomaticStopTimeMinutes = aws.Int64(int64(v.(int))) + } + if v, ok := d.GetOk("description"); ok { + params.Description = aws.String(v.(string)) + } + if v, ok := d.GetOk("owner_arn"); ok { + params.OwnerArn = aws.String(v.(string)) + } + if v, ok := d.GetOk("subnet_id"); ok { + params.SubnetId = aws.String(v.(string)) + } + + out, err := conn.CreateEnvironmentEC2(params) + if err != nil { + return err + } + d.SetId(*out.EnvironmentId) + + stateConf := resource.StateChangeConf{ + Pending: []string{ + cloud9.EnvironmentStatusConnecting, + cloud9.EnvironmentStatusCreating, + }, + Target: []string{ + cloud9.EnvironmentStatusReady, + }, + Timeout: 10 * time.Minute, + Refresh: func() (interface{}, string, error) { + out, err := conn.DescribeEnvironmentStatus(&cloud9.DescribeEnvironmentStatusInput{ + EnvironmentId: aws.String(d.Id()), + }) + if err != nil { + return 42, "", err + } + + status := *out.Status + var sErr error + if status == cloud9.EnvironmentStatusError && out.Message != nil { + sErr = fmt.Errorf("Reason: %s", *out.Message) + } + + return out, status, sErr + }, + } + _, err = stateConf.WaitForState() + if err != nil { + return err + } + + return resourceAwsCloud9EnvironmentEc2Read(d, meta) +} + +func resourceAwsCloud9EnvironmentEc2Read(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cloud9conn + + log.Printf("[INFO] Reading Cloud9 Environment EC2 %s", d.Id()) + + out, err := conn.DescribeEnvironments(&cloud9.DescribeEnvironmentsInput{ + EnvironmentIds: []*string{aws.String(d.Id())}, + }) + if err != nil { + if isAWSErr(err, cloud9.ErrCodeNotFoundException, "") { + log.Printf("[WARN] Cloud9 Environment EC2 (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + return err + } + if len(out.Environments) == 0 { + log.Printf("[WARN] Cloud9 Environment EC2 (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + env := out.Environments[0] + + d.Set("arn", env.Arn) + d.Set("description", env.Description) + d.Set("name", env.Name) + d.Set("owner_arn", env.OwnerArn) + d.Set("type", env.Type) + + log.Printf("[DEBUG] Received Cloud9 Environment EC2: %s", env) + + return nil +} + +func resourceAwsCloud9EnvironmentEc2Update(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cloud9conn + + input := cloud9.UpdateEnvironmentInput{ + Description: aws.String(d.Get("description").(string)), + EnvironmentId: aws.String(d.Id()), + Name: aws.String(d.Get("name").(string)), + } + + log.Printf("[INFO] Updating Cloud9 Environment EC2: %s", input) + + out, err := conn.UpdateEnvironment(&input) + if err != nil { + return err + } + + log.Printf("[DEBUG] Cloud9 Environment EC2 updated: %s", out) + + return resourceAwsCloud9EnvironmentEc2Read(d, meta) +} + +func resourceAwsCloud9EnvironmentEc2Delete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cloud9conn + + _, err := conn.DeleteEnvironment(&cloud9.DeleteEnvironmentInput{ + EnvironmentId: aws.String(d.Id()), + }) + if err != nil { + return err + } + err = resource.Retry(1*time.Minute, func() *resource.RetryError { + out, err := conn.DescribeEnvironments(&cloud9.DescribeEnvironmentsInput{ + EnvironmentIds: []*string{aws.String(d.Id())}, + }) + if err != nil { + if isAWSErr(err, cloud9.ErrCodeNotFoundException, "") { + return nil + } + // :'-( + if isAWSErr(err, "AccessDeniedException", "is not authorized to access this resource") { + return nil + } + return resource.NonRetryableError(err) + } + if len(out.Environments) == 0 { + return nil + } + return resource.RetryableError(fmt.Errorf("Cloud9 EC2 Environment %q still exists", d.Id())) + }) + if err != nil { + return err + } + + return err +} diff --git a/aws/resource_aws_cloud9_environment_ec2_test.go b/aws/resource_aws_cloud9_environment_ec2_test.go new file mode 100644 index 000000000000..d64451829447 --- /dev/null +++ b/aws/resource_aws_cloud9_environment_ec2_test.go @@ -0,0 +1,237 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloud9" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSCloud9EnvironmentEc2_basic(t *testing.T) { + var conf cloud9.Environment + + rString := acctest.RandString(8) + envName := fmt.Sprintf("tf_acc_env_basic_%s", rString) + uEnvName := fmt.Sprintf("tf_acc_env_basic_updated_%s", rString) + + resourceName := "aws_cloud9_environment_ec2.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCloud9EnvironmentEc2Destroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSCloud9EnvironmentEc2Config(envName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCloud9EnvironmentEc2Exists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "instance_type", "t2.micro"), + resource.TestCheckResourceAttr(resourceName, "name", envName), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttrSet(resourceName, "owner_arn"), + ), + }, + resource.TestStep{ + Config: testAccAWSCloud9EnvironmentEc2Config(uEnvName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCloud9EnvironmentEc2Exists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "instance_type", "t2.micro"), + resource.TestCheckResourceAttr(resourceName, "name", uEnvName), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttrSet(resourceName, "owner_arn"), + ), + }, + }, + }) +} + +func TestAccAWSCloud9EnvironmentEc2_allFields(t *testing.T) { + var conf cloud9.Environment + + rString := acctest.RandString(8) + envName := fmt.Sprintf("tf_acc_env_basic_%s", rString) + uEnvName := fmt.Sprintf("tf_acc_env_basic_updated_%s", rString) + description := fmt.Sprintf("Tf Acc Test %s", rString) + uDescription := fmt.Sprintf("Tf Acc Test Updated %s", rString) + + resourceName := "aws_cloud9_environment_ec2.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCloud9EnvironmentEc2Destroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSCloud9EnvironmentEc2AllFieldsConfig(envName, description), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCloud9EnvironmentEc2Exists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "instance_type", "t2.micro"), + resource.TestCheckResourceAttr(resourceName, "name", envName), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttrSet(resourceName, "owner_arn"), + resource.TestCheckResourceAttrSet(resourceName, "type"), + ), + }, + resource.TestStep{ + Config: testAccAWSCloud9EnvironmentEc2AllFieldsConfig(uEnvName, uDescription), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCloud9EnvironmentEc2Exists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "instance_type", "t2.micro"), + resource.TestCheckResourceAttr(resourceName, "name", uEnvName), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttrSet(resourceName, "owner_arn"), + resource.TestCheckResourceAttrSet(resourceName, "type"), + ), + }, + }, + }) +} + +func TestAccAWSCloud9EnvironmentEc2_importBasic(t *testing.T) { + var conf cloud9.Environment + + rString := acctest.RandString(8) + name := fmt.Sprintf("tf_acc_api_doc_part_import_%s", rString) + + resourceName := "aws_cloud9_environment_ec2.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCloud9EnvironmentEc2Destroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSCloud9EnvironmentEc2Config(name), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCloud9EnvironmentEc2Exists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "instance_type", "t2.micro"), + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttrSet(resourceName, "owner_arn"), + resource.TestCheckResourceAttrSet(resourceName, "type"), + ), + }, + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"instance_type"}, + }, + }, + }) +} + +func testAccCheckAWSCloud9EnvironmentEc2Exists(n string, res *cloud9.Environment) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Cloud9 Environment EC2 ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).cloud9conn + + out, err := conn.DescribeEnvironments(&cloud9.DescribeEnvironmentsInput{ + EnvironmentIds: []*string{aws.String(rs.Primary.ID)}, + }) + if err != nil { + if isAWSErr(err, cloud9.ErrCodeNotFoundException, "") { + return fmt.Errorf("Cloud9 Environment EC2 (%q) not found", rs.Primary.ID) + } + return err + } + if len(out.Environments) == 0 { + return fmt.Errorf("Cloud9 Environment EC2 (%q) not found", rs.Primary.ID) + } + env := out.Environments[0] + + *res = *env + + return nil + } +} + +func testAccCheckAWSCloud9EnvironmentEc2Destroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).cloud9conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_cloud9_environment_ec2" { + continue + } + + out, err := conn.DescribeEnvironments(&cloud9.DescribeEnvironmentsInput{ + EnvironmentIds: []*string{aws.String(rs.Primary.ID)}, + }) + if err != nil { + if isAWSErr(err, cloud9.ErrCodeNotFoundException, "") { + return nil + } + // :'-( + if isAWSErr(err, "AccessDeniedException", "is not authorized to access this resource") { + return nil + } + return err + } + if len(out.Environments) == 0 { + return nil + } + + return fmt.Errorf("Cloud9 Environment EC2 %q still exists.", rs.Primary.ID) + } + return nil +} + +func testAccAWSCloud9EnvironmentEc2Config(name string) string { + return fmt.Sprintf(` +resource "aws_cloud9_environment_ec2" "test" { + instance_type = "t2.micro" + name = "%s" +} +`, name) +} + +func testAccAWSCloud9EnvironmentEc2AllFieldsConfig(name, description string) string { + return fmt.Sprintf(` +resource "aws_cloud9_environment_ec2" "test" { + instance_type = "t2.micro" + name = "%s" + description = "%s" + automatic_stop_time_minutes = 60 + subnet_id = "${aws_subnet.test.id}" + depends_on = ["aws_route_table_association.test"] +} + +resource "aws_vpc" "test" { + cidr_block = "10.10.0.0/16" +} + +resource "aws_subnet" "test" { + vpc_id = "${aws_vpc.test.id}" + cidr_block = "10.10.0.0/19" +} + +resource "aws_internet_gateway" "test" { + vpc_id = "${aws_vpc.test.id}" +} + +resource "aws_route_table" "test" { + vpc_id = "${aws_vpc.test.id}" + route { + cidr_block = "0.0.0.0/0" + gateway_id = "${aws_internet_gateway.test.id}" + } +} + +resource "aws_route_table_association" "test" { + subnet_id = "${aws_subnet.test.id}" + route_table_id = "${aws_route_table.test.id}" +} +`, name, description) +} diff --git a/website/aws.erb b/website/aws.erb index ad34ee554630..603ad5f6974a 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -359,6 +359,15 @@ + > + Cloud9 Resources + + + > CloudFormation Resources