Skip to content

Commit

Permalink
Merge pull request #19385 from hashicorp/f-servicecat-constraint
Browse files Browse the repository at this point in the history
r/servicecatalog: New resources (constraint, product_portfolio_association)
  • Loading branch information
YakDriver authored May 19, 2021
2 parents 8b7b344 + b188a11 commit 91cf32b
Show file tree
Hide file tree
Showing 14 changed files with 1,366 additions and 1 deletion.
7 changes: 7 additions & 0 deletions .changelog/19385.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:new-resource
aws_servicecatalog_constraint
```

```release-note:new-resource
aws_servicecatalog_product_portfolio_association
```
18 changes: 18 additions & 0 deletions aws/internal/service/servicecatalog/enum.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
package servicecatalog

const (
// If AWS adds these to the API, we should use those and remove these.

ServiceCatalogAcceptLanguageEnglish = "en"
ServiceCatalogAcceptLanguageJapanese = "jp"
ServiceCatalogAcceptLanguageChinese = "zh"

ConstraintTypeLaunch = "LAUNCH"
ConstraintTypeNotification = "NOTIFICATION"
ConstraintTypeResourceUpdate = "RESOURCE_UPDATE"
ConstraintTypeStackset = "STACKSET"
ConstraintTypeTemplate = "TEMPLATE"
)

func AcceptLanguage_Values() []string {
Expand All @@ -13,3 +21,13 @@ func AcceptLanguage_Values() []string {
ServiceCatalogAcceptLanguageChinese,
}
}

func ConstraintType_Values() []string {
return []string{
ConstraintTypeLaunch,
ConstraintTypeNotification,
ConstraintTypeResourceUpdate,
ConstraintTypeStackset,
ConstraintTypeTemplate,
}
}
34 changes: 34 additions & 0 deletions aws/internal/service/servicecatalog/finder/finder.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,37 @@ func PortfolioShare(conn *servicecatalog.ServiceCatalog, portfolioID, shareType,

return result, err
}

func ProductPortfolioAssociation(conn *servicecatalog.ServiceCatalog, acceptLanguage, portfolioID, productID string) (*servicecatalog.PortfolioDetail, error) {
// seems odd that the sourcePortfolioID is not returned or searchable...
input := &servicecatalog.ListPortfoliosForProductInput{
ProductId: aws.String(productID),
}

if acceptLanguage != "" {
input.AcceptLanguage = aws.String(acceptLanguage)
}

var result *servicecatalog.PortfolioDetail

err := conn.ListPortfoliosForProductPages(input, func(page *servicecatalog.ListPortfoliosForProductOutput, lastPage bool) bool {
if page == nil {
return !lastPage
}

for _, deet := range page.PortfolioDetails {
if deet == nil {
continue
}

if aws.StringValue(deet.Id) == portfolioID {
result = deet
return false
}
}

return !lastPage
})

return result, err
}
14 changes: 14 additions & 0 deletions aws/internal/service/servicecatalog/id.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,17 @@ func PortfolioShareParseResourceID(id string) (string, string, string, error) {
func PortfolioShareCreateResourceID(portfolioID, shareType, principalID string) string {
return strings.Join([]string{portfolioID, shareType, principalID}, ":")
}

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

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

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

func ProductPortfolioAssociationCreateID(acceptLanguage, portfolioID, productID string) string {
return strings.Join([]string{acceptLanguage, portfolioID, productID}, ":")
}
57 changes: 57 additions & 0 deletions aws/internal/service/servicecatalog/waiter/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/aws/aws-sdk-go/service/servicecatalog"
"github.com/hashicorp/aws-sdk-go-base/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
tfservicecatalog "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/servicecatalog"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/servicecatalog/finder"
)

Expand Down Expand Up @@ -140,3 +141,59 @@ func OrganizationsAccessStatus(conn *servicecatalog.ServiceCatalog) resource.Sta
return output, aws.StringValue(output.AccessStatus), err
}
}

func ConstraintStatus(conn *servicecatalog.ServiceCatalog, acceptLanguage, id string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
input := &servicecatalog.DescribeConstraintInput{
Id: aws.String(id),
}

if acceptLanguage != "" {
input.AcceptLanguage = aws.String(acceptLanguage)
}

output, err := conn.DescribeConstraint(input)

if tfawserr.ErrCodeEquals(err, servicecatalog.ErrCodeResourceNotFoundException) {
return nil, StatusNotFound, &resource.NotFoundError{
Message: fmt.Sprintf("constraint not found (accept language %s, ID: %s): %s", acceptLanguage, id, err),
}
}

if err != nil {
return nil, servicecatalog.StatusFailed, fmt.Errorf("error describing constraint: %w", err)
}

if output == nil || output.ConstraintDetail == nil {
return nil, StatusNotFound, &resource.NotFoundError{
Message: fmt.Sprintf("describing constraint (accept language %s, ID: %s): empty response", acceptLanguage, id),
}
}

return output, aws.StringValue(output.Status), err
}
}

func ProductPortfolioAssociationStatus(conn *servicecatalog.ServiceCatalog, acceptLanguage, portfolioID, productID string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
output, err := finder.ProductPortfolioAssociation(conn, acceptLanguage, portfolioID, productID)

if tfawserr.ErrCodeEquals(err, servicecatalog.ErrCodeResourceNotFoundException) {
return nil, StatusNotFound, &resource.NotFoundError{
Message: fmt.Sprintf("product portfolio association not found (%s): %s", tfservicecatalog.ProductPortfolioAssociationCreateID(acceptLanguage, portfolioID, productID), err),
}
}

if err != nil {
return nil, servicecatalog.StatusFailed, fmt.Errorf("error describing product portfolio association: %w", err)
}

if output == nil {
return nil, StatusNotFound, &resource.NotFoundError{
Message: fmt.Sprintf("finding product portfolio association (%s): empty response", tfservicecatalog.ProductPortfolioAssociationCreateID(acceptLanguage, portfolioID, productID)),
}
}

return output, servicecatalog.StatusAvailable, err
}
}
65 changes: 65 additions & 0 deletions aws/internal/service/servicecatalog/waiter/waiter.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ const (
PortfolioShareCreateTimeout = 3 * time.Minute

OrganizationsAccessStableTimeout = 3 * time.Minute
ConstraintReadyTimeout = 3 * time.Minute
ConstraintDeleteTimeout = 3 * time.Minute

ProductPortfolioAssociationReadyTimeout = 3 * time.Minute
ProductPortfolioAssociationDeleteTimeout = 3 * time.Minute

StatusNotFound = "NOT_FOUND"
StatusUnavailable = "UNAVAILABLE"
Expand Down Expand Up @@ -198,3 +203,63 @@ func OrganizationsAccessStable(conn *servicecatalog.ServiceCatalog) (string, err

return "", err
}

func ConstraintReady(conn *servicecatalog.ServiceCatalog, acceptLanguage, id string) (*servicecatalog.DescribeConstraintOutput, error) {
stateConf := &resource.StateChangeConf{
Pending: []string{StatusNotFound, servicecatalog.StatusCreating, StatusUnavailable},
Target: []string{servicecatalog.StatusAvailable},
Refresh: ConstraintStatus(conn, acceptLanguage, id),
Timeout: ConstraintReadyTimeout,
}

outputRaw, err := stateConf.WaitForState()

if output, ok := outputRaw.(*servicecatalog.DescribeConstraintOutput); ok {
return output, err
}

return nil, err
}

func ConstraintDeleted(conn *servicecatalog.ServiceCatalog, acceptLanguage, id string) error {
stateConf := &resource.StateChangeConf{
Pending: []string{servicecatalog.StatusAvailable, servicecatalog.StatusCreating},
Target: []string{StatusNotFound},
Refresh: ConstraintStatus(conn, acceptLanguage, id),
Timeout: ConstraintDeleteTimeout,
}

_, err := stateConf.WaitForState()

return err
}

func ProductPortfolioAssociationReady(conn *servicecatalog.ServiceCatalog, acceptLanguage, portfolioID, productID string) (*servicecatalog.PortfolioDetail, error) {
stateConf := &resource.StateChangeConf{
Pending: []string{StatusNotFound, StatusUnavailable},
Target: []string{servicecatalog.StatusAvailable},
Refresh: ProductPortfolioAssociationStatus(conn, acceptLanguage, portfolioID, productID),
Timeout: ProductPortfolioAssociationReadyTimeout,
}

outputRaw, err := stateConf.WaitForState()

if output, ok := outputRaw.(*servicecatalog.PortfolioDetail); ok {
return output, err
}

return nil, err
}

func ProductPortfolioAssociationDeleted(conn *servicecatalog.ServiceCatalog, acceptLanguage, portfolioID, productID string) error {
stateConf := &resource.StateChangeConf{
Pending: []string{servicecatalog.StatusAvailable},
Target: []string{StatusNotFound, StatusUnavailable},
Refresh: ProductPortfolioAssociationStatus(conn, acceptLanguage, portfolioID, productID),
Timeout: ProductPortfolioAssociationDeleteTimeout,
}

_, err := stateConf.WaitForState()

return err
}
2 changes: 2 additions & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -1020,11 +1020,13 @@ func Provider() *schema.Provider {
"aws_securityhub_organization_admin_account": resourceAwsSecurityHubOrganizationAdminAccount(),
"aws_securityhub_product_subscription": resourceAwsSecurityHubProductSubscription(),
"aws_securityhub_standards_subscription": resourceAwsSecurityHubStandardsSubscription(),
"aws_servicecatalog_constraint": resourceAwsServiceCatalogConstraint(),
"aws_servicecatalog_organizations_access": resourceAwsServiceCatalogOrganizationsAccess(),
"aws_servicecatalog_portfolio": resourceAwsServiceCatalogPortfolio(),
"aws_servicecatalog_portfolio_share": resourceAwsServiceCatalogPortfolioShare(),
"aws_servicecatalog_product": resourceAwsServiceCatalogProduct(),
"aws_servicecatalog_tag_option": resourceAwsServiceCatalogTagOption(),
"aws_servicecatalog_product_portfolio_association": resourceAwsServiceCatalogProductPortfolioAssociation(),
"aws_service_discovery_http_namespace": resourceAwsServiceDiscoveryHttpNamespace(),
"aws_service_discovery_private_dns_namespace": resourceAwsServiceDiscoveryPrivateDnsNamespace(),
"aws_service_discovery_public_dns_namespace": resourceAwsServiceDiscoveryPublicDnsNamespace(),
Expand Down
Loading

0 comments on commit 91cf32b

Please sign in to comment.