From e6dcf8159c27652e0758ff047f5a1a64044eebd9 Mon Sep 17 00:00:00 2001 From: Julian Sommer <115516918+juliansommer@users.noreply.github.com> Date: Tue, 6 Oct 2026 11:19:42 +1000 Subject: [PATCH] fix: refuse an omitted DBParameterGroupFamily and use AWS's unknown-family message --- spinifex/handlers/rds/engine.go | 2 +- spinifex/handlers/rds/parametergroup.go | 9 ++--- spinifex/handlers/rds/parametergroup_test.go | 39 +++++++++++++++----- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/spinifex/handlers/rds/engine.go b/spinifex/handlers/rds/engine.go index a3f93a711..da5263e2d 100644 --- a/spinifex/handlers/rds/engine.go +++ b/spinifex/handlers/rds/engine.go @@ -155,7 +155,7 @@ func engineForFamily(family string) (Engine, error) { engine, ok := enginesByFamily[normaliseFamily(family)] if !ok { return Engine{}, awserrors.Errorf(awserrors.ErrorInvalidParameterValue, - "DBParameterGroupFamily %q is not offered; supported families are %s", + "ParameterGroupFamily %s is not a valid parameter group family. Supported families: %s.", family, strings.Join(SupportedParameterGroupFamilies(), ", ")) } return engine, nil diff --git a/spinifex/handlers/rds/parametergroup.go b/spinifex/handlers/rds/parametergroup.go index 24a7bf51d..38c4a1a27 100644 --- a/spinifex/handlers/rds/parametergroup.go +++ b/spinifex/handlers/rds/parametergroup.go @@ -477,13 +477,12 @@ func engineForDefaultParameterGroup(name string) (Engine, bool) { return Engine{}, false } -// An omitted family takes PostgreSQL's rather than failing, which AWS clients -// that predate a second engine depend on. The cost is that a group meant for -// another engine is created as a PostgreSQL one and is only refused a call -// later, when an instance of that engine tries to attach it. +// The family is required: defaulting it would create a group for an engine the +// caller never named, refused only later when another engine tries to attach it. func validateParameterGroupFamily(family string) (string, error) { if normaliseFamily(family) == "" { - return enginePostgres.ParameterGroupFamily(), nil + return "", awserrors.Errorf(awserrors.ErrorInvalidParameterValue, + "The parameter ParameterGroupFamily must be provided and must not be empty.") } engine, err := engineForFamily(family) if err != nil { diff --git a/spinifex/handlers/rds/parametergroup_test.go b/spinifex/handlers/rds/parametergroup_test.go index 5d0c3385e..09d32ab33 100644 --- a/spinifex/handlers/rds/parametergroup_test.go +++ b/spinifex/handlers/rds/parametergroup_test.go @@ -90,24 +90,45 @@ func TestCreateDBParameterGroup_StoresAnEmptyGroup(t *testing.T) { } } -// An omitted family can only mean the one family this platform offers, so it -// takes the pin rather than failing a Terraform config that leaves it out. -func TestCreateDBParameterGroup_DefaultsTheFamilyAndRejectsAnother(t *testing.T) { +// AWS refuses an omitted and an empty family with the same code and message. +func TestCreateDBParameterGroup_RefusesAnOmittedOrEmptyFamily(t *testing.T) { t.Parallel() h := newCreateHarness(t, testBaseDomain) - input := parameterGroupInput(testParameterGroup) - input.DBParameterGroupFamily = nil - out, err := h.svc.CreateDBParameterGroup(t.Context(), input, testAccountID) - require.NoError(t, err) - assert.Equal(t, "postgres18", aws.StringValue(out.DBParameterGroup.DBParameterGroupFamily)) + for name, family := range map[string]*string{"omitted": nil, "empty": aws.String("")} { + input := parameterGroupInput(testParameterGroup) + input.DBParameterGroupFamily = family + _, err := h.svc.CreateDBParameterGroup(t.Context(), input, testAccountID) + code, message, ok := awserrors.ResolveErrorDetail(err) + require.True(t, ok, name) + assert.Equal(t, awserrors.ErrorInvalidParameterValue, code, name) + assert.Equal(t, "The parameter ParameterGroupFamily must be provided and must not be empty.", message, name) + } + + _, err := h.svc.DescribeDBParameterGroups(t.Context(), &rds.DescribeDBParameterGroupsInput{ + DBParameterGroupName: aws.String(testParameterGroup), + }, testAccountID) + assert.Equal(t, awserrors.ErrorDBParameterGroupNotFound, awserrors.ValidErrorCodeFromError(err), + "a refused create must not leave a group behind") +} + +func TestCreateDBParameterGroup_RejectsAnUnofferedFamily(t *testing.T) { + t.Parallel() + h := newCreateHarness(t, testBaseDomain) other := parameterGroupInput("mysql-tuned") other.DBParameterGroupFamily = aws.String("mysql8.0") - _, err = h.svc.CreateDBParameterGroup(t.Context(), other, testAccountID) + _, err := h.svc.CreateDBParameterGroup(t.Context(), other, testAccountID) require.Error(t, err) assert.Equal(t, awserrors.ErrorInvalidParameterValue, awserrors.ValidErrorCodeFromError(err), "the code has to survive resolution or the client sees a 500") + + // AWS's wording, with the offered families appended so the caller can retry. + _, message, _ := awserrors.ResolveErrorDetail(err) + const prefix = "ParameterGroupFamily mysql8.0 is not a valid parameter group family. Supported families: " + require.True(t, strings.HasPrefix(message, prefix), message) + named := strings.Split(strings.TrimSuffix(strings.TrimPrefix(message, prefix), "."), ", ") + assert.ElementsMatch(t, SupportedParameterGroupFamilies(), named) } // A customer group under the reserved prefix would be indistinguishable from the