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/servicecatalog_tag_option_resource_association: New resource #19448

Merged
merged 10 commits into from
May 20, 2021
3 changes: 3 additions & 0 deletions .changelog/19448.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
aws_servicecatalog_tag_option_resource_association
```
29 changes: 29 additions & 0 deletions aws/internal/service/servicecatalog/finder/finder.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,32 @@ func BudgetResourceAssociation(conn *servicecatalog.ServiceCatalog, budgetName,

return result, err
}

func TagOptionResourceAssociation(conn *servicecatalog.ServiceCatalog, tagOptionID, resourceID string) (*servicecatalog.ResourceDetail, error) {
input := &servicecatalog.ListResourcesForTagOptionInput{
TagOptionId: aws.String(tagOptionID),
}

var result *servicecatalog.ResourceDetail

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

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

if aws.StringValue(deet.Id) == resourceID {
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 @@ -46,3 +46,17 @@ func BudgetResourceAssociationParseID(id string) (string, string, error) {
func BudgetResourceAssociationID(budgetName, resourceID string) string {
return strings.Join([]string{budgetName, resourceID}, ":")
}

func TagOptionResourceAssociationParseID(id string) (string, string, error) {
parts := strings.SplitN(id, ":", 2)

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

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

func TagOptionResourceAssociationID(tagOptionID, resourceID string) string {
return strings.Join([]string{tagOptionID, resourceID}, ":")
}
24 changes: 24 additions & 0 deletions aws/internal/service/servicecatalog/waiter/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,3 +250,27 @@ func BudgetResourceAssociationStatus(conn *servicecatalog.ServiceCatalog, budget
return output, servicecatalog.StatusAvailable, err
}
}

func TagOptionResourceAssociationStatus(conn *servicecatalog.ServiceCatalog, tagOptionID, resourceID string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
output, err := finder.TagOptionResourceAssociation(conn, tagOptionID, resourceID)

if tfawserr.ErrCodeEquals(err, servicecatalog.ErrCodeResourceNotFoundException) {
return nil, StatusNotFound, &resource.NotFoundError{
Message: fmt.Sprintf("tag option resource association not found (%s): %s", tfservicecatalog.TagOptionResourceAssociationID(tagOptionID, resourceID), err),
}
}

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

if output == nil {
return nil, StatusNotFound, &resource.NotFoundError{
Message: fmt.Sprintf("finding tag option resource association (%s): empty response", tfservicecatalog.TagOptionResourceAssociationID(tagOptionID, resourceID)),
}
}

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

TagOptionResourceAssociationReadyTimeout = 3 * time.Minute
TagOptionResourceAssociationDeleteTimeout = 3 * time.Minute

StatusNotFound = "NOT_FOUND"
StatusUnavailable = "UNAVAILABLE"

Expand Down Expand Up @@ -333,3 +336,33 @@ func BudgetResourceAssociationDeleted(conn *servicecatalog.ServiceCatalog, budge

return err
}

func TagOptionResourceAssociationReady(conn *servicecatalog.ServiceCatalog, tagOptionID, resourceID string) (*servicecatalog.ResourceDetail, error) {
stateConf := &resource.StateChangeConf{
Pending: []string{StatusNotFound, StatusUnavailable},
Target: []string{servicecatalog.StatusAvailable},
Refresh: TagOptionResourceAssociationStatus(conn, tagOptionID, resourceID),
Timeout: TagOptionResourceAssociationReadyTimeout,
}

outputRaw, err := stateConf.WaitForState()

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

return nil, err
}

func TagOptionResourceAssociationDeleted(conn *servicecatalog.ServiceCatalog, tagOptionID, resourceID string) error {
stateConf := &resource.StateChangeConf{
Pending: []string{servicecatalog.StatusAvailable},
Target: []string{StatusNotFound, StatusUnavailable},
Refresh: TagOptionResourceAssociationStatus(conn, tagOptionID, resourceID),
Timeout: TagOptionResourceAssociationDeleteTimeout,
}

_, err := stateConf.WaitForState()

return err
}
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,7 @@ func Provider() *schema.Provider {
"aws_servicecatalog_product": resourceAwsServiceCatalogProduct(),
"aws_servicecatalog_service_action": resourceAwsServiceCatalogServiceAction(),
"aws_servicecatalog_tag_option": resourceAwsServiceCatalogTagOption(),
"aws_servicecatalog_tag_option_resource_association": resourceAwsServiceCatalogTagOptionResourceAssociation(),
"aws_servicecatalog_product_portfolio_association": resourceAwsServiceCatalogProductPortfolioAssociation(),
"aws_service_discovery_http_namespace": resourceAwsServiceDiscoveryHttpNamespace(),
"aws_service_discovery_private_dns_namespace": resourceAwsServiceDiscoveryPrivateDnsNamespace(),
Expand Down
170 changes: 170 additions & 0 deletions aws/resource_aws_servicecatalog_tag_option_resource_association.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package aws

import (
"fmt"
"log"
"time"

"github.com/aws/aws-sdk-go/aws"
"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"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
iamwaiter "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam/waiter"
tfservicecatalog "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/servicecatalog"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/servicecatalog/waiter"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource"
)

func resourceAwsServiceCatalogTagOptionResourceAssociation() *schema.Resource {
return &schema.Resource{
Create: resourceAwsServiceCatalogTagOptionResourceAssociationCreate,
Read: resourceAwsServiceCatalogTagOptionResourceAssociationRead,
Delete: resourceAwsServiceCatalogTagOptionResourceAssociationDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"resource_arn": {
Type: schema.TypeString,
Computed: true,
},
"resource_created_time": {
Type: schema.TypeString,
Computed: true,
},
"resource_description": {
Type: schema.TypeString,
Computed: true,
},
"resource_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"resource_name": {
Type: schema.TypeString,
Computed: true,
},
"tag_option_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}

func resourceAwsServiceCatalogTagOptionResourceAssociationCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).scconn

input := &servicecatalog.AssociateTagOptionWithResourceInput{
ResourceId: aws.String(d.Get("resource_id").(string)),
TagOptionId: aws.String(d.Get("tag_option_id").(string)),
}

var output *servicecatalog.AssociateTagOptionWithResourceOutput
err := resource.Retry(iamwaiter.PropagationTimeout, func() *resource.RetryError {
var err error

output, err = conn.AssociateTagOptionWithResource(input)

if tfawserr.ErrMessageContains(err, servicecatalog.ErrCodeInvalidParametersException, "profile does not exist") {
return resource.RetryableError(err)
}

if err != nil {
return resource.NonRetryableError(err)
}

return nil
})

if tfresource.TimedOut(err) {
output, err = conn.AssociateTagOptionWithResource(input)
}

if err != nil {
return fmt.Errorf("error associating Service Catalog Tag Option with Resource: %w", err)
}

if output == nil {
return fmt.Errorf("error creating Service Catalog Tag Option Resource Association: empty response")
}

d.SetId(tfservicecatalog.TagOptionResourceAssociationID(d.Get("tag_option_id").(string), d.Get("resource_id").(string)))

return resourceAwsServiceCatalogTagOptionResourceAssociationRead(d, meta)
}

func resourceAwsServiceCatalogTagOptionResourceAssociationRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).scconn

tagOptionID, resourceID, err := tfservicecatalog.TagOptionResourceAssociationParseID(d.Id())

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

output, err := waiter.TagOptionResourceAssociationReady(conn, tagOptionID, resourceID)

if !d.IsNewResource() && tfresource.NotFound(err) {
log.Printf("[WARN] Service Catalog Tag Option Resource Association (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

if err != nil {
return fmt.Errorf("error describing Service Catalog Tag Option Resource Association (%s): %w", d.Id(), err)
}

if output == nil {
return fmt.Errorf("error getting Service Catalog Tag Option Resource Association (%s): empty response", d.Id())
}

if output.CreatedTime != nil {
d.Set("resource_created_time", output.CreatedTime.Format(time.RFC3339))
}

d.Set("resource_arn", output.ARN)
d.Set("resource_description", output.Description)
d.Set("resource_id", output.Id)
d.Set("resource_name", output.Name)
d.Set("tag_option_id", tagOptionID)

return nil
}

func resourceAwsServiceCatalogTagOptionResourceAssociationDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).scconn

tagOptionID, resourceID, err := tfservicecatalog.TagOptionResourceAssociationParseID(d.Id())

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

input := &servicecatalog.DisassociateTagOptionFromResourceInput{
ResourceId: aws.String(resourceID),
TagOptionId: aws.String(tagOptionID),
}

_, err = conn.DisassociateTagOptionFromResource(input)

if tfawserr.ErrCodeEquals(err, servicecatalog.ErrCodeResourceNotFoundException) {
return nil
}

if err != nil {
return fmt.Errorf("error disassociating Service Catalog Tag Option from Resource (%s): %w", d.Id(), err)
}

err = waiter.TagOptionResourceAssociationDeleted(conn, tagOptionID, resourceID)

if err != nil && !tfresource.NotFound(err) {
return fmt.Errorf("error waiting for Service Catalog Tag Option Resource Disassociation (%s): %w", d.Id(), err)
}

return nil
}
Loading