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: New resource #19300

Merged
merged 12 commits into from
May 13, 2021
Merged
Show file tree
Hide file tree
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/19300.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
aws_servicecatalog_tag_option
```
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 @@ -44,3 +44,27 @@ func ProductStatus(conn *servicecatalog.ServiceCatalog, acceptLanguage, productI
return output, aws.StringValue(output.ProductViewDetail.Status), err
}
}

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

output, err := conn.DescribeTagOption(input)

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

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

if output == nil || output.TagOptionDetail == nil {
return nil, StatusUnavailable, fmt.Errorf("error describing tag option: empty tag option detail")
}

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

TagOptionReadyTimeout = 3 * time.Minute
TagOptionDeleteTimeout = 3 * time.Minute

StatusNotFound = "NOT_FOUND"
StatusUnavailable = "UNAVAILABLE"

Expand Down Expand Up @@ -52,3 +55,37 @@ func ProductDeleted(conn *servicecatalog.ServiceCatalog, acceptLanguage, product

return nil, err
}

func TagOptionReady(conn *servicecatalog.ServiceCatalog, id string) (*servicecatalog.TagOptionDetail, error) {
stateConf := &resource.StateChangeConf{
Pending: []string{StatusNotFound, StatusUnavailable},
Target: []string{servicecatalog.StatusAvailable},
Refresh: TagOptionStatus(conn, id),
Timeout: TagOptionReadyTimeout,
}

outputRaw, err := stateConf.WaitForState()

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

return nil, err
}

func TagOptionDeleted(conn *servicecatalog.ServiceCatalog, id string) error {
stateConf := &resource.StateChangeConf{
Pending: []string{servicecatalog.StatusAvailable},
Target: []string{StatusNotFound, StatusUnavailable},
Refresh: TagOptionStatus(conn, id),
Timeout: TagOptionDeleteTimeout,
}

_, err := stateConf.WaitForState()

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

return err
}
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -1006,6 +1006,7 @@ func Provider() *schema.Provider {
"aws_securityhub_standards_subscription": resourceAwsSecurityHubStandardsSubscription(),
"aws_servicecatalog_portfolio": resourceAwsServiceCatalogPortfolio(),
"aws_servicecatalog_product": resourceAwsServiceCatalogProduct(),
"aws_servicecatalog_tag_option": resourceAwsServiceCatalogTagOption(),
"aws_service_discovery_http_namespace": resourceAwsServiceDiscoveryHttpNamespace(),
"aws_service_discovery_private_dns_namespace": resourceAwsServiceDiscoveryPrivateDnsNamespace(),
"aws_service_discovery_public_dns_namespace": resourceAwsServiceDiscoveryPublicDnsNamespace(),
Expand Down
198 changes: 198 additions & 0 deletions aws/resource_aws_servicecatalog_tag_option.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package aws

import (
"fmt"
"log"

"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"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/servicecatalog/waiter"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource"
)

func resourceAwsServiceCatalogTagOption() *schema.Resource {
return &schema.Resource{
Create: resourceAwsServiceCatalogTagOptionCreate,
Read: resourceAwsServiceCatalogTagOptionRead,
Update: resourceAwsServiceCatalogTagOptionUpdate,
Delete: resourceAwsServiceCatalogTagOptionDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"active": {
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"key": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"owner": {
Type: schema.TypeString,
Computed: true,
},
"value": {
Type: schema.TypeString,
Required: true,
},
},
}
}

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

input := &servicecatalog.CreateTagOptionInput{
Key: aws.String(d.Get("key").(string)),
Value: aws.String(d.Get("value").(string)),
}

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

output, err = conn.CreateTagOption(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.CreateTagOption(input)
}

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

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

d.SetId(aws.StringValue(output.TagOptionDetail.Id))

// Active is not a field of CreateTagOption but is a field of UpdateTagOption. In order to create an
// inactive Tag Option, you must create an active one and then update it (but calling this resource's
// Update will error with ErrCodeDuplicateResourceException because Value is unchanged).
if v, ok := d.GetOk("active"); !ok {
_, err = conn.UpdateTagOption(&servicecatalog.UpdateTagOptionInput{
Id: aws.String(d.Id()),
Active: aws.Bool(v.(bool)),
})

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

return resourceAwsServiceCatalogTagOptionRead(d, meta)
}

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

output, err := waiter.TagOptionReady(conn, d.Id())

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

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

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

d.Set("active", output.Active)
d.Set("key", output.Key)
d.Set("owner", output.Owner)
d.Set("value", output.Value)

return nil
}

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

input := &servicecatalog.UpdateTagOptionInput{
Id: aws.String(d.Id()),
}

// UpdateTagOption() is very particular about what it receives. Only fields that change should
// be included or it will throw servicecatalog.ErrCodeDuplicateResourceException, "already exists"

if d.HasChange("active") {
input.Active = aws.Bool(d.Get("active").(bool))
}

if d.HasChange("value") {
input.Value = aws.String(d.Get("value").(string))
}

err := resource.Retry(iamwaiter.PropagationTimeout, func() *resource.RetryError {
_, err := conn.UpdateTagOption(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) {
_, err = conn.UpdateTagOption(input)
}

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

return resourceAwsServiceCatalogTagOptionRead(d, meta)
}

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

input := &servicecatalog.DeleteTagOptionInput{
Id: aws.String(d.Id()),
}

_, err := conn.DeleteTagOption(input)

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

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

if err := waiter.TagOptionDeleted(conn, d.Id()); err != nil {
return fmt.Errorf("error waiting for Service Catalog Tag Option (%s) to be deleted: %w", d.Id(), err)
}

return nil
}
Loading