diff --git a/pkg/common/engine.go b/pkg/common/engine.go index cacea43f..cf3ee7a6 100644 --- a/pkg/common/engine.go +++ b/pkg/common/engine.go @@ -84,6 +84,44 @@ func NormalizePlatformName(platform string) string { return p } +// Canonical tokens returned by NormalizeScopeName. +const ( + ScopeRegional = "regional" + ScopeZonal = "zonal" +) + +// NormalizeTenancyName normalizes an EC2 tenancy for comparison. "shared" is +// the operator spelling of the API value "default"; "dedicated" and "host" stay +// distinct. Unrecognized input returns "" (unknown, a wildcard). Mirrors the +// accepted inputs of parseEC2Tenancy in providers/aws/services/ec2/client.go, +// which pkg cannot import. +func NormalizeTenancyName(tenancy string) string { + switch t := strings.ToLower(strings.TrimSpace(tenancy)); t { + case "default", "shared": + return "default" + case "dedicated", "host": + return t + default: + return "" + } +} + +// NormalizeScopeName normalizes an EC2 reservation scope to ScopeRegional or +// ScopeZonal. Unrecognized input returns "" (unknown, a wildcard). Mirrors +// parseEC2Scope in providers/aws/services/ec2/client.go ("Region", +// "Availability Zone", and the hyphenated legacy spelling), which pkg cannot +// import. +func NormalizeScopeName(scope string) string { + switch strings.ToLower(strings.TrimSpace(scope)) { + case "region", "regional": + return ScopeRegional + case "availability zone", "availability-zone", "zonal": + return ScopeZonal + default: + return "" + } +} + // EngineFilterKey returns the value the --include-engines/--exclude-engines // filters compare against, and the normalizer to apply to each filter entry. // Databases and caches use their engine; compute (EC2) uses its platform, diff --git a/pkg/common/engine_test.go b/pkg/common/engine_test.go index 54e07be0..357fa353 100644 --- a/pkg/common/engine_test.go +++ b/pkg/common/engine_test.go @@ -69,3 +69,38 @@ func TestEngineFromDetails(t *testing.T) { }) } } + +func TestNormalizeTenancyName(t *testing.T) { + t.Parallel() + + tests := []struct{ in, want string }{ + {"default", "default"}, + {"Shared", "default"}, + {" SHARED ", "default"}, + {"Dedicated", "dedicated"}, + {"host", "host"}, + {"Bogus", ""}, + {"", ""}, + } + for _, tt := range tests { + assert.Equal(t, tt.want, NormalizeTenancyName(tt.in), "input %q", tt.in) + } +} + +func TestNormalizeScopeName(t *testing.T) { + t.Parallel() + + tests := []struct{ in, want string }{ + {"Region", ScopeRegional}, + {"regional", ScopeRegional}, + {" REGIONAL ", ScopeRegional}, + {"Availability Zone", ScopeZonal}, + {"availability-zone", ScopeZonal}, + {"Zonal", ScopeZonal}, + {"", ""}, + {"Bogus", ""}, + } + for _, tt := range tests { + assert.Equal(t, tt.want, NormalizeScopeName(tt.in), "input %q", tt.in) + } +} diff --git a/pkg/common/types.go b/pkg/common/types.go index d36c5e2a..120f2811 100644 --- a/pkg/common/types.go +++ b/pkg/common/types.go @@ -497,6 +497,14 @@ type Commitment struct { // means StartDate is a precise timestamp; true means the real instant // lies in [StartDate, StartDate+24h). StartDateIsDay bool `json:"start_date_is_day,omitempty"` + + // EC2 reserved-instance dimensions. Empty means unknown or a wildcard. + // Filled by the EC2 commitment listing from the reserved instance's + // product description, tenancy, scope and availability zone. + Platform string `json:"platform,omitempty"` + Tenancy string `json:"tenancy,omitempty"` + Scope string `json:"scope,omitempty"` + AvailabilityZone string `json:"availability_zone,omitempty"` } // OfferingDetails represents cloud provider offering details. diff --git a/pkg/common/types_test.go b/pkg/common/types_test.go index d62698ab..6e2020ce 100644 --- a/pkg/common/types_test.go +++ b/pkg/common/types_test.go @@ -598,3 +598,25 @@ func TestCommitment_StartDateIsDayJSON(t *testing.T) { require.NoError(t, err) assert.Contains(t, string(b), `"start_date_is_day":true`) } + +func TestCommitment_EC2DimensionsJSON(t *testing.T) { + empty, err := json.Marshal(Commitment{}) + require.NoError(t, err) + for _, key := range []string{`"platform"`, `"tenancy"`, `"scope"`, `"availability_zone"`} { + assert.NotContains(t, string(empty), key) + } + + in := Commitment{Platform: "Linux/UNIX", Tenancy: "default", Scope: "Availability Zone", AvailabilityZone: "us-east-1a"} + raw, err := json.Marshal(in) + require.NoError(t, err) + assert.Contains(t, string(raw), `"availability_zone":"us-east-1a"`) + var out Commitment + require.NoError(t, json.Unmarshal(raw, &out)) + assert.Equal(t, in, out) + + var old Commitment + require.NoError(t, json.Unmarshal([]byte(`{"provider":"aws","commitment_id":"ri-1","count":2}`), &old)) + assert.Equal(t, "ri-1", old.CommitmentID) + assert.Empty(t, old.Platform) + assert.Empty(t, old.AvailabilityZone) +}