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

Adding servicecatalog_portfolio resource #1694

Merged
merged 15 commits into from
Oct 18, 2017
3 changes: 3 additions & 0 deletions aws/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import (
"github.com/aws/aws-sdk-go/service/redshift"
"github.com/aws/aws-sdk-go/service/route53"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/servicecatalog"
"github.com/aws/aws-sdk-go/service/ses"
"github.com/aws/aws-sdk-go/service/sfn"
"github.com/aws/aws-sdk-go/service/simpledb"
Expand Down Expand Up @@ -144,6 +145,7 @@ type AWSClient struct {
appautoscalingconn *applicationautoscaling.ApplicationAutoScaling
autoscalingconn *autoscaling.AutoScaling
s3conn *s3.S3
scconn *servicecatalog.ServiceCatalog
sesConn *ses.SES
simpledbconn *simpledb.SimpleDB
sqsconn *sqs.SQS
Expand Down Expand Up @@ -379,6 +381,7 @@ func (c *Config) Client() (interface{}, error) {
client.redshiftconn = redshift.New(sess)
client.simpledbconn = simpledb.New(sess)
client.s3conn = s3.New(awsS3Sess)
client.scconn = servicecatalog.New(sess)
client.sesConn = ses.New(sess)
client.sfnconn = sfn.New(sess)
client.snsconn = sns.New(awsSnsSess)
Expand Down
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,7 @@ func Provider() terraform.ResourceProvider {
"aws_network_interface_sg_attachment": resourceAwsNetworkInterfaceSGAttachment(),
"aws_default_security_group": resourceAwsDefaultSecurityGroup(),
"aws_security_group_rule": resourceAwsSecurityGroupRule(),
"aws_servicecatalog_portfolio": resourceAwsServiceCatalogPortfolio(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also add the documentation for this resource?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ninir will do

"aws_simpledb_domain": resourceAwsSimpleDBDomain(),
"aws_ssm_activation": resourceAwsSsmActivation(),
"aws_ssm_association": resourceAwsSsmAssociation(),
Expand Down
228 changes: 228 additions & 0 deletions aws/resource_aws_servicecatalog_portfolio.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
package aws
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also add the documentation please? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ninir will do


import (
"fmt"
"log"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/servicecatalog"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsServiceCatalogPortfolio() *schema.Resource {
return &schema.Resource{
Create: resourceAwsServiceCatalogPortfolioCreate,
Read: resourceAwsServiceCatalogPortfolioRead,
Update: resourceAwsServiceCatalogPortfolioUpdate,
Delete: resourceAwsServiceCatalogPortfolioDelete,

Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(30 * time.Minute),
Update: schema.DefaultTimeout(30 * time.Minute),
Delete: schema.DefaultTimeout(30 * time.Minute),
},

Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Computed: true,
},
"arn": {
Type: schema.TypeString,
Computed: true,
},
"created_time": {
Type: schema.TypeString,
Computed: true,
},
"name": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validateServiceCatalogPortfolioName,
},
"description": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validateServiceCatalogPortfolioDescription,
},
"provider_name": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateServiceCatalogPortfolioProviderName,
},
"tags": tagsSchema(),
},
}
}
func resourceAwsServiceCatalogPortfolioCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).scconn
input := servicecatalog.CreatePortfolioInput{
AcceptLanguage: aws.String("en"),
}
name := d.Get("name").(string)
input.DisplayName = &name
now := time.Now()
input.IdempotencyToken = aws.String(fmt.Sprintf("%d", now.UnixNano()))

if v, ok := d.GetOk("description"); ok {
input.Description = aws.String(v.(string))
}

if v, ok := d.GetOk("provider_name"); ok {
input.ProviderName = aws.String(v.(string))
}

if v, ok := d.GetOk("tags"); ok {
tags := []*servicecatalog.Tag{}
t := v.(map[string]interface{})
for k, v := range t {
tag := servicecatalog.Tag{
Key: aws.String(k),
Value: aws.String(v.(string)),
}
tags = append(tags, &tag)
}
input.Tags = tags
}

log.Printf("[DEBUG] Creating Service Catalog Portfolio: %#v", input)
resp, err := conn.CreatePortfolio(&input)
if err != nil {
return fmt.Errorf("Creating Service Catalog Portfolio failed: %s", err.Error())
}
d.SetId(*resp.PortfolioDetail.Id)

return resourceAwsServiceCatalogPortfolioRead(d, meta)
}

func resourceAwsServiceCatalogPortfolioRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).scconn
input := servicecatalog.DescribePortfolioInput{
AcceptLanguage: aws.String("en"),
}
input.Id = aws.String(d.Id())

log.Printf("[DEBUG] Reading Service Catalog Portfolio: %#v", input)
resp, err := conn.DescribePortfolio(&input)
if err != nil {
if scErr, ok := err.(awserr.Error); ok && scErr.Code() == "ResourceNotFoundException" {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ninir can you suggest, or point me to a similar example, how to idiomatically write a test for this type of error in the tf test framework?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This kind of test is not simple to implement, due to the way to test: we need to create the resource, add a custom step to remove the object, and expect a non-empty plan.

In this example, you first have a test that expects an error, and then a test that expects a non empty plan.
Depending on our implementation, we will go for one or the other (I think it will be the non-empty plan)

The best way to proceed is perhaps to make build the provider and check it manually.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ninir Ran it manually, removed the resource and it works.

2017/09/19 06:17:46 [WARN] Service Catalog "port-123456781234" not found, removing from state

Still a bit hazy but will try to construct a non-empty plan test using the above.

Copy link
Contributor Author

@bw-intuit bw-intuit Sep 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ninir added a first pass at a test. Can you provide feedback?

Thanks

log.Printf("[WARN] Service Catalog Portfolio %q not found, removing from state", d.Id())
d.SetId("")
return nil
}
return fmt.Errorf("Reading ServiceCatalog Portfolio '%s' failed: %s", *input.Id, err.Error())
}
portfolioDetail := resp.PortfolioDetail
if err := d.Set("created_time", portfolioDetail.CreatedTime.Format(time.RFC3339)); err != nil {
log.Printf("[DEBUG] Error setting created_time: %s", err)
}
d.Set("arn", portfolioDetail.ARN)
d.Set("description", portfolioDetail.Description)
d.Set("name", portfolioDetail.DisplayName)
d.Set("provider_name", portfolioDetail.ProviderName)
tags := map[string]string{}
for _, tag := range resp.Tags {
tags[*tag.Key] = *tag.Value
}
d.Set("tags", tags)
return nil
}

func resourceAwsServiceCatalogPortfolioUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).scconn
input := servicecatalog.UpdatePortfolioInput{
AcceptLanguage: aws.String("en"),
Id: aws.String(d.Id()),
}

if d.HasChange("name") {
v, _ := d.GetOk("name")
input.DisplayName = aws.String(v.(string))
}

if d.HasChange("accept_language") {
v, _ := d.GetOk("accept_language")
input.AcceptLanguage = aws.String(v.(string))
}

if d.HasChange("description") {
v, _ := d.GetOk("description")
input.Description = aws.String(v.(string))
}

if d.HasChange("provider_name") {
v, _ := d.GetOk("provider_name")
input.ProviderName = aws.String(v.(string))
}

if d.HasChange("tags") {
currentTags, requiredTags := d.GetChange("tags")
log.Printf("[DEBUG] Current Tags: %#v", currentTags)
log.Printf("[DEBUG] Required Tags: %#v", requiredTags)

tagsToAdd, tagsToRemove := tagUpdates(requiredTags.(map[string]interface{}), currentTags.(map[string]interface{}))
log.Printf("[DEBUG] Tags To Add: %#v", tagsToAdd)
log.Printf("[DEBUG] Tags To Remove: %#v", tagsToRemove)
input.AddTags = tagsToAdd
input.RemoveTags = tagsToRemove
}

log.Printf("[DEBUG] Update Service Catalog Portfolio: %#v", input)
_, err := conn.UpdatePortfolio(&input)
if err != nil {
return fmt.Errorf("Updating Service Catalog Portfolio '%s' failed: %s", *input.Id, err.Error())
}
return resourceAwsServiceCatalogPortfolioRead(d, meta)
}

func tagUpdates(requriedTags, currentTags map[string]interface{}) ([]*servicecatalog.Tag, []*string) {
var tagsToAdd []*servicecatalog.Tag
var tagsToRemove []*string

for rk, rv := range requriedTags {
addTag := true
for ck, cv := range currentTags {
if (rk == ck) && (rv.(string) == cv.(string)) {
addTag = false
}
}
if addTag {
tag := &servicecatalog.Tag{Key: aws.String(rk), Value: aws.String(rv.(string))}
tagsToAdd = append(tagsToAdd, tag)
}
}

for ck, cv := range currentTags {
removeTag := true
for rk, rv := range requriedTags {
if (rk == ck) && (rv.(string) == cv.(string)) {
removeTag = false
}
}
if removeTag {
tagsToRemove = append(tagsToRemove, aws.String(ck))
}
}

return tagsToAdd, tagsToRemove
}

func resourceAwsServiceCatalogPortfolioDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).scconn
input := servicecatalog.DeletePortfolioInput{}
input.Id = aws.String(d.Id())

log.Printf("[DEBUG] Delete Service Catalog Portfolio: %#v", input)
_, err := conn.DeletePortfolio(&input)
if err != nil {
return fmt.Errorf("Deleting Service Catalog Portfolio '%s' failed: %s", *input.Id, err.Error())
}
return nil
}
Loading