Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions pkg/lint2/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,44 @@ func hasKind(path string, kind string) (bool, error) {
return false, nil
}

// hasAPIVersionKind checks if a YAML file contains a specific apiVersion and kind.
// Handles multi-document YAML files properly using yaml.NewDecoder.
// For files with syntax errors, falls back to simple regex matching to detect both apiVersion and kind.
func hasAPIVersionKind(path string, apiVersion string, kind string) (bool, error) {
data, err := os.ReadFile(path)
if err != nil {
return false, err
}

decoder := yaml.NewDecoder(bytes.NewReader(data))

for {
var doc struct {
Kind string `yaml:"kind"`
APIVersion string `yaml:"apiVersion"`
}

err := decoder.Decode(&doc)
if err != nil {
if err == io.EOF {
break
}
// Parse error - fall back to regex matching
kindPattern := fmt.Sprintf(`(?m)^kind:\s+%s\s*$`, regexp.QuoteMeta(kind))
apiPattern := fmt.Sprintf(`(?m)^apiVersion:\s+%s\s*$`, regexp.QuoteMeta(apiVersion))
kindMatched, _ := regexp.Match(kindPattern, data)
apiMatched, _ := regexp.Match(apiPattern, data)
return kindMatched && apiMatched, nil
}

if doc.Kind == kind && doc.APIVersion == apiVersion {
return true, nil
}
}

return false, nil
}

// discoverPreflightPaths discovers Preflight spec files from a glob pattern.
// This is a thin wrapper around discoverYAMLsByKind for backward compatibility.
//
Expand Down
140 changes: 140 additions & 0 deletions pkg/lint2/discovery_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2676,3 +2676,143 @@ func TestDiscoverSupportBundlePaths_ExplicitBypass(t *testing.T) {
t.Errorf("Expected bundle in dist/, got: %s", bundles[0])
}
}

// Phase 9 Tests: hasAPIVersionKind

func TestHasAPIVersionKind_ECConfigMatch(t *testing.T) {
tmpDir := t.TempDir()
path := filepath.Join(tmpDir, "ec.yaml")
content := `apiVersion: embeddedcluster.replicated.com/v1beta1
kind: Config
metadata:
name: ec
`
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
t.Fatal(err)
}

got, err := hasAPIVersionKind(path, "embeddedcluster.replicated.com/v1beta1", "Config")
if err != nil {
t.Fatalf("hasAPIVersionKind() error = %v", err)
}
if !got {
t.Errorf("hasAPIVersionKind() = false, want true for EC Config")
}
}

func TestHasAPIVersionKind_KOTSConfigMismatch(t *testing.T) {
tmpDir := t.TempDir()
path := filepath.Join(tmpDir, "config.yaml")
content := `apiVersion: kots.io/v1beta1
kind: Config
metadata:
name: config
`
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
t.Fatal(err)
}

got, err := hasAPIVersionKind(path, "embeddedcluster.replicated.com/v1beta1", "Config")
if err != nil {
t.Fatalf("hasAPIVersionKind() error = %v", err)
}
if got {
t.Errorf("hasAPIVersionKind() = true, want false for KOTS Config")
}
}

func TestHasAPIVersionKind_MultiDocumentWithECConfig(t *testing.T) {
tmpDir := t.TempDir()
path := filepath.Join(tmpDir, "multi.yaml")
content := `apiVersion: v1
kind: ConfigMap
metadata:
name: cm
---
apiVersion: embeddedcluster.replicated.com/v1beta1
kind: Config
metadata:
name: ec
`
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
t.Fatal(err)
}

got, err := hasAPIVersionKind(path, "embeddedcluster.replicated.com/v1beta1", "Config")
if err != nil {
t.Fatalf("hasAPIVersionKind() error = %v", err)
}
if !got {
t.Errorf("hasAPIVersionKind() = false, want true for multi-doc with EC Config")
}
}

func TestHasAPIVersionKind_MultiDocumentWithoutECConfig(t *testing.T) {
tmpDir := t.TempDir()
path := filepath.Join(tmpDir, "multi.yaml")
content := `apiVersion: v1
kind: ConfigMap
metadata:
name: cm
---
apiVersion: kots.io/v1beta1
kind: Config
metadata:
name: config
`
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
t.Fatal(err)
}

got, err := hasAPIVersionKind(path, "embeddedcluster.replicated.com/v1beta1", "Config")
if err != nil {
t.Fatalf("hasAPIVersionKind() error = %v", err)
}
if got {
t.Errorf("hasAPIVersionKind() = true, want false for multi-doc without EC Config")
}
}

func TestHasAPIVersionKind_EmptyFile(t *testing.T) {
tmpDir := t.TempDir()
path := filepath.Join(tmpDir, "empty.yaml")
if err := os.WriteFile(path, []byte(""), 0644); err != nil {
t.Fatal(err)
}

got, err := hasAPIVersionKind(path, "embeddedcluster.replicated.com/v1beta1", "Config")
if err != nil {
t.Fatalf("hasAPIVersionKind() error = %v", err)
}
if got {
t.Errorf("hasAPIVersionKind() = true, want false for empty file")
}
}

func TestHasAPIVersionKind_InvalidYAMLRegexFallback(t *testing.T) {
tmpDir := t.TempDir()
path := filepath.Join(tmpDir, "invalid.yaml")
content := `apiVersion: embeddedcluster.replicated.com/v1beta1
kind: Config
metadata:
name: ec
spec:
version: 3.0.0
extensions:
helmCharts:
- chart:
name: my-app
chartVersion: 1.2.3
`
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
t.Fatal(err)
}

got, err := hasAPIVersionKind(path, "embeddedcluster.replicated.com/v1beta1", "Config")
if err != nil {
t.Fatalf("hasAPIVersionKind() error = %v", err)
}
if !got {
t.Errorf("hasAPIVersionKind() = false, want true for valid EC Config")
}
}
Loading
Loading