From 0fe0c98a0188ee8b97eacdfb2b3fb8e02831962e Mon Sep 17 00:00:00 2001 From: Jimmi Dyson Date: Thu, 5 Sep 2024 12:18:35 +0100 Subject: [PATCH] build: Include Calico images in image list Calico images are deployed by the tigera-operator and as such are not discoverable by the helm list images plugin. Using the extra images file functionality of the helm list images plugin allows for adding these images as templated strings that will adapt with the chart version as it is updated in future. This commit also fixes up some other things: - Use `filepath` instead of `path` for portabiity - Output the image list to stdout from make to allow user to decide where to redirect list to - Omit changing dir from image list when running as goreleaser hook - Slightly more defensive when checkinbg errors --- .goreleaser.yml | 2 +- hack/tools/fetch-images/main.go | 132 +++++++++++++++++++++++--------- make/addons.mk | 9 +-- 3 files changed, 99 insertions(+), 44 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index e648df5d6..ab5f5946b 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -56,7 +56,7 @@ before: - make template-helm-repository - | sh -ec 'if [ {{ .IsSnapshot }} == false ] ; then - env CAREN_VERSION=v{{ trimprefix .Version "v" }} make list-images + make --no-print-directory CAREN_VERSION=v{{ trimprefix .Version "v" }} list-images >caren-images.txt fi' builds: diff --git a/hack/tools/fetch-images/main.go b/hack/tools/fetch-images/main.go index d26172036..017bdbaee 100644 --- a/hack/tools/fetch-images/main.go +++ b/hack/tools/fetch-images/main.go @@ -9,7 +9,7 @@ import ( "fmt" "io" "os" - "path" + "path/filepath" "slices" "strings" "text/template" @@ -20,6 +20,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/yaml" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" ) @@ -30,10 +31,11 @@ const ( ) type ChartInfo struct { - repo string - name string - valuesFile string - stringValues []string + repo string + name string + valuesFile string + stringValues []string + extraImagesFiles []string } func main() { @@ -47,9 +49,9 @@ func main() { flagSet.StringVar(&chartDirectory, "chart-directory", "", "path to chart directory for CAREN") flagSet.StringVar(&helmChartConfigMap, "helm-chart-configmap", "", - "path to chart directory for CAREN") + "path to helm chart configmap for CAREN") flagSet.StringVar(&carenVersion, "caren-version", "", - "caren version for images override") + "CAREN version for images override") err := flagSet.Parse(args[1:]) if err != nil { fmt.Println("failed to parse args", err.Error()) @@ -66,7 +68,7 @@ func main() { os.Exit(1) } if chartDirectory == "" || helmChartConfigMap == "" { - fmt.Println("chart-directory helm-chart-configmap must be set") + fmt.Println("chart-directory and helm-chart-configmap must be set") os.Exit(1) } i := &ChartInfo{ @@ -97,15 +99,10 @@ func main() { } func EnsureFullPath(filename string) (string, error) { - if path.IsAbs(filename) { - return filename, nil - } - wd, err := os.Getwd() + fullPath, err := filepath.Abs(filename) if err != nil { - return "", fmt.Errorf("failed to get wd: %w", err) + return "", err } - fullPath := path.Join(wd, filename) - fullPath = path.Clean(fullPath) _, err = os.Stat(fullPath) if err != nil { return "", err @@ -129,7 +126,7 @@ func getImagesForAddons(helmChartConfigMap, carenChartDirectory string) ([]strin if err != nil { return nil, fmt.Errorf("failed to unmarshal configmap to object %w", err) } - images := []string{} + var images []string for _, chartInfoRaw := range cm.Data { var settings HelmChartFromConfigMap err = yamlv2.Unmarshal([]byte(chartInfoRaw), &settings) @@ -140,11 +137,16 @@ func getImagesForAddons(helmChartConfigMap, carenChartDirectory string) ([]strin name: settings.ChartName, repo: settings.Repository, } - valuesFile := getValuesFileForChartIfNeeded(settings.ChartName, carenChartDirectory) + valuesFile, err := getValuesFileForChartIfNeeded(settings.ChartName, carenChartDirectory) + if err != nil { + return nil, fmt.Errorf("failed to get values file for %s: %w", settings.ChartName, err) + } if valuesFile != "" { info.valuesFile = valuesFile } - if settings.ChartName == "aws-cloud-controller-manager" { + + switch settings.ChartName { + case "aws-cloud-controller-manager": values, err := getHelmValues(carenChartDirectory) if err != nil { return nil, err @@ -152,7 +154,7 @@ func getImagesForAddons(helmChartConfigMap, carenChartDirectory string) ([]strin awsImages, found, err := unstructured.NestedStringMap(values, "hooks", "ccm", "aws", "k8sMinorVersionToCCMVersion") if !found { return images, fmt.Errorf("failed to find k8sMinorVersionToCCMVersion from file %s", - path.Join(carenChartDirectory, "values.yaml")) + filepath.Join(carenChartDirectory, "values.yaml")) } if err != nil { return images, fmt.Errorf("failed to get map k8sMinorVersionToCCMVersion with error %w", @@ -168,9 +170,33 @@ func getImagesForAddons(helmChartConfigMap, carenChartDirectory string) ([]strin } images = append(images, chartImages...) } - // skip the to next addon because we got what we needed + // skip to the next addon because we got what we needed continue + case "tigera-operator": + extraImagesFile, err := os.CreateTemp("", "") + if err != nil { + return nil, fmt.Errorf("failed to create temp file for extra Calico images: %w", err) + } + defer os.Remove(extraImagesFile.Name()) //nolint:gocritic // Won't be leaked. + _, err = extraImagesFile.WriteString(` +{{default "docker.io/" .Values.installation.registry }}calico/cni:{{ .Chart.Version }} +{{default "docker.io/" .Values.installation.registry }}calico/kube-controllers:{{ .Chart.Version }} +{{default "docker.io/" .Values.installation.registry }}calico/node:{{ .Chart.Version }} +{{default "docker.io/" .Values.installation.registry }}calico/apiserver:{{ .Chart.Version }} +{{default "docker.io/" .Values.installation.registry }}calico/pod2daemon-flexvol:{{ .Chart.Version }} +{{default "docker.io/" .Values.installation.registry }}calico/typha:{{ .Chart.Version }} +{{default "docker.io/" .Values.installation.registry }}calico/csi:{{ .Chart.Version }} +{{default "docker.io/" .Values.installation.registry }}calico/node-driver-registrar:{{ .Chart.Version }} +{{default "docker.io/" .Values.installation.registry }}calico/ctl:{{ .Chart.Version }} +`) + _ = extraImagesFile.Close() + if err != nil { + return nil, fmt.Errorf("failed to write to temp file for extra Calico images: %w", err) + } + + info.extraImagesFiles = append(info.extraImagesFiles, extraImagesFile.Name()) } + chartImages, err := getImagesForChart(info) if err != nil { return nil, fmt.Errorf("failed to get images for %s with error %w", info.name, err) @@ -181,7 +207,7 @@ func getImagesForAddons(helmChartConfigMap, carenChartDirectory string) ([]strin } func getHelmValues(carenChartDirectory string) (map[string]interface{}, error) { - values := path.Join(carenChartDirectory, "values.yaml") + values := filepath.Join(carenChartDirectory, "values.yaml") valuesFile, err := os.Open(values) if err != nil { return nil, fmt.Errorf("failed to open file %s with %w", values, err) @@ -195,21 +221,50 @@ func getHelmValues(carenChartDirectory string) (map[string]interface{}, error) { return m, nil } -func getValuesFileForChartIfNeeded(chartName, carenChartDirectory string) string { +func getValuesFileForChartIfNeeded(chartName, carenChartDirectory string) (string, error) { switch chartName { case "nutanix-csi-storage": - return path.Join(carenChartDirectory, "addons", "csi", "nutanix", defaultHelmAddonFilename) + return filepath.Join(carenChartDirectory, "addons", "csi", "nutanix", defaultHelmAddonFilename), nil case "node-feature-discovery": - return path.Join(carenChartDirectory, "addons", "nfd", defaultHelmAddonFilename) + return filepath.Join(carenChartDirectory, "addons", "nfd", defaultHelmAddonFilename), nil case "snapshot-controller": - return path.Join(carenChartDirectory, "addons", "csi", "snapshot-controller", defaultHelmAddonFilename) + return filepath.Join(carenChartDirectory, "addons", "csi", "snapshot-controller", defaultHelmAddonFilename), nil case "cilium": - return path.Join(carenChartDirectory, "addons", "cni", "cilium", defaultHelmAddonFilename) + return filepath.Join(carenChartDirectory, "addons", "cni", "cilium", defaultHelmAddonFilename), nil + // Calico values differ slightly per provider, but that does not have a material imapct on the images required + // so we can use the default values file for AWS provider. + case "tigera-operator": + f := filepath.Join(carenChartDirectory, "addons", "cni", "calico", "aws", defaultHelmAddonFilename) + tempFile, err := os.CreateTemp("", "") + if err != nil { + return "", fmt.Errorf("failed to create temp file: %w", err) + } + + // CAAPH uses unstructured internally, so we need to create an unstructured copy of a cluster + // to pass to the CAAPH values template. + c, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(&clusterv1.Cluster{}) + + templateInput := struct { + Cluster map[string]interface{} + }{ + Cluster: c, + } + + err = template.Must(template.New(defaultHelmAddonFilename).ParseFiles(f)).Execute(tempFile, &templateInput) + if err != nil { + return "", fmt.Errorf("failed to execute helm values template %w", err) + } + + return tempFile.Name(), nil // this uses the values from kustomize because the file at addons/cluster-autoscaler/values-template.yaml // is a file that is templated case "cluster-autoscaler": - f := path.Join(carenChartDirectory, "addons", "cluster-autoscaler", defaultHelmAddonFilename) - tempFile, _ := os.CreateTemp("", "") + f := filepath.Join(carenChartDirectory, "addons", "cluster-autoscaler", defaultHelmAddonFilename) + tempFile, err := os.CreateTemp("", "") + if err != nil { + return "", fmt.Errorf("failed to create temp file: %w", err) + } + c := clusterv1.Cluster{ ObjectMeta: metav1.ObjectMeta{ Name: "tmplCluster", @@ -227,25 +282,26 @@ func getValuesFileForChartIfNeeded(chartName, carenChartDirectory string) string Cluster: &c, } - template.Must(template.New(defaultHelmAddonFilename).ParseFiles(f)).Execute(tempFile, &templateInput) - return tempFile.Name() + err = template.Must(template.New(defaultHelmAddonFilename).ParseFiles(f)).Execute(tempFile, &templateInput) + if err != nil { + return "", fmt.Errorf("failed to execute helm values template %w", err) + } + + return tempFile.Name(), nil default: - return "" + return "", nil } } func getImagesForChart(info *ChartInfo) ([]string, error) { images := pkg.Images{} images.SetChart(info.name) - if info.repo != "" { - images.RepoURL = info.repo - } + images.RepoURL = info.repo if info.valuesFile != "" { - images.ValueFiles.Set(info.valuesFile) - } - if len(info.stringValues) > 0 { - images.StringValues = info.stringValues + _ = images.ValueFiles.Set(info.valuesFile) } + images.StringValues = info.stringValues + images.ExtraImagesFiles = info.extraImagesFiles // kubeVersion needs to be set for some addons images.KubeVersion = "v1.29.0" images.SetRelease("sample") diff --git a/make/addons.mk b/make/addons.mk index dc716c72c..ec422bbe6 100644 --- a/make/addons.mk +++ b/make/addons.mk @@ -87,8 +87,7 @@ template-helm-repository: generate-mindthegap-repofile ## this is used by goreal .PHONY: list-images list-images: - cd hack/tools/fetch-images && go build - ./hack/tools/fetch-images/fetch-images \ - -chart-directory=./charts/cluster-api-runtime-extensions-nutanix/ \ - -helm-chart-configmap=./charts/cluster-api-runtime-extensions-nutanix/templates/helm-config.yaml \ - -caren-version=$(CAREN_VERSION) >> caren-images.txt + cd hack/tools/fetch-images && go run . \ + -chart-directory=$(PWD)/charts/cluster-api-runtime-extensions-nutanix/ \ + -helm-chart-configmap=$(PWD)/charts/cluster-api-runtime-extensions-nutanix/templates/helm-config.yaml \ + -caren-version=$(CAREN_VERSION)