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

Feature/add resource tags for appmesh mesh resource #8111

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
27 changes: 25 additions & 2 deletions aws/resource_aws_appmesh_mesh.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ func resourceAwsAppmeshMesh() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},

"tags": tagsSchema(),
},
}
}
Expand All @@ -85,6 +87,7 @@ func resourceAwsAppmeshMeshCreate(d *schema.ResourceData, meta interface{}) erro
req := &appmesh.CreateMeshInput{
MeshName: aws.String(meshName),
Spec: expandAppmeshMeshSpec(d.Get("spec").([]interface{})),
Tags: tagsFromMapAppmesh(d.Get("tags").(map[string]interface{})),
}

log.Printf("[DEBUG] Creating App Mesh service mesh: %#v", req)
Expand All @@ -104,7 +107,7 @@ func resourceAwsAppmeshMeshRead(d *schema.ResourceData, meta interface{}) error
resp, err := conn.DescribeMesh(&appmesh.DescribeMeshInput{
MeshName: aws.String(d.Id()),
})
if isAWSErr(err, "NotFoundException", "") {
if isAWSErr(err, appmesh.ErrCodeNotFoundException, "") {
log.Printf("[WARN] App Mesh service mesh (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
Expand All @@ -127,6 +130,16 @@ func resourceAwsAppmeshMeshRead(d *schema.ResourceData, meta interface{}) error
return fmt.Errorf("error setting spec: %s", err)
}

err = saveTagsAppmesh(conn, d, aws.StringValue(resp.Mesh.Metadata.Arn))
if isAWSErr(err, appmesh.ErrCodeNotFoundException, "") {
log.Printf("[WARN] App Mesh service mesh (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}
if err != nil {
return fmt.Errorf("error saving tags: %s", err)
}

return nil
}

Expand All @@ -147,6 +160,16 @@ func resourceAwsAppmeshMeshUpdate(d *schema.ResourceData, meta interface{}) erro
}
}

err := setTagsAppmesh(conn, d, d.Get("arn").(string))
if isAWSErr(err, appmesh.ErrCodeNotFoundException, "") {
log.Printf("[WARN] App Mesh service mesh (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}
if err != nil {
return fmt.Errorf("error setting tags: %s", err)
}

return resourceAwsAppmeshMeshRead(d, meta)
}

Expand All @@ -157,7 +180,7 @@ func resourceAwsAppmeshMeshDelete(d *schema.ResourceData, meta interface{}) erro
_, err := conn.DeleteMesh(&appmesh.DeleteMeshInput{
MeshName: aws.String(d.Id()),
})
if isAWSErr(err, "NotFoundException", "") {
if isAWSErr(err, appmesh.ErrCodeNotFoundException, "") {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice. Could you also replace the hard-coded string value in resourceAwsAppmeshMeshRead()? https:/terraform-providers/terraform-provider-aws/blob/1bb0dead12829bf6c9da9e3170f52a8f24984497/aws/resource_aws_appmesh_mesh.go#L107

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed it.

return nil
}
if err != nil {
Expand Down
93 changes: 93 additions & 0 deletions aws/resource_aws_appmesh_mesh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,60 @@ func testAccAwsAppmeshMesh_egressFilter(t *testing.T) {
})
}

func testAccAwsAppmeshMesh_tags(t *testing.T) {
var mesh appmesh.MeshData
resourceName := "aws_appmesh_mesh.foo"
rName := fmt.Sprintf("tf-test-%d", acctest.RandInt())

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAppmeshMeshDestroy,
Steps: []resource.TestStep{
{
Config: testAccAppmeshMeshConfigWithTags(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAppmeshMeshExists(
resourceName, &mesh),
resource.TestCheckResourceAttr(
resourceName, "tags.%", "2"),
resource.TestCheckResourceAttr(
resourceName, "tags.foo", "bar"),
resource.TestCheckResourceAttr(
resourceName, "tags.good", "bad"),
),
},
{
Config: testAccAppmeshMeshConfigWithUpdateTags(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAppmeshMeshExists(
resourceName, &mesh),
resource.TestCheckResourceAttr(
resourceName, "tags.%", "3"),
resource.TestCheckResourceAttr(
resourceName, "tags.good", "bad2"),
resource.TestCheckResourceAttr(
resourceName, "tags.fizz", "buzz"),
),
},
{
Config: testAccAppmeshMeshConfigWithRemoveTags(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAppmeshMeshExists(
resourceName, &mesh),
resource.TestCheckResourceAttr(
resourceName, "tags.%", "1"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccCheckAppmeshMeshDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).appmeshconn

Expand Down Expand Up @@ -200,3 +254,42 @@ resource "aws_appmesh_mesh" "foo" {
}
`, name, egressFilterType)
}

func testAccAppmeshMeshConfigWithTags(name string) string {
return fmt.Sprintf(`
resource "aws_appmesh_mesh" "foo" {
name = "%s"

tags = {
foo = "bar"
good = "bad"
}
}
`, name)
}

func testAccAppmeshMeshConfigWithUpdateTags(name string) string {
return fmt.Sprintf(`
resource "aws_appmesh_mesh" "foo" {
name = "%s"

tags = {
foo = "bar"
good = "bad2"
fizz = "buzz"
}
}
`, name)
}

func testAccAppmeshMeshConfigWithRemoveTags(name string) string {
return fmt.Sprintf(`
resource "aws_appmesh_mesh" "foo" {
name = "%s"

tags = {
foo = "bar"
}
}
`, name)
}
1 change: 1 addition & 0 deletions aws/resource_aws_appmesh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ func TestAccAWSAppmesh(t *testing.T) {
"Mesh": {
"basic": testAccAwsAppmeshMesh_basic,
"egressFilter": testAccAwsAppmeshMesh_egressFilter,
"tags": testAccAwsAppmeshMesh_tags,
},
"Route": {
"httpRoute": testAccAwsAppmeshRoute_httpRoute,
Expand Down
131 changes: 131 additions & 0 deletions aws/tagsAppmesh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package aws

import (
"log"
"regexp"

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

// setTags is a helper to set the tags for a resource. It expects the
// tags field to be named "tags"
func setTagsAppmesh(conn *appmesh.AppMesh, d *schema.ResourceData, arn string) error {
if d.HasChange("tags") {
oraw, nraw := d.GetChange("tags")
o := oraw.(map[string]interface{})
n := nraw.(map[string]interface{})
create, remove := diffTagsAppmesh(tagsFromMapAppmesh(o), tagsFromMapAppmesh(n))

// Set tags
if len(remove) > 0 {
input := appmesh.UntagResourceInput{
ResourceArn: aws.String(arn),
TagKeys: remove,
}
log.Printf("[DEBUG] Removing Appmesh tags: %s", input)
_, err := conn.UntagResource(&input)
if err != nil {
return err
}
}
if len(create) > 0 {
input := appmesh.TagResourceInput{
ResourceArn: aws.String(arn),
Tags: create,
}
log.Printf("[DEBUG] Adding Appmesh tags: %s", input)
_, err := conn.TagResource(&input)
if err != nil {
return err
}
}
}

return nil
}

// diffTags takes our tags locally and the ones remotely and returns
// the set of tags that must be created, and the set of tags that must
// be destroyed.
func diffTagsAppmesh(oldTags, newTags []*appmesh.TagRef) ([]*appmesh.TagRef, []*string) {
// First, we're creating everything we have
create := make(map[string]interface{})
for _, t := range newTags {
create[aws.StringValue(t.Key)] = aws.StringValue(t.Value)
}

// Build the list of what to remove
var remove []*string
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed it.

for _, t := range oldTags {
old, ok := create[aws.StringValue(t.Key)]
if !ok || old != aws.StringValue(t.Value) {
remove = append(remove, t.Key)
} else if ok {
// already present so remove from new
delete(create, aws.StringValue(t.Key))
}
}

return tagsFromMapAppmesh(create), remove
}

// tagsFromMap returns the tags for the given map of data.
func tagsFromMapAppmesh(m map[string]interface{}) []*appmesh.TagRef {
var result []*appmesh.TagRef
for k, v := range m {
t := &appmesh.TagRef{
Key: aws.String(k),
Value: aws.String(v.(string)),
}
if !tagIgnoredAppmesh(t) {
result = append(result, t)
}
}

return result
}

// tagsToMap turns the list of tags into a map.
func tagsToMapAppmesh(ts []*appmesh.TagRef) map[string]string {
result := make(map[string]string)
for _, t := range ts {
if !tagIgnoredAppmesh(t) {
result[aws.StringValue(t.Key)] = aws.StringValue(t.Value)
}
}

return result
}

func saveTagsAppmesh(conn *appmesh.AppMesh, d *schema.ResourceData, arn string) error {
resp, err := conn.ListTagsForResource(&appmesh.ListTagsForResourceInput{
ResourceArn: aws.String(arn),
})
if err != nil {
return err
}

var dt []*appmesh.TagRef
if len(resp.Tags) > 0 {
dt = resp.Tags
}

return d.Set("tags", tagsToMapAppmesh(dt))
}

// compare a tag against a list of strings and checks if it should
// be ignored or not
func tagIgnoredAppmesh(t *appmesh.TagRef) bool {
filter := []string{"^aws:"}
for _, v := range filter {
log.Printf("[DEBUG] Matching %v with %v\n", v, aws.StringValue(t.Key))
r, _ := regexp.MatchString(v, aws.StringValue(t.Key))
if r {
log.Printf("[DEBUG] Found AWS specific tag %s (val: %s), ignoring.\n", aws.StringValue(t.Key), aws.StringValue(t.Value))
return true
}
}
return false
}
Loading