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

Addressing Issue 32229 - cleaning up IAM_PATTERN properly #32243

Merged
merged 13 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion internal/service/servicecatalog/find.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func FindTagOptionResourceAssociation(ctx context.Context, conn *servicecatalog.
return result, err
}

func FindPrincipalPortfolioAssociation(ctx context.Context, conn *servicecatalog.ServiceCatalog, acceptLanguage, principalARN, portfolioID string) (*servicecatalog.Principal, error) {
func FindPrincipalPortfolioAssociation(ctx context.Context, conn *servicecatalog.ServiceCatalog, acceptLanguage, principalARN, portfolioID string, principalType string) (*servicecatalog.Principal, error) {
input := &servicecatalog.ListPrincipalsForPortfolioInput{
PortfolioId: aws.String(portfolioID),
}
Expand Down
14 changes: 7 additions & 7 deletions internal/service/servicecatalog/id.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,18 +74,18 @@ func ProvisioningArtifactParseID(id string) (string, string, error) {
return parts[0], parts[1], nil
}

func PrincipalPortfolioAssociationParseID(id string) (string, string, string, error) {
parts := strings.SplitN(id, ",", 3)
func PrincipalPortfolioAssociationParseID(id string) (string, string, string, string, error) {
parts := strings.SplitN(id, ",", 4)

if len(parts) != 3 || parts[0] == "" || parts[1] == "" || parts[2] == "" {
return "", "", "", fmt.Errorf("unexpected format of ID (%s), expected acceptLanguage,principalARN,portfolioID", id)
if len(parts) != 4 || parts[0] == "" || parts[1] == "" || parts[2] == "" || parts[3] == "" {
return "", "", "", "", fmt.Errorf("unexpected format of ID (%s), expected acceptLanguage,principalARN,portfolioID,principalType", id)
}

return parts[0], parts[1], parts[2], nil
return parts[0], parts[1], parts[2], parts[3], nil
}

func PrincipalPortfolioAssociationID(acceptLanguage, principalARN, portfolioID string) string {
return strings.Join([]string{acceptLanguage, principalARN, portfolioID}, ",")
func PrincipalPortfolioAssociationID(acceptLanguage, principalARN, portfolioID string, principalType string) string {
return strings.Join([]string{acceptLanguage, principalARN, portfolioID, principalType}, ",")
}

func PortfolioConstraintsID(acceptLanguage, portfolioID, productID string) string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@ func resourcePrincipalPortfolioAssociationCreate(ctx context.Context, d *schema.
conn := meta.(*conns.AWSClient).ServiceCatalogConn(ctx)

input := &servicecatalog.AssociatePrincipalWithPortfolioInput{
PortfolioId: aws.String(d.Get("portfolio_id").(string)),
PrincipalARN: aws.String(d.Get("principal_arn").(string)),
PortfolioId: aws.String(d.Get("portfolio_id").(string)),
PrincipalARN: aws.String(d.Get("principal_arn").(string)),
PrincipalType: aws.String(d.Get("principal_type").(string)),
}

if v, ok := d.GetOk("accept_language"); ok {
Expand Down Expand Up @@ -107,7 +108,7 @@ func resourcePrincipalPortfolioAssociationCreate(ctx context.Context, d *schema.
return sdkdiag.AppendErrorf(diags, "creating Service Catalog Principal Portfolio Association: empty response")
}

d.SetId(PrincipalPortfolioAssociationID(d.Get("accept_language").(string), d.Get("principal_arn").(string), d.Get("portfolio_id").(string)))
d.SetId(PrincipalPortfolioAssociationID(d.Get("accept_language").(string), d.Get("principal_arn").(string), d.Get("portfolio_id").(string), d.Get("principal_type").(string)))

return append(diags, resourcePrincipalPortfolioAssociationRead(ctx, d, meta)...)
}
Expand All @@ -116,7 +117,7 @@ func resourcePrincipalPortfolioAssociationRead(ctx context.Context, d *schema.Re
var diags diag.Diagnostics
conn := meta.(*conns.AWSClient).ServiceCatalogConn(ctx)

acceptLanguage, principalARN, portfolioID, err := PrincipalPortfolioAssociationParseID(d.Id())
acceptLanguage, principalARN, portfolioID, principalType, err := PrincipalPortfolioAssociationParseID(d.Id())

if err != nil {
return sdkdiag.AppendErrorf(diags, "could not parse ID (%s): %s", d.Id(), err)
Expand All @@ -126,7 +127,7 @@ func resourcePrincipalPortfolioAssociationRead(ctx context.Context, d *schema.Re
acceptLanguage = AcceptLanguageEnglish
}

output, err := WaitPrincipalPortfolioAssociationReady(ctx, conn, acceptLanguage, principalARN, portfolioID, d.Timeout(schema.TimeoutRead))
output, err := WaitPrincipalPortfolioAssociationReady(ctx, conn, acceptLanguage, principalARN, portfolioID, principalType, d.Timeout(schema.TimeoutRead))

if !d.IsNewResource() && (tfresource.NotFound(err) || tfawserr.ErrCodeEquals(err, servicecatalog.ErrCodeResourceNotFoundException)) {
log.Printf("[WARN] Service Catalog Principal Portfolio Association (%s) not found, removing from state", d.Id())
Expand Down Expand Up @@ -154,7 +155,7 @@ func resourcePrincipalPortfolioAssociationDelete(ctx context.Context, d *schema.
var diags diag.Diagnostics
conn := meta.(*conns.AWSClient).ServiceCatalogConn(ctx)

acceptLanguage, principalARN, portfolioID, err := PrincipalPortfolioAssociationParseID(d.Id())
acceptLanguage, principalARN, portfolioID, principalType, err := PrincipalPortfolioAssociationParseID(d.Id())

if err != nil {
return sdkdiag.AppendErrorf(diags, "could not parse ID (%s): %s", d.Id(), err)
Expand All @@ -168,6 +169,7 @@ func resourcePrincipalPortfolioAssociationDelete(ctx context.Context, d *schema.
PortfolioId: aws.String(portfolioID),
PrincipalARN: aws.String(principalARN),
AcceptLanguage: aws.String(acceptLanguage),
PrincipalType: aws.String(principalType),
}

_, err = conn.DisassociatePrincipalFromPortfolioWithContext(ctx, input)
Expand All @@ -180,7 +182,7 @@ func resourcePrincipalPortfolioAssociationDelete(ctx context.Context, d *schema.
return sdkdiag.AppendErrorf(diags, "disassociating Service Catalog Principal from Portfolio (%s): %s", d.Id(), err)
}

err = WaitPrincipalPortfolioAssociationDeleted(ctx, conn, acceptLanguage, principalARN, portfolioID, d.Timeout(schema.TimeoutDelete))
err = WaitPrincipalPortfolioAssociationDeleted(ctx, conn, acceptLanguage, principalARN, portfolioID, principalType, d.Timeout(schema.TimeoutDelete))

if tfresource.NotFound(err) || tfawserr.ErrCodeEquals(err, servicecatalog.ErrCodeResourceNotFoundException) {
return diags
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,33 @@ func TestAccServiceCatalogPrincipalPortfolioAssociation_basic(t *testing.T) {
})
}

func TestAccServiceCatalogPrincipalPortfolioAssociation_iam_pattern(t *testing.T) {
ctx := acctest.Context(t)
resourceName := "aws_servicecatalog_principal_portfolio_association.test"
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, servicecatalog.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckPrincipalPortfolioAssociationDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccPrincipalPortfolioAssociationConfig_iam_pattern(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckPrincipalPortfolioAssociationExists(ctx, resourceName),
resource.TestCheckResourceAttrPair(resourceName, "portfolio_id", "aws_servicecatalog_portfolio.test", "id"),
resource.TestCheckResourceAttr(resourceName, "principal_type", "IAM_PATTERN"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}
func TestAccServiceCatalogPrincipalPortfolioAssociation_disappears(t *testing.T) {
ctx := acctest.Context(t)
resourceName := "aws_servicecatalog_principal_portfolio_association.test"
Expand Down Expand Up @@ -78,13 +105,13 @@ func testAccCheckPrincipalPortfolioAssociationDestroy(ctx context.Context) resou
continue
}

acceptLanguage, principalARN, portfolioID, err := tfservicecatalog.PrincipalPortfolioAssociationParseID(rs.Primary.ID)
acceptLanguage, principalARN, portfolioID, principalType, err := tfservicecatalog.PrincipalPortfolioAssociationParseID(rs.Primary.ID)

if err != nil {
return fmt.Errorf("could not parse ID (%s): %w", rs.Primary.ID, err)
}

err = tfservicecatalog.WaitPrincipalPortfolioAssociationDeleted(ctx, conn, acceptLanguage, principalARN, portfolioID, tfservicecatalog.PrincipalPortfolioAssociationDeleteTimeout)
err = tfservicecatalog.WaitPrincipalPortfolioAssociationDeleted(ctx, conn, acceptLanguage, principalARN, portfolioID, principalType, tfservicecatalog.PrincipalPortfolioAssociationDeleteTimeout)

if tfresource.NotFound(err) || tfawserr.ErrCodeEquals(err, servicecatalog.ErrCodeResourceNotFoundException) {
continue
Expand All @@ -107,15 +134,15 @@ func testAccCheckPrincipalPortfolioAssociationExists(ctx context.Context, resour
return fmt.Errorf("resource not found: %s", resourceName)
}

acceptLanguage, principalARN, portfolioID, err := tfservicecatalog.PrincipalPortfolioAssociationParseID(rs.Primary.ID)
acceptLanguage, principalARN, portfolioID, principalType, err := tfservicecatalog.PrincipalPortfolioAssociationParseID(rs.Primary.ID)

if err != nil {
return fmt.Errorf("could not parse ID (%s): %w", rs.Primary.ID, err)
}

conn := acctest.Provider.Meta().(*conns.AWSClient).ServiceCatalogConn(ctx)

_, err = tfservicecatalog.WaitPrincipalPortfolioAssociationReady(ctx, conn, acceptLanguage, principalARN, portfolioID, tfservicecatalog.PrincipalPortfolioAssociationReadyTimeout)
_, err = tfservicecatalog.WaitPrincipalPortfolioAssociationReady(ctx, conn, acceptLanguage, principalARN, portfolioID, principalType, tfservicecatalog.PrincipalPortfolioAssociationReadyTimeout)

if err != nil {
return fmt.Errorf("waiting for Service Catalog Principal Portfolio Association existence (%s): %w", rs.Primary.ID, err)
Expand Down Expand Up @@ -160,3 +187,13 @@ resource "aws_servicecatalog_principal_portfolio_association" "test" {
}
`)
}

func testAccPrincipalPortfolioAssociationConfig_iam_pattern(rName string) string {
return acctest.ConfigCompose(testAccPrincipalPortfolioAssociationConfig_base(rName), `
resource "aws_servicecatalog_principal_portfolio_association" "test" {
portfolio_id = aws_servicecatalog_portfolio.test.id
principal_arn = "arn:aws:iam:::role/${aws_iam_role.test.name}"
principal_type = "IAM_PATTERN"
}
`)
}
4 changes: 2 additions & 2 deletions internal/service/servicecatalog/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,9 +293,9 @@ func StatusProvisioningArtifact(ctx context.Context, conn *servicecatalog.Servic
}
}

func StatusPrincipalPortfolioAssociation(ctx context.Context, conn *servicecatalog.ServiceCatalog, acceptLanguage, principalARN, portfolioID string) retry.StateRefreshFunc {
func StatusPrincipalPortfolioAssociation(ctx context.Context, conn *servicecatalog.ServiceCatalog, acceptLanguage, principalARN, portfolioID string, principalType string) retry.StateRefreshFunc {
return func() (interface{}, string, error) {
output, err := FindPrincipalPortfolioAssociation(ctx, conn, acceptLanguage, principalARN, portfolioID)
output, err := FindPrincipalPortfolioAssociation(ctx, conn, acceptLanguage, principalARN, portfolioID, principalType)

if tfawserr.ErrCodeEquals(err, servicecatalog.ErrCodeResourceNotFoundException) {
return nil, StatusNotFound, err
Expand Down
8 changes: 4 additions & 4 deletions internal/service/servicecatalog/wait.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,11 +438,11 @@ func WaitProvisioningArtifactDeleted(ctx context.Context, conn *servicecatalog.S
return err
}

func WaitPrincipalPortfolioAssociationReady(ctx context.Context, conn *servicecatalog.ServiceCatalog, acceptLanguage, principalARN, portfolioID string, timeout time.Duration) (*servicecatalog.Principal, error) {
func WaitPrincipalPortfolioAssociationReady(ctx context.Context, conn *servicecatalog.ServiceCatalog, acceptLanguage, principalARN, portfolioID string, principalType string, timeout time.Duration) (*servicecatalog.Principal, error) {
stateConf := &retry.StateChangeConf{
Pending: []string{StatusNotFound, StatusUnavailable},
Target: []string{servicecatalog.StatusAvailable},
Refresh: StatusPrincipalPortfolioAssociation(ctx, conn, acceptLanguage, principalARN, portfolioID),
Refresh: StatusPrincipalPortfolioAssociation(ctx, conn, acceptLanguage, principalARN, portfolioID, principalType),
Timeout: timeout,
ContinuousTargetOccurence: ContinuousTargetOccurrence,
NotFoundChecks: NotFoundChecks,
Expand All @@ -458,11 +458,11 @@ func WaitPrincipalPortfolioAssociationReady(ctx context.Context, conn *serviceca
return nil, err
}

func WaitPrincipalPortfolioAssociationDeleted(ctx context.Context, conn *servicecatalog.ServiceCatalog, acceptLanguage, principalARN, portfolioID string, timeout time.Duration) error {
func WaitPrincipalPortfolioAssociationDeleted(ctx context.Context, conn *servicecatalog.ServiceCatalog, acceptLanguage, principalARN, portfolioID string, principalType string, timeout time.Duration) error {
stateConf := &retry.StateChangeConf{
Pending: []string{servicecatalog.StatusAvailable},
Target: []string{StatusNotFound, StatusUnavailable},
Refresh: StatusPrincipalPortfolioAssociation(ctx, conn, acceptLanguage, principalARN, portfolioID),
Refresh: StatusPrincipalPortfolioAssociation(ctx, conn, acceptLanguage, principalARN, portfolioID, principalType),
Timeout: timeout,
NotFoundChecks: 1,
}
Expand Down