-
Notifications
You must be signed in to change notification settings - Fork 9.3k
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
Service Catalog Provisioned Product and related resources #13797
Changes from 66 commits
f6321f3
8915edc
f365893
bfa2bc2
9227706
9370c2d
0e57346
07107de
ea45424
051a1a0
5179004
88f81f0
22148da
8870e42
e887f5f
b261bbd
c01782c
bea44a7
383dbfd
975fd2d
848689c
413cd8a
dee8315
56493a6
1d8c7a6
bfaadb1
243dc9a
440ef4b
676b9d0
eb1a397
258271d
93fbd22
24f04a2
1818eac
3b98f38
63763ef
7a15c8c
222fea6
971ac5b
edc5efd
b24619a
25ab24a
07c104a
610384d
4e025ed
3c661c6
1b8d9f3
e1327f5
515b1cd
0b7e87e
7377257
c94fab4
035dfac
59fa151
c34bb97
d22fcd8
888aa0b
06af8fb
9294fb1
80ff8af
f096f5d
f5ca63e
ae3e74d
bc84989
9793677
b8b8b4b
866f047
19fca67
603d288
eee00a1
08716d3
70ac64a
625c9b7
2dcb083
85d33e4
9af48f5
db3e1aa
8cfecab
038fd4a
0551515
46303a4
6983acb
c655600
3994bf4
dfbc50f
9cea7db
bdce9ea
a57bf3f
256bd32
a7f6049
08a888d
ccba736
822ac3f
c5870a1
763f582
4724b3e
11d3c81
7dddfab
9ac47f6
9a2da04
7c4981c
cd321eb
98c2c44
242ee56
ecb78d4
90a0a2c
c22f20e
ee6d2cf
9ba34d8
4320102
5717d01
4cee871
0c70f23
26e4e36
0158cd0
c8c96cd
a2da3ee
0eb761d
d37ad72
05c6044
39a5a7a
2ed98dc
24cb977
81ba7eb
131d9ed
a0da08f
312d002
862fb04
40a91d1
87e6ab0
fd79364
f5076f3
aa3a333
3214eac
a8576b5
d41a8c6
978af98
b03c4c4
2f7c275
c69e597
2192e12
f1b3c20
eecd2fd
be3065b
e1d1d3b
1294114
1e57005
da120b2
2d6833b
ec3aaed
597a929
33be215
c8bae8b
8c4f3bb
a6ec0ca
2703aa7
d90c3f1
ef4b2d1
9c9dd75
08e3b91
e65df4f
7dbf42f
5eef5df
9e7e621
0818862
fc440d5
a61501d
9da757f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
package aws | ||
|
||
import ( | ||
"fmt" | ||
"github.com/aws/aws-sdk-go/aws" | ||
"github.com/aws/aws-sdk-go/service/servicecatalog" | ||
"github.com/hashicorp/terraform-plugin-sdk/helper/resource" | ||
"github.com/hashicorp/terraform-plugin-sdk/helper/schema" | ||
"log" | ||
"strings" | ||
"time" | ||
) | ||
|
||
func resourceAwsServiceCatalogPortfolioPrincipalAssociation() *schema.Resource { | ||
return &schema.Resource{ | ||
Create: resourceAwsServiceCatalogPortfolioPrincipalAssociationCreate, | ||
Read: resourceAwsServiceCatalogPortfolioPrincipalAssociationRead, | ||
Update: resourceAwsServiceCatalogPortfolioPrincipalAssociationUpdate, | ||
Delete: resourceAwsServiceCatalogPortfolioPrincipalAssociationDelete, | ||
Importer: &schema.ResourceImporter{ | ||
State: schema.ImportStatePassthrough, | ||
}, | ||
Timeouts: &schema.ResourceTimeout{ | ||
Create: schema.DefaultTimeout(10 * time.Minute), | ||
Update: schema.DefaultTimeout(10 * time.Minute), | ||
Delete: schema.DefaultTimeout(10 * time.Minute), | ||
}, | ||
Schema: map[string]*schema.Schema{ | ||
"portfolio_id": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
}, | ||
"principal_arn": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please add validation here using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done (check-in to come) |
||
}, | ||
}, | ||
} | ||
} | ||
|
||
func resourceAwsServiceCatalogPortfolioPrincipalAssociationCreate(d *schema.ResourceData, meta interface{}) error { | ||
_, portfolioId, principalArn := resourceAwsServiceCatalogPortfolioPrincipalAssociationRequiredParameters(d) | ||
input := servicecatalog.AssociatePrincipalWithPortfolioInput{ | ||
PortfolioId: aws.String(portfolioId), | ||
PrincipalARN: aws.String(principalArn), | ||
PrincipalType: aws.String("IAM"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you change hardcoded "IAM" to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done (check-in to come) |
||
} | ||
conn := meta.(*AWSClient).scconn | ||
_, err := conn.AssociatePrincipalWithPortfolio(&input) | ||
if err != nil { | ||
return fmt.Errorf("creating Service Catalog Principal(%s)/Portfolio(%s) Association failed: %s", | ||
principalArn, portfolioId, err.Error()) | ||
} | ||
|
||
stateConf := resource.StateChangeConf{ | ||
Pending: []string{servicecatalog.StatusCreating}, | ||
Target: []string{servicecatalog.StatusAvailable}, | ||
Timeout: 1 * time.Minute, | ||
PollInterval: 3 * time.Second, | ||
Refresh: func() (interface{}, string, error) { | ||
err := resourceAwsServiceCatalogPortfolioPrincipalAssociationRead(d, meta) | ||
if err != nil { | ||
return 42, "", err | ||
} | ||
if d.Id() != "" { | ||
return 42, servicecatalog.StatusAvailable, err | ||
} | ||
return 0, servicecatalog.StatusCreating, err | ||
}, | ||
} | ||
_, err = stateConf.WaitForState() | ||
return err | ||
} | ||
|
||
func resourceAwsServiceCatalogPortfolioPrincipalAssociationRead(d *schema.ResourceData, meta interface{}) error { | ||
id, portfolioId, principalArn := resourceAwsServiceCatalogPortfolioPrincipalAssociationRequiredParameters(d) | ||
input := servicecatalog.ListPrincipalsForPortfolioInput{ | ||
PortfolioId: aws.String(portfolioId), | ||
} | ||
conn := meta.(*AWSClient).scconn | ||
var pageToken = "" | ||
isFound := false | ||
for { | ||
pageOfDetails, nextPageToken, err := resourceAwsServiceCatalogPortfolioPrincipalAssociationListPrincipalsForPortfolioPage(conn, input, &pageToken) | ||
if err != nil { | ||
return err | ||
} | ||
for _, principal := range pageOfDetails { | ||
if *principal.PrincipalARN == principalArn { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wrap There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done (check-in to come) |
||
isFound = true | ||
d.SetId(id) | ||
break | ||
} | ||
} | ||
if nextPageToken == nil || isFound { | ||
break | ||
} | ||
pageToken = *nextPageToken | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as above There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done (check-in to come) |
||
} | ||
if !isFound { | ||
log.Printf("[WARN] Service Catalog Principal(%s)/Portfolio(%s) Association not found, removing from state", | ||
principalArn, portfolioId) | ||
d.SetId("") | ||
} | ||
d.Set("principal_arn", principalArn) | ||
d.Set("portfolio_id", portfolioId) | ||
return nil | ||
} | ||
|
||
func resourceAwsServiceCatalogPortfolioPrincipalAssociationListPrincipalsForPortfolioPage(conn *servicecatalog.ServiceCatalog, input servicecatalog.ListPrincipalsForPortfolioInput, nextPageToken *string) ([]*servicecatalog.Principal, *string, error) { | ||
input.PageToken = nextPageToken | ||
var page, err = conn.ListPrincipalsForPortfolio(&input) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("retrieving Service Catalog Associations for Principal/Portfolios: %s", err.Error()) | ||
} | ||
principalDetails := page.Principals | ||
return principalDetails, page.NextPageToken, nil | ||
} | ||
|
||
func resourceAwsServiceCatalogPortfolioPrincipalAssociationUpdate(d *schema.ResourceData, meta interface{}) error { | ||
const principalArnKey = "principal_arn" | ||
const portfolioIdKey = "portfolio_id" | ||
if d.HasChange(principalArnKey) || d.HasChange(portfolioIdKey) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. change to a single There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. btw, any reason for the consts? using the string literals are the way its done in other resources There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the linter picked these up and already changed (already checked in) the only reason for the constants was that in some projects that's preferred (to prevent typos) but we've switched to this project's convention of repreated string literals |
||
oldPrincipalArn, newPrincipalArn := d.GetChange(principalArnKey) | ||
oldPortfolioId, newPortfolioId := d.GetChange(portfolioIdKey) | ||
d.Set(principalArnKey, oldPrincipalArn) | ||
d.Set(portfolioIdKey, oldPortfolioId) | ||
resourceAwsServiceCatalogPortfolioPrincipalAssociationDelete(d, meta) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is a bit unusual as far as i can tell, same thing for the call to create. can we extract the logic to other functions and re use them? update should only call read There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unsure about this one: The only way to "update" this resource is to delete the old and create a new. So this implementation seemed a good way. But happy to change it. Options I see are: (a) As is -- call the canonical "delete" and "create" methods What do you recommend? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (d) seems the right way: as soon as all fields are |
||
d.Set(principalArnKey, newPrincipalArn) | ||
d.Set(portfolioIdKey, newPortfolioId) | ||
resourceAwsServiceCatalogPortfolioPrincipalAssociationCreate(d, meta) | ||
} | ||
return resourceAwsServiceCatalogPortfolioPrincipalAssociationRead(d, meta) | ||
} | ||
|
||
func resourceAwsServiceCatalogPortfolioPrincipalAssociationDelete(d *schema.ResourceData, meta interface{}) error { | ||
_, portfolioId, principalArn := resourceAwsServiceCatalogPortfolioPrincipalAssociationRequiredParameters(d) | ||
input := servicecatalog.DisassociatePrincipalFromPortfolioInput{ | ||
PortfolioId: aws.String(portfolioId), | ||
PrincipalARN: aws.String(principalArn), | ||
} | ||
conn := meta.(*AWSClient).scconn | ||
_, err := conn.DisassociatePrincipalFromPortfolio(&input) | ||
if err != nil { | ||
return fmt.Errorf("deleting Service Catalog Principal(%s)/Portfolio(%s) Association failed: %s", | ||
principalArn, portfolioId, err.Error()) | ||
} | ||
return nil | ||
} | ||
|
||
func resourceAwsServiceCatalogPortfolioPrincipalAssociationRequiredParameters(d *schema.ResourceData) (string, string, string) { | ||
if principalArn, ok := d.GetOk("principal_arn"); ok { | ||
portfolioId := d.Get("portfolio_id").(string) | ||
id := portfolioId + "--" + principalArn.(string) | ||
return id, portfolioId, principalArn.(string) | ||
} | ||
return parseServiceCatalogPortfolioPrincipalAssociationResourceId(d.Id()) | ||
} | ||
|
||
func parseServiceCatalogPortfolioPrincipalAssociationResourceId(id string) (string, string, string) { | ||
s := strings.SplitN(id, "--", 2) | ||
portfolioId := s[0] | ||
principalArn := s[1] | ||
return id, portfolioId, principalArn | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package aws | ||
|
||
import ( | ||
"fmt" | ||
"github.com/aws/aws-sdk-go/service/servicecatalog" | ||
"github.com/hashicorp/terraform-plugin-sdk/helper/acctest" | ||
"github.com/hashicorp/terraform-plugin-sdk/helper/resource" | ||
"github.com/hashicorp/terraform-plugin-sdk/terraform" | ||
"testing" | ||
) | ||
|
||
func TestAccAWSServiceCatalogPortfolioPrincipalAssociation_Basic(t *testing.T) { | ||
salt := acctest.RandString(5) | ||
resource.ParallelTest(t, resource.TestCase{ | ||
PreCheck: func() { testAccPreCheck(t) }, | ||
Providers: testAccProviders, | ||
CheckDestroy: testAccCheckServiceCatalogPortfolioPrincipalAssociationDestroy, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: testAccCheckAwsServiceCatalogPortfolioPrincipalAssociationConfigBasic(salt), | ||
Check: testAccCheckAwsServiceCatalogPortfolioPrincipalAssociation(), | ||
}, | ||
{ | ||
ResourceName: "aws_servicecatalog_portfolio_principal_association.association", | ||
ImportState: true, | ||
ImportStateVerify: true, | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func testAccCheckAwsServiceCatalogPortfolioPrincipalAssociation() resource.TestCheckFunc { | ||
return func(s *terraform.State) error { | ||
conn := testAccProvider.Meta().(*AWSClient).scconn | ||
for _, rs := range s.RootModule().Resources { | ||
if rs.Type != "aws_servicecatalog_portfolio_principal_association" { | ||
continue // not our monkey | ||
} | ||
_, portfolioId, principalArn := parseServiceCatalogPortfolioPrincipalAssociationResourceId(rs.Primary.ID) | ||
input := servicecatalog.ListPrincipalsForPortfolioInput{PortfolioId: &portfolioId} | ||
page, err := conn.ListPrincipalsForPortfolio(&input) | ||
if err != nil { | ||
return err | ||
} | ||
for _, principalDetail := range page.Principals { | ||
if *principalDetail.PrincipalARN == principalArn { | ||
return nil //is good | ||
} | ||
} | ||
return fmt.Errorf("association not found between portfolio %s and principal %s", portfolioId, principalArn) | ||
} | ||
return fmt.Errorf("no associations found") | ||
} | ||
} | ||
|
||
func testAccCheckServiceCatalogPortfolioPrincipalAssociationDestroy(s *terraform.State) error { | ||
conn := testAccProvider.Meta().(*AWSClient).scconn | ||
for _, rs := range s.RootModule().Resources { | ||
if rs.Type != "aws_servicecatalog_portfolio_principal_association" { | ||
continue // not our monkey | ||
} | ||
_, portfolioId, principalArn := parseServiceCatalogPortfolioPrincipalAssociationResourceId(rs.Primary.ID) | ||
input := servicecatalog.ListPrincipalsForPortfolioInput{PortfolioId: &portfolioId} | ||
page, err := conn.ListPrincipalsForPortfolio(&input) | ||
if err != nil { | ||
if isAWSErr(err, servicecatalog.ErrCodeResourceNotFoundException, "") { | ||
return nil // not found for principal is good | ||
} | ||
return err // some other unexpected error | ||
} | ||
for _, principalDetail := range page.Principals { | ||
if *principalDetail.PrincipalARN == principalArn { | ||
return fmt.Errorf("expected AWS Service Catalog Portfolio Principal Association to be gone, but it was still found") | ||
} | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func testAccCheckAwsServiceCatalogPortfolioPrincipalAssociationConfigBasic(salt string) string { | ||
return testAccCheckAwsServiceCatalogPortfolioResourceConfigBasic("tfm-test-"+salt) + "\n" + | ||
testAccCheckAwsServiceCatalogPortfolioPrincipalAssociationConfigRoleAndAssociation(salt) | ||
} | ||
|
||
func testAccCheckAwsServiceCatalogPortfolioPrincipalAssociationConfigRole(salt string) string { | ||
roleName := "tfm-sc-tester-" + salt | ||
return fmt.Sprintf(` | ||
# IAM | ||
resource "aws_iam_role" "tfm-sc-tester" { | ||
name = "%s" | ||
assume_role_policy = <<EOF | ||
{ | ||
"Version": "2012-10-17", | ||
"Statement": [ | ||
{ | ||
"Action": "sts:AssumeRole", | ||
"Principal": { "AWS": "*" }, | ||
"Effect": "Allow", | ||
"Sid": "" | ||
} | ||
] | ||
} | ||
EOF | ||
} | ||
`, roleName) | ||
} | ||
|
||
func testAccCheckAwsServiceCatalogPortfolioPrincipalAssociationConfigAssociation() string { | ||
return fmt.Sprintf(` | ||
resource "aws_servicecatalog_portfolio_principal_association" "association" { | ||
portfolio_id = aws_servicecatalog_portfolio.test.id | ||
principal_arn = aws_iam_role.tfm-sc-tester.arn | ||
}`) | ||
} | ||
|
||
func testAccCheckAwsServiceCatalogPortfolioPrincipalAssociationConfigRoleAndAssociation(salt string) string { | ||
return testAccCheckAwsServiceCatalogPortfolioPrincipalAssociationConfigRole(salt) + | ||
testAccCheckAwsServiceCatalogPortfolioPrincipalAssociationConfigAssociation() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
small nit, can you split the deps to go packages and external dep packages like other resources?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done - all files (check-in to come)