From 45fdbc0444eab9261f763753ae4feed89b062bba Mon Sep 17 00:00:00 2001 From: Julian Sommer <115516918+juliansommer@users.noreply.github.com> Date: Wed, 30 Sep 2026 11:05:44 +1000 Subject: [PATCH] fix: apply tag: filters to DescribeTags rows --- spinifex/handlers/ec2/tags/service_impl.go | 19 ++++++++++ .../handlers/ec2/tags/service_impl_test.go | 35 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/spinifex/handlers/ec2/tags/service_impl.go b/spinifex/handlers/ec2/tags/service_impl.go index fac37be80..acb22f645 100644 --- a/spinifex/handlers/ec2/tags/service_impl.go +++ b/spinifex/handlers/ec2/tags/service_impl.go @@ -344,6 +344,9 @@ func (s *TagsServiceImpl) DescribeTags(ctx context.Context, input *ec2.DescribeT if !filterutil.MatchesAny(filters["tag-key"], key) || !filterutil.MatchesAny(filters["tag-value"], value) { continue } + if !rowMatchesTagFilters(filters, key, value) { + continue + } tags = append(tags, &ec2.TagDescription{ ResourceId: aws.String(resourceID), @@ -361,6 +364,22 @@ func (s *TagsServiceImpl) DescribeTags(ctx context.Context, input *ec2.DescribeT }, nil } +// rowMatchesTagFilters applies each tag: filter to one tag row, as +// tag-key and tag-value are applied here: the row must be that key, with a +// value the filter names. +func rowMatchesTagFilters(filters map[string][]string, key, value string) bool { + for name, values := range filters { + tagKey, ok := strings.CutPrefix(name, "tag:") + if !ok { + continue + } + if key != tagKey || !filterutil.MatchesAny(values, value) { + return false + } + } + return true +} + // DeleteTags removes tags from the specified resources. func (s *TagsServiceImpl) DeleteTags(ctx context.Context, input *ec2.DeleteTagsInput, accountID string) (*ec2.DeleteTagsOutput, error) { if input == nil { diff --git a/spinifex/handlers/ec2/tags/service_impl_test.go b/spinifex/handlers/ec2/tags/service_impl_test.go index a17e4a738..a7dd989a5 100644 --- a/spinifex/handlers/ec2/tags/service_impl_test.go +++ b/spinifex/handlers/ec2/tags/service_impl_test.go @@ -322,6 +322,41 @@ func TestDescribeTags_FilterByTagKeyAndTagValue(t *testing.T) { assert.Empty(t, describe("tag-value", "zz-awsdiff-none")) } +// TestDescribeTags_FilterByTagKeyName checks that a tag: filter selects +// the matching tag row, and only rows of resources that carry it. +func TestDescribeTags_FilterByTagKeyName(t *testing.T) { + svc, _ := setupTestTagsService(t) + + for id, run := range map[string]string{"subnet-test1": "run-1", "subnet-test2": "run-2"} { + _, err := svc.CreateTags(context.Background(), &ec2.CreateTagsInput{ + Resources: []*string{aws.String(id)}, + Tags: []*ec2.Tag{ + {Key: aws.String("env"), Value: aws.String("prod")}, + {Key: aws.String("awsdiff"), Value: aws.String(run)}, + }, + }, testAccountID) + require.NoError(t, err) + } + + result, err := svc.DescribeTags(context.Background(), &ec2.DescribeTagsInput{ + Filters: []*ec2.Filter{ + {Name: aws.String("resource-type"), Values: []*string{aws.String("subnet")}}, + {Name: aws.String("tag:awsdiff"), Values: []*string{aws.String("run-1")}}, + }, + }, testAccountID) + require.NoError(t, err) + require.Len(t, result.Tags, 1) + assert.Equal(t, "subnet-test1", aws.StringValue(result.Tags[0].ResourceId)) + assert.Equal(t, "awsdiff", aws.StringValue(result.Tags[0].Key)) + assert.Equal(t, "run-1", aws.StringValue(result.Tags[0].Value)) + + result, err = svc.DescribeTags(context.Background(), &ec2.DescribeTagsInput{ + Filters: []*ec2.Filter{{Name: aws.String("tag:awsdiff"), Values: []*string{aws.String("zz-awsdiff-none")}}}, + }, testAccountID) + require.NoError(t, err) + assert.Empty(t, result.Tags) +} + // TestDescribeTags_Empty tests listing tags when none exist. func TestDescribeTags_Empty(t *testing.T) { svc, _ := setupTestTagsService(t)