diff --git a/internal/reconcile/reconcile.go b/internal/reconcile/reconcile.go index aa5c70f56..e136c4146 100644 --- a/internal/reconcile/reconcile.go +++ b/internal/reconcile/reconcile.go @@ -64,6 +64,11 @@ var kindDependencies = map[string][]string{ // Write `spec: []` to mean that on purpose. func parseDocuments(registry map[string]Reconciler, data []byte) ([]parsedDocument, error) { dec := yaml.NewDecoder(bytes.NewReader(data)) + // Reject unknown top-level keys (a stray "metadata:", a typo'd "apiVarsion") + // instead of ignoring them, the same way entry decoding does. The document's + // own spec content is decoded separately, so this only guards the outer + // apiVersion/kind/spec envelope. + dec.KnownFields(true) var docs []parsedDocument for { var doc document diff --git a/internal/reconcile/reconcile_test.go b/internal/reconcile/reconcile_test.go index 85865007c..7b3833efd 100644 --- a/internal/reconcile/reconcile_test.go +++ b/internal/reconcile/reconcile_test.go @@ -112,6 +112,29 @@ func TestRun_SpecHandling(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 1, rec.called) }) + + t.Run("a document with an unknown top-level field is rejected", func(t *testing.T) { + rec := &fakeReconciler{} + reg := map[string]Reconciler{KindPlatformUser: rec} + + // A stray top-level key (here a typo of "spec") must fail the file rather + // than being ignored, the same way entry decoding rejects unknown fields. + _, err := Run(context.Background(), reg, []byte("kind: PlatformUser\nspec: []\nspce: oops\n"), false) + assert.ErrorContains(t, err, "field spce not found") + assert.Zero(t, rec.called) + }) + + t.Run("an unknown field in a later document is rejected", func(t *testing.T) { + rec := &fakeReconciler{} + reg := map[string]Reconciler{KindPlatformUser: rec} + + // The stray key sits in the second document. The check must fire on every + // document, not only the first, which is the whole point of checking the file. + file := []byte("kind: PlatformUser\nspec: []\n---\nkind: PlatformUser\nspec: []\nspce: oops\n") + _, err := Run(context.Background(), reg, file, false) + assert.ErrorContains(t, err, "field spce not found") + assert.Zero(t, rec.called) + }) } func TestExport_Errors(t *testing.T) {