diff --git a/example/20-crd-extensions.gardener.cloud_workers.yaml b/example/20-crd-extensions.gardener.cloud_workers.yaml index 0717dd204..0f06d223d 100644 --- a/example/20-crd-extensions.gardener.cloud_workers.yaml +++ b/example/20-crd-extensions.gardener.cloud_workers.yaml @@ -725,7 +725,6 @@ spec: - maximum - minimum - name - - priority - userDataSecretRef type: object type: array @@ -908,7 +907,6 @@ spec: - maximum - minimum - name - - priority type: object type: array machineDeploymentsLastUpdateTime: diff --git a/go.mod b/go.mod index 74e3035f3..b5d33f2be 100644 --- a/go.mod +++ b/go.mod @@ -20,9 +20,9 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.34.1 github.com/aws/smithy-go v1.22.5 github.com/coreos/go-systemd/v22 v22.6.0 - github.com/gardener/etcd-druid/api v0.31.0 + github.com/gardener/etcd-druid/api v0.32.0 github.com/gardener/external-dns-management v0.27.0 - github.com/gardener/gardener v1.127.1 + github.com/gardener/gardener v1.128.2 github.com/gardener/machine-controller-manager v0.60.0 github.com/go-logr/logr v1.4.3 github.com/google/go-cmp v0.7.0 @@ -33,17 +33,17 @@ require ( github.com/spf13/pflag v1.0.10 go.uber.org/atomic v1.11.0 go.uber.org/mock v0.6.0 - golang.org/x/time v0.12.0 - golang.org/x/tools v0.36.0 + golang.org/x/time v0.13.0 + golang.org/x/tools v0.37.0 gopkg.in/inf.v0 v0.9.1 - k8s.io/api v0.33.4 - k8s.io/apiextensions-apiserver v0.33.4 - k8s.io/apimachinery v0.33.4 + k8s.io/api v0.33.5 + k8s.io/apiextensions-apiserver v0.33.5 + k8s.io/apimachinery v0.33.5 k8s.io/autoscaler/vertical-pod-autoscaler v1.4.2 - k8s.io/client-go v0.33.4 - k8s.io/code-generator v0.33.4 - k8s.io/component-base v0.33.4 - k8s.io/kubelet v0.33.4 + k8s.io/client-go v0.33.5 + k8s.io/code-generator v0.33.5 + k8s.io/component-base v0.33.5 + k8s.io/kubelet v0.33.5 k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d sigs.k8s.io/controller-runtime v0.21.0 sigs.k8s.io/controller-tools v0.18.0 @@ -85,8 +85,8 @@ require ( github.com/fluent/fluent-operator/v3 v3.3.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.8.0 // indirect - github.com/gardener/cert-management v0.17.8 // indirect - github.com/go-jose/go-jose/v4 v4.1.0 // indirect + github.com/gardener/cert-management v0.18.0 // indirect + github.com/go-jose/go-jose/v4 v4.1.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/errors v0.22.0 // indirect @@ -136,7 +136,7 @@ require ( github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/nexucis/lamenv v0.5.2 // indirect - github.com/open-telemetry/opentelemetry-operator v0.131.0 // indirect + github.com/open-telemetry/opentelemetry-operator v0.132.0 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/perses/common v0.27.1-0.20250326140707-96e439b14e0e // indirect github.com/perses/perses v0.51.0 // indirect @@ -144,18 +144,18 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.23.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/otlptranslator v0.0.0-20250717125610-8549f4ab4f8f // indirect github.com/prometheus/procfs v0.17.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.14.0 // indirect - github.com/spf13/cast v1.7.1 // indirect - github.com/spf13/viper v1.20.1 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect + github.com/spf13/afero v1.15.0 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/viper v1.21.0 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect @@ -164,7 +164,7 @@ require ( github.com/zitadel/oidc/v3 v3.38.1 // indirect github.com/zitadel/schema v1.3.1 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/collector/featuregate v1.36.0 // indirect + go.opentelemetry.io/collector/featuregate v1.37.0 // indirect go.opentelemetry.io/contrib/otelconf v0.17.0 // indirect go.opentelemetry.io/otel v1.37.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.13.0 // indirect @@ -189,37 +189,39 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - go.yaml.in/yaml/v3 v3.0.3 // indirect - golang.org/x/crypto v0.41.0 // indirect - golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/net v0.43.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect + golang.org/x/crypto v0.42.0 // indirect + golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect + golang.org/x/mod v0.28.0 // indirect + golang.org/x/net v0.44.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/term v0.34.0 // indirect - golang.org/x/text v0.28.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053 // indirect + golang.org/x/term v0.35.0 // indirect + golang.org/x/text v0.29.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect google.golang.org/grpc v1.73.0 // indirect - google.golang.org/protobuf v1.36.6 // indirect + google.golang.org/protobuf v1.36.8 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect helm.sh/helm/v3 v3.18.6 // indirect - istio.io/api v1.25.4 // indirect + istio.io/api v1.25.5 // indirect istio.io/client-go v1.25.1 // indirect - k8s.io/apiserver v0.33.4 // indirect - k8s.io/cluster-bootstrap v0.33.4 // indirect - k8s.io/component-helpers v0.33.4 // indirect + k8s.io/apiserver v0.33.5 // indirect + k8s.io/cluster-bootstrap v0.33.5 // indirect + k8s.io/component-helpers v0.33.5 // indirect k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 // indirect k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7 // indirect k8s.io/klog v1.0.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-aggregator v0.33.4 // indirect + k8s.io/kube-aggregator v0.33.5 // indirect k8s.io/kube-openapi v0.0.0-20250701173324-9bd5c66d9911 // indirect - k8s.io/metrics v0.33.4 // indirect + k8s.io/metrics v0.33.5 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect diff --git a/go.sum b/go.sum index fd34c3dfc..3b12f348c 100644 --- a/go.sum +++ b/go.sum @@ -176,8 +176,8 @@ cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZ cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= -cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= +cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= @@ -893,8 +893,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/docker v27.4.1+incompatible h1:ZJvcY7gfwHn1JF48PfbyXg7Jyt9ZCWDW+GGXOIxEwp4= -github.com/docker/docker v27.4.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.0.0+incompatible h1:Olh0KS820sJ7nPsBKChVhk5pzqcwDR15fumfAd/p9hM= +github.com/docker/docker v28.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -954,14 +954,14 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU= github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/gardener/cert-management v0.17.8 h1:G2vfNtWGgyWQn0e2RWEGiKo1sAgF0t6U7LPvI+faV7Q= -github.com/gardener/cert-management v0.17.8/go.mod h1:wl4YqTM/evCPITOj//sJsGiSebww7ofAtlsi5/RhbjQ= -github.com/gardener/etcd-druid/api v0.31.0 h1:iH800fQOTeTAwQzaUQ8jxKFlSI8shZtpNfCTpsm3EyA= -github.com/gardener/etcd-druid/api v0.31.0/go.mod h1:usOvhSOpqlrlnr/DTugq8VDoZRCU2YmwyDfiy6hRVO8= +github.com/gardener/cert-management v0.18.0 h1:s2YhkN8z7lXe9En52GCeqQ9be10uEbLtH/FFAh6BVgQ= +github.com/gardener/cert-management v0.18.0/go.mod h1:9+JT+EBJB2OIX65EG+P1p/DZ/UJ3W8WR0h40ZjKbw+Q= +github.com/gardener/etcd-druid/api v0.32.0 h1:B3MEBe9q3+Q0jjFb/BhMigde05mYkVjWzVHgFd0/WuA= +github.com/gardener/etcd-druid/api v0.32.0/go.mod h1:Qpl1PDJ+bKa6OPWk4o7WBzvjPqZc/CxIXbiTkdRhCrg= github.com/gardener/external-dns-management v0.27.0 h1:S1j0vbFxiB5qUyX7OwFIOajsViloEYynu07Ia2WCzP4= github.com/gardener/external-dns-management v0.27.0/go.mod h1:tvnfgbra3qq0UaLBUsuny2TdXw5mev0I/f0rHAF/Uds= -github.com/gardener/gardener v1.127.1 h1:bFBsmM0zccIlgSWCstbEx2n/EHbVDgJa71A/i4+L5qI= -github.com/gardener/gardener v1.127.1/go.mod h1:OQBZdCX82NxFs3N2w94YyOXWRZDqzo/W0PGj7QevMvQ= +github.com/gardener/gardener v1.128.2 h1:L2HyIfi5UnpOEqG29RVlDHEt2PiDwxeSNAqX9iXJNVo= +github.com/gardener/gardener v1.128.2/go.mod h1:ZDYwIG/NSi4kQJuTVGr0jUugXRn70gLWlszqg4PQY8Q= github.com/gardener/machine-controller-manager v0.60.0 h1:aaSE85Yu0hcHYsP5/x1rxWa5o2zhmsmXlKQ+xefHY/Q= github.com/gardener/machine-controller-manager v0.60.0/go.mod h1:8eE1qLztrWIbOM71mHSQGaC6Q+pl5lvOyN08qP39D7o= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -972,8 +972,8 @@ github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2H github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY= -github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw= +github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= +github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= @@ -1247,8 +1247,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE= -github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE= +github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0= +github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= @@ -1296,8 +1296,8 @@ github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGV github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.38.0 h1:c/WX+w8SLAinvuKKQFh77WEucCnPk4j2OTUr7lt7BeY= github.com/onsi/gomega v1.38.0/go.mod h1:OcXcwId0b9QsE7Y49u+BTrL4IdKOBOKnD6VQNTJEB6o= -github.com/open-telemetry/opentelemetry-operator v0.131.0 h1:UTZZG8jh51q5Dzd70JZWN/6s9cY+dLomhSzoV2bQeLo= -github.com/open-telemetry/opentelemetry-operator v0.131.0/go.mod h1:D4Z+Ed4NJ3Vcxt2z3XZETbeWQGLzJN8h79KslqF5A5k= +github.com/open-telemetry/opentelemetry-operator v0.132.0 h1:EAOnWJAgGhWeqjrHVLIibm2l3rMMstRG+5cN9I16hP0= +github.com/open-telemetry/opentelemetry-operator v0.132.0/go.mod h1:aEihUoahiiYsgtzbviEttWcP/zO9BEX58cXcUUHsinA= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -1346,8 +1346,8 @@ github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3d github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= -github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= github.com/prometheus/otlptranslator v0.0.0-20250717125610-8549f4ab4f8f h1:QQB6SuvGZjK8kdc2YaLJpYhV8fxauOsjE6jgcL6YJ8Q= github.com/prometheus/otlptranslator v0.0.0-20250717125610-8549f4ab4f8f/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= @@ -1368,8 +1368,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= -github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= -github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= +github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 h1:yoKAVkEVwAqbGbR8n87rHQ1dulL25rKloGadb3vm770= @@ -1380,17 +1380,17 @@ github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+D github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= -github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -1398,8 +1398,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= -github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -1419,8 +1419,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -1450,8 +1450,8 @@ go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/collector/featuregate v1.36.0 h1:rK5a4C05RuvGCvlWRFU35Zb/4V6eTNWUNTZv2mhi4bs= -go.opentelemetry.io/collector/featuregate v1.36.0/go.mod h1:Y/KsHbvREENKvvN9RlpiWk/IGBK+CATBYzIIpU7nccc= +go.opentelemetry.io/collector/featuregate v1.37.0 h1:CjsHzjktiqq/dxid4Xkhuf3yD6oB/c7yRBWhokBJqpE= +go.opentelemetry.io/collector/featuregate v1.37.0/go.mod h1:Y/KsHbvREENKvvN9RlpiWk/IGBK+CATBYzIIpU7nccc= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= go.opentelemetry.io/contrib/otelconf v0.17.0 h1:Yh9uifPSe8yiksLshMbeAXGm/ZRmo7LD7Di+/yd1L5w= @@ -1513,8 +1513,10 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= -go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= -go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s= +go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -1533,8 +1535,8 @@ golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0 golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1542,8 +1544,8 @@ golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= -golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0= -golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= +golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= +golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -1578,8 +1580,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1630,8 +1632,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1674,8 +1676,8 @@ golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1736,8 +1738,10 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053 h1:dHQOQddU4YHS5gY33/6klKjq7Gp3WwMyOXGNp5nzRj8= +golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053/go.mod h1:+nZKN+XVh4LCiA9DV3ywrzN4gumyCnKjau3NGb9SGoE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1751,8 +1755,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= -golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= +golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -1770,15 +1774,15 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= +golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1811,8 +1815,8 @@ golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= @@ -1984,8 +1988,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130/go. google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5/go.mod h1:5DZzOUPCLYL3mNkQ0ms0F3EuUNZ7py1Bqeq6sxzI7/Q= google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= -google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= -google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234015-3fc162c6f38a/go.mod h1:xURIpW9ES5+/GZhnV6beoEtxQrnkRGIfP5VQG2tCBLc= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= @@ -1996,8 +2000,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I= google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -2055,8 +2059,8 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -2087,34 +2091,34 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -istio.io/api v1.25.4 h1:iuv1XZE9zHbpXp65CiLBI7AKLnx5hb/Ovn0HJzkbTPE= -istio.io/api v1.25.4/go.mod h1:QFzEXv/IT582T0FHZVp1QoolvE4ws0zz/vVO55blmlE= +istio.io/api v1.25.5 h1:6PnzDFjnKxWeVDs4gVV7fDEUj9t+Ulv2330KYw5L7Jw= +istio.io/api v1.25.5/go.mod h1:QFzEXv/IT582T0FHZVp1QoolvE4ws0zz/vVO55blmlE= istio.io/client-go v1.25.1 h1:1Asibz5v2NBA6w4Sqa85NS7TkpEolZcPKAT5oUAXWi8= istio.io/client-go v1.25.1/go.mod h1:Vap9OyHJMvvDegYoZczcNybS4wbPaTk+4bZcWMb8+vE= k8s.io/api v0.19.0/go.mod h1:I1K45XlvTrDjmj5LoM5LuP/KYrhWbjUKT/SoPG0qTjw= -k8s.io/api v0.33.4 h1:oTzrFVNPXBjMu0IlpA2eDDIU49jsuEorGHB4cvKupkk= -k8s.io/api v0.33.4/go.mod h1:VHQZ4cuxQ9sCUMESJV5+Fe8bGnqAARZ08tSTdHWfeAc= -k8s.io/apiextensions-apiserver v0.33.4 h1:rtq5SeXiDbXmSwxsF0MLe2Mtv3SwprA6wp+5qh/CrOU= -k8s.io/apiextensions-apiserver v0.33.4/go.mod h1:mWXcZQkQV1GQyxeIjYApuqsn/081hhXPZwZ2URuJeSs= +k8s.io/api v0.33.5 h1:YR+uhYj05jdRpcksv8kjSliW+v9hwXxn6Cv10aR8Juw= +k8s.io/api v0.33.5/go.mod h1:2gzShdwXKT5yPGiqrTrn/U/nLZ7ZyT4WuAj3XGDVgVs= +k8s.io/apiextensions-apiserver v0.33.5 h1:93NZh6rmrcamX/tfv/dZrTsMiQX69ufANmDcKPEgSeA= +k8s.io/apiextensions-apiserver v0.33.5/go.mod h1:JIbyQnNlu6nQa7b1vgFi51pmlXOk8mdn0WJwUJnz/7U= k8s.io/apimachinery v0.19.0/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= -k8s.io/apimachinery v0.33.4 h1:SOf/JW33TP0eppJMkIgQ+L6atlDiP/090oaX0y9pd9s= -k8s.io/apimachinery v0.33.4/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/apiserver v0.33.4 h1:6N0TEVA6kASUS3owYDIFJjUH6lgN8ogQmzZvaFFj1/Y= -k8s.io/apiserver v0.33.4/go.mod h1:8ODgXMnOoSPLMUg1aAzMFx+7wTJM+URil+INjbTZCok= +k8s.io/apimachinery v0.33.5 h1:NiT64hln4TQXeYR18/ES39OrNsjGz8NguxsBgp+6QIo= +k8s.io/apimachinery v0.33.5/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apiserver v0.33.5 h1:X1Gy33r4YkRLRqTjGjofk7X1/EjSLEVSJ/A+1qjoj60= +k8s.io/apiserver v0.33.5/go.mod h1:Q+b5Btbc8x0PqOCeh/xBTesKk+cXQRN+PF2wdrTKDeg= k8s.io/autoscaler/vertical-pod-autoscaler v1.4.2 h1:47RLgLhrxXfdBchTeNT2S9Xe9o+Y4kThExLfcGGUQMk= k8s.io/autoscaler/vertical-pod-autoscaler v1.4.2/go.mod h1:rIBiAf+sK2mw8ryeHIZuY5juhJ4e2rNLwo59SDRXF7I= k8s.io/client-go v0.19.0/go.mod h1:H9E/VT95blcFQnlyShFgnFT9ZnJOAceiUHM3MlRC+mU= -k8s.io/client-go v0.33.4 h1:TNH+CSu8EmXfitntjUPwaKVPN0AYMbc9F1bBS8/ABpw= -k8s.io/client-go v0.33.4/go.mod h1:LsA0+hBG2DPwovjd931L/AoaezMPX9CmBgyVyBZmbCY= -k8s.io/cluster-bootstrap v0.33.4 h1:on2rpd9l+UOhXAeouFn8ROBSo+Ad6U9NELNpmwRxZ44= -k8s.io/cluster-bootstrap v0.33.4/go.mod h1:SaOAiv+B/RQeUbcmjXKZO62w5BX4oT3ZJ8RFNl3ZoS8= +k8s.io/client-go v0.33.5 h1:I8BdmQGxInpkMEnJvV6iG7dqzP3JRlpZZlib3OMFc3o= +k8s.io/client-go v0.33.5/go.mod h1:W8PQP4MxbM4ypgagVE65mUUqK1/ByQkSALF9tzuQ6u0= +k8s.io/cluster-bootstrap v0.33.5 h1:VO0BhAwtAa6PYbBTkpK6mZwkGs8dlbeAN8pXbEovWWw= +k8s.io/cluster-bootstrap v0.33.5/go.mod h1:jPhTDgjG8RfxGkNJQzt3Qffq/KcYGRJI+UbKeJvEFyw= k8s.io/code-generator v0.19.0/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk= -k8s.io/code-generator v0.33.4 h1:DiA801QxqApRIBh3OWULasVAUA237XnYvFNMh+E34Y8= -k8s.io/code-generator v0.33.4/go.mod h1:ifWxKWhEl/Z1K7WmWAyOBEf3ex/i546ingCzLC8YVIY= -k8s.io/component-base v0.33.4 h1:Jvb/aw/tl3pfgnJ0E0qPuYLT0NwdYs1VXXYQmSuxJGY= -k8s.io/component-base v0.33.4/go.mod h1:567TeSdixWW2Xb1yYUQ7qk5Docp2kNznKL87eygY8Rc= -k8s.io/component-helpers v0.33.4 h1:DYHQPxWB3XIk7hwAQ4YczUelJ37PcUHfnLeee0qFqV8= -k8s.io/component-helpers v0.33.4/go.mod h1:kRgidIgCKFqOW/wy7D8IL3YOT3iaIRZu6FcTEyRr7WU= +k8s.io/code-generator v0.33.5 h1:KwkOvhwAaorjSwF2MQhhdhL3i8bBmAal/TWhX67kdHw= +k8s.io/code-generator v0.33.5/go.mod h1:Ra+sdZquRakeTGcEnQAPw6BmlZ92IvxwQQTX/XOvOIE= +k8s.io/component-base v0.33.5 h1:4D3kxjEx1pJRy3WHAZsmX3+LCpmd4ftE+2J4v6naTnQ= +k8s.io/component-base v0.33.5/go.mod h1:Zma1YjBVuuGxIbspj1vGR3/5blzo2ARf1v0QTtog1to= +k8s.io/component-helpers v0.33.5 h1:1LDSMzn7YTreVLPaOBJK36ase/FWi2sDpeJJvbEBO2s= +k8s.io/component-helpers v0.33.5/go.mod h1:C3HsDU2lANSLgTTgMJ0TFnG5xZrVrxR3Ss9n7Wrsw4s= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201203183100-97869a43a9d9/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= @@ -2129,15 +2133,15 @@ k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-aggregator v0.33.4 h1:TdIJKHb0/bLpby7FblXIaVEzyA1jGEjzt/n9cRvwq8U= -k8s.io/kube-aggregator v0.33.4/go.mod h1:wZuctdRvGde5bwzxkZRs0GYj2KOpCNgx8rRGVoNb62k= +k8s.io/kube-aggregator v0.33.5 h1:5libMG9e4m9lwhNBT89bBCd9x/rZebMahw5CHq9DE/Q= +k8s.io/kube-aggregator v0.33.5/go.mod h1:mHmmDqxY2ZkInu7eSAXb1ecaKV/U9DqPTQWBV2O84go= k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= k8s.io/kube-openapi v0.0.0-20250701173324-9bd5c66d9911 h1:gAXU86Fmbr/ktY17lkHwSjw5aoThQvhnstGGIYKlKYc= k8s.io/kube-openapi v0.0.0-20250701173324-9bd5c66d9911/go.mod h1:GLOk5B+hDbRROvt0X2+hqX64v/zO3vXN7J78OUmBSKw= -k8s.io/kubelet v0.33.4 h1:+sbpLmSq+Y8DF/OQeyw75OpuiF60tvlYcmc/yjN+nl4= -k8s.io/kubelet v0.33.4/go.mod h1:wboarviFRQld5rzZUjTliv7x00YVx+YhRd/p1OahX7Y= -k8s.io/metrics v0.33.4 h1:eJ6UdTpKTUQVZbKpUdm5ve39aPpAvvNwLrs13oQcWKc= -k8s.io/metrics v0.33.4/go.mod h1:NO/lgFtyIPTurz56debdSh5qRqRfpO8MlkMpau1Ue8U= +k8s.io/kubelet v0.33.5 h1:PYV+O8B6ZoQMDaQdYTSCzNdQTLdjAAWTspS2KkNfjLQ= +k8s.io/kubelet v0.33.5/go.mod h1:8SQ/0fgyNwm6zjHktBhPAUlgtji19YMa2lhSlYKUhrA= +k8s.io/metrics v0.33.5 h1:dOG+Yh4SZ6TJ7LcjvVXrYFvZ9G+sogCpTWyhoOVgQCE= +k8s.io/metrics v0.33.5/go.mod h1:YvBlRD01hos/j7dmjdtSBaZbmGyCJrxVS977x5+Y1kk= k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= diff --git a/hack/api-reference/api.md b/hack/api-reference/api.md index e25cbd97d..f9a372546 100644 --- a/hack/api-reference/api.md +++ b/hack/api-reference/api.md @@ -518,49 +518,6 @@ string -
-(Appears on: -MachineImageVersion) -
--
CapabilitySet groups all RegionAMIMappings for a specific et of capabilities.
- -| Field | -Description | -
|---|---|
-regions
-
-
-[]RegionAMIMapping
-
-
- |
-
- Regions is a mapping to the correct AMI for the machine image in the supported regions. - |
-
-capabilities
-
-github.com/gardener/gardener/pkg/apis/core/v1beta1.Capabilities
-
- |
-
- Capabilities that are supported by the AMIs in this set. - |
-
@@ -1337,6 +1294,49 @@ github.com/gardener/gardener/pkg/apis/core/v1beta1.Capabilities +
+(Appears on: +MachineImageVersion) +
++
MachineImageFlavor groups all RegionAMIMappings for a specific set of capabilities.
+ +| Field | +Description | +
|---|---|
+regions
+
+
+[]RegionAMIMapping
+
+
+ |
+
+ Regions is a mapping to the correct AMI for the machine image in the supported regions. + |
+
+capabilities
+
+github.com/gardener/gardener/pkg/apis/core/v1beta1.Capabilities
+
+ |
+
+ Capabilities that are supported by the AMIs in this set. + |
+
@@ -1380,15 +1380,15 @@ string
capabilitySets
+capabilityFlavors
-
-[]CapabilitySet
+
+[]MachineImageFlavor
CapabilitySets is grouping of region AMIs by capabilities.
+CapabilityFlavors is grouping of region AMIs by capabilities.
(Appears on: -CapabilitySet, +MachineImageFlavor, MachineImageVersion)
diff --git a/pkg/admission/mutator/cloudprofile.go b/pkg/admission/mutator/cloudprofile.go new file mode 100644 index 000000000..311ae1c2e --- /dev/null +++ b/pkg/admission/mutator/cloudprofile.go @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package mutator + +import ( + "context" + "fmt" + "slices" + + extensionswebhook "github.com/gardener/gardener/extensions/pkg/webhook" + gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + + "github.com/gardener/gardener-extension-provider-aws/pkg/apis/aws/v1alpha1" +) + +// NewCloudProfileMutator returns a new instance of a CloudProfile mutator. +func NewCloudProfileMutator(mgr manager.Manager) extensionswebhook.Mutator { + return &cloudProfile{ + client: mgr.GetClient(), + decoder: serializer.NewCodecFactory(mgr.GetScheme(), serializer.EnableStrict).UniversalDecoder(), + } +} + +type cloudProfile struct { + client client.Client + decoder runtime.Decoder +} + +// Mutate mutates the given CloudProfile object. +func (p *cloudProfile) Mutate(_ context.Context, newObj, _ client.Object) error { + profile, ok := newObj.(*gardencorev1beta1.CloudProfile) + if !ok { + return fmt.Errorf("wrong object type %T", newObj) + } + + // Skip mutation if CloudProfile is being deleted or when no capabilities used in that profile + if profile.DeletionTimestamp != nil || profile.Spec.ProviderConfig == nil || len(profile.Spec.MachineCapabilities) == 0 { + return nil + } + + specConfig := &v1alpha1.CloudProfileConfig{} + if _, _, err := p.decoder.Decode(profile.Spec.ProviderConfig.Raw, nil, specConfig); err != nil { + return fmt.Errorf("could not decode providerConfig of cloudProfile for '%s': %w", profile.Name, err) + } + + overwriteMachineImageCapabilityFlavors(profile, specConfig) + return nil +} + +// overwriteMachineImageCapabilityFlavors updates the capability flavors of machine images in the CloudProfile +func overwriteMachineImageCapabilityFlavors(profile *gardencorev1beta1.CloudProfile, config *v1alpha1.CloudProfileConfig) { + for _, providerMachineImage := range config.MachineImages { + // Find the corresponding machine image in the CloudProfile + imageIdx := slices.IndexFunc(profile.Spec.MachineImages, func(mi gardencorev1beta1.MachineImage) bool { + return mi.Name == providerMachineImage.Name + }) + if imageIdx == -1 { + continue + } + + // Iterate over versions in the provider's machine image + for _, providerVersion := range providerMachineImage.Versions { + // Find the corresponding version in the CloudProfile's machine image + versionIdx := slices.IndexFunc(profile.Spec.MachineImages[imageIdx].Versions, func(miv gardencorev1beta1.MachineImageVersion) bool { + return miv.Version == providerVersion.Version + }) + if versionIdx == -1 { + continue + } + + profile.Spec.MachineImages[imageIdx].Versions[versionIdx].CapabilityFlavors = convertCapabilityFlavors(providerVersion.CapabilityFlavors) + } + } +} + +// convertCapabilityFlavors converts provider capability flavors to CloudProfile capability flavors +func convertCapabilityFlavors(providerFlavors []v1alpha1.MachineImageFlavor) []gardencorev1beta1.MachineImageFlavor { + capabilityFlavors := make([]gardencorev1beta1.MachineImageFlavor, 0, len(providerFlavors)) + for _, providerFlavor := range providerFlavors { + capabilityFlavors = append(capabilityFlavors, gardencorev1beta1.MachineImageFlavor{ + Capabilities: providerFlavor.GetCapabilities(), + }) + } + return capabilityFlavors +} diff --git a/pkg/admission/mutator/cloudprofile_test.go b/pkg/admission/mutator/cloudprofile_test.go new file mode 100644 index 000000000..3564b5f90 --- /dev/null +++ b/pkg/admission/mutator/cloudprofile_test.go @@ -0,0 +1,266 @@ +// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package mutator_test + +import ( + "context" + "fmt" + + extensionswebhook "github.com/gardener/gardener/extensions/pkg/webhook" + "github.com/gardener/gardener/pkg/apis/core/v1beta1" + "github.com/gardener/gardener/pkg/utils/test" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gstruct" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/manager" + + "github.com/gardener/gardener-extension-provider-aws/pkg/admission/mutator" + "github.com/gardener/gardener-extension-provider-aws/pkg/apis/aws/install" +) + +var _ = Describe("CloudProfile Mutator", func() { + var ( + fakeClient client.Client + fakeManager manager.Manager + ctx = context.Background() + + cloudProfileMutator extensionswebhook.Mutator + cloudProfile *v1beta1.CloudProfile + ) + + BeforeEach(func() { + scheme := runtime.NewScheme() + utilruntime.Must(install.AddToScheme(scheme)) + utilruntime.Must(v1beta1.AddToScheme(scheme)) + fakeClient = fakeclient.NewClientBuilder().WithScheme(scheme).Build() + fakeManager = &test.FakeManager{ + Client: fakeClient, + Scheme: scheme, + } + + cloudProfileMutator = mutator.NewCloudProfileMutator(fakeManager) + + imageVersion := "1.0.0" + latestImageVersion := "1.0.1" + imageName := "os-1" + + machineImages := []v1beta1.MachineImage{ + { + Name: imageName, + Versions: []v1beta1.MachineImageVersion{{ + ExpirableVersion: v1beta1.ExpirableVersion{ + Version: imageVersion, + }, + }, { + ExpirableVersion: v1beta1.ExpirableVersion{ + Version: latestImageVersion, + }, + }, + }, + }, + } + + cloudProfile = &v1beta1.CloudProfile{ + ObjectMeta: metav1.ObjectMeta{ + Name: "aws", + }, + Spec: v1beta1.CloudProfileSpec{ + MachineImages: machineImages, + }, + } + }) + + Describe("#Mutate", func() { + Context("CloudProfile without machineCapabilities", func() { + BeforeEach(func() { + cloudProfile.Spec.ProviderConfig = nil + }) + + It("should succeed and not modify the CloudProfile", func() { + cloudProfile.Spec.ProviderConfig = &runtime.RawExtension{Raw: []byte(`{ +"apiVersion":"aws.provider.extensions.gardener.cloud/v1alpha1", +"kind":"CloudProfileConfig", +"machineImages":[ + {"name":"image-1","versions":[{"version":"1.1","regions":[{"name":"eu2","ami":"ami-124","architecture":"armhf"}]}]} +]}`)} + expectedProfileSpec := cloudProfile.Spec.DeepCopy() + Expect(cloudProfileMutator.Mutate(ctx, cloudProfile, nil)).To(Succeed()) + + Expect(cloudProfile.Spec.MachineImages).To(Equal(expectedProfileSpec.MachineImages)) + }) + }) + + Context("CloudProfile with machineCapabilities", func() { + BeforeEach(func() { + cloudProfile.Spec.MachineCapabilities = []v1beta1.CapabilityDefinition{{ + Name: "architecture", + Values: []string{"amd64", "arm64", "armhf"}, + }, { + Name: "gpu", + Values: []string{"true", "false"}, + }} + }) + It("should succeed for CloudProfile without provider config", func() { + expectedProfile := cloudProfile.DeepCopy() + Expect(cloudProfileMutator.Mutate(ctx, cloudProfile, nil)).To(Succeed()) + Expect(cloudProfile).To(Equal(expectedProfile)) + + }) + + It("should skip if CloudProfile is in deletion phase", func() { + cloudProfile.DeletionTimestamp = ptr.To(metav1.Now()) + expectedProfile := cloudProfile.DeepCopy() + + Expect(cloudProfileMutator.Mutate(ctx, cloudProfile, nil)).To(Succeed()) + + Expect(cloudProfile).To(Equal(expectedProfile)) + }) + + It("should fill capabilityFlavors based on provider config", func() { + image1AmiMappings := `"capabilityFlavors":[ +{"capabilities":{"architecture":["arm64"]},"regions":[{"name":"image-region-1","ami":"id-img-reg-1"}]}, +{"capabilities":{"architecture":["amd64"]},"regions":[{"name":"image-region-2","ami":"id-img-reg-2"}]} +]` + image1FallbackMappings := `"capabilityFlavors":[ +{"capabilities":{"architecture":["amd64"]},"regions":[{"name":"image-region-2","ami":"id-img-reg-2"}]} +]` + + cloudProfile.Spec.ProviderConfig = &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{ +"apiVersion":"aws.provider.extensions.gardener.cloud/v1alpha1", +"kind":"CloudProfileConfig", +"machineImages":[ + {"name":"os-1","versions":[ + {"version":"1.0.0",%s}, + {"version":"1.0.1",%s} + ]} +]}`, image1AmiMappings, image1FallbackMappings))} + Expect(cloudProfileMutator.Mutate(ctx, cloudProfile, nil)).To(Succeed()) + Expect(cloudProfile.Spec.MachineImages).To(Equal([]v1beta1.MachineImage{ + { + Name: "os-1", + Versions: []v1beta1.MachineImageVersion{ + { + ExpirableVersion: v1beta1.ExpirableVersion{Version: "1.0.0"}, + CapabilityFlavors: []v1beta1.MachineImageFlavor{ + {Capabilities: v1beta1.Capabilities{"architecture": []string{"arm64"}}}, + {Capabilities: v1beta1.Capabilities{"architecture": []string{"amd64"}}}, + }, + }, + { + ExpirableVersion: v1beta1.ExpirableVersion{Version: "1.0.1"}, + CapabilityFlavors: []v1beta1.MachineImageFlavor{ + {Capabilities: v1beta1.Capabilities{"architecture": []string{"amd64"}}}, + }, + }, + }, + }, + })) + }) + + It("should overwrite capabilityFlavors when some versions already have them", func() { + twoFlavors := `"capabilityFlavors":[ +{"capabilities":{"architecture":["arm64"]},"regions":[{"name":"image-region-1","ami":"id-img-reg-1"}]}, +{"capabilities":{"architecture":["amd64"]},"regions":[{"name":"image-region-2","ami":"id-img-reg-2"}]} +]` + oneFlavors := `"capabilityFlavors":[ +{"capabilities":{"architecture":["amd64"]},"regions":[{"name":"image-region-2","ami":"id-img-reg-2"}]} +]` + cloudProfile.Spec.MachineImages = []v1beta1.MachineImage{ + { + Name: "os-1", + Versions: []v1beta1.MachineImageVersion{ + { + ExpirableVersion: v1beta1.ExpirableVersion{Version: "1.0.0"}, + CapabilityFlavors: []v1beta1.MachineImageFlavor{ + {Capabilities: v1beta1.Capabilities{"architecture": []string{"not-existing"}}}, + {Capabilities: v1beta1.Capabilities{"architecture": []string{"amd64"}}}, + }, + }, + {ExpirableVersion: v1beta1.ExpirableVersion{Version: "1.0.1"}}, + }, + }, + { + Name: "os-2", + Versions: []v1beta1.MachineImageVersion{ + {ExpirableVersion: v1beta1.ExpirableVersion{Version: "1.0.0"}}, + {ExpirableVersion: v1beta1.ExpirableVersion{Version: "1.0.1"}}, + }, + }, + } + cloudProfile.Spec.ProviderConfig = &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{ +"apiVersion":"aws.provider.extensions.gardener.cloud/v1alpha1", +"kind":"CloudProfileConfig", +"machineImages":[ + {"name":"os-1","versions":[ + {"version":"1.0.0",%s}, + {"version":"1.0.1",%s} + ]}, + {"name":"os-2","versions":[ + {"version":"1.0.0",%s}, + {"version":"1.0.1",%s} + ]} +]}`, twoFlavors, oneFlavors, oneFlavors, twoFlavors))} + Expect(cloudProfileMutator.Mutate(ctx, cloudProfile, nil)).To(Succeed()) + Expect(cloudProfile.Spec.MachineImages).To(ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "Name": Equal("os-1"), + "Versions": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "ExpirableVersion": MatchFields(IgnoreExtras, Fields{"Version": Equal("1.0.0")}), + "CapabilityFlavors": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "Capabilities": Equal(v1beta1.Capabilities{"architecture": []string{"arm64"}}), + }), + MatchFields(IgnoreExtras, Fields{ + "Capabilities": Equal(v1beta1.Capabilities{"architecture": []string{"amd64"}}), + }), + ), + }), + MatchFields(IgnoreExtras, Fields{ + "ExpirableVersion": MatchFields(IgnoreExtras, Fields{"Version": Equal("1.0.1")}), + "CapabilityFlavors": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "Capabilities": Equal(v1beta1.Capabilities{"architecture": []string{"amd64"}}), + }), + ), + }), + ), + }), + MatchFields(IgnoreExtras, Fields{ + "Name": Equal("os-2"), + "Versions": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "ExpirableVersion": MatchFields(IgnoreExtras, Fields{"Version": Equal("1.0.0")}), + "CapabilityFlavors": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "Capabilities": Equal(v1beta1.Capabilities{"architecture": []string{"amd64"}}), + }), + ), + }), + MatchFields(IgnoreExtras, Fields{ + "ExpirableVersion": MatchFields(IgnoreExtras, Fields{"Version": Equal("1.0.1")}), + "CapabilityFlavors": ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "Capabilities": Equal(v1beta1.Capabilities{"architecture": []string{"arm64"}}), + }), + MatchFields(IgnoreExtras, Fields{ + "Capabilities": Equal(v1beta1.Capabilities{"architecture": []string{"amd64"}}), + }), + ), + }), + ), + }), + )) + }) + }) + + }) +}) diff --git a/pkg/admission/mutator/namespacedcloudprofile_test.go b/pkg/admission/mutator/namespacedcloudprofile_test.go index 42a8ecd46..5d39a02fa 100644 --- a/pkg/admission/mutator/namespacedcloudprofile_test.go +++ b/pkg/admission/mutator/namespacedcloudprofile_test.go @@ -110,6 +110,65 @@ var _ = Describe("NamespacedCloudProfile Mutator", func() { }), )) }) + It("should correctly merge extended machineImages using capabilities ", func() { + namespacedCloudProfile.Status.CloudProfileSpec.MachineCapabilities = []v1beta1.CapabilityDefinition{{ + Name: "architecture", + Values: []string{"amd64", "armhf"}, + }} + namespacedCloudProfile.Status.CloudProfileSpec.ProviderConfig = &runtime.RawExtension{Raw: []byte(`{ +"apiVersion":"aws.provider.extensions.gardener.cloud/v1alpha1", +"kind":"CloudProfileConfig", +"machineImages":[ + {"name":"image-1","versions":[{"version":"1.0","capabilityFlavors":[ +{"capabilities":{"architecture":["amd64"]},"regions":[{"name":"eu1","ami":"ami-123"}]} +]}]} +]}`)} + namespacedCloudProfile.Spec.ProviderConfig = &runtime.RawExtension{Raw: []byte(`{ +"apiVersion":"aws.provider.extensions.gardener.cloud/v1alpha1", +"kind":"CloudProfileConfig", +"machineImages":[ + {"name":"image-1","versions":[{"version":"1.1","capabilityFlavors":[ +{"capabilities":{"architecture":["armhf"]},"regions":[{"name":"eu2","ami":"ami-124"}]} +]}]}, + {"name":"image-2","versions":[{"version":"2.0","capabilityFlavors":[ +{"capabilities":{"architecture":["amd64"]},"regions":[{"name":"eu3","ami":"ami-125"}]} +]}]} +]}`)} + + Expect(namespacedCloudProfileMutator.Mutate(ctx, namespacedCloudProfile, nil)).To(Succeed()) + + mergedConfig, err := decodeCloudProfileConfig(decoder, namespacedCloudProfile.Status.CloudProfileSpec.ProviderConfig) + Expect(err).ToNot(HaveOccurred()) + Expect(mergedConfig.MachineImages).To(ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "Name": Equal("image-1"), + "Versions": ContainElements( + api.MachineImageVersion{Version: "1.0", + CapabilityFlavors: []api.MachineImageFlavor{{ + Capabilities: v1beta1.Capabilities{"architecture": []string{"amd64"}}, + Regions: []api.RegionAMIMapping{{Name: "eu1", AMI: "ami-123", Architecture: ptr.To("ignore")}}, + }}, + }, + api.MachineImageVersion{Version: "1.1", + CapabilityFlavors: []api.MachineImageFlavor{{ + Capabilities: v1beta1.Capabilities{"architecture": []string{"armhf"}}, + Regions: []api.RegionAMIMapping{{Name: "eu2", AMI: "ami-124", Architecture: ptr.To("ignore")}}, + }}, + }, + ), + }), + MatchFields(IgnoreExtras, Fields{ + "Name": Equal("image-2"), + "Versions": ContainElements( + api.MachineImageVersion{Version: "2.0", + CapabilityFlavors: []api.MachineImageFlavor{{ + Capabilities: v1beta1.Capabilities{"architecture": []string{"amd64"}}, + Regions: []api.RegionAMIMapping{{Name: "eu3", AMI: "ami-125", Architecture: ptr.To("ignore")}}, + }}, + }), + }), + )) + }) }) }) }) diff --git a/pkg/admission/mutator/webhook.go b/pkg/admission/mutator/webhook.go index dd8c1382e..41fd74000 100644 --- a/pkg/admission/mutator/webhook.go +++ b/pkg/admission/mutator/webhook.go @@ -33,6 +33,7 @@ func New(mgr manager.Manager) (*extensionswebhook.Webhook, error) { Mutators: map[extensionswebhook.Mutator][]extensionswebhook.Type{ NewShootMutator(mgr): {{Obj: &gardencorev1beta1.Shoot{}}}, NewNamespacedCloudProfileMutator(mgr): {{Obj: &gardencorev1beta1.NamespacedCloudProfile{}, Subresource: ptr.To("status")}}, + NewCloudProfileMutator(mgr): {{Obj: &gardencorev1beta1.CloudProfile{}}}, }, Target: extensionswebhook.TargetSeed, ObjectSelector: &metav1.LabelSelector{ diff --git a/pkg/admission/validator/cloudprofile.go b/pkg/admission/validator/cloudprofile.go index ad424a566..50dd67e27 100644 --- a/pkg/admission/validator/cloudprofile.go +++ b/pkg/admission/validator/cloudprofile.go @@ -10,29 +10,29 @@ import ( extensionswebhook "github.com/gardener/gardener/extensions/pkg/webhook" "github.com/gardener/gardener/pkg/apis/core" + gardencorev1beta1helper "github.com/gardener/gardener/pkg/apis/core/v1beta1/helper" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/util/validation/field" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/manager" - "github.com/gardener/gardener-extension-provider-aws/pkg/apis/aws/helper" awsvalidation "github.com/gardener/gardener-extension-provider-aws/pkg/apis/aws/validation" ) // NewCloudProfileValidator returns a new instance of a cloud profile validator. func NewCloudProfileValidator(mgr manager.Manager) extensionswebhook.Validator { - return &cloudProfile{ + return &cloudProfileValidator{ decoder: serializer.NewCodecFactory(mgr.GetScheme(), serializer.EnableStrict).UniversalDecoder(), } } -type cloudProfile struct { +type cloudProfileValidator struct { decoder runtime.Decoder } // Validate validates the given cloud profile objects. -func (cp *cloudProfile) Validate(_ context.Context, newObj, _ client.Object) error { +func (cp *cloudProfileValidator) Validate(_ context.Context, newObj, _ client.Object) error { cloudProfile, ok := newObj.(*core.CloudProfile) if !ok { return fmt.Errorf("wrong object type %T", newObj) @@ -45,13 +45,13 @@ func (cp *cloudProfile) Validate(_ context.Context, newObj, _ client.Object) err cpConfig, err := decodeCloudProfileConfig(cp.decoder, cloudProfile.Spec.ProviderConfig) if err != nil { - return err + return fmt.Errorf("could not decode providerConfig of CloudProfile %q: %w", cloudProfile.Name, err) } - capabilitiesDefinition, err := helper.ConvertV1beta1CapabilitiesDefinitions(cloudProfile.Spec.Capabilities) + capabilityDefinitions, err := gardencorev1beta1helper.ConvertV1beta1CapabilityDefinitions(cloudProfile.Spec.MachineCapabilities) if err != nil { - return field.InternalError(field.NewPath("spec").Child("capabilities"), err) + return field.InternalError(field.NewPath("spec").Child("machineCapabilities"), err) } - return awsvalidation.ValidateCloudProfileConfig(cpConfig, cloudProfile.Spec.MachineImages, capabilitiesDefinition, providerConfigPath).ToAggregate() + return awsvalidation.ValidateCloudProfileConfig(cpConfig, cloudProfile.Spec.MachineImages, capabilityDefinitions, providerConfigPath).ToAggregate() } diff --git a/pkg/admission/validator/namespacedcloudprofile.go b/pkg/admission/validator/namespacedcloudprofile.go index e4271398e..701fac00a 100644 --- a/pkg/admission/validator/namespacedcloudprofile.go +++ b/pkg/admission/validator/namespacedcloudprofile.go @@ -24,7 +24,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/manager" api "github.com/gardener/gardener-extension-provider-aws/pkg/apis/aws" - "github.com/gardener/gardener-extension-provider-aws/pkg/apis/aws/helper" "github.com/gardener/gardener-extension-provider-aws/pkg/apis/aws/validation" ) @@ -43,25 +42,25 @@ type namespacedCloudProfile struct { // Validate validates the given NamespacedCloudProfile objects. func (p *namespacedCloudProfile) Validate(ctx context.Context, newObj, _ client.Object) error { - profile, ok := newObj.(*core.NamespacedCloudProfile) + cloudProfile, ok := newObj.(*core.NamespacedCloudProfile) if !ok { return fmt.Errorf("wrong object type %T", newObj) } - if profile.DeletionTimestamp != nil { + if cloudProfile.DeletionTimestamp != nil { return nil } - cpConfig := &api.CloudProfileConfig{} - if profile.Spec.ProviderConfig != nil { + cloudProfileConfig := &api.CloudProfileConfig{} + if cloudProfile.Spec.ProviderConfig != nil { var err error - cpConfig, err = decodeCloudProfileConfig(p.decoder, profile.Spec.ProviderConfig) + cloudProfileConfig, err = decodeCloudProfileConfig(p.decoder, cloudProfile.Spec.ProviderConfig) if err != nil { - return err + return fmt.Errorf("could not decode providerConfig of NamespacedCloudProfile for '%s': %w", cloudProfile.Name, err) } } - parentCloudProfile := profile.Spec.Parent + parentCloudProfile := cloudProfile.Spec.Parent if parentCloudProfile.Kind != constants.CloudProfileReferenceKindCloudProfile { return fmt.Errorf("parent reference must be of kind CloudProfile (unsupported kind: %s)", parentCloudProfile.Kind) } @@ -70,16 +69,7 @@ func (p *namespacedCloudProfile) Validate(ctx context.Context, newObj, _ client. return err } - return p.validateNamespacedCloudProfileProviderConfig(cpConfig, profile.Spec, parentProfile.Spec).ToAggregate() -} - -// validateNamespacedCloudProfileProviderConfig validates the CloudProfileConfig passed with a NamespacedCloudProfile. -func (p *namespacedCloudProfile) validateNamespacedCloudProfileProviderConfig(providerConfig *api.CloudProfileConfig, profileSpec core.NamespacedCloudProfileSpec, parentSpec gardencorev1beta1.CloudProfileSpec) field.ErrorList { - allErrs := field.ErrorList{} - - allErrs = append(allErrs, p.validateMachineImages(providerConfig, profileSpec.MachineImages, parentSpec)...) - - return allErrs + return p.validateMachineImages(cloudProfileConfig, cloudProfile.Spec.MachineImages, parentProfile.Spec).ToAggregate() } func (p *namespacedCloudProfile) validateMachineImages(providerConfig *api.CloudProfileConfig, machineImages []core.MachineImage, parentSpec gardencorev1beta1.CloudProfileSpec) field.ErrorList { @@ -88,7 +78,7 @@ func (p *namespacedCloudProfile) validateMachineImages(providerConfig *api.Cloud machineImagesPath := field.NewPath("spec.providerConfig.machineImages") for i, machineImage := range providerConfig.MachineImages { idxPath := machineImagesPath.Index(i) - allErrs = append(allErrs, validation.ValidateProviderMachineImage(idxPath, machineImage, parentSpec.Capabilities)...) + allErrs = append(allErrs, validation.ValidateProviderMachineImage(machineImage, parentSpec.MachineCapabilities, idxPath)...) } profileImages := gutil.NewCoreImagesContext(machineImages) @@ -115,14 +105,14 @@ func (p *namespacedCloudProfile) validateMachineImages(providerConfig *api.Cloud continue } - if len(parentSpec.Capabilities) == 0 { + if len(parentSpec.MachineCapabilities) == 0 { allErrs = append(allErrs, validateMachineImageArchitectures(machineImage, version, providerImageVersion)...) } else { var v1betaVersion gardencorev1beta1.MachineImageVersion if err := gardencoreapi.Scheme.Convert(&version, &v1betaVersion, nil); err != nil { return append(allErrs, field.InternalError(machineImagesPath, err)) } - allErrs = append(allErrs, validateMachineImageCapabilities(machineImage, v1betaVersion, providerImageVersion, parentSpec.Capabilities)...) + allErrs = append(allErrs, validateMachineImageCapabilities(machineImage, v1betaVersion, providerImageVersion, parentSpec.MachineCapabilities)...) } } } @@ -161,62 +151,62 @@ func (p *namespacedCloudProfile) validateMachineImages(providerConfig *api.Cloud return allErrs } -func validateMachineImageCapabilities(machineImage core.MachineImage, version gardencorev1beta1.MachineImageVersion, providerImageVersion api.MachineImageVersion, capabilitiesDefinition []gardencorev1beta1.CapabilityDefinition) field.ErrorList { +func validateMachineImageCapabilities(machineImage core.MachineImage, version gardencorev1beta1.MachineImageVersion, providerImageVersion api.MachineImageVersion, capabilityDefinitions []gardencorev1beta1.CapabilityDefinition) field.ErrorList { allErrs := field.ErrorList{} path := field.NewPath("spec.providerConfig.machineImages") - defaultedCapabilitySets := gardencorev1beta1helper.GetCapabilitySetsWithAppliedDefaults(version.CapabilitySets, capabilitiesDefinition) + defaultedCapabilityFlavors := gardencorev1beta1helper.GetImageFlavorsWithAppliedDefaults(version.CapabilityFlavors, capabilityDefinitions) regionsCapabilitiesMap := map[string][]gardencorev1beta1.Capabilities{} - // 1. Create an error for each capabilitySet in the providerConfig that is not defined in the core machine image version - for _, capabilitySet := range providerImageVersion.CapabilitySets { + // 1. Create an error for each capabilityFlavor in the providerConfig that is not defined in the core machine image version + for _, capabilityFlavor := range providerImageVersion.CapabilityFlavors { isFound := false - for _, coreDefaultedCapabilitySet := range defaultedCapabilitySets { - defaultedProviderCapabilities := gardencorev1beta1helper.GetCapabilitiesWithAppliedDefaults(capabilitySet.Capabilities, capabilitiesDefinition) - if helper.AreCapabilitiesEqual(coreDefaultedCapabilitySet.Capabilities, defaultedProviderCapabilities) { + for _, coreDefaultedCapabilitySet := range defaultedCapabilityFlavors { + defaultedProviderCapabilities := gardencorev1beta1helper.GetCapabilitiesWithAppliedDefaults(capabilityFlavor.Capabilities, capabilityDefinitions) + if gardencorev1beta1helper.AreCapabilitiesEqual(coreDefaultedCapabilitySet.Capabilities, defaultedProviderCapabilities) { isFound = true } } if !isFound { allErrs = append(allErrs, field.Forbidden(path, - fmt.Sprintf("machine image version %s@%s has an excess capabilitySet %v, which is not defined in the machineImages spec", - machineImage.Name, version.Version, capabilitySet.Capabilities))) + fmt.Sprintf("machine image version %s@%s has an excess capabilityFlavor %v, which is not defined in the machineImages spec", + machineImage.Name, version.Version, capabilityFlavor.Capabilities))) } - for _, regionMapping := range capabilitySet.Regions { - regionsCapabilitiesMap[regionMapping.Name] = append(regionsCapabilitiesMap[regionMapping.Name], capabilitySet.Capabilities) + for _, regionMapping := range capabilityFlavor.Regions { + regionsCapabilitiesMap[regionMapping.Name] = append(regionsCapabilitiesMap[regionMapping.Name], capabilityFlavor.Capabilities) } } - // 2. Create an error for each capabilitySet in the core machine image version that is not defined in the providerConfig - for _, coreDefaultedCapabilitySet := range defaultedCapabilitySets { + // 2. Create an error for each capabilityFlavor in the core machine image version that is not defined in the providerConfig + for _, coreDefaultedCapabilityFlavor := range defaultedCapabilityFlavors { isFound := false - for _, capabilitySet := range providerImageVersion.CapabilitySets { - defaultedProviderCapabilities := gardencorev1beta1helper.GetCapabilitiesWithAppliedDefaults(capabilitySet.Capabilities, capabilitiesDefinition) - if helper.AreCapabilitiesEqual(coreDefaultedCapabilitySet.Capabilities, defaultedProviderCapabilities) { + for _, capabilityFlavor := range providerImageVersion.CapabilityFlavors { + defaultedProviderCapabilities := gardencorev1beta1helper.GetCapabilitiesWithAppliedDefaults(capabilityFlavor.Capabilities, capabilityDefinitions) + if gardencorev1beta1helper.AreCapabilitiesEqual(coreDefaultedCapabilityFlavor.Capabilities, defaultedProviderCapabilities) { isFound = true } } if !isFound { allErrs = append(allErrs, field.Required(path, - fmt.Sprintf("machine image version %s@%s has a capabilitySet %v not defined in the NamespacedCloudProfile providerConfig", - machineImage.Name, version.Version, coreDefaultedCapabilitySet.Capabilities))) - // no need to check the regions if the capabilitySet is not defined in the providerConfig + fmt.Sprintf("machine image version %s@%s has a capabilityFlavor %v not defined in the NamespacedCloudProfile providerConfig", + machineImage.Name, version.Version, coreDefaultedCapabilityFlavor.Capabilities))) + // no need to check the regions if the capabilityFlavor is not defined in the providerConfig continue } - // 3. Create an error for each region that is not part of every capabilitySet + // 3. Create an error for each region that is not part of every capabilityFlavor for region, regionCapabilities := range regionsCapabilitiesMap { isFound := false for _, capabilities := range regionCapabilities { - regionDefaultedCapabilities := gardencorev1beta1helper.GetCapabilitiesWithAppliedDefaults(capabilities, capabilitiesDefinition) - if helper.AreCapabilitiesEqual(regionDefaultedCapabilities, coreDefaultedCapabilitySet.Capabilities) { + regionDefaultedCapabilities := gardencorev1beta1helper.GetCapabilitiesWithAppliedDefaults(capabilities, capabilityDefinitions) + if gardencorev1beta1helper.AreCapabilitiesEqual(regionDefaultedCapabilities, coreDefaultedCapabilityFlavor.Capabilities) { isFound = true } } if !isFound { allErrs = append(allErrs, field.Required(path, - fmt.Sprintf("machine image version %s@%s is missing region %q in capabilitySet %v in the NamespacedCloudProfile providerConfig", - machineImage.Name, version.Version, region, coreDefaultedCapabilitySet.Capabilities))) + fmt.Sprintf("machine image version %s@%s is missing region %q in capabilityFlavor %v in the NamespacedCloudProfile providerConfig", + machineImage.Name, version.Version, region, coreDefaultedCapabilityFlavor.Capabilities))) } } } diff --git a/pkg/admission/validator/namespacedcloudprofile_test.go b/pkg/admission/validator/namespacedcloudprofile_test.go index 6ae1cc672..3b4b98b39 100644 --- a/pkg/admission/validator/namespacedcloudprofile_test.go +++ b/pkg/admission/validator/namespacedcloudprofile_test.go @@ -40,13 +40,13 @@ var _ = DescribeTableSubtree("NamespacedCloudProfile Validator", func(isCapabili namespacedCloudProfileValidator extensionswebhook.Validator namespacedCloudProfile *core.NamespacedCloudProfile cloudProfile *v1beta1.CloudProfile - capabilitiesDefinitions []v1beta1.CapabilityDefinition + capabilityDefinitions []v1beta1.CapabilityDefinition ) BeforeEach(func() { if isCapabilitiesCloudProfile { - capabilitiesDefinitions = []v1beta1.CapabilityDefinition{ - {Name: v1beta1constants.ArchitectureName, Values: []string{v1beta1constants.ArchitectureAMD64}}, + capabilityDefinitions = []v1beta1.CapabilityDefinition{ + {Name: v1beta1constants.ArchitectureName, Values: []string{"amd64"}}, } } scheme := runtime.NewScheme() @@ -77,7 +77,7 @@ var _ = DescribeTableSubtree("NamespacedCloudProfile Validator", func(isCapabili Name: "cloud-profile", }, Spec: v1beta1.CloudProfileSpec{ - Capabilities: capabilitiesDefinitions, + MachineCapabilities: capabilityDefinitions, }, } }) @@ -99,9 +99,9 @@ var _ = DescribeTableSubtree("NamespacedCloudProfile Validator", func(isCapabili namespacedAmiMappings := `{"name":"image-1","versions":[{"version":"1.1","regions":[{"name":"eu1","ami":"ami-123"}]}]}, {"name":"image-2","versions":[{"version":"2.0","regions":[{"name":"eu1","ami":"ami-123"}]}]}` if isCapabilitiesCloudProfile { - amiMappings = `"capabilitySets":[{"regions":[{"name":"eu1","ami":"ami-123"}]}]` - namespacedAmiMappings = `{"name":"image-1","versions":[{"version":"1.1","capabilitySets":[{"regions":[{"name":"eu1","ami":"ami-123"}]}]}]}, - {"name":"image-2","versions":[{"version":"2.0","capabilitySets":[{"regions":[{"name":"eu1","ami":"ami-123"}]}]}]}` + amiMappings = `"capabilityFlavors":[{"regions":[{"name":"eu1","ami":"ami-123"}]}]` + namespacedAmiMappings = `{"name":"image-1","versions":[{"version":"1.1","capabilityFlavors":[{"regions":[{"name":"eu1","ami":"ami-123"}]}]}]}, + {"name":"image-2","versions":[{"version":"2.0","capabilityFlavors":[{"regions":[{"name":"eu1","ami":"ami-123"}]}]}]}` } cloudProfile.Spec.ProviderConfig = &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{ @@ -149,7 +149,7 @@ var _ = DescribeTableSubtree("NamespacedCloudProfile Validator", func(isCapabili amiMappings := `"regions":[{"name":"eu1","ami":"ami-123"}]` if isCapabilitiesCloudProfile { - amiMappings = `"capabilitySets":[{"regions":[{"name":"eu1","ami":"ami-123"}]}]` + amiMappings = `"capabilityFlavors":[{"regions":[{"name":"eu1","ami":"ami-123"}]}]` } cloudProfile.Spec.MachineImages = []v1beta1.MachineImage{ @@ -187,7 +187,7 @@ var _ = DescribeTableSubtree("NamespacedCloudProfile Validator", func(isCapabili It("should fail for NamespacedCloudProfile specifying provider config without the according version in the spec.machineImages", func() { amiMappings := `"regions":[{"name":"eu1","ami":"ami-123"}]` if isCapabilitiesCloudProfile { - amiMappings = `"capabilitySets":[{"regions":[{"name":"eu1","ami":"ami-123"}]}]` + amiMappings = `"capabilityFlavors":[{"regions":[{"name":"eu1","ami":"ami-123"}]}]` } namespacedCloudProfile.Spec.ProviderConfig = &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{ @@ -228,14 +228,14 @@ var _ = DescribeTableSubtree("NamespacedCloudProfile Validator", func(isCapabili ]` image1FallbackMappings := `"regions":[{"name":"image-region-2","ami":"id-img-reg-2"}]` if isCapabilitiesCloudProfile { - image1AmiMappings = `"capabilitySets":[ + image1AmiMappings = `"capabilityFlavors":[ {"capabilities":{"architecture":["arm64"]},"regions":[{"name":"image-region-1","ami":"id-img-reg-1"}]}, {"capabilities":{"architecture":["amd64"]},"regions":[{"name":"image-region-2","ami":"id-img-reg-2"}]} ]` - image1FallbackMappings = `"capabilitySets":[ + image1FallbackMappings = `"capabilityFlavors":[ {"capabilities":{"architecture":["amd64"]},"regions":[{"name":"image-region-2","ami":"id-img-reg-2"}]} ]` - cloudProfile.Spec.Capabilities[0].Values = []string{v1beta1constants.ArchitectureAMD64, v1beta1constants.ArchitectureARM64} + cloudProfile.Spec.MachineCapabilities[0].Values = []string{"amd64", "arm64"} } namespacedCloudProfile.Spec.ProviderConfig = &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{ "apiVersion":"aws.provider.extensions.gardener.cloud/v1alpha1", @@ -251,16 +251,16 @@ var _ = DescribeTableSubtree("NamespacedCloudProfile Validator", func(isCapabili Name: "image-1", Versions: []core.MachineImageVersion{ {ExpirableVersion: core.ExpirableVersion{Version: "1.1-regions"}, Architectures: []string{"amd64", "arm64"}, - CapabilitySets: []core.CapabilitySet{ + CapabilityFlavors: []core.MachineImageFlavor{ {Capabilities: core.Capabilities{v1beta1constants.ArchitectureName: []string{"amd64"}}}, {Capabilities: core.Capabilities{v1beta1constants.ArchitectureName: []string{"arm64"}}}, }}, {ExpirableVersion: core.ExpirableVersion{Version: "1.1-fallback"}, Architectures: []string{"arm64"}, - CapabilitySets: []core.CapabilitySet{ + CapabilityFlavors: []core.MachineImageFlavor{ {Capabilities: core.Capabilities{v1beta1constants.ArchitectureName: []string{"arm64"}}}, }}, {ExpirableVersion: core.ExpirableVersion{Version: "1.1-missing"}, Architectures: []string{"arm64"}, - CapabilitySets: []core.CapabilitySet{ + CapabilityFlavors: []core.MachineImageFlavor{ {Capabilities: core.Capabilities{v1beta1constants.ArchitectureName: []string{"arm64"}}}, }}, }, @@ -275,19 +275,19 @@ var _ = DescribeTableSubtree("NamespacedCloudProfile Validator", func(isCapabili Expect(err).To(ConsistOf(PointTo(MatchFields(IgnoreExtras, Fields{ "Type": Equal(field.ErrorTypeRequired), "Field": fieldMatcher, - "Detail": Equal("machine image version image-1@1.1-regions is missing region \"image-region-1\" in capabilitySet map[architecture:[amd64]] in the NamespacedCloudProfile providerConfig"), + "Detail": Equal("machine image version image-1@1.1-regions is missing region \"image-region-1\" in capabilityFlavor map[architecture:[amd64]] in the NamespacedCloudProfile providerConfig"), })), PointTo(MatchFields(IgnoreExtras, Fields{ "Type": Equal(field.ErrorTypeRequired), "Field": fieldMatcher, - "Detail": Equal("machine image version image-1@1.1-regions is missing region \"image-region-2\" in capabilitySet map[architecture:[arm64]] in the NamespacedCloudProfile providerConfig"), + "Detail": Equal("machine image version image-1@1.1-regions is missing region \"image-region-2\" in capabilityFlavor map[architecture:[arm64]] in the NamespacedCloudProfile providerConfig"), })), PointTo(MatchFields(IgnoreExtras, Fields{ "Type": Equal(field.ErrorTypeForbidden), "Field": fieldMatcher, - "Detail": Equal("machine image version image-1@1.1-fallback has an excess capabilitySet map[architecture:[amd64]], which is not defined in the machineImages spec"), + "Detail": Equal("machine image version image-1@1.1-fallback has an excess capabilityFlavor map[architecture:[amd64]], which is not defined in the machineImages spec"), })), PointTo(MatchFields(IgnoreExtras, Fields{ "Type": Equal(field.ErrorTypeRequired), "Field": fieldMatcher, - "Detail": Equal("machine image version image-1@1.1-fallback has a capabilitySet map[architecture:[arm64]] not defined in the NamespacedCloudProfile providerConfig"), + "Detail": Equal("machine image version image-1@1.1-fallback has a capabilityFlavor map[architecture:[arm64]] not defined in the NamespacedCloudProfile providerConfig"), })), PointTo(MatchFields(IgnoreExtras, Fields{ "Type": Equal(field.ErrorTypeRequired), "Field": fieldMatcher, diff --git a/pkg/admission/validator/shoot.go b/pkg/admission/validator/shoot.go index 36f191aa5..bc2941bcd 100644 --- a/pkg/admission/validator/shoot.go +++ b/pkg/admission/validator/shoot.go @@ -166,7 +166,7 @@ func (s *shoot) validateShootUpdate(ctx context.Context, oldShoot, shoot *core.S if shoot.DeletionTimestamp == nil { // If the Shoot is being deleted, we do not validate the workers against the cloud // profile, as the workers will be deleted anyway. - if errList := awsvalidation.ValidateWorkersAgainstCloudProfileOnUpdate(oldShoot.Spec.Provider.Workers, shoot.Spec.Provider.Workers, shoot.Spec.Region, awsCloudProfile, cloudProfileSpec.MachineTypes, cloudProfileSpec.Capabilities, fldPath.Child("workers")); len(errList) != 0 { + if errList := awsvalidation.ValidateWorkersAgainstCloudProfileOnUpdate(oldShoot.Spec.Provider.Workers, shoot.Spec.Provider.Workers, shoot.Spec.Region, awsCloudProfile, cloudProfileSpec.MachineTypes, cloudProfileSpec.MachineCapabilities, fldPath.Child("workers")); len(errList) != 0 { return errList.ToAggregate() } } @@ -184,7 +184,7 @@ func (s *shoot) validateShootCreation(ctx context.Context, shoot *core.Shoot, cl return err } - if errList := awsvalidation.ValidateWorkersAgainstCloudProfileOnCreation(shoot.Spec.Provider.Workers, shoot.Spec.Region, awsCloudProfile, cloudProfileSpec.MachineTypes, cloudProfileSpec.Capabilities, fldPath.Child("workers")); len(errList) != 0 { + if errList := awsvalidation.ValidateWorkersAgainstCloudProfileOnCreation(shoot.Spec.Provider.Workers, shoot.Spec.Region, awsCloudProfile, cloudProfileSpec.MachineTypes, cloudProfileSpec.MachineCapabilities, fldPath.Child("workers")); len(errList) != 0 { return errList.ToAggregate() } diff --git a/pkg/apis/aws/helper/capabilities.go b/pkg/apis/aws/helper/capabilities.go deleted file mode 100644 index fd54320a5..000000000 --- a/pkg/apis/aws/helper/capabilities.go +++ /dev/null @@ -1,248 +0,0 @@ -// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - -package helper - -// This file contains helper functions for capabilities handling, e.g. conversion, comparison, validation. -// These functions can be used by different providers and should not contain any provider-specific logic. -// All functions in this file should be transitioned into the Gardener core repository over time once the -// implementation is stable. - -import ( - "fmt" - "maps" - "slices" - - gardencoreapi "github.com/gardener/gardener/pkg/api" - gardencore "github.com/gardener/gardener/pkg/apis/core" - gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1" - v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants" - gardencorev1beta1helper "github.com/gardener/gardener/pkg/apis/core/v1beta1/helper" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/validation/field" -) - -// ConvertV1beta1CapabilitiesDefinitions converts core.CapabilityDefinition objects to v1beta1.CapabilityDefinition objects. -func ConvertV1beta1CapabilitiesDefinitions(capabilitiesDefinitions []gardencore.CapabilityDefinition) ([]gardencorev1beta1.CapabilityDefinition, error) { - var v1beta1CapabilitiesDefinitions []gardencorev1beta1.CapabilityDefinition - for _, capabilityDefinition := range capabilitiesDefinitions { - var v1beta1CapabilityDefinition gardencorev1beta1.CapabilityDefinition - err := gardencoreapi.Scheme.Convert(&capabilityDefinition, &v1beta1CapabilityDefinition, nil) - if err != nil { - return nil, fmt.Errorf("failed to convert capability definition: %w", err) - } - v1beta1CapabilitiesDefinitions = append(v1beta1CapabilitiesDefinitions, v1beta1CapabilityDefinition) - } - return v1beta1CapabilitiesDefinitions, nil -} - -// AreCapabilitiesEqual checks if two capabilities are semantically equal. -func AreCapabilitiesEqual(a, b gardencorev1beta1.Capabilities) bool { - return areCapabilitiesSubsetOf(a, b) && areCapabilitiesSubsetOf(b, a) -} - -// areCapabilitiesSubsetOf verifies if all keys and values in `source` exist in `target`. -func areCapabilitiesSubsetOf(source, target gardencorev1beta1.Capabilities) bool { - for key, valuesSource := range source { - valuesTarget, exists := target[key] - if !exists { - return false - } - for _, value := range valuesSource { - if !slices.Contains(valuesTarget, value) { - return false - } - } - } - return true -} - -// ValidateCapabilities validates the capabilities of a machine type or machine image against the capabilitiesDefinition located in a cloud profile at spec.capabilities. -// It checks if the capabilities are supported by the cloud profile and if the architecture is defined correctly. -// It returns a list of field errors if any validation fails. -func ValidateCapabilities(capabilities gardencorev1beta1.Capabilities, capabilitiesDefinitions []gardencorev1beta1.CapabilityDefinition, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - // create map from capabilitiesDefinitions - capabilitiesDefinition := make(map[string][]string) - for _, capabilityDefinition := range capabilitiesDefinitions { - capabilitiesDefinition[capabilityDefinition.Name] = capabilityDefinition.Values - } - supportedCapabilityKeys := slices.Collect(maps.Keys(capabilitiesDefinition)) - - // Check if all capabilities are supported by the cloud profile - for capabilityKey, capability := range capabilities { - supportedValues, keyExists := capabilitiesDefinition[capabilityKey] - if !keyExists { - allErrs = append(allErrs, field.NotSupported(fldPath, capabilityKey, supportedCapabilityKeys)) - continue - } - for i, value := range capability { - if !slices.Contains(supportedValues, value) { - allErrs = append(allErrs, field.NotSupported(fldPath.Child(capabilityKey).Index(i), value, supportedValues)) - } - } - } - - // Check additional requirements for architecture - // - must be defined when multiple architectures are supported by the cloud profile - supportedArchitectures := capabilitiesDefinition[v1beta1constants.ArchitectureName] - architectures := capabilities[v1beta1constants.ArchitectureName] - if len(supportedArchitectures) > 1 && len(architectures) != 1 { - allErrs = append(allErrs, field.Invalid(fldPath.Child(v1beta1constants.ArchitectureName), architectures, "must define exactly one architecture when multiple architectures are supported by the cloud profile")) - } - - return allErrs -} - -// AreCapabilitiesCompatible checks if two sets of capabilities are compatible. -// It applies defaults from the capability definitions to both sets before checking compatibility. -func AreCapabilitiesCompatible(capabilities1, capabilities2 gardencorev1beta1.Capabilities, capabilitiesDefinitions []gardencorev1beta1.CapabilityDefinition) bool { - defaultedCapabilities1 := gardencorev1beta1helper.GetCapabilitiesWithAppliedDefaults(capabilities1, capabilitiesDefinitions) - defaultedCapabilities2 := gardencorev1beta1helper.GetCapabilitiesWithAppliedDefaults(capabilities2, capabilitiesDefinitions) - - isSupported := true - commonCapabilities := getCapabilitiesIntersection(defaultedCapabilities1, defaultedCapabilities2) - // If the intersection has at least one value for each capability, the capabilities are supported. - for _, values := range commonCapabilities { - if len(values) == 0 { - isSupported = false - break - } - } - - return isSupported -} - -// getCapabilitiesIntersection returns the intersection of multiple capabilities objects. -func getCapabilitiesIntersection(capabilitiesList ...gardencorev1beta1.Capabilities) gardencorev1beta1.Capabilities { - intersection := make(gardencorev1beta1.Capabilities) - - if len(capabilitiesList) == 0 { - return intersection - } - - // Initialize intersection with the first capabilities object - maps.Copy(intersection, capabilitiesList[0]) - - intersect := func(slice1, slice2 []string) []string { - elementSet1 := sets.New(slice1...) - elementSet2 := sets.New(slice2...) - - return elementSet1.Intersection(elementSet2).UnsortedList() - } - - // Iterate through the remaining capabilities objects and refine the intersection - for _, capabilities := range capabilitiesList[1:] { - for key, values := range intersection { - intersection[key] = intersect(values, capabilities[key]) - } - } - - return intersection -} - -// HasCapabilities defines an interface for types that contain Capabilities -type HasCapabilities interface { - GetCapabilities() gardencorev1beta1.Capabilities - SetCapabilities(gardencorev1beta1.Capabilities) -} - -// FindBestCapabilitySet finds the most appropriate capability set from the provided capability sets -// based on the requested machine capabilities and the definitions of capabilities. -func FindBestCapabilitySet[T HasCapabilities]( - capabilitySets []T, - machineCapabilities gardencorev1beta1.Capabilities, - capabilitiesDefinitions []gardencorev1beta1.CapabilityDefinition, -) (T, error) { - var zeroValue T - compatibleCapabilitySets := findCompatibleCapabilitySets(capabilitySets, machineCapabilities, capabilitiesDefinitions) - - if len(compatibleCapabilitySets) == 0 { - return zeroValue, fmt.Errorf("no compatible capability set found") - } - - bestMatch, err := selectBestCapabilitySet(compatibleCapabilitySets, capabilitiesDefinitions) - if err != nil { - return zeroValue, err - } - return bestMatch, nil -} - -// findCompatibleCapabilitySets returns all capability sets that are compatible with the given machine capabilities. -func findCompatibleCapabilitySets[T HasCapabilities]( - capabilitySets []T, machineCapabilities gardencorev1beta1.Capabilities, capabilitiesDefinitions []gardencorev1beta1.CapabilityDefinition, -) []T { - var compatibleSets []T - for _, capabilitySet := range capabilitySets { - if AreCapabilitiesCompatible(capabilitySet.GetCapabilities(), machineCapabilities, capabilitiesDefinitions) { - compatibleSets = append(compatibleSets, capabilitySet) - } - } - return compatibleSets -} - -// selectBestCapabilitySet selects the most appropriate capability set based on the priority -// of capabilities and their values as defined in capabilitiesDefinitions. -// -// Selection follows a priority-based approach: -// 1. Capabilities are ordered by priority in the definitions list (highest priority first) -// 2. Within each capability, values are ordered by preference (most preferred first) -// 3. Selection is determined by the first capability value difference found -func selectBestCapabilitySet[T HasCapabilities]( - compatibleSets []T, - capabilitiesDefinitions []gardencorev1beta1.CapabilityDefinition, -) (T, error) { - var zeroValue T - if len(compatibleSets) == 1 { - return compatibleSets[0], nil - } - - // Apply capability defaults for better comparison - normalizedSets := make([]T, len(compatibleSets)) - copy(normalizedSets, compatibleSets) - - // Normalize capability sets by applying defaults - for i := range normalizedSets { - normalizedSets[i].SetCapabilities(gardencorev1beta1helper.GetCapabilitiesWithAppliedDefaults( - normalizedSets[i].GetCapabilities(), - capabilitiesDefinitions, - )) - } - - // Evaluate capability sets based on capability definitions priority - remainingSets := normalizedSets - - // For each capability (in priority order) - for _, capabilityDef := range capabilitiesDefinitions { - // For each preferred value (in preference order) - for _, capabilityValue := range capabilityDef.Values { - var setsWithPreferredValue []T - - // Find sets that support this capability value - for _, set := range remainingSets { - if slices.Contains(set.GetCapabilities()[capabilityDef.Name], capabilityValue) { - setsWithPreferredValue = append(setsWithPreferredValue, set) - } - } - - // If we found sets with this value, narrow down our selection - if len(setsWithPreferredValue) > 0 { - remainingSets = setsWithPreferredValue - - // If only one set remains, we've found our match - if len(remainingSets) == 1 { - return remainingSets[0], nil - } - } - } - } - - // If we couldn't determine a single best match, this indicates a problem with the cloud profile - if len(remainingSets) != 1 { - return zeroValue, fmt.Errorf("found multiple capability sets with identical capabilities; this indicates an invalid cloudprofile was admitted. Please open a bug report at https://github.com/gardener/gardener/issues") - } - - return remainingSets[0], nil -} diff --git a/pkg/apis/aws/helper/helper.go b/pkg/apis/aws/helper/helper.go index 7d1a7f0a0..9e9546159 100644 --- a/pkg/apis/aws/helper/helper.go +++ b/pkg/apis/aws/helper/helper.go @@ -7,9 +7,11 @@ package helper import ( "fmt" + "github.com/gardener/gardener/extensions/pkg/controller/worker" "github.com/gardener/gardener/extensions/pkg/util" gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1" v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants" + gardencorev1beta1helper "github.com/gardener/gardener/pkg/apis/core/v1beta1/helper" "k8s.io/apimachinery/pkg/runtime" "k8s.io/utils/ptr" @@ -85,14 +87,14 @@ func FindImageInCloudProfile( name, version, region string, arch *string, machineCapabilities gardencorev1beta1.Capabilities, - capabilitiesDefinitions []gardencorev1beta1.CapabilityDefinition, -) (*api.CapabilitySet, error) { + capabilityDefinitions []gardencorev1beta1.CapabilityDefinition, +) (*api.MachineImageFlavor, error) { if cloudProfileConfig == nil { return nil, fmt.Errorf("cloud profile config is nil") } machineImages := cloudProfileConfig.MachineImages - capabilitySet, err := findCapabilitySetFromMachineImages(machineImages, name, version, region, arch, machineCapabilities, capabilitiesDefinitions) + capabilitySet, err := findMachineImageFlavor(machineImages, name, version, region, arch, machineCapabilities, capabilityDefinitions) if err != nil { return nil, fmt.Errorf("could not find an AMI for region %q, image %q, version %q that supports %v: %w", region, name, version, machineCapabilities, err) } @@ -106,9 +108,9 @@ func FindImageInCloudProfile( // FindImageInWorkerStatus takes a list of machine images from the worker status and tries to find the first entry // whose name, version, architecture, capabilities and zone matches with the given ones. If no such entry is // found then an error will be returned. -func FindImageInWorkerStatus(machineImages []api.MachineImage, name string, version string, architecture *string, machineCapabilities gardencorev1beta1.Capabilities, capabilitiesDefinitions []gardencorev1beta1.CapabilityDefinition) (*api.MachineImage, error) { - // If no capabilitiesDefinitions are specified, return the (legacy) architecture format field as no Capabilities are used. - if len(capabilitiesDefinitions) == 0 { +func FindImageInWorkerStatus(machineImages []api.MachineImage, name string, version string, architecture *string, machineCapabilities gardencorev1beta1.Capabilities, capabilityDefinitions []gardencorev1beta1.CapabilityDefinition) (*api.MachineImage, error) { + // If no capabilityDefinitions are specified, return the (legacy) architecture format field as no Capabilities are used. + if len(capabilityDefinitions) == 0 { for _, statusMachineImage := range machineImages { if statusMachineImage.Architecture == nil { statusMachineImage.Architecture = ptr.To(v1beta1constants.ArchitectureAMD64) @@ -120,26 +122,26 @@ func FindImageInWorkerStatus(machineImages []api.MachineImage, name string, vers return nil, fmt.Errorf("no machine image found for image %q with version %q and architecture %q", name, version, *architecture) } - // If capabilitiesDefinitions are specified, we need to find the best matching capability set. + // If capabilityDefinitions are specified, we need to find the best matching capability set. for _, statusMachineImage := range machineImages { var statusMachineImageV1alpha1 v1alpha1.MachineImage if err := v1alpha1.Convert_aws_MachineImage_To_v1alpha1_MachineImage(&statusMachineImage, &statusMachineImageV1alpha1, nil); err != nil { return nil, fmt.Errorf("failed to convert machine image: %w", err) } - if statusMachineImage.Name == name && statusMachineImage.Version == version && AreCapabilitiesCompatible(statusMachineImageV1alpha1.Capabilities, machineCapabilities, capabilitiesDefinitions) { + if statusMachineImage.Name == name && statusMachineImage.Version == version && gardencorev1beta1helper.AreCapabilitiesCompatible(statusMachineImageV1alpha1.Capabilities, machineCapabilities, capabilityDefinitions) { return &statusMachineImage, nil } } return nil, fmt.Errorf("no machine image found for image %q with version %q and capabilities %v", name, version, machineCapabilities) } -func findCapabilitySetFromMachineImages( +func findMachineImageFlavor( machineImages []api.MachineImages, imageName, imageVersion, region string, arch *string, machineCapabilities gardencorev1beta1.Capabilities, - capabilitiesDefinitions []gardencorev1beta1.CapabilityDefinition, -) (*api.CapabilitySet, error) { + capabilityDefinitions []gardencorev1beta1.CapabilityDefinition, +) (*api.MachineImageFlavor, error) { for _, machineImage := range machineImages { if machineImage.Name != imageName { continue @@ -149,10 +151,10 @@ func findCapabilitySetFromMachineImages( continue } - if len(capabilitiesDefinitions) == 0 { + if len(capabilityDefinitions) == 0 { for _, mapping := range version.Regions { if region == mapping.Name && ptr.Equal(arch, mapping.Architecture) { - return &api.CapabilitySet{ + return &api.MachineImageFlavor{ Regions: []api.RegionAMIMapping{mapping}, Capabilities: gardencorev1beta1.Capabilities{}, }, nil @@ -161,10 +163,10 @@ func findCapabilitySetFromMachineImages( continue } - filteredCapabilitySets := filterCapabilitySetsByRegion(version.CapabilitySets, region) - bestMatch, err := FindBestCapabilitySet(filteredCapabilitySets, machineCapabilities, capabilitiesDefinitions) + filteredCapabilityFlavors := filterCapabilityFlavorsByRegion(version.CapabilityFlavors, region) + bestMatch, err := worker.FindBestImageFlavor(filteredCapabilityFlavors, machineCapabilities, capabilityDefinitions) if err != nil { - return nil, fmt.Errorf("could not determine best capabilitySet %w", err) + return nil, fmt.Errorf("could not determine best flavor %w", err) } return bestMatch, nil @@ -173,26 +175,26 @@ func findCapabilitySetFromMachineImages( return nil, nil } -// filterCapabilitySetsByRegion returns a new list with capabilitySets that only contain RegionAMIMappings +// filterCapabilityFlavorsByRegion returns a new list with capabilityFlavors that only contain RegionAMIMappings // of the region to filter for. -func filterCapabilitySetsByRegion(capabilitySets []api.CapabilitySet, regionName string) []*api.CapabilitySet { - var compatibleSets []*api.CapabilitySet +func filterCapabilityFlavorsByRegion(capabilityFlavors []api.MachineImageFlavor, regionName string) []*api.MachineImageFlavor { + var compatibleFlavors []*api.MachineImageFlavor - for _, capabilitySet := range capabilitySets { + for _, capabilityFlavor := range capabilityFlavors { var regionAMIMapping *api.RegionAMIMapping - for _, region := range capabilitySet.Regions { + for _, region := range capabilityFlavor.Regions { if region.Name == regionName { regionAMIMapping = ®ion } } if regionAMIMapping != nil { - compatibleSets = append(compatibleSets, &api.CapabilitySet{ + compatibleFlavors = append(compatibleFlavors, &api.MachineImageFlavor{ Regions: []api.RegionAMIMapping{*regionAMIMapping}, - Capabilities: capabilitySet.Capabilities, + Capabilities: capabilityFlavor.Capabilities, }) } } - return compatibleSets + return compatibleFlavors } // FindDataVolumeByName takes a list of data volumes and a data volume name. It tries to find the data volume entry for diff --git a/pkg/apis/aws/helper/helper_test.go b/pkg/apis/aws/helper/helper_test.go index a0a84fb84..538aa055b 100644 --- a/pkg/apis/aws/helper/helper_test.go +++ b/pkg/apis/aws/helper/helper_test.go @@ -73,13 +73,13 @@ var _ = Describe("Helper", func() { ) DescribeTableSubtree("Select Worker Images", func(hasCapabilities bool) { - var capabilitiesDefinitions []v1beta1.CapabilityDefinition + var capabilityDefinitions []v1beta1.CapabilityDefinition var machineCapabilities v1beta1.Capabilities var imageCapabilities v1beta1.Capabilities region := "europe" if hasCapabilities { - capabilitiesDefinitions = []v1beta1.CapabilityDefinition{ + capabilityDefinitions = []v1beta1.CapabilityDefinition{ {Name: "architecture", Values: []string{"amd64", "arm64"}}, {Name: "capability1", Values: []string{"value1", "value2", "value3"}}, } @@ -102,7 +102,7 @@ var _ = Describe("Helper", func() { expectedMachineImage.Architecture = nil } } - machineImage, err := FindImageInWorkerStatus(machineImages, name, version, arch, machineCapabilities, capabilitiesDefinitions) + machineImage, err := FindImageInWorkerStatus(machineImages, name, version, arch, machineCapabilities, capabilityDefinitions) expectResults(machineImage, expectedMachineImage, err, expectErr) }, @@ -123,7 +123,7 @@ var _ = Describe("Helper", func() { cfg := &api.CloudProfileConfig{} cfg.MachineImages = profileImages - capabilitySet, err := FindImageInCloudProfile(cfg, imageName, version, regionName, arch, machineCapabilities, capabilitiesDefinitions) + capabilitySet, err := FindImageInCloudProfile(cfg, imageName, version, regionName, arch, machineCapabilities, capabilityDefinitions) if expectedAMI != "" { Expect(err).NotTo(HaveOccurred()) @@ -236,7 +236,7 @@ func makeProfileMachineImages(name, version, region, ami string, arch *string, c Architecture: arch, }} } else { - versions[0].CapabilitySets = []api.CapabilitySet{{ + versions[0].CapabilityFlavors = []api.MachineImageFlavor{{ Capabilities: capabilities, Regions: []api.RegionAMIMapping{{ Name: region, diff --git a/pkg/apis/aws/types_cloudprofile.go b/pkg/apis/aws/types_cloudprofile.go index 473ad7d6d..9e85eac5b 100644 --- a/pkg/apis/aws/types_cloudprofile.go +++ b/pkg/apis/aws/types_cloudprofile.go @@ -32,39 +32,34 @@ type MachineImages struct { type MachineImageVersion struct { // Version is the version of the image. Version string - // TODO @Roncossek add "// deprecated" once aws cloudprofiles are migrated to use CapabilitySets + // TODO @Roncossek add "// deprecated" once aws cloudprofiles are migrated to use CapabilityFlavors // Regions is a mapping to the correct AMI for the machine image in the supported regions. Regions []RegionAMIMapping - // CapabilitySets is grouping of region AMIs by capabilities. - CapabilitySets []CapabilitySet + // CapabilityFlavors is grouping of region AMIs by capabilities. + CapabilityFlavors []MachineImageFlavor } -// CapabilitySet groups all RegionAMIMappings for a specific et of capabilities. -type CapabilitySet struct { +// MachineImageFlavor groups all RegionAMIMappings for a specific set of capabilities. +type MachineImageFlavor struct { // Regions is a mapping to the correct AMI for the machine image in the supported regions. Regions []RegionAMIMapping // Capabilities that are supported by the AMIs in this set. Capabilities gardencorev1beta1.Capabilities } -// GetCapabilities returns the Capabilities of a CapabilitySet -func (cs *CapabilitySet) GetCapabilities() gardencorev1beta1.Capabilities { +// GetCapabilities returns the Capabilities of a MachineImageFlavor +func (cs MachineImageFlavor) GetCapabilities() gardencorev1beta1.Capabilities { return cs.Capabilities } -// SetCapabilities sets the Capabilities on a CapabilitySet -func (cs *CapabilitySet) SetCapabilities(capabilities gardencorev1beta1.Capabilities) { - cs.Capabilities = capabilities -} - // RegionAMIMapping is a mapping to the correct AMI for the machine image in the given region. type RegionAMIMapping struct { // Name is the name of the region. Name string // AMI is the AMI for the machine image. AMI string - // TODO @Roncossek add "// deprecated" once aws cloudprofiles are migrated to use CapabilitySets + // TODO @Roncossek add "// deprecated" once aws cloudprofiles are migrated to use CapabilityFlavors // Architecture is the CPU architecture of the machine image. Architecture *string diff --git a/pkg/apis/aws/v1alpha1/defaults.go b/pkg/apis/aws/v1alpha1/defaults.go index 9ce446192..3fbc8ea07 100644 --- a/pkg/apis/aws/v1alpha1/defaults.go +++ b/pkg/apis/aws/v1alpha1/defaults.go @@ -46,9 +46,9 @@ func SetDefaults_MachineImage(obj *MachineImage) { } } -// SetDefaults_CapabilitySet sets the architecture of capability set regions to "ignore". -func SetDefaults_CapabilitySet(obj *CapabilitySet) { - // Implementation is only needed to ensure SetDefaults_RegionAMIMapping is not executed on CapabilitySet.Regions +// SetDefaults_MachineImageFlavor sets the architecture of capability set regions to "ignore". +func SetDefaults_MachineImageFlavor(obj *MachineImageFlavor) { + // Implementation is only needed to ensure SetDefaults_RegionAMIMapping is not executed on MachineImageFlavor.Regions for l := range obj.Regions { d := &obj.Regions[l] if d.Architecture == nil { diff --git a/pkg/apis/aws/v1alpha1/types_cloudprofile.go b/pkg/apis/aws/v1alpha1/types_cloudprofile.go index 61fa31f59..be5f7bf6d 100644 --- a/pkg/apis/aws/v1alpha1/types_cloudprofile.go +++ b/pkg/apis/aws/v1alpha1/types_cloudprofile.go @@ -33,29 +33,34 @@ type MachineImages struct { type MachineImageVersion struct { // Version is the version of the image. Version string `json:"version"` - // TODO @Roncossek add "// deprecated" once aws cloudprofiles are migrated to use CapabilitySets + // TODO @Roncossek add "// deprecated" once aws cloudprofiles are migrated to use CapabilityFlavors // Regions is a mapping to the correct AMI for the machine image in the supported regions. Regions []RegionAMIMapping `json:"regions"` - // CapabilitySets is grouping of region AMIs by capabilities. - CapabilitySets []CapabilitySet `json:"capabilitySets"` + // CapabilityFlavors is grouping of region AMIs by capabilities. + CapabilityFlavors []MachineImageFlavor `json:"capabilityFlavors"` } -// CapabilitySet groups all RegionAMIMappings for a specific et of capabilities. -type CapabilitySet struct { +// MachineImageFlavor groups all RegionAMIMappings for a specific set of capabilities. +type MachineImageFlavor struct { // Regions is a mapping to the correct AMI for the machine image in the supported regions. Regions []RegionAMIMapping `json:"regions"` // Capabilities that are supported by the AMIs in this set. Capabilities gardencorev1beta1.Capabilities `json:"capabilities,omitempty"` } +// GetCapabilities returns the Capabilities of a MachineImageFlavor +func (cs *MachineImageFlavor) GetCapabilities() gardencorev1beta1.Capabilities { + return cs.Capabilities +} + // RegionAMIMapping is a mapping to the correct AMI for the machine image in the given region. type RegionAMIMapping struct { // Name is the name of the region. Name string `json:"name"` // AMI is the AMI for the machine image. AMI string `json:"ami"` - // TODO @Roncossek add "// deprecated" once aws cloudprofiles are migrated to use CapabilitySets + // TODO @Roncossek add "// deprecated" once aws cloudprofiles are migrated to use CapabilityFlavors // Architecture is the CPU architecture of the machine image. // +optional diff --git a/pkg/apis/aws/v1alpha1/zz_generated.conversion.go b/pkg/apis/aws/v1alpha1/zz_generated.conversion.go index a55d45612..3cc27f8ac 100644 --- a/pkg/apis/aws/v1alpha1/zz_generated.conversion.go +++ b/pkg/apis/aws/v1alpha1/zz_generated.conversion.go @@ -36,16 +36,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*CapabilitySet)(nil), (*aws.CapabilitySet)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_CapabilitySet_To_aws_CapabilitySet(a.(*CapabilitySet), b.(*aws.CapabilitySet), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*aws.CapabilitySet)(nil), (*CapabilitySet)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_aws_CapabilitySet_To_v1alpha1_CapabilitySet(a.(*aws.CapabilitySet), b.(*CapabilitySet), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*CloudControllerManagerConfig)(nil), (*aws.CloudControllerManagerConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha1_CloudControllerManagerConfig_To_aws_CloudControllerManagerConfig(a.(*CloudControllerManagerConfig), b.(*aws.CloudControllerManagerConfig), scope) }); err != nil { @@ -246,6 +236,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*MachineImageFlavor)(nil), (*aws.MachineImageFlavor)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_MachineImageFlavor_To_aws_MachineImageFlavor(a.(*MachineImageFlavor), b.(*aws.MachineImageFlavor), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*aws.MachineImageFlavor)(nil), (*MachineImageFlavor)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_aws_MachineImageFlavor_To_v1alpha1_MachineImageFlavor(a.(*aws.MachineImageFlavor), b.(*MachineImageFlavor), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*MachineImageVersion)(nil), (*aws.MachineImageVersion)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha1_MachineImageVersion_To_aws_MachineImageVersion(a.(*MachineImageVersion), b.(*aws.MachineImageVersion), scope) }); err != nil { @@ -419,28 +419,6 @@ func Convert_aws_BackupBucketConfig_To_v1alpha1_BackupBucketConfig(in *aws.Backu return autoConvert_aws_BackupBucketConfig_To_v1alpha1_BackupBucketConfig(in, out, s) } -func autoConvert_v1alpha1_CapabilitySet_To_aws_CapabilitySet(in *CapabilitySet, out *aws.CapabilitySet, s conversion.Scope) error { - out.Regions = *(*[]aws.RegionAMIMapping)(unsafe.Pointer(&in.Regions)) - out.Capabilities = *(*v1beta1.Capabilities)(unsafe.Pointer(&in.Capabilities)) - return nil -} - -// Convert_v1alpha1_CapabilitySet_To_aws_CapabilitySet is an autogenerated conversion function. -func Convert_v1alpha1_CapabilitySet_To_aws_CapabilitySet(in *CapabilitySet, out *aws.CapabilitySet, s conversion.Scope) error { - return autoConvert_v1alpha1_CapabilitySet_To_aws_CapabilitySet(in, out, s) -} - -func autoConvert_aws_CapabilitySet_To_v1alpha1_CapabilitySet(in *aws.CapabilitySet, out *CapabilitySet, s conversion.Scope) error { - out.Regions = *(*[]RegionAMIMapping)(unsafe.Pointer(&in.Regions)) - out.Capabilities = *(*v1beta1.Capabilities)(unsafe.Pointer(&in.Capabilities)) - return nil -} - -// Convert_aws_CapabilitySet_To_v1alpha1_CapabilitySet is an autogenerated conversion function. -func Convert_aws_CapabilitySet_To_v1alpha1_CapabilitySet(in *aws.CapabilitySet, out *CapabilitySet, s conversion.Scope) error { - return autoConvert_aws_CapabilitySet_To_v1alpha1_CapabilitySet(in, out, s) -} - func autoConvert_v1alpha1_CloudControllerManagerConfig_To_aws_CloudControllerManagerConfig(in *CloudControllerManagerConfig, out *aws.CloudControllerManagerConfig, s conversion.Scope) error { out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates)) out.UseCustomRouteController = (*bool)(unsafe.Pointer(in.UseCustomRouteController)) @@ -919,10 +897,32 @@ func Convert_aws_MachineImage_To_v1alpha1_MachineImage(in *aws.MachineImage, out return autoConvert_aws_MachineImage_To_v1alpha1_MachineImage(in, out, s) } +func autoConvert_v1alpha1_MachineImageFlavor_To_aws_MachineImageFlavor(in *MachineImageFlavor, out *aws.MachineImageFlavor, s conversion.Scope) error { + out.Regions = *(*[]aws.RegionAMIMapping)(unsafe.Pointer(&in.Regions)) + out.Capabilities = *(*v1beta1.Capabilities)(unsafe.Pointer(&in.Capabilities)) + return nil +} + +// Convert_v1alpha1_MachineImageFlavor_To_aws_MachineImageFlavor is an autogenerated conversion function. +func Convert_v1alpha1_MachineImageFlavor_To_aws_MachineImageFlavor(in *MachineImageFlavor, out *aws.MachineImageFlavor, s conversion.Scope) error { + return autoConvert_v1alpha1_MachineImageFlavor_To_aws_MachineImageFlavor(in, out, s) +} + +func autoConvert_aws_MachineImageFlavor_To_v1alpha1_MachineImageFlavor(in *aws.MachineImageFlavor, out *MachineImageFlavor, s conversion.Scope) error { + out.Regions = *(*[]RegionAMIMapping)(unsafe.Pointer(&in.Regions)) + out.Capabilities = *(*v1beta1.Capabilities)(unsafe.Pointer(&in.Capabilities)) + return nil +} + +// Convert_aws_MachineImageFlavor_To_v1alpha1_MachineImageFlavor is an autogenerated conversion function. +func Convert_aws_MachineImageFlavor_To_v1alpha1_MachineImageFlavor(in *aws.MachineImageFlavor, out *MachineImageFlavor, s conversion.Scope) error { + return autoConvert_aws_MachineImageFlavor_To_v1alpha1_MachineImageFlavor(in, out, s) +} + func autoConvert_v1alpha1_MachineImageVersion_To_aws_MachineImageVersion(in *MachineImageVersion, out *aws.MachineImageVersion, s conversion.Scope) error { out.Version = in.Version out.Regions = *(*[]aws.RegionAMIMapping)(unsafe.Pointer(&in.Regions)) - out.CapabilitySets = *(*[]aws.CapabilitySet)(unsafe.Pointer(&in.CapabilitySets)) + out.CapabilityFlavors = *(*[]aws.MachineImageFlavor)(unsafe.Pointer(&in.CapabilityFlavors)) return nil } @@ -934,7 +934,7 @@ func Convert_v1alpha1_MachineImageVersion_To_aws_MachineImageVersion(in *Machine func autoConvert_aws_MachineImageVersion_To_v1alpha1_MachineImageVersion(in *aws.MachineImageVersion, out *MachineImageVersion, s conversion.Scope) error { out.Version = in.Version out.Regions = *(*[]RegionAMIMapping)(unsafe.Pointer(&in.Regions)) - out.CapabilitySets = *(*[]CapabilitySet)(unsafe.Pointer(&in.CapabilitySets)) + out.CapabilityFlavors = *(*[]MachineImageFlavor)(unsafe.Pointer(&in.CapabilityFlavors)) return nil } diff --git a/pkg/apis/aws/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/aws/v1alpha1/zz_generated.deepcopy.go index 5f017b2de..89aab80bb 100644 --- a/pkg/apis/aws/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/aws/v1alpha1/zz_generated.deepcopy.go @@ -45,44 +45,6 @@ func (in *BackupBucketConfig) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CapabilitySet) DeepCopyInto(out *CapabilitySet) { - *out = *in - if in.Regions != nil { - in, out := &in.Regions, &out.Regions - *out = make([]RegionAMIMapping, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Capabilities != nil { - in, out := &in.Capabilities, &out.Capabilities - *out = make(v1beta1.Capabilities, len(*in)) - for key, val := range *in { - var outVal []string - if val == nil { - (*out)[key] = nil - } else { - in, out := &val, &outVal - *out = make(v1beta1.CapabilityValues, len(*in)) - copy(*out, *in) - } - (*out)[key] = outVal - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CapabilitySet. -func (in *CapabilitySet) DeepCopy() *CapabilitySet { - if in == nil { - return nil - } - out := new(CapabilitySet) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CloudControllerManagerConfig) DeepCopyInto(out *CloudControllerManagerConfig) { *out = *in @@ -606,6 +568,44 @@ func (in *MachineImage) DeepCopy() *MachineImage { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MachineImageFlavor) DeepCopyInto(out *MachineImageFlavor) { + *out = *in + if in.Regions != nil { + in, out := &in.Regions, &out.Regions + *out = make([]RegionAMIMapping, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Capabilities != nil { + in, out := &in.Capabilities, &out.Capabilities + *out = make(v1beta1.Capabilities, len(*in)) + for key, val := range *in { + var outVal []string + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = make(v1beta1.CapabilityValues, len(*in)) + copy(*out, *in) + } + (*out)[key] = outVal + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineImageFlavor. +func (in *MachineImageFlavor) DeepCopy() *MachineImageFlavor { + if in == nil { + return nil + } + out := new(MachineImageFlavor) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MachineImageVersion) DeepCopyInto(out *MachineImageVersion) { *out = *in @@ -616,9 +616,9 @@ func (in *MachineImageVersion) DeepCopyInto(out *MachineImageVersion) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.CapabilitySets != nil { - in, out := &in.CapabilitySets, &out.CapabilitySets - *out = make([]CapabilitySet, len(*in)) + if in.CapabilityFlavors != nil { + in, out := &in.CapabilityFlavors, &out.CapabilityFlavors + *out = make([]MachineImageFlavor, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } diff --git a/pkg/apis/aws/v1alpha1/zz_generated.defaults.go b/pkg/apis/aws/v1alpha1/zz_generated.defaults.go index 4c59cfea4..dc455a165 100644 --- a/pkg/apis/aws/v1alpha1/zz_generated.defaults.go +++ b/pkg/apis/aws/v1alpha1/zz_generated.defaults.go @@ -32,9 +32,9 @@ func SetObjectDefaults_CloudProfileConfig(in *CloudProfileConfig) { c := &b.Regions[k] SetDefaults_RegionAMIMapping(c) } - for k := range b.CapabilitySets { - c := &b.CapabilitySets[k] - SetDefaults_CapabilitySet(c) + for k := range b.CapabilityFlavors { + c := &b.CapabilityFlavors[k] + SetDefaults_MachineImageFlavor(c) for l := range c.Regions { d := &c.Regions[l] SetDefaults_RegionAMIMapping(d) diff --git a/pkg/apis/aws/validation/cloudprofile.go b/pkg/apis/aws/validation/cloudprofile.go index 28c0589da..a1fda89b7 100644 --- a/pkg/apis/aws/validation/cloudprofile.go +++ b/pkg/apis/aws/validation/cloudprofile.go @@ -20,30 +20,44 @@ import ( "k8s.io/utils/ptr" apisaws "github.com/gardener/gardener-extension-provider-aws/pkg/apis/aws" - "github.com/gardener/gardener-extension-provider-aws/pkg/apis/aws/helper" ) // ValidateCloudProfileConfig validates a CloudProfileConfig object. -func ValidateCloudProfileConfig(cpConfig *apisaws.CloudProfileConfig, machineImages []core.MachineImage, capabilitiesDefinitions []gardencorev1beta1.CapabilityDefinition, fldPath *field.Path) field.ErrorList { +func ValidateCloudProfileConfig(cpConfig *apisaws.CloudProfileConfig, machineImages []core.MachineImage, capabilityDefinitions []gardencorev1beta1.CapabilityDefinition, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} - machineImagesPath := fldPath.Child("machineImages") - if len(cpConfig.MachineImages) == 0 { - allErrs = append(allErrs, field.Required(machineImagesPath, "must provide at least one machine image")) + + // Validate machine images section + allErrs = append(allErrs, validateMachineImages(cpConfig.MachineImages, capabilityDefinitions, machineImagesPath)...) + + // Validate machine image mappings + allErrs = append(allErrs, validateMachineImageMapping(machineImages, cpConfig, capabilityDefinitions, field.NewPath("spec").Child("machineImages"))...) + + return allErrs +} + +// validateMachineImages validates the machine images section of CloudProfileConfig +func validateMachineImages(machineImages []apisaws.MachineImages, capabilityDefinitions []gardencorev1beta1.CapabilityDefinition, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + // Ensure at least one machine image is provided + if len(machineImages) == 0 { + allErrs = append(allErrs, field.Required(fldPath, "must provide at least one machine image")) + return allErrs } - for i, machineImage := range cpConfig.MachineImages { - idxPath := machineImagesPath.Index(i) - allErrs = append(allErrs, ValidateProviderMachineImage(idxPath, machineImage, capabilitiesDefinitions)...) + + // Validate each machine image + for i, machineImage := range machineImages { + idxPath := fldPath.Index(i) + allErrs = append(allErrs, ValidateProviderMachineImage(machineImage, capabilityDefinitions, idxPath)...) } - allErrs = append(allErrs, validateMachineImageMapping(machineImages, cpConfig, capabilitiesDefinitions, field.NewPath("spec").Child("machineImages"))...) return allErrs } // ValidateProviderMachineImage validates a CloudProfileConfig MachineImages entry. -func ValidateProviderMachineImage(validationPath *field.Path, providerImage apisaws.MachineImages, capabilitiesDefinitions []gardencorev1beta1.CapabilityDefinition) field.ErrorList { +func ValidateProviderMachineImage(providerImage apisaws.MachineImages, capabilityDefinitions []gardencorev1beta1.CapabilityDefinition, validationPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} - hasCloudProfileCapabilities := len(capabilitiesDefinitions) > 0 if len(providerImage.Name) == 0 { allErrs = append(allErrs, field.Required(validationPath.Child("name"), "must provide a name")) @@ -52,34 +66,55 @@ func ValidateProviderMachineImage(validationPath *field.Path, providerImage apis if len(providerImage.Versions) == 0 { allErrs = append(allErrs, field.Required(validationPath.Child("versions"), fmt.Sprintf("must provide at least one version for machine image %q", providerImage.Name))) } + + // Validate each version for j, version := range providerImage.Versions { jdxPath := validationPath.Child("versions").Index(j) + allErrs = append(allErrs, validateMachineImageVersion(providerImage, capabilityDefinitions, version, jdxPath)...) + } - if len(version.Version) == 0 { - allErrs = append(allErrs, field.Required(jdxPath.Child("version"), "must provide a version")) - } + return allErrs +} - if hasCloudProfileCapabilities { - for k, capabilitySet := range version.CapabilitySets { - kdxPath := jdxPath.Child("capabilitySets").Index(k) - allErrs = append(allErrs, helper.ValidateCapabilities(capabilitySet.Capabilities, capabilitiesDefinitions, kdxPath.Child("capabilities"))...) - allErrs = append(allErrs, validateRegions(capabilitySet.Regions, providerImage.Name, version.Version, hasCloudProfileCapabilities, kdxPath)...) - } - if len(version.Regions) > 0 { - allErrs = append(allErrs, field.Forbidden(jdxPath.Child("regions"), "must not be set as CloudProfile defines capabilities. Use capabilitySets.regions instead.")) - } - } else { - allErrs = append(allErrs, validateRegions(version.Regions, providerImage.Name, version.Version, hasCloudProfileCapabilities, jdxPath)...) - if len(version.CapabilitySets) > 0 { - allErrs = append(allErrs, field.Forbidden(jdxPath.Child("capabilitySets"), "must not be set as CloudProfile does not define capabilities. Use regions instead.")) - } +// validateMachineImageVersion validates a specific machine image version +func validateMachineImageVersion(providerImage apisaws.MachineImages, capabilityDefinitions []gardencorev1beta1.CapabilityDefinition, version apisaws.MachineImageVersion, jdxPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if len(version.Version) == 0 { + allErrs = append(allErrs, field.Required(jdxPath.Child("version"), "must provide a version")) + } + + if len(capabilityDefinitions) > 0 { + allErrs = append(allErrs, validateCapabilityFlavors(providerImage, version, capabilityDefinitions, jdxPath)...) + } else { + allErrs = append(allErrs, validateRegions(version.Regions, providerImage.Name, version.Version, capabilityDefinitions, jdxPath)...) + if len(version.CapabilityFlavors) > 0 { + allErrs = append(allErrs, field.Forbidden(jdxPath.Child("capabilityFlavors"), "must not be set as CloudProfile does not define capabilities. Use regions instead.")) } } + return allErrs +} + +// validateCapabilityFlavors validates the capability flavors of a machine image version. +func validateCapabilityFlavors(providerImage apisaws.MachineImages, version apisaws.MachineImageVersion, capabilityDefinitions []gardencorev1beta1.CapabilityDefinition, jdxPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + // When using capabilities, regions must not be set + if len(version.Regions) > 0 { + allErrs = append(allErrs, field.Forbidden(jdxPath.Child("regions"), "must not be set as CloudProfile defines capabilities. Use capabilityFlavors.regions instead.")) + } + // Validate each flavor's capabilities and regions + for k, capabilitySet := range version.CapabilityFlavors { + kdxPath := jdxPath.Child("capabilityFlavors").Index(k) + allErrs = append(allErrs, gutil.ValidateCapabilities(capabilitySet.Capabilities, capabilityDefinitions, kdxPath.Child("capabilities"))...) + allErrs = append(allErrs, validateRegions(capabilitySet.Regions, providerImage.Name, version.Version, capabilityDefinitions, kdxPath)...) + } return allErrs } -func validateRegions(regions []apisaws.RegionAMIMapping, version, name string, hasCloudProfileCapabilities bool, jdxPath *field.Path) field.ErrorList { +// validateRegions validates the regions of a machine image version or capability flavor. +func validateRegions(regions []apisaws.RegionAMIMapping, name, version string, capabilityDefinitions []gardencorev1beta1.CapabilityDefinition, jdxPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if len(regions) == 0 { return append(allErrs, field.Required(jdxPath.Child("regions"), fmt.Sprintf("must provide at least one region for machine image %q and version %q", name, version))) @@ -95,18 +130,18 @@ func validateRegions(regions []apisaws.RegionAMIMapping, version, name string, h if len(region.AMI) == 0 { allErrs = append(allErrs, field.Required(kdxPath.Child("ami"), "must provide an ami")) } - if !hasCloudProfileCapabilities { + if len(capabilityDefinitions) == 0 { if !slices.Contains(v1beta1constants.ValidArchitectures, arch) { allErrs = append(allErrs, field.NotSupported(kdxPath.Child("architecture"), arch, v1beta1constants.ValidArchitectures)) } } // This should be commented in once the defaulting of the architecture field is implemented via mutating webhook // currently there is no way to distinguish between a user set architecture and the default one - if hasCloudProfileCapabilities { + if len(capabilityDefinitions) > 0 { // If Capabilities are defined in the CloudProfile, the architecture gets defaulted to "ignore" during runtime if not set. architecture := ptr.Deref(region.Architecture, "ignore") if architecture != "ignore" { - allErrs = append(allErrs, field.Forbidden(kdxPath.Child("architecture"), "must be defined in .capabilities.architecture"+architecture)) + allErrs = append(allErrs, field.Forbidden(kdxPath.Child("architecture"), "must be defined in .capabilities.architecture")) } } } @@ -124,7 +159,7 @@ func NewProviderImagesContext(providerImages []apisaws.MachineImages) *gutil.Ima } // validateMachineImageMapping validates that for each machine image there is a corresponding cpConfig image. -func validateMachineImageMapping(machineImages []core.MachineImage, cpConfig *apisaws.CloudProfileConfig, capabilitiesDefinitions []gardencorev1beta1.CapabilityDefinition, fldPath *field.Path) field.ErrorList { +func validateMachineImageMapping(machineImages []core.MachineImage, cpConfig *apisaws.CloudProfileConfig, capabilityDefinitions []gardencorev1beta1.CapabilityDefinition, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} providerImages := NewProviderImagesContext(cpConfig.MachineImages) @@ -136,46 +171,35 @@ func validateMachineImageMapping(machineImages []core.MachineImage, cpConfig *ap machineImagePath := fldPath.Index(idxImage) // validate that for each machine image there is a corresponding cpConfig image if _, existsInConfig := providerImages.GetImage(machineImage.Name); !existsInConfig { - allErrs = append(allErrs, field.Required(machineImagePath, - fmt.Sprintf("must provide an image mapping for image %q in providerConfig", machineImage.Name))) + allErrs = append(allErrs, field.Required(machineImagePath, fmt.Sprintf("must provide an image mapping for image %q in providerConfig", machineImage.Name))) continue } - // validate that for each machine image version entry a mapped entry in cpConfig exists - for idxVersion, version := range machineImage.Versions { - machineImageVersionPath := machineImagePath.Child("versions").Index(idxVersion) - if len(capabilitiesDefinitions) > 0 { - // check that each CapabilitySet in version.CapabilitySets has a corresponding imageVersion.CapabilitySets - imageVersion, exists := providerImages.GetImageVersion(machineImage.Name, version.Version) - if !exists { - allErrs = append(allErrs, field.Required(machineImageVersionPath, - fmt.Sprintf("machine image version %s@%s is not defined in the providerConfig", - machineImage.Name, version.Version), - )) - continue - } - var v1beta1Version gardencorev1beta1.MachineImageVersion - if err := gardencoreapi.Scheme.Convert(&version, &v1beta1Version, nil); err != nil { - return append(allErrs, field.InternalError(machineImageVersionPath, err)) - } - defaultedCapabilitySets := gardencorev1beta1helper.GetCapabilitySetsWithAppliedDefaults(v1beta1Version.CapabilitySets, capabilitiesDefinitions) - for idxCapability, defaultedCapabilitySet := range defaultedCapabilitySets { - isFound := false - // search for the corresponding imageVersion.CapabilitySet - for _, providerCapabilitySet := range imageVersion.CapabilitySets { - providerDefaultedCapabilities := gardencorev1beta1helper.GetCapabilitiesWithAppliedDefaults(providerCapabilitySet.Capabilities, capabilitiesDefinitions) - if helper.AreCapabilitiesEqual(defaultedCapabilitySet.Capabilities, providerDefaultedCapabilities) { - isFound = true - } - } - if !isFound { - allErrs = append(allErrs, field.Required(machineImageVersionPath.Child("capabilitySets").Index(idxCapability), - fmt.Sprintf("missing providerConfig mapping for machine image version %s@%s and capabilitySet %v", machineImage.Name, version.Version, defaultedCapabilitySet.Capabilities))) - } - } + allErrs = append(allErrs, validateMachineImageVersionMapping(machineImage, machineImagePath, capabilityDefinitions, providerImages)...) + } + + return allErrs +} + +func validateMachineImageVersionMapping(machineImage core.MachineImage, machineImagePath *field.Path, capabilityDefinitions []gardencorev1beta1.CapabilityDefinition, providerImages *gutil.ImagesContext[apisaws.MachineImages, apisaws.MachineImageVersion]) field.ErrorList { + allErrs := field.ErrorList{} + + // validate that for each machine image version entry a mapped entry in cpConfig exists + for idxVersion, version := range machineImage.Versions { + machineImageVersionPath := machineImagePath.Child("versions").Index(idxVersion) + + if len(capabilityDefinitions) > 0 { + // check that each MachineImageFlavor in version.CapabilityFlavors has a corresponding imageVersion.CapabilityFlavors + imageVersion, exists := providerImages.GetImageVersion(machineImage.Name, version.Version) + if !exists { + allErrs = append(allErrs, field.Required(machineImageVersionPath, + fmt.Sprintf("machine image version %s@%s is not defined in the providerConfig", + machineImage.Name, version.Version), + )) continue } - + allErrs = append(allErrs, validateImageFlavorMapping(machineImage, version, machineImageVersionPath, capabilityDefinitions, imageVersion)...) + } else { for _, expectedArchitecture := range version.Architectures { // validate that machine image version exists in cpConfig imageVersion, exists := providerImages.GetImageVersion(machineImage.Name, version.Version) @@ -203,6 +227,33 @@ func validateMachineImageMapping(machineImages []core.MachineImage, cpConfig *ap } } } + return allErrs +} + +// validateImageFlavorMapping validates that each flavor in a version has a corresponding mapping +func validateImageFlavorMapping(machineImage core.MachineImage, version core.MachineImageVersion, machineImageVersionPath *field.Path, capabilityDefinitions []gardencorev1beta1.CapabilityDefinition, imageVersion apisaws.MachineImageVersion) field.ErrorList { + allErrs := field.ErrorList{} + + var v1beta1Version gardencorev1beta1.MachineImageVersion + if err := gardencoreapi.Scheme.Convert(&version, &v1beta1Version, nil); err != nil { + return append(allErrs, field.InternalError(machineImageVersionPath, err)) + } + defaultedCapabilityFlavors := gardencorev1beta1helper.GetImageFlavorsWithAppliedDefaults(v1beta1Version.CapabilityFlavors, capabilityDefinitions) + for idxCapability, defaultedCapabilitySet := range defaultedCapabilityFlavors { + isFound := false + // search for the corresponding imageVersion.MachineImageFlavor + for _, providerCapabilitySet := range imageVersion.CapabilityFlavors { + providerDefaultedCapabilities := gardencorev1beta1helper.GetCapabilitiesWithAppliedDefaults(providerCapabilitySet.Capabilities, capabilityDefinitions) + if gardencorev1beta1helper.AreCapabilitiesEqual(defaultedCapabilitySet.Capabilities, providerDefaultedCapabilities) { + isFound = true + break + } + } + if !isFound { + allErrs = append(allErrs, field.Required(machineImageVersionPath.Child("capabilityFlavors").Index(idxCapability), + fmt.Sprintf("missing providerConfig mapping for machine image version %s@%s and capabilitySet %v", machineImage.Name, version.Version, defaultedCapabilitySet.Capabilities))) + } + } return allErrs } diff --git a/pkg/apis/aws/validation/cloudprofile_test.go b/pkg/apis/aws/validation/cloudprofile_test.go index b68fa34ea..ff3a45f3a 100644 --- a/pkg/apis/aws/validation/cloudprofile_test.go +++ b/pkg/apis/aws/validation/cloudprofile_test.go @@ -22,12 +22,12 @@ import ( var _ = Describe("CloudProfileConfig validation", func() { DescribeTableSubtree("#ValidateCloudProfileConfig", func(isCapabilitiesCloudProfile bool) { var ( - capabilitiesDefinitions []v1beta1.CapabilityDefinition - cloudProfileConfig *apisaws.CloudProfileConfig - machineImages []core.MachineImage - machineImageName string - machineImageVersion string - fldPath *field.Path + capabilityDefinitions []v1beta1.CapabilityDefinition + cloudProfileConfig *apisaws.CloudProfileConfig + machineImages []core.MachineImage + machineImageName string + machineImageVersion string + fldPath *field.Path ) BeforeEach(func() { @@ -35,21 +35,21 @@ var _ = Describe("CloudProfileConfig validation", func() { Name: "eu", AMI: "ami-1234", }} - var capabilitySets []apisaws.CapabilitySet + var capabilityFlavors []apisaws.MachineImageFlavor if isCapabilitiesCloudProfile { - capabilitiesDefinitions = []v1beta1.CapabilityDefinition{{ + capabilityDefinitions = []v1beta1.CapabilityDefinition{{ Name: v1beta1constants.ArchitectureName, - Values: []string{v1beta1constants.ArchitectureAMD64}, + Values: []string{"amd64"}, }} - capabilitySets = []apisaws.CapabilitySet{{ + capabilityFlavors = []apisaws.MachineImageFlavor{{ Regions: regions, Capabilities: v1beta1.Capabilities{ - v1beta1constants.ArchitectureName: []string{v1beta1constants.ArchitectureAMD64}, + v1beta1constants.ArchitectureName: []string{"amd64"}, }}} regions = nil } else { - regions[0].Architecture = ptr.To(v1beta1constants.ArchitectureAMD64) + regions[0].Architecture = ptr.To("amd64") } machineImageName = "ubuntu" @@ -60,9 +60,9 @@ var _ = Describe("CloudProfileConfig validation", func() { Name: machineImageName, Versions: []apisaws.MachineImageVersion{ { - Version: machineImageVersion, - Regions: regions, - CapabilitySets: capabilitySets, + Version: machineImageVersion, + Regions: regions, + CapabilityFlavors: capabilityFlavors, }, }, }, @@ -74,7 +74,7 @@ var _ = Describe("CloudProfileConfig validation", func() { Versions: []core.MachineImageVersion{ { ExpirableVersion: core.ExpirableVersion{Version: machineImageVersion}, - Architectures: []string{v1beta1constants.ArchitectureAMD64}, + Architectures: []string{"amd64"}, }, }, }, @@ -83,14 +83,14 @@ var _ = Describe("CloudProfileConfig validation", func() { Context("machine image validation", func() { It("should pass validation with valid config", func() { - errorList := ValidateCloudProfileConfig(cloudProfileConfig, machineImages, capabilitiesDefinitions, fldPath) + errorList := ValidateCloudProfileConfig(cloudProfileConfig, machineImages, capabilityDefinitions, fldPath) Expect(errorList).To(BeEmpty()) }) It("should enforce that at least one machine image has been defined", func() { cloudProfileConfig.MachineImages = []apisaws.MachineImages{} - errorList := ValidateCloudProfileConfig(cloudProfileConfig, machineImages, capabilitiesDefinitions, fldPath) + errorList := ValidateCloudProfileConfig(cloudProfileConfig, machineImages, capabilityDefinitions, fldPath) Expect(errorList).To(ConsistOf(PointTo(MatchFields(IgnoreExtras, Fields{ "Type": Equal(field.ErrorTypeRequired), @@ -101,10 +101,42 @@ var _ = Describe("CloudProfileConfig validation", func() { })))) }) + It("should forbid images with empty regions", func() { + var fieldMatcher string + if isCapabilitiesCloudProfile { + fieldMatcher = "machineImages[0].versions[0].capabilityFlavors[0].regions" + cloudProfileConfig.MachineImages[0].Versions[0].CapabilityFlavors[0].Regions = []apisaws.RegionAMIMapping{} + } else { + fieldMatcher = "machineImages[0].versions[0].regions" + cloudProfileConfig.MachineImages[0].Versions[0].Regions = []apisaws.RegionAMIMapping{} + } + + errorList := ValidateCloudProfileConfig(cloudProfileConfig, machineImages, capabilityDefinitions, fldPath) + if isCapabilitiesCloudProfile { + Expect(errorList).To(ConsistOf(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeRequired), + "Detail": Equal("must provide at least one region for machine image \"ubuntu\" and version \"1.2.3\""), + "Field": Equal(fieldMatcher), + })))) + } else { + Expect(errorList).To(ConsistOf(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeRequired), + "Detail": Equal("missing providerConfig mapping for machine image version ubuntu@1.2.3 and architecture: amd64"), + "Field": Equal("spec.machineImages[0].versions[0]"), + })), + PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeRequired), + "Detail": Equal("must provide at least one region for machine image \"ubuntu\" and version \"1.2.3\""), + "Field": Equal(fieldMatcher), + })), + )) + } + }) + It("should forbid unsupported machine image configuration", func() { cloudProfileConfig.MachineImages = []apisaws.MachineImages{{}} - errorList := ValidateCloudProfileConfig(cloudProfileConfig, machineImages, capabilitiesDefinitions, fldPath) + errorList := ValidateCloudProfileConfig(cloudProfileConfig, machineImages, capabilityDefinitions, fldPath) Expect(errorList).To(ConsistOf(PointTo(MatchFields(IgnoreExtras, Fields{ "Type": Equal(field.ErrorTypeRequired), @@ -128,13 +160,13 @@ var _ = Describe("CloudProfileConfig validation", func() { }, } if isCapabilitiesCloudProfile { - matcher = Equal("machineImages[0].versions[0].capabilitySets[0].regions") - cloudProfileConfig.MachineImages[0].Versions[0].CapabilitySets = []apisaws.CapabilitySet{{}} + matcher = Equal("machineImages[0].versions[0].capabilityFlavors[0].regions") + cloudProfileConfig.MachineImages[0].Versions[0].CapabilityFlavors = []apisaws.MachineImageFlavor{{}} } else { matcher = Equal("machineImages[0].versions[0].regions") } - errorList := ValidateCloudProfileConfig(cloudProfileConfig, machineImages, capabilitiesDefinitions, fldPath) + errorList := ValidateCloudProfileConfig(cloudProfileConfig, machineImages, capabilityDefinitions, fldPath) Expect(errorList).To(ConsistOf(PointTo(MatchFields(IgnoreExtras, Fields{ "Type": Equal(field.ErrorTypeRequired), @@ -153,13 +185,13 @@ var _ = Describe("CloudProfileConfig validation", func() { var machineImageVersion apisaws.MachineImageVersion var nameMatcher, amiMatcher types.GomegaMatcher if isCapabilitiesCloudProfile { - nameMatcher = Equal("machineImages[0].versions[0].capabilitySets[0].regions[0].name") - amiMatcher = Equal("machineImages[0].versions[0].capabilitySets[0].regions[0].ami") + nameMatcher = Equal("machineImages[0].versions[0].capabilityFlavors[0].regions[0].name") + amiMatcher = Equal("machineImages[0].versions[0].capabilityFlavors[0].regions[0].ami") machineImageVersion = apisaws.MachineImageVersion{ Version: "1.2.3", - CapabilitySets: []apisaws.CapabilitySet{{ + CapabilityFlavors: []apisaws.MachineImageFlavor{{ Regions: []apisaws.RegionAMIMapping{{}}, - Capabilities: v1beta1.Capabilities{v1beta1constants.ArchitectureName: {v1beta1constants.ArchitectureAMD64}}, + Capabilities: v1beta1.Capabilities{v1beta1constants.ArchitectureName: {"amd64"}}, }}, } } else { @@ -177,7 +209,7 @@ var _ = Describe("CloudProfileConfig validation", func() { }, } - errorList := ValidateCloudProfileConfig(cloudProfileConfig, machineImages, capabilitiesDefinitions, fldPath) + errorList := ValidateCloudProfileConfig(cloudProfileConfig, machineImages, capabilityDefinitions, fldPath) Expect(errorList).To(ConsistOf(PointTo(MatchFields(IgnoreExtras, Fields{ "Type": Equal(field.ErrorTypeRequired), @@ -196,9 +228,9 @@ var _ = Describe("CloudProfileConfig validation", func() { It("should forbid unsupported machine image architecture configuration", func() { var notSupportedField, requiredField types.GomegaMatcher if isCapabilitiesCloudProfile { - cloudProfileConfig.MachineImages[0].Versions[0].CapabilitySets[0].Capabilities[v1beta1constants.ArchitectureName] = []string{"foo"} - notSupportedField = Equal("machineImages[0].versions[0].capabilitySets[0].capabilities.architecture[0]") - requiredField = Equal("spec.machineImages[0].versions[0].capabilitySets[0]") + cloudProfileConfig.MachineImages[0].Versions[0].CapabilityFlavors[0].Capabilities[v1beta1constants.ArchitectureName] = []string{"foo"} + notSupportedField = Equal("machineImages[0].versions[0].capabilityFlavors[0].capabilities.architecture[0]") + requiredField = Equal("spec.machineImages[0].versions[0].capabilityFlavors[0]") } else { cloudProfileConfig.MachineImages[0].Versions[0].Regions[0].Architecture = ptr.To("foo") notSupportedField = Equal("machineImages[0].versions[0].regions[0].architecture") @@ -206,7 +238,7 @@ var _ = Describe("CloudProfileConfig validation", func() { } - errorList := ValidateCloudProfileConfig(cloudProfileConfig, machineImages, capabilitiesDefinitions, fldPath) + errorList := ValidateCloudProfileConfig(cloudProfileConfig, machineImages, capabilityDefinitions, fldPath) Expect(errorList).To(ConsistOf(PointTo(MatchFields(IgnoreExtras, Fields{ "Type": Equal(field.ErrorTypeNotSupported), @@ -221,45 +253,45 @@ var _ = Describe("CloudProfileConfig validation", func() { It("should forbid missing architecture or capabilitySet mapping", func() { var fieldMatcher types.GomegaMatcher if isCapabilitiesCloudProfile { - machineImages[0].Versions[0].CapabilitySets = []core.CapabilitySet{ - {Capabilities: core.Capabilities{v1beta1constants.ArchitectureName: []string{v1beta1constants.ArchitectureARM64}}}, + machineImages[0].Versions[0].CapabilityFlavors = []core.MachineImageFlavor{ + {Capabilities: core.Capabilities{v1beta1constants.ArchitectureName: []string{"arm64"}}}, } - fieldMatcher = Equal("spec.machineImages[0].versions[0].capabilitySets[0]") + fieldMatcher = Equal("spec.machineImages[0].versions[0].capabilityFlavors[0]") } else { - machineImages[0].Versions[0].Architectures = []string{v1beta1constants.ArchitectureARM64} + machineImages[0].Versions[0].Architectures = []string{"arm64"} fieldMatcher = Equal("spec.machineImages[0].versions[0]") } - errorList := ValidateCloudProfileConfig(cloudProfileConfig, machineImages, capabilitiesDefinitions, fldPath) + errorList := ValidateCloudProfileConfig(cloudProfileConfig, machineImages, capabilityDefinitions, fldPath) Expect(errorList).To(ConsistOf( PointTo(MatchFields(IgnoreExtras, Fields{"Type": Equal(field.ErrorTypeRequired), "Field": fieldMatcher})), )) }) - It("should automatically use amd64 (or default to capabilitiesDefinition)", func() { + It("should automatically use amd64 (or default to capabilityDefinitions)", func() { if !isCapabilitiesCloudProfile { cloudProfileConfig.MachineImages[0].Versions[0].Regions[0].Architecture = nil } - errorList := ValidateCloudProfileConfig(cloudProfileConfig, machineImages, capabilitiesDefinitions, fldPath) + errorList := ValidateCloudProfileConfig(cloudProfileConfig, machineImages, capabilityDefinitions, fldPath) Expect(errorList).To(BeEmpty()) }) - It("should reject when machineImage.regions and machineImage.capabilitySets is set", func() { + It("should reject when machineImage.regions and machineImage.capabilityFlavors is set", func() { var fieldMatcher types.GomegaMatcher if isCapabilitiesCloudProfile { fieldMatcher = Equal("machineImages[0].versions[0].regions") } else { - fieldMatcher = Equal("machineImages[0].versions[0].capabilitySets") + fieldMatcher = Equal("machineImages[0].versions[0].capabilityFlavors") } cloudProfileConfig.MachineImages[0].Versions[0].Regions = append(cloudProfileConfig.MachineImages[0].Versions[0].Regions, apisaws.RegionAMIMapping{ Name: "eu", AMI: "ami-1234", - Architecture: ptr.To(v1beta1constants.ArchitectureAMD64), + Architecture: ptr.To("amd64"), }) - cloudProfileConfig.MachineImages[0].Versions[0].CapabilitySets = append(cloudProfileConfig.MachineImages[0].Versions[0].CapabilitySets, apisaws.CapabilitySet{ + cloudProfileConfig.MachineImages[0].Versions[0].CapabilityFlavors = append(cloudProfileConfig.MachineImages[0].Versions[0].CapabilityFlavors, apisaws.MachineImageFlavor{ Regions: []apisaws.RegionAMIMapping{{Name: "eu", AMI: "ami-1234"}}, }) - errorList := ValidateCloudProfileConfig(cloudProfileConfig, machineImages, capabilitiesDefinitions, fldPath) + errorList := ValidateCloudProfileConfig(cloudProfileConfig, machineImages, capabilityDefinitions, fldPath) Expect(errorList).To(ConsistOf(PointTo(MatchFields(IgnoreExtras, Fields{ "Type": Equal(field.ErrorTypeForbidden), "Field": fieldMatcher, diff --git a/pkg/apis/aws/validation/worker.go b/pkg/apis/aws/validation/worker.go index f5c997b48..b6682cd25 100644 --- a/pkg/apis/aws/validation/worker.go +++ b/pkg/apis/aws/validation/worker.go @@ -107,7 +107,7 @@ func ValidateWorkersAgainstCloudProfileOnCreation( region string, awsCloudProfile *apisaws.CloudProfileConfig, machineTypes []v1beta1.MachineType, - capabilitiesDefinitions []v1beta1.CapabilityDefinition, + capabilityDefinitions []v1beta1.CapabilityDefinition, fldPath *field.Path, ) field.ErrorList { allErrs := field.ErrorList{} @@ -119,7 +119,7 @@ func ValidateWorkersAgainstCloudProfileOnCreation( continue } - allErrs = append(allErrs, validateWorkerConfigAgainstCloudProfile(w, region, awsCloudProfile, machineType.Capabilities, capabilitiesDefinitions, fldPath.Index(i))...) + allErrs = append(allErrs, validateWorkerConfigAgainstCloudProfile(w, region, awsCloudProfile, machineType.Capabilities, capabilityDefinitions, fldPath.Index(i))...) } return allErrs } @@ -130,7 +130,7 @@ func ValidateWorkersAgainstCloudProfileOnUpdate( region string, awsCloudProfile *apisaws.CloudProfileConfig, machineTypes []v1beta1.MachineType, - capabilitiesDefinitions []v1beta1.CapabilityDefinition, + capabilityDefinitions []v1beta1.CapabilityDefinition, fldPath *field.Path, ) field.ErrorList { allErrs := field.ErrorList{} @@ -153,7 +153,7 @@ func ValidateWorkersAgainstCloudProfileOnUpdate( continue } - allErrs = append(allErrs, validateWorkerConfigAgainstCloudProfile(newWorker, region, awsCloudProfile, machineType.Capabilities, capabilitiesDefinitions, fldPath.Index(i))...) + allErrs = append(allErrs, validateWorkerConfigAgainstCloudProfile(newWorker, region, awsCloudProfile, machineType.Capabilities, capabilityDefinitions, fldPath.Index(i))...) } } @@ -165,7 +165,7 @@ func validateWorkerConfigAgainstCloudProfile( region string, awsCloudProfile *apisaws.CloudProfileConfig, machineCapabilities v1beta1.Capabilities, - capabilitiesDefinitions []v1beta1.CapabilityDefinition, + capabilityDefinitions []v1beta1.CapabilityDefinition, fldPath *field.Path, ) field.ErrorList { var ( @@ -178,7 +178,7 @@ func validateWorkerConfigAgainstCloudProfile( return allErrs } - if _, err := apisawshelper.FindImageInCloudProfile(awsCloudProfile, image.Name, image.Version, region, architecture, machineCapabilities, capabilitiesDefinitions); err != nil { + if _, err := apisawshelper.FindImageInCloudProfile(awsCloudProfile, image.Name, image.Version, region, architecture, machineCapabilities, capabilityDefinitions); err != nil { allErrs = append(allErrs, field.Invalid(fldPath.Child("machine", "image"), image, fmt.Sprint(err))) } return allErrs diff --git a/pkg/apis/aws/zz_generated.deepcopy.go b/pkg/apis/aws/zz_generated.deepcopy.go index 9001dfe61..656a43748 100644 --- a/pkg/apis/aws/zz_generated.deepcopy.go +++ b/pkg/apis/aws/zz_generated.deepcopy.go @@ -45,44 +45,6 @@ func (in *BackupBucketConfig) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CapabilitySet) DeepCopyInto(out *CapabilitySet) { - *out = *in - if in.Regions != nil { - in, out := &in.Regions, &out.Regions - *out = make([]RegionAMIMapping, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Capabilities != nil { - in, out := &in.Capabilities, &out.Capabilities - *out = make(v1beta1.Capabilities, len(*in)) - for key, val := range *in { - var outVal []string - if val == nil { - (*out)[key] = nil - } else { - in, out := &val, &outVal - *out = make(v1beta1.CapabilityValues, len(*in)) - copy(*out, *in) - } - (*out)[key] = outVal - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CapabilitySet. -func (in *CapabilitySet) DeepCopy() *CapabilitySet { - if in == nil { - return nil - } - out := new(CapabilitySet) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CloudControllerManagerConfig) DeepCopyInto(out *CloudControllerManagerConfig) { *out = *in @@ -622,6 +584,44 @@ func (in *MachineImage) DeepCopy() *MachineImage { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MachineImageFlavor) DeepCopyInto(out *MachineImageFlavor) { + *out = *in + if in.Regions != nil { + in, out := &in.Regions, &out.Regions + *out = make([]RegionAMIMapping, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Capabilities != nil { + in, out := &in.Capabilities, &out.Capabilities + *out = make(v1beta1.Capabilities, len(*in)) + for key, val := range *in { + var outVal []string + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = make(v1beta1.CapabilityValues, len(*in)) + copy(*out, *in) + } + (*out)[key] = outVal + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineImageFlavor. +func (in *MachineImageFlavor) DeepCopy() *MachineImageFlavor { + if in == nil { + return nil + } + out := new(MachineImageFlavor) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MachineImageVersion) DeepCopyInto(out *MachineImageVersion) { *out = *in @@ -632,9 +632,9 @@ func (in *MachineImageVersion) DeepCopyInto(out *MachineImageVersion) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.CapabilitySets != nil { - in, out := &in.CapabilitySets, &out.CapabilitySets - *out = make([]CapabilitySet, len(*in)) + if in.CapabilityFlavors != nil { + in, out := &in.CapabilityFlavors, &out.CapabilityFlavors + *out = make([]MachineImageFlavor, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } diff --git a/pkg/controller/bastion/options.go b/pkg/controller/bastion/options.go index a1c9ff1c6..7d93026da 100644 --- a/pkg/controller/bastion/options.go +++ b/pkg/controller/bastion/options.go @@ -17,7 +17,6 @@ import ( gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1" extensionsv1alpha1 "github.com/gardener/gardener/pkg/apis/extensions/v1alpha1" - api "github.com/gardener/gardener-extension-provider-aws/pkg/apis/aws" "github.com/gardener/gardener-extension-provider-aws/pkg/apis/aws/helper" awsclient "github.com/gardener/gardener-extension-provider-aws/pkg/aws/client" ) @@ -99,15 +98,13 @@ func NewOpts(ctx context.Context, bastion *extensionsv1alpha1.Bastion, cluster * return Options{}, fmt.Errorf("failed to extract cloud provider config from cluster: %w", err) } - machineImageVersion, err := getProviderSpecificImage(cloudProfileConfig.MachineImages, vmDetails) + var ami string + imageFlavor, err := helper.FindImageInCloudProfile(cloudProfileConfig, vmDetails.ImageBaseName, vmDetails.ImageVersion, region, &vmDetails.Architecture, vmDetails.MachineTypeCapabilities, cluster.CloudProfile.Spec.MachineCapabilities) if err != nil { - return Options{}, fmt.Errorf("failed to extract image from provider config: %w", err) - } - - ami, err := findImageAMIByRegion(machineImageVersion, vmDetails, region) - if err != nil { - return Options{}, fmt.Errorf("failed to find image AMI by region: %w", err) + return Options{}, fmt.Errorf("failed to find machine image in CloudProfileConfig: %w", err) } + // We can safely assume that the AMI exists, because FindImageInCloudProfile would have errored otherwise. + ami = imageFlavor.Regions[0].AMI ipV6 := cluster.Shoot.Spec.Networking != nil && slices.Contains(cluster.Shoot.Spec.Networking.IPFamilies, gardencorev1beta1.IPFamilyIPv6) @@ -145,41 +142,3 @@ func resolveSubnetName(ctx context.Context, awsClient *awsclient.Client, subnetN return } - -// getProviderSpecificImage returns the provider specific MachineImageVersion that matches with the given VmDetails -func getProviderSpecificImage(images []api.MachineImages, vm extensionsbastion.MachineSpec) (api.MachineImageVersion, error) { - imageIndex := slices.IndexFunc(images, func(image api.MachineImages) bool { - return image.Name == vm.ImageBaseName - }) - - if imageIndex == -1 { - return api.MachineImageVersion{}, - fmt.Errorf("machine image with name %s not found in cloudProfileConfig", vm.ImageBaseName) - } - - versions := images[imageIndex].Versions - versionIndex := slices.IndexFunc(versions, func(version api.MachineImageVersion) bool { - return version.Version == vm.ImageVersion - }) - - if versionIndex == -1 { - return api.MachineImageVersion{}, - fmt.Errorf("version %s for arch %s of image %s not found in cloudProfileConfig", - vm.ImageVersion, vm.Architecture, vm.ImageBaseName) - } - - return versions[versionIndex], nil -} - -func findImageAMIByRegion(image api.MachineImageVersion, vmDetails extensionsbastion.MachineSpec, region string) (string, error) { - regionIndex := slices.IndexFunc(image.Regions, func(RegionAMIMapping api.RegionAMIMapping) bool { - return RegionAMIMapping.Name == region && RegionAMIMapping.Architecture != nil && *RegionAMIMapping.Architecture == vmDetails.Architecture - }) - - if regionIndex == -1 { - return "", fmt.Errorf("image '%s' with version '%s' and architecture '%s' not found in region '%s'", - vmDetails.ImageBaseName, image.Version, vmDetails.Architecture, region) - } - - return image.Regions[regionIndex].AMI, nil -} diff --git a/pkg/controller/bastion/options_test.go b/pkg/controller/bastion/options_test.go deleted file mode 100644 index 0eceb8fae..000000000 --- a/pkg/controller/bastion/options_test.go +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors -// -// SPDX-License-Identifier: Apache-2.0 - -package bastion - -import ( - extensionsbastion "github.com/gardener/gardener/extensions/pkg/bastion" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "k8s.io/utils/ptr" - - api "github.com/gardener/gardener-extension-provider-aws/pkg/apis/aws" -) - -var _ = Describe("Bastion Options", func() { - var region = "test-region" - var image = "gardenlinux" - var version = "1.0.0" - var ami = "test-ami" - var machineName = "test-machine" - var architecture = "amd64" - var amiMapping []api.RegionAMIMapping - var machineImageVersion api.MachineImageVersion - var machineImages []api.MachineImages - var vmDetails extensionsbastion.MachineSpec - - BeforeEach(func() { - amiMapping = []api.RegionAMIMapping{ - { - Name: region, - AMI: ami, - Architecture: ptr.To(architecture), - }, - } - machineImageVersion = api.MachineImageVersion{ - Version: version, - Regions: amiMapping, - } - machineImages = []api.MachineImages{ - { - Name: image, - Versions: []api.MachineImageVersion{machineImageVersion}, - }, - } - vmDetails = extensionsbastion.MachineSpec{ - MachineTypeName: machineName, - Architecture: architecture, - ImageBaseName: image, - ImageVersion: version, - } - }) - - Context("getProviderSpecificImage", func() { - It("should succeed for existing image and version", func() { - machineImageVersion, err := getProviderSpecificImage(machineImages, vmDetails) - Expect(err).NotTo(HaveOccurred()) - Expect(machineImageVersion).To(Equal(machineImageVersion)) - }) - - It("fail if image name does not exist", func() { - vmDetails.ImageBaseName = "unknown" - _, err := getProviderSpecificImage(machineImages, vmDetails) - Expect(err).To(HaveOccurred()) - }) - - It("fail if image version does not exist", func() { - vmDetails.ImageVersion = "6.6.6" - _, err := getProviderSpecificImage(machineImages, vmDetails) - Expect(err).To(HaveOccurred()) - }) - }) - - Context("findImageAMIByRegion", func() { - It("should find image AMI by region", func() { - imageAmi, err := findImageAMIByRegion(machineImageVersion, vmDetails, region) - Expect(err).NotTo(HaveOccurred()) - Expect(imageAmi).To(Equal(ami)) - }) - - It("fail if region does not match", func() { - _, err := findImageAMIByRegion(machineImageVersion, vmDetails, "unknown") - Expect(err).To(HaveOccurred()) - }) - - It("fail if architecture does not match", func() { - vmDetails.Architecture = "x86" - _, err := findImageAMIByRegion(machineImageVersion, vmDetails, region) - Expect(err).To(HaveOccurred()) - }) - }) -}) diff --git a/pkg/controller/worker/machine_images.go b/pkg/controller/worker/machine_images.go index 4787335aa..f6ad55898 100644 --- a/pkg/controller/worker/machine_images.go +++ b/pkg/controller/worker/machine_images.go @@ -45,7 +45,7 @@ func (w *WorkerDelegate) selectMachineImageForWorkerPool(name, version string, r Version: version, } - if capabilitySet, err := helper.FindImageInCloudProfile(w.cloudProfileConfig, name, version, region, arch, machineCapabilities, w.cluster.CloudProfile.Spec.Capabilities); err == nil { + if capabilitySet, err := helper.FindImageInCloudProfile(w.cloudProfileConfig, name, version, region, arch, machineCapabilities, w.cluster.CloudProfile.Spec.MachineCapabilities); err == nil { selectedMachineImage.Capabilities = capabilitySet.Capabilities selectedMachineImage.AMI = capabilitySet.Regions[0].AMI selectedMachineImage.Architecture = capabilitySet.Regions[0].Architecture @@ -59,15 +59,15 @@ func (w *WorkerDelegate) selectMachineImageForWorkerPool(name, version string, r return nil, fmt.Errorf("could not decode worker status of worker '%s': %w", k8sclient.ObjectKeyFromObject(w.worker), err) } - return helper.FindImageInWorkerStatus(workerStatus.MachineImages, name, version, arch, machineCapabilities, w.cluster.CloudProfile.Spec.Capabilities) + return helper.FindImageInWorkerStatus(workerStatus.MachineImages, name, version, arch, machineCapabilities, w.cluster.CloudProfile.Spec.MachineCapabilities) } return nil, worker.ErrorMachineImageNotFound(name, version, *arch, region) } -func appendMachineImage(machineImages []api.MachineImage, machineImage api.MachineImage, capabilitiesDefinitions []gardencorev1beta1.CapabilityDefinition) []api.MachineImage { +func appendMachineImage(machineImages []api.MachineImage, machineImage api.MachineImage, capabilityDefinitions []gardencorev1beta1.CapabilityDefinition) []api.MachineImage { // support for cloudprofile machine images without capabilities - if len(capabilitiesDefinitions) == 0 { + if len(capabilityDefinitions) == 0 { for _, image := range machineImages { if image.Name == machineImage.Name && image.Version == machineImage.Version && machineImage.Architecture == image.Architecture { // If the image already exists without capabilities, we can just return the existing list. @@ -82,11 +82,11 @@ func appendMachineImage(machineImages []api.MachineImage, machineImage api.Machi }) } - defaultedCapabilities := gardencorev1beta1helper.GetCapabilitiesWithAppliedDefaults(machineImage.Capabilities, capabilitiesDefinitions) + defaultedCapabilities := gardencorev1beta1helper.GetCapabilitiesWithAppliedDefaults(machineImage.Capabilities, capabilityDefinitions) for _, existingMachineImage := range machineImages { - existingDefaultedCapabilities := gardencorev1beta1helper.GetCapabilitiesWithAppliedDefaults(existingMachineImage.Capabilities, capabilitiesDefinitions) - if existingMachineImage.Name == machineImage.Name && existingMachineImage.Version == machineImage.Version && helper.AreCapabilitiesEqual(defaultedCapabilities, existingDefaultedCapabilities) { + existingDefaultedCapabilities := gardencorev1beta1helper.GetCapabilitiesWithAppliedDefaults(existingMachineImage.Capabilities, capabilityDefinitions) + if existingMachineImage.Name == machineImage.Name && existingMachineImage.Version == machineImage.Version && gardencorev1beta1helper.AreCapabilitiesEqual(defaultedCapabilities, existingDefaultedCapabilities) { // If the image already exists with the same capabilities return the existing list. return machineImages } diff --git a/pkg/controller/worker/machines.go b/pkg/controller/worker/machines.go index 177e87b27..d3b3998ae 100644 --- a/pkg/controller/worker/machines.go +++ b/pkg/controller/worker/machines.go @@ -120,8 +120,8 @@ func (w *WorkerDelegate) generateMachineConfig(ctx context.Context) error { return err } - machineImages = EnsureUniformMachineImages(machineImages, w.cluster.CloudProfile.Spec.Capabilities) - machineImages = appendMachineImage(machineImages, *machineImage, w.cluster.CloudProfile.Spec.Capabilities) + machineImages = EnsureUniformMachineImages(machineImages, w.cluster.CloudProfile.Spec.MachineCapabilities) + machineImages = appendMachineImage(machineImages, *machineImage, w.cluster.CloudProfile.Spec.MachineCapabilities) blockDevices, err := w.computeBlockDevices(pool, workerConfig) if err != nil { @@ -533,7 +533,7 @@ func EnsureUniformMachineImages(images []awsapi.MachineImage, definitions []gard return uniformMachineImages } - // transform images that were added without Capabilities to contain a CapabilitySet with defaulted Architecture + // transform images that were added without Capabilities to contain a MachineImageFlavor with defaulted Architecture for _, img := range images { if len(img.Capabilities) > 0 { // image is already in the new format with Capabilities diff --git a/pkg/controller/worker/machines_test.go b/pkg/controller/worker/machines_test.go index 644791705..9f65dfb5b 100644 --- a/pkg/controller/worker/machines_test.go +++ b/pkg/controller/worker/machines_test.go @@ -119,7 +119,7 @@ var _ = Describe("Machines", func() { namePool2 string minPool2 int32 maxPool2 int32 - priorityPool2 int32 + priorityPool2 *int32 maxSurgePool2 intstr.IntOrString maxUnavailablePool2 intstr.IntOrString @@ -162,13 +162,13 @@ var _ = Describe("Machines", func() { if isCapabilitiesCloudProfile { capabilityDefinitions = []gardencorev1beta1.CapabilityDefinition{ {Name: "some-capability", Values: []string{"a", "b", "c"}}, - {Name: v1beta1constants.ArchitectureName, Values: []string{v1beta1constants.ArchitectureAMD64, v1beta1constants.ArchitectureARM64}}, + {Name: v1beta1constants.ArchitectureName, Values: []string{"amd64", "arm64"}}, } capabilitiesAmd = gardencorev1beta1.Capabilities{ - v1beta1constants.ArchitectureName: []string{v1beta1constants.ArchitectureAMD64}, + v1beta1constants.ArchitectureName: []string{"amd64"}, } capabilitiesArm = gardencorev1beta1.Capabilities{ - v1beta1constants.ArchitectureName: []string{v1beta1constants.ArchitectureARM64}, + v1beta1constants.ArchitectureName: []string{"arm64"}, } } namespace = "shoot--foobar--aws" @@ -222,7 +222,7 @@ var _ = Describe("Machines", func() { namePool2 = "pool-2" minPool2 = 30 maxPool2 = 45 - priorityPool2 = 100 + priorityPool2 = ptr.To(int32(100)) maxSurgePool2 = intstr.FromInt(10) maxUnavailablePool2 = intstr.FromInt(15) @@ -306,7 +306,7 @@ var _ = Describe("Machines", func() { Versions: []apiv1alpha1.MachineImageVersion{ { Version: machineImageVersion, - CapabilitySets: []apiv1alpha1.CapabilitySet{ + CapabilityFlavors: []apiv1alpha1.MachineImageFlavor{ { Capabilities: capabilitiesAmd, Regions: []apiv1alpha1.RegionAMIMapping{ @@ -379,7 +379,7 @@ var _ = Describe("Machines", func() { Name: cloudProfileName, }, Spec: gardencorev1beta1.CloudProfileSpec{ - Capabilities: capabilityDefinitions, + MachineCapabilities: capabilityDefinitions, MachineTypes: []gardencorev1beta1.MachineType{ { Name: machineType, @@ -1494,7 +1494,7 @@ var _ = Describe("Machines", func() { }) }) - DescribeTable("EnsureUniformMachineImages", func(capabilitiesDefinitions []gardencorev1beta1.CapabilityDefinition, expectedImages []api.MachineImage) { + DescribeTable("EnsureUniformMachineImages", func(capabilityDefinitions []gardencorev1beta1.CapabilityDefinition, expectedImages []api.MachineImage) { machineImages := []api.MachineImage{ // images with capability sets { @@ -1502,7 +1502,7 @@ var _ = Describe("Machines", func() { Version: "1.2.1", AMI: "ami-for-arm64", Capabilities: gardencorev1beta1.Capabilities{ - v1beta1constants.ArchitectureName: []string{v1beta1constants.ArchitectureARM64}, + v1beta1constants.ArchitectureName: []string{"arm64"}, }, }, { @@ -1510,7 +1510,7 @@ var _ = Describe("Machines", func() { Version: "1.2.2", AMI: "ami-for-amd64", Capabilities: gardencorev1beta1.Capabilities{ - v1beta1constants.ArchitectureName: []string{v1beta1constants.ArchitectureAMD64}, + v1beta1constants.ArchitectureName: []string{"amd64"}, }, }, // legacy image entry without capability sets @@ -1518,22 +1518,22 @@ var _ = Describe("Machines", func() { Name: "some-image", Version: "1.2.3", AMI: "ami-for-amd64", - Architecture: ptr.To(v1beta1constants.ArchitectureAMD64), + Architecture: ptr.To("amd64"), }, { Name: "some-image", Version: "1.2.2", AMI: "ami-for-amd64", - Architecture: ptr.To(v1beta1constants.ArchitectureAMD64), + Architecture: ptr.To("amd64"), }, { Name: "some-image", Version: "1.2.1", AMI: "ami-for-amd64", - Architecture: ptr.To(v1beta1constants.ArchitectureAMD64), + Architecture: ptr.To("amd64"), }, } - actualImages := EnsureUniformMachineImages(machineImages, capabilitiesDefinitions) + actualImages := EnsureUniformMachineImages(machineImages, capabilityDefinitions) Expect(actualImages).To(ContainElements(expectedImages)) }, @@ -1543,31 +1543,31 @@ var _ = Describe("Machines", func() { Name: "some-image", Version: "1.2.1", AMI: "ami-for-arm64", - Architecture: ptr.To(v1beta1constants.ArchitectureARM64), + Architecture: ptr.To("arm64"), }, { Name: "some-image", Version: "1.2.2", AMI: "ami-for-amd64", - Architecture: ptr.To(v1beta1constants.ArchitectureAMD64), + Architecture: ptr.To("amd64"), }, // legacy image entry without capability sets { Name: "some-image", Version: "1.2.3", AMI: "ami-for-amd64", - Architecture: ptr.To(v1beta1constants.ArchitectureAMD64), + Architecture: ptr.To("amd64"), }, { Name: "some-image", Version: "1.2.1", AMI: "ami-for-amd64", - Architecture: ptr.To(v1beta1constants.ArchitectureAMD64), + Architecture: ptr.To("amd64"), }, }), Entry("should return images with Capabilities", []gardencorev1beta1.CapabilityDefinition{{ Name: v1beta1constants.ArchitectureName, - Values: []string{v1beta1constants.ArchitectureAMD64, v1beta1constants.ArchitectureARM64}, + Values: []string{"amd64", "arm64"}, }}, []api.MachineImage{ // images with capability sets { @@ -1575,7 +1575,7 @@ var _ = Describe("Machines", func() { Version: "1.2.1", AMI: "ami-for-arm64", Capabilities: gardencorev1beta1.Capabilities{ - v1beta1constants.ArchitectureName: []string{v1beta1constants.ArchitectureARM64}, + v1beta1constants.ArchitectureName: []string{"arm64"}, }, }, { @@ -1583,7 +1583,7 @@ var _ = Describe("Machines", func() { Version: "1.2.2", AMI: "ami-for-amd64", Capabilities: gardencorev1beta1.Capabilities{ - v1beta1constants.ArchitectureName: []string{v1beta1constants.ArchitectureAMD64}, + v1beta1constants.ArchitectureName: []string{"amd64"}, }, }, // legacy image entry without capability sets @@ -1592,14 +1592,14 @@ var _ = Describe("Machines", func() { Version: "1.2.3", AMI: "ami-for-amd64", Capabilities: gardencorev1beta1.Capabilities{ - v1beta1constants.ArchitectureName: []string{v1beta1constants.ArchitectureAMD64}, + v1beta1constants.ArchitectureName: []string{"amd64"}, }}, { Name: "some-image", Version: "1.2.1", AMI: "ami-for-amd64", Capabilities: gardencorev1beta1.Capabilities{ - v1beta1constants.ArchitectureName: []string{v1beta1constants.ArchitectureAMD64}, + v1beta1constants.ArchitectureName: []string{"amd64"}, }, }, }),