diff --git a/spinifex/foundation/aws/identifiers/identifiers.go b/spinifex/foundation/aws/identifiers/identifiers.go new file mode 100644 index 000000000..2d48d7112 --- /dev/null +++ b/spinifex/foundation/aws/identifiers/identifiers.go @@ -0,0 +1,19 @@ +// Package identifiers provides small helpers for AWS resource identifiers. +package identifiers + +// Distinct returns the distinct non-nil values of a request's resource-ID +// slice, in request order. A describe that reads one document per named ID must +// not read or report the same resource twice because the request listed it +// twice. +func Distinct(ids []*string) []string { + distinct := make([]string, 0, len(ids)) + seen := make(map[string]bool, len(ids)) + for _, id := range ids { + if id == nil || seen[*id] { + continue + } + seen[*id] = true + distinct = append(distinct, *id) + } + return distinct +} diff --git a/spinifex/foundation/aws/identifiers/identifiers_test.go b/spinifex/foundation/aws/identifiers/identifiers_test.go new file mode 100644 index 000000000..385500025 --- /dev/null +++ b/spinifex/foundation/aws/identifiers/identifiers_test.go @@ -0,0 +1,31 @@ +package identifiers + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// A describe that reads one document per named ID pays for every entry it is +// given, so a repeated or nil ID has to be dropped before the fan-out and the +// surviving order has to be the request's. +func TestDistinct(t *testing.T) { + first, second := "vol-1", "vol-2" + tests := []struct { + name string + ids []*string + want []string + }{ + {name: "nil slice", ids: nil, want: []string{}}, + {name: "only nil entries", ids: []*string{nil, nil}, want: []string{}}, + {name: "request order", ids: []*string{&second, &first}, want: []string{"vol-2", "vol-1"}}, + {name: "duplicates", ids: []*string{&first, &second, &first}, want: []string{"vol-1", "vol-2"}}, + {name: "nil among values", ids: []*string{&first, nil, &second}, want: []string{"vol-1", "vol-2"}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, Distinct(tt.ids)) + }) + } +} diff --git a/spinifex/handlers/ec2/image/service_impl.go b/spinifex/handlers/ec2/image/service_impl.go index 2f3a33544..d79f4fd6d 100644 --- a/spinifex/handlers/ec2/image/service_impl.go +++ b/spinifex/handlers/ec2/image/service_impl.go @@ -19,6 +19,7 @@ import ( ec2platform "github.com/mulgadc/spinifex/spinifex/domains/ec2/platform" "github.com/mulgadc/spinifex/spinifex/foundation/aws/errors" awsfilters "github.com/mulgadc/spinifex/spinifex/foundation/aws/filters" + awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers" handlers_ec2_snapshot "github.com/mulgadc/spinifex/spinifex/handlers/ec2/snapshot" "github.com/mulgadc/spinifex/spinifex/providers/ebs" "github.com/mulgadc/spinifex/spinifex/providers/objectstore" @@ -179,7 +180,7 @@ func (s *ImageServiceImpl) describeImagesByIDs(ctx context.Context, input *ec2.D parsedFilters map[string][]string, encryptedAtRest bool) (*ec2.DescribeImagesOutput, error) { // Only nil entries names nothing, which the enumerating path answers with an // empty result rather than a missing ID. - imageIDs := utils.DistinctIDs(input.ImageIds) + imageIDs := awsidentifiers.Distinct(input.ImageIds) if len(imageIDs) == 0 { return &ec2.DescribeImagesOutput{}, nil } diff --git a/spinifex/handlers/ec2/snapshot/service_impl.go b/spinifex/handlers/ec2/snapshot/service_impl.go index a186fcad7..db344691e 100644 --- a/spinifex/handlers/ec2/snapshot/service_impl.go +++ b/spinifex/handlers/ec2/snapshot/service_impl.go @@ -21,6 +21,7 @@ import ( "github.com/mulgadc/spinifex/spinifex/domains/ec2/ebs/metadata" "github.com/mulgadc/spinifex/spinifex/foundation/aws/errors" awsfilters "github.com/mulgadc/spinifex/spinifex/foundation/aws/filters" + awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers" "github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore" "github.com/mulgadc/spinifex/spinifex/foundation/state/migrate" "github.com/mulgadc/spinifex/spinifex/handlers/ec2/volumestate" @@ -518,7 +519,7 @@ func (s *SnapshotServiceImpl) describeSnapshotsByIDs(ctx context.Context, input accountID string, parsedFilters map[string][]string, strict bool) (*ec2.DescribeSnapshotsOutput, error) { // Only nil entries names nothing, which the listing path answers with an // empty result rather than a missing ID. - snapshotIDs := utils.DistinctIDs(input.SnapshotIds) + snapshotIDs := awsidentifiers.Distinct(input.SnapshotIds) if len(snapshotIDs) == 0 { return &ec2.DescribeSnapshotsOutput{}, nil } diff --git a/spinifex/utils/utils.go b/spinifex/utils/utils.go index b6ab5ee58..269ad83c7 100644 --- a/spinifex/utils/utils.go +++ b/spinifex/utils/utils.go @@ -37,22 +37,6 @@ func GenerateResourceID(prefix string) string { return prefix + "-" + hex.EncodeToString(b)[:17] } -// DistinctIDs returns the distinct non-nil values of a request's resource-ID -// slice, in request order. A describe that reads one document per named ID must -// not read or report the same resource twice because the request listed it twice. -func DistinctIDs(ids []*string) []string { - distinct := make([]string, 0, len(ids)) - seen := make(map[string]bool, len(ids)) - for _, id := range ids { - if id == nil || seen[*id] { - continue - } - seen[*id] = true - distinct = append(distinct, *id) - } - return distinct -} - func MarshalToXML(payload any) ([]byte, error) { var buf bytes.Buffer enc := xml.NewEncoder(&buf) diff --git a/spinifex/utils/utils_test.go b/spinifex/utils/utils_test.go index d466d053b..473d34850 100644 --- a/spinifex/utils/utils_test.go +++ b/spinifex/utils/utils_test.go @@ -26,30 +26,6 @@ import ( "github.com/stretchr/testify/require" ) -// A describe that reads one document per named ID pays for every entry it is -// given, so a repeated or nil ID has to be dropped before the fan-out and the -// surviving order has to be the request's. -func TestDistinctIDs(t *testing.T) { - first, second := "vol-1", "vol-2" - tests := []struct { - name string - ids []*string - want []string - }{ - {name: "nil slice", ids: nil, want: []string{}}, - {name: "only nil entries", ids: []*string{nil, nil}, want: []string{}}, - {name: "request order", ids: []*string{&second, &first}, want: []string{"vol-2", "vol-1"}}, - {name: "duplicates", ids: []*string{&first, &second, &first}, want: []string{"vol-1", "vol-2"}}, - {name: "nil among values", ids: []*string{&first, nil, &second}, want: []string{"vol-1", "vol-2"}}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want, DistinctIDs(tt.ids)) - }) - } -} - func TestGenerateResourceID(t *testing.T) { tests := []struct { prefix string