Skip to content

Commit 7e3f3ff

Browse files
faiqdlipovetsky
authored andcommitted
ci: images tool (#822)
**What problem does this PR solve?**: Adds a tool to fetch all images CAREN uses **Which issue(s) this PR fixes**: Fixes # **How Has This Been Tested?**: <!-- Please describe the tests that you ran to verify your changes. Provide output from the tests and any manual steps needed to replicate the tests. --> I ran the following command from `hack/tools/fetch-images` ``` $ go run main.go -chart-directory=../../../charts/cluster-api-runtime-extensions-nutanix/ --helm-chart-configmap=../../../charts/cluster-api-runtime-extensions-nutanix/templates/helm-config.yaml -caren-version=v0.13.1 ghcr.io/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix:v0.13.1 ghcr.io/nutanix-cloud-native/caren-helm-reg:v0.13.1 registry.k8s.io/sig-storage/snapshot-controller:v8.0.1 registry.k8s.io/provider-aws/cloud-controller-manager:v1.27.7 registry.k8s.io/provider-aws/cloud-controller-manager:v1.28.6 registry.k8s.io/provider-aws/cloud-controller-manager:v1.29.3 registry.k8s.io/provider-aws/cloud-controller-manager:v1.30.1 public.ecr.aws/ebs-csi-driver/aws-ebs-csi-driver:v1.33.0 public.ecr.aws/eks-distro/kubernetes-csi/node-driver-registrar:v2.11.0-eks-1-30-10 public.ecr.aws/eks-distro/kubernetes-csi/livenessprobe:v2.13.0-eks-1-30-10 public.ecr.aws/ebs-csi-driver/aws-ebs-csi-driver:v1.33.0 public.ecr.aws/eks-distro/kubernetes-csi/external-provisioner:v5.0.1-eks-1-30-10 public.ecr.aws/eks-distro/kubernetes-csi/external-attacher:v4.6.1-eks-1-30-10 public.ecr.aws/eks-distro/kubernetes-csi/external-resizer:v1.11.1-eks-1-30-10 public.ecr.aws/eks-distro/kubernetes-csi/livenessprobe:v2.13.0-eks-1-30-10 registry.k8s.io/autoscaling/cluster-autoscaler:v1.30.0 quay.io/metallb/speaker:v0.14.8 quay.io/frrouting/frr:9.1.0 quay.io/metallb/speaker:v0.14.8 quay.io/metallb/controller:v0.14.8 registry.k8s.io/nfd/node-feature-discovery:v0.16.1-minimal registry.k8s.io/sig-storage/csi-node-driver-registrar:v2.9.3 docker.io/nutanix/ntnx-csi:3.0.0 registry.k8s.io/sig-storage/livenessprobe:v2.11.0 registry.k8s.io/sig-storage/csi-provisioner:v3.6.3 registry.k8s.io/sig-storage/csi-attacher:v4.4.3 registry.k8s.io/sig-storage/csi-resizer:v1.9.3 registry.k8s.io/sig-storage/csi-snapshotter:v3.0.3 docker.io/nutanix/ntnx-csi:3.0.0 registry.k8s.io/sig-storage/livenessprobe:v2.11.0 registry.k8s.io/sig-storage/csi-external-health-monitor-controller:v0.10.0 docker.io/nutanix/ntnx-csi-precheck:3.0.0 quay.io/cilium/cilium:v1.16.0 quay.io/cilium/cilium-envoy@sha256:bd5ff8c66716080028f414ec1cb4f7dc66f40d2fb5a009fff187f4a9b90b566b quay.io/cilium/operator-generic:v1.16.0 quay.io/cilium/certgen:v0.2.0 docker.io/rancher/local-path-provisioner:v0.0.28 ghcr.io/nutanix-cloud-native/cloud-provider-nutanix/controller:v0.3.2 quay.io/tigera/operator:v1.34.0 ``` **Special notes for your reviewer**: <!-- Use this to provide any additional information to the reviewers. This may include: - Best way to review the PR. - Where the author wants the most review attention on. - etc. --> Depends on #896.
1 parent 9756cbf commit 7e3f3ff

File tree

6 files changed

+2022
-0
lines changed

6 files changed

+2022
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,5 @@ node_modules/
4747
/_artifacts/
4848
test/e2e/config/caren-envsubst.yaml
4949
/release-metadata.yaml
50+
hack/tools/fetch-images/fetch-images
51+
caren-images.txt

.goreleaser.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Copyright 2023 Nutanix. All rights reserved.
22
# SPDX-License-Identifier: Apache-2.0
33

4+
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
45
version: 2
56

67
project_name: cluster-api-runtime-extensions-nutanix
@@ -30,6 +31,7 @@ release:
3031
name_template: metadata.yaml
3132
- glob: runtime-extension-components.yaml
3233
- glob: ./charts/{{ .ProjectName }}/defaultclusterclasses/*.yaml
34+
- glob: caren-images.txt
3335

3436
before:
3537
hooks:
@@ -52,6 +54,10 @@ before:
5254
".releaseSeries |= (. + [{contract: \"v1beta1\", major: {{ .Major }}, minor: {{ .Minor }}}] | unique)" \
5355
metadata.yaml >release-metadata.yaml'
5456
- make template-helm-repository
57+
- |
58+
sh -ec 'if [ {{ .IsSnapshot }} == false ] ; then
59+
env CAREN_VERSION=v{{ trimprefix .Version "v" }} make list-images
60+
fi'
5561
5662
builds:
5763
- id: cluster-api-runtime-extensions-nutanix

hack/tools/fetch-images/main.go

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
// Copyright 2024 Nutanix. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package main
5+
6+
import (
7+
"bytes"
8+
"flag"
9+
"fmt"
10+
"io"
11+
"os"
12+
"path"
13+
"slices"
14+
"strings"
15+
"text/template"
16+
17+
"github.com/d2iq-labs/helm-list-images/pkg"
18+
"github.com/d2iq-labs/helm-list-images/pkg/k8s"
19+
yamlv2 "gopkg.in/yaml.v2"
20+
corev1 "k8s.io/api/core/v1"
21+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
23+
"k8s.io/apimachinery/pkg/util/yaml"
24+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
25+
)
26+
27+
const (
28+
createImagesCMD = "create-images"
29+
defaultHelmAddonFilename = "values-template.yaml"
30+
)
31+
32+
type ChartInfo struct {
33+
repo string
34+
name string
35+
valuesFile string
36+
stringValues []string
37+
}
38+
39+
func main() {
40+
args := os.Args
41+
var (
42+
chartDirectory string
43+
helmChartConfigMap string
44+
carenVersion string
45+
)
46+
flagSet := flag.NewFlagSet(createImagesCMD, flag.ExitOnError)
47+
flagSet.StringVar(&chartDirectory, "chart-directory", "",
48+
"path to chart directory for CAREN")
49+
flagSet.StringVar(&helmChartConfigMap, "helm-chart-configmap", "",
50+
"path to chart directory for CAREN")
51+
flagSet.StringVar(&carenVersion, "caren-version", "",
52+
"caren version for images override")
53+
err := flagSet.Parse(args[1:])
54+
if err != nil {
55+
fmt.Println("failed to parse args", err.Error())
56+
os.Exit(1)
57+
}
58+
chartDirectory, err = EnsureFullPath(chartDirectory)
59+
if err != nil {
60+
fmt.Println("failed to get full path for chart", err.Error())
61+
os.Exit(1)
62+
}
63+
helmChartConfigMap, err = EnsureFullPath(helmChartConfigMap)
64+
if err != nil {
65+
fmt.Println("failed to get full path for helmChartConfigMap", err.Error())
66+
os.Exit(1)
67+
}
68+
if chartDirectory == "" || helmChartConfigMap == "" {
69+
fmt.Println("chart-directory helm-chart-configmap must be set")
70+
os.Exit(1)
71+
}
72+
i := &ChartInfo{
73+
name: chartDirectory,
74+
}
75+
if carenVersion != "" {
76+
i.stringValues = []string{
77+
fmt.Sprintf("image.tag=%s", carenVersion),
78+
fmt.Sprintf("helmRepositoryImage.tag=%s", carenVersion),
79+
}
80+
}
81+
images, err := getImagesForChart(i)
82+
if err != nil {
83+
fmt.Println("failed to get images", err.Error())
84+
os.Exit(1)
85+
}
86+
addonImages, err := getImagesForAddons(helmChartConfigMap, chartDirectory)
87+
if err != nil {
88+
fmt.Println("failed to get images from addons", err.Error())
89+
os.Exit(1)
90+
}
91+
images = append(images, addonImages...)
92+
slices.Sort(images)
93+
images = slices.Compact(images)
94+
for _, image := range images {
95+
fmt.Println(image)
96+
}
97+
}
98+
99+
func EnsureFullPath(filename string) (string, error) {
100+
if path.IsAbs(filename) {
101+
return filename, nil
102+
}
103+
wd, err := os.Getwd()
104+
if err != nil {
105+
return "", fmt.Errorf("failed to get wd: %w", err)
106+
}
107+
fullPath := path.Join(wd, filename)
108+
fullPath = path.Clean(fullPath)
109+
_, err = os.Stat(fullPath)
110+
if err != nil {
111+
return "", err
112+
}
113+
return fullPath, nil
114+
}
115+
116+
type HelmChartFromConfigMap struct {
117+
Version string `yaml:"ChartVersion"`
118+
Repository string `yaml:"RepositoryURL"`
119+
ChartName string `yaml:"ChartName"`
120+
}
121+
122+
func getImagesForAddons(helmChartConfigMap, carenChartDirectory string) ([]string, error) {
123+
templatedConfigMap, err := getTemplatedHelmConfigMap(helmChartConfigMap)
124+
if err != nil {
125+
return nil, err
126+
}
127+
cm := &corev1.ConfigMap{}
128+
err = yaml.Unmarshal([]byte(templatedConfigMap), cm)
129+
if err != nil {
130+
return nil, fmt.Errorf("failed to unmarshal configmap to object %w", err)
131+
}
132+
images := []string{}
133+
for _, chartInfoRaw := range cm.Data {
134+
var settings HelmChartFromConfigMap
135+
err = yamlv2.Unmarshal([]byte(chartInfoRaw), &settings)
136+
if err != nil {
137+
return nil, fmt.Errorf("failed to unmarshal chart info from configmap %w", err)
138+
}
139+
info := &ChartInfo{
140+
name: settings.ChartName,
141+
repo: settings.Repository,
142+
}
143+
valuesFile := getValuesFileForChartIfNeeded(settings.ChartName, carenChartDirectory)
144+
if valuesFile != "" {
145+
info.valuesFile = valuesFile
146+
}
147+
if settings.ChartName == "aws-cloud-controller-manager" {
148+
values, err := getHelmValues(carenChartDirectory)
149+
if err != nil {
150+
return nil, err
151+
}
152+
awsImages, found, err := unstructured.NestedStringMap(values, "hooks", "ccm", "aws", "k8sMinorVersionToCCMVersion")
153+
if !found {
154+
return images, fmt.Errorf("failed to find k8sMinorVersionToCCMVersion from file %s",
155+
path.Join(carenChartDirectory, "values.yaml"))
156+
}
157+
if err != nil {
158+
return images, fmt.Errorf("failed to get map k8sMinorVersionToCCMVersion with error %w",
159+
err)
160+
}
161+
for _, tag := range awsImages {
162+
info.stringValues = []string{
163+
fmt.Sprintf("image.tag=%s", tag),
164+
}
165+
chartImages, err := getImagesForChart(info)
166+
if err != nil {
167+
return nil, fmt.Errorf("failed to get images for %s with error %w", info.name, err)
168+
}
169+
images = append(images, chartImages...)
170+
}
171+
// skip the to next addon because we got what we needed
172+
continue
173+
}
174+
chartImages, err := getImagesForChart(info)
175+
if err != nil {
176+
return nil, fmt.Errorf("failed to get images for %s with error %w", info.name, err)
177+
}
178+
images = append(images, chartImages...)
179+
}
180+
return images, nil
181+
}
182+
183+
func getHelmValues(carenChartDirectory string) (map[string]interface{}, error) {
184+
values := path.Join(carenChartDirectory, "values.yaml")
185+
valuesFile, err := os.Open(values)
186+
if err != nil {
187+
return nil, fmt.Errorf("failed to open file %s with %w", values, err)
188+
}
189+
defer valuesFile.Close()
190+
m := make(map[string]interface{})
191+
err = yaml.NewYAMLOrJSONDecoder(valuesFile, 1024).Decode(&m)
192+
if err != nil {
193+
return nil, fmt.Errorf("failed to open file %s with %w", values, err)
194+
}
195+
return m, nil
196+
}
197+
198+
func getValuesFileForChartIfNeeded(chartName, carenChartDirectory string) string {
199+
switch chartName {
200+
case "nutanix-csi-storage":
201+
return path.Join(carenChartDirectory, "addons", "csi", "nutanix", defaultHelmAddonFilename)
202+
case "node-feature-discovery":
203+
return path.Join(carenChartDirectory, "addons", "nfd", defaultHelmAddonFilename)
204+
case "snapshot-controller":
205+
return path.Join(carenChartDirectory, "addons", "csi", "snapshot-controller", defaultHelmAddonFilename)
206+
case "cilium":
207+
return path.Join(carenChartDirectory, "addons", "cni", "cilium", defaultHelmAddonFilename)
208+
// this uses the values from kustomize because the file at addons/cluster-autoscaler/values-template.yaml
209+
// is a file that is templated
210+
case "cluster-autoscaler":
211+
f := path.Join(carenChartDirectory, "addons", "cluster-autoscaler", defaultHelmAddonFilename)
212+
tempFile, _ := os.CreateTemp("", "")
213+
c := clusterv1.Cluster{
214+
ObjectMeta: metav1.ObjectMeta{
215+
Name: "tmplCluster",
216+
Namespace: "templNamespace",
217+
Annotations: map[string]string{
218+
"caren.nutanix.com/cluster-uuid": "03c19062-493d-4c41-89c3-728624313118",
219+
},
220+
},
221+
}
222+
type input struct {
223+
Cluster *clusterv1.Cluster
224+
}
225+
226+
templateInput := input{
227+
Cluster: &c,
228+
}
229+
230+
template.Must(template.New(defaultHelmAddonFilename).ParseFiles(f)).Execute(tempFile, &templateInput)
231+
return tempFile.Name()
232+
default:
233+
return ""
234+
}
235+
}
236+
237+
func getImagesForChart(info *ChartInfo) ([]string, error) {
238+
images := pkg.Images{}
239+
images.SetChart(info.name)
240+
if info.repo != "" {
241+
images.RepoURL = info.repo
242+
}
243+
if info.valuesFile != "" {
244+
images.ValueFiles.Set(info.valuesFile)
245+
}
246+
if len(info.stringValues) > 0 {
247+
images.StringValues = info.stringValues
248+
}
249+
// kubeVersion needs to be set for some addons
250+
images.KubeVersion = "v1.29.0"
251+
images.SetRelease("sample")
252+
images.SetLogger("INFO")
253+
images.Kind = k8s.SupportedKinds()
254+
b := bytes.NewBuffer([]byte{})
255+
images.SetWriter(b)
256+
images.ImageRegex = pkg.ImageRegex
257+
err := images.GetImages()
258+
if err != nil {
259+
return nil, err
260+
}
261+
raw, err := io.ReadAll(b)
262+
if err != nil {
263+
return nil, err
264+
}
265+
ret := strings.Split(string(raw), "\n")
266+
// this skips the last new line.
267+
ret = ret[0 : len(ret)-1]
268+
return ret, nil
269+
}
270+
271+
func getTemplatedHelmConfigMap(helmChartConfigMap string) (string, error) {
272+
helmFile, err := os.Open(helmChartConfigMap)
273+
if err != nil {
274+
return "", fmt.Errorf("failed to parse files %w", err)
275+
}
276+
defer helmFile.Close()
277+
helmFileBytes, err := io.ReadAll(helmFile)
278+
if err != nil {
279+
return "", fmt.Errorf("failed to read file %w", err)
280+
}
281+
t, err := template.New(helmChartConfigMap).Parse(string(helmFileBytes))
282+
if err != nil {
283+
return "", fmt.Errorf("failed to parse files %w", err)
284+
}
285+
var b bytes.Buffer
286+
err = t.Execute(&b, nil)
287+
if err != nil {
288+
return "", fmt.Errorf("failed to execute template %w", err)
289+
}
290+
return b.String(), nil
291+
}

0 commit comments

Comments
 (0)