Repository: suse-edge/edge-image-builder (go)

Issue:
Helm validation does not check if a chart uses an undefined repository
Given the following definition:

```
apiVersion: 1.0
image:
  arch: x86_64
  imageType: raw
  baseImage: SLE-Micro.x86_64-5.5.0-Default-GM.raw
  outputImageName: raw-image.raw
kubernetes:
  version: v1.28.7+k3s1
  helm:
    charts:
    - name: edge-stuff
      version: 0.14.3
      repositoryName: suse-edge
    - name: not-edge-stuff
      version: 0.14.3
      repositoryName: not-suse-edge
    repositories:
      - name: not-suse-edge
        url: https://suse-edge.github.io/charts
```

I would have expected a validation-time failure because `edge-stuff` references the repo `suse-edge` that doesn't exist. Instead, it goes until build time when I see the following in eib-build.log:

```
2024-04-08T14:25:59.787Z	WARN	kubernetes/cluster.go:103	Kubernetes config file '/eib/kubernetes/config/server.yaml' was not provided
2024-04-08T14:25:59.787Z	INFO	combustion/network.go:53	Configuring network component...
2024-04-08T14:25:59.787Z	INFO	combustion/network.go:57	Skipping network component, configuration is not provided
2024-04-08T14:25:59.787Z	INFO	combustion/rpm.go:32	Skipping RPM component. Configuration is not provided
2024-04-08T14:25:59.787Z	INFO	combustion/elemental.go:33	Skipping elemental registration component, configuration is not provided
2024-04-08T14:25:59.787Z	FATAL	build/build.go:102	An error occurred building the image: configuring combustion: configuring component "embedded artifact registry": configuring embedded artifact registry: parsing helm charts: repository not found for chart edge-stuff
github.com/suse-edge/edge-image-builder/pkg/cli/build.Run
	/home/abuild/rpmbuild/BUILD/edge-image-builder-1.0.0.rc3/pkg/cli/build/build.go:102
github.com/urfave/cli/v2.(*Command).Run
	/home/abuild/rpmbuild/BUILD/edge-image-builder-1.0.0.rc3/vendor/github.com/urfave/cli/v2/command.go:279
github.com/urfave/cli/v2.(*Command).Run
	/home/abuild/rpmbuild/BUILD/edge-image-builder-1.0.0.rc3/vendor/github.com/urfave/cli/v2/command.go:272
github.com/urfave/cli/v2.(*App).RunContext
	/home/abuild/rpmbuild/BUILD/edge-image-builder-1.0.0.rc3/vendor/github.com/urfave/cli/v2/app.go:337
github.com/urfave/cli/v2.(*App).Run
	/home/abuild/rpmbuild/BUILD/edge-image-builder-1.0.0.rc3/vendor/github.com/urfave/cli/v2/app.go:311
main.main
	/home/abuild/rpmbuild/BUILD/edge-image-builder-1.0.0.rc3/cmd/eib/main.go:19
runtime.main
	/usr/lib64/go/1.22/src/runtime/proc.go:271
```

@dbw7 Is this intentional? It's possible there's some other way of getting repos that I'm not aware of that can only be done at runtime, but on the surface this feels like a missing check.

Pull request:
Fix Helm chart validation bug
Closes #359 .

Patch:
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 93d1355..7ac3052 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -13,6 +13,7 @@
 ## Bug Fixes
 
 * [#352](https://github.com/suse-edge/edge-image-builder/issues/352) - Resizing raw images results in dracut-pre-mount failure
+* [#359](https://github.com/suse-edge/edge-image-builder/issues/359) - Helm validation does not check if a chart uses an undefined repository
 
 ---
 
diff --git a/pkg/image/validation/kubernetes.go b/pkg/image/validation/kubernetes.go
index 0c45734..3f0671c 100644
--- a/pkg/image/validation/kubernetes.go
+++ b/pkg/image/validation/kubernetes.go
@@ -161,6 +161,11 @@ func validateHelm(k8s *image.Kubernetes, imageConfigDir string) []FailedValidati
 		return failures
 	}
 
+	var helmRepositoryNames []string
+	for _, repo := range k8s.Helm.Repositories {
+		helmRepositoryNames = append(helmRepositoryNames, repo.Name)
+	}
+
 	if failure := validateHelmChartDuplicates(k8s.Helm.Charts); failure != "" {
 		failures = append(failures, FailedValidation{
 			UserMessage: failure,
@@ -170,7 +175,7 @@ func validateHelm(k8s *image.Kubernetes, imageConfigDir string) []FailedValidati
 	seenHelmRepos := make(map[string]bool)
 	for _, chart := range k8s.Helm.Charts {
 		c := chart
-		failures = append(failures, validateChart(&c, imageConfigDir)...)
+		failures = append(failures, validateChart(&c, helmRepositoryNames, imageConfigDir)...)
 
 		seenHelmRepos[chart.RepositoryName] = true
 	}
@@ -183,7 +188,7 @@ func validateHelm(k8s *image.Kubernetes, imageConfigDir string) []FailedValidati
 	return failures
 }
 
-func validateChart(chart *image.HelmChart, imageConfigDir string) []FailedValidation {
+func validateChart(chart *image.HelmChart, repositoryNames []string, imageConfigDir string) []FailedValidation {
 	var failures []FailedValidation
 
 	if chart.Name == "" {
@@ -196,6 +201,10 @@ func validateChart(chart *image.HelmChart, imageConfigDir string) []FailedValida
 		failures = append(failures, FailedValidation{
 			UserMessage: fmt.Sprintf("Helm chart 'repositoryName' field for %q must be defined.", chart.Name),
 		})
+	} else if !slices.Contains(repositoryNames, chart.RepositoryName) {
+		failures = append(failures, FailedValidation{
+			UserMessage: fmt.Sprintf("Helm chart 'repositoryName' %q for Helm chart %q does not match the name of any defined repository.", chart.RepositoryName, chart.Name),
+		})
 	}
 
 	if chart.Version == "" {


Test patch:
diff --git a/pkg/image/validation/kubernetes_test.go b/pkg/image/validation/kubernetes_test.go
index 5a16e85..19e0dbf 100644
--- a/pkg/image/validation/kubernetes_test.go
+++ b/pkg/image/validation/kubernetes_test.go
@@ -97,6 +97,7 @@ func TestValidateKubernetes(t *testing.T) {
 				"Entries in 'urls' must begin with either 'http://' or 'https://'.",
 				"Helm chart 'name' field must be defined.",
 				"Helm repository 'name' field for \"apache-repo\" must match the 'repositoryName' field in at least one defined Helm chart.",
+				"Helm chart 'repositoryName' \"another-apache-repo\" for Helm chart \"\" does not match the name of any defined repository.",
 			},
 		},
 	}
@@ -481,7 +482,7 @@ func TestValidateHelmCharts(t *testing.T) {
 				"Helm chart 'name' field must be defined.",
 			},
 		},
-		`helm chart no repository name`: {
+		`helm chart undefined repository name`: {
 			K8s: image.Kubernetes{
 				Helm: image.Helm{
 					Charts: []image.HelmChart{
@@ -508,6 +509,33 @@ func TestValidateHelmCharts(t *testing.T) {
 				"Helm chart 'repositoryName' field for \"metallb\" must be defined.",
 			},
 		},
+		`helm chart no matching repository name`: {
+			K8s: image.Kubernetes{
+				Helm: image.Helm{
+					Charts: []image.HelmChart{
+						{
+							Name:           "kubevirt",
+							RepositoryName: "suse-edge",
+							Version:        "0.2.2",
+						},
+						{
+							Name:           "metallb",
+							RepositoryName: "this-is-not-suse-edge",
+							Version:        "0.14.3",
+						},
+					},
+					Repositories: []image.HelmRepository{
+						{
+							Name: "suse-edge",
+							URL:  "https://suse-edge.github.io/charts",
+						},
+					},
+				},
+			},
+			ExpectedFailedMessages: []string{
+				"Helm chart 'repositoryName' \"this-is-not-suse-edge\" for Helm chart \"metallb\" does not match the name of any defined repository.",
+			},
+		},
 		`helm chart no version`: {
 			K8s: image.Kubernetes{
 				Helm: image.Helm{