diff --git a/aws/config.go b/aws/config.go index 8380a90dede..f51dc8ed8bf 100644 --- a/aws/config.go +++ b/aws/config.go @@ -83,6 +83,7 @@ import ( "github.com/aws/aws-sdk-go/service/pricing" "github.com/aws/aws-sdk-go/service/rds" "github.com/aws/aws-sdk-go/service/redshift" + "github.com/aws/aws-sdk-go/service/resourcegroups" "github.com/aws/aws-sdk-go/service/route53" "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/secretsmanager" @@ -201,6 +202,7 @@ type AWSClient struct { snsconn *sns.SNS stsconn *sts.STS redshiftconn *redshift.Redshift + resourcegroupsconn *resourcegroups.ResourceGroups r53conn *route53.Route53 partition string accountid string @@ -556,6 +558,7 @@ func (c *Config) Client() (interface{}, error) { client.r53conn = route53.New(r53Sess) client.rdsconn = rds.New(awsRdsSess) client.redshiftconn = redshift.New(sess) + client.resourcegroupsconn = resourcegroups.New(sess) client.simpledbconn = simpledb.New(sess) client.s3conn = s3.New(awsS3Sess) client.scconn = servicecatalog.New(sess) diff --git a/aws/provider.go b/aws/provider.go index f709961b5f4..82adc676686 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -567,6 +567,7 @@ func Provider() terraform.ResourceProvider { "aws_redshift_subnet_group": resourceAwsRedshiftSubnetGroup(), "aws_redshift_snapshot_copy_grant": resourceAwsRedshiftSnapshotCopyGrant(), "aws_redshift_event_subscription": resourceAwsRedshiftEventSubscription(), + "aws_resourcegroups_group": resourceAwsResourceGroupsGroup(), "aws_route53_delegation_set": resourceAwsRoute53DelegationSet(), "aws_route53_query_log": resourceAwsRoute53QueryLog(), "aws_route53_record": resourceAwsRoute53Record(), diff --git a/aws/resource_aws_resourcegroups_group.go b/aws/resource_aws_resourcegroups_group.go new file mode 100644 index 00000000000..5422e19e465 --- /dev/null +++ b/aws/resource_aws_resourcegroups_group.go @@ -0,0 +1,198 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/resourcegroups" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" +) + +func resourceAwsResourceGroupsGroup() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsResourceGroupsGroupCreate, + Read: resourceAwsResourceGroupsGroupRead, + Update: resourceAwsResourceGroupsGroupUpdate, + Delete: resourceAwsResourceGroupsGroupDelete, + + // As of 10/20/2018 it's not possible to import a complete resource because + // a resource group's tags are not returned by GetGroup nor ListGroups. This + // leads to dirty plans after an import, which breaks import tests. + // + // For now we ForceNew on the tags attribute and disable importing. + // + // Importer: &schema.ResourceImporter{ + // State: schema.ImportStatePassthrough, + // }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + }, + + "resource_query": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "query": { + Type: schema.TypeString, + Required: true, + }, + + "type": { + Type: schema.TypeString, + Optional: true, + Default: resourcegroups.QueryTypeTagFilters10, + ValidateFunc: validation.StringInSlice([]string{ + resourcegroups.QueryTypeTagFilters10, + }, false), + }, + }, + }, + }, + + "arn": { + Type: schema.TypeString, + Computed: true, + }, + + "tags": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func extractResourceGroupResourceQuery(resourceQueryList []interface{}) *resourcegroups.ResourceQuery { + resourceQuery := resourceQueryList[0].(map[string]interface{}) + + return &resourcegroups.ResourceQuery{ + Query: aws.String(resourceQuery["query"].(string)), + Type: aws.String(resourceQuery["type"].(string)), + } +} + +func extractResourceGroupTags(m map[string]interface{}) map[string]*string { + result := make(map[string]*string) + for k, v := range m { + result[k] = aws.String(v.(string)) + } + + return result +} + +func resourceAwsResourceGroupsGroupCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).resourcegroupsconn + + input := resourcegroups.CreateGroupInput{ + Description: aws.String(d.Get("description").(string)), + Name: aws.String(d.Get("name").(string)), + ResourceQuery: extractResourceGroupResourceQuery(d.Get("resource_query").([]interface{})), + Tags: extractResourceGroupTags(d.Get("tags").(map[string]interface{})), + } + + res, err := conn.CreateGroup(&input) + if err != nil { + return fmt.Errorf("error creating resource group: %s", err) + } + + d.SetId(aws.StringValue(res.Group.Name)) + + return resourceAwsResourceGroupsGroupRead(d, meta) +} + +func resourceAwsResourceGroupsGroupRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).resourcegroupsconn + + g, err := conn.GetGroup(&resourcegroups.GetGroupInput{ + GroupName: aws.String(d.Id()), + }) + + if err != nil { + if isAWSErr(err, resourcegroups.ErrCodeNotFoundException, "") { + log.Printf("[WARN] Resource Groups Group (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + return fmt.Errorf("error reading resource group (%s): %s", d.Id(), err) + } + + d.Set("name", aws.StringValue(g.Group.Name)) + d.Set("description", aws.StringValue(g.Group.Description)) + d.Set("arn", aws.StringValue(g.Group.GroupArn)) + + q, err := conn.GetGroupQuery(&resourcegroups.GetGroupQueryInput{ + GroupName: aws.String(d.Id()), + }) + + if err != nil { + return fmt.Errorf("error reading resource query for resource group (%s): %s", d.Id(), err) + } + + resultQuery := map[string]interface{}{} + resultQuery["query"] = aws.StringValue(q.GroupQuery.ResourceQuery.Query) + resultQuery["type"] = aws.StringValue(q.GroupQuery.ResourceQuery.Type) + d.Set("resource_query", []map[string]interface{}{resultQuery}) + + return nil +} + +func resourceAwsResourceGroupsGroupUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).resourcegroupsconn + + if d.HasChange("description") { + input := resourcegroups.UpdateGroupInput{ + GroupName: aws.String(d.Id()), + Description: aws.String(d.Get("description").(string)), + } + + _, err := conn.UpdateGroup(&input) + if err != nil { + return fmt.Errorf("error updating resource group (%s): %s", d.Id(), err) + } + } + + if d.HasChange("resource_query") { + input := resourcegroups.UpdateGroupQueryInput{ + GroupName: aws.String(d.Id()), + ResourceQuery: extractResourceGroupResourceQuery(d.Get("resource_query").([]interface{})), + } + + _, err := conn.UpdateGroupQuery(&input) + if err != nil { + return fmt.Errorf("error updating resource query for resource group (%s): %s", d.Id(), err) + } + } + + return resourceAwsResourceGroupsGroupRead(d, meta) +} + +func resourceAwsResourceGroupsGroupDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).resourcegroupsconn + + input := resourcegroups.DeleteGroupInput{ + GroupName: aws.String(d.Id()), + } + + _, err := conn.DeleteGroup(&input) + if err != nil { + return fmt.Errorf("error deleting resource group (%s): %s", d.Id(), err) + } + + return nil +} diff --git a/aws/resource_aws_resourcegroups_group_test.go b/aws/resource_aws_resourcegroups_group_test.go new file mode 100644 index 00000000000..f9b66dbeb56 --- /dev/null +++ b/aws/resource_aws_resourcegroups_group_test.go @@ -0,0 +1,145 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/resourcegroups" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSResourceGroup_basic(t *testing.T) { + resourceName := "aws_resourcegroups_group.test" + n := fmt.Sprintf("test-group-%d", acctest.RandInt()) + + desc1 := "Hello World" + desc2 := "Foo Bar" + + query1 := `{ + "ResourceTypeFilters": [ + "AWS::EC2::Instance" + ], + "TagFilters": [ + { + "Key": "Stage", + "Values": ["Test"] + } + ] +} +` + + query2 := `{ + "ResourceTypeFilters": [ + "AWS::EC2::Instance" + ], + "TagFilters": [ + { + "Key": "Hello", + "Values": ["World"] + } + ] +} +` + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSResourceGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSResourceGroupConfig_basic(n, desc1, query1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSResourceGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", n), + resource.TestCheckResourceAttr(resourceName, "description", desc1), + resource.TestCheckResourceAttr(resourceName, "resource_query.0.query", query1+"\n"), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + ), + }, + // See comment in resource. We can't cleanly import resources as of 10/20/2018. + // { + // ResourceName: resourceName, + // ImportState: true, + // ImportStateVerify: true, + // }, + { + Config: testAccAWSResourceGroupConfig_basic(n, desc2, query2), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "description", desc2), + resource.TestCheckResourceAttr(resourceName, "resource_query.0.query", query2+"\n"), + ), + }, + }, + }) +} + +func testAccCheckAWSResourceGroupExists(n string) 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 resource group name is set") + } + + conn := testAccProvider.Meta().(*AWSClient).resourcegroupsconn + + _, err := conn.GetGroup(&resourcegroups.GetGroupInput{ + GroupName: aws.String(rs.Primary.ID), + }) + + if err != nil { + return err + } + + return nil + } +} + +func testAccCheckAWSResourceGroupDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_waf_rule_group" { + continue + } + + conn := testAccProvider.Meta().(*AWSClient).resourcegroupsconn + resp, err := conn.GetGroup(&resourcegroups.GetGroupInput{ + GroupName: aws.String(rs.Primary.ID), + }) + + if err == nil { + if *resp.Group.Name == rs.Primary.ID { + return fmt.Errorf("Resource Group %s still exists", rs.Primary.ID) + } + } + + if isAWSErr(err, resourcegroups.ErrCodeNotFoundException, "") { + + return nil + } + + return err + } + + return nil +} + +func testAccAWSResourceGroupConfig_basic(rName string, desc string, query string) string { + return fmt.Sprintf(` +resource "aws_resourcegroups_group" "test" { + name = "%s" + description = "%s" + + resource_query { + query = < + + > + Resource Groups Resources + + + > WAF Resources