Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

r/aws_servicecatalog_provisioned_product: outputs refresh when resource updates #29559

Merged
merged 5 commits into from
Feb 22, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/29559.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
resource/aws_servicecatalog_provisioned_product: Fix to allow `outputs` to be `Computed` when the resource changes
```
16 changes: 15 additions & 1 deletion internal/service/servicecatalog/provisioned_product.go
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ import (
"github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
@@ -252,10 +253,23 @@ func ResourceProvisionedProduct() *schema.Resource {
},
},

CustomizeDiff: verify.SetTagsDiff,
CustomizeDiff: customdiff.All(
refreshOutputsDiff,
verify.SetTagsDiff,
),
}
}

func refreshOutputsDiff(_ context.Context, diff *schema.ResourceDiff, meta interface{}) error {
if diff.HasChanges("provisioning_parameters", "provisioning_artifact_id", "provisioning_artifact_name") {
if err := diff.SetNewComputed("outputs"); err != nil {
return err
}
}

return nil
}

func resourceProvisionedProductCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
var diags diag.Diagnostics
conn := meta.(*conns.AWSClient).ServiceCatalogConn()
225 changes: 194 additions & 31 deletions internal/service/servicecatalog/provisioned_product_test.go
Original file line number Diff line number Diff line change
@@ -142,6 +142,55 @@ func TestAccServiceCatalogProvisionedProduct_update(t *testing.T) {
})
}

func TestAccServiceCatalogProvisionedProduct_computedOutputs(t *testing.T) {
ctx := acctest.Context(t)
resourceName := "aws_servicecatalog_provisioned_product.test"

rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
domain := fmt.Sprintf("http://%s", acctest.RandomDomainName())

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ErrorCheck: acctest.ErrorCheck(t, servicecatalog.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckProvisionedProductDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccProvisionedProductConfig_computedOutputs(rName, domain, acctest.DefaultEmailAddress, "10.1.0.0/16"),
Check: resource.ComposeTestCheckFunc(
testAccCheckProvisionedProductExists(ctx, resourceName),
resource.TestCheckResourceAttr(resourceName, "outputs.#", "3"),
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "outputs.*", map[string]string{
"description": "VPC ID",
"key": "VpcID",
}),
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "outputs.*", map[string]string{
"description": "VPC CIDR",
"key": "VPCPrimaryCIDR",
"value": "10.1.0.0/16",
}),
),
},
{
Config: testAccProvisionedProductConfig_computedOutputs(rName, domain, acctest.DefaultEmailAddress, "10.1.0.1/16"),
Check: resource.ComposeTestCheckFunc(
testAccCheckProvisionedProductExists(ctx, resourceName),
resource.TestCheckResourceAttr(resourceName, "outputs.#", "3"),
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "outputs.*", map[string]string{
"description": "VPC ID",
"key": "VpcID",
}),
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "outputs.*", map[string]string{
"description": "VPC CIDR",
"key": "VPCPrimaryCIDR",
"value": "10.1.0.1/16",
}),
),
},
},
})
}

func TestAccServiceCatalogProvisionedProduct_disappears(t *testing.T) {
ctx := acctest.Context(t)
resourceName := "aws_servicecatalog_provisioned_product.test"
@@ -277,8 +326,54 @@ func testAccCheckProvisionedProductExists(ctx context.Context, resourceName stri
}
}

func testAccProvisionedProductTemplateURLBaseConfig(rName, domain, email string) string {
func testAccProvisionedProductPortfolioBaseConfig(rName string) string {
return fmt.Sprintf(`
resource "aws_servicecatalog_portfolio" "test" {
name = %[1]q
description = %[1]q
provider_name = %[1]q
}

resource "aws_servicecatalog_constraint" "test" {
description = %[1]q
portfolio_id = aws_servicecatalog_product_portfolio_association.test.portfolio_id
product_id = aws_servicecatalog_product_portfolio_association.test.product_id
type = "RESOURCE_UPDATE"

parameters = jsonencode({
Version = "2.0"
Properties = {
TagUpdateOnProvisionedProduct = "ALLOWED"
}
})
}

resource "aws_servicecatalog_product_portfolio_association" "test" {
portfolio_id = aws_servicecatalog_principal_portfolio_association.test.portfolio_id # avoid depends_on
product_id = aws_servicecatalog_product.test.id
}

data "aws_caller_identity" "current" {}

data "aws_iam_session_context" "current" {
arn = data.aws_caller_identity.current.arn
}

resource "aws_servicecatalog_principal_portfolio_association" "test" {
portfolio_id = aws_servicecatalog_portfolio.test.id
principal_arn = data.aws_iam_session_context.current.issuer_arn # unfortunately, you cannot get launch_path for arbitrary role - only caller
}

data "aws_servicecatalog_launch_paths" "test" {
product_id = aws_servicecatalog_product_portfolio_association.test.product_id # avoid depends_on
}
`, rName)
}

func testAccProvisionedProductTemplateURLBaseConfig(rName, domain, email string) string {
return acctest.ConfigCompose(
testAccProvisionedProductPortfolioBaseConfig(rName),
fmt.Sprintf(`
resource "aws_s3_bucket" "test" {
bucket = %[1]q
force_destroy = true
@@ -360,47 +455,93 @@ resource "aws_servicecatalog_product" "test" {
Name = %[1]q
}
}

resource "aws_servicecatalog_portfolio" "test" {
name = %[1]q
description = %[1]q
provider_name = %[1]q
`, rName, domain, email))
}

resource "aws_servicecatalog_constraint" "test" {
description = %[1]q
portfolio_id = aws_servicecatalog_product_portfolio_association.test.portfolio_id
product_id = aws_servicecatalog_product_portfolio_association.test.product_id
type = "RESOURCE_UPDATE"
func testAccProvisionedProductPhysicalTemplateIDBaseConfig(rName, domain, email string) string {
return acctest.ConfigCompose(
testAccProvisionedProductPortfolioBaseConfig(rName),
fmt.Sprintf(`
resource "aws_cloudformation_stack" "test" {
name = %[1]q

parameters = jsonencode({
Version = "2.0"
Properties = {
TagUpdateOnProvisionedProduct = "ALLOWED"
template_body = jsonencode({
AWSTemplateFormatVersion = "2010-09-09"

Parameters = {
VPCPrimaryCIDR = {
Type = "String"
Default = "10.0.0.0/16"
}
LeaveMeEmpty = {
Type = "String"
Description = "Make sure that empty values come through. Issue #21349"
Default = ""
}
}
})
}

resource "aws_servicecatalog_product_portfolio_association" "test" {
portfolio_id = aws_servicecatalog_principal_portfolio_association.test.portfolio_id # avoid depends_on
product_id = aws_servicecatalog_product.test.id
}
"Conditions" = {
"IsEmptyParameter" = {
"Fn::Equals" = [
{
"Ref" = "LeaveMeEmpty"
},
"",
]
}
}

data "aws_caller_identity" "current" {}
Resources = {
MyVPC = {
Type = "AWS::EC2::VPC"
Condition = "IsEmptyParameter"
Properties = {
CidrBlock = { Ref = "VPCPrimaryCIDR" }
}
}
}

data "aws_iam_session_context" "current" {
arn = data.aws_caller_identity.current.arn
}
Outputs = {
VpcID = {
Description = "VPC ID"
Value = {
Ref = "MyVPC"
}
}

resource "aws_servicecatalog_principal_portfolio_association" "test" {
portfolio_id = aws_servicecatalog_portfolio.test.id
principal_arn = data.aws_iam_session_context.current.issuer_arn # unfortunately, you cannot get launch_path for arbitrary role - only caller
VPCPrimaryCIDR = {
Description = "VPC CIDR"
Value = {
Ref = "VPCPrimaryCIDR"
}
}
}
})
}

data "aws_servicecatalog_launch_paths" "test" {
product_id = aws_servicecatalog_product_portfolio_association.test.product_id # avoid depends_on
resource "aws_servicecatalog_product" "test" {
description = %[1]q
distributor = "distributör"
name = %[1]q
owner = "ägare"
type = "CLOUD_FORMATION_TEMPLATE"
support_description = %[1]q
support_email = %[3]q
support_url = %[2]q

provisioning_artifact_parameters {
description = "artefaktbeskrivning"
disable_template_validation = true
name = %[1]q
template_physical_id = aws_cloudformation_stack.test.id
type = "CLOUD_FORMATION_TEMPLATE"
}

tags = {
Name = %[1]q
}
}
`, rName, domain, email)
`, rName, domain, email))
}

func testAccProvisionedProductConfig_basic(rName, domain, email, vpcCidr string) string {
@@ -425,6 +566,28 @@ resource "aws_servicecatalog_provisioned_product" "test" {
`, rName, vpcCidr))
}

func testAccProvisionedProductConfig_computedOutputs(rName, domain, email, vpcCidr string) string {
return acctest.ConfigCompose(testAccProvisionedProductPhysicalTemplateIDBaseConfig(rName, domain, email),
fmt.Sprintf(`
resource "aws_servicecatalog_provisioned_product" "test" {
name = %[1]q
product_id = aws_servicecatalog_product.test.id
provisioning_artifact_name = %[1]q
path_id = data.aws_servicecatalog_launch_paths.test.summaries[0].path_id

provisioning_parameters {
key = "VPCPrimaryCIDR"
value = %[2]q
}

provisioning_parameters {
key = "LeaveMeEmpty"
value = ""
}
}
`, rName, vpcCidr))
}

func testAccProvisionedProductConfig_updateTainted(rName, domain, email, vpcCidr string) string {
return acctest.ConfigCompose(testAccProvisionedProductTemplateURLBaseConfig(rName, domain, email),
fmt.Sprintf(`