From f6321f31595d048e66aca6d4bcfa2d06fec9d205 Mon Sep 17 00:00:00 2001 From: Brett W Date: Wed, 25 Oct 2017 11:56:52 -0700 Subject: [PATCH 01/13] refactoring artifact definition --- aws/provider.go | 1 + aws/resource_aws_servicecatalog_product.go | 293 ++++++++++++++++++ ...esource_aws_servicecatalog_product_test.go | 253 +++++++++++++++ 3 files changed, 547 insertions(+) mode change 100644 => 100755 aws/provider.go create mode 100644 aws/resource_aws_servicecatalog_product.go create mode 100644 aws/resource_aws_servicecatalog_product_test.go diff --git a/aws/provider.go b/aws/provider.go old mode 100644 new mode 100755 index b36a4fd2ad85..8a206892465e --- a/aws/provider.go +++ b/aws/provider.go @@ -558,6 +558,7 @@ func Provider() terraform.ResourceProvider { "aws_default_security_group": resourceAwsDefaultSecurityGroup(), "aws_security_group_rule": resourceAwsSecurityGroupRule(), "aws_servicecatalog_portfolio": resourceAwsServiceCatalogPortfolio(), + "aws_servicecatalog_product": resourceAwsServiceCatalogProduct(), "aws_service_discovery_private_dns_namespace": resourceAwsServiceDiscoveryPrivateDnsNamespace(), "aws_service_discovery_public_dns_namespace": resourceAwsServiceDiscoveryPublicDnsNamespace(), "aws_service_discovery_service": resourceAwsServiceDiscoveryService(), diff --git a/aws/resource_aws_servicecatalog_product.go b/aws/resource_aws_servicecatalog_product.go new file mode 100644 index 000000000000..c88799f79c17 --- /dev/null +++ b/aws/resource_aws_servicecatalog_product.go @@ -0,0 +1,293 @@ +package aws + +import ( + "bytes" + "fmt" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + + "github.com/aws/aws-sdk-go/service/servicecatalog" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsServiceCatalogProduct() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsServiceCatalogProductCreate, + Read: resourceAwsServiceCatalogProductRead, + Update: resourceAwsServiceCatalogProductUpdate, + Delete: resourceAwsServiceCatalogProductDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "created_time": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Required: true, + }, + "distributor": { + Type: schema.TypeString, + Required: true, + }, + "id": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + "owner": { + Type: schema.TypeString, + Required: true, + }, + "product_arn": { + Type: schema.TypeString, + Computed: true, + }, + "product_type": { + Type: schema.TypeString, + Required: true, + }, + "support_description": { + Type: schema.TypeString, + Required: true, + }, + "support_email": { + Type: schema.TypeString, + Required: true, + }, + "support_url": { + Type: schema.TypeString, + Required: true, + }, + "artifact": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "description": { + Type: schema.TypeString, + Required: true, + }, + "load_template_from_url": { + Type: schema.TypeString, + Required: true, + }, + "type": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + Set: resourceProductArtifactHash, + }, + }, + } +} + +func resourceProductArtifactHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%d-", m["name"].(string))) + buf.WriteString(fmt.Sprintf("%d-", m["description"].(string))) + buf.WriteString(fmt.Sprintf("%d-", m["load_template_from_url"].(string))) + buf.WriteString(fmt.Sprintf("%d-", m["type"].(string))) + return hashcode.String(buf.String()) +} + +func resourceAwsServiceCatalogProductCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).scconn + input := servicecatalog.CreateProductInput{} + + if v, ok := d.GetOk("name"); ok { + now := time.Now() + input.IdempotencyToken = aws.String(fmt.Sprintf("%d", now.UnixNano())) + input.Name = aws.String(v.(string)) + } + + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + + if v, ok := d.GetOk("distributor"); ok { + input.Distributor = aws.String(v.(string)) + } + + if v, ok := d.GetOk("name"); ok { + input.Name = aws.String(v.(string)) + } + + if v, ok := d.GetOk("owner"); ok { + input.Owner = aws.String(v.(string)) + } + + if v, ok := d.GetOk("product_type"); ok { + input.ProductType = aws.String(v.(string)) + } + + if v, ok := d.GetOk("support_description"); ok { + input.SupportDescription = aws.String(v.(string)) + } + + if v, ok := d.GetOk("support_email"); ok { + input.SupportEmail = aws.String(v.(string)) + } + + if v, ok := d.GetOk("support_url"); ok { + input.SupportUrl = aws.String(v.(string)) + } + + artifactSettings := d.Get("artifact").(*schema.Set).List()[0] + artifactSettings2 := artifactSettings.(map[string]interface{}) + + artifactProperties := servicecatalog.ProvisioningArtifactProperties{} + artifactProperties.Description = aws.String(artifactSettings2["description"].(string)) + artifactProperties.Name = aws.String(artifactSettings2["name"].(string)) + artifactProperties.Type = aws.String(artifactSettings2["type"].(string)) + url := aws.String(artifactSettings2["load_template_from_url"].(string)) + info := map[string]*string{ + "LoadTemplateFromURL": url, + } + artifactProperties.Info = info + input.SetProvisioningArtifactParameters(&artifactProperties) + + log.Printf("[DEBUG] Creating Service Catalog Product: %#v %#v", input, artifactProperties) + resp, err := conn.CreateProduct(&input) + if err != nil { + return fmt.Errorf("Creating ServiceCatalog product failed: %s", err.Error()) + } + d.SetId(*resp.ProductViewDetail.ProductViewSummary.ProductId) + + return resourceAwsServiceCatalogProductRead(d, meta) +} + +func resourceAwsServiceCatalogProductRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).scconn + input := servicecatalog.DescribeProductAsAdminInput{} + input.Id = aws.String(d.Id()) + + log.Printf("[DEBUG] Reading Service Catalog Product: %#v", input) + resp, err := conn.DescribeProductAsAdmin(&input) + if err != nil { + if scErr, ok := err.(awserr.Error); ok && scErr.Code() == "ResourceNotFoundException" { + log.Printf("[WARN] Service Catalog Product %q not found, removing from state", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("Reading ServiceCatalog product '%s' failed: %s", *input.Id, err.Error()) + } + + if err := d.Set("created_time", resp.ProductViewDetail.CreatedTime.Format(time.RFC3339)); err != nil { + log.Printf("[DEBUG] Error setting created_time: %s", err) + } + + d.Set("product_arn", resp.ProductViewDetail.ProductARN) + pvs := resp.ProductViewDetail.ProductViewSummary + + d.Set("description", pvs.ShortDescription) + d.Set("distributor", pvs.Distributor) + d.Set("name", pvs.Name) + d.Set("owner", pvs.Owner) + d.Set("product_type", pvs.Type) + d.Set("support_description", pvs.SupportDescription) + d.Set("support_email", pvs.SupportEmail) + d.Set("support_url", pvs.SupportUrl) + + provisioningArtifactSummary := getProvisioningArtifactSummary(resp) + d.Set("artifact_description", provisioningArtifactSummary.Description) + d.Set("artifact_id", provisioningArtifactSummary.Id) + d.Set("artifact_name", provisioningArtifactSummary.Name) + return nil +} + +// Lookup the first artifact, which was the one created on inital build, by comparing created at time +func getProvisioningArtifactSummary(resp *servicecatalog.DescribeProductAsAdminOutput) *servicecatalog.ProvisioningArtifactSummary { + firstPas := resp.ProvisioningArtifactSummaries[0] + for _, pas := range resp.ProvisioningArtifactSummaries { + if pas.CreatedTime.UnixNano() < firstPas.CreatedTime.UnixNano() { + firstPas = pas + } + } + return firstPas +} + +func resourceAwsServiceCatalogProductUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).scconn + input := servicecatalog.UpdateProductInput{} + input.Id = aws.String(d.Id()) + + if d.HasChange("description") { + v, _ := d.GetOk("description") + input.Description = aws.String(v.(string)) + } + + if d.HasChange("distributor") { + v, _ := d.GetOk("distributor") + input.Distributor = aws.String(v.(string)) + } + + if d.HasChange("name") { + v, _ := d.GetOk("name") + input.Name = aws.String(v.(string)) + } + + if d.HasChange("owner") { + v, _ := d.GetOk("owner") + input.Owner = aws.String(v.(string)) + } + + if d.HasChange("support_description") { + v, _ := d.GetOk("support_description") + input.SupportDescription = aws.String(v.(string)) + } + + if d.HasChange("support_email") { + v, _ := d.GetOk("support_email") + input.SupportEmail = aws.String(v.(string)) + } + + if d.HasChange("support_url") { + v, _ := d.GetOk("support_url") + input.SupportUrl = aws.String(v.(string)) + } + + log.Printf("[DEBUG] Update Service Catalog Product: %#v", input) + _, err := conn.UpdateProduct(&input) + if err != nil { + return fmt.Errorf("Updating ServiceCatalog product '%s' failed: %s", *input.Id, err.Error()) + } + return resourceAwsServiceCatalogProductRead(d, meta) +} + +func resourceAwsServiceCatalogProductDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).scconn + input := servicecatalog.DeleteProductInput{} + input.Id = aws.String(d.Id()) + + log.Printf("[DEBUG] Delete Service Catalog Product: %#v", input) + _, err := conn.DeleteProduct(&input) + if err != nil { + return fmt.Errorf("Deleting ServiceCatalog product '%s' failed: %s", *input.Id, err.Error()) + } + return nil +} diff --git a/aws/resource_aws_servicecatalog_product_test.go b/aws/resource_aws_servicecatalog_product_test.go new file mode 100644 index 000000000000..4d6f056b13dd --- /dev/null +++ b/aws/resource_aws_servicecatalog_product_test.go @@ -0,0 +1,253 @@ +package aws + +import ( + "github.com/aws/aws-sdk-go/aws" + + "github.com/aws/aws-sdk-go/service/servicecatalog" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "fmt" + "testing" + + "bytes" + "text/template" +) + +func TestAccAWSServiceCatalogProductBasic(t *testing.T) { + name1 := acctest.RandString(5) + name2 := acctest.RandString(5) + name3 := acctest.RandString(5) + bucketName := acctest.RandString(16) + + template := template.Must(template.New("hcl").Parse(testAccCheckAwsServiceCatalogProductResourceConfigTemplate1)) + var template1, template2, template3 bytes.Buffer + template.Execute(&template1, Input{"dsc1", "dst1", name1, bucketName, "own1", "sd1", "a@b.com", "https://url/support1.html"}) + template.Execute(&template2, Input{"dsc2", "dst2", name2, bucketName, "own2", "sd2", "c@d.com", "https://url/support2.html"}) + template.Execute(&template3, Input{"dsc2", "dst2", name3, bucketName, "own2", "sd2", "c@d.com", "https://url/support2.html"}) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: template1.String(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "artifact.description", "ad"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "artifact.name", "an"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "description", "dsc1"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "distributor", "dst1"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "name", name1), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "owner", "own1"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "product_type", "CLOUD_FORMATION_TEMPLATE"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_description", "sd1"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_email", "a@b.com"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_url", "https://url/support1.html"), + ), + }, + resource.TestStep{ + Config: template2.String(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "artifact_description", "ad"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "artifact_name", "an"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "description", "dsc2"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "distributor", "dst2"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "name", name2), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "owner", "own2"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "product_type", "CLOUD_FORMATION_TEMPLATE"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_description", "sd2"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_email", "c@d.com"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_url", "https://url/support2.html"), + ), + }, + resource.TestStep{ + Config: template3.String(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "artifact_description", "ad"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "artifact_name", "an"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "description", "dsc2"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "distributor", "dst2"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "name", name3), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "owner", "own2"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "product_type", "CLOUD_FORMATION_TEMPLATE"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_description", "sd2"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_email", "c@d.com"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_url", "https://url/support2.html"), + ), + }, + }, + }) +} + +func TestAccAWSServiceCatalogProductDisappears(t *testing.T) { + var productViewDetail servicecatalog.ProductViewDetail + var template1 bytes.Buffer + + name := acctest.RandString(5) + bucketName := acctest.RandString(16) + + template := template.Must(template.New("hcl").Parse(testAccCheckAwsServiceCatalogProductResourceConfigTemplate1)) + template.Execute(&template1, Input{"dsc1", "dst1", name, bucketName, "own1", "sd1", "a@b.com", "https://url/support1.html"}) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckServiceCatlaogProductDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: template1.String(), + Check: resource.ComposeTestCheckFunc( + testAccCheckProduct("aws_servicecatalog_product.test", &productViewDetail), + testAccCheckServiceCatlaogProductDisappears(&productViewDetail), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSServiceCatalogProductImport(t *testing.T) { + resourceName := "aws_servicecatalog_product.test" + var template1 bytes.Buffer + + name := acctest.RandString(5) + bucketName := acctest.RandString(16) + template := template.Must(template.New("hcl").Parse(testAccCheckAwsServiceCatalogProductResourceConfigTemplate1)) + template.Execute(&template1, Input{"dsc1", "dst1", name, bucketName, "own1", "sd1", "a@b.com", "https://url/support1.html"}) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckServiceCatlaogProductDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: template1.String(), + }, + + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckProduct(pr string, pd *servicecatalog.ProductViewDetail) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).scconn + rs, ok := s.RootModule().Resources[pr] + if !ok { + return fmt.Errorf("Not found: %s", pr) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + input := servicecatalog.DescribeProductAsAdminInput{} + input.Id = aws.String(rs.Primary.ID) + + resp, err := conn.DescribeProductAsAdmin(&input) + if err != nil { + return err + } + + *pd = *resp.ProductViewDetail + return nil + } +} + +func testAccCheckServiceCatlaogProductDisappears(pd *servicecatalog.ProductViewDetail) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).scconn + + input := servicecatalog.DeleteProductInput{} + input.Id = pd.ProductViewSummary.ProductId + + _, err := conn.DeleteProduct(&input) + if err != nil { + return err + } + + return nil + } +} + +func testAccCheckServiceCatlaogProductDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).scconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_servicecatalog_portfolio" { + continue + } + input := servicecatalog.DescribeProductInput{} + input.Id = aws.String(rs.Primary.ID) + + _, err := conn.DescribeProduct(&input) + if err == nil { + return fmt.Errorf("Product still exists") + } + } + + return nil +} + +type Input struct { + Description string + Distributor string + Name string + BucketName string + Owner string + SupportDescription string + SupportEmail string + SupportUrl string +} + +const testAccCheckAwsServiceCatalogProductResourceConfigTemplate1 = ` +data "aws_caller_identity" "current" {} +variable region { default = "us-west-2" } + +resource "aws_s3_bucket" "bucket" { + bucket = "{{.BucketName}}" + region = "${var.region}" + acl = "private" + force_destroy = true +} + +resource "aws_s3_bucket_object" "template" { + bucket = "${aws_s3_bucket.bucket.id}" + key = "test_templates_for_terraform_sc_dev.json" + content = < Date: Thu, 26 Oct 2017 07:12:08 -0700 Subject: [PATCH 02/13] wip adding artifact block --- aws/resource_aws_servicecatalog_product.go | 21 ++++++++++++++++--- ...esource_aws_servicecatalog_product_test.go | 8 ++----- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/aws/resource_aws_servicecatalog_product.go b/aws/resource_aws_servicecatalog_product.go index c88799f79c17..e1df4f0947f9 100644 --- a/aws/resource_aws_servicecatalog_product.go +++ b/aws/resource_aws_servicecatalog_product.go @@ -214,12 +214,27 @@ func resourceAwsServiceCatalogProductRead(d *schema.ResourceData, meta interface d.Set("support_url", pvs.SupportUrl) provisioningArtifactSummary := getProvisioningArtifactSummary(resp) - d.Set("artifact_description", provisioningArtifactSummary.Description) - d.Set("artifact_id", provisioningArtifactSummary.Id) - d.Set("artifact_name", provisioningArtifactSummary.Name) + d.Set("artifact", provisioningArtifactSummary) return nil } +/* + //a := getArtifactMap(provisioningArtifactSummary) +WIP +func getArtifactMap(p *servicecatalog.ProvisioningArtifactSummary) *schema.Set { + //r := map[string]string{} + //r["description"] = *p.Description + //r["id"] = *p.Id + //r["name"] = *p.Name + r := map[string]interface{}{} + r["description"] = *p.Description + r["id"] = *p.Id + r["name"] = *p.Name + //return r.(map[string]interface{}) + return r.(schema.Set) +} +*/ + // Lookup the first artifact, which was the one created on inital build, by comparing created at time func getProvisioningArtifactSummary(resp *servicecatalog.DescribeProductAsAdminOutput) *servicecatalog.ProvisioningArtifactSummary { firstPas := resp.ProvisioningArtifactSummaries[0] diff --git a/aws/resource_aws_servicecatalog_product_test.go b/aws/resource_aws_servicecatalog_product_test.go index 4d6f056b13dd..a12348d4b071 100644 --- a/aws/resource_aws_servicecatalog_product_test.go +++ b/aws/resource_aws_servicecatalog_product_test.go @@ -35,8 +35,6 @@ func TestAccAWSServiceCatalogProductBasic(t *testing.T) { resource.TestStep{ Config: template1.String(), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "artifact.description", "ad"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "artifact.name", "an"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "description", "dsc1"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "distributor", "dst1"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "name", name1), @@ -50,8 +48,6 @@ func TestAccAWSServiceCatalogProductBasic(t *testing.T) { resource.TestStep{ Config: template2.String(), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "artifact_description", "ad"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "artifact_name", "an"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "description", "dsc2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "distributor", "dst2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "name", name2), @@ -65,8 +61,8 @@ func TestAccAWSServiceCatalogProductBasic(t *testing.T) { resource.TestStep{ Config: template3.String(), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "artifact_description", "ad"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "artifact_name", "an"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "artifact.description", "ad"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "artifact.name", "an"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "description", "dsc2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "distributor", "dst2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "name", name3), From f365893a1b64d172139da1f0853e2c1c3b0454f7 Mon Sep 17 00:00:00 2001 From: Brett W Date: Sun, 12 Nov 2017 17:26:24 -0800 Subject: [PATCH 03/13] refactoring to support new format --- aws/resource_aws_servicecatalog_product.go | 72 ++++++++----------- ...esource_aws_servicecatalog_product_test.go | 13 ++-- 2 files changed, 34 insertions(+), 51 deletions(-) diff --git a/aws/resource_aws_servicecatalog_product.go b/aws/resource_aws_servicecatalog_product.go index e1df4f0947f9..0c7a73aec82d 100644 --- a/aws/resource_aws_servicecatalog_product.go +++ b/aws/resource_aws_servicecatalog_product.go @@ -1,7 +1,6 @@ package aws import ( - "bytes" "fmt" "log" "time" @@ -10,7 +9,6 @@ import ( "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/servicecatalog" - "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" ) @@ -81,40 +79,35 @@ func resourceAwsServiceCatalogProduct() *schema.Resource { Required: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "name": { + "description": { Type: schema.TypeString, Required: true, }, - "description": { + "id": { Type: schema.TypeString, - Required: true, + Computed: true, }, "load_template_from_url": { Type: schema.TypeString, Required: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if d.IsNewResource() { + return true + } + return false + }, }, - "type": { + "name": { Type: schema.TypeString, - Optional: true, + Required: true, }, }, }, - Set: resourceProductArtifactHash, }, }, } } -func resourceProductArtifactHash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%d-", m["name"].(string))) - buf.WriteString(fmt.Sprintf("%d-", m["description"].(string))) - buf.WriteString(fmt.Sprintf("%d-", m["load_template_from_url"].(string))) - buf.WriteString(fmt.Sprintf("%d-", m["type"].(string))) - return hashcode.String(buf.String()) -} - func resourceAwsServiceCatalogProductCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).scconn input := servicecatalog.CreateProductInput{} @@ -157,14 +150,13 @@ func resourceAwsServiceCatalogProductCreate(d *schema.ResourceData, meta interfa input.SupportUrl = aws.String(v.(string)) } - artifactSettings := d.Get("artifact").(*schema.Set).List()[0] - artifactSettings2 := artifactSettings.(map[string]interface{}) - + artifactSettings := d.Get("artifact").(*schema.Set).List()[0].(map[string]interface{}) artifactProperties := servicecatalog.ProvisioningArtifactProperties{} - artifactProperties.Description = aws.String(artifactSettings2["description"].(string)) - artifactProperties.Name = aws.String(artifactSettings2["name"].(string)) - artifactProperties.Type = aws.String(artifactSettings2["type"].(string)) - url := aws.String(artifactSettings2["load_template_from_url"].(string)) + artifactProperties.Description = aws.String(artifactSettings["description"].(string)) + artifactProperties.Name = aws.String(artifactSettings["name"].(string)) + artifactProperties.Type = aws.String("CLOUD_FORMATION_TEMPLATE") + + url := aws.String(artifactSettings["load_template_from_url"].(string)) info := map[string]*string{ "LoadTemplateFromURL": url, } @@ -177,6 +169,8 @@ func resourceAwsServiceCatalogProductCreate(d *schema.ResourceData, meta interfa return fmt.Errorf("Creating ServiceCatalog product failed: %s", err.Error()) } d.SetId(*resp.ProductViewDetail.ProductViewSummary.ProductId) + // Debuging + // fmt.Printf("Resp: %#v", resp) return resourceAwsServiceCatalogProductRead(d, meta) } @@ -214,26 +208,18 @@ func resourceAwsServiceCatalogProductRead(d *schema.ResourceData, meta interface d.Set("support_url", pvs.SupportUrl) provisioningArtifactSummary := getProvisioningArtifactSummary(resp) - d.Set("artifact", provisioningArtifactSummary) - return nil -} + a := make([]map[string]interface{}, 0, 1) + artifact := make(map[string]interface{}) + artifact["description"] = *provisioningArtifactSummary.Description + artifact["id"] = *provisioningArtifactSummary.Id + artifact["name"] = *provisioningArtifactSummary.Name + a = append(a, artifact) + if err := d.Set("artifact", a); err != nil { + return err + } -/* - //a := getArtifactMap(provisioningArtifactSummary) -WIP -func getArtifactMap(p *servicecatalog.ProvisioningArtifactSummary) *schema.Set { - //r := map[string]string{} - //r["description"] = *p.Description - //r["id"] = *p.Id - //r["name"] = *p.Name - r := map[string]interface{}{} - r["description"] = *p.Description - r["id"] = *p.Id - r["name"] = *p.Name - //return r.(map[string]interface{}) - return r.(schema.Set) + return nil } -*/ // Lookup the first artifact, which was the one created on inital build, by comparing created at time func getProvisioningArtifactSummary(resp *servicecatalog.DescribeProductAsAdminOutput) *servicecatalog.ProvisioningArtifactSummary { diff --git a/aws/resource_aws_servicecatalog_product_test.go b/aws/resource_aws_servicecatalog_product_test.go index a12348d4b071..9578804e5a5c 100644 --- a/aws/resource_aws_servicecatalog_product_test.go +++ b/aws/resource_aws_servicecatalog_product_test.go @@ -1,19 +1,17 @@ package aws import ( - "github.com/aws/aws-sdk-go/aws" + "bytes" + "fmt" + "testing" + "text/template" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/servicecatalog" "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" - - "fmt" - "testing" - - "bytes" - "text/template" ) func TestAccAWSServiceCatalogProductBasic(t *testing.T) { @@ -243,7 +241,6 @@ resource "aws_servicecatalog_product" "test" { description = "ad" name = "an" load_template_from_url = "https://s3-${var.region}.amazonaws.com/${aws_s3_bucket.bucket.id}/${aws_s3_bucket_object.template.key}" - type = "CLOUD_FORMATION_TEMPLATE" } } ` From bfa2bc20d42a4ee2d9c5648a6228feb4d0ffbea0 Mon Sep 17 00:00:00 2001 From: Brett W Date: Mon, 13 Nov 2017 06:48:12 -0800 Subject: [PATCH 04/13] minor code cleanup --- aws/resource_aws_servicecatalog_product.go | 13 ++++++------- aws/resource_aws_servicecatalog_product_test.go | 1 - 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/aws/resource_aws_servicecatalog_product.go b/aws/resource_aws_servicecatalog_product.go index 0c7a73aec82d..ef7094729826 100644 --- a/aws/resource_aws_servicecatalog_product.go +++ b/aws/resource_aws_servicecatalog_product.go @@ -91,7 +91,8 @@ func resourceAwsServiceCatalogProduct() *schema.Resource { Type: schema.TypeString, Required: true, DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - if d.IsNewResource() { + // Only check for a diff on initial create. + if d.Id() != "" { return true } return false @@ -169,8 +170,6 @@ func resourceAwsServiceCatalogProductCreate(d *schema.ResourceData, meta interfa return fmt.Errorf("Creating ServiceCatalog product failed: %s", err.Error()) } d.SetId(*resp.ProductViewDetail.ProductViewSummary.ProductId) - // Debuging - // fmt.Printf("Resp: %#v", resp) return resourceAwsServiceCatalogProductRead(d, meta) } @@ -194,10 +193,9 @@ func resourceAwsServiceCatalogProductRead(d *schema.ResourceData, meta interface if err := d.Set("created_time", resp.ProductViewDetail.CreatedTime.Format(time.RFC3339)); err != nil { log.Printf("[DEBUG] Error setting created_time: %s", err) } - d.Set("product_arn", resp.ProductViewDetail.ProductARN) - pvs := resp.ProductViewDetail.ProductViewSummary + pvs := resp.ProductViewDetail.ProductViewSummary d.Set("description", pvs.ShortDescription) d.Set("distributor", pvs.Distributor) d.Set("name", pvs.Name) @@ -208,16 +206,17 @@ func resourceAwsServiceCatalogProductRead(d *schema.ResourceData, meta interface d.Set("support_url", pvs.SupportUrl) provisioningArtifactSummary := getProvisioningArtifactSummary(resp) - a := make([]map[string]interface{}, 0, 1) + var a []map[string]interface{} artifact := make(map[string]interface{}) artifact["description"] = *provisioningArtifactSummary.Description artifact["id"] = *provisioningArtifactSummary.Id + artifact["load_template_from_url"] = "only_used_on_initial_create" artifact["name"] = *provisioningArtifactSummary.Name a = append(a, artifact) + if err := d.Set("artifact", a); err != nil { return err } - return nil } diff --git a/aws/resource_aws_servicecatalog_product_test.go b/aws/resource_aws_servicecatalog_product_test.go index 9578804e5a5c..91b8585a9ff2 100644 --- a/aws/resource_aws_servicecatalog_product_test.go +++ b/aws/resource_aws_servicecatalog_product_test.go @@ -119,7 +119,6 @@ func TestAccAWSServiceCatalogProductImport(t *testing.T) { resource.TestStep{ Config: template1.String(), }, - resource.TestStep{ ResourceName: resourceName, ImportState: true, From 92277067edc4faf507eb8140b02f712f6b2b9029 Mon Sep 17 00:00:00 2001 From: Brett W Date: Mon, 9 Apr 2018 15:04:50 -0700 Subject: [PATCH 05/13] update based on code review --- aws/resource_aws_servicecatalog_product.go | 42 ++++++------------- ...esource_aws_servicecatalog_product_test.go | 26 +++++++----- 2 files changed, 27 insertions(+), 41 deletions(-) diff --git a/aws/resource_aws_servicecatalog_product.go b/aws/resource_aws_servicecatalog_product.go index ef7094729826..d057ad23bf39 100644 --- a/aws/resource_aws_servicecatalog_product.go +++ b/aws/resource_aws_servicecatalog_product.go @@ -7,7 +7,6 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/service/servicecatalog" "github.com/hashicorp/terraform/helper/schema" ) @@ -23,28 +22,14 @@ func resourceAwsServiceCatalogProduct() *schema.Resource { State: schema.ImportStatePassthrough, }, - Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(30 * time.Minute), - Update: schema.DefaultTimeout(30 * time.Minute), - Delete: schema.DefaultTimeout(30 * time.Minute), - }, - Schema: map[string]*schema.Schema{ - "created_time": { - Type: schema.TypeString, - Computed: true, - }, "description": { Type: schema.TypeString, - Required: true, + Optional: true, }, "distributor": { Type: schema.TypeString, - Required: true, - }, - "id": { - Type: schema.TypeString, - Computed: true, + Optional: true, }, "name": { Type: schema.TypeString, @@ -64,17 +49,17 @@ func resourceAwsServiceCatalogProduct() *schema.Resource { }, "support_description": { Type: schema.TypeString, - Required: true, + Optional: true, }, "support_email": { Type: schema.TypeString, - Required: true, + Optional: true, }, "support_url": { Type: schema.TypeString, - Required: true, + Optional: true, }, - "artifact": { + "provisioning_artifact": { Type: schema.TypeSet, Required: true, Elem: &schema.Resource{ @@ -151,7 +136,7 @@ func resourceAwsServiceCatalogProductCreate(d *schema.ResourceData, meta interfa input.SupportUrl = aws.String(v.(string)) } - artifactSettings := d.Get("artifact").(*schema.Set).List()[0].(map[string]interface{}) + artifactSettings := d.Get("provisioning_artifact").(*schema.Set).List()[0].(map[string]interface{}) artifactProperties := servicecatalog.ProvisioningArtifactProperties{} artifactProperties.Description = aws.String(artifactSettings["description"].(string)) artifactProperties.Name = aws.String(artifactSettings["name"].(string)) @@ -164,7 +149,7 @@ func resourceAwsServiceCatalogProductCreate(d *schema.ResourceData, meta interfa artifactProperties.Info = info input.SetProvisioningArtifactParameters(&artifactProperties) - log.Printf("[DEBUG] Creating Service Catalog Product: %#v %#v", input, artifactProperties) + log.Printf("[DEBUG] Creating Service Catalog Product: %s %s", input, artifactProperties) resp, err := conn.CreateProduct(&input) if err != nil { return fmt.Errorf("Creating ServiceCatalog product failed: %s", err.Error()) @@ -179,7 +164,7 @@ func resourceAwsServiceCatalogProductRead(d *schema.ResourceData, meta interface input := servicecatalog.DescribeProductAsAdminInput{} input.Id = aws.String(d.Id()) - log.Printf("[DEBUG] Reading Service Catalog Product: %#v", input) + log.Printf("[DEBUG] Reading Service Catalog Product: %s", input) resp, err := conn.DescribeProductAsAdmin(&input) if err != nil { if scErr, ok := err.(awserr.Error); ok && scErr.Code() == "ResourceNotFoundException" { @@ -190,9 +175,6 @@ func resourceAwsServiceCatalogProductRead(d *schema.ResourceData, meta interface return fmt.Errorf("Reading ServiceCatalog product '%s' failed: %s", *input.Id, err.Error()) } - if err := d.Set("created_time", resp.ProductViewDetail.CreatedTime.Format(time.RFC3339)); err != nil { - log.Printf("[DEBUG] Error setting created_time: %s", err) - } d.Set("product_arn", resp.ProductViewDetail.ProductARN) pvs := resp.ProductViewDetail.ProductViewSummary @@ -214,7 +196,7 @@ func resourceAwsServiceCatalogProductRead(d *schema.ResourceData, meta interface artifact["name"] = *provisioningArtifactSummary.Name a = append(a, artifact) - if err := d.Set("artifact", a); err != nil { + if err := d.Set("provisioning_artifact", a); err != nil { return err } return nil @@ -271,7 +253,7 @@ func resourceAwsServiceCatalogProductUpdate(d *schema.ResourceData, meta interfa input.SupportUrl = aws.String(v.(string)) } - log.Printf("[DEBUG] Update Service Catalog Product: %#v", input) + log.Printf("[DEBUG] Update Service Catalog Product: %s", input) _, err := conn.UpdateProduct(&input) if err != nil { return fmt.Errorf("Updating ServiceCatalog product '%s' failed: %s", *input.Id, err.Error()) @@ -284,7 +266,7 @@ func resourceAwsServiceCatalogProductDelete(d *schema.ResourceData, meta interfa input := servicecatalog.DeleteProductInput{} input.Id = aws.String(d.Id()) - log.Printf("[DEBUG] Delete Service Catalog Product: %#v", input) + log.Printf("[DEBUG] Delete Service Catalog Product: %s", input) _, err := conn.DeleteProduct(&input) if err != nil { return fmt.Errorf("Deleting ServiceCatalog product '%s' failed: %s", *input.Id, err.Error()) diff --git a/aws/resource_aws_servicecatalog_product_test.go b/aws/resource_aws_servicecatalog_product_test.go index 91b8585a9ff2..e0ca4d1cb00e 100644 --- a/aws/resource_aws_servicecatalog_product_test.go +++ b/aws/resource_aws_servicecatalog_product_test.go @@ -59,8 +59,8 @@ func TestAccAWSServiceCatalogProductBasic(t *testing.T) { resource.TestStep{ Config: template3.String(), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "artifact.description", "ad"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "artifact.name", "an"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.description", "ad"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.name", "an"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "description", "dsc2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "distributor", "dst2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "name", name3), @@ -88,13 +88,13 @@ func TestAccAWSServiceCatalogProductDisappears(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, - CheckDestroy: testAccCheckServiceCatlaogProductDestroy, + CheckDestroy: testAccCheckServiceCatalogProductDestroy, Steps: []resource.TestStep{ resource.TestStep{ Config: template1.String(), Check: resource.ComposeTestCheckFunc( testAccCheckProduct("aws_servicecatalog_product.test", &productViewDetail), - testAccCheckServiceCatlaogProductDisappears(&productViewDetail), + testAccCheckServiceCatalogProductDisappears(&productViewDetail), ), ExpectNonEmptyPlan: true, }, @@ -114,7 +114,7 @@ func TestAccAWSServiceCatalogProductImport(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, - CheckDestroy: testAccCheckServiceCatlaogProductDestroy, + CheckDestroy: testAccCheckServiceCatalogProductDestroy, Steps: []resource.TestStep{ resource.TestStep{ Config: template1.String(), @@ -153,7 +153,7 @@ func testAccCheckProduct(pr string, pd *servicecatalog.ProductViewDetail) resour } } -func testAccCheckServiceCatlaogProductDisappears(pd *servicecatalog.ProductViewDetail) resource.TestCheckFunc { +func testAccCheckServiceCatalogProductDisappears(pd *servicecatalog.ProductViewDetail) resource.TestCheckFunc { return func(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).scconn @@ -169,20 +169,24 @@ func testAccCheckServiceCatlaogProductDisappears(pd *servicecatalog.ProductViewD } } -func testAccCheckServiceCatlaogProductDestroy(s *terraform.State) error { +func testAccCheckServiceCatalogProductDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).scconn for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_servicecatalog_portfolio" { + if rs.Type != "aws_servicecatalog_product" { continue } input := servicecatalog.DescribeProductInput{} input.Id = aws.String(rs.Primary.ID) _, err := conn.DescribeProduct(&input) - if err == nil { - return fmt.Errorf("Product still exists") + if err != nil { + if isAWSErr(err, servicecatalog.ErrCodeResourceNotFoundException, "") { + return nil + } + return err } + return fmt.Errorf("Product still exists") } return nil @@ -236,7 +240,7 @@ resource "aws_servicecatalog_product" "test" { support_email = "{{.SupportEmail}}" support_url = "{{.SupportUrl}}" - artifact { + provisioning_artifact { description = "ad" name = "an" load_template_from_url = "https://s3-${var.region}.amazonaws.com/${aws_s3_bucket.bucket.id}/${aws_s3_bucket_object.template.key}" From 9370c2df78f923c304a3a8c462f328a5af4d4979 Mon Sep 17 00:00:00 2001 From: Brett W Date: Tue, 10 Apr 2018 08:51:20 -0700 Subject: [PATCH 06/13] updates based on code review --- aws/resource_aws_servicecatalog_product.go | 9 +++--- ...esource_aws_servicecatalog_product_test.go | 32 +++++++++---------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/aws/resource_aws_servicecatalog_product.go b/aws/resource_aws_servicecatalog_product.go index d057ad23bf39..113a52eba7ef 100644 --- a/aws/resource_aws_servicecatalog_product.go +++ b/aws/resource_aws_servicecatalog_product.go @@ -6,7 +6,6 @@ import ( "time" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/servicecatalog" "github.com/hashicorp/terraform/helper/schema" ) @@ -46,6 +45,7 @@ func resourceAwsServiceCatalogProduct() *schema.Resource { "product_type": { Type: schema.TypeString, Required: true, + ForceNew: true, }, "support_description": { Type: schema.TypeString, @@ -161,13 +161,14 @@ func resourceAwsServiceCatalogProductCreate(d *schema.ResourceData, meta interfa func resourceAwsServiceCatalogProductRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).scconn - input := servicecatalog.DescribeProductAsAdminInput{} - input.Id = aws.String(d.Id()) + input := servicecatalog.DescribeProductAsAdminInput{ + Id: aws.String(d.Id()), + } log.Printf("[DEBUG] Reading Service Catalog Product: %s", input) resp, err := conn.DescribeProductAsAdmin(&input) if err != nil { - if scErr, ok := err.(awserr.Error); ok && scErr.Code() == "ResourceNotFoundException" { + if isAWSErr(err, servicecatalog.ErrCodeResourceNotFoundException, "") { log.Printf("[WARN] Service Catalog Product %q not found, removing from state", d.Id()) d.SetId("") return nil diff --git a/aws/resource_aws_servicecatalog_product_test.go b/aws/resource_aws_servicecatalog_product_test.go index e0ca4d1cb00e..788fc84e2b8f 100644 --- a/aws/resource_aws_servicecatalog_product_test.go +++ b/aws/resource_aws_servicecatalog_product_test.go @@ -205,18 +205,18 @@ type Input struct { const testAccCheckAwsServiceCatalogProductResourceConfigTemplate1 = ` data "aws_caller_identity" "current" {} -variable region { default = "us-west-2" } +data "aws_region" "current" {} resource "aws_s3_bucket" "bucket" { - bucket = "{{.BucketName}}" - region = "${var.region}" - acl = "private" + bucket = "{{.BucketName}}" + region = "${data.aws_region.current.name}" + acl = "private" force_destroy = true } resource "aws_s3_bucket_object" "template" { - bucket = "${aws_s3_bucket.bucket.id}" - key = "test_templates_for_terraform_sc_dev.json" + bucket = "${aws_s3_bucket.bucket.id}" + key = "test_templates_for_terraform_sc_dev.json" content = < Date: Thu, 3 May 2018 10:13:48 -0700 Subject: [PATCH 07/13] refactoring to updated template structure --- aws/resource_aws_servicecatalog_product.go | 84 +++++++++++++------ ...esource_aws_servicecatalog_product_test.go | 51 +++++++++-- 2 files changed, 101 insertions(+), 34 deletions(-) diff --git a/aws/resource_aws_servicecatalog_product.go b/aws/resource_aws_servicecatalog_product.go index 113a52eba7ef..877c38c68ca3 100644 --- a/aws/resource_aws_servicecatalog_product.go +++ b/aws/resource_aws_servicecatalog_product.go @@ -60,7 +60,8 @@ func resourceAwsServiceCatalogProduct() *schema.Resource { Optional: true, }, "provisioning_artifact": { - Type: schema.TypeSet, + Type: schema.TypeList, + MinItems: 1, Required: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -75,13 +76,6 @@ func resourceAwsServiceCatalogProduct() *schema.Resource { "load_template_from_url": { Type: schema.TypeString, Required: true, - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - // Only check for a diff on initial create. - if d.Id() != "" { - return true - } - return false - }, }, "name": { Type: schema.TypeString, @@ -97,9 +91,9 @@ func resourceAwsServiceCatalogProduct() *schema.Resource { func resourceAwsServiceCatalogProductCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).scconn input := servicecatalog.CreateProductInput{} + now := time.Now() if v, ok := d.GetOk("name"); ok { - now := time.Now() input.IdempotencyToken = aws.String(fmt.Sprintf("%d", now.UnixNano())) input.Name = aws.String(v.(string)) } @@ -136,26 +130,60 @@ func resourceAwsServiceCatalogProductCreate(d *schema.ResourceData, meta interfa input.SupportUrl = aws.String(v.(string)) } - artifactSettings := d.Get("provisioning_artifact").(*schema.Set).List()[0].(map[string]interface{}) - artifactProperties := servicecatalog.ProvisioningArtifactProperties{} - artifactProperties.Description = aws.String(artifactSettings["description"].(string)) - artifactProperties.Name = aws.String(artifactSettings["name"].(string)) - artifactProperties.Type = aws.String("CLOUD_FORMATION_TEMPLATE") + if pa, ok := d.GetOk("provisioning_artifact"); ok { + pvs := pa.([]interface{}) + v := pvs[0] + bd := v.(map[string]interface{}) + artifactProperties := servicecatalog.ProvisioningArtifactProperties{} + artifactProperties.Description = aws.String(bd["description"].(string)) + artifactProperties.Name = aws.String(bd["name"].(string)) + artifactProperties.Type = aws.String("CLOUD_FORMATION_TEMPLATE") + url := aws.String(bd["load_template_from_url"].(string)) + info := map[string]*string{ + "LoadTemplateFromURL": url, + } + artifactProperties.Info = info + input.IdempotencyToken = aws.String(fmt.Sprintf("%d", now.UnixNano())) + input.SetProvisioningArtifactParameters(&artifactProperties) + log.Printf("[DEBUG] Creating Service Catalog Product: %s %s", input, artifactProperties) - url := aws.String(artifactSettings["load_template_from_url"].(string)) - info := map[string]*string{ - "LoadTemplateFromURL": url, } - artifactProperties.Info = info - input.SetProvisioningArtifactParameters(&artifactProperties) - log.Printf("[DEBUG] Creating Service Catalog Product: %s %s", input, artifactProperties) resp, err := conn.CreateProduct(&input) if err != nil { return fmt.Errorf("Creating ServiceCatalog product failed: %s", err.Error()) } d.SetId(*resp.ProductViewDetail.ProductViewSummary.ProductId) + if pa, ok := d.GetOk("provisioning_artifact"); ok { + pvs := pa.([]interface{}) + if len(pvs) > 1 { + for _, pa := range pvs[1:len(pvs)] { + bd := pa.(map[string]interface{}) + parameters := servicecatalog.ProvisioningArtifactProperties{} + parameters.Description = aws.String(bd["description"].(string)) + parameters.Name = aws.String(bd["name"].(string)) + parameters.Type = aws.String("CLOUD_FORMATION_TEMPLATE") + + url := aws.String(bd["load_template_from_url"].(string)) + info := map[string]*string{ + "LoadTemplateFromURL": url, + } + parameters.Info = info + + cpai := servicecatalog.CreateProvisioningArtifactInput{} + cpai.Parameters = ¶meters + cpai.ProductId = aws.String(d.Id()) + cpai.IdempotencyToken = aws.String(fmt.Sprintf("%d", now.UnixNano())) + log.Printf("[DEBUG] Adding Service Catalog Provisioning Artifact : %s", cpai) + resp, err := conn.CreateProvisioningArtifact(&cpai) + if err != nil { + return fmt.Errorf("Creating ServiceCatalog provisioning artifact failed: %s %s", err.Error(), resp) + } + } + } + } + return resourceAwsServiceCatalogProductRead(d, meta) } @@ -188,14 +216,16 @@ func resourceAwsServiceCatalogProductRead(d *schema.ResourceData, meta interface d.Set("support_email", pvs.SupportEmail) d.Set("support_url", pvs.SupportUrl) - provisioningArtifactSummary := getProvisioningArtifactSummary(resp) var a []map[string]interface{} - artifact := make(map[string]interface{}) - artifact["description"] = *provisioningArtifactSummary.Description - artifact["id"] = *provisioningArtifactSummary.Id - artifact["load_template_from_url"] = "only_used_on_initial_create" - artifact["name"] = *provisioningArtifactSummary.Name - a = append(a, artifact) + for i, pas := range resp.ProvisioningArtifactSummaries { + artifact := make(map[string]interface{}) + artifact["description"] = *pas.Description + artifact["id"] = *pas.Id + template_url_key := fmt.Sprintf("provisioning_artifact.%d.load_template_from_url", i) + artifact["load_template_from_url"] = d.State().Attributes[template_url_key] + artifact["name"] = *pas.Name + a = append(a, artifact) + } if err := d.Set("provisioning_artifact", a); err != nil { return err diff --git a/aws/resource_aws_servicecatalog_product_test.go b/aws/resource_aws_servicecatalog_product_test.go index 788fc84e2b8f..c585fcc58369 100644 --- a/aws/resource_aws_servicecatalog_product_test.go +++ b/aws/resource_aws_servicecatalog_product_test.go @@ -33,6 +33,12 @@ func TestAccAWSServiceCatalogProductBasic(t *testing.T) { resource.TestStep{ Config: template1.String(), Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.#", "2"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.description", "ad1"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.name", "an1"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "description", "dsc2"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.description", "ad2"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.name", "an2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "description", "dsc1"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "distributor", "dst1"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "name", name1), @@ -46,6 +52,12 @@ func TestAccAWSServiceCatalogProductBasic(t *testing.T) { resource.TestStep{ Config: template2.String(), Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.#", "2"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.description", "ad1"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.name", "an1"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "description", "dsc2"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.description", "ad2"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.name", "an2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "description", "dsc2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "distributor", "dst2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "name", name2), @@ -59,9 +71,12 @@ func TestAccAWSServiceCatalogProductBasic(t *testing.T) { resource.TestStep{ Config: template3.String(), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.description", "ad"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.name", "an"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.#", "2"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.description", "ad1"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.name", "an1"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "description", "dsc2"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.description", "ad2"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.name", "an2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "distributor", "dst2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "name", name3), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "owner", "own2"), @@ -214,9 +229,25 @@ resource "aws_s3_bucket" "bucket" { force_destroy = true } -resource "aws_s3_bucket_object" "template" { +resource "aws_s3_bucket_object" "template1" { bucket = "${aws_s3_bucket.bucket.id}" - key = "test_templates_for_terraform_sc_dev.json" + key = "test_templates_for_terraform_sc_dev1.json" + content = < Date: Thu, 3 May 2018 10:24:27 -0700 Subject: [PATCH 08/13] fix test --- aws/resource_aws_servicecatalog_product_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/aws/resource_aws_servicecatalog_product_test.go b/aws/resource_aws_servicecatalog_product_test.go index c585fcc58369..6afa05484e46 100644 --- a/aws/resource_aws_servicecatalog_product_test.go +++ b/aws/resource_aws_servicecatalog_product_test.go @@ -36,7 +36,6 @@ func TestAccAWSServiceCatalogProductBasic(t *testing.T) { resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.#", "2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.description", "ad1"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.name", "an1"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "description", "dsc2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.description", "ad2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.name", "an2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "description", "dsc1"), @@ -55,7 +54,6 @@ func TestAccAWSServiceCatalogProductBasic(t *testing.T) { resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.#", "2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.description", "ad1"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.name", "an1"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "description", "dsc2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.description", "ad2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.name", "an2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "description", "dsc2"), From ea45424e78b117bcefb3cfabf060c9a9e6550c6c Mon Sep 17 00:00:00 2001 From: Brett W Date: Thu, 3 May 2018 10:34:44 -0700 Subject: [PATCH 09/13] minor code / test cleanup --- aws/resource_aws_servicecatalog_product.go | 11 ------- ...esource_aws_servicecatalog_product_test.go | 29 +++++++++---------- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/aws/resource_aws_servicecatalog_product.go b/aws/resource_aws_servicecatalog_product.go index 877c38c68ca3..a7e70fd2ec9b 100644 --- a/aws/resource_aws_servicecatalog_product.go +++ b/aws/resource_aws_servicecatalog_product.go @@ -233,17 +233,6 @@ func resourceAwsServiceCatalogProductRead(d *schema.ResourceData, meta interface return nil } -// Lookup the first artifact, which was the one created on inital build, by comparing created at time -func getProvisioningArtifactSummary(resp *servicecatalog.DescribeProductAsAdminOutput) *servicecatalog.ProvisioningArtifactSummary { - firstPas := resp.ProvisioningArtifactSummaries[0] - for _, pas := range resp.ProvisioningArtifactSummaries { - if pas.CreatedTime.UnixNano() < firstPas.CreatedTime.UnixNano() { - firstPas = pas - } - } - return firstPas -} - func resourceAwsServiceCatalogProductUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).scconn input := servicecatalog.UpdateProductInput{} diff --git a/aws/resource_aws_servicecatalog_product_test.go b/aws/resource_aws_servicecatalog_product_test.go index 6afa05484e46..11833d6bd4e5 100644 --- a/aws/resource_aws_servicecatalog_product_test.go +++ b/aws/resource_aws_servicecatalog_product_test.go @@ -33,16 +33,16 @@ func TestAccAWSServiceCatalogProductBasic(t *testing.T) { resource.TestStep{ Config: template1.String(), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.#", "2"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.description", "ad1"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.name", "an1"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.description", "ad2"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.name", "an2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "description", "dsc1"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "distributor", "dst1"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "name", name1), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "owner", "own1"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "product_type", "CLOUD_FORMATION_TEMPLATE"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.#", "2"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.description", "ad1"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.name", "an1"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.description", "ad2"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.name", "an2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_description", "sd1"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_email", "a@b.com"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_url", "https://url/support1.html"), @@ -51,16 +51,16 @@ func TestAccAWSServiceCatalogProductBasic(t *testing.T) { resource.TestStep{ Config: template2.String(), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.#", "2"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.description", "ad1"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.name", "an1"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.description", "ad2"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.name", "an2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "description", "dsc2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "distributor", "dst2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "name", name2), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "owner", "own2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "product_type", "CLOUD_FORMATION_TEMPLATE"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.#", "2"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.description", "ad1"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.name", "an1"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.description", "ad2"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.name", "an2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_description", "sd2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_email", "c@d.com"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_url", "https://url/support2.html"), @@ -69,16 +69,15 @@ func TestAccAWSServiceCatalogProductBasic(t *testing.T) { resource.TestStep{ Config: template3.String(), Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "distributor", "dst2"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "name", name3), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "owner", "own2"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "product_type", "CLOUD_FORMATION_TEMPLATE"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.#", "2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.description", "ad1"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.name", "an1"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "description", "dsc2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.description", "ad2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.name", "an2"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "distributor", "dst2"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "name", name3), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "owner", "own2"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "product_type", "CLOUD_FORMATION_TEMPLATE"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_description", "sd2"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_email", "c@d.com"), resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_url", "https://url/support2.html"), From 051a1a0b4012b1aa5a321f159c375a9ffee708e4 Mon Sep 17 00:00:00 2001 From: Brett W Date: Thu, 3 May 2018 10:36:51 -0700 Subject: [PATCH 10/13] adding comment --- aws/resource_aws_servicecatalog_product.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_servicecatalog_product.go b/aws/resource_aws_servicecatalog_product.go index a7e70fd2ec9b..baa511421e07 100644 --- a/aws/resource_aws_servicecatalog_product.go +++ b/aws/resource_aws_servicecatalog_product.go @@ -222,7 +222,7 @@ func resourceAwsServiceCatalogProductRead(d *schema.ResourceData, meta interface artifact["description"] = *pas.Description artifact["id"] = *pas.Id template_url_key := fmt.Sprintf("provisioning_artifact.%d.load_template_from_url", i) - artifact["load_template_from_url"] = d.State().Attributes[template_url_key] + artifact["load_template_from_url"] = d.State().Attributes[template_url_key] // loading template url from state as it is not available from API artifact["name"] = *pas.Name a = append(a, artifact) } From 51790047fd137b5f2796d8ba2951e17c0d4e92dc Mon Sep 17 00:00:00 2001 From: Brett W Date: Thu, 3 May 2018 10:57:52 -0700 Subject: [PATCH 11/13] reading template url, fix import --- aws/resource_aws_servicecatalog_product.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/aws/resource_aws_servicecatalog_product.go b/aws/resource_aws_servicecatalog_product.go index baa511421e07..2b28e0cd2e98 100644 --- a/aws/resource_aws_servicecatalog_product.go +++ b/aws/resource_aws_servicecatalog_product.go @@ -217,13 +217,21 @@ func resourceAwsServiceCatalogProductRead(d *schema.ResourceData, meta interface d.Set("support_url", pvs.SupportUrl) var a []map[string]interface{} - for i, pas := range resp.ProvisioningArtifactSummaries { + for _, pas := range resp.ProvisioningArtifactSummaries { artifact := make(map[string]interface{}) artifact["description"] = *pas.Description artifact["id"] = *pas.Id - template_url_key := fmt.Sprintf("provisioning_artifact.%d.load_template_from_url", i) - artifact["load_template_from_url"] = d.State().Attributes[template_url_key] // loading template url from state as it is not available from API artifact["name"] = *pas.Name + + i := servicecatalog.DescribeProvisioningArtifactInput{ + ProductId: aws.String(d.Id()), + ProvisioningArtifactId: aws.String(*pas.Id), + } + dpao, err := conn.DescribeProvisioningArtifact(&i) + if err != nil { + return err + } + artifact["load_template_from_url"] = *dpao.Info["TemplateUrl"] a = append(a, artifact) } From 88f81f02425e7126ac1c5aeb6e53f1559f8ba5ba Mon Sep 17 00:00:00 2001 From: Trung Nguyen Date: Thu, 21 Jun 2018 13:30:52 -0400 Subject: [PATCH 12/13] refactored, added acceptance tests and documentation --- aws/resource_aws_servicecatalog_product.go | 236 +++++++++----- ...esource_aws_servicecatalog_product_test.go | 295 ++++++++---------- aws/tagsServiceCatalog.go | 30 ++ website/aws.erb | 3 + .../r/servicecatalog_portfolio.html.markdown | 0 .../r/servicecatalog_product.html.markdown | 78 +++++ 6 files changed, 390 insertions(+), 252 deletions(-) mode change 100644 => 100755 aws/resource_aws_servicecatalog_product.go mode change 100644 => 100755 aws/resource_aws_servicecatalog_product_test.go create mode 100755 aws/tagsServiceCatalog.go mode change 100644 => 100755 website/aws.erb mode change 100644 => 100755 website/docs/r/servicecatalog_portfolio.html.markdown create mode 100755 website/docs/r/servicecatalog_product.html.markdown diff --git a/aws/resource_aws_servicecatalog_product.go b/aws/resource_aws_servicecatalog_product.go old mode 100644 new mode 100755 index 2b28e0cd2e98..05225189472f --- a/aws/resource_aws_servicecatalog_product.go +++ b/aws/resource_aws_servicecatalog_product.go @@ -7,6 +7,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/servicecatalog" + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" ) @@ -21,6 +22,10 @@ func resourceAwsServiceCatalogProduct() *schema.Resource { State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(15 * time.Minute), + }, + Schema: map[string]*schema.Schema{ "description": { Type: schema.TypeString, @@ -42,6 +47,10 @@ func resourceAwsServiceCatalogProduct() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "has_default_path": { + Type: schema.TypeBool, + Computed: true, + }, "product_type": { Type: schema.TypeString, Required: true, @@ -61,29 +70,46 @@ func resourceAwsServiceCatalogProduct() *schema.Resource { }, "provisioning_artifact": { Type: schema.TypeList, - MinItems: 1, + MaxItems: 1, Required: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "description": { + Type: schema.TypeString, + Optional: true, + }, + "info": { + Type: schema.TypeMap, + Required: true, + Elem: schema.TypeString, + ForceNew: true, + }, + "name": { Type: schema.TypeString, Required: true, }, + "type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: servicecatalog.ProvisioningArtifactTypeCloudFormationTemplate, + }, // CLOUD_FORMATION_TEMPLATE | MARKETPLACE_AMI | MARKETPLACE_CAR "id": { Type: schema.TypeString, Computed: true, }, - "load_template_from_url": { - Type: schema.TypeString, - Required: true, + "active": { + Type: schema.TypeBool, + Computed: true, }, - "name": { + "created_time": { Type: schema.TypeString, - Required: true, + Computed: true, }, }, }, }, + "tags": tagsSchema(), }, } } @@ -94,7 +120,6 @@ func resourceAwsServiceCatalogProductCreate(d *schema.ResourceData, meta interfa now := time.Now() if v, ok := d.GetOk("name"); ok { - input.IdempotencyToken = aws.String(fmt.Sprintf("%d", now.UnixNano())) input.Name = aws.String(v.(string)) } @@ -130,113 +155,116 @@ func resourceAwsServiceCatalogProductCreate(d *schema.ResourceData, meta interfa input.SupportUrl = aws.String(v.(string)) } - if pa, ok := d.GetOk("provisioning_artifact"); ok { - pvs := pa.([]interface{}) - v := pvs[0] - bd := v.(map[string]interface{}) - artifactProperties := servicecatalog.ProvisioningArtifactProperties{} - artifactProperties.Description = aws.String(bd["description"].(string)) - artifactProperties.Name = aws.String(bd["name"].(string)) - artifactProperties.Type = aws.String("CLOUD_FORMATION_TEMPLATE") - url := aws.String(bd["load_template_from_url"].(string)) - info := map[string]*string{ - "LoadTemplateFromURL": url, - } - artifactProperties.Info = info - input.IdempotencyToken = aws.String(fmt.Sprintf("%d", now.UnixNano())) - input.SetProvisioningArtifactParameters(&artifactProperties) - log.Printf("[DEBUG] Creating Service Catalog Product: %s %s", input, artifactProperties) + if v, ok := d.GetOk("tags"); ok { + input.Tags = tagsFromMapServiceCatalog(v.(map[string]interface{})) + } + pa := d.Get("provisioning_artifact") + paList := pa.([]interface{}) + paParameters := paList[0].(map[string]interface{}) + artifactProperties := servicecatalog.ProvisioningArtifactProperties{} + artifactProperties.Description = aws.String(paParameters["description"].(string)) + artifactProperties.Name = aws.String(paParameters["name"].(string)) + if v, ok := paParameters["type"]; ok && v != "" { + artifactProperties.Type = aws.String(v.(string)) + } else { + artifactProperties.Type = aws.String(servicecatalog.ProvisioningArtifactTypeCloudFormationTemplate) } + artifactProperties.Info = make(map[string]*string) + for k, v := range paParameters["info"].(map[string]interface{}) { + artifactProperties.Info[k] = aws.String(v.(string)) + } + input.IdempotencyToken = aws.String(fmt.Sprintf("%d", now.UnixNano())) + input.SetProvisioningArtifactParameters(&artifactProperties) + log.Printf("[DEBUG] Creating Service Catalog Product: %s %s", input, artifactProperties) resp, err := conn.CreateProduct(&input) if err != nil { - return fmt.Errorf("Creating ServiceCatalog product failed: %s", err.Error()) + return fmt.Errorf("creating ServiceCatalog product failed: %s", err) } - d.SetId(*resp.ProductViewDetail.ProductViewSummary.ProductId) - - if pa, ok := d.GetOk("provisioning_artifact"); ok { - pvs := pa.([]interface{}) - if len(pvs) > 1 { - for _, pa := range pvs[1:len(pvs)] { - bd := pa.(map[string]interface{}) - parameters := servicecatalog.ProvisioningArtifactProperties{} - parameters.Description = aws.String(bd["description"].(string)) - parameters.Name = aws.String(bd["name"].(string)) - parameters.Type = aws.String("CLOUD_FORMATION_TEMPLATE") - - url := aws.String(bd["load_template_from_url"].(string)) - info := map[string]*string{ - "LoadTemplateFromURL": url, - } - parameters.Info = info - - cpai := servicecatalog.CreateProvisioningArtifactInput{} - cpai.Parameters = ¶meters - cpai.ProductId = aws.String(d.Id()) - cpai.IdempotencyToken = aws.String(fmt.Sprintf("%d", now.UnixNano())) - log.Printf("[DEBUG] Adding Service Catalog Provisioning Artifact : %s", cpai) - resp, err := conn.CreateProvisioningArtifact(&cpai) - if err != nil { - return fmt.Errorf("Creating ServiceCatalog provisioning artifact failed: %s %s", err.Error(), resp) - } + productId := aws.StringValue(resp.ProductViewDetail.ProductViewSummary.ProductId) + + waitForCreated := &resource.StateChangeConf{ + Target: []string{"CREATED", servicecatalog.StatusAvailable}, + Refresh: func() (result interface{}, state string, err error) { + resp, err := conn.DescribeProductAsAdmin(&servicecatalog.DescribeProductAsAdminInput{ + Id: aws.String(productId), + }) + if err != nil { + return nil, "", err } - } + return resp, aws.StringValue(resp.ProductViewDetail.Status), nil + }, + Timeout: d.Timeout(schema.TimeoutCreate), + PollInterval: 3 * time.Second, + } + if _, err := waitForCreated.WaitForState(); err != nil { + return err } + d.SetId(productId) + return resourceAwsServiceCatalogProductRead(d, meta) } func resourceAwsServiceCatalogProductRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).scconn - input := servicecatalog.DescribeProductAsAdminInput{ + log.Printf("[DEBUG] Reading Service Catalog Product with id %s", d.Id()) + resp, err := conn.DescribeProductAsAdmin(&servicecatalog.DescribeProductAsAdminInput{ Id: aws.String(d.Id()), - } - - log.Printf("[DEBUG] Reading Service Catalog Product: %s", input) - resp, err := conn.DescribeProductAsAdmin(&input) + }) if err != nil { if isAWSErr(err, servicecatalog.ErrCodeResourceNotFoundException, "") { log.Printf("[WARN] Service Catalog Product %q not found, removing from state", d.Id()) d.SetId("") return nil } - return fmt.Errorf("Reading ServiceCatalog product '%s' failed: %s", *input.Id, err.Error()) + return fmt.Errorf("reading ServiceCatalog product '%s' failed: %s", d.Id(), err) } d.Set("product_arn", resp.ProductViewDetail.ProductARN) - - pvs := resp.ProductViewDetail.ProductViewSummary - d.Set("description", pvs.ShortDescription) - d.Set("distributor", pvs.Distributor) - d.Set("name", pvs.Name) - d.Set("owner", pvs.Owner) - d.Set("product_type", pvs.Type) - d.Set("support_description", pvs.SupportDescription) - d.Set("support_email", pvs.SupportEmail) - d.Set("support_url", pvs.SupportUrl) - - var a []map[string]interface{} + d.Set("tags", tagsToMapServiceCatalog(resp.Tags)) + + product := resp.ProductViewDetail.ProductViewSummary + d.Set("has_default_path", aws.BoolValue(product.HasDefaultPath)) + d.Set("description", aws.StringValue(product.ShortDescription)) + d.Set("distributor", aws.StringValue(product.Distributor)) + d.Set("name", aws.StringValue(product.Name)) + d.Set("owner", aws.StringValue(product.Owner)) + d.Set("product_type", aws.StringValue(product.Type)) + d.Set("support_description", aws.StringValue(product.SupportDescription)) + d.Set("support_email", aws.StringValue(product.SupportEmail)) + d.Set("support_url", aws.StringValue(product.SupportUrl)) + + provisioningArtifactList := make([]map[string]interface{}, 0) for _, pas := range resp.ProvisioningArtifactSummaries { artifact := make(map[string]interface{}) artifact["description"] = *pas.Description artifact["id"] = *pas.Id artifact["name"] = *pas.Name - i := servicecatalog.DescribeProvisioningArtifactInput{ + paOutput, err := conn.DescribeProvisioningArtifact(&servicecatalog.DescribeProvisioningArtifactInput{ ProductId: aws.String(d.Id()), - ProvisioningArtifactId: aws.String(*pas.Id), - } - dpao, err := conn.DescribeProvisioningArtifact(&i) + ProvisioningArtifactId: pas.Id, + }) if err != nil { - return err + return fmt.Errorf("reading ProvisioningArtifact '%s' for product '%s' failed: %s", *pas.Id, d.Id(), err) + } + artifact["type"] = aws.StringValue(paOutput.ProvisioningArtifactDetail.Type) + artifact["active"] = aws.BoolValue(paOutput.ProvisioningArtifactDetail.Active) + artifact["created_time"] = paOutput.ProvisioningArtifactDetail.CreatedTime.Format(time.RFC3339) + replaceProvisioningArtifactParametersKeys(paOutput.Info) + log.Printf("[DEBUG] Info map coming from READ: %v", paOutput.Info) + info := make(map[string]string) + for k, v := range paOutput.Info { + info[k] = aws.StringValue(v) } - artifact["load_template_from_url"] = *dpao.Info["TemplateUrl"] - a = append(a, artifact) + artifact["info"] = info + provisioningArtifactList = append(provisioningArtifactList, artifact) } - if err := d.Set("provisioning_artifact", a); err != nil { - return err + if err := d.Set("provisioning_artifact", provisioningArtifactList); err != nil { + return fmt.Errorf("setting ProvisioningArtifact for product '%s' failed: %s", d.Id(), err) } return nil } @@ -281,11 +309,47 @@ func resourceAwsServiceCatalogProductUpdate(d *schema.ResourceData, meta interfa input.SupportUrl = aws.String(v.(string)) } + // figure out what tags to add and what tags to remove + if d.HasChange("tags") { + oldTags, newTags := d.GetChange("tags") + removeTags := make([]*string, 0) + for k := range oldTags.(map[string]interface{}) { + if _, ok := (newTags.(map[string]interface{}))[k]; !ok { + removeTags = append(removeTags, &k) + } + } + addTags := make(map[string]interface{}) + for k, v := range newTags.(map[string]interface{}) { + if _, ok := (oldTags.(map[string]interface{}))[k]; !ok { + addTags[k] = v + } + } + input.AddTags = tagsFromMapServiceCatalog(addTags) + input.RemoveTags = removeTags + } + log.Printf("[DEBUG] Update Service Catalog Product: %s", input) _, err := conn.UpdateProduct(&input) if err != nil { - return fmt.Errorf("Updating ServiceCatalog product '%s' failed: %s", *input.Id, err.Error()) + return fmt.Errorf("updating ServiceCatalog product '%s' failed: %s", *input.Id, err) + } + + // this change is slightly more complicated as basically we need to update the provisioning artifact + if d.HasChange("provisioning_artifact") { + _, newProvisioningArtifactList := d.GetChange("provisioning_artifact") + newProvisioningArtifact := (newProvisioningArtifactList.([]interface{}))[0].(map[string]interface{}) + paId := newProvisioningArtifact["id"].(string) + _, err := conn.UpdateProvisioningArtifact(&servicecatalog.UpdateProvisioningArtifactInput{ + ProductId: aws.String(d.Id()), + ProvisioningArtifactId: aws.String(paId), + Name: aws.String(newProvisioningArtifact["name"].(string)), + Description: aws.String(newProvisioningArtifact["description"].(string)), + }) + if err != nil { + return fmt.Errorf("unable to update provisioning artifact %s for product %s due to %s", d.Id(), paId, err) + } } + return resourceAwsServiceCatalogProductRead(d, meta) } @@ -297,7 +361,17 @@ func resourceAwsServiceCatalogProductDelete(d *schema.ResourceData, meta interfa log.Printf("[DEBUG] Delete Service Catalog Product: %s", input) _, err := conn.DeleteProduct(&input) if err != nil { - return fmt.Errorf("Deleting ServiceCatalog product '%s' failed: %s", *input.Id, err.Error()) + return fmt.Errorf("deleting ServiceCatalog product '%s' failed: %s", *input.Id, err) } return nil } + +// this is to workaround the issue of the `info` map which contains different keys between user-provided and READ operation +func replaceProvisioningArtifactParametersKeys(m map[string]*string) { + replaceProvisioningArtifactParametersKey(m, "TemplateUrl", "LoadTemplateFromURL") +} + +func replaceProvisioningArtifactParametersKey(m map[string]*string, replacedKey, withKey string) { + m[withKey] = m[replacedKey] + delete(m, replacedKey) +} diff --git a/aws/resource_aws_servicecatalog_product_test.go b/aws/resource_aws_servicecatalog_product_test.go old mode 100644 new mode 100755 index 11833d6bd4e5..08ddb42c9e56 --- a/aws/resource_aws_servicecatalog_product_test.go +++ b/aws/resource_aws_servicecatalog_product_test.go @@ -1,10 +1,8 @@ package aws import ( - "bytes" "fmt" "testing" - "text/template" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/servicecatalog" @@ -14,124 +12,146 @@ import ( "github.com/hashicorp/terraform/terraform" ) -func TestAccAWSServiceCatalogProductBasic(t *testing.T) { - name1 := acctest.RandString(5) - name2 := acctest.RandString(5) - name3 := acctest.RandString(5) - bucketName := acctest.RandString(16) - - template := template.Must(template.New("hcl").Parse(testAccCheckAwsServiceCatalogProductResourceConfigTemplate1)) - var template1, template2, template3 bytes.Buffer - template.Execute(&template1, Input{"dsc1", "dst1", name1, bucketName, "own1", "sd1", "a@b.com", "https://url/support1.html"}) - template.Execute(&template2, Input{"dsc2", "dst2", name2, bucketName, "own2", "sd2", "c@d.com", "https://url/support2.html"}) - template.Execute(&template3, Input{"dsc2", "dst2", name3, bucketName, "own2", "sd2", "c@d.com", "https://url/support2.html"}) +func TestAccAWSServiceCatalogProduct_basic(t *testing.T) { + arbitraryProductName := fmt.Sprintf("product-%s", acctest.RandString(5)) + arbitraryProvisionArtifactName := fmt.Sprintf("pa-%s", acctest.RandString(5)) + arbitraryBucketName := fmt.Sprintf("bucket-%s", acctest.RandString(16)) + tag1 := "FooKey = \"bar\"" + tag2 := "BarKey = \"foo\"" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, Steps: []resource.TestStep{ - resource.TestStep{ - Config: template1.String(), + { + Config: testAccCheckAwsServiceCatalogProductResourceConfigTemplate(arbitraryBucketName, arbitraryProductName, arbitraryProvisionArtifactName, tag1, tag2), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "description", "dsc1"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "distributor", "dst1"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "name", name1), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "owner", "own1"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "product_type", "CLOUD_FORMATION_TEMPLATE"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.#", "2"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.description", "ad1"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.name", "an1"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.description", "ad2"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.name", "an2"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_description", "sd1"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_email", "a@b.com"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_url", "https://url/support1.html"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "name", arbitraryProductName), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.#", "1"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.name", arbitraryProvisionArtifactName), + resource.TestCheckResourceAttrSet("aws_servicecatalog_product.test", "provisioning_artifact.0.description"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "tags.%", "2"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "tags.FooKey", "bar"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "tags.BarKey", "foo"), + resource.TestCheckResourceAttrSet("aws_servicecatalog_product.test", "description"), + resource.TestCheckResourceAttrSet("aws_servicecatalog_product.test", "distributor"), + resource.TestCheckResourceAttrSet("aws_servicecatalog_product.test", "owner"), + resource.TestCheckResourceAttrSet("aws_servicecatalog_product.test", "product_type"), + resource.TestCheckResourceAttrSet("aws_servicecatalog_product.test", "support_description"), + resource.TestCheckResourceAttrSet("aws_servicecatalog_product.test", "support_email"), + resource.TestCheckResourceAttrSet("aws_servicecatalog_product.test", "support_url"), ), }, - resource.TestStep{ - Config: template2.String(), + }, + }) +} + +func TestAccAWSServiceCatalogProduct_updateTags(t *testing.T) { + arbitraryProductName := fmt.Sprintf("product-%s", acctest.RandString(5)) + arbitraryProvisionArtifactName := fmt.Sprintf("pa-%s", acctest.RandString(5)) + arbitraryBucketName := fmt.Sprintf("bucket-%s", acctest.RandString(16)) + tag1 := "FooKey = \"bar\"" + tag2 := "BarKey = \"foo\"" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckAwsServiceCatalogProductResourceConfigTemplate(arbitraryBucketName, arbitraryProductName, arbitraryProvisionArtifactName, tag1, ""), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "description", "dsc2"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "distributor", "dst2"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "name", name2), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "owner", "own2"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "product_type", "CLOUD_FORMATION_TEMPLATE"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.#", "2"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.description", "ad1"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.name", "an1"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.description", "ad2"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.name", "an2"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_description", "sd2"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_email", "c@d.com"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_url", "https://url/support2.html"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "tags.%", "1"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "tags.FooKey", "bar"), ), }, - resource.TestStep{ - Config: template3.String(), + { + Config: testAccCheckAwsServiceCatalogProductResourceConfigTemplate(arbitraryBucketName, arbitraryProductName, arbitraryProvisionArtifactName, "", tag2), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "distributor", "dst2"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "name", name3), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "owner", "own2"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "product_type", "CLOUD_FORMATION_TEMPLATE"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.#", "2"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.description", "ad1"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.name", "an1"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.description", "ad2"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.1.name", "an2"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_description", "sd2"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_email", "c@d.com"), - resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "support_url", "https://url/support2.html"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "tags.%", "1"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "tags.BarKey", "foo"), ), }, }, }) } -func TestAccAWSServiceCatalogProductDisappears(t *testing.T) { - var productViewDetail servicecatalog.ProductViewDetail - var template1 bytes.Buffer +func TestAccAWSServiceCatalogProduct_updateProvisioningArtifactBasic(t *testing.T) { + arbitraryProductName := fmt.Sprintf("product-%s", acctest.RandString(5)) + arbitraryProvisionArtifactName := fmt.Sprintf("pa-%s", acctest.RandString(5)) + arbitraryBucketName := fmt.Sprintf("bucket-%s", acctest.RandString(16)) + newArbitraryProvisionArtifactName := fmt.Sprintf("pa-new-%s", acctest.RandString(5)) + tag1 := "FooKey = \"bar\"" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckAwsServiceCatalogProductResourceConfigTemplate(arbitraryBucketName, arbitraryProductName, arbitraryProvisionArtifactName, tag1, ""), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.#", "1"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.name", arbitraryProvisionArtifactName), + ), + }, + { + Config: testAccCheckAwsServiceCatalogProductResourceConfigTemplate(arbitraryBucketName, arbitraryProductName, newArbitraryProvisionArtifactName, tag1, ""), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.#", "1"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.name", newArbitraryProvisionArtifactName), + ), + }, + }, + }) +} - name := acctest.RandString(5) - bucketName := acctest.RandString(16) +// update the LoadFromTemplateURL to force recreating new Product +func TestAccAWSServiceCatalogProduct_updateProvisioningArtifactForceNew(t *testing.T) { + arbitraryProductName := fmt.Sprintf("product-%s", acctest.RandString(5)) + arbitraryProvisionArtifactName := fmt.Sprintf("pa-%s", acctest.RandString(5)) + arbitraryBucketName := fmt.Sprintf("bucket-%s", acctest.RandString(16)) + tag1 := "FooKey = \"bar\"" - template := template.Must(template.New("hcl").Parse(testAccCheckAwsServiceCatalogProductResourceConfigTemplate1)) - template.Execute(&template1, Input{"dsc1", "dst1", name, bucketName, "own1", "sd1", "a@b.com", "https://url/support1.html"}) + newArbitraryBucketName := fmt.Sprintf("bucket-new-%s", acctest.RandString(16)) + newArbitraryProvisionArtifactName := fmt.Sprintf("pa-new-%s", acctest.RandString(5)) resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckServiceCatalogProductDestroy, + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, Steps: []resource.TestStep{ - resource.TestStep{ - Config: template1.String(), + { + Config: testAccCheckAwsServiceCatalogProductResourceConfigTemplate(arbitraryBucketName, arbitraryProductName, arbitraryProvisionArtifactName, tag1, ""), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.#", "1"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.name", arbitraryProvisionArtifactName), + ), + }, + { + Config: testAccCheckAwsServiceCatalogProductResourceConfigTemplate(newArbitraryBucketName, arbitraryProductName, newArbitraryProvisionArtifactName, tag1, ""), Check: resource.ComposeTestCheckFunc( - testAccCheckProduct("aws_servicecatalog_product.test", &productViewDetail), - testAccCheckServiceCatalogProductDisappears(&productViewDetail), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.#", "1"), + resource.TestCheckResourceAttr("aws_servicecatalog_product.test", "provisioning_artifact.0.name", newArbitraryProvisionArtifactName), ), - ExpectNonEmptyPlan: true, }, }, }) } -func TestAccAWSServiceCatalogProductImport(t *testing.T) { +func TestAccAWSServiceCatalogProduct_import(t *testing.T) { resourceName := "aws_servicecatalog_product.test" - var template1 bytes.Buffer - - name := acctest.RandString(5) - bucketName := acctest.RandString(16) - template := template.Must(template.New("hcl").Parse(testAccCheckAwsServiceCatalogProductResourceConfigTemplate1)) - template.Execute(&template1, Input{"dsc1", "dst1", name, bucketName, "own1", "sd1", "a@b.com", "https://url/support1.html"}) + arbitraryProductName := fmt.Sprintf("product-%s", acctest.RandString(5)) + arbitraryProvisionArtifactName := fmt.Sprintf("pa-%s", acctest.RandString(5)) + arbitraryBucketName := fmt.Sprintf("bucket-%s", acctest.RandString(16)) + tag1 := "FooKey = \"bar\"" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckServiceCatalogProductDestroy, Steps: []resource.TestStep{ - resource.TestStep{ - Config: template1.String(), + { + Config: testAccCheckAwsServiceCatalogProductResourceConfigTemplate(arbitraryBucketName, arbitraryProductName, arbitraryProvisionArtifactName, tag1, ""), }, - resource.TestStep{ + { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, @@ -140,47 +160,6 @@ func TestAccAWSServiceCatalogProductImport(t *testing.T) { }) } -func testAccCheckProduct(pr string, pd *servicecatalog.ProductViewDetail) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).scconn - rs, ok := s.RootModule().Resources[pr] - if !ok { - return fmt.Errorf("Not found: %s", pr) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - input := servicecatalog.DescribeProductAsAdminInput{} - input.Id = aws.String(rs.Primary.ID) - - resp, err := conn.DescribeProductAsAdmin(&input) - if err != nil { - return err - } - - *pd = *resp.ProductViewDetail - return nil - } -} - -func testAccCheckServiceCatalogProductDisappears(pd *servicecatalog.ProductViewDetail) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).scconn - - input := servicecatalog.DeleteProductInput{} - input.Id = pd.ProductViewSummary.ProductId - - _, err := conn.DeleteProduct(&input) - if err != nil { - return err - } - - return nil - } -} - func testAccCheckServiceCatalogProductDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).scconn @@ -198,29 +177,18 @@ func testAccCheckServiceCatalogProductDestroy(s *terraform.State) error { } return err } - return fmt.Errorf("Product still exists") + return fmt.Errorf("product still exists") } return nil } -type Input struct { - Description string - Distributor string - Name string - BucketName string - Owner string - SupportDescription string - SupportEmail string - SupportUrl string -} - -const testAccCheckAwsServiceCatalogProductResourceConfigTemplate1 = ` -data "aws_caller_identity" "current" {} -data "aws_region" "current" {} +func testAccCheckAwsServiceCatalogProductResourceConfigTemplate(bucketName, productName, provisioningArtifactName, tag1, tag2 string) string { + return fmt.Sprintf(` +data "aws_region" "current" { } resource "aws_s3_bucket" "bucket" { - bucket = "{{.BucketName}}" + bucket = "%s" region = "${data.aws_region.current.name}" acl = "private" force_destroy = true @@ -239,45 +207,30 @@ resource "aws_s3_bucket_object" "template1" { } } } -EOF -} - -resource "aws_s3_bucket_object" "template2" { - bucket = "${aws_s3_bucket.bucket.id}" - key = "test_templates_for_terraform_sc_dev2.json" - content = <> aws_servicecatalog_portfolio + > + aws_servicecatalog_product + diff --git a/website/docs/r/servicecatalog_portfolio.html.markdown b/website/docs/r/servicecatalog_portfolio.html.markdown old mode 100644 new mode 100755 diff --git a/website/docs/r/servicecatalog_product.html.markdown b/website/docs/r/servicecatalog_product.html.markdown new file mode 100755 index 000000000000..030b7512adad --- /dev/null +++ b/website/docs/r/servicecatalog_product.html.markdown @@ -0,0 +1,78 @@ +--- +layout: "aws" +page_title: "AWS: aws_servicecatalog_product" +sidebar_current: "docs-aws-resource-servicecatalog-product" +description: |- + Provides a resource to create a Service Catalog Product +--- + +# aws_servicecatalog_product + +Provides a resource to create a Service Catalog Product. +Noted that this only creates the very first Provisioning Artifact that comes along with the product being created. + +## Example Usage + +```hcl +resource "aws_servicecatalog_product" "test" { + description = "arbitrary product description" + distributor = "arbitrary distributor" + name = "My First Product" + owner = "arbitrary owner" + product_type = "CLOUD_FORMATION_TEMPLATE" + support_description = "arbitrary support description" + support_email = "arbitrary@email.com" + support_url = "http://arbitrary_url/foo.html" + + provisioning_artifact { + description = "arbitrary description" + name = "v1.0.0" + info { + LoadTemplateFromURL = "https://s3.amazonaws.com/bucket-xyz/cloudformation.json" + } + } + + tags { + Foo = "bar" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `description` - (Optional) The description of the product. +* `destributor` - (Optional) The distributor of the product. +* `name` - (Required) The name of the product. +* `owner` - (Required) The owner of the product. +* `product_type` - (Required) The type of product. Valid values: `CLOUD_FORMATION_TEMPLATE` or `MARKETPLACE` +* `provisioning_artifact` - (Required) The configuration of the provisioning artifact. This object supports the following: + * `description` - (Optional) The description of the provisioning artifact. + * `name` - (Required) The name of the provisioning artifact (for example, v1 v2beta). No spaces are allowed. + * `type` - (Optional) The type of provisioning artifact. Valid Values: `CLOUD_FORMATION_TEMPLATE` or `MARKETPLACE_AMI` or `MARKETPLACE_CAR`. Default is `CLOUD_FORMATION_TEMPLATE` + * `info` - (Required) The URL of the CloudFormation template in Amazon S3. Specify the URL as `LoadTemplateFromURL = https://s3.amazonaws.com/cf-templates-ozkq9d3hgiq2-us-east-1/...` +* `support_description` - (Optional) The support information about the product. +* `support_email` - (Optional) The contact email for product support. +* `support_url` - (Optional) The contact URL for product support. +* `tags` - (Optional) Tags to apply to the connection. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The ID of the Service Catalog Product. +* `product_arn` - The ARN of the product. +* `has_default_path` - Indicates whether the product has a default path. +* `provisioning_artifact` - Object attributes that are exported are: + * `id` - The ID of the Provisioning Artifact + * `active` - Indicates whether the product version is active. + * `created_time` - The UTC time stamp of the creation time. + +## Import + +Service Catalog Products can be imported using the service catalog product id, e.g. + +``` +$ terraform import aws_servicecatalog_product.test p-12344321 +``` From 22148da48f31100e3b95f44e8fd6dbb8dce79bec Mon Sep 17 00:00:00 2001 From: Trung Nguyen Date: Mon, 25 Jun 2018 14:44:54 -0400 Subject: [PATCH 13/13] minor fixes --- aws/resource_aws_servicecatalog_product_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_servicecatalog_product_test.go b/aws/resource_aws_servicecatalog_product_test.go index 08ddb42c9e56..2a96cf6cd540 100755 --- a/aws/resource_aws_servicecatalog_product_test.go +++ b/aws/resource_aws_servicecatalog_product_test.go @@ -207,7 +207,7 @@ resource "aws_s3_bucket_object" "template1" { } } } -EOF +EOF } resource "aws_servicecatalog_product" "test" {