cmd/flux: search for kind within the YAML document instead of line+1

detectFileUpgrades hard-coded the kind: lookup at apiVersion line+1,
so manifests with any other field (metadata:, comments, annotations)
or reversed field order silently returned "no migration needed" even
when apiVersion was deprecated (#5839).

Search within the current YAML document boundaries: scan backwards
to the previous '---' separator or start-of-file, then forwards to
the next '---' separator. First kind: line wins. The order is
backwards-then-forwards so that for a conventional apiVersion/kind
ordering we only scan the single line + the document boundary; the
forward fallback covers the reversed-order case.

Adds a regression fixture with three documents covering:
  - apiVersion first, kind after metadata:
  - kind first, apiVersion after metadata:
  - kind first, apiVersion last

Fixes #5839

Signed-off-by: SAY-5 <SAY-5@users.noreply.github.com>
This commit is contained in:
SAY-5 2026-04-21 17:06:51 -07:00
parent 4e78a9d7e0
commit e8d8562b9e
5 changed files with 71 additions and 9 deletions

View file

@ -624,18 +624,35 @@ func (f *FileSystemMigrator) detectFileUpgrades(file string) ([]APIUpgrade, erro
continue
}
// Parse kind.
if line+1 >= len(lines) {
continue
}
kindLine := lines[line+1]
// Parse kind. YAML allows fields to be ordered arbitrarily, so the
// kind: line may appear before or after apiVersion: within the same
// document. Search within the current document boundaries (delimited
// by "---" separators) rather than hard-coding line+1.
const kindPrefix = "kind: "
idx = strings.Index(kindLine, kindPrefix)
if idx == -1 {
kind := ""
for j := line - 1; j >= 0; j-- {
if strings.HasPrefix(strings.TrimSpace(lines[j]), "---") {
break
}
if k := strings.Index(lines[j], kindPrefix); k != -1 {
kind = strings.Split(strings.TrimSpace(lines[j][k+len(kindPrefix):]), " ")[0]
break
}
}
if kind == "" {
for j := line + 1; j < len(lines); j++ {
if strings.HasPrefix(strings.TrimSpace(lines[j]), "---") {
break
}
if k := strings.Index(lines[j], kindPrefix); k != -1 {
kind = strings.Split(strings.TrimSpace(lines[j][k+len(kindPrefix):]), " ")[0]
break
}
}
}
if kind == "" {
continue
}
kindValuePrefix := strings.TrimSpace(kindLine[idx+len(kindPrefix):])
kind := strings.Split(kindValuePrefix, " ")[0]
// Build GroupKind.
gk := schema.GroupKind{

View file

@ -71,6 +71,17 @@ func TestFileSystemMigrator(t *testing.T) {
},
},
},
{
name: "migrate single file with non-adjacent apiVersion and kind",
path: "testdata/migrate/file-system/non-adjacent-kind.yaml",
outputGolden: "testdata/migrate/file-system/non-adjacent-kind.yaml.output.golden",
writtenFiles: []writtenFile{
{
file: "testdata/migrate/file-system/non-adjacent-kind.yaml",
goldenFile: "testdata/migrate/file-system/non-adjacent-kind.yaml.golden",
},
},
},
{
name: "migrate files in directory",
path: "testdata/migrate/file-system/dir",

View file

@ -0,0 +1,14 @@
apiVersion: image.toolkit.fluxcd.io/v1beta1
metadata:
name: podinfo
kind: ImageRepository
---
metadata:
name: alpine
kind: ImagePolicy
apiVersion: image.toolkit.fluxcd.io/v1beta2
---
kind: ImageUpdateAutomation
metadata:
name: flux-system
apiVersion: image.toolkit.fluxcd.io/v1beta1

View file

@ -0,0 +1,14 @@
apiVersion: image.toolkit.fluxcd.io/v1
metadata:
name: podinfo
kind: ImageRepository
---
metadata:
name: alpine
kind: ImagePolicy
apiVersion: image.toolkit.fluxcd.io/v1
---
kind: ImageUpdateAutomation
metadata:
name: flux-system
apiVersion: image.toolkit.fluxcd.io/v1

View file

@ -0,0 +1,6 @@
► starting migration of custom resources
✚ testdata/migrate/file-system/non-adjacent-kind.yaml:1: ImageRepository v1beta1 -> v1
✚ testdata/migrate/file-system/non-adjacent-kind.yaml:9: ImagePolicy v1beta2 -> v1
✚ testdata/migrate/file-system/non-adjacent-kind.yaml:14: ImageUpdateAutomation v1beta1 -> v1
✔ file testdata/migrate/file-system/non-adjacent-kind.yaml migrated successfully
✔ custom resources migrated successfully