From 657481cc1b607911d54ee32bf349b170e6ac8f93 Mon Sep 17 00:00:00 2001 From: Christoph Mewes Date: Tue, 25 Feb 2025 09:27:17 +0100 Subject: [PATCH 01/42] [release/v2.27] prepare 2.27.1 (#7185) --- Makefile | 2 +- modules/api/Makefile | 2 +- modules/api/go.mod | 2 +- modules/api/go.sum | 4 ++-- modules/web/Makefile | 2 +- modules/web/package-lock.json | 4 ++-- modules/web/package.json | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index d6640c1659..2912e466ff 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ SHELL = /bin/bash -eu -o pipefail export KUBERMATIC_EDITION ?= ee -KUBERMATIC_VERSION?=v2.27.0 +KUBERMATIC_VERSION?=v2.27.1 DOCKER_REPO ?= quay.io/kubermatic REPO = $(DOCKER_REPO)/dashboard$(shell [[ "$(KUBERMATIC_EDITION)" != "ce" ]] && printf -- '-%s' ${KUBERMATIC_EDITION}) IMAGE_TAG=$(shell echo $$(git rev-parse HEAD)|tr -d '\n') diff --git a/modules/api/Makefile b/modules/api/Makefile index ed55335689..37d4eddadf 100644 --- a/modules/api/Makefile +++ b/modules/api/Makefile @@ -1,6 +1,6 @@ SHELL = /bin/bash -eu -o pipefail export KUBERMATIC_EDITION ?= ee -KUBERMATIC_VERSION?=v2.27.0 +KUBERMATIC_VERSION?=v2.27.1 DOCKER_REPO ?= quay.io/kubermatic REPO = $(DOCKER_REPO)/dashboard$(shell [[ "$(KUBERMATIC_EDITION)" != "ce" ]] && printf -- '-%s' ${KUBERMATIC_EDITION}) IMAGE_TAG=$(shell echo $$(git rev-parse HEAD)|tr -d '\n') diff --git a/modules/api/go.mod b/modules/api/go.mod index 4781b1d260..05b6260480 100644 --- a/modules/api/go.mod +++ b/modules/api/go.mod @@ -70,7 +70,7 @@ require ( google.golang.org/api v0.209.0 gopkg.in/yaml.v3 v3.0.1 k8c.io/kubeone v1.7.3 - k8c.io/kubermatic/v2 v2.27.0-rc.0.0.20250218085523-655dc5da7852 + k8c.io/kubermatic/v2 v2.27.0 k8c.io/machine-controller v1.61.0 k8c.io/operating-system-manager v1.6.1-0.20241118134103-5db575f65108 k8c.io/reconciler v0.5.0 diff --git a/modules/api/go.sum b/modules/api/go.sum index 478640765c..c09a1917a7 100644 --- a/modules/api/go.sum +++ b/modules/api/go.sum @@ -1158,8 +1158,8 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8c.io/kubeone v1.7.2 h1:uLH19VEp1S5j4f3UwQP4CLHErJ23UiJM2MnbufNWDgI= k8c.io/kubeone v1.7.2/go.mod h1:9v2VFz/+l36cW65kd5YufEYHunbKlJ6P8SBakj05xgM= -k8c.io/kubermatic/v2 v2.27.0-rc.0.0.20250218085523-655dc5da7852 h1:8aC+iwWxZN+Us8wimmqNm1M1P20t3B3p57OhcZ8b3yo= -k8c.io/kubermatic/v2 v2.27.0-rc.0.0.20250218085523-655dc5da7852/go.mod h1:ig2jw2L4/LVa8dhLfB90OcJh3VfB673ecFF90gKmDIE= +k8c.io/kubermatic/v2 v2.27.0 h1:SSOcgnY7my14tcw1GIoofN7l8g7UJsHOwd5MWGcwivs= +k8c.io/kubermatic/v2 v2.27.0/go.mod h1:ig2jw2L4/LVa8dhLfB90OcJh3VfB673ecFF90gKmDIE= k8c.io/machine-controller v1.61.0 h1:d7KVD2CDG2K76ujSt5RPLUP3BCNDcioObdM1N0BUNlc= k8c.io/machine-controller v1.61.0/go.mod h1:ZGDFyUeEp66RHcNB5Ki/OJyFdZFgo9dkHJ9s6YJWPcg= k8c.io/operating-system-manager v1.6.1-0.20241118134103-5db575f65108 h1:xiKGpydY/jsBVD5XT3zvAxW2pPKEtgcv526zdVFzbuw= diff --git a/modules/web/Makefile b/modules/web/Makefile index 96661ec252..45b514cffd 100644 --- a/modules/web/Makefile +++ b/modules/web/Makefile @@ -1,6 +1,6 @@ SHELL = /bin/bash -eu -o pipefail export KUBERMATIC_EDITION ?= ee -KUBERMATIC_VERSION?=v2.27.0 +KUBERMATIC_VERSION?=v2.27.1 CC=npm GOOS ?= $(shell go env GOOS) export GOOS diff --git a/modules/web/package-lock.json b/modules/web/package-lock.json index 2acb3a0eea..2ac763f992 100644 --- a/modules/web/package-lock.json +++ b/modules/web/package-lock.json @@ -1,12 +1,12 @@ { "name": "kubermatic-dashboard", - "version": "2.27.0", + "version": "2.27.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "kubermatic-dashboard", - "version": "2.27.0", + "version": "2.27.1", "hasInstallScript": true, "license": "proprietary", "dependencies": { diff --git a/modules/web/package.json b/modules/web/package.json index 2e18c773fe..334c9ec9c7 100644 --- a/modules/web/package.json +++ b/modules/web/package.json @@ -1,7 +1,7 @@ { "name": "kubermatic-dashboard", "description": "Kubermatic Dashboard", - "version": "2.27.0", + "version": "2.27.1", "type": "module", "license": "proprietary", "repository": "https://github.com/kubermatic/dashboard", From 5da065b67057ca71b18d4447f46bc927eb2d9389 Mon Sep 17 00:00:00 2001 From: Archana Sawant Date: Tue, 11 Mar 2025 20:16:20 +0530 Subject: [PATCH 02/42] [release/v2.27] Bump KKP dependency for 2.27.1 patch release (#7215) Signed-off-by: archups --- modules/api/go.mod | 2 +- modules/api/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/api/go.mod b/modules/api/go.mod index 05b6260480..50b13a3e03 100644 --- a/modules/api/go.mod +++ b/modules/api/go.mod @@ -70,7 +70,7 @@ require ( google.golang.org/api v0.209.0 gopkg.in/yaml.v3 v3.0.1 k8c.io/kubeone v1.7.3 - k8c.io/kubermatic/v2 v2.27.0 + k8c.io/kubermatic/v2 v2.27.1-0.20250311141421-1c2270778cac k8c.io/machine-controller v1.61.0 k8c.io/operating-system-manager v1.6.1-0.20241118134103-5db575f65108 k8c.io/reconciler v0.5.0 diff --git a/modules/api/go.sum b/modules/api/go.sum index c09a1917a7..40aadaa87d 100644 --- a/modules/api/go.sum +++ b/modules/api/go.sum @@ -1158,8 +1158,8 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8c.io/kubeone v1.7.2 h1:uLH19VEp1S5j4f3UwQP4CLHErJ23UiJM2MnbufNWDgI= k8c.io/kubeone v1.7.2/go.mod h1:9v2VFz/+l36cW65kd5YufEYHunbKlJ6P8SBakj05xgM= -k8c.io/kubermatic/v2 v2.27.0 h1:SSOcgnY7my14tcw1GIoofN7l8g7UJsHOwd5MWGcwivs= -k8c.io/kubermatic/v2 v2.27.0/go.mod h1:ig2jw2L4/LVa8dhLfB90OcJh3VfB673ecFF90gKmDIE= +k8c.io/kubermatic/v2 v2.27.1-0.20250311141421-1c2270778cac h1:5yH+uH3dbfOYnubLBho/oVHGZTshcka510dNI6xIjZw= +k8c.io/kubermatic/v2 v2.27.1-0.20250311141421-1c2270778cac/go.mod h1:ig2jw2L4/LVa8dhLfB90OcJh3VfB673ecFF90gKmDIE= k8c.io/machine-controller v1.61.0 h1:d7KVD2CDG2K76ujSt5RPLUP3BCNDcioObdM1N0BUNlc= k8c.io/machine-controller v1.61.0/go.mod h1:ZGDFyUeEp66RHcNB5Ki/OJyFdZFgo9dkHJ9s6YJWPcg= k8c.io/operating-system-manager v1.6.1-0.20241118134103-5db575f65108 h1:xiKGpydY/jsBVD5XT3zvAxW2pPKEtgcv526zdVFzbuw= From 4cdfa8857475feb6f189c9a826a651ce8dbbc217 Mon Sep 17 00:00:00 2001 From: Kubermatic Bot <41968677+kubermatic-bot@users.noreply.github.com> Date: Mon, 17 Mar 2025 09:49:41 +0100 Subject: [PATCH 03/42] [release/v2.27] (fix): use OSP from node instead of default OSP from dc (#7220) * Update `getOperatingSystemProfile` helper to parse Node deployment instead of relying on the datacenter spec which needs to be used as defaulting not as an enforeced OSP Signed-off-by: Burak Sekili <32663655+buraksekili@users.noreply.github.com> * Fix OSP annotation handling in MachineDeployment Improve Operating System Profile annotation logic to properly handle fallbacks: - Keep existing non-empty OSP annotations in MD, - Fall back to datacenter defaults when annotation is missing/empty, - Set annotation value (even if empty) to ensure proper OSM defaulting This preserves the datacenter default handling while ensuring consistent annotation behavior for the OSM component. Signed-off-by: Burak Sekili <32663655+buraksekili@users.noreply.github.com> * Do not set OSP annotation if osp is an empty string. OSM will handle both cases in its defaulting logic. This change allows us to pass test cases without modification. Signed-off-by: Burak Sekili <32663655+buraksekili@users.noreply.github.com> --------- Signed-off-by: Burak Sekili <32663655+buraksekili@users.noreply.github.com> Co-authored-by: Burak Sekili <32663655+buraksekili@users.noreply.github.com> --- .../pkg/resources/machine/machinedeployment.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/modules/api/pkg/resources/machine/machinedeployment.go b/modules/api/pkg/resources/machine/machinedeployment.go index 435d81d3aa..f44f052b25 100644 --- a/modules/api/pkg/resources/machine/machinedeployment.go +++ b/modules/api/pkg/resources/machine/machinedeployment.go @@ -69,12 +69,19 @@ func Deployment(ctx context.Context, c *kubermaticv1.Cluster, nd *apiv1.NodeDepl // Add Annotations to Machine Deployment md.Annotations = nd.Annotations - osp := getOperatingSystemProfile(nd, dc) - if osp != "" { - if md.Annotations == nil { - md.Annotations = make(map[string]string) + // OSP is an optional value passed via annotations with fallback logic: + // 1. Use existing non-empty annotation if present + // 2. Fall back to datacenter-level defaults when annotation is missing/empty + // 3. Allow empty value to let OSM apply its defaulting logic + if osp := nd.Annotations[osmresources.MachineDeploymentOSPAnnotation]; osp == "" { + osp = getOperatingSystemProfile(nd, dc) + if osp != "" { + if md.Annotations == nil { + md.Annotations = make(map[string]string) + } + + md.Annotations[osmresources.MachineDeploymentOSPAnnotation] = osp } - md.Annotations[osmresources.MachineDeploymentOSPAnnotation] = osp } md.Namespace = metav1.NamespaceSystem From 26396fb536101d1b53326b09a5cbac19156f5539 Mon Sep 17 00:00:00 2001 From: Archana Sawant Date: Tue, 25 Mar 2025 20:35:58 +0530 Subject: [PATCH 04/42] [release/v2.27] Bump KKP dashboard version and update dependencies (#7238) * [release/v2.27] Bump KKP dashboard version and update dependencies Signed-off-by: archups * Bump KKP dependencies with latest commit Signed-off-by: archups --------- Signed-off-by: archups --- Makefile | 2 +- modules/api/Makefile | 2 +- modules/api/go.mod | 20 ++++++------- modules/api/go.sum | 53 +++++++++++++---------------------- modules/web/Makefile | 2 +- modules/web/package-lock.json | 4 +-- modules/web/package.json | 2 +- 7 files changed, 35 insertions(+), 50 deletions(-) diff --git a/Makefile b/Makefile index 2912e466ff..8873628cc7 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ SHELL = /bin/bash -eu -o pipefail export KUBERMATIC_EDITION ?= ee -KUBERMATIC_VERSION?=v2.27.1 +KUBERMATIC_VERSION?=v2.27.2 DOCKER_REPO ?= quay.io/kubermatic REPO = $(DOCKER_REPO)/dashboard$(shell [[ "$(KUBERMATIC_EDITION)" != "ce" ]] && printf -- '-%s' ${KUBERMATIC_EDITION}) IMAGE_TAG=$(shell echo $$(git rev-parse HEAD)|tr -d '\n') diff --git a/modules/api/Makefile b/modules/api/Makefile index 37d4eddadf..0cde7dd279 100644 --- a/modules/api/Makefile +++ b/modules/api/Makefile @@ -1,6 +1,6 @@ SHELL = /bin/bash -eu -o pipefail export KUBERMATIC_EDITION ?= ee -KUBERMATIC_VERSION?=v2.27.1 +KUBERMATIC_VERSION?=v2.27.2 DOCKER_REPO ?= quay.io/kubermatic REPO = $(DOCKER_REPO)/dashboard$(shell [[ "$(KUBERMATIC_EDITION)" != "ce" ]] && printf -- '-%s' ${KUBERMATIC_EDITION}) IMAGE_TAG=$(shell echo $$(git rev-parse HEAD)|tr -d '\n') diff --git a/modules/api/go.mod b/modules/api/go.mod index 50b13a3e03..c75b918451 100644 --- a/modules/api/go.mod +++ b/modules/api/go.mod @@ -70,7 +70,7 @@ require ( google.golang.org/api v0.209.0 gopkg.in/yaml.v3 v3.0.1 k8c.io/kubeone v1.7.3 - k8c.io/kubermatic/v2 v2.27.1-0.20250311141421-1c2270778cac + k8c.io/kubermatic/v2 v2.27.2-0.20250325143358-06e82b91a3ec k8c.io/machine-controller v1.61.0 k8c.io/operating-system-manager v1.6.1-0.20241118134103-5db575f65108 k8c.io/reconciler v0.5.0 @@ -105,12 +105,11 @@ replace k8s.io/client-go => k8s.io/client-go v0.31.1 replace k8c.io/kubeone => k8c.io/kubeone v1.7.2 require ( - cel.dev/expr v0.19.0 // indirect cloud.google.com/go/auth v0.11.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect cloud.google.com/go/compute/metadata v0.5.2 // indirect dario.cat/mergo v1.0.1 // indirect - github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect + github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.29 // indirect @@ -119,13 +118,13 @@ require ( github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect github.com/BurntSushi/toml v1.4.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/sprig/v3 v3.3.0 // indirect github.com/PaesslerAG/gval v1.2.4 // indirect github.com/PaesslerAG/jsonpath v0.1.1 // indirect - github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect @@ -145,7 +144,7 @@ require ( github.com/cert-manager/cert-manager v1.16.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/containerd/containerd v1.7.24 // indirect - github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -178,14 +177,13 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/cel-go v0.22.1 // indirect - github.com/google/gnostic-models v0.6.9 // indirect + github.com/google/cel-go v0.21.0 // indirect + github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.1-0.20210504230335-f78f29fc09ea // indirect github.com/google/s2a-go v0.1.8 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/gax-go/v2 v2.14.0 // indirect - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-plugin v1.6.0 // indirect @@ -203,7 +201,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.11 // indirect - github.com/klauspost/cpuid/v2 v2.2.9 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/kubernetes-csi/external-snapshotter/client/v7 v7.0.0 // indirect @@ -293,7 +291,7 @@ require ( k8s.io/kube-aggregator v0.31.3 // indirect k8s.io/kube-openapi v0.31.2 // indirect kubevirt.io/controller-lifecycle-operator-sdk/api v0.2.4 // indirect - sigs.k8s.io/gateway-api v1.2.1 // indirect + sigs.k8s.io/gateway-api v1.1.0 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.3 // indirect ) diff --git a/modules/api/go.sum b/modules/api/go.sum index 40aadaa87d..199fae7bcc 100644 --- a/modules/api/go.sum +++ b/modules/api/go.sum @@ -1,5 +1,3 @@ -cel.dev/expr v0.19.0 h1:lXuo+nDhpyJSpWxpPVi5cPUwzKb+dsdOiw6IreM5yt0= -cel.dev/expr v0.19.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go/auth v0.11.0 h1:Ic5SZz2lsvbYcWT5dfjNWgw6tTlGi2Wc8hyQSC9BstA= cloud.google.com/go/auth v0.11.0/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= @@ -14,8 +12,8 @@ dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/99designs/gqlgen v0.15.1 h1:48bRXecwlCNTa/n2bMSp2rQsXNxwZ54QHbiULNf78ec= github.com/99designs/gqlgen v0.15.1/go.mod h1:nbeSjFkqphIqpZsYe1ULVz0yfH8hjpJdJIQoX/e0G2I= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M= @@ -68,8 +66,8 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ= -github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= @@ -99,8 +97,8 @@ github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVb github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/aliyun/alibaba-cloud-sdk-go v1.63.63 h1:+Yah3qC7eYcJORQrL9VC62uFbkce99co27dfsogAa8A= github.com/aliyun/alibaba-cloud-sdk-go v1.63.63/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ= -github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= -github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA= github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -150,7 +148,6 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 h1:s4074ZO1Hk8qv65GqNXqDjmkf4HS github.com/aws/aws-sdk-go-v2/service/sts v1.33.2/go.mod h1:mVggCnIWoM09jP71Wh+ea7+5gAp53q+49wDFs1SW5z8= github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= @@ -182,8 +179,8 @@ github.com/containerd/containerd v1.7.24 h1:zxszGrGjrra1yYJW/6rhm9cJ1ZQ8rkKBR48b github.com/containerd/containerd v1.7.24/go.mod h1:7QUzfURqZWCZV7RLNEn1XjUCQLEf0bkaK4GjUaZehxw= github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= -github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= -github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= +github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= @@ -371,11 +368,11 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/cel-go v0.22.1 h1:AfVXx3chM2qwoSbM7Da8g8hX8OVSkBFwX+rz2+PcK40= -github.com/google/cel-go v0.22.1/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8= +github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= +github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= -github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= -github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= +github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= +github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -424,8 +421,8 @@ github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pw github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99 h1:JYghRBlGCZyCF2wNUJ8W0cwaQdtpcssJ4CgC406g+WU= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= @@ -482,8 +479,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= -github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -521,7 +518,6 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/minio-go/v7 v7.0.81 h1:SzhMN0TQ6T/xSBu6Nvw3M5M8voM+Ht8RH3hE8S7zxaA= @@ -652,18 +648,13 @@ github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k= github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= -github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= -github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= @@ -873,7 +864,6 @@ golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 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= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -1037,7 +1027,6 @@ golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1092,7 +1081,6 @@ google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a h1: google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY= google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a h1:hgh8P4EuoxpsuKMXX/To36nOFD7vixReXgn8lPGnt+o= google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= -google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -1153,13 +1141,12 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= helm.sh/helm/v3 v3.16.3 h1:kb8bSxMeRJ+knsK/ovvlaVPfdis0X3/ZhYCSFRP+YmY= helm.sh/helm/v3 v3.16.3/go.mod h1:zeVWGDR4JJgiRbT3AnNsjYaX8OTJlIE9zC+Q7F7iUSU= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8c.io/kubeone v1.7.2 h1:uLH19VEp1S5j4f3UwQP4CLHErJ23UiJM2MnbufNWDgI= k8c.io/kubeone v1.7.2/go.mod h1:9v2VFz/+l36cW65kd5YufEYHunbKlJ6P8SBakj05xgM= -k8c.io/kubermatic/v2 v2.27.1-0.20250311141421-1c2270778cac h1:5yH+uH3dbfOYnubLBho/oVHGZTshcka510dNI6xIjZw= -k8c.io/kubermatic/v2 v2.27.1-0.20250311141421-1c2270778cac/go.mod h1:ig2jw2L4/LVa8dhLfB90OcJh3VfB673ecFF90gKmDIE= +k8c.io/kubermatic/v2 v2.27.2-0.20250325143358-06e82b91a3ec h1:N8wOjGGtHE6vbIXX9jPc6+TPRBhK3i3hcIFSqWoSiDM= +k8c.io/kubermatic/v2 v2.27.2-0.20250325143358-06e82b91a3ec/go.mod h1:bOX9nDcTwcYNCPfPzHUGhe9MWuLqzAxszFzyK1dtLqU= k8c.io/machine-controller v1.61.0 h1:d7KVD2CDG2K76ujSt5RPLUP3BCNDcioObdM1N0BUNlc= k8c.io/machine-controller v1.61.0/go.mod h1:ZGDFyUeEp66RHcNB5Ki/OJyFdZFgo9dkHJ9s6YJWPcg= k8c.io/operating-system-manager v1.6.1-0.20241118134103-5db575f65108 h1:xiKGpydY/jsBVD5XT3zvAxW2pPKEtgcv526zdVFzbuw= @@ -1223,8 +1210,8 @@ sigs.k8s.io/controller-runtime v0.19.3 h1:XO2GvC9OPftRst6xWCpTgBZO04S2cbp0Qqkj8b sigs.k8s.io/controller-runtime v0.19.3/go.mod h1:j4j87DqtsThvwTv5/Tc5NFRyyF/RF0ip4+62tbTSIUM= sigs.k8s.io/controller-tools v0.16.5 h1:5k9FNRqziBPwqr17AMEPPV/En39ZBplLAdOwwQHruP4= sigs.k8s.io/controller-tools v0.16.5/go.mod h1:8vztuRVzs8IuuJqKqbXCSlXcw+lkAv/M2sTpg55qjMY= -sigs.k8s.io/gateway-api v1.2.1 h1:fZZ/+RyRb+Y5tGkwxFKuYuSRQHu9dZtbjenblleOLHM= -sigs.k8s.io/gateway-api v1.2.1/go.mod h1:EpNfEXNjiYfUJypf0eZ0P5iXA9ekSGWaS1WgPaM42X0= +sigs.k8s.io/gateway-api v1.1.0 h1:DsLDXCi6jR+Xz8/xd0Z1PYl2Pn0TyaFMOPPZIj4inDM= +sigs.k8s.io/gateway-api v1.1.0/go.mod h1:ZH4lHrL2sDi0FHZ9jjneb8kKnGzFWyrTya35sWUTrRs= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= diff --git a/modules/web/Makefile b/modules/web/Makefile index 45b514cffd..e0b2cbbbbc 100644 --- a/modules/web/Makefile +++ b/modules/web/Makefile @@ -1,6 +1,6 @@ SHELL = /bin/bash -eu -o pipefail export KUBERMATIC_EDITION ?= ee -KUBERMATIC_VERSION?=v2.27.1 +KUBERMATIC_VERSION?=v2.27.2 CC=npm GOOS ?= $(shell go env GOOS) export GOOS diff --git a/modules/web/package-lock.json b/modules/web/package-lock.json index 2ac763f992..509558c5b6 100644 --- a/modules/web/package-lock.json +++ b/modules/web/package-lock.json @@ -1,12 +1,12 @@ { "name": "kubermatic-dashboard", - "version": "2.27.1", + "version": "2.27.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "kubermatic-dashboard", - "version": "2.27.1", + "version": "2.27.2", "hasInstallScript": true, "license": "proprietary", "dependencies": { diff --git a/modules/web/package.json b/modules/web/package.json index 334c9ec9c7..0739f44004 100644 --- a/modules/web/package.json +++ b/modules/web/package.json @@ -1,7 +1,7 @@ { "name": "kubermatic-dashboard", "description": "Kubermatic Dashboard", - "version": "2.27.1", + "version": "2.27.2", "type": "module", "license": "proprietary", "repository": "https://github.com/kubermatic/dashboard", From df8e77494595dd42ab428ebcd611b858f4a96c9b Mon Sep 17 00:00:00 2001 From: soer3n <43064202+soer3n@users.noreply.github.com> Date: Tue, 8 Apr 2025 08:27:12 +0200 Subject: [PATCH 05/42] [release/v2.27] Add kubevirt cpu alloc ratio support (#7252) (#7264) * Add kubevirt cpu alloc ratio support (#7252) * add logic for new kubevirt dc config value + bump kkp sdk and osm Signed-off-by: soer3n * fix ui kubevirt cpu displaying * add condition for ui to display cpus also when vcpus are set instead of cpus * fix parsing of cpus in case vcpus are configured * remove not needed condition regarding instance types Signed-off-by: soer3n * update swagger.json and api client Signed-off-by: soer3n * simplify condition to render cpu value for kubevirt vm Signed-off-by: soer3n --------- Signed-off-by: soer3n * modify default k8s version accordingly to kkp Signed-off-by: soer3n --------- Signed-off-by: soer3n --- modules/api/cmd/kubermatic-api/swagger.json | 5 +++++ modules/api/go.mod | 6 +++--- modules/api/go.sum | 12 ++++++------ .../v2/cluster_default/cluster_default_test.go | 8 ++++---- .../v2/external_cluster/external_cluster_test.go | 8 ++++---- modules/api/pkg/machine/convert.go | 6 +++++- modules/api/pkg/resources/machine/common.go | 11 ++++++++++- .../apiclient/models/datacenter_spec_kubevirt.go | 4 ++++ 8 files changed, 41 insertions(+), 19 deletions(-) diff --git a/modules/api/cmd/kubermatic-api/swagger.json b/modules/api/cmd/kubermatic-api/swagger.json index 8703628f2f..6e3583d03c 100644 --- a/modules/api/cmd/kubermatic-api/swagger.json +++ b/modules/api/cmd/kubermatic-api/swagger.json @@ -30990,6 +30990,11 @@ "type": "string", "x-go-name": "DNSPolicy" }, + "enableDedicatedCpus": { + "description": "Optional: EnableDedicatedCPUs enables the assignment of dedicated cpus instead of resource requests and limits for a virtual machine.\nDefaults to false.", + "type": "boolean", + "x-go-name": "EnableDedicatedCPUs" + }, "enableDefaultNetworkPolicies": { "description": "Optional: EnableDefaultNetworkPolicies enables deployment of default network policies like cluster isolation.\nDefaults to true.", "type": "boolean", diff --git a/modules/api/go.mod b/modules/api/go.mod index c75b918451..ecd3d207c7 100644 --- a/modules/api/go.mod +++ b/modules/api/go.mod @@ -70,9 +70,9 @@ require ( google.golang.org/api v0.209.0 gopkg.in/yaml.v3 v3.0.1 k8c.io/kubeone v1.7.3 - k8c.io/kubermatic/v2 v2.27.2-0.20250325143358-06e82b91a3ec - k8c.io/machine-controller v1.61.0 - k8c.io/operating-system-manager v1.6.1-0.20241118134103-5db575f65108 + k8c.io/kubermatic/v2 v2.27.3-0.20250407201314-a67d58c4cd98 + k8c.io/machine-controller v1.61.1 + k8c.io/operating-system-manager v1.6.4 k8c.io/reconciler v0.5.0 k8s.io/api v0.31.3 k8s.io/apiextensions-apiserver v0.31.3 diff --git a/modules/api/go.sum b/modules/api/go.sum index 199fae7bcc..3fe1dd9086 100644 --- a/modules/api/go.sum +++ b/modules/api/go.sum @@ -1145,12 +1145,12 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8c.io/kubeone v1.7.2 h1:uLH19VEp1S5j4f3UwQP4CLHErJ23UiJM2MnbufNWDgI= k8c.io/kubeone v1.7.2/go.mod h1:9v2VFz/+l36cW65kd5YufEYHunbKlJ6P8SBakj05xgM= -k8c.io/kubermatic/v2 v2.27.2-0.20250325143358-06e82b91a3ec h1:N8wOjGGtHE6vbIXX9jPc6+TPRBhK3i3hcIFSqWoSiDM= -k8c.io/kubermatic/v2 v2.27.2-0.20250325143358-06e82b91a3ec/go.mod h1:bOX9nDcTwcYNCPfPzHUGhe9MWuLqzAxszFzyK1dtLqU= -k8c.io/machine-controller v1.61.0 h1:d7KVD2CDG2K76ujSt5RPLUP3BCNDcioObdM1N0BUNlc= -k8c.io/machine-controller v1.61.0/go.mod h1:ZGDFyUeEp66RHcNB5Ki/OJyFdZFgo9dkHJ9s6YJWPcg= -k8c.io/operating-system-manager v1.6.1-0.20241118134103-5db575f65108 h1:xiKGpydY/jsBVD5XT3zvAxW2pPKEtgcv526zdVFzbuw= -k8c.io/operating-system-manager v1.6.1-0.20241118134103-5db575f65108/go.mod h1:Dh9IZp5pb5G3s2r6ZrHUb+gTuHw5AmbIFYuFxIcgU7o= +k8c.io/kubermatic/v2 v2.27.3-0.20250407201314-a67d58c4cd98 h1:RAYxSDlrpH7gX5AC8FSRJ5dzAmEYWmDL93+ofL+ER/g= +k8c.io/kubermatic/v2 v2.27.3-0.20250407201314-a67d58c4cd98/go.mod h1:SO+EkmaM+I27QlA2EK0ip6TiK9dXyu9N0PzjEIEbvJA= +k8c.io/machine-controller v1.61.1 h1:Zy3kg9t0WrDN0Wo3y/pAJp7jdkThcJt070f0fAL9MVc= +k8c.io/machine-controller v1.61.1/go.mod h1:ZGDFyUeEp66RHcNB5Ki/OJyFdZFgo9dkHJ9s6YJWPcg= +k8c.io/operating-system-manager v1.6.4 h1:MDtULmZeqsLEg9Fs96i41qVKOyakyV31OcxU54edQTI= +k8c.io/operating-system-manager v1.6.4/go.mod h1:Dh9IZp5pb5G3s2r6ZrHUb+gTuHw5AmbIFYuFxIcgU7o= k8c.io/reconciler v0.5.0 h1:BHpelg1UfI/7oBFctqOq8sX6qzflXpl3SlvHe7e8wak= k8c.io/reconciler v0.5.0/go.mod h1:pT1+SVcVXJQeBJhpJBXQ5XW64QnKKeYTnVlQf0dGE0k= k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= diff --git a/modules/api/pkg/handler/v2/cluster_default/cluster_default_test.go b/modules/api/pkg/handler/v2/cluster_default/cluster_default_test.go index c077266e17..490dc1735f 100644 --- a/modules/api/pkg/handler/v2/cluster_default/cluster_default_test.go +++ b/modules/api/pkg/handler/v2/cluster_default/cluster_default_test.go @@ -50,7 +50,7 @@ func TestGetEndpoint(t *testing.T) { ), ExistingAPIUser: test.GenDefaultAPIUser(), ExpectedHTTPStatusCode: http.StatusOK, - ExpectedResponse: `{"name":"","creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","aws":{}},"version":"v1.31.1","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20","fd02::/120"]},"pods":{"cidrBlocks":["172.25.0.0/16","fd01::/48"]},"nodeCidrMaskSizeIPv4":24,"nodeCidrMaskSizeIPv6":64,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":""}}`, + ExpectedResponse: `{"name":"","creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","aws":{}},"version":"v1.31.7","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20","fd02::/120"]},"pods":{"cidrBlocks":["172.25.0.0/16","fd01::/48"]},"nodeCidrMaskSizeIPv4":24,"nodeCidrMaskSizeIPv6":64,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":""}}`, }, { Name: "Default cluster for Azure", @@ -61,7 +61,7 @@ func TestGetEndpoint(t *testing.T) { ), ExistingAPIUser: test.GenDefaultAPIUser(), ExpectedHTTPStatusCode: http.StatusOK, - ExpectedResponse: `{"name":"","creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","azure":{"assignAvailabilitySet":null}},"version":"v1.31.1","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20","fd02::/120"]},"pods":{"cidrBlocks":["172.25.0.0/16","fd01::/48"]},"nodeCidrMaskSizeIPv4":24,"nodeCidrMaskSizeIPv6":64,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":""}}`, + ExpectedResponse: `{"name":"","creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","azure":{"assignAvailabilitySet":null}},"version":"v1.31.7","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20","fd02::/120"]},"pods":{"cidrBlocks":["172.25.0.0/16","fd01::/48"]},"nodeCidrMaskSizeIPv4":24,"nodeCidrMaskSizeIPv6":64,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":""}}`, }, { Name: "Default cluster for vSphere", @@ -72,7 +72,7 @@ func TestGetEndpoint(t *testing.T) { ), ExistingAPIUser: test.GenDefaultAPIUser(), ExpectedHTTPStatusCode: http.StatusOK, - ExpectedResponse: `{"name":"","creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","vsphere":{}},"version":"v1.31.1","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20","fd02::/120"]},"pods":{"cidrBlocks":["172.25.0.0/16","fd01::/48"]},"nodeCidrMaskSizeIPv4":24,"nodeCidrMaskSizeIPv6":64,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":""}}`, + ExpectedResponse: `{"name":"","creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","vsphere":{}},"version":"v1.31.7","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20","fd02::/120"]},"pods":{"cidrBlocks":["172.25.0.0/16","fd01::/48"]},"nodeCidrMaskSizeIPv4":24,"nodeCidrMaskSizeIPv6":64,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":""}}`, }, { Name: "Default cluster for GCP", @@ -83,7 +83,7 @@ func TestGetEndpoint(t *testing.T) { ), ExistingAPIUser: test.GenDefaultAPIUser(), ExpectedHTTPStatusCode: http.StatusOK, - ExpectedResponse: `{"name":"","creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","gcp":{}},"version":"v1.31.1","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20","fd02::/120"]},"pods":{"cidrBlocks":["172.25.0.0/16","fd01::/48"]},"nodeCidrMaskSizeIPv4":24,"nodeCidrMaskSizeIPv6":64,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":""}}`, + ExpectedResponse: `{"name":"","creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","gcp":{}},"version":"v1.31.7","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20","fd02::/120"]},"pods":{"cidrBlocks":["172.25.0.0/16","fd01::/48"]},"nodeCidrMaskSizeIPv4":24,"nodeCidrMaskSizeIPv6":64,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":""}}`, }, } diff --git a/modules/api/pkg/handler/v2/external_cluster/external_cluster_test.go b/modules/api/pkg/handler/v2/external_cluster/external_cluster_test.go index 4fd9c3225f..7e65e84482 100644 --- a/modules/api/pkg/handler/v2/external_cluster/external_cluster_test.go +++ b/modules/api/pkg/handler/v2/external_cluster/external_cluster_test.go @@ -306,7 +306,7 @@ func TestListClusters(t *testing.T) { ID: "clusterAbcID", }, Spec: apiv1.ClusterSpec{ - Version: "v1.31.1", + Version: "v1.31.7", }, Labels: map[string]string{kubermaticv1.ProjectIDLabelKey: test.GenDefaultProject().Name}, }, @@ -316,7 +316,7 @@ func TestListClusters(t *testing.T) { ID: "clusterDefID", }, Spec: apiv1.ClusterSpec{ - Version: "v1.31.1", + Version: "v1.31.7", }, Labels: map[string]string{kubermaticv1.ProjectIDLabelKey: test.GenDefaultProject().Name}, }, @@ -339,7 +339,7 @@ func TestListClusters(t *testing.T) { ID: "clusterAbcID", }, Spec: apiv1.ClusterSpec{ - Version: "v1.31.1", + Version: "v1.31.7", }, Labels: map[string]string{kubermaticv1.ProjectIDLabelKey: test.GenDefaultProject().Name}, }, @@ -349,7 +349,7 @@ func TestListClusters(t *testing.T) { ID: "clusterDefID", }, Spec: apiv1.ClusterSpec{ - Version: "v1.31.1", + Version: "v1.31.7", }, Labels: map[string]string{kubermaticv1.ProjectIDLabelKey: test.GenDefaultProject().Name}, }, diff --git a/modules/api/pkg/machine/convert.go b/modules/api/pkg/machine/convert.go index 4fd27554c8..291aaf36f9 100644 --- a/modules/api/pkg/machine/convert.go +++ b/modules/api/pkg/machine/convert.go @@ -352,7 +352,6 @@ func GetAPIV2NodeCloudSpec(machineSpec clusterv1alpha1.MachineSpec) (*apiv1.Node Instancetype: config.VirtualMachine.Instancetype, Preference: config.VirtualMachine.Preference, - CPUs: config.VirtualMachine.Template.CPUs.Value, Memory: config.VirtualMachine.Template.Memory.Value, PrimaryDiskOSImage: config.VirtualMachine.Template.PrimaryDisk.OsImage.Value, PrimaryDiskStorageClassName: config.VirtualMachine.Template.PrimaryDisk.StorageClassName.Value, @@ -362,6 +361,11 @@ func GetAPIV2NodeCloudSpec(machineSpec clusterv1alpha1.MachineSpec) (*apiv1.Node Key: config.Affinity.NodeAffinityPreset.Key.Value, }, } + if config.VirtualMachine.Template.VCPUs.Cores != 0 { + cloudSpec.Kubevirt.CPUs = strconv.Itoa(config.VirtualMachine.Template.VCPUs.Cores) + } else { + cloudSpec.Kubevirt.CPUs = config.VirtualMachine.Template.CPUs.Value + } cloudSpec.Kubevirt.SecondaryDisks = make([]apiv1.SecondaryDisks, 0, len(config.VirtualMachine.Template.SecondaryDisks)) for _, sd := range config.VirtualMachine.Template.SecondaryDisks { secondaryDisk := apiv1.SecondaryDisks{Size: sd.Size.Value, StorageClassName: sd.StorageClassName.Value} diff --git a/modules/api/pkg/resources/machine/common.go b/modules/api/pkg/resources/machine/common.go index 58e120c8a5..0f7f844e17 100644 --- a/modules/api/pkg/resources/machine/common.go +++ b/modules/api/pkg/resources/machine/common.go @@ -598,7 +598,6 @@ func GetKubevirtProviderConfig(cluster *kubermaticv1.Cluster, nodeSpec apiv1.Nod Instancetype: nodeSpec.Cloud.Kubevirt.Instancetype, Preference: nodeSpec.Cloud.Kubevirt.Preference, Template: kubevirt.Template{ - CPUs: providerconfig.ConfigVarString{Value: nodeSpec.Cloud.Kubevirt.CPUs}, Memory: providerconfig.ConfigVarString{Value: nodeSpec.Cloud.Kubevirt.Memory}, PrimaryDisk: kubevirt.PrimaryDisk{ Disk: kubevirt.Disk{ @@ -620,6 +619,16 @@ func GetKubevirtProviderConfig(cluster *kubermaticv1.Cluster, nodeSpec apiv1.Nod }, } + if dc.Spec.Kubevirt.EnableDedicatedCPUs { + vcpus, err := strconv.ParseInt(nodeSpec.Cloud.Kubevirt.CPUs, 0, 64) + if err != nil { + return nil, err + } + config.VirtualMachine.Template.VCPUs.Cores = int(vcpus) + } else { + config.VirtualMachine.Template.CPUs = providerconfig.ConfigVarString{Value: nodeSpec.Cloud.Kubevirt.CPUs} + } + var subnet string if nodeSpec.Cloud.Kubevirt.Subnet != "" { subnet = nodeSpec.Cloud.Kubevirt.Subnet diff --git a/modules/api/pkg/test/e2e/utils/apiclient/models/datacenter_spec_kubevirt.go b/modules/api/pkg/test/e2e/utils/apiclient/models/datacenter_spec_kubevirt.go index 21c431b584..925e3a594b 100644 --- a/modules/api/pkg/test/e2e/utils/apiclient/models/datacenter_spec_kubevirt.go +++ b/modules/api/pkg/test/e2e/utils/apiclient/models/datacenter_spec_kubevirt.go @@ -31,6 +31,10 @@ type DatacenterSpecKubevirt struct { // policy selected with DNSPolicy. DNSPolicy string `json:"dnsPolicy,omitempty"` + // Optional: EnableDedicatedCPUs enables the assignment of dedicated cpus instead of resource requests and limits for a virtual machine. + // Defaults to false. + EnableDedicatedCPUs bool `json:"enableDedicatedCpus,omitempty"` + // Optional: EnableDefaultNetworkPolicies enables deployment of default network policies like cluster isolation. // Defaults to true. EnableDefaultNetworkPolicies bool `json:"enableDefaultNetworkPolicies,omitempty"` From 082df31628dee712f87b95dfc83b56174977fda1 Mon Sep 17 00:00:00 2001 From: Archana Sawant Date: Tue, 8 Apr 2025 15:03:12 +0530 Subject: [PATCH 06/42] [release/v2.27] Bump KKP dashboard version and dependencies for April 2025 patch release 2.27.3 (#7266) * [release/v2.27] Bump KKP Dashboard version and dependencies for April 2025 patch release Signed-off-by: archups * go mod tidy --------- Signed-off-by: archups Co-authored-by: Christoph Mewes --- Makefile | 2 +- modules/api/Makefile | 2 +- modules/api/go.mod | 2 +- modules/api/go.sum | 16 ++++++++-------- modules/web/Makefile | 2 +- modules/web/package-lock.json | 4 ++-- modules/web/package.json | 2 +- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index 8873628cc7..e701eaacf2 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ SHELL = /bin/bash -eu -o pipefail export KUBERMATIC_EDITION ?= ee -KUBERMATIC_VERSION?=v2.27.2 +KUBERMATIC_VERSION?=v2.27.3 DOCKER_REPO ?= quay.io/kubermatic REPO = $(DOCKER_REPO)/dashboard$(shell [[ "$(KUBERMATIC_EDITION)" != "ce" ]] && printf -- '-%s' ${KUBERMATIC_EDITION}) IMAGE_TAG=$(shell echo $$(git rev-parse HEAD)|tr -d '\n') diff --git a/modules/api/Makefile b/modules/api/Makefile index 0cde7dd279..ac293149f7 100644 --- a/modules/api/Makefile +++ b/modules/api/Makefile @@ -1,6 +1,6 @@ SHELL = /bin/bash -eu -o pipefail export KUBERMATIC_EDITION ?= ee -KUBERMATIC_VERSION?=v2.27.2 +KUBERMATIC_VERSION?=v2.27.3 DOCKER_REPO ?= quay.io/kubermatic REPO = $(DOCKER_REPO)/dashboard$(shell [[ "$(KUBERMATIC_EDITION)" != "ce" ]] && printf -- '-%s' ${KUBERMATIC_EDITION}) IMAGE_TAG=$(shell echo $$(git rev-parse HEAD)|tr -d '\n') diff --git a/modules/api/go.mod b/modules/api/go.mod index ecd3d207c7..6253c92cc1 100644 --- a/modules/api/go.mod +++ b/modules/api/go.mod @@ -70,7 +70,7 @@ require ( google.golang.org/api v0.209.0 gopkg.in/yaml.v3 v3.0.1 k8c.io/kubeone v1.7.3 - k8c.io/kubermatic/v2 v2.27.3-0.20250407201314-a67d58c4cd98 + k8c.io/kubermatic/v2 v2.27.3-0.20250407231911-b56d2995a821 k8c.io/machine-controller v1.61.1 k8c.io/operating-system-manager v1.6.4 k8c.io/reconciler v0.5.0 diff --git a/modules/api/go.sum b/modules/api/go.sum index 3fe1dd9086..1c28da65ff 100644 --- a/modules/api/go.sum +++ b/modules/api/go.sum @@ -764,12 +764,12 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.anx.io/go-anxcloud v0.7.6 h1:n1dbOtKx85Qpcw6wWrLeLST01zsmwmPyRR3IoyC9QGg= go.anx.io/go-anxcloud v0.7.6/go.mod h1:4OObxqH+9mkf4i4C6VG7u6HCG4qTiMNsj+2kNP60WcE= -go.etcd.io/etcd/api/v3 v3.5.16 h1:WvmyJVbjWqK4R1E+B12RRHz3bRGy9XVfh++MgbN+6n0= -go.etcd.io/etcd/api/v3 v3.5.16/go.mod h1:1P4SlIP/VwkDmGo3OlOD7faPeP8KDIFhqvciH5EfN28= -go.etcd.io/etcd/client/pkg/v3 v3.5.16 h1:ZgY48uH6UvB+/7R9Yf4x574uCO3jIx0TRDyetSfId3Q= -go.etcd.io/etcd/client/pkg/v3 v3.5.16/go.mod h1:V8acl8pcEK0Y2g19YlOV9m9ssUe6MgiDSobSoaBAM0E= -go.etcd.io/etcd/client/v3 v3.5.16 h1:sSmVYOAHeC9doqi0gv7v86oY/BTld0SEFGaxsU9eRhE= -go.etcd.io/etcd/client/v3 v3.5.16/go.mod h1:X+rExSGkyqxvu276cr2OwPLBaeqFu1cIl4vmRjAD/50= +go.etcd.io/etcd/api/v3 v3.5.17 h1:cQB8eb8bxwuxOilBpMJAEo8fAONyrdXTHUNcMd8yT1w= +go.etcd.io/etcd/api/v3 v3.5.17/go.mod h1:d1hvkRuXkts6PmaYk2Vrgqbv7H4ADfAKhyJqHNLJCB4= +go.etcd.io/etcd/client/pkg/v3 v3.5.17 h1:XxnDXAWq2pnxqx76ljWwiQ9jylbpC4rvkAeRVOUKKVw= +go.etcd.io/etcd/client/pkg/v3 v3.5.17/go.mod h1:4DqK1TKacp/86nJk4FLQqo6Mn2vvQFBmruW3pP14H/w= +go.etcd.io/etcd/client/v3 v3.5.17 h1:o48sINNeWz5+pjy/Z0+HKpj/xSnBkuVhVvXkjEXbqZY= +go.etcd.io/etcd/client/v3 v3.5.17/go.mod h1:j2d4eXTHWkT2ClBgnnEPm/Wuu7jsqku41v9DZ3OtjQo= go.keploy.io/server v0.7.12 h1:DKDSO6T9Q4d4A8MKL+sk7U26KRcvZ+ZG0mbFhYIJJyk= go.keploy.io/server v0.7.12/go.mod h1:ch4rD1NCgtxozDHD9yVk+sLHWz5HgefOqrgEdEIgfBQ= go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM= @@ -1145,8 +1145,8 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8c.io/kubeone v1.7.2 h1:uLH19VEp1S5j4f3UwQP4CLHErJ23UiJM2MnbufNWDgI= k8c.io/kubeone v1.7.2/go.mod h1:9v2VFz/+l36cW65kd5YufEYHunbKlJ6P8SBakj05xgM= -k8c.io/kubermatic/v2 v2.27.3-0.20250407201314-a67d58c4cd98 h1:RAYxSDlrpH7gX5AC8FSRJ5dzAmEYWmDL93+ofL+ER/g= -k8c.io/kubermatic/v2 v2.27.3-0.20250407201314-a67d58c4cd98/go.mod h1:SO+EkmaM+I27QlA2EK0ip6TiK9dXyu9N0PzjEIEbvJA= +k8c.io/kubermatic/v2 v2.27.3-0.20250407231911-b56d2995a821 h1:Kfd1L7A9j4cM2x/ICXZHvDyUu5HiYizfPGP9ho4RN1I= +k8c.io/kubermatic/v2 v2.27.3-0.20250407231911-b56d2995a821/go.mod h1:rc/2inBvHXCBIPmzawH1NYICKkVPnm9ASBZl63wdUz0= k8c.io/machine-controller v1.61.1 h1:Zy3kg9t0WrDN0Wo3y/pAJp7jdkThcJt070f0fAL9MVc= k8c.io/machine-controller v1.61.1/go.mod h1:ZGDFyUeEp66RHcNB5Ki/OJyFdZFgo9dkHJ9s6YJWPcg= k8c.io/operating-system-manager v1.6.4 h1:MDtULmZeqsLEg9Fs96i41qVKOyakyV31OcxU54edQTI= diff --git a/modules/web/Makefile b/modules/web/Makefile index e0b2cbbbbc..445d472136 100644 --- a/modules/web/Makefile +++ b/modules/web/Makefile @@ -1,6 +1,6 @@ SHELL = /bin/bash -eu -o pipefail export KUBERMATIC_EDITION ?= ee -KUBERMATIC_VERSION?=v2.27.2 +KUBERMATIC_VERSION?=v2.27.3 CC=npm GOOS ?= $(shell go env GOOS) export GOOS diff --git a/modules/web/package-lock.json b/modules/web/package-lock.json index 509558c5b6..13785aca57 100644 --- a/modules/web/package-lock.json +++ b/modules/web/package-lock.json @@ -1,12 +1,12 @@ { "name": "kubermatic-dashboard", - "version": "2.27.2", + "version": "2.27.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "kubermatic-dashboard", - "version": "2.27.2", + "version": "2.27.3", "hasInstallScript": true, "license": "proprietary", "dependencies": { diff --git a/modules/web/package.json b/modules/web/package.json index 0739f44004..44ee2bb4ce 100644 --- a/modules/web/package.json +++ b/modules/web/package.json @@ -1,7 +1,7 @@ { "name": "kubermatic-dashboard", "description": "Kubermatic Dashboard", - "version": "2.27.2", + "version": "2.27.3", "type": "module", "license": "proprietary", "repository": "https://github.com/kubermatic/dashboard", From 110e3d0014d85f35f6a3ec20b0b7deee126529a5 Mon Sep 17 00:00:00 2001 From: Kubermatic Bot <41968677+kubermatic-bot@users.noreply.github.com> Date: Wed, 9 Apr 2025 12:13:13 +0200 Subject: [PATCH 07/42] add functionality to pick the highest priority (#7276) Co-authored-by: ahmadhamzh --- modules/web/src/app/shared/utils/member.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/web/src/app/shared/utils/member.ts b/modules/web/src/app/shared/utils/member.ts index cac9088fec..c70b188d12 100644 --- a/modules/web/src/app/shared/utils/member.ts +++ b/modules/web/src/app/shared/utils/member.ts @@ -31,12 +31,13 @@ export enum Group { export class MemberUtils { static getGroupInProject(member: Member, projectID: string): string { - if (!member || !member.projects) { - return ''; - } + if (!member?.projects) return ''; + + const priority = [Group.Owner, Group.ProjectManager, Group.Editor, Group.Viewer]; + + const groups = member.projects.filter(p => p.id === projectID).map(p => p.group); - const project = member.projects.find(memberProject => memberProject.id === projectID); - return project ? project.group : ''; + return priority.find(role => groups.includes(role)) || ''; } static getGroupDisplayName(groupInternalName: string): string { From 10efe923bdd61a13d664e63139d516a32eb1a67d Mon Sep 17 00:00:00 2001 From: Kubermatic Bot <41968677+kubermatic-bot@users.noreply.github.com> Date: Mon, 14 Apr 2025 14:30:18 +0200 Subject: [PATCH 08/42] [release/v2.27] Add Input restrictions and escape values to avoid rendering as HTML in dialogs (#7285) * Escape value to convert any HTML characters to their corresponding HTML entities * Add pattern validator to restrict certain special characters from being input * Remove security trust bypass for addon and application logo data --------- Co-authored-by: Waseem Abbas --- .../backup/details/automatic-backup/component.ts | 5 +++-- .../list/automatic-backup/add-dialog/component.ts | 3 ++- .../automatic-backup/add-dialog/template.html | 3 +++ .../app/backup/list/automatic-backup/component.ts | 3 ++- .../web/src/app/backup/list/restore/component.ts | 3 ++- modules/web/src/app/cluster-template/component.ts | 2 +- .../src/app/cluster/details/cluster/component.ts | 2 +- .../details/cluster/constraints/component.ts | 2 +- .../constraints/constraint-dialog/component.ts | 2 ++ .../constraints/constraint-dialog/template.html | 3 +++ .../details/cluster/edit-sshkeys/component.ts | 4 ++-- .../cluster/gatekeeper-config/component.ts | 2 +- .../cluster/mla/alertmanager-config/component.ts | 3 ++- .../details/cluster/mla/rule-groups/component.ts | 2 +- .../rule-groups/rule-group-dialog/component.ts | 2 +- .../details/cluster/node-list/component.ts | 2 +- .../cluster/rbac/add-binding-dialog/component.ts | 7 ++++--- .../cluster/rbac/add-binding-dialog/template.html | 6 ++++++ .../app/cluster/details/cluster/rbac/component.ts | 15 +++++++-------- .../component.ts | 5 +++-- modules/web/src/app/core/services/application.ts | 8 +++----- .../web/src/app/core/services/external-cluster.ts | 3 ++- .../core/services/external-machine-deployment.ts | 3 ++- modules/web/src/app/core/services/node.ts | 5 +++-- .../allowed-registry-dialog/component.ts | 2 ++ .../allowed-registry-dialog/template.html | 3 +++ .../enterprise/allowed-registries/component.ts | 2 +- .../group/add-group-dialog/component.ts | 3 ++- .../group/add-group-dialog/template.html | 3 +++ .../src/app/dynamic/enterprise/group/component.ts | 2 +- .../metering/legacy-reports/list/component.ts | 3 ++- .../metering/schedule-config/component.ts | 3 ++- .../schedule-config/report-list/component.ts | 3 ++- .../app/dynamic/enterprise/quotas/component.ts | 2 +- modules/web/src/app/member/component.ts | 2 +- modules/web/src/app/serviceaccount/component.ts | 2 +- .../app/serviceaccount/create-dialog/component.ts | 3 ++- .../serviceaccount/create-dialog/template.html | 3 +++ .../app/serviceaccount/edit-dialog/component.ts | 3 ++- .../app/serviceaccount/edit-dialog/template.html | 3 +++ .../web/src/app/serviceaccount/token/component.ts | 3 ++- .../src/app/settings/admin/admins/component.ts | 3 ++- .../bucket-settings/destinations/component.ts | 3 ++- .../admin/dynamic-datacenters/component.ts | 2 +- .../datacenter-data-dialog/component.ts | 6 +++++- .../datacenter-data-dialog/template.html | 3 +++ .../admin/opa/constraint-templates/component.ts | 2 +- .../admin/opa/default-constraints/component.ts | 2 +- .../default-constraint-dialog/component.ts | 3 ++- .../default-constraint-dialog/template.html | 3 +++ .../src/app/settings/admin/presets/component.ts | 8 ++++---- .../app/settings/admin/rule-groups/component.ts | 2 +- .../components/add-ssh-key-dialog/component.ts | 3 ++- .../components/add-ssh-key-dialog/template.html | 3 +++ .../app/shared/components/addon-list/component.ts | 8 +++----- .../addon-list/edit-addon-dialog/component.ts | 6 ++---- .../addon-list/install-addon-dialog/component.ts | 9 ++++----- .../components/application-list/component.ts | 4 ++-- modules/web/src/app/shared/entity/addon.ts | 1 - modules/web/src/app/shared/entity/application.ts | 1 - modules/web/src/app/shared/validators/others.ts | 3 +++ modules/web/src/app/sshkey/component.ts | 2 +- .../web/src/app/wizard/step/cluster/component.ts | 2 ++ .../web/src/app/wizard/step/cluster/template.html | 3 +++ 64 files changed, 141 insertions(+), 81 deletions(-) diff --git a/modules/web/src/app/backup/details/automatic-backup/component.ts b/modules/web/src/app/backup/details/automatic-backup/component.ts index 8eada5a66c..d3a4aafa84 100644 --- a/modules/web/src/app/backup/details/automatic-backup/component.ts +++ b/modules/web/src/app/backup/details/automatic-backup/component.ts @@ -15,7 +15,7 @@ import {Component, OnDestroy, OnInit} from '@angular/core'; import {MatDialog, MatDialogConfig} from '@angular/material/dialog'; import {ActivatedRoute, Router} from '@angular/router'; -import {HealthStatus, getBackupHealthStatus} from '@app/shared/utils/health-status'; +import {getBackupHealthStatus, HealthStatus} from '@app/shared/utils/health-status'; import {BackupService} from '@core/services/backup'; import {ProjectService} from '@core/services/project'; import {UserService} from '@core/services/user'; @@ -26,6 +26,7 @@ import {Member} from '@shared/entity/member'; import {Project} from '@shared/entity/project'; import {GroupConfig} from '@shared/model/Config'; import {MemberUtils, Permission} from '@shared/utils/member'; +import _ from 'lodash'; import {Subject} from 'rxjs'; import {filter, map, switchMap, take, takeUntil} from 'rxjs/operators'; @@ -103,7 +104,7 @@ export class AutomaticBackupDetailsComponent implements OnInit, OnDestroy { const config: MatDialogConfig = { data: { title: 'Delete Automatic Backup', - message: `Delete ${backup.name} automatic backup of ${this.selectedProject.name} project and all its associated backups permanently?`, + message: `Delete ${_.escape(backup.name)} automatic backup of ${_.escape(this.selectedProject.name)} project and all its associated backups permanently?`, confirmLabel: 'Delete', } as ConfirmationDialogConfig, }; diff --git a/modules/web/src/app/backup/list/automatic-backup/add-dialog/component.ts b/modules/web/src/app/backup/list/automatic-backup/add-dialog/component.ts index f41ceb9247..1252f0e844 100644 --- a/modules/web/src/app/backup/list/automatic-backup/add-dialog/component.ts +++ b/modules/web/src/app/backup/list/automatic-backup/add-dialog/component.ts @@ -23,6 +23,7 @@ import {NotificationService} from '@core/services/notification'; import {UserService} from '@core/services/user'; import {EtcdBackupConfig, EtcdBackupConfigSpec} from '@shared/entity/backup'; import {Cluster} from '@shared/entity/cluster'; +import {NON_SPECIAL_CHARACTERS_PATTERN_VALIDATOR} from '@shared/validators/others'; import {EMPTY, iif, Observable, Subject} from 'rxjs'; import {switchMap, take, takeUntil, tap} from 'rxjs/operators'; @@ -121,7 +122,7 @@ export class AddAutomaticBackupDialogComponent implements OnInit, OnDestroy { ngOnInit(): void { this.form = this._builder.group({ [Controls.Cluster]: this._builder.control('', Validators.required), - [Controls.Name]: this._builder.control('', Validators.required), + [Controls.Name]: this._builder.control('', [Validators.required, NON_SPECIAL_CHARACTERS_PATTERN_VALIDATOR]), [Controls.Destination]: this._builder.control('', Validators.required), [Controls.Group]: this._builder.control(DefaultSchuleOption.Daily, Validators.required), [Controls.Schedule]: this._builder.control(''), diff --git a/modules/web/src/app/backup/list/automatic-backup/add-dialog/template.html b/modules/web/src/app/backup/list/automatic-backup/add-dialog/template.html index 59bf7cc44c..e1c22f3fa8 100644 --- a/modules/web/src/app/backup/list/automatic-backup/add-dialog/template.html +++ b/modules/web/src/app/backup/list/automatic-backup/add-dialog/template.html @@ -45,6 +45,9 @@ Required + + Name cannot contain special characters like | " < > { } [ ] ` \ ' ; & +
${backup.name} automatic backup of ${this._selectedProject.name} project and all its associated backups permanently?`, + message: `Delete ${_.escape(backup.name)} automatic backup of ${_.escape(this._selectedProject.name)} project and all its associated backups permanently?`, confirmLabel: 'Delete', } as ConfirmationDialogConfig, }; diff --git a/modules/web/src/app/backup/list/restore/component.ts b/modules/web/src/app/backup/list/restore/component.ts index d0aac30f69..190b66c8d8 100644 --- a/modules/web/src/app/backup/list/restore/component.ts +++ b/modules/web/src/app/backup/list/restore/component.ts @@ -27,6 +27,7 @@ import {Member} from '@shared/entity/member'; import {Project} from '@shared/entity/project'; import {GroupConfig} from '@shared/model/Config'; import {MemberUtils, Permission} from '@shared/utils/member'; +import _ from 'lodash'; import {Subject} from 'rxjs'; import {filter, switchMap, take, takeUntil, tap} from 'rxjs/operators'; @@ -111,7 +112,7 @@ export class RestoreListComponent implements OnInit, OnDestroy { const config: MatDialogConfig = { data: { title: 'Delete Restore Object', - message: `Delete ${restore.name} restore object permanently?`, + message: `Delete ${_.escape(restore.name)} restore object permanently?`, confirmLabel: 'Delete', } as ConfirmationDialogConfig, }; diff --git a/modules/web/src/app/cluster-template/component.ts b/modules/web/src/app/cluster-template/component.ts index 33719e3ab4..7f771aff6a 100644 --- a/modules/web/src/app/cluster-template/component.ts +++ b/modules/web/src/app/cluster-template/component.ts @@ -229,7 +229,7 @@ export class ClusterTemplateComponent implements OnInit, OnChanges, OnDestroy { const dialogConfig: MatDialogConfig = { data: { title: 'Delete Cluster Template', - message: `Delete ${template.name} cluster template permanently? The clusters created using this template will not be deleted.`, + message: `Delete ${_.escape(template.name)} cluster template permanently? The clusters created using this template will not be deleted.`, confirmLabel: 'Delete', }, }; diff --git a/modules/web/src/app/cluster/details/cluster/component.ts b/modules/web/src/app/cluster/details/cluster/component.ts index 3bef571afe..de748e2067 100644 --- a/modules/web/src/app/cluster/details/cluster/component.ts +++ b/modules/web/src/app/cluster/details/cluster/component.ts @@ -436,7 +436,7 @@ export class ClusterDetailsComponent implements OnInit, OnDestroy { const dialogConfig: MatDialogConfig = { data: { title: 'External CCM Migration', - message: `Start external CCM migration procedure of ${this.cluster.name} cluster?`, + message: `Start external CCM migration procedure of ${_.escape(this.cluster.name)} cluster?`, confirmLabel: 'Start', }, }; diff --git a/modules/web/src/app/cluster/details/cluster/constraints/component.ts b/modules/web/src/app/cluster/details/cluster/constraints/component.ts index c3d892106c..40b6726f8f 100644 --- a/modules/web/src/app/cluster/details/cluster/constraints/component.ts +++ b/modules/web/src/app/cluster/details/cluster/constraints/component.ts @@ -172,7 +172,7 @@ export class ConstraintsComponent implements OnInit, OnChanges, OnDestroy { const dialogConfig: MatDialogConfig = { data: { title: 'Delete Constraint', - message: `Delete ${constraint.name} OPA constraint of ${this.cluster.name} cluster permanently?`, + message: `Delete ${_.escape(constraint.name)} OPA constraint of ${_.escape(this.cluster.name)} cluster permanently?`, confirmLabel: 'Delete', }, }; diff --git a/modules/web/src/app/cluster/details/cluster/constraints/constraint-dialog/component.ts b/modules/web/src/app/cluster/details/cluster/constraints/constraint-dialog/component.ts index e67287dc5a..20174ce452 100644 --- a/modules/web/src/app/cluster/details/cluster/constraints/constraint-dialog/component.ts +++ b/modules/web/src/app/cluster/details/cluster/constraints/constraint-dialog/component.ts @@ -20,6 +20,7 @@ import {NotificationService} from '@core/services/notification'; import {Cluster} from '@shared/entity/cluster'; import {Constraint, ConstraintTemplate, ConstraintSpec} from '@shared/entity/opa'; import {DialogActionMode} from '@shared/types/common'; +import {NON_SPECIAL_CHARACTERS_PATTERN_VALIDATOR} from '@shared/validators/others'; import * as y from 'js-yaml'; import _ from 'lodash'; import {Observable, Subject} from 'rxjs'; @@ -67,6 +68,7 @@ export class ConstraintDialog implements OnInit, OnDestroy { this.form = this._builder.group({ [Controls.Name]: this._builder.control(this.data.mode === this.Mode.Edit ? this.data.constraint.name : '', [ Validators.required, + NON_SPECIAL_CHARACTERS_PATTERN_VALIDATOR, ]), [Controls.ConstraintTemplate]: this._builder.control( { diff --git a/modules/web/src/app/cluster/details/cluster/constraints/constraint-dialog/template.html b/modules/web/src/app/cluster/details/cluster/constraints/constraint-dialog/template.html index ca140dfa3f..0ce0b600c4 100644 --- a/modules/web/src/app/cluster/details/cluster/constraints/constraint-dialog/template.html +++ b/modules/web/src/app/cluster/details/cluster/constraints/constraint-dialog/template.html @@ -33,6 +33,9 @@ Required + + Name cannot contain special characters like | " < > { } [ ] ` \ ' ; & + diff --git a/modules/web/src/app/cluster/details/cluster/edit-sshkeys/component.ts b/modules/web/src/app/cluster/details/cluster/edit-sshkeys/component.ts index 8c359a00ab..7eb77fe397 100644 --- a/modules/web/src/app/cluster/details/cluster/edit-sshkeys/component.ts +++ b/modules/web/src/app/cluster/details/cluster/edit-sshkeys/component.ts @@ -125,8 +125,8 @@ export class EditSSHKeysComponent implements OnInit, OnDestroy { const dialogConfig: MatDialogConfig = { data: { title: 'Remove SSH Key', - message: `Remove ${sshKey.name} - SSH key from ${this.cluster.name} cluster?`, + message: `Remove ${_.escape(sshKey.name)} + SSH key from ${_.escape(this.cluster.name)} cluster?`, confirmLabel: 'Remove', }, }; diff --git a/modules/web/src/app/cluster/details/cluster/gatekeeper-config/component.ts b/modules/web/src/app/cluster/details/cluster/gatekeeper-config/component.ts index b5241fc66c..4a52bf967c 100644 --- a/modules/web/src/app/cluster/details/cluster/gatekeeper-config/component.ts +++ b/modules/web/src/app/cluster/details/cluster/gatekeeper-config/component.ts @@ -119,7 +119,7 @@ export class GatekeeperConfigComponent implements OnChanges, OnDestroy { const dialogConfig: MatDialogConfig = { data: { title: 'Delete Gatekeeper Config', - message: `Delete OPA gatekeeper config of ${this.cluster.name} cluster permanently?`, + message: `Delete OPA gatekeeper config of ${_.escape(this.cluster.name)} cluster permanently?`, confirmLabel: 'Delete', }, }; diff --git a/modules/web/src/app/cluster/details/cluster/mla/alertmanager-config/component.ts b/modules/web/src/app/cluster/details/cluster/mla/alertmanager-config/component.ts index ac4f511027..883033dfde 100644 --- a/modules/web/src/app/cluster/details/cluster/mla/alertmanager-config/component.ts +++ b/modules/web/src/app/cluster/details/cluster/mla/alertmanager-config/component.ts @@ -25,6 +25,7 @@ import {Cluster} from '@shared/entity/cluster'; import {SeedSettings} from '@shared/entity/datacenter'; import {AlertmanagerConfig} from '@shared/entity/mla'; import {AdminSettings} from '@shared/entity/settings'; +import _ from 'lodash'; import {Subject} from 'rxjs'; import {filter, switchMap, take, takeUntil, tap} from 'rxjs/operators'; import {AlertmanagerConfigDialog} from './alertmanager-config-dialog/component'; @@ -195,7 +196,7 @@ export class AlertmanagerConfigComponent implements OnInit, OnDestroy { const dialogConfig: MatDialogConfig = { data: { title: 'Reset Alertmanager Config', - message: `Reset Alertmanager config of ${this.cluster.name} cluster to default?`, + message: `Reset Alertmanager config of ${_.escape(this.cluster.name)} cluster to default?`, confirmLabel: 'Reset', }, }; diff --git a/modules/web/src/app/cluster/details/cluster/mla/rule-groups/component.ts b/modules/web/src/app/cluster/details/cluster/mla/rule-groups/component.ts index 1d14157066..e4246f01da 100644 --- a/modules/web/src/app/cluster/details/cluster/mla/rule-groups/component.ts +++ b/modules/web/src/app/cluster/details/cluster/mla/rule-groups/component.ts @@ -138,7 +138,7 @@ export class RuleGroupsComponent implements OnInit, OnChanges, OnDestroy { const dialogConfig: MatDialogConfig = { data: { title: 'Delete Rule Group', - message: `Delete ${ruleGroup.name} recording and alerting rule group of ${this.cluster.name} cluster permanently?`, + message: `Delete ${_.escape(ruleGroup.name)} recording and alerting rule group of ${_.escape(this.cluster.name)} cluster permanently?`, confirmLabel: 'Delete', }, }; diff --git a/modules/web/src/app/cluster/details/cluster/mla/rule-groups/rule-group-dialog/component.ts b/modules/web/src/app/cluster/details/cluster/mla/rule-groups/rule-group-dialog/component.ts index 26884134de..c1622a4c1c 100644 --- a/modules/web/src/app/cluster/details/cluster/mla/rule-groups/rule-group-dialog/component.ts +++ b/modules/web/src/app/cluster/details/cluster/mla/rule-groups/rule-group-dialog/component.ts @@ -94,7 +94,7 @@ export class RuleGroupDialog implements OnInit, OnDestroy { case Mode.Add: return 'Create recording and alerting rule group'; case Mode.Edit: - return `Edit ${this.data.ruleGroup.name} recording and alerting rule group of ${this.data.cluster.name} cluster`; + return `Edit ${_.escape(this.data.ruleGroup.name)} recording and alerting rule group of ${_.escape(this.data.cluster.name)} cluster`; } } diff --git a/modules/web/src/app/cluster/details/cluster/node-list/component.ts b/modules/web/src/app/cluster/details/cluster/node-list/component.ts index 19bb9a6e63..228e6b7661 100644 --- a/modules/web/src/app/cluster/details/cluster/node-list/component.ts +++ b/modules/web/src/app/cluster/details/cluster/node-list/component.ts @@ -149,7 +149,7 @@ export class NodeListComponent implements OnInit, OnChanges, OnDestroy { const dialogConfig: MatDialogConfig = { data: { title: 'Delete Node', - message: `Delete ${node.name} node of ${this.mdName} machine deployment of ${this.cluster.name} cluster permanently?`, + message: `Delete ${_.escape(node.name)} node of ${_.escape(this.mdName)} machine deployment of ${_.escape(this.cluster.name)} cluster permanently?`, confirmLabel: 'Delete', }, }; diff --git a/modules/web/src/app/cluster/details/cluster/rbac/add-binding-dialog/component.ts b/modules/web/src/app/cluster/details/cluster/rbac/add-binding-dialog/component.ts index 0fbf76dfac..83f096ef79 100644 --- a/modules/web/src/app/cluster/details/cluster/rbac/add-binding-dialog/component.ts +++ b/modules/web/src/app/cluster/details/cluster/rbac/add-binding-dialog/component.ts @@ -16,6 +16,7 @@ import {Component, Input, OnDestroy, OnInit} from '@angular/core'; import {FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms'; import {MatButtonToggleChange} from '@angular/material/button-toggle'; import {MatDialogRef} from '@angular/material/dialog'; +import {NON_SPECIAL_CHARACTERS_PATTERN_VALIDATOR} from '@shared/validators/others'; import _ from 'lodash'; import {Observable, Subject} from 'rxjs'; import {debounceTime, takeUntil, tap} from 'rxjs/operators'; @@ -83,7 +84,7 @@ export class AddBindingDialogComponent implements OnInit, OnDestroy { ngOnInit(): void { this.form = this._builder.group({ - [Controls.Email]: new FormControl('', [Validators.required]), + [Controls.Email]: new FormControl('', [Validators.required, NON_SPECIAL_CHARACTERS_PATTERN_VALIDATOR]), [Controls.Group]: new FormControl(''), [Controls.Role]: new FormControl('', [Validators.required]), [Controls.Namespace]: new FormControl(''), @@ -138,9 +139,9 @@ export class AddBindingDialogComponent implements OnInit, OnDestroy { this.form.get(Controls.Email).clearValidators(); this.form.get(Controls.Group).clearValidators(); if (this.subjectType === Kind.User) { - this.form.get(Controls.Email).setValidators([Validators.required]); + this.form.get(Controls.Email).setValidators([Validators.required, NON_SPECIAL_CHARACTERS_PATTERN_VALIDATOR]); } else { - this.form.get(Controls.Group).setValidators([Validators.required]); + this.form.get(Controls.Group).setValidators([Validators.required, NON_SPECIAL_CHARACTERS_PATTERN_VALIDATOR]); } this.form.get(Controls.Email).updateValueAndValidity(); this.form.get(Controls.Group).updateValueAndValidity(); diff --git a/modules/web/src/app/cluster/details/cluster/rbac/add-binding-dialog/template.html b/modules/web/src/app/cluster/details/cluster/rbac/add-binding-dialog/template.html index 64582583cc..233e45edbc 100644 --- a/modules/web/src/app/cluster/details/cluster/rbac/add-binding-dialog/template.html +++ b/modules/web/src/app/cluster/details/cluster/rbac/add-binding-dialog/template.html @@ -43,6 +43,9 @@ Required + + User Email cannot contain special characters like | " < > { } [ ] ` \ ' ; & + Required + + Group cannot contain special characters like | " < > { } [ ] ` \ ' ; & + diff --git a/modules/web/src/app/cluster/details/cluster/rbac/component.ts b/modules/web/src/app/cluster/details/cluster/rbac/component.ts index 9a3b37edff..c5e74fb00f 100644 --- a/modules/web/src/app/cluster/details/cluster/rbac/component.ts +++ b/modules/web/src/app/cluster/details/cluster/rbac/component.ts @@ -13,19 +13,20 @@ // limitations under the License. import {Component, Input, OnDestroy} from '@angular/core'; +import {FormControl} from '@angular/forms'; import {MatDialog, MatDialogConfig} from '@angular/material/dialog'; +import {ClusterServiceAccountService} from '@core/services/cluster-service-account'; import {NotificationService} from '@core/services/notification'; import {RBACService} from '@core/services/rbac'; import {ConfirmationDialogComponent} from '@shared/components/confirmation-dialog/component'; import {Cluster} from '@shared/entity/cluster'; import {ClusterBinding, ClusterServiceAccount, Kind, SimpleClusterBinding} from '@shared/entity/rbac'; +import {iif, Subject} from 'rxjs'; import {filter, switchMap, take} from 'rxjs/operators'; import {AddBindingDialogComponent} from './add-binding-dialog/component'; -import {iif, Subject} from 'rxjs'; -import {FormControl} from '@angular/forms'; -import {AddServiceAccountDialogComponent} from './add-service-account-dialog/component'; import {AddServiceAccountBindingDialogComponent} from './add-service-account-binding-dialog/component'; -import {ClusterServiceAccountService} from '@core/services/cluster-service-account'; +import {AddServiceAccountDialogComponent} from './add-service-account-dialog/component'; +import _ from 'lodash'; @Component({ selector: 'km-rbac', @@ -101,7 +102,7 @@ export class RBACComponent implements OnDestroy { const dialogConfig: MatDialogConfig = { data: { title: 'Delete Service Account', - message: `Delete service account ${element.name} of ${this.cluster.name} cluster permanently?`, + message: `Delete service account ${_.escape(element.name)} of ${_.escape(this.cluster.name)} cluster permanently?`, confirmLabel: 'Delete', }, }; @@ -127,9 +128,7 @@ export class RBACComponent implements OnDestroy { const dialogConfig: MatDialogConfig = { data: { title: 'Delete Binding', - message: `Delete binding for ${element.kind.toLowerCase()} ${element.name} of ${ - this.cluster.name - } cluster permanently?`, + message: `Delete binding for ${element.kind.toLowerCase()} ${_.escape(element.name)} of ${_.escape(this.cluster.name)} cluster permanently?`, confirmLabel: 'Delete', }, }; diff --git a/modules/web/src/app/cluster/details/external-cluster/external-cluster-delete-confirmation/component.ts b/modules/web/src/app/cluster/details/external-cluster/external-cluster-delete-confirmation/component.ts index 0477e1bfa1..24dab3a995 100644 --- a/modules/web/src/app/cluster/details/external-cluster/external-cluster-delete-confirmation/component.ts +++ b/modules/web/src/app/cluster/details/external-cluster/external-cluster-delete-confirmation/component.ts @@ -17,6 +17,7 @@ import {MatDialogRef} from '@angular/material/dialog'; import {GoogleAnalyticsService} from '@app/google-analytics.service'; import {NotificationService} from '@core/services/notification'; import {AdminSettings} from '@shared/entity/settings'; +import _ from 'lodash'; import {ClipboardService} from 'ngx-clipboard'; import {Observable, Subject} from 'rxjs'; import {finalize, take} from 'rxjs/operators'; @@ -71,9 +72,9 @@ export class ExternalClusterDeleteConfirmationComponent implements OnInit, OnDes if (machineDeployments.length > 0) { const nodegroupNames = machineDeployments.map((md: ExternalMachineDeployment) => md.name).join(', '); if (this.clusterProvider === ExternalClusterProvider.EKS) { - this.warningMessage = `Cluster has nodegroups attached (${nodegroupNames}). Please delete nodegroups before deleting the cluster.`; + this.warningMessage = `Cluster has nodegroups attached (${_.escape(nodegroupNames)}). Please delete nodegroups before deleting the cluster.`; } else { - this.warningMessage = `Cluster has nodegroups attached (${nodegroupNames}).`; + this.warningMessage = `Cluster has nodegroups attached (${_.escape(nodegroupNames)}).`; } } else { this.warningMessage = ''; diff --git a/modules/web/src/app/core/services/application.ts b/modules/web/src/app/core/services/application.ts index f747ff002b..18013400ff 100644 --- a/modules/web/src/app/core/services/application.ts +++ b/modules/web/src/app/core/services/application.ts @@ -14,7 +14,6 @@ import {HttpClient} from '@angular/common/http'; import {EventEmitter, Injectable} from '@angular/core'; -import {DomSanitizer} from '@angular/platform-browser'; import {AppConfigService} from '@app/config.service'; import {environment} from '@environments/environment'; import { @@ -39,8 +38,7 @@ export class ApplicationService { constructor( private readonly _appConfigService: AppConfigService, - private readonly _httpClient: HttpClient, - private readonly _domSanitizer: DomSanitizer + private readonly _httpClient: HttpClient ) {} get applications(): Application[] { @@ -74,7 +72,7 @@ export class ApplicationService { this._applicationDefinitions = appDefs.map(appDef => { const logoData = getApplicationLogoData(appDef); if (logoData) { - appDef.spec.logoData = this._domSanitizer.bypassSecurityTrustUrl(logoData); + appDef.spec.logoData = logoData; } const oldAppDef = this._applicationDefinitions.find(item => item.name === appDef.name); if (oldAppDef) { @@ -99,7 +97,7 @@ export class ApplicationService { tap(appDef => { const logoData = getApplicationLogoData(appDef); if (logoData) { - appDef.spec.logoData = this._domSanitizer.bypassSecurityTrustUrl(logoData); + appDef.spec.logoData = logoData; } this._applicationDefinitions = this._applicationDefinitions.map(item => { if (item.name === name) { diff --git a/modules/web/src/app/core/services/external-cluster.ts b/modules/web/src/app/core/services/external-cluster.ts index c2e75dd5d5..84752221e9 100644 --- a/modules/web/src/app/core/services/external-cluster.ts +++ b/modules/web/src/app/core/services/external-cluster.ts @@ -33,6 +33,7 @@ import {PresetList} from '@shared/entity/preset'; import {AKSCluster, AKSLocation, AKSVMSize, AzureResourceGroup} from '@shared/entity/provider/aks'; import {EKSCluster, EKSClusterRole, EKSSecurityGroup, EKSSubnet, EKSVpc} from '@shared/entity/provider/eks'; import {GKECluster, GKEZone} from '@shared/entity/provider/gke'; +import _ from 'lodash'; import {BehaviorSubject, Observable, of, Subject} from 'rxjs'; import {catchError, filter} from 'rxjs/operators'; @@ -364,7 +365,7 @@ export class ExternalClusterService { const dialogConfig: MatDialogConfig = { data: { title: 'Disconnect Cluster', - message: `Are you sure you want to disconnect ${cluster.name} cluster? This action will not remove the cluster from the cloud provider’s end.`, + message: `Are you sure you want to disconnect ${_.escape(cluster.name)} cluster? This action will not remove the cluster from the cloud provider’s end.`, confirmLabel: 'Disconnect', throttleButton: true, observable: this.deleteExternalCluster(projectID, cluster.id, DeleteExternalClusterAction.Disconnect), diff --git a/modules/web/src/app/core/services/external-machine-deployment.ts b/modules/web/src/app/core/services/external-machine-deployment.ts index 8e655954b5..b474ed6dc4 100644 --- a/modules/web/src/app/core/services/external-machine-deployment.ts +++ b/modules/web/src/app/core/services/external-machine-deployment.ts @@ -14,6 +14,7 @@ import {HttpClient} from '@angular/common/http'; import {Injectable} from '@angular/core'; +import _ from 'lodash'; import {Observable, of} from 'rxjs'; import {catchError, filter, map, mergeMap, switchMap, take} from 'rxjs/operators'; import {MatDialog, MatDialogConfig} from '@angular/material/dialog'; @@ -148,7 +149,7 @@ export class ExternalMachineDeploymentService { const dialogConfig: MatDialogConfig = { data: { title: `Delete ${cluster?.cloud.eks ? 'Node Group' : 'Node Pool'}`, - message: `Delete ${md.name} of ${cluster.name} cluster permanently?`, + message: `Delete ${_.escape(md.name)} of ${_.escape(cluster.name)} cluster permanently?`, confirmLabel: 'Delete', }, }; diff --git a/modules/web/src/app/core/services/node.ts b/modules/web/src/app/core/services/node.ts index b92c3b72e7..44639ad7d8 100644 --- a/modules/web/src/app/core/services/node.ts +++ b/modules/web/src/app/core/services/node.ts @@ -20,6 +20,7 @@ import {ConfirmationDialogComponent} from '@shared/components/confirmation-dialo import {Cluster} from '@shared/entity/cluster'; import {MachineDeployment, MachineDeploymentPatch} from '@shared/entity/machine-deployment'; import {NodeData} from '@shared/model/NodeSpecChange'; +import _ from 'lodash'; import {Observable, of} from 'rxjs'; import {catchError, filter, finalize, mergeMap, switchMap, take} from 'rxjs/operators'; import {MachineDeploymentService} from '@core/services/machine-deployment'; @@ -157,7 +158,7 @@ export class NodeService { const dialogConfig: MatDialogConfig = { data: { title: 'Delete Machine Deployment', - message: `Delete ${md.name} machine deployment of ${cluster.name} cluster permanently?`, + message: `Delete ${_.escape(md.name)} machine deployment of ${_.escape(cluster.name)} cluster permanently?`, confirmLabel: 'Delete', }, }; @@ -201,7 +202,7 @@ export class NodeService { const dialogConfig: MatDialogConfig = { data: { title: 'Restart Machine Deployment', - message: `Perform rolling restart of ${md.name} machine deployment of ${cluster.name} cluster?`, + message: `Perform rolling restart of ${_.escape(md.name)} machine deployment of ${_.escape(cluster.name)} cluster?`, confirmLabel: 'Restart', }, }; diff --git a/modules/web/src/app/dynamic/enterprise/allowed-registries/allowed-registry-dialog/component.ts b/modules/web/src/app/dynamic/enterprise/allowed-registries/allowed-registry-dialog/component.ts index df032ce052..7d2b9b1762 100644 --- a/modules/web/src/app/dynamic/enterprise/allowed-registries/allowed-registry-dialog/component.ts +++ b/modules/web/src/app/dynamic/enterprise/allowed-registries/allowed-registry-dialog/component.ts @@ -23,6 +23,7 @@ import {FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms'; import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; import {NotificationService} from '@core/services/notification'; import {getIconClassForButton} from '@shared/utils/common'; +import {NON_SPECIAL_CHARACTERS_PATTERN_VALIDATOR} from '@shared/validators/others'; import {Observable, Subject} from 'rxjs'; import {take} from 'rxjs/operators'; import {AllowedRegistry} from '../entity'; @@ -65,6 +66,7 @@ export class AllowedRegistryDialog implements OnInit, OnDestroy { this.form = this._builder.group({ [Controls.Name]: new FormControl(this.data.mode === this.Mode.Edit ? this.data.allowedRegistry.name : '', [ Validators.required, + NON_SPECIAL_CHARACTERS_PATTERN_VALIDATOR, ]), [Controls.RegistryPrefix]: new FormControl( this.data.mode === this.Mode.Edit ? this.data.allowedRegistry.spec.registryPrefix : '', diff --git a/modules/web/src/app/dynamic/enterprise/allowed-registries/allowed-registry-dialog/template.html b/modules/web/src/app/dynamic/enterprise/allowed-registries/allowed-registry-dialog/template.html index d8e9ecee97..9e9944807f 100644 --- a/modules/web/src/app/dynamic/enterprise/allowed-registries/allowed-registry-dialog/template.html +++ b/modules/web/src/app/dynamic/enterprise/allowed-registries/allowed-registry-dialog/template.html @@ -39,6 +39,9 @@ Required + + Name cannot contain special characters like | " < > { } [ ] ` \ ' ; & + diff --git a/modules/web/src/app/dynamic/enterprise/allowed-registries/component.ts b/modules/web/src/app/dynamic/enterprise/allowed-registries/component.ts index 8c09762229..0d880fff56 100644 --- a/modules/web/src/app/dynamic/enterprise/allowed-registries/component.ts +++ b/modules/web/src/app/dynamic/enterprise/allowed-registries/component.ts @@ -137,7 +137,7 @@ export class AllowedRegistriesComponent extends DynamicTab implements OnInit, On const dialogConfig: MatDialogConfig = { data: { title: 'Delete Allowed Registry', - message: `Delete ${allowedRegistry.name} allowed registry permanently?`, + message: `Delete ${_.escape(allowedRegistry.name)} allowed registry permanently?`, confirmLabel: 'Delete', }, }; diff --git a/modules/web/src/app/dynamic/enterprise/group/add-group-dialog/component.ts b/modules/web/src/app/dynamic/enterprise/group/add-group-dialog/component.ts index cd261a4022..f2ac8148a5 100644 --- a/modules/web/src/app/dynamic/enterprise/group/add-group-dialog/component.ts +++ b/modules/web/src/app/dynamic/enterprise/group/add-group-dialog/component.ts @@ -23,6 +23,7 @@ import {FormBuilder, FormGroup, Validators} from '@angular/forms'; import {MatDialogRef} from '@angular/material/dialog'; import {NotificationService} from '@core/services/notification'; import {Project} from '@shared/entity/project'; +import {NON_SPECIAL_CHARACTERS_PATTERN_VALIDATOR} from '@shared/validators/others'; import {Observable} from 'rxjs'; import {Group} from '@app/dynamic/enterprise/group/entity'; import {GroupService} from '@app/dynamic/enterprise/group/service'; @@ -51,7 +52,7 @@ export class AddGroupDialogComponent implements OnInit { ngOnInit(): void { this.form = this._builder.group({ - [Controls.Group]: this._builder.control('', Validators.required), + [Controls.Group]: this._builder.control('', [Validators.required, NON_SPECIAL_CHARACTERS_PATTERN_VALIDATOR]), [Controls.Role]: this._builder.control('', Validators.required), }); } diff --git a/modules/web/src/app/dynamic/enterprise/group/add-group-dialog/template.html b/modules/web/src/app/dynamic/enterprise/group/add-group-dialog/template.html index 73d2246309..5975748f8a 100644 --- a/modules/web/src/app/dynamic/enterprise/group/add-group-dialog/template.html +++ b/modules/web/src/app/dynamic/enterprise/group/add-group-dialog/template.html @@ -37,6 +37,9 @@ Required + + Name cannot contain special characters like | " < > { } [ ] ` \ ' ; & + diff --git a/modules/web/src/app/dynamic/enterprise/group/component.ts b/modules/web/src/app/dynamic/enterprise/group/component.ts index 5432f15a8f..e77cc5baf3 100644 --- a/modules/web/src/app/dynamic/enterprise/group/component.ts +++ b/modules/web/src/app/dynamic/enterprise/group/component.ts @@ -131,7 +131,7 @@ export class GroupComponent extends DynamicTab implements OnInit, OnDestroy { const dialogConfig: MatDialogConfig = { data: { title: 'Remove Group', - message: `Remove ${group.group} group from ${this._selectedProject.name} project?`, + message: `Remove ${_.escape(group.group)} group from ${_.escape(this._selectedProject.name)} project?`, confirmLabel: 'Remove', }, }; diff --git a/modules/web/src/app/dynamic/enterprise/metering/legacy-reports/list/component.ts b/modules/web/src/app/dynamic/enterprise/metering/legacy-reports/list/component.ts index 2ac3a328db..b1b8c2b335 100644 --- a/modules/web/src/app/dynamic/enterprise/metering/legacy-reports/list/component.ts +++ b/modules/web/src/app/dynamic/enterprise/metering/legacy-reports/list/component.ts @@ -28,6 +28,7 @@ import {NotificationService} from '@app/core/services/notification'; import {MeteringService} from '@app/dynamic/enterprise/metering/service/metering'; import {ConfirmationDialogComponent} from '@app/shared/components/confirmation-dialog/component'; import {Report} from '@shared/entity/metering'; +import _ from 'lodash'; import {Subject} from 'rxjs'; import {filter, switchMap, take, takeUntil} from 'rxjs/operators'; @@ -95,7 +96,7 @@ export class MeteringLegacyReportListComponent implements OnInit, OnDestroy { const dialogConfig: MatDialogConfig = { data: { title: 'Delete Metering Report', - message: `Do you want to delete ${reportName} report permanently?`, + message: `Do you want to delete ${_.escape(reportName)} report permanently?`, confirmLabel: 'Delete', warning: 'This change is permanent.', }, diff --git a/modules/web/src/app/dynamic/enterprise/metering/schedule-config/component.ts b/modules/web/src/app/dynamic/enterprise/metering/schedule-config/component.ts index 0507fd609a..d7cb435718 100644 --- a/modules/web/src/app/dynamic/enterprise/metering/schedule-config/component.ts +++ b/modules/web/src/app/dynamic/enterprise/metering/schedule-config/component.ts @@ -30,6 +30,7 @@ import {MeteringService} from '@app/dynamic/enterprise/metering/service/metering import {NotificationService} from '@core/services/notification'; import {ConfirmationDialogComponent} from '@shared/components/confirmation-dialog/component'; import {MeteringReportConfiguration} from '@shared/entity/datacenter'; +import _ from 'lodash'; import {filter, switchMap, take} from 'rxjs'; enum Column { @@ -110,7 +111,7 @@ export class MeteringScheduleConfigComponent implements OnInit { const dialogConfig: MatDialogConfig = { data: { title: 'Delete Schedule Configuration', - message: `Delete ${name} schedule permanently?`, + message: `Delete ${_.escape(name)} schedule permanently?`, confirmLabel: 'Delete', warning: 'Deleting this will NOT remove reports related to it.', }, diff --git a/modules/web/src/app/dynamic/enterprise/metering/schedule-config/report-list/component.ts b/modules/web/src/app/dynamic/enterprise/metering/schedule-config/report-list/component.ts index 552a2b4cd1..e6b99bd812 100644 --- a/modules/web/src/app/dynamic/enterprise/metering/schedule-config/report-list/component.ts +++ b/modules/web/src/app/dynamic/enterprise/metering/schedule-config/report-list/component.ts @@ -30,6 +30,7 @@ import {MeteringService} from '@app/dynamic/enterprise/metering/service/metering import {ConfirmationDialogComponent} from '@app/shared/components/confirmation-dialog/component'; import {UserService} from '@core/services/user'; import {Report} from '@shared/entity/metering'; +import _ from 'lodash'; import {Subject} from 'rxjs'; import {filter, switchMap, take, takeUntil} from 'rxjs/operators'; @@ -151,7 +152,7 @@ export class MeteringReportListComponent implements OnInit { const dialogConfig: MatDialogConfig = { data: { title: 'Delete Metering Report', - message: `Do you want to delete ${reportName} report permanently?`, + message: `Do you want to delete ${_.escape(reportName)} report permanently?`, confirmLabel: 'Delete', warning: 'This change is permanent.', }, diff --git a/modules/web/src/app/dynamic/enterprise/quotas/component.ts b/modules/web/src/app/dynamic/enterprise/quotas/component.ts index 065aebc4ad..b7b5b5230e 100644 --- a/modules/web/src/app/dynamic/enterprise/quotas/component.ts +++ b/modules/web/src/app/dynamic/enterprise/quotas/component.ts @@ -187,7 +187,7 @@ export class QuotasComponent implements OnInit { const config = { data: { title: 'Delete Quota', - message: `Delete quota for ${subjectHumanReadableName ?? name}?`, + message: `Delete quota for ${_.escape(subjectHumanReadableName ?? name)}?`, confirmLabel: 'Delete', }, } as MatDialogConfig; diff --git a/modules/web/src/app/member/component.ts b/modules/web/src/app/member/component.ts index fd8a289045..ac4b73e4f3 100644 --- a/modules/web/src/app/member/component.ts +++ b/modules/web/src/app/member/component.ts @@ -202,7 +202,7 @@ export class MemberComponent implements OnInit, OnChanges, OnDestroy, AfterViewI const dialogConfig: MatDialogConfig = { data: { title: 'Remove Member', - message: `Remove ${member.name} from ${this._selectedProject.name} project?`, + message: `Remove ${_.escape(member.name)} from ${_.escape(this._selectedProject.name)} project?`, confirmLabel: 'Remove', }, }; diff --git a/modules/web/src/app/serviceaccount/component.ts b/modules/web/src/app/serviceaccount/component.ts index 2c4cd1631a..419c1dd7eb 100644 --- a/modules/web/src/app/serviceaccount/component.ts +++ b/modules/web/src/app/serviceaccount/component.ts @@ -231,7 +231,7 @@ export class ServiceAccountComponent implements OnInit, OnChanges, OnDestroy { const dialogConfig: MatDialogConfig = { data: { title: 'Delete Service Account', - message: `Delete ${serviceAccount.name} service account of ${this._selectedProject.name} project permanently?`, + message: `Delete ${_.escape(serviceAccount.name)} service account of ${_.escape(this._selectedProject.name)} project permanently?`, confirmLabel: 'Delete', }, }; diff --git a/modules/web/src/app/serviceaccount/create-dialog/component.ts b/modules/web/src/app/serviceaccount/create-dialog/component.ts index 13631a8079..1f0ff31665 100644 --- a/modules/web/src/app/serviceaccount/create-dialog/component.ts +++ b/modules/web/src/app/serviceaccount/create-dialog/component.ts @@ -18,6 +18,7 @@ import {MatDialogRef} from '@angular/material/dialog'; import {NotificationService} from '@core/services/notification'; import {Project} from '@shared/entity/project'; import {ServiceAccount} from '@shared/entity/service-account'; +import {NON_SPECIAL_CHARACTERS_PATTERN_VALIDATOR} from '@shared/validators/others'; import {take} from 'rxjs/operators'; import {ServiceAccountService} from '@core/services/service-account'; import {Observable} from 'rxjs'; @@ -38,7 +39,7 @@ export class CreateServiceAccountDialogComponent implements OnInit { ngOnInit(): void { this.form = new FormGroup({ - name: new FormControl('', [Validators.required]), + name: new FormControl('', [Validators.required, NON_SPECIAL_CHARACTERS_PATTERN_VALIDATOR]), group: new FormControl('editors', [Validators.required]), }); } diff --git a/modules/web/src/app/serviceaccount/create-dialog/template.html b/modules/web/src/app/serviceaccount/create-dialog/template.html index 9ebc80ddaa..52742e2246 100644 --- a/modules/web/src/app/serviceaccount/create-dialog/template.html +++ b/modules/web/src/app/serviceaccount/create-dialog/template.html @@ -30,6 +30,9 @@ Required + + Name cannot contain special characters like | " < > { } [ ] ` \ ' ; & + Required + + Name cannot contain special characters like | " < > { } [ ] ` \ ' ; & + diff --git a/modules/web/src/app/serviceaccount/token/component.ts b/modules/web/src/app/serviceaccount/token/component.ts index 51b75c3b2d..3b4592d678 100644 --- a/modules/web/src/app/serviceaccount/token/component.ts +++ b/modules/web/src/app/serviceaccount/token/component.ts @@ -30,6 +30,7 @@ import {ConfirmationDialogComponent} from '@shared/components/confirmation-dialo import {Project} from '@shared/entity/project'; import {ServiceAccount, ServiceAccountToken} from '@shared/entity/service-account'; import {GroupConfig} from '@shared/model/Config'; +import _ from 'lodash'; import {filter, switchMap, take} from 'rxjs/operators'; @Component({ @@ -128,7 +129,7 @@ export class ServiceAccountTokenComponent implements OnInit { const dialogConfig: MatDialogConfig = { data: { title: 'Delete Token', - message: `Delete ${token.name} token of ${this.serviceaccount.name} service account of ${this._selectedProject.name} project permanently?`, + message: `Delete ${_.escape(token.name)} token of ${_.escape(this.serviceaccount.name)} service account of ${this._selectedProject.name} project permanently?`, confirmLabel: 'Delete', }, }; diff --git a/modules/web/src/app/settings/admin/admins/component.ts b/modules/web/src/app/settings/admin/admins/component.ts index 897507623e..10f15d6053 100644 --- a/modules/web/src/app/settings/admin/admins/component.ts +++ b/modules/web/src/app/settings/admin/admins/component.ts @@ -22,6 +22,7 @@ import {SettingsService} from '@core/services/settings'; import {UserService} from '@core/services/user'; import {ConfirmationDialogComponent} from '@shared/components/confirmation-dialog/component'; import {Admin, Member} from '@shared/entity/member'; +import _ from 'lodash'; import {Subject} from 'rxjs'; import {filter, take, takeUntil} from 'rxjs/operators'; import {AddAdminDialogComponent} from './add-admin-dialog/component'; @@ -84,7 +85,7 @@ export class AdminsComponent implements OnInit, OnChanges { const dialogConfig: MatDialogConfig = { data: { title: 'Remove Administrator', - message: `Remove ${admin.name} from administrators?`, + message: `Remove ${_.escape(admin.name)} from administrators?`, confirmLabel: 'Remove', }, }; diff --git a/modules/web/src/app/settings/admin/bucket-settings/destinations/component.ts b/modules/web/src/app/settings/admin/bucket-settings/destinations/component.ts index 1bbf22248f..547b29bd25 100644 --- a/modules/web/src/app/settings/admin/bucket-settings/destinations/component.ts +++ b/modules/web/src/app/settings/admin/bucket-settings/destinations/component.ts @@ -22,6 +22,7 @@ import {DatacenterService} from '@core/services/datacenter'; import {NotificationService} from '@core/services/notification'; import {ConfirmationDialogComponent, ConfirmationDialogConfig} from '@shared/components/confirmation-dialog/component'; import {AdminSeed, BackupDestination} from '@shared/entity/datacenter'; +import _ from 'lodash'; import {filter, switchMap, take} from 'rxjs/operators'; import {DestinationDialog, Mode} from './destination-dialog/component'; import {EditCredentialsDialog} from './edit-credentials-dialog/component'; @@ -118,7 +119,7 @@ export class DestinationsComponent implements OnInit { const dialogConfig: MatDialogConfig = { data: { title: 'Delete Destination', - message: `Delete ${destination.destinationName} destination permanently?`, + message: `Delete ${_.escape(destination.destinationName)} destination permanently?`, warning: 'Associated backups and snapshots will not be usable after deleting this destination.', confirmLabel: 'Delete Destination', } as ConfirmationDialogConfig, diff --git a/modules/web/src/app/settings/admin/dynamic-datacenters/component.ts b/modules/web/src/app/settings/admin/dynamic-datacenters/component.ts index ab1fdeac7c..42c374dfae 100644 --- a/modules/web/src/app/settings/admin/dynamic-datacenters/component.ts +++ b/modules/web/src/app/settings/admin/dynamic-datacenters/component.ts @@ -173,7 +173,7 @@ export class DynamicDatacentersComponent implements OnInit, OnDestroy, OnChanges const dialogConfig: MatDialogConfig = { data: { title: 'Delete Datacenter', - message: `Delete ${datacenter.metadata.name} datacenter permanently?`, + message: `Delete ${_.escape(datacenter.metadata.name)} datacenter permanently?`, confirmLabel: 'Delete', }, }; diff --git a/modules/web/src/app/settings/admin/dynamic-datacenters/datacenter-data-dialog/component.ts b/modules/web/src/app/settings/admin/dynamic-datacenters/datacenter-data-dialog/component.ts index 2e400ef751..58f81dccee 100644 --- a/modules/web/src/app/settings/admin/dynamic-datacenters/datacenter-data-dialog/component.ts +++ b/modules/web/src/app/settings/admin/dynamic-datacenters/datacenter-data-dialog/component.ts @@ -22,6 +22,7 @@ import {CreateDatacenterModel, Datacenter, MachineFlavorFilter} from '@shared/en import {DialogActionMode} from '@shared/types/common'; import {INTERNAL_NODE_PROVIDERS} from '@shared/model/NodeProviderConstants'; import {getIconClassForButton} from '@shared/utils/common'; +import {NON_SPECIAL_CHARACTERS_PATTERN_VALIDATOR} from '@shared/validators/others'; import * as countryCodeLookup from 'country-code-lookup'; import * as y from 'js-yaml'; import _ from 'lodash'; @@ -101,7 +102,10 @@ export class DatacenterDataDialogComponent implements OnInit, OnDestroy { } this.form = new FormGroup({ - name: new FormControl(this.data.isEditing ? this.data.datacenter.metadata.name : '', [Validators.required]), + name: new FormControl(this.data.isEditing ? this.data.datacenter.metadata.name : '', [ + Validators.required, + NON_SPECIAL_CHARACTERS_PATTERN_VALIDATOR, + ]), provider: new FormControl( {value: this.data.isEditing ? this.data.datacenter.spec.provider : '', disabled: this.data.isEditing}, [Validators.required] diff --git a/modules/web/src/app/settings/admin/dynamic-datacenters/datacenter-data-dialog/template.html b/modules/web/src/app/settings/admin/dynamic-datacenters/datacenter-data-dialog/template.html index 2392a66cc0..44cc65d3a8 100644 --- a/modules/web/src/app/settings/admin/dynamic-datacenters/datacenter-data-dialog/template.html +++ b/modules/web/src/app/settings/admin/dynamic-datacenters/datacenter-data-dialog/template.html @@ -34,6 +34,9 @@ Required + + Name cannot contain special characters like | " < > { } [ ] ` \ ' ; & + diff --git a/modules/web/src/app/settings/admin/opa/constraint-templates/component.ts b/modules/web/src/app/settings/admin/opa/constraint-templates/component.ts index 8ec736f40b..30fd02e5a1 100644 --- a/modules/web/src/app/settings/admin/opa/constraint-templates/component.ts +++ b/modules/web/src/app/settings/admin/opa/constraint-templates/component.ts @@ -132,7 +132,7 @@ export class ConstraintTemplatesComponent implements OnInit, OnChanges, OnDestro const dialogConfig: MatDialogConfig = { data: { title: 'Delete Constraint Template', - message: `Delete ${constraintTemplate.name} constraint template permanently?`, + message: `Delete ${_.escape(constraintTemplate.name)} constraint template permanently?`, confirmLabel: 'Delete', warning: 'Deleting this constraint template will cause all constraints related to it to be deleted as well.', }, diff --git a/modules/web/src/app/settings/admin/opa/default-constraints/component.ts b/modules/web/src/app/settings/admin/opa/default-constraints/component.ts index f58f2d0d99..b06bd7ce55 100644 --- a/modules/web/src/app/settings/admin/opa/default-constraints/component.ts +++ b/modules/web/src/app/settings/admin/opa/default-constraints/component.ts @@ -185,7 +185,7 @@ export class DefaultConstraintComponent implements OnInit, OnChanges, OnDestroy const dialogConfig: MatDialogConfig = { data: { title: 'Delete Default Constraint', - message: `Delete ${defaultConstraint.name} default constraint permanently?`, + message: `Delete ${_.escape(defaultConstraint.name)} default constraint permanently?`, confirmLabel: 'Delete', }, }; diff --git a/modules/web/src/app/settings/admin/opa/default-constraints/default-constraint-dialog/component.ts b/modules/web/src/app/settings/admin/opa/default-constraints/default-constraint-dialog/component.ts index 1eec7cd22b..95b1762a5e 100644 --- a/modules/web/src/app/settings/admin/opa/default-constraints/default-constraint-dialog/component.ts +++ b/modules/web/src/app/settings/admin/opa/default-constraints/default-constraint-dialog/component.ts @@ -20,6 +20,7 @@ import {NotificationService} from '@core/services/notification'; import {Constraint, ConstraintTemplate, ConstraintSpec} from '@shared/entity/opa'; import {getIconClassForButton} from '@shared/utils/common'; import {DialogActionMode} from '@shared/types/common'; +import {NON_SPECIAL_CHARACTERS_PATTERN_VALIDATOR} from '@shared/validators/others'; import * as y from 'js-yaml'; import _ from 'lodash'; import {Observable, Subject} from 'rxjs'; @@ -76,7 +77,7 @@ export class DefaultConstraintDialog implements OnInit, OnDestroy { this.form = this._builder.group({ [Controls.Name]: this._builder.control( this.data.mode === this.Mode.Edit ? this.data.defaultConstraint.name : '', - [Validators.required] + [Validators.required, NON_SPECIAL_CHARACTERS_PATTERN_VALIDATOR] ), [Controls.ConstraintTemplate]: this._builder.control( { diff --git a/modules/web/src/app/settings/admin/opa/default-constraints/default-constraint-dialog/template.html b/modules/web/src/app/settings/admin/opa/default-constraints/default-constraint-dialog/template.html index 626b57adf9..35c925bcb0 100644 --- a/modules/web/src/app/settings/admin/opa/default-constraints/default-constraint-dialog/template.html +++ b/modules/web/src/app/settings/admin/opa/default-constraints/default-constraint-dialog/template.html @@ -31,6 +31,9 @@ Required + + Constraint Name cannot contain special characters like | " < > { } [ ] ` \ ' ; & + diff --git a/modules/web/src/app/settings/admin/presets/component.ts b/modules/web/src/app/settings/admin/presets/component.ts index 8b84b017f5..88006a18b3 100644 --- a/modules/web/src/app/settings/admin/presets/component.ts +++ b/modules/web/src/app/settings/admin/presets/component.ts @@ -215,8 +215,8 @@ export class PresetListComponent implements OnInit, OnDestroy, OnChanges { steps: ['Provider', 'Settings'], mode: Mode.Add, preset: preset, - descriptionProvider: `Add provider to ${preset.name} preset`, - descriptionSettings: `Specify provider settings for ${preset.name} provider preset`, + descriptionProvider: `Add provider to ${_.escape(preset.name)} preset`, + descriptionSettings: `Specify provider settings for ${_.escape(preset.name)} provider preset`, } as PresetDialogData, }; @@ -234,8 +234,8 @@ export class PresetListComponent implements OnInit, OnDestroy, OnChanges { data: { title: 'Edit Preset Provider', steps: ['Provider', 'Settings'], - descriptionProvider: `Choose a provider of ${preset.name} provider preset to edit`, - descriptionSettings: `Edit provider settings of ${preset.name} provider preset`, + descriptionProvider: `Choose a provider of ${_.escape(preset.name)} provider preset to edit`, + descriptionSettings: `Edit provider settings of ${_.escape(preset.name)} provider preset`, mode: Mode.Edit, preset: preset, } as PresetDialogData, diff --git a/modules/web/src/app/settings/admin/rule-groups/component.ts b/modules/web/src/app/settings/admin/rule-groups/component.ts index 6dbce3661d..29b27152fe 100644 --- a/modules/web/src/app/settings/admin/rule-groups/component.ts +++ b/modules/web/src/app/settings/admin/rule-groups/component.ts @@ -172,7 +172,7 @@ export class AdminSettingsRuleGroupsComponent implements OnInit, OnChanges, OnDe hasBackdrop: true, data: { title: 'Delete Rule Group', - message: `Delete ${adminRuleGroup.name} rule group of ${adminRuleGroup.seed} seed permanently?`, + message: `Delete ${_.escape(adminRuleGroup.name)} rule group of ${_.escape(adminRuleGroup.seed)} seed permanently?`, confirmLabel: 'Delete', }, }; diff --git a/modules/web/src/app/shared/components/add-ssh-key-dialog/component.ts b/modules/web/src/app/shared/components/add-ssh-key-dialog/component.ts index 9f0aa87446..52951ca2db 100644 --- a/modules/web/src/app/shared/components/add-ssh-key-dialog/component.ts +++ b/modules/web/src/app/shared/components/add-ssh-key-dialog/component.ts @@ -18,6 +18,7 @@ import {MatDialogRef} from '@angular/material/dialog'; import {GoogleAnalyticsService} from '@app/google-analytics.service'; import {NotificationService} from '@core/services/notification'; import {SSHKey} from '@shared/entity/ssh-key'; +import {NON_SPECIAL_CHARACTERS_PATTERN_VALIDATOR} from '@shared/validators/others'; import {SSHKeyFormValidator} from '@shared/validators/ssh-key-form.validator'; import {SSHKeyService} from '@core/services/ssh-key'; import {Observable} from 'rxjs'; @@ -48,7 +49,7 @@ export class AddSshKeyDialogComponent implements OnInit { ngOnInit(): void { this.form = this.formBuilder.group({ - [Controls.Name]: ['', [Validators.required]], + [Controls.Name]: ['', [Validators.required, NON_SPECIAL_CHARACTERS_PATTERN_VALIDATOR]], [Controls.Key]: ['', [Validators.required, SSHKeyFormValidator()]], }); this.googleAnalyticsService.emitEvent('addSshKey', 'addSshKeyDialogOpened'); diff --git a/modules/web/src/app/shared/components/add-ssh-key-dialog/template.html b/modules/web/src/app/shared/components/add-ssh-key-dialog/template.html index 7e55179c40..fb97b21f1e 100644 --- a/modules/web/src/app/shared/components/add-ssh-key-dialog/template.html +++ b/modules/web/src/app/shared/components/add-ssh-key-dialog/template.html @@ -29,6 +29,9 @@ Required + + Name cannot contain special characters like | " < > { } [ ] ` \ ' ; & + diff --git a/modules/web/src/app/shared/components/addon-list/component.ts b/modules/web/src/app/shared/components/addon-list/component.ts index 9624668fc6..09bbd31969 100644 --- a/modules/web/src/app/shared/components/addon-list/component.ts +++ b/modules/web/src/app/shared/components/addon-list/component.ts @@ -14,7 +14,6 @@ import {Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges} from '@angular/core'; import {MatDialog, MatDialogConfig} from '@angular/material/dialog'; -import {DomSanitizer, SafeUrl} from '@angular/platform-browser'; import {Addon, AddonConfig, getAddonLogoData, hasAddonLogoData} from '@shared/entity/addon'; import {Cluster} from '@shared/entity/cluster'; import _ from 'lodash'; @@ -55,7 +54,6 @@ export class AddonsListComponent implements OnInit, OnChanges, OnDestroy { constructor( private readonly _addonService: AddonService, private readonly _matDialog: MatDialog, - private readonly _domSanitizer: DomSanitizer, private readonly _dialogModeService: DialogModeService ) {} @@ -95,8 +93,8 @@ export class AddonsListComponent implements OnInit, OnChanges, OnDestroy { return hasAddonLogoData(this.addonConfigs.get(name)); } - getAddonLogo(name: string): SafeUrl { - return this._domSanitizer.bypassSecurityTrustUrl(getAddonLogoData(this.addonConfigs.get(name))); + getAddonLogo(name: string): string { + return getAddonLogoData(this.addonConfigs.get(name)); } canAdd(): boolean { @@ -164,7 +162,7 @@ export class AddonsListComponent implements OnInit, OnChanges, OnDestroy { const config: MatDialogConfig = { data: { title: 'Delete Addon', - message: `Delete ${addon.name} addon of ${this.cluster.name} cluster permanently?`, + message: `Delete ${_.escape(addon.name)} addon of ${_.escape(this.cluster.name)} cluster permanently?`, confirmLabel: 'Delete', }, }; diff --git a/modules/web/src/app/shared/components/addon-list/edit-addon-dialog/component.ts b/modules/web/src/app/shared/components/addon-list/edit-addon-dialog/component.ts index 34c0cb2b2c..ec4ba53ce2 100644 --- a/modules/web/src/app/shared/components/addon-list/edit-addon-dialog/component.ts +++ b/modules/web/src/app/shared/components/addon-list/edit-addon-dialog/component.ts @@ -15,7 +15,6 @@ import {Component, Input, OnInit} from '@angular/core'; import {FormBuilder, FormControl, FormGroup, ValidatorFn, Validators} from '@angular/forms'; import {MatDialogRef} from '@angular/material/dialog'; -import {DomSanitizer, SafeUrl} from '@angular/platform-browser'; import { AddonConfig, @@ -56,7 +55,6 @@ export class EditAddonDialogComponent implements OnInit { constructor( public dialogRef: MatDialogRef, - private readonly _domSanitizer: DomSanitizer, private readonly _builder: FormBuilder ) {} @@ -86,8 +84,8 @@ export class EditAddonDialogComponent implements OnInit { return hasAddonLogoData(this.addonConfig); } - getAddonLogo(): SafeUrl { - return this._domSanitizer.bypassSecurityTrustUrl(getAddonLogoData(this.addonConfig)); + getAddonLogo(): string { + return getAddonLogoData(this.addonConfig); } private _getAddonPatch(): Addon { diff --git a/modules/web/src/app/shared/components/addon-list/install-addon-dialog/component.ts b/modules/web/src/app/shared/components/addon-list/install-addon-dialog/component.ts index c8ab4a08e9..369070aa10 100644 --- a/modules/web/src/app/shared/components/addon-list/install-addon-dialog/component.ts +++ b/modules/web/src/app/shared/components/addon-list/install-addon-dialog/component.ts @@ -14,7 +14,6 @@ import {Component, Input, ViewChild} from '@angular/core'; import {MatDialogRef} from '@angular/material/dialog'; -import {DomSanitizer, SafeUrl} from '@angular/platform-browser'; import { Addon, @@ -28,6 +27,7 @@ import { import {FormBuilder, FormControl, FormGroup, ValidatorFn, Validators} from '@angular/forms'; import {MatStepper} from '@angular/material/stepper'; import {getEditionVersion} from '@shared/utils/common'; +import _ from 'lodash'; export enum Controls { ContinuouslyReconcile = 'continuouslyReconcile', @@ -60,7 +60,6 @@ export class InstallAddonDialogComponent { constructor( public dialogRef: MatDialogRef, - private readonly _domSanitizer: DomSanitizer, private readonly _builder: FormBuilder ) {} @@ -72,12 +71,12 @@ export class InstallAddonDialogComponent { return hasAddonFormData(this.addonConfigs.get(name)); } - getAddonLogo(name: string): SafeUrl { - return this._domSanitizer.bypassSecurityTrustUrl(getAddonLogoData(this.addonConfigs.get(name))); + getAddonLogo(name: string): string { + return getAddonLogoData(this.addonConfigs.get(name)); } getAddonShortDescription(name: string): string { - return getAddonShortDescription(this.addonConfigs.get(name)); + return _.escape(getAddonShortDescription(this.addonConfigs.get(name))); } select(name: string): void { diff --git a/modules/web/src/app/shared/components/application-list/component.ts b/modules/web/src/app/shared/components/application-list/component.ts index 55fc6c64a7..38d9456155 100644 --- a/modules/web/src/app/shared/components/application-list/component.ts +++ b/modules/web/src/app/shared/components/application-list/component.ts @@ -266,8 +266,8 @@ export class ApplicationListComponent implements OnInit, OnDestroy { const config: MatDialogConfig = { data: { title: 'Delete Application', - message: `Delete ${application.name} application${ - this.cluster ? ` of ${this.cluster.name} cluster permanently` : '' + message: `Delete ${_.escape(application.name)} application${ + this.cluster ? ` of ${_.escape(this.cluster.name)} cluster permanently` : '' }?`, confirmLabel: 'Delete', }, diff --git a/modules/web/src/app/shared/entity/addon.ts b/modules/web/src/app/shared/entity/addon.ts index 4ad3a5793d..d8dcbceeeb 100644 --- a/modules/web/src/app/shared/entity/addon.ts +++ b/modules/web/src/app/shared/entity/addon.ts @@ -66,7 +66,6 @@ export function hasAddonLogoData(addonConfig: AddonConfig): boolean { return !!addonConfig && !!addonConfig.spec && !!addonConfig.spec.logo && !!addonConfig.spec.logoFormat; } -// Before using it in HTML it has to be go through DomSanitizer.bypassSecurityTrustUrl() method. export function getAddonLogoData(addonConfig: AddonConfig): string { return addonConfig && addonConfig.spec ? `data:image/${addonConfig.spec.logoFormat};base64,${addonConfig.spec.logo}` diff --git a/modules/web/src/app/shared/entity/application.ts b/modules/web/src/app/shared/entity/application.ts index d3410df81d..432ff2d592 100644 --- a/modules/web/src/app/shared/entity/application.ts +++ b/modules/web/src/app/shared/entity/application.ts @@ -170,7 +170,6 @@ export enum ApplicationLabelValue { KKP = 'kkp', } -// Before using it in HTML it has to be go through DomSanitizer.bypassSecurityTrustUrl() method. export function getApplicationLogoData(applicationDefinition: ApplicationDefinition): string { return applicationDefinition?.spec?.logo && applicationDefinition.spec.logoFormat ? `data:image/${applicationDefinition.spec.logoFormat};base64,${applicationDefinition.spec.logo}` diff --git a/modules/web/src/app/shared/validators/others.ts b/modules/web/src/app/shared/validators/others.ts index 0d67b465bf..d5e97292d1 100644 --- a/modules/web/src/app/shared/validators/others.ts +++ b/modules/web/src/app/shared/validators/others.ts @@ -40,3 +40,6 @@ export const Cluster_BACKUP_EXPIRES_IN = Validators.pattern('^(0|[0-9]{1,2}h?[0- // String shouldn't start with ( or [ or } or ) or | export const KUBERNETES_ANNOTATION_VALUE_PATTERN = '^[^(})|\\[]*'; export const KUBERNETES_ANNOTATION_VALUE_PATTERN_VALIDATOR = Validators.pattern(KUBERNETES_ANNOTATION_VALUE_PATTERN); + +export const NON_SPECIAL_CHARACTERS_PATTERN = /^[^|"<>[\]{}`\\';&]+$/; +export const NON_SPECIAL_CHARACTERS_PATTERN_VALIDATOR = Validators.pattern(NON_SPECIAL_CHARACTERS_PATTERN); diff --git a/modules/web/src/app/sshkey/component.ts b/modules/web/src/app/sshkey/component.ts index 76402bb0a4..18bc310d79 100644 --- a/modules/web/src/app/sshkey/component.ts +++ b/modules/web/src/app/sshkey/component.ts @@ -153,7 +153,7 @@ export class SSHKeyComponent implements OnInit, OnChanges, OnDestroy { data: { dialogId: 'km-delete-sshkey-dialog', title: 'Delete SSH Key', - message: `Delete ${sshKey.name} SSH key of ${this.project.name} project permanently?`, + message: `Delete ${_.escape(sshKey.name)} SSH key of ${_.escape(this.project.name)} project permanently?`, confirmLabel: 'Delete', confirmLabelId: 'km-delete-sshkey-dialog-btn', }, diff --git a/modules/web/src/app/wizard/step/cluster/component.ts b/modules/web/src/app/wizard/step/cluster/component.ts index 23ce9e3724..2322604988 100644 --- a/modules/web/src/app/wizard/step/cluster/component.ts +++ b/modules/web/src/app/wizard/step/cluster/component.ts @@ -32,6 +32,7 @@ import { IPV4_CIDR_PATTERN_VALIDATOR, IPV4_IPV6_CIDR_PATTERN, IPV6_CIDR_PATTERN_VALIDATOR, + NON_SPECIAL_CHARACTERS_PATTERN_VALIDATOR, } from '@app/shared/validators/others'; import {ClusterService} from '@core/services/cluster'; import {ClusterSpecService} from '@core/services/cluster-spec'; @@ -572,6 +573,7 @@ export class ClusterStepComponent extends StepBase implements OnInit, ControlVal [Controls.Name]: this._builder.control(this._clusterSpecService?.cluster?.name ?? '', [ Validators.required, Validators.minLength(this._minNameLength), + NON_SPECIAL_CHARACTERS_PATTERN_VALIDATOR, ]), [Controls.Version]: this._builder.control(clusterSpec?.version ?? '', [Validators.required]), [Controls.ContainerRuntime]: this._builder.control(clusterSpec?.containerRuntime ?? ContainerRuntime.Containerd, [ diff --git a/modules/web/src/app/wizard/step/cluster/template.html b/modules/web/src/app/wizard/step/cluster/template.html index 630fce0bd9..b54439fee3 100644 --- a/modules/web/src/app/wizard/step/cluster/template.html +++ b/modules/web/src/app/wizard/step/cluster/template.html @@ -54,6 +54,9 @@ Name must be at least {{ control(Controls.Name).getError('minlength').requiredLength }} characters. + + Name cannot contain special characters like | " < > { } [ ] ` \ ' ; & + From 37d9a3bd02eff1180c9551aa41b9ac8518600ffe Mon Sep 17 00:00:00 2001 From: Kubermatic Bot <41968677+kubermatic-bot@users.noreply.github.com> Date: Mon, 14 Apr 2025 14:53:18 +0200 Subject: [PATCH 09/42] disable add autoscaler app option (#7284) Co-authored-by: ahmadhamzh --- modules/web/src/app/node-data/component.ts | 20 +++++++++++++++++++- modules/web/src/app/node-data/template.html | 12 +++++++----- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/modules/web/src/app/node-data/component.ts b/modules/web/src/app/node-data/component.ts index 04a08d1beb..5ead59e25a 100644 --- a/modules/web/src/app/node-data/component.ts +++ b/modules/web/src/app/node-data/component.ts @@ -45,7 +45,7 @@ import {QuotaWidgetComponent} from '@dynamic/enterprise/quotas/quota-widget/comp import {QuotaCalculationService} from '@dynamic/enterprise/quotas/services/quota-calculation'; import {DynamicModule} from '@dynamic/module-registry'; import {AutocompleteControls} from '@shared/components/autocomplete/component'; -import {CLUSTER_AUTOSCALING_APP_DEF_NAME} from '@shared/entity/application'; +import {ApplicationDefinition, CLUSTER_AUTOSCALING_APP_DEF_NAME} from '@shared/entity/application'; import {Datacenter} from '@shared/entity/datacenter'; import {NodeNetworkSpec, OperatingSystemSpec, Taint} from '@shared/entity/node'; import {OperatingSystemProfile} from '@shared/entity/operating-system-profile'; @@ -125,6 +125,8 @@ export class NodeDataComponent extends BaseFormValidator implements OnInit, OnDe currentNodeOS: OperatingSystem; allowedOperatingSystems = DEFAULT_ADMIN_SETTINGS.allowedOperatingSystems; DNSServers: string[] = []; + autoscalingTooltipText = + 'Autoscaling of machines requires the Cluster Autoscaler application to be installed. Enable this option to install Cluster Autoscaler Application.'; private isCusterTemplateEditMode = false; private quotaWidgetComponentRef: QuotaWidgetComponent; @@ -237,6 +239,18 @@ export class NodeDataComponent extends BaseFormValidator implements OnInit, OnDe } } + this._applicationService + .listApplicationDefinitions() + .pipe(takeUntil(this._unsubscribe)) + .subscribe(apps => { + if (!this._isClusterAutoscalingAppExist(apps)) { + this.form.get(Controls.EnableClusterAutoscalingApp).setValue(false); + this.form.get(Controls.EnableClusterAutoscalingApp).disable(); + this.autoscalingTooltipText = + 'To enable autoscaling, the Cluster Autoscaler application must be added to the applications catalog.'; + } + }); + this.currentNodeOS = (this.dialogEditMode || this.wizardMode) && this._nodeDataService.operatingSystem; this._init(); @@ -675,4 +689,8 @@ export class NodeDataComponent extends BaseFormValidator implements OnInit, OnDe replicas: this._nodeDataService.nodeData.count, }; } + + private _isClusterAutoscalingAppExist(apps: ApplicationDefinition[]): boolean { + return !!apps.find(app => app.name === CLUSTER_AUTOSCALING_APP_DEF_NAME); + } } diff --git a/modules/web/src/app/node-data/template.html b/modules/web/src/app/node-data/template.html index f603a66886..200dd56b89 100644 --- a/modules/web/src/app/node-data/template.html +++ b/modules/web/src/app/node-data/template.html @@ -249,12 +249,14 @@ - Enable Cluster Autoscaler Application +
+ Enable Cluster Autoscaler Application + - + [matTooltip]="autoscalingTooltipText"> +
Date: Mon, 5 May 2025 12:22:56 +0200 Subject: [PATCH 10/42] =?UTF-8?q?[release/v2.27]=20Support=20KubeVirt=20Su?= =?UTF-8?q?bnet=20and=20StorageClasses=20Location=20Compatibility=20=20(#?= =?UTF-8?q?=E2=80=A6=20(#7303)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Support KubeVirt Subnet and StorageClasses Location Compatibility (#7301) * support subnet selection by storage class name * bump kkp sdk go mod --------- Signed-off-by: moadqassem (cherry picked from commit c4a0c4702558d6edf90fb1a1a4340595f07aa52f) Signed-off-by: moadqassem * update go mods Signed-off-by: moadqassem Signed-off-by: moadqassem * fix api tests Signed-off-by: moadqassem Signed-off-by: moadqassem --------- Signed-off-by: moadqassem --- modules/api/cmd/kubermatic-api/swagger.json | 46 ++++++++++-- modules/api/go.mod | 4 +- modules/api/go.sum | 8 +- .../pkg/handler/common/provider/kubevirt.go | 30 +++++++- modules/api/pkg/handler/test/helper.go | 4 +- .../cluster_default/cluster_default_test.go | 8 +- .../external_cluster/external_cluster_test.go | 8 +- .../api/pkg/handler/v2/provider/kubevirt.go | 74 ++++++++++++++++--- modules/api/pkg/handler/v2/routes_v2.go | 2 +- ..._virt_subnets_no_credentials_parameters.go | 31 ++++++++ ...ist_project_kubevirt_subnets_parameters.go | 22 ++++++ .../models/datacenter_spec_kubevirt.go | 5 ++ .../test/e2e/utils/apiclient/models/subnet.go | 8 ++ 13 files changed, 213 insertions(+), 37 deletions(-) diff --git a/modules/api/cmd/kubermatic-api/swagger.json b/modules/api/cmd/kubermatic-api/swagger.json index 6e3583d03c..aea1d40baf 100644 --- a/modules/api/cmd/kubermatic-api/swagger.json +++ b/modules/api/cmd/kubermatic-api/swagger.json @@ -12106,6 +12106,13 @@ ], "operationId": "listAWSSizesNoCredentialsV2", "parameters": [ + { + "type": "string", + "x-go-name": "Architecture", + "description": "architecture query parameter. Supports: arm64 and x64 types.", + "name": "architecture", + "in": "query" + }, { "type": "string", "x-go-name": "ProjectID", @@ -12119,13 +12126,6 @@ "name": "cluster_id", "in": "path", "required": true - }, - { - "type": "string", - "x-go-name": "Architecture", - "description": "architecture query parameter. Supports: arm64 and x64 types.", - "name": "architecture", - "in": "query" } ], "responses": { @@ -12766,6 +12766,12 @@ "summary": "List Subnets for a VPC associated with a cluster.", "operationId": "listKubeVirtSubnetsNoCredentials", "parameters": [ + { + "type": "string", + "x-go-name": "StorageClassName", + "name": "storageClassName", + "in": "query" + }, { "type": "string", "x-go-name": "ProjectID", @@ -20437,6 +20443,11 @@ "type": "string", "name": "VPCName", "in": "header" + }, + { + "type": "string", + "name": "StorageClassName", + "in": "header" } ], "responses": { @@ -31011,6 +31022,11 @@ }, "x-go-name": "InfraStorageClasses" }, + "matchSubnetAndStorageLocation": { + "description": "Optional: MatchSubnetAndStorageLocation if set to true, the region and zone of the subnet and storage class must match. For\nexample, if the storage class has the region `eu` and zone was `central`, the subnet must be in the same region and zone.\notherwise KKP will reject the creation of the machine deployment and eventually the cluster.", + "type": "boolean", + "x-go-name": "MatchSubnetAndStorageLocation" + }, "namespacedMode": { "$ref": "#/definitions/NamespacedMode" }, @@ -39736,6 +39752,22 @@ "name": { "type": "string", "x-go-name": "Name" + }, + "regions": { + "description": "Regions represents a larger domain, made up of one or more zones. It is uncommon for Kubernetes clusters\nto span multiple regions", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Regions" + }, + "zones": { + "description": "Zones represent a logical failure domain. It is common for Kubernetes clusters to span multiple zones\nfor increased availability", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Zones" } }, "x-go-package": "k8c.io/kubermatic/v2/pkg/apis/kubermatic/v1" diff --git a/modules/api/go.mod b/modules/api/go.mod index 6253c92cc1..6e89e938dd 100644 --- a/modules/api/go.mod +++ b/modules/api/go.mod @@ -70,9 +70,9 @@ require ( google.golang.org/api v0.209.0 gopkg.in/yaml.v3 v3.0.1 k8c.io/kubeone v1.7.3 - k8c.io/kubermatic/v2 v2.27.3-0.20250407231911-b56d2995a821 + k8c.io/kubermatic/v2 v2.27.4-0.20250430120652-5bc371b62738 k8c.io/machine-controller v1.61.1 - k8c.io/operating-system-manager v1.6.4 + k8c.io/operating-system-manager v1.6.5 k8c.io/reconciler v0.5.0 k8s.io/api v0.31.3 k8s.io/apiextensions-apiserver v0.31.3 diff --git a/modules/api/go.sum b/modules/api/go.sum index 1c28da65ff..163a2a9688 100644 --- a/modules/api/go.sum +++ b/modules/api/go.sum @@ -1145,12 +1145,12 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8c.io/kubeone v1.7.2 h1:uLH19VEp1S5j4f3UwQP4CLHErJ23UiJM2MnbufNWDgI= k8c.io/kubeone v1.7.2/go.mod h1:9v2VFz/+l36cW65kd5YufEYHunbKlJ6P8SBakj05xgM= -k8c.io/kubermatic/v2 v2.27.3-0.20250407231911-b56d2995a821 h1:Kfd1L7A9j4cM2x/ICXZHvDyUu5HiYizfPGP9ho4RN1I= -k8c.io/kubermatic/v2 v2.27.3-0.20250407231911-b56d2995a821/go.mod h1:rc/2inBvHXCBIPmzawH1NYICKkVPnm9ASBZl63wdUz0= +k8c.io/kubermatic/v2 v2.27.4-0.20250430120652-5bc371b62738 h1:y+JiTT/dUDbQFXAqqq2ZQHzdpCujg+smSXHm7FIgz+k= +k8c.io/kubermatic/v2 v2.27.4-0.20250430120652-5bc371b62738/go.mod h1:NbOQXhqp0TcszS2zJHmvlCPBNY4XYMMAKg3UyA6pjM8= k8c.io/machine-controller v1.61.1 h1:Zy3kg9t0WrDN0Wo3y/pAJp7jdkThcJt070f0fAL9MVc= k8c.io/machine-controller v1.61.1/go.mod h1:ZGDFyUeEp66RHcNB5Ki/OJyFdZFgo9dkHJ9s6YJWPcg= -k8c.io/operating-system-manager v1.6.4 h1:MDtULmZeqsLEg9Fs96i41qVKOyakyV31OcxU54edQTI= -k8c.io/operating-system-manager v1.6.4/go.mod h1:Dh9IZp5pb5G3s2r6ZrHUb+gTuHw5AmbIFYuFxIcgU7o= +k8c.io/operating-system-manager v1.6.5 h1:F7oBJKEv2t3uzG8k4e9s9j9vCAvXN1FGEkqV0+dMh60= +k8c.io/operating-system-manager v1.6.5/go.mod h1:Dh9IZp5pb5G3s2r6ZrHUb+gTuHw5AmbIFYuFxIcgU7o= k8c.io/reconciler v0.5.0 h1:BHpelg1UfI/7oBFctqOq8sX6qzflXpl3SlvHe7e8wak= k8c.io/reconciler v0.5.0/go.mod h1:pT1+SVcVXJQeBJhpJBXQ5XW64QnKKeYTnVlQf0dGE0k= k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= diff --git a/modules/api/pkg/handler/common/provider/kubevirt.go b/modules/api/pkg/handler/common/provider/kubevirt.go index a9e4778419..ccc0c5d771 100644 --- a/modules/api/pkg/handler/common/provider/kubevirt.go +++ b/modules/api/pkg/handler/common/provider/kubevirt.go @@ -40,6 +40,7 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -162,7 +163,7 @@ func KubeVirtVPCsWithClusterCredentialsEndpoint(ctx context.Context, userInfoGet } func KubeVirtSubnetsWithClusterCredentialsEndpoint(ctx context.Context, userInfoGetter provider.UserInfoGetter, projectProvider provider.ProjectProvider, privilegedProjectProvider provider.PrivilegedProjectProvider, - seedsGetter provider.SeedsGetter, projectID, clusterID string) (interface{}, error) { + seedsGetter provider.SeedsGetter, projectID, clusterID, storageClassName string) (interface{}, error) { userInfo, err := userInfoGetter(ctx, projectID) if err != nil { return nil, common.KubernetesErrorToHTTPError(err) @@ -185,10 +186,31 @@ func KubeVirtSubnetsWithClusterCredentialsEndpoint(ctx context.Context, userInfo for _, vpc := range datacenter.Spec.Kubevirt.ProviderNetwork.VPCs { if cluster.Spec.Cloud.Kubevirt.VPCName == vpc.Name { for _, subnet := range vpc.Subnets { - kvSubnet := apiv2.KubeVirtSubnet{ - Name: subnet.Name, + if datacenter.Spec.Kubevirt.MatchSubnetAndStorageLocation != nil && *datacenter.Spec.Kubevirt.MatchSubnetAndStorageLocation { + for _, sc := range datacenter.Spec.Kubevirt.InfraStorageClasses { + if storageClassName == "" && sc.IsDefaultClass != nil && *sc.IsDefaultClass { + storageClassName = sc.Name + } + if sc.Name == storageClassName { + scRegions := sets.New[string]().Insert(sc.Regions...) + scZones := sets.New[string]().Insert(sc.Zones...) + + if scRegions.HasAll(subnet.Regions...) && scZones.HasAll(subnet.Zones...) { + kvSubnet := apiv2.KubeVirtSubnet{ + Name: subnet.Name, + } + + kvSubnets = append(kvSubnets, kvSubnet) + } + } + } + } else { + kvSubnet := apiv2.KubeVirtSubnet{ + Name: subnet.Name, + } + + kvSubnets = append(kvSubnets, kvSubnet) } - kvSubnets = append(kvSubnets, kvSubnet) } } } diff --git a/modules/api/pkg/handler/test/helper.go b/modules/api/pkg/handler/test/helper.go index 88199a118c..cfb64e884c 100644 --- a/modules/api/pkg/handler/test/helper.go +++ b/modules/api/pkg/handler/test/helper.go @@ -126,8 +126,8 @@ const ( TestSeedDatacenter = "us-central1" // TestServiceAccountHashKey authenticates the service account's token value using HMAC. TestServiceAccountHashKey = "eyJhbGciOiJIUzI1NeyJhbGciOiJIUzI1N" - // TestFakeToken signed JWT token with fake data. It will expire after 3 years from 12-04-2022. To generate new token use kubermatic/pkg/serviceaccount/jwt_test.go. - TestFakeToken = "eyJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6InRlc3RAZXhhbXBsZS5jb20iLCJleHAiOjE3NDQ0NjQ1OTYsImlhdCI6MTY0OTc3MDE5NiwibmJmIjoxNjQ5NzcwMTk2LCJwcm9qZWN0X2lkIjoidGVzdFByb2plY3QiLCJ0b2tlbl9pZCI6InRlc3RUb2tlbiJ9.IGcnVhrTGeemEZ_dOGCRE1JXwpSMWJEbrG8hylpTEUY" + // TestFakeToken signed JWT token with fake data. It will expire after 3 years from 04-05-2025. To generate new token use kubermatic/pkg/serviceaccount/jwt_test.go. + TestFakeToken = "eyJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6InRlc3RAZXhhbXBsZS5jb20iLCJleHAiOjE4NDEwOTE0NTUsImlhdCI6MTc0NjM5NzA1NSwibmJmIjoxNzQ2Mzk3MDU1LCJwcm9qZWN0X2lkIjoidGVzdFByb2plY3QiLCJ0b2tlbl9pZCI6InRlc3RUb2tlbiJ9.pz7r-3U_af92dOT0dIIHxOZpnWUBBaVpzTJOQ6mvfu4" // TestOSdomain OpenStack domain. TestOSdomain = "OSdomain" // TestOSuserPass OpenStack user password. diff --git a/modules/api/pkg/handler/v2/cluster_default/cluster_default_test.go b/modules/api/pkg/handler/v2/cluster_default/cluster_default_test.go index 490dc1735f..1c2725c64c 100644 --- a/modules/api/pkg/handler/v2/cluster_default/cluster_default_test.go +++ b/modules/api/pkg/handler/v2/cluster_default/cluster_default_test.go @@ -50,7 +50,7 @@ func TestGetEndpoint(t *testing.T) { ), ExistingAPIUser: test.GenDefaultAPIUser(), ExpectedHTTPStatusCode: http.StatusOK, - ExpectedResponse: `{"name":"","creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","aws":{}},"version":"v1.31.7","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20","fd02::/120"]},"pods":{"cidrBlocks":["172.25.0.0/16","fd01::/48"]},"nodeCidrMaskSizeIPv4":24,"nodeCidrMaskSizeIPv6":64,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":""}}`, + ExpectedResponse: `{"name":"","creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","aws":{}},"version":"v1.31.8","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20","fd02::/120"]},"pods":{"cidrBlocks":["172.25.0.0/16","fd01::/48"]},"nodeCidrMaskSizeIPv4":24,"nodeCidrMaskSizeIPv6":64,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":""}}`, }, { Name: "Default cluster for Azure", @@ -61,7 +61,7 @@ func TestGetEndpoint(t *testing.T) { ), ExistingAPIUser: test.GenDefaultAPIUser(), ExpectedHTTPStatusCode: http.StatusOK, - ExpectedResponse: `{"name":"","creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","azure":{"assignAvailabilitySet":null}},"version":"v1.31.7","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20","fd02::/120"]},"pods":{"cidrBlocks":["172.25.0.0/16","fd01::/48"]},"nodeCidrMaskSizeIPv4":24,"nodeCidrMaskSizeIPv6":64,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":""}}`, + ExpectedResponse: `{"name":"","creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","azure":{"assignAvailabilitySet":null}},"version":"v1.31.8","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20","fd02::/120"]},"pods":{"cidrBlocks":["172.25.0.0/16","fd01::/48"]},"nodeCidrMaskSizeIPv4":24,"nodeCidrMaskSizeIPv6":64,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":""}}`, }, { Name: "Default cluster for vSphere", @@ -72,7 +72,7 @@ func TestGetEndpoint(t *testing.T) { ), ExistingAPIUser: test.GenDefaultAPIUser(), ExpectedHTTPStatusCode: http.StatusOK, - ExpectedResponse: `{"name":"","creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","vsphere":{}},"version":"v1.31.7","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20","fd02::/120"]},"pods":{"cidrBlocks":["172.25.0.0/16","fd01::/48"]},"nodeCidrMaskSizeIPv4":24,"nodeCidrMaskSizeIPv6":64,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":""}}`, + ExpectedResponse: `{"name":"","creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","vsphere":{}},"version":"v1.31.8","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20","fd02::/120"]},"pods":{"cidrBlocks":["172.25.0.0/16","fd01::/48"]},"nodeCidrMaskSizeIPv4":24,"nodeCidrMaskSizeIPv6":64,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":""}}`, }, { Name: "Default cluster for GCP", @@ -83,7 +83,7 @@ func TestGetEndpoint(t *testing.T) { ), ExistingAPIUser: test.GenDefaultAPIUser(), ExpectedHTTPStatusCode: http.StatusOK, - ExpectedResponse: `{"name":"","creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","gcp":{}},"version":"v1.31.7","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20","fd02::/120"]},"pods":{"cidrBlocks":["172.25.0.0/16","fd01::/48"]},"nodeCidrMaskSizeIPv4":24,"nodeCidrMaskSizeIPv6":64,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":""}}`, + ExpectedResponse: `{"name":"","creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","gcp":{}},"version":"v1.31.8","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20","fd02::/120"]},"pods":{"cidrBlocks":["172.25.0.0/16","fd01::/48"]},"nodeCidrMaskSizeIPv4":24,"nodeCidrMaskSizeIPv6":64,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":""}}`, }, } diff --git a/modules/api/pkg/handler/v2/external_cluster/external_cluster_test.go b/modules/api/pkg/handler/v2/external_cluster/external_cluster_test.go index 7e65e84482..50f5bf123e 100644 --- a/modules/api/pkg/handler/v2/external_cluster/external_cluster_test.go +++ b/modules/api/pkg/handler/v2/external_cluster/external_cluster_test.go @@ -306,7 +306,7 @@ func TestListClusters(t *testing.T) { ID: "clusterAbcID", }, Spec: apiv1.ClusterSpec{ - Version: "v1.31.7", + Version: "v1.31.8", }, Labels: map[string]string{kubermaticv1.ProjectIDLabelKey: test.GenDefaultProject().Name}, }, @@ -316,7 +316,7 @@ func TestListClusters(t *testing.T) { ID: "clusterDefID", }, Spec: apiv1.ClusterSpec{ - Version: "v1.31.7", + Version: "v1.31.8", }, Labels: map[string]string{kubermaticv1.ProjectIDLabelKey: test.GenDefaultProject().Name}, }, @@ -339,7 +339,7 @@ func TestListClusters(t *testing.T) { ID: "clusterAbcID", }, Spec: apiv1.ClusterSpec{ - Version: "v1.31.7", + Version: "v1.31.8", }, Labels: map[string]string{kubermaticv1.ProjectIDLabelKey: test.GenDefaultProject().Name}, }, @@ -349,7 +349,7 @@ func TestListClusters(t *testing.T) { ID: "clusterDefID", }, Spec: apiv1.ClusterSpec{ - Version: "v1.31.7", + Version: "v1.31.8", }, Labels: map[string]string{kubermaticv1.ProjectIDLabelKey: test.GenDefaultProject().Name}, }, diff --git a/modules/api/pkg/handler/v2/provider/kubevirt.go b/modules/api/pkg/handler/v2/provider/kubevirt.go index 0171c8e1dd..5ad04347e9 100644 --- a/modules/api/pkg/handler/v2/provider/kubevirt.go +++ b/modules/api/pkg/handler/v2/provider/kubevirt.go @@ -32,6 +32,7 @@ import ( kubermaticprovider "k8c.io/kubermatic/v2/pkg/provider" utilerrors "k8c.io/kubermatic/v2/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/utils/ptr" ) @@ -63,6 +64,9 @@ type KubeVirtVPCSubnetsReq struct { // in: header // name: VPCName VPCName string + // in: header + // name: StorageClassName + StorageClassName string } // KubeVirtListImagesReq represents a request to list KubeVirt images @@ -79,6 +83,14 @@ type KubeVirtGenericNoCredentialReq struct { cluster.GetClusterReq } +// KubeVirtSubnetsNoCredentialReq represent a KubeVirt provider network subnet request with cluster credentials. +// swagger:parameters listKubeVirtSubnetsNoCredentials +type KubeVirtSubnetsNoCredentialReq struct { + cluster.GetClusterReq + + StorageClassName string `json:"storageClassName,omitempty"` +} + func getKubeconfig(ctx context.Context, kubeconfig, credential, projectID string, presetsProvider provider.PresetProvider, userInfoGetter provider.UserInfoGetter) (string, error) { userInfo, err := userInfoGetter(ctx, projectID) if err != nil { @@ -276,9 +288,10 @@ func KubeVirtVPCsEndpoint(presetsProvider provider.PresetProvider, userInfoGette func KubeVirtSubnetsEndpoint(presetsProvider provider.PresetProvider, userInfoGetter provider.UserInfoGetter, seedsGetter provider.SeedsGetter, withProject bool) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { var ( - req KubeVirtGenericReq - projectID string - vpcName string + req KubeVirtGenericReq + projectID string + vpcName string + storageClassName string ) if !withProject { @@ -297,6 +310,7 @@ func KubeVirtSubnetsEndpoint(presetsProvider provider.PresetProvider, userInfoGe req = vpcSubnetsReq.KubeVirtGenericReq projectID = vpcSubnetsReq.GetProjectID() vpcName = vpcSubnetsReq.VPCName + storageClassName = vpcSubnetsReq.StorageClassName } userInfo, err := userInfoGetter(ctx, projectID) @@ -322,11 +336,31 @@ func KubeVirtSubnetsEndpoint(presetsProvider provider.PresetProvider, userInfoGe for _, vpc := range datacenter.Spec.Kubevirt.ProviderNetwork.VPCs { if vpc.Name == vpcName { for _, subnet := range vpc.Subnets { - kvSubnet := apiv2.KubeVirtSubnet{ - Name: subnet.Name, + if datacenter.Spec.Kubevirt.MatchSubnetAndStorageLocation != nil && *datacenter.Spec.Kubevirt.MatchSubnetAndStorageLocation { + for _, sc := range datacenter.Spec.Kubevirt.InfraStorageClasses { + if storageClassName == "" && sc.IsDefaultClass != nil && *sc.IsDefaultClass { + storageClassName = sc.Name + } + if sc.Name == storageClassName { + scRegions := sets.New[string]().Insert(sc.Regions...) + scZones := sets.New[string]().Insert(sc.Zones...) + + if scRegions.HasAll(subnet.Regions...) && scZones.HasAll(subnet.Zones...) { + kvSubnet := apiv2.KubeVirtSubnet{ + Name: subnet.Name, + } + + kvSubnets = append(kvSubnets, kvSubnet) + } + } + } + } else { + kvSubnet := apiv2.KubeVirtSubnet{ + Name: subnet.Name, + } + + kvSubnets = append(kvSubnets, kvSubnet) } - - kvSubnets = append(kvSubnets, kvSubnet) } } } @@ -363,11 +397,11 @@ func KubeVirtVPCsWithClusterCredentialsEndpoint(projectProvider provider.Project // KubeVirtSubnetsWithClusterCredentialsEndpoint handles the request to list Subnets for a VPC (cluster credentials). func KubeVirtSubnetsWithClusterCredentialsEndpoint(projectProvider provider.ProjectProvider, privilegedProjectProvider provider.PrivilegedProjectProvider, seedsGetter provider.SeedsGetter, userInfoGetter provider.UserInfoGetter) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { - req, ok := request.(KubeVirtGenericNoCredentialReq) + req, ok := request.(KubeVirtSubnetsNoCredentialReq) if !ok { return nil, utilerrors.NewBadRequest("invalid request") } - return providercommon.KubeVirtSubnetsWithClusterCredentialsEndpoint(ctx, userInfoGetter, projectProvider, privilegedProjectProvider, seedsGetter, req.ProjectID, req.ClusterID) + return providercommon.KubeVirtSubnetsWithClusterCredentialsEndpoint(ctx, userInfoGetter, projectProvider, privilegedProjectProvider, seedsGetter, req.ProjectID, req.ClusterID, req.StorageClassName) } } @@ -421,10 +455,12 @@ func DecodeKubeVirtVPCSubnetsReq(c context.Context, r *http.Request) (interface{ } vpcName := r.Header.Get("VPCName") + storageClassName := r.Header.Get("StorageClassName") return KubeVirtVPCSubnetsReq{ ProjectReq: projectReq.(common.ProjectReq), KubeVirtGenericReq: kubevirtReq.(KubeVirtGenericReq), VPCName: vpcName, + StorageClassName: storageClassName, }, nil } @@ -446,6 +482,26 @@ func DecodeKubeVirtGenericNoCredentialReq(c context.Context, r *http.Request) (i return req, nil } +func DecodeKubeVirtSubnetsNoCredentialReq(c context.Context, r *http.Request) (interface{}, error) { + var req KubeVirtSubnetsNoCredentialReq + clusterID, err := common.DecodeClusterID(c, r) + if err != nil { + return nil, err + } + + req.ClusterID = clusterID + + pr, err := common.DecodeProjectRequest(c, r) + if err != nil { + return nil, err + } + + req.ProjectReq = pr.(common.ProjectReq) + req.StorageClassName = mux.Vars(r)["storageClassName"] + + return req, nil +} + func DecodeKubeVirtListImageReq(c context.Context, r *http.Request) (interface{}, error) { var req KubeVirtListImagesReq diff --git a/modules/api/pkg/handler/v2/routes_v2.go b/modules/api/pkg/handler/v2/routes_v2.go index 653d64d749..43897b3e1e 100644 --- a/modules/api/pkg/handler/v2/routes_v2.go +++ b/modules/api/pkg/handler/v2/routes_v2.go @@ -4985,7 +4985,7 @@ func (r Routing) listKubeVirtSubnetsNoCredentials() http.Handler { middleware.SetClusterProvider(r.clusterProviderGetter, r.seedsGetter), middleware.SetPrivilegedClusterProvider(r.clusterProviderGetter, r.seedsGetter), )(provider.KubeVirtSubnetsWithClusterCredentialsEndpoint(r.projectProvider, r.privilegedProjectProvider, r.seedsGetter, r.userInfoGetter)), - provider.DecodeKubeVirtGenericNoCredentialReq, + provider.DecodeKubeVirtSubnetsNoCredentialReq, handler.EncodeJSON, r.defaultServerOptions()..., ) diff --git a/modules/api/pkg/test/e2e/utils/apiclient/client/kubevirt/list_kube_virt_subnets_no_credentials_parameters.go b/modules/api/pkg/test/e2e/utils/apiclient/client/kubevirt/list_kube_virt_subnets_no_credentials_parameters.go index db8a31ce09..91ab5ec54e 100644 --- a/modules/api/pkg/test/e2e/utils/apiclient/client/kubevirt/list_kube_virt_subnets_no_credentials_parameters.go +++ b/modules/api/pkg/test/e2e/utils/apiclient/client/kubevirt/list_kube_virt_subnets_no_credentials_parameters.go @@ -67,6 +67,9 @@ type ListKubeVirtSubnetsNoCredentialsParams struct { // ProjectID. ProjectID string + // StorageClassName. + StorageClassName *string + timeout time.Duration Context context.Context HTTPClient *http.Client @@ -142,6 +145,17 @@ func (o *ListKubeVirtSubnetsNoCredentialsParams) SetProjectID(projectID string) o.ProjectID = projectID } +// WithStorageClassName adds the storageClassName to the list kube virt subnets no credentials params +func (o *ListKubeVirtSubnetsNoCredentialsParams) WithStorageClassName(storageClassName *string) *ListKubeVirtSubnetsNoCredentialsParams { + o.SetStorageClassName(storageClassName) + return o +} + +// SetStorageClassName adds the storageClassName to the list kube virt subnets no credentials params +func (o *ListKubeVirtSubnetsNoCredentialsParams) SetStorageClassName(storageClassName *string) { + o.StorageClassName = storageClassName +} + // WriteToRequest writes these params to a swagger request func (o *ListKubeVirtSubnetsNoCredentialsParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { @@ -160,6 +174,23 @@ func (o *ListKubeVirtSubnetsNoCredentialsParams) WriteToRequest(r runtime.Client return err } + if o.StorageClassName != nil { + + // query param storageClassName + var qrStorageClassName string + + if o.StorageClassName != nil { + qrStorageClassName = *o.StorageClassName + } + qStorageClassName := qrStorageClassName + if qStorageClassName != "" { + + if err := r.SetQueryParam("storageClassName", qStorageClassName); err != nil { + return err + } + } + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } diff --git a/modules/api/pkg/test/e2e/utils/apiclient/client/kubevirt/list_project_kubevirt_subnets_parameters.go b/modules/api/pkg/test/e2e/utils/apiclient/client/kubevirt/list_project_kubevirt_subnets_parameters.go index 4521a4a988..deb8e29c09 100644 --- a/modules/api/pkg/test/e2e/utils/apiclient/client/kubevirt/list_project_kubevirt_subnets_parameters.go +++ b/modules/api/pkg/test/e2e/utils/apiclient/client/kubevirt/list_project_kubevirt_subnets_parameters.go @@ -70,6 +70,9 @@ type ListProjectKubevirtSubnetsParams struct { // Kubeconfig. Kubeconfig *string + // StorageClassName. + StorageClassName *string + // VPCName. VPCName *string @@ -162,6 +165,17 @@ func (o *ListProjectKubevirtSubnetsParams) SetKubeconfig(kubeconfig *string) { o.Kubeconfig = kubeconfig } +// WithStorageClassName adds the storageClassName to the list project kubevirt subnets params +func (o *ListProjectKubevirtSubnetsParams) WithStorageClassName(storageClassName *string) *ListProjectKubevirtSubnetsParams { + o.SetStorageClassName(storageClassName) + return o +} + +// SetStorageClassName adds the storageClassName to the list project kubevirt subnets params +func (o *ListProjectKubevirtSubnetsParams) SetStorageClassName(storageClassName *string) { + o.StorageClassName = storageClassName +} + // WithVPCName adds the vPCName to the list project kubevirt subnets params func (o *ListProjectKubevirtSubnetsParams) WithVPCName(vPCName *string) *ListProjectKubevirtSubnetsParams { o.SetVPCName(vPCName) @@ -216,6 +230,14 @@ func (o *ListProjectKubevirtSubnetsParams) WriteToRequest(r runtime.ClientReques } } + if o.StorageClassName != nil { + + // header param StorageClassName + if err := r.SetHeaderParam("StorageClassName", *o.StorageClassName); err != nil { + return err + } + } + if o.VPCName != nil { // header param VPCName diff --git a/modules/api/pkg/test/e2e/utils/apiclient/models/datacenter_spec_kubevirt.go b/modules/api/pkg/test/e2e/utils/apiclient/models/datacenter_spec_kubevirt.go index 925e3a594b..8621589981 100644 --- a/modules/api/pkg/test/e2e/utils/apiclient/models/datacenter_spec_kubevirt.go +++ b/modules/api/pkg/test/e2e/utils/apiclient/models/datacenter_spec_kubevirt.go @@ -45,6 +45,11 @@ type DatacenterSpecKubevirt struct { // kubevirt- InfraStorageClasses []*KubeVirtInfraStorageClass `json:"infraStorageClasses"` + // Optional: MatchSubnetAndStorageLocation if set to true, the region and zone of the subnet and storage class must match. For + // example, if the storage class has the region `eu` and zone was `central`, the subnet must be in the same region and zone. + // otherwise KKP will reject the creation of the machine deployment and eventually the cluster. + MatchSubnetAndStorageLocation bool `json:"matchSubnetAndStorageLocation,omitempty"` + // csi driver operator CsiDriverOperator *KubeVirtCSIDriverOperator `json:"csiDriverOperator,omitempty"` diff --git a/modules/api/pkg/test/e2e/utils/apiclient/models/subnet.go b/modules/api/pkg/test/e2e/utils/apiclient/models/subnet.go index 714e0a1907..36487cb082 100644 --- a/modules/api/pkg/test/e2e/utils/apiclient/models/subnet.go +++ b/modules/api/pkg/test/e2e/utils/apiclient/models/subnet.go @@ -19,6 +19,14 @@ type Subnet struct { // name Name string `json:"name,omitempty"` + + // Regions represents a larger domain, made up of one or more zones. It is uncommon for Kubernetes clusters + // to span multiple regions + Regions []string `json:"regions"` + + // Zones represent a logical failure domain. It is common for Kubernetes clusters to span multiple zones + // for increased availability + Zones []string `json:"zones"` } // Validate validates this subnet From e0ee04ae88bad298f6df3daab799782c5e22a5fa Mon Sep 17 00:00:00 2001 From: Kubermatic Bot <41968677+kubermatic-bot@users.noreply.github.com> Date: Tue, 6 May 2025 00:10:57 +0200 Subject: [PATCH 11/42] make subenets required on select a VPC (#7306) Co-authored-by: ahmadhamzh --- modules/api/cmd/kubermatic-api/swagger.json | 48 ++++- modules/api/pkg/api/v1/types.go | 2 + .../api/pkg/handler/v2/provider/kubevirt.go | 4 +- modules/api/pkg/handler/v2/routes_v2.go | 2 +- .../client/kubevirt/kubevirt_client.go | 42 ++++- ...e_virt_subnets_no_credentials_responses.go | 8 +- ...e_virt_v_p_cs_no_credentials_parameters.go | 167 +++++++++++++++++ ...be_virt_v_p_cs_no_credentials_responses.go | 175 ++++++++++++++++++ .../models/public_kubevirt_cloud_spec.go | 3 + .../services/node-data/provider/kubevirt.ts | 36 +++- .../app/core/services/provider/kubevirt.ts | 7 +- .../core/services/wizard/provider/kubevirt.ts | 13 +- .../basic/provider/kubevirt/component.ts | 21 ++- .../basic/provider/kubevirt/template.html | 4 +- 14 files changed, 506 insertions(+), 26 deletions(-) create mode 100644 modules/api/pkg/test/e2e/utils/apiclient/client/kubevirt/list_kube_virt_v_p_cs_no_credentials_parameters.go create mode 100644 modules/api/pkg/test/e2e/utils/apiclient/client/kubevirt/list_kube_virt_v_p_cs_no_credentials_responses.go diff --git a/modules/api/cmd/kubermatic-api/swagger.json b/modules/api/cmd/kubermatic-api/swagger.json index aea1d40baf..3ba3b029bc 100644 --- a/modules/api/cmd/kubermatic-api/swagger.json +++ b/modules/api/cmd/kubermatic-api/swagger.json @@ -12755,7 +12755,7 @@ } } }, - "/api/v2/projects/{project_id}/clusters/{cluster_id}/providers/kubevirt/vpcs": { + "/api/v2/projects/{project_id}/clusters/{cluster_id}/providers/kubevirt/subnets": { "get": { "produces": [ "application/json" @@ -12803,6 +12803,48 @@ } } }, + "/api/v2/projects/{project_id}/clusters/{cluster_id}/providers/kubevirt/vpcs": { + "get": { + "description": "List VPCs for a cluster", + "produces": [ + "application/json" + ], + "tags": [ + "kubevirt" + ], + "operationId": "listKubeVirtVPCsNoCredentials", + "parameters": [ + { + "type": "string", + "x-go-name": "ProjectID", + "name": "project_id", + "in": "path", + "required": true + }, + { + "type": "string", + "x-go-name": "ClusterID", + "name": "cluster_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "KubeVirtVPCList", + "schema": { + "$ref": "#/definitions/KubeVirtVPCList" + } + }, + "default": { + "description": "errorResponse", + "schema": { + "$ref": "#/definitions/errorResponse" + } + } + } + } + }, "/api/v2/projects/{project_id}/clusters/{cluster_id}/providers/nutanix/categories": { "get": { "description": "Lists available Nutanix categories", @@ -38675,6 +38717,10 @@ "$ref": "#/definitions/PreAllocatedDataVolume" }, "x-go-name": "PreAllocatedDataVolumes" + }, + "vpcName": { + "type": "string", + "x-go-name": "VPCName" } }, "x-go-package": "k8c.io/dashboard/v2/pkg/api/v1" diff --git a/modules/api/pkg/api/v1/types.go b/modules/api/pkg/api/v1/types.go index fbedf79d8b..6ec23eface 100644 --- a/modules/api/pkg/api/v1/types.go +++ b/modules/api/pkg/api/v1/types.go @@ -1346,6 +1346,7 @@ func newPublicGCPCloudSpec(internal *kubermaticv1.GCPCloudSpec) (public *PublicG // PublicKubevirtCloudSpec is a public counterpart of apiv1.KubevirtCloudSpec. type PublicKubevirtCloudSpec struct { + VPCName string `json:"vpcName,omitempty"` PreAllocatedDataVolumes []kubermaticv1.PreAllocatedDataVolume `json:"preAllocatedDataVolumes,omitempty"` } @@ -1355,6 +1356,7 @@ func newPublicKubevirtCloudSpec(internal *kubermaticv1.KubevirtCloudSpec) (publi } return &PublicKubevirtCloudSpec{ + VPCName: internal.VPCName, PreAllocatedDataVolumes: internal.PreAllocatedDataVolumes, } } diff --git a/modules/api/pkg/handler/v2/provider/kubevirt.go b/modules/api/pkg/handler/v2/provider/kubevirt.go index 5ad04347e9..944d847b48 100644 --- a/modules/api/pkg/handler/v2/provider/kubevirt.go +++ b/modules/api/pkg/handler/v2/provider/kubevirt.go @@ -87,7 +87,7 @@ type KubeVirtGenericNoCredentialReq struct { // swagger:parameters listKubeVirtSubnetsNoCredentials type KubeVirtSubnetsNoCredentialReq struct { cluster.GetClusterReq - + // in: query StorageClassName string `json:"storageClassName,omitempty"` } @@ -497,7 +497,7 @@ func DecodeKubeVirtSubnetsNoCredentialReq(c context.Context, r *http.Request) (i } req.ProjectReq = pr.(common.ProjectReq) - req.StorageClassName = mux.Vars(r)["storageClassName"] + req.StorageClassName = r.URL.Query().Get("storageClassName") return req, nil } diff --git a/modules/api/pkg/handler/v2/routes_v2.go b/modules/api/pkg/handler/v2/routes_v2.go index 43897b3e1e..a95fcdf616 100644 --- a/modules/api/pkg/handler/v2/routes_v2.go +++ b/modules/api/pkg/handler/v2/routes_v2.go @@ -4967,7 +4967,7 @@ func (r Routing) listKubeVirtVPCsNoCredentials() http.Handler { ) } -// swagger:route GET /api/v2/projects/{project_id}/clusters/{cluster_id}/providers/kubevirt/vpcs kubevirt listKubeVirtSubnetsNoCredentials +// swagger:route GET /api/v2/projects/{project_id}/clusters/{cluster_id}/providers/kubevirt/subnets kubevirt listKubeVirtSubnetsNoCredentials // // List Subnets for a VPC associated with a cluster. // diff --git a/modules/api/pkg/test/e2e/utils/apiclient/client/kubevirt/kubevirt_client.go b/modules/api/pkg/test/e2e/utils/apiclient/client/kubevirt/kubevirt_client.go index 723d4774ef..98e4ccf329 100644 --- a/modules/api/pkg/test/e2e/utils/apiclient/client/kubevirt/kubevirt_client.go +++ b/modules/api/pkg/test/e2e/utils/apiclient/client/kubevirt/kubevirt_client.go @@ -40,6 +40,8 @@ type ClientService interface { ListKubeVirtSubnetsNoCredentials(params *ListKubeVirtSubnetsNoCredentialsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListKubeVirtSubnetsNoCredentialsOK, error) + ListKubeVirtVPCsNoCredentials(params *ListKubeVirtVPCsNoCredentialsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListKubeVirtVPCsNoCredentialsOK, error) + ListKubevirtImages(params *ListKubevirtImagesParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListKubevirtImagesOK, error) ListKubevirtStorageClassesNoCredentials(params *ListKubevirtStorageClassesNoCredentialsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListKubevirtStorageClassesNoCredentialsOK, error) @@ -258,7 +260,7 @@ func (a *Client) ListKubeVirtSubnetsNoCredentials(params *ListKubeVirtSubnetsNoC op := &runtime.ClientOperation{ ID: "listKubeVirtSubnetsNoCredentials", Method: "GET", - PathPattern: "/api/v2/projects/{project_id}/clusters/{cluster_id}/providers/kubevirt/vpcs", + PathPattern: "/api/v2/projects/{project_id}/clusters/{cluster_id}/providers/kubevirt/subnets", ProducesMediaTypes: []string{"application/json"}, ConsumesMediaTypes: []string{"application/json"}, Schemes: []string{"https"}, @@ -285,6 +287,44 @@ func (a *Client) ListKubeVirtSubnetsNoCredentials(params *ListKubeVirtSubnetsNoC return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) } +/* +ListKubeVirtVPCsNoCredentials List VPCs for a cluster +*/ +func (a *Client) ListKubeVirtVPCsNoCredentials(params *ListKubeVirtVPCsNoCredentialsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListKubeVirtVPCsNoCredentialsOK, error) { + // TODO: Validate the params before sending + if params == nil { + params = NewListKubeVirtVPCsNoCredentialsParams() + } + op := &runtime.ClientOperation{ + ID: "listKubeVirtVPCsNoCredentials", + Method: "GET", + PathPattern: "/api/v2/projects/{project_id}/clusters/{cluster_id}/providers/kubevirt/vpcs", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"https"}, + Params: params, + Reader: &ListKubeVirtVPCsNoCredentialsReader{formats: a.formats}, + AuthInfo: authInfo, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + success, ok := result.(*ListKubeVirtVPCsNoCredentialsOK) + if ok { + return success, nil + } + // unexpected success response + unexpectedSuccess := result.(*ListKubeVirtVPCsNoCredentialsDefault) + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + /* ListKubevirtImages List KubeVirt images */ diff --git a/modules/api/pkg/test/e2e/utils/apiclient/client/kubevirt/list_kube_virt_subnets_no_credentials_responses.go b/modules/api/pkg/test/e2e/utils/apiclient/client/kubevirt/list_kube_virt_subnets_no_credentials_responses.go index f8c85053de..20f1c911c1 100644 --- a/modules/api/pkg/test/e2e/utils/apiclient/client/kubevirt/list_kube_virt_subnets_no_credentials_responses.go +++ b/modules/api/pkg/test/e2e/utils/apiclient/client/kubevirt/list_kube_virt_subnets_no_credentials_responses.go @@ -81,11 +81,11 @@ func (o *ListKubeVirtSubnetsNoCredentialsOK) IsCode(code int) bool { } func (o *ListKubeVirtSubnetsNoCredentialsOK) Error() string { - return fmt.Sprintf("[GET /api/v2/projects/{project_id}/clusters/{cluster_id}/providers/kubevirt/vpcs][%d] listKubeVirtSubnetsNoCredentialsOK %+v", 200, o.Payload) + return fmt.Sprintf("[GET /api/v2/projects/{project_id}/clusters/{cluster_id}/providers/kubevirt/subnets][%d] listKubeVirtSubnetsNoCredentialsOK %+v", 200, o.Payload) } func (o *ListKubeVirtSubnetsNoCredentialsOK) String() string { - return fmt.Sprintf("[GET /api/v2/projects/{project_id}/clusters/{cluster_id}/providers/kubevirt/vpcs][%d] listKubeVirtSubnetsNoCredentialsOK %+v", 200, o.Payload) + return fmt.Sprintf("[GET /api/v2/projects/{project_id}/clusters/{cluster_id}/providers/kubevirt/subnets][%d] listKubeVirtSubnetsNoCredentialsOK %+v", 200, o.Payload) } func (o *ListKubeVirtSubnetsNoCredentialsOK) GetPayload() models.KubeVirtSubnetList { @@ -151,11 +151,11 @@ func (o *ListKubeVirtSubnetsNoCredentialsDefault) IsCode(code int) bool { } func (o *ListKubeVirtSubnetsNoCredentialsDefault) Error() string { - return fmt.Sprintf("[GET /api/v2/projects/{project_id}/clusters/{cluster_id}/providers/kubevirt/vpcs][%d] listKubeVirtSubnetsNoCredentials default %+v", o._statusCode, o.Payload) + return fmt.Sprintf("[GET /api/v2/projects/{project_id}/clusters/{cluster_id}/providers/kubevirt/subnets][%d] listKubeVirtSubnetsNoCredentials default %+v", o._statusCode, o.Payload) } func (o *ListKubeVirtSubnetsNoCredentialsDefault) String() string { - return fmt.Sprintf("[GET /api/v2/projects/{project_id}/clusters/{cluster_id}/providers/kubevirt/vpcs][%d] listKubeVirtSubnetsNoCredentials default %+v", o._statusCode, o.Payload) + return fmt.Sprintf("[GET /api/v2/projects/{project_id}/clusters/{cluster_id}/providers/kubevirt/subnets][%d] listKubeVirtSubnetsNoCredentials default %+v", o._statusCode, o.Payload) } func (o *ListKubeVirtSubnetsNoCredentialsDefault) GetPayload() *models.ErrorResponse { diff --git a/modules/api/pkg/test/e2e/utils/apiclient/client/kubevirt/list_kube_virt_v_p_cs_no_credentials_parameters.go b/modules/api/pkg/test/e2e/utils/apiclient/client/kubevirt/list_kube_virt_v_p_cs_no_credentials_parameters.go new file mode 100644 index 0000000000..7acc775043 --- /dev/null +++ b/modules/api/pkg/test/e2e/utils/apiclient/client/kubevirt/list_kube_virt_v_p_cs_no_credentials_parameters.go @@ -0,0 +1,167 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package kubevirt + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewListKubeVirtVPCsNoCredentialsParams creates a new ListKubeVirtVPCsNoCredentialsParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewListKubeVirtVPCsNoCredentialsParams() *ListKubeVirtVPCsNoCredentialsParams { + return &ListKubeVirtVPCsNoCredentialsParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewListKubeVirtVPCsNoCredentialsParamsWithTimeout creates a new ListKubeVirtVPCsNoCredentialsParams object +// with the ability to set a timeout on a request. +func NewListKubeVirtVPCsNoCredentialsParamsWithTimeout(timeout time.Duration) *ListKubeVirtVPCsNoCredentialsParams { + return &ListKubeVirtVPCsNoCredentialsParams{ + timeout: timeout, + } +} + +// NewListKubeVirtVPCsNoCredentialsParamsWithContext creates a new ListKubeVirtVPCsNoCredentialsParams object +// with the ability to set a context for a request. +func NewListKubeVirtVPCsNoCredentialsParamsWithContext(ctx context.Context) *ListKubeVirtVPCsNoCredentialsParams { + return &ListKubeVirtVPCsNoCredentialsParams{ + Context: ctx, + } +} + +// NewListKubeVirtVPCsNoCredentialsParamsWithHTTPClient creates a new ListKubeVirtVPCsNoCredentialsParams object +// with the ability to set a custom HTTPClient for a request. +func NewListKubeVirtVPCsNoCredentialsParamsWithHTTPClient(client *http.Client) *ListKubeVirtVPCsNoCredentialsParams { + return &ListKubeVirtVPCsNoCredentialsParams{ + HTTPClient: client, + } +} + +/* +ListKubeVirtVPCsNoCredentialsParams contains all the parameters to send to the API endpoint + + for the list kube virt v p cs no credentials operation. + + Typically these are written to a http.Request. +*/ +type ListKubeVirtVPCsNoCredentialsParams struct { + + // ClusterID. + ClusterID string + + // ProjectID. + ProjectID string + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the list kube virt v p cs no credentials params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *ListKubeVirtVPCsNoCredentialsParams) WithDefaults() *ListKubeVirtVPCsNoCredentialsParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the list kube virt v p cs no credentials params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *ListKubeVirtVPCsNoCredentialsParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the list kube virt v p cs no credentials params +func (o *ListKubeVirtVPCsNoCredentialsParams) WithTimeout(timeout time.Duration) *ListKubeVirtVPCsNoCredentialsParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the list kube virt v p cs no credentials params +func (o *ListKubeVirtVPCsNoCredentialsParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the list kube virt v p cs no credentials params +func (o *ListKubeVirtVPCsNoCredentialsParams) WithContext(ctx context.Context) *ListKubeVirtVPCsNoCredentialsParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the list kube virt v p cs no credentials params +func (o *ListKubeVirtVPCsNoCredentialsParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the list kube virt v p cs no credentials params +func (o *ListKubeVirtVPCsNoCredentialsParams) WithHTTPClient(client *http.Client) *ListKubeVirtVPCsNoCredentialsParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the list kube virt v p cs no credentials params +func (o *ListKubeVirtVPCsNoCredentialsParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithClusterID adds the clusterID to the list kube virt v p cs no credentials params +func (o *ListKubeVirtVPCsNoCredentialsParams) WithClusterID(clusterID string) *ListKubeVirtVPCsNoCredentialsParams { + o.SetClusterID(clusterID) + return o +} + +// SetClusterID adds the clusterId to the list kube virt v p cs no credentials params +func (o *ListKubeVirtVPCsNoCredentialsParams) SetClusterID(clusterID string) { + o.ClusterID = clusterID +} + +// WithProjectID adds the projectID to the list kube virt v p cs no credentials params +func (o *ListKubeVirtVPCsNoCredentialsParams) WithProjectID(projectID string) *ListKubeVirtVPCsNoCredentialsParams { + o.SetProjectID(projectID) + return o +} + +// SetProjectID adds the projectId to the list kube virt v p cs no credentials params +func (o *ListKubeVirtVPCsNoCredentialsParams) SetProjectID(projectID string) { + o.ProjectID = projectID +} + +// WriteToRequest writes these params to a swagger request +func (o *ListKubeVirtVPCsNoCredentialsParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + + // path param cluster_id + if err := r.SetPathParam("cluster_id", o.ClusterID); err != nil { + return err + } + + // path param project_id + if err := r.SetPathParam("project_id", o.ProjectID); err != nil { + return err + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/modules/api/pkg/test/e2e/utils/apiclient/client/kubevirt/list_kube_virt_v_p_cs_no_credentials_responses.go b/modules/api/pkg/test/e2e/utils/apiclient/client/kubevirt/list_kube_virt_v_p_cs_no_credentials_responses.go new file mode 100644 index 0000000000..35ca5aefe1 --- /dev/null +++ b/modules/api/pkg/test/e2e/utils/apiclient/client/kubevirt/list_kube_virt_v_p_cs_no_credentials_responses.go @@ -0,0 +1,175 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package kubevirt + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "k8c.io/dashboard/v2/pkg/test/e2e/utils/apiclient/models" +) + +// ListKubeVirtVPCsNoCredentialsReader is a Reader for the ListKubeVirtVPCsNoCredentials structure. +type ListKubeVirtVPCsNoCredentialsReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *ListKubeVirtVPCsNoCredentialsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { + switch response.Code() { + case 200: + result := NewListKubeVirtVPCsNoCredentialsOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewListKubeVirtVPCsNoCredentialsDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewListKubeVirtVPCsNoCredentialsOK creates a ListKubeVirtVPCsNoCredentialsOK with default headers values +func NewListKubeVirtVPCsNoCredentialsOK() *ListKubeVirtVPCsNoCredentialsOK { + return &ListKubeVirtVPCsNoCredentialsOK{} +} + +/* +ListKubeVirtVPCsNoCredentialsOK describes a response with status code 200, with default header values. + +KubeVirtVPCList +*/ +type ListKubeVirtVPCsNoCredentialsOK struct { + Payload models.KubeVirtVPCList +} + +// IsSuccess returns true when this list kube virt v p cs no credentials o k response has a 2xx status code +func (o *ListKubeVirtVPCsNoCredentialsOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this list kube virt v p cs no credentials o k response has a 3xx status code +func (o *ListKubeVirtVPCsNoCredentialsOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this list kube virt v p cs no credentials o k response has a 4xx status code +func (o *ListKubeVirtVPCsNoCredentialsOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this list kube virt v p cs no credentials o k response has a 5xx status code +func (o *ListKubeVirtVPCsNoCredentialsOK) IsServerError() bool { + return false +} + +// IsCode returns true when this list kube virt v p cs no credentials o k response a status code equal to that given +func (o *ListKubeVirtVPCsNoCredentialsOK) IsCode(code int) bool { + return code == 200 +} + +func (o *ListKubeVirtVPCsNoCredentialsOK) Error() string { + return fmt.Sprintf("[GET /api/v2/projects/{project_id}/clusters/{cluster_id}/providers/kubevirt/vpcs][%d] listKubeVirtVPCsNoCredentialsOK %+v", 200, o.Payload) +} + +func (o *ListKubeVirtVPCsNoCredentialsOK) String() string { + return fmt.Sprintf("[GET /api/v2/projects/{project_id}/clusters/{cluster_id}/providers/kubevirt/vpcs][%d] listKubeVirtVPCsNoCredentialsOK %+v", 200, o.Payload) +} + +func (o *ListKubeVirtVPCsNoCredentialsOK) GetPayload() models.KubeVirtVPCList { + return o.Payload +} + +func (o *ListKubeVirtVPCsNoCredentialsOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + // response payload + if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} + +// NewListKubeVirtVPCsNoCredentialsDefault creates a ListKubeVirtVPCsNoCredentialsDefault with default headers values +func NewListKubeVirtVPCsNoCredentialsDefault(code int) *ListKubeVirtVPCsNoCredentialsDefault { + return &ListKubeVirtVPCsNoCredentialsDefault{ + _statusCode: code, + } +} + +/* +ListKubeVirtVPCsNoCredentialsDefault describes a response with status code -1, with default header values. + +errorResponse +*/ +type ListKubeVirtVPCsNoCredentialsDefault struct { + _statusCode int + + Payload *models.ErrorResponse +} + +// Code gets the status code for the list kube virt v p cs no credentials default response +func (o *ListKubeVirtVPCsNoCredentialsDefault) Code() int { + return o._statusCode +} + +// IsSuccess returns true when this list kube virt v p cs no credentials default response has a 2xx status code +func (o *ListKubeVirtVPCsNoCredentialsDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this list kube virt v p cs no credentials default response has a 3xx status code +func (o *ListKubeVirtVPCsNoCredentialsDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this list kube virt v p cs no credentials default response has a 4xx status code +func (o *ListKubeVirtVPCsNoCredentialsDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this list kube virt v p cs no credentials default response has a 5xx status code +func (o *ListKubeVirtVPCsNoCredentialsDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this list kube virt v p cs no credentials default response a status code equal to that given +func (o *ListKubeVirtVPCsNoCredentialsDefault) IsCode(code int) bool { + return o._statusCode == code +} + +func (o *ListKubeVirtVPCsNoCredentialsDefault) Error() string { + return fmt.Sprintf("[GET /api/v2/projects/{project_id}/clusters/{cluster_id}/providers/kubevirt/vpcs][%d] listKubeVirtVPCsNoCredentials default %+v", o._statusCode, o.Payload) +} + +func (o *ListKubeVirtVPCsNoCredentialsDefault) String() string { + return fmt.Sprintf("[GET /api/v2/projects/{project_id}/clusters/{cluster_id}/providers/kubevirt/vpcs][%d] listKubeVirtVPCsNoCredentials default %+v", o._statusCode, o.Payload) +} + +func (o *ListKubeVirtVPCsNoCredentialsDefault) GetPayload() *models.ErrorResponse { + return o.Payload +} + +func (o *ListKubeVirtVPCsNoCredentialsDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.ErrorResponse) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} diff --git a/modules/api/pkg/test/e2e/utils/apiclient/models/public_kubevirt_cloud_spec.go b/modules/api/pkg/test/e2e/utils/apiclient/models/public_kubevirt_cloud_spec.go index 5d61017f10..c3ae4bfcfe 100644 --- a/modules/api/pkg/test/e2e/utils/apiclient/models/public_kubevirt_cloud_spec.go +++ b/modules/api/pkg/test/e2e/utils/apiclient/models/public_kubevirt_cloud_spec.go @@ -21,6 +21,9 @@ type PublicKubevirtCloudSpec struct { // pre allocated data volumes PreAllocatedDataVolumes []*PreAllocatedDataVolume `json:"preAllocatedDataVolumes"` + + // v p c name + VPCName string `json:"vpcName,omitempty"` } // Validate validates this public kubevirt cloud spec diff --git a/modules/web/src/app/core/services/node-data/provider/kubevirt.ts b/modules/web/src/app/core/services/node-data/provider/kubevirt.ts index 0b6158fc1c..67867a5875 100644 --- a/modules/web/src/app/core/services/node-data/provider/kubevirt.ts +++ b/modules/web/src/app/core/services/node-data/provider/kubevirt.ts @@ -186,9 +186,31 @@ export class NodeDataKubeVirtProvider { } } - subnets(onError: () => void = undefined, onLoadingCb: () => void = null): Observable { + subnets( + onError: () => void = undefined, + onLoadingCb: () => void = null, + storageClass?: string + ): Observable { switch (this._nodeDataService.mode) { case NodeDataMode.Wizard: + if (storageClass) { + const clusterCloudSpec = this._clusterSpecService.cluster.spec.cloud; + return this._presetService + .provider(NodeProvider.KUBEVIRT) + .kubeconfig(clusterCloudSpec.kubevirt.kubeconfig) + .credential(this._presetService.preset) + .datacenterName(clusterCloudSpec.dc) + .subnets(clusterCloudSpec.kubevirt.vpcName, onLoadingCb, storageClass) + .pipe( + catchError(_ => { + if (onError) { + onError(); + } + + return onErrorResumeNext(of([])); + }) + ); + } return this._clusterSpecService.clusterChanges .pipe(filter(_ => this._clusterSpecService.provider === NodeProvider.KUBEVIRT)) .pipe(debounceTime(this._debounce)) @@ -199,7 +221,11 @@ export class NodeDataKubeVirtProvider { .kubeconfig(cluster.spec.cloud.kubevirt.kubeconfig) .credential(this._presetService.preset) .datacenterName(cluster.spec.cloud.dc) - .subnets(cluster.spec.cloud.kubevirt?.vpcName, onLoadingCb) + .subnets( + cluster.spec.cloud.kubevirt?.vpcName, + onLoadingCb, + this._nodeDataService?.nodeData?.spec?.cloud?.kubevirt?.primaryDiskStorageClassName + ) .pipe( catchError(_ => { if (onError) { @@ -217,7 +243,11 @@ export class NodeDataKubeVirtProvider { .pipe(debounceTime(this._debounce)) .pipe(tap(project => (selectedProject = project.id))) .pipe(tap(_ => (onLoadingCb ? onLoadingCb() : null))) - .pipe(switchMap(_ => this._kubeVirtService.getSubnets(selectedProject, this._clusterSpecService.cluster.id))) + .pipe( + switchMap(_ => + this._kubeVirtService.getSubnets(selectedProject, this._clusterSpecService.cluster.id, storageClass) + ) + ) .pipe( catchError(_ => { if (onError) { diff --git a/modules/web/src/app/core/services/provider/kubevirt.ts b/modules/web/src/app/core/services/provider/kubevirt.ts index 08813a3745..6df7368ce8 100644 --- a/modules/web/src/app/core/services/provider/kubevirt.ts +++ b/modules/web/src/app/core/services/provider/kubevirt.ts @@ -45,8 +45,11 @@ export class KubeVirtService { return this._httpClient.get(url); } - getSubnets(projectID: string, clusterID: string): Observable { - const url = `${this._newRestRoot}/projects/${projectID}/clusters/${clusterID}/providers/kubevirt/subnets`; + getSubnets(projectID: string, clusterID: string, storageClass?: string): Observable { + let url = `${this._newRestRoot}/projects/${projectID}/clusters/${clusterID}/providers/kubevirt/subnets`; + if (storageClass) { + url = `${url}?storageClassName=${storageClass}`; + } return this._httpClient.get(url); } diff --git a/modules/web/src/app/core/services/wizard/provider/kubevirt.ts b/modules/web/src/app/core/services/wizard/provider/kubevirt.ts index 1ab5cbdb4e..59b5424389 100644 --- a/modules/web/src/app/core/services/wizard/provider/kubevirt.ts +++ b/modules/web/src/app/core/services/wizard/provider/kubevirt.ts @@ -22,7 +22,7 @@ import { KubeVirtVPC, } from '@shared/entity/provider/kubevirt'; import {NodeProvider} from '@shared/model/NodeProviderConstants'; -import {EMPTY, Observable} from 'rxjs'; +import {EMPTY, Observable, of} from 'rxjs'; import {Provider} from './provider'; export class KubeVirt extends Provider { @@ -103,22 +103,24 @@ export class KubeVirt extends Provider { return this._http.get(url, {headers: this._headers}); } - subnets(vpcName: string, onLoadingCb: () => void = null): Observable { + subnets(vpcName: string, onLoadingCb: () => void = null, storageClass?: string): Observable { if (!this._hasRequiredHeaders()) { return EMPTY; } // Either credential header is present or vpcName is not empty if (!this._headers.has(Provider.SharedHeader.Credential) && !vpcName) { - return EMPTY; + return of([]); } if (onLoadingCb) { onLoadingCb(); } - const headers = vpcName ? this._headers.set(KubeVirt.Header.VPCName, vpcName) : this._headers; - + let headers = vpcName ? this._headers.set(KubeVirt.Header.VPCName, vpcName) : this._headers; + if (storageClass) { + headers = headers.append(KubeVirt.Header.StorageClassName, storageClass); + } const url = `${this._newRestRoot}/projects/${this._projectID}/providers/${this._provider}/subnets`; return this._http.get(url, {headers}); } @@ -141,5 +143,6 @@ export namespace KubeVirt { Kubeconfig = 'Kubeconfig', DatacenterName = 'DatacenterName', VPCName = 'VPCName', + StorageClassName = 'StorageClassName', } } diff --git a/modules/web/src/app/node-data/basic/provider/kubevirt/component.ts b/modules/web/src/app/node-data/basic/provider/kubevirt/component.ts index 3ae90f7bb9..53c740fcad 100644 --- a/modules/web/src/app/node-data/basic/provider/kubevirt/component.ts +++ b/modules/web/src/app/node-data/basic/provider/kubevirt/component.ts @@ -24,6 +24,7 @@ import { } from '@angular/core'; import {FormBuilder, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validators} from '@angular/forms'; import {MatDialog} from '@angular/material/dialog'; +import {ClusterSpecService} from '@app/core/services/cluster-spec'; import {DynamicModule} from '@app/dynamic/module-registry'; import {InstanceDetailsDialogComponent} from '@app/node-data/basic/provider/kubevirt/instance-details/component'; import {GlobalModule} from '@core/services/global/module'; @@ -56,7 +57,7 @@ import {BaseFormValidator} from '@shared/validators/base-form.validator'; import {KUBERNETES_RESOURCE_NAME_PATTERN} from '@shared/validators/others'; import _ from 'lodash'; import {merge, Observable} from 'rxjs'; -import {filter, map, takeUntil} from 'rxjs/operators'; +import {filter, map, take, takeUntil} from 'rxjs/operators'; enum Controls { InstanceType = 'instancetype', @@ -181,7 +182,8 @@ export class KubeVirtBasicNodeDataComponent private readonly _builder: FormBuilder, private readonly _nodeDataService: NodeDataService, private readonly _cdr: ChangeDetectorRef, - private readonly _matDialog: MatDialog + private readonly _matDialog: MatDialog, + private readonly _clusterSpecService: ClusterSpecService ) { super(); @@ -267,7 +269,7 @@ export class KubeVirtBasicNodeDataComponent } }); - this._subnetsObservable.pipe(takeUntil(this._unsubscribe)).subscribe(this._setDefaultSubnet.bind(this)); + this._subnetsObservable().pipe(takeUntil(this._unsubscribe)).subscribe(this._setDefaultSubnet.bind(this)); } ngAfterViewChecked(): void { @@ -301,6 +303,10 @@ export class KubeVirtBasicNodeDataComponent return this._nodeDataService.nodeData.spec.cloud.kubevirt?.topologySpreadConstraints || []; } + get isSubnetsRequired(): boolean { + return !!this._clusterSpecService.cluster?.spec?.cloud?.kubevirt?.vpcName; + } + getInstanceTypeOptions(group: string): KubeVirtInstanceType[] { return this._instanceTypes?.instancetypes?.[group] || []; } @@ -451,8 +457,12 @@ export class KubeVirtBasicNodeDataComponent this._nodeDataService.nodeDataChanges.next(this._nodeDataService.nodeData); } - private get _subnetsObservable(): Observable { - return this._nodeDataService.kubeVirt.subnets(this._clearSubnet.bind(this), this._onSubnetLoading.bind(this)); + private _subnetsObservable(storageClass?: string): Observable { + return this._nodeDataService.kubeVirt.subnets( + this._clearSubnet.bind(this), + this._onSubnetLoading.bind(this), + storageClass + ); } private _onSubnetLoading(): void { @@ -624,6 +634,7 @@ export class KubeVirtBasicNodeDataComponent onStorageClassChange(storageClass: string): void { this._nodeDataService.nodeData.spec.cloud.kubevirt.primaryDiskStorageClassName = storageClass; + this._subnetsObservable(storageClass).pipe(take(1)).subscribe(this._setDefaultSubnet.bind(this)); this._nodeDataService.nodeDataChanges.next(this._nodeDataService.nodeData); } diff --git a/modules/web/src/app/node-data/basic/provider/kubevirt/template.html b/modules/web/src/app/node-data/basic/provider/kubevirt/template.html index 841aa45e69..c4e564df07 100644 --- a/modules/web/src/app/node-data/basic/provider/kubevirt/template.html +++ b/modules/web/src/app/node-data/basic/provider/kubevirt/template.html @@ -114,8 +114,8 @@ [options]="subnets" [formControlName]="Controls.Subnet" [label]="subnetLabel" - hint="Optional: Use specific subnet for the machines. VPC needs to be selected first to use this option." - [required]="false" + hint="Use specific subnet for the machines. VPC needs to be selected first to use this option." + [required]="isSubnetsRequired" (changed)="onSubnetChange($event)" inputLabel="Select Subnet..." filterBy="name"> From 67656cc257ccab0134fdff505475e894b63ec998 Mon Sep 17 00:00:00 2001 From: Archana Sawant Date: Tue, 6 May 2025 19:05:57 +0530 Subject: [PATCH 12/42] [release/v2.27] Bump KKP Dashboard version and dependencies for May 2025 (#7309) * [release/v2.27] Bump KKP Dashboard version and dependencies for May 2025 Signed-off-by: archups * Fixed api unit tests failures Signed-off-by: archups --------- Signed-off-by: archups --- Makefile | 2 +- modules/api/Makefile | 2 +- modules/api/cmd/kubermatic-api/swagger.json | 26 +++---- modules/api/go.mod | 26 +++---- modules/api/go.sum | 70 ++++++++++--------- .../pkg/handler/v1/cluster/cluster_test.go | 10 +-- .../pkg/handler/v2/cluster/cluster_test.go | 12 ++-- .../cluster_default/cluster_default_test.go | 8 +-- .../cluster_template/cluster_template_test.go | 12 ++-- modules/web/Makefile | 2 +- modules/web/package-lock.json | 4 +- modules/web/package.json | 2 +- 12 files changed, 89 insertions(+), 87 deletions(-) diff --git a/Makefile b/Makefile index e701eaacf2..21c2ae1501 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ SHELL = /bin/bash -eu -o pipefail export KUBERMATIC_EDITION ?= ee -KUBERMATIC_VERSION?=v2.27.3 +KUBERMATIC_VERSION?=v2.27.4 DOCKER_REPO ?= quay.io/kubermatic REPO = $(DOCKER_REPO)/dashboard$(shell [[ "$(KUBERMATIC_EDITION)" != "ce" ]] && printf -- '-%s' ${KUBERMATIC_EDITION}) IMAGE_TAG=$(shell echo $$(git rev-parse HEAD)|tr -d '\n') diff --git a/modules/api/Makefile b/modules/api/Makefile index ac293149f7..1587555afa 100644 --- a/modules/api/Makefile +++ b/modules/api/Makefile @@ -1,6 +1,6 @@ SHELL = /bin/bash -eu -o pipefail export KUBERMATIC_EDITION ?= ee -KUBERMATIC_VERSION?=v2.27.3 +KUBERMATIC_VERSION?=v2.27.4 DOCKER_REPO ?= quay.io/kubermatic REPO = $(DOCKER_REPO)/dashboard$(shell [[ "$(KUBERMATIC_EDITION)" != "ce" ]] && printf -- '-%s' ${KUBERMATIC_EDITION}) IMAGE_TAG=$(shell echo $$(git rev-parse HEAD)|tr -d '\n') diff --git a/modules/api/cmd/kubermatic-api/swagger.json b/modules/api/cmd/kubermatic-api/swagger.json index 3ba3b029bc..b05eeeca6a 100644 --- a/modules/api/cmd/kubermatic-api/swagger.json +++ b/modules/api/cmd/kubermatic-api/swagger.json @@ -12106,13 +12106,6 @@ ], "operationId": "listAWSSizesNoCredentialsV2", "parameters": [ - { - "type": "string", - "x-go-name": "Architecture", - "description": "architecture query parameter. Supports: arm64 and x64 types.", - "name": "architecture", - "in": "query" - }, { "type": "string", "x-go-name": "ProjectID", @@ -12126,6 +12119,13 @@ "name": "cluster_id", "in": "path", "required": true + }, + { + "type": "string", + "x-go-name": "Architecture", + "description": "architecture query parameter. Supports: arm64 and x64 types.", + "name": "architecture", + "in": "query" } ], "responses": { @@ -12766,12 +12766,6 @@ "summary": "List Subnets for a VPC associated with a cluster.", "operationId": "listKubeVirtSubnetsNoCredentials", "parameters": [ - { - "type": "string", - "x-go-name": "StorageClassName", - "name": "storageClassName", - "in": "query" - }, { "type": "string", "x-go-name": "ProjectID", @@ -12785,6 +12779,12 @@ "name": "cluster_id", "in": "path", "required": true + }, + { + "type": "string", + "x-go-name": "StorageClassName", + "name": "storageClassName", + "in": "query" } ], "responses": { diff --git a/modules/api/go.mod b/modules/api/go.mod index 6e89e938dd..846477df8c 100644 --- a/modules/api/go.mod +++ b/modules/api/go.mod @@ -66,11 +66,11 @@ require ( github.com/vmware/govmomi v0.46.2 go.anx.io/go-anxcloud v0.7.6 go.uber.org/zap v1.27.0 - golang.org/x/oauth2 v0.24.0 + golang.org/x/oauth2 v0.27.0 google.golang.org/api v0.209.0 gopkg.in/yaml.v3 v3.0.1 k8c.io/kubeone v1.7.3 - k8c.io/kubermatic/v2 v2.27.4-0.20250430120652-5bc371b62738 + k8c.io/kubermatic/v2 v2.27.4-0.20250506093457-a0144c86d480 k8c.io/machine-controller v1.61.1 k8c.io/operating-system-manager v1.6.5 k8c.io/reconciler v0.5.0 @@ -143,7 +143,7 @@ require ( github.com/blang/semver/v4 v4.0.0 // indirect github.com/cert-manager/cert-manager v1.16.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/containerd/containerd v1.7.24 // indirect + github.com/containerd/containerd v1.7.27 // indirect github.com/containerd/errdefs v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect @@ -262,21 +262,21 @@ require ( go.opentelemetry.io/otel/metric v1.32.0 // indirect go.opentelemetry.io/otel/trace v1.32.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.32.0 // indirect + golang.org/x/crypto v0.36.0 // indirect golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/net v0.33.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/term v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect golang.org/x/time v0.8.0 // indirect golang.org/x/tools v0.27.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect - google.golang.org/grpc v1.68.0 // indirect - google.golang.org/protobuf v1.35.2 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250212204824-5a70512c5d8b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250212204824-5a70512c5d8b // indirect + google.golang.org/grpc v1.70.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/modules/api/go.sum b/modules/api/go.sum index 163a2a9688..7518a614ba 100644 --- a/modules/api/go.sum +++ b/modules/api/go.sum @@ -175,10 +175,10 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= -github.com/containerd/containerd v1.7.24 h1:zxszGrGjrra1yYJW/6rhm9cJ1ZQ8rkKBR48brqsa7nA= -github.com/containerd/containerd v1.7.24/go.mod h1:7QUzfURqZWCZV7RLNEn1XjUCQLEf0bkaK4GjUaZehxw= -github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= -github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/containerd/containerd v1.7.27 h1:yFyEyojddO3MIGVER2xJLWoCIn+Up4GaHFquP7hsFII= +github.com/containerd/containerd v1.7.27/go.mod h1:xZmPnl75Vc+BLGt4MIfu6bp+fy03gdHAn9bz+FreFR0= +github.com/containerd/continuity v0.4.4 h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34PlCpMKII= +github.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= @@ -424,8 +424,8 @@ github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= @@ -788,12 +788,14 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 h1:FFeLy go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0/go.mod h1:TMu73/k1CP8nBUpDLc71Wj/Kf7ZS9FK5b53VapRsP9o= go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= -go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= -go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= +go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= @@ -825,8 +827,8 @@ golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOM golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= 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= @@ -903,11 +905,11 @@ golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= -golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -922,8 +924,8 @@ golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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= @@ -979,8 +981,8 @@ golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1003,8 +1005,8 @@ golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= 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= @@ -1022,8 +1024,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1077,17 +1079,17 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a h1:OAiGFfOiA0v9MRYsSidp3ubZaBnteRUyn3xB2ZQ5G/E= -google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a h1:hgh8P4EuoxpsuKMXX/To36nOFD7vixReXgn8lPGnt+o= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/genproto/googleapis/api v0.0.0-20250212204824-5a70512c5d8b h1:i+d0RZa8Hs2L/MuaOQYI+krthcxdEbEM2N+Tf3kJ4zk= +google.golang.org/genproto/googleapis/api v0.0.0-20250212204824-5a70512c5d8b/go.mod h1:iYONQfRdizDB8JJBybql13nArx91jcUk7zCXEsOofM4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250212204824-5a70512c5d8b h1:FQtJ1MxbXoIIrZHZ33M+w5+dAP9o86rgpjoKr/ZmT7k= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250212204824-5a70512c5d8b/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= -google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= +google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= +google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1104,8 +1106,8 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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= @@ -1145,8 +1147,8 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8c.io/kubeone v1.7.2 h1:uLH19VEp1S5j4f3UwQP4CLHErJ23UiJM2MnbufNWDgI= k8c.io/kubeone v1.7.2/go.mod h1:9v2VFz/+l36cW65kd5YufEYHunbKlJ6P8SBakj05xgM= -k8c.io/kubermatic/v2 v2.27.4-0.20250430120652-5bc371b62738 h1:y+JiTT/dUDbQFXAqqq2ZQHzdpCujg+smSXHm7FIgz+k= -k8c.io/kubermatic/v2 v2.27.4-0.20250430120652-5bc371b62738/go.mod h1:NbOQXhqp0TcszS2zJHmvlCPBNY4XYMMAKg3UyA6pjM8= +k8c.io/kubermatic/v2 v2.27.4-0.20250506093457-a0144c86d480 h1:/7pobTFIaXpB5VfHVWbUaWGPMZvmo9T9uOSLTKtTlZA= +k8c.io/kubermatic/v2 v2.27.4-0.20250506093457-a0144c86d480/go.mod h1:6gxHUzvh33Vwyq4kcg5BjxsiHSATdPH44LGpk1TxHPU= k8c.io/machine-controller v1.61.1 h1:Zy3kg9t0WrDN0Wo3y/pAJp7jdkThcJt070f0fAL9MVc= k8c.io/machine-controller v1.61.1/go.mod h1:ZGDFyUeEp66RHcNB5Ki/OJyFdZFgo9dkHJ9s6YJWPcg= k8c.io/operating-system-manager v1.6.5 h1:F7oBJKEv2t3uzG8k4e9s9j9vCAvXN1FGEkqV0+dMh60= diff --git a/modules/api/pkg/handler/v1/cluster/cluster_test.go b/modules/api/pkg/handler/v1/cluster/cluster_test.go index d99cb2095f..eaf6641302 100644 --- a/modules/api/pkg/handler/v1/cluster/cluster_test.go +++ b/modules/api/pkg/handler/v1/cluster/cluster_test.go @@ -635,7 +635,7 @@ func TestCreateClusterEndpoint(t *testing.T) { { Name: "scenario 2: cluster is created when valid spec and ssh key are passed", Body: fmt.Sprintf(`{"cluster":{"name":"keen-snyder","spec":{"version":"%s","cloud":{"fake":{"token":"dummy_token"},"dc":"fake-dc"},"exposeStrategy":"NodePort"}}}`, version), - ExpectedResponse: fmt.Sprintf(`{"id":"%%s","name":"keen-snyder","annotations":{"kubermatic.io/initial-application-installations-request":"[]"},"creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":"Unsupported"}}`, version), + ExpectedResponse: fmt.Sprintf(`{"id":"%%s","name":"keen-snyder","annotations":{"kubermatic.io/initial-application-installations-request":"[]"},"creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.9"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":"Unsupported"}}`, version), RewriteClusterID: true, HTTPStatus: http.StatusCreated, ProjectToSync: test.GenDefaultProject().Name, ExistingKubermaticObjs: test.GenDefaultKubermaticObjects( @@ -720,7 +720,7 @@ func TestCreateClusterEndpoint(t *testing.T) { { Name: "scenario 10a: create a cluster in email-restricted datacenter, to which the user does have access - legacy single domain restriction with requiredEmailDomains", Body: fmt.Sprintf(`{"cluster":{"name":"keen-snyder","spec":{"version":"%s","cloud":{"fake":{"token":"dummy_token"},"dc":"restricted-fake-dc"}}}}`, version), - ExpectedResponse: fmt.Sprintf(`{"id":"%%s","name":"keen-snyder","annotations":{"kubermatic.io/initial-application-installations-request":"[]"},"creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"restricted-fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":"Unsupported"}}`, version), + ExpectedResponse: fmt.Sprintf(`{"id":"%%s","name":"keen-snyder","annotations":{"kubermatic.io/initial-application-installations-request":"[]"},"creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"restricted-fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.9"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":"Unsupported"}}`, version), RewriteClusterID: true, HTTPStatus: http.StatusCreated, ProjectToSync: test.GenDefaultProject().Name, @@ -734,7 +734,7 @@ func TestCreateClusterEndpoint(t *testing.T) { { Name: "scenario 10b: create a cluster in email-restricted datacenter, to which the user does have access - domain array restriction with `requiredEmailDomains`", Body: fmt.Sprintf(`{"cluster":{"name":"keen-snyder","spec":{"version":"%s","cloud":{"fake":{"token":"dummy_token"},"dc":"restricted-fake-dc2"}}}}`, version), - ExpectedResponse: fmt.Sprintf(`{"id":"%%s","name":"keen-snyder","annotations":{"kubermatic.io/initial-application-installations-request":"[]"},"creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"restricted-fake-dc2","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":"Unsupported"}}`, version), + ExpectedResponse: fmt.Sprintf(`{"id":"%%s","name":"keen-snyder","annotations":{"kubermatic.io/initial-application-installations-request":"[]"},"creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"restricted-fake-dc2","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.9"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":"Unsupported"}}`, version), RewriteClusterID: true, HTTPStatus: http.StatusCreated, ProjectToSync: test.GenDefaultProject().Name, @@ -748,7 +748,7 @@ func TestCreateClusterEndpoint(t *testing.T) { { Name: "scenario 11: create a cluster in audit-logging-enforced datacenter, without explicitly enabling audit logging", Body: fmt.Sprintf(`{"cluster":{"name":"keen-snyder","spec":{"version":"%s","cloud":{"fake":{"token":"dummy_token"},"dc":"audited-dc"}}}}`, version), - ExpectedResponse: fmt.Sprintf(`{"id":"%%s","name":"keen-snyder","annotations":{"kubermatic.io/initial-application-installations-request":"[]"},"creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"audited-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"auditLogging":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":"Unsupported"}}`, version), + ExpectedResponse: fmt.Sprintf(`{"id":"%%s","name":"keen-snyder","annotations":{"kubermatic.io/initial-application-installations-request":"[]"},"creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"audited-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"auditLogging":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.9"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":"Unsupported"}}`, version), RewriteClusterID: true, HTTPStatus: http.StatusCreated, ProjectToSync: test.GenDefaultProject().Name, @@ -762,7 +762,7 @@ func TestCreateClusterEndpoint(t *testing.T) { { Name: "scenario 12: the admin user can create cluster for any project", Body: fmt.Sprintf(`{"cluster":{"name":"keen-snyder","spec":{"version":"%s","cloud":{"fake":{"token":"dummy_token"},"dc":"fake-dc"}}}}`, version), - ExpectedResponse: fmt.Sprintf(`{"id":"%%s","name":"keen-snyder","annotations":{"kubermatic.io/initial-application-installations-request":"[]"},"creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":"Unsupported"}}`, version), + ExpectedResponse: fmt.Sprintf(`{"id":"%%s","name":"keen-snyder","annotations":{"kubermatic.io/initial-application-installations-request":"[]"},"creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.9"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":"Unsupported"}}`, version), RewriteClusterID: true, HTTPStatus: http.StatusCreated, ProjectToSync: test.GenDefaultProject().Name, diff --git a/modules/api/pkg/handler/v2/cluster/cluster_test.go b/modules/api/pkg/handler/v2/cluster/cluster_test.go index dd99f31cda..ba9f4d2ab8 100644 --- a/modules/api/pkg/handler/v2/cluster/cluster_test.go +++ b/modules/api/pkg/handler/v2/cluster/cluster_test.go @@ -87,7 +87,7 @@ func TestCreateClusterEndpoint(t *testing.T) { { Name: "scenario 2: cluster is created when valid spec and ssh key are passed", Body: fmt.Sprintf(`{"cluster":{"name":"keen-snyder","spec":{"version":"%s","cloud":{"fake":{"token":"dummy_token"},"dc":"fake-dc"}}}}`, version), - ExpectedResponse: fmt.Sprintf(`{"id":"%%s","name":"keen-snyder","annotations":{"kubermatic.io/initial-application-installations-request":"[]"},"creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":"Unsupported"}}`, version), + ExpectedResponse: fmt.Sprintf(`{"id":"%%s","name":"keen-snyder","annotations":{"kubermatic.io/initial-application-installations-request":"[]"},"creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.9"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":"Unsupported"}}`, version), RewriteClusterID: true, HTTPStatus: http.StatusCreated, ProjectToSync: test.GenDefaultProject().Name, @@ -173,7 +173,7 @@ func TestCreateClusterEndpoint(t *testing.T) { { Name: "scenario 10a: create a cluster in email-restricted datacenter, to which the user does have access - legacy single domain restriction with requiredEmailDomains", Body: fmt.Sprintf(`{"cluster":{"name":"keen-snyder","spec":{"version":"%s","cloud":{"fake":{"token":"dummy_token"},"dc":"restricted-fake-dc"}}}}`, version), - ExpectedResponse: fmt.Sprintf(`{"id":"%%s","name":"keen-snyder","annotations":{"kubermatic.io/initial-application-installations-request":"[]"},"creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"restricted-fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":"Unsupported"}}`, version), + ExpectedResponse: fmt.Sprintf(`{"id":"%%s","name":"keen-snyder","annotations":{"kubermatic.io/initial-application-installations-request":"[]"},"creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"restricted-fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.9"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":"Unsupported"}}`, version), RewriteClusterID: true, HTTPStatus: http.StatusCreated, ProjectToSync: test.GenDefaultProject().Name, @@ -187,7 +187,7 @@ func TestCreateClusterEndpoint(t *testing.T) { { Name: "scenario 10b: create a cluster in email-restricted datacenter, to which the user does have access - domain array restriction with `requiredEmailDomains`", Body: fmt.Sprintf(`{"cluster":{"name":"keen-snyder","spec":{"version":"%s","cloud":{"fake":{"token":"dummy_token"},"dc":"restricted-fake-dc2"}}}}`, version), - ExpectedResponse: fmt.Sprintf(`{"id":"%%s","name":"keen-snyder","annotations":{"kubermatic.io/initial-application-installations-request":"[]"},"creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"restricted-fake-dc2","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":"Unsupported"}}`, version), + ExpectedResponse: fmt.Sprintf(`{"id":"%%s","name":"keen-snyder","annotations":{"kubermatic.io/initial-application-installations-request":"[]"},"creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"restricted-fake-dc2","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.9"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":"Unsupported"}}`, version), RewriteClusterID: true, HTTPStatus: http.StatusCreated, ProjectToSync: test.GenDefaultProject().Name, @@ -201,7 +201,7 @@ func TestCreateClusterEndpoint(t *testing.T) { { Name: "scenario 11: create a cluster in audit-logging-enforced datacenter, without explicitly enabling audit logging", Body: fmt.Sprintf(`{"cluster":{"name":"keen-snyder","spec":{"version":"%s","cloud":{"fake":{"token":"dummy_token"},"dc":"audited-dc"}}}}`, version), - ExpectedResponse: fmt.Sprintf(`{"id":"%%s","name":"keen-snyder","annotations":{"kubermatic.io/initial-application-installations-request":"[]"},"creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"audited-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"auditLogging":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":"Unsupported"}}`, version), + ExpectedResponse: fmt.Sprintf(`{"id":"%%s","name":"keen-snyder","annotations":{"kubermatic.io/initial-application-installations-request":"[]"},"creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"audited-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"auditLogging":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.9"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":"Unsupported"}}`, version), RewriteClusterID: true, HTTPStatus: http.StatusCreated, ProjectToSync: test.GenDefaultProject().Name, @@ -215,7 +215,7 @@ func TestCreateClusterEndpoint(t *testing.T) { { Name: "scenario 12: the admin user can create cluster for any project", Body: fmt.Sprintf(`{"cluster":{"name":"keen-snyder","spec":{"version":"%s","cloud":{"fake":{"token":"dummy_token"},"dc":"fake-dc"}}}}`, version), - ExpectedResponse: fmt.Sprintf(`{"id":"%%s","name":"keen-snyder","annotations":{"kubermatic.io/initial-application-installations-request":"[]"},"creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":"Unsupported"}}`, version), + ExpectedResponse: fmt.Sprintf(`{"id":"%%s","name":"keen-snyder","annotations":{"kubermatic.io/initial-application-installations-request":"[]"},"creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.9"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":"Unsupported"}}`, version), RewriteClusterID: true, HTTPStatus: http.StatusCreated, ProjectToSync: test.GenDefaultProject().Name, @@ -268,7 +268,7 @@ func TestCreateClusterEndpoint(t *testing.T) { { Name: "scenario 15: cluster is created with preset annotation", Body: fmt.Sprintf(`{"cluster":{"name":"keen-snyder","credential":"fake","spec":{"version":"%s","cloud":{"fake":{},"dc":"fake-dc"}}}}`, version), - ExpectedResponse: fmt.Sprintf(`{"id":"%%s","name":"keen-snyder","annotations":{"kubermatic.io/initial-application-installations-request":"[]","presetName":"fake"},"creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":"Unsupported"}}`, version), + ExpectedResponse: fmt.Sprintf(`{"id":"%%s","name":"keen-snyder","annotations":{"kubermatic.io/initial-application-installations-request":"[]","presetName":"fake"},"creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.9"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":"Unsupported"}}`, version), RewriteClusterID: true, HTTPStatus: http.StatusCreated, ProjectToSync: test.GenDefaultProject().Name, diff --git a/modules/api/pkg/handler/v2/cluster_default/cluster_default_test.go b/modules/api/pkg/handler/v2/cluster_default/cluster_default_test.go index 1c2725c64c..f541a3c0cd 100644 --- a/modules/api/pkg/handler/v2/cluster_default/cluster_default_test.go +++ b/modules/api/pkg/handler/v2/cluster_default/cluster_default_test.go @@ -50,7 +50,7 @@ func TestGetEndpoint(t *testing.T) { ), ExistingAPIUser: test.GenDefaultAPIUser(), ExpectedHTTPStatusCode: http.StatusOK, - ExpectedResponse: `{"name":"","creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","aws":{}},"version":"v1.31.8","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20","fd02::/120"]},"pods":{"cidrBlocks":["172.25.0.0/16","fd01::/48"]},"nodeCidrMaskSizeIPv4":24,"nodeCidrMaskSizeIPv6":64,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":""}}`, + ExpectedResponse: `{"name":"","creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","aws":{}},"version":"v1.31.8","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20","fd02::/120"]},"pods":{"cidrBlocks":["172.25.0.0/16","fd01::/48"]},"nodeCidrMaskSizeIPv4":24,"nodeCidrMaskSizeIPv6":64,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.9"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":""}}`, }, { Name: "Default cluster for Azure", @@ -61,7 +61,7 @@ func TestGetEndpoint(t *testing.T) { ), ExistingAPIUser: test.GenDefaultAPIUser(), ExpectedHTTPStatusCode: http.StatusOK, - ExpectedResponse: `{"name":"","creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","azure":{"assignAvailabilitySet":null}},"version":"v1.31.8","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20","fd02::/120"]},"pods":{"cidrBlocks":["172.25.0.0/16","fd01::/48"]},"nodeCidrMaskSizeIPv4":24,"nodeCidrMaskSizeIPv6":64,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":""}}`, + ExpectedResponse: `{"name":"","creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","azure":{"assignAvailabilitySet":null}},"version":"v1.31.8","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20","fd02::/120"]},"pods":{"cidrBlocks":["172.25.0.0/16","fd01::/48"]},"nodeCidrMaskSizeIPv4":24,"nodeCidrMaskSizeIPv6":64,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.9"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":""}}`, }, { Name: "Default cluster for vSphere", @@ -72,7 +72,7 @@ func TestGetEndpoint(t *testing.T) { ), ExistingAPIUser: test.GenDefaultAPIUser(), ExpectedHTTPStatusCode: http.StatusOK, - ExpectedResponse: `{"name":"","creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","vsphere":{}},"version":"v1.31.8","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20","fd02::/120"]},"pods":{"cidrBlocks":["172.25.0.0/16","fd01::/48"]},"nodeCidrMaskSizeIPv4":24,"nodeCidrMaskSizeIPv6":64,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":""}}`, + ExpectedResponse: `{"name":"","creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","vsphere":{}},"version":"v1.31.8","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20","fd02::/120"]},"pods":{"cidrBlocks":["172.25.0.0/16","fd01::/48"]},"nodeCidrMaskSizeIPv4":24,"nodeCidrMaskSizeIPv6":64,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.9"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":""}}`, }, { Name: "Default cluster for GCP", @@ -83,7 +83,7 @@ func TestGetEndpoint(t *testing.T) { ), ExistingAPIUser: test.GenDefaultAPIUser(), ExpectedHTTPStatusCode: http.StatusOK, - ExpectedResponse: `{"name":"","creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","gcp":{}},"version":"v1.31.8","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20","fd02::/120"]},"pods":{"cidrBlocks":["172.25.0.0/16","fd01::/48"]},"nodeCidrMaskSizeIPv4":24,"nodeCidrMaskSizeIPv6":64,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":""}}`, + ExpectedResponse: `{"name":"","creationTimestamp":"0001-01-01T00:00:00Z","type":"kubernetes","spec":{"cloud":{"dc":"fake-dc","gcp":{}},"version":"v1.31.8","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20","fd02::/120"]},"pods":{"cidrBlocks":["172.25.0.0/16","fd01::/48"]},"nodeCidrMaskSizeIPv4":24,"nodeCidrMaskSizeIPv6":64,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.9"},"exposeStrategy":"NodePort"},"status":{"version":"","url":"","externalCCMMigration":""}}`, }, } diff --git a/modules/api/pkg/handler/v2/cluster_template/cluster_template_test.go b/modules/api/pkg/handler/v2/cluster_template/cluster_template_test.go index 77ee42c83f..25bddc7402 100644 --- a/modules/api/pkg/handler/v2/cluster_template/cluster_template_test.go +++ b/modules/api/pkg/handler/v2/cluster_template/cluster_template_test.go @@ -55,7 +55,7 @@ func TestCreateClusterTemplateEndpoint(t *testing.T) { { Name: "scenario 1: create cluster template in user scope", Body: fmt.Sprintf(`{"name":"test","scope":"user","cluster":{"name":"keen-snyder","spec":{"version":"%s","cloud":{"fake":{"token":"dummy_token"},"dc":"fake-dc"}}}}`, version), - ExpectedResponse: fmt.Sprintf(`{"annotations":{"kubermatic.io/initial-application-installations-request":"[]","kubermatic.io/initial-machinedeployment-request":"","user":"bob@acme.com"},"creationTimestamp":"0001-01-01T00:00:00Z","name":"test","id":"%%s","projectID":"my-first-project-ID","user":"bob@acme.com","scope":"user","cluster":{"annotations":{"kubermatic.io/initial-application-installations-request":"[]","kubermatic.io/initial-machinedeployment-request":"","user":"bob@acme.com"},"spec":{"cloud":{"dc":"fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"}},"nodeDeployment":{"spec":{"replicas":0,"template":{"cloud":{},"operatingSystem":{},"versions":{"kubelet":""}}}}}`, version), + ExpectedResponse: fmt.Sprintf(`{"annotations":{"kubermatic.io/initial-application-installations-request":"[]","kubermatic.io/initial-machinedeployment-request":"","user":"bob@acme.com"},"creationTimestamp":"0001-01-01T00:00:00Z","name":"test","id":"%%s","projectID":"my-first-project-ID","user":"bob@acme.com","scope":"user","cluster":{"annotations":{"kubermatic.io/initial-application-installations-request":"[]","kubermatic.io/initial-machinedeployment-request":"","user":"bob@acme.com"},"spec":{"cloud":{"dc":"fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.9"},"exposeStrategy":"NodePort"}},"nodeDeployment":{"spec":{"replicas":0,"template":{"cloud":{},"operatingSystem":{},"versions":{"kubelet":""}}}}}`, version), RewriteClusterID: true, HTTPStatus: http.StatusCreated, ProjectToSync: test.GenDefaultProject().Name, @@ -68,7 +68,7 @@ func TestCreateClusterTemplateEndpoint(t *testing.T) { { Name: "scenario 2: create cluster template in project scope", Body: fmt.Sprintf(`{"name":"test","scope":"project","cluster":{"name":"keen-snyder","spec":{"version":"%s","cloud":{"fake":{"token":"dummy_token"},"dc":"fake-dc"}}}}`, version), - ExpectedResponse: fmt.Sprintf(`{"annotations":{"kubermatic.io/initial-application-installations-request":"[]","kubermatic.io/initial-machinedeployment-request":"","user":"bob@acme.com"},"creationTimestamp":"0001-01-01T00:00:00Z","name":"test","id":"%%s","projectID":"my-first-project-ID","user":"bob@acme.com","scope":"project","cluster":{"annotations":{"kubermatic.io/initial-application-installations-request":"[]","kubermatic.io/initial-machinedeployment-request":"","user":"bob@acme.com"},"spec":{"cloud":{"dc":"fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"}},"nodeDeployment":{"spec":{"replicas":0,"template":{"cloud":{},"operatingSystem":{},"versions":{"kubelet":""}}}}}`, version), + ExpectedResponse: fmt.Sprintf(`{"annotations":{"kubermatic.io/initial-application-installations-request":"[]","kubermatic.io/initial-machinedeployment-request":"","user":"bob@acme.com"},"creationTimestamp":"0001-01-01T00:00:00Z","name":"test","id":"%%s","projectID":"my-first-project-ID","user":"bob@acme.com","scope":"project","cluster":{"annotations":{"kubermatic.io/initial-application-installations-request":"[]","kubermatic.io/initial-machinedeployment-request":"","user":"bob@acme.com"},"spec":{"cloud":{"dc":"fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.9"},"exposeStrategy":"NodePort"}},"nodeDeployment":{"spec":{"replicas":0,"template":{"cloud":{},"operatingSystem":{},"versions":{"kubelet":""}}}}}`, version), RewriteClusterID: true, HTTPStatus: http.StatusCreated, ProjectToSync: test.GenDefaultProject().Name, @@ -81,7 +81,7 @@ func TestCreateClusterTemplateEndpoint(t *testing.T) { { Name: "scenario 3: create cluster template in global scope by admin", Body: fmt.Sprintf(`{"name":"test","scope":"global","cluster":{"name":"keen-snyder","spec":{"version":"%s","cloud":{"fake":{"token":"dummy_token"},"dc":"fake-dc"}}}}`, version), - ExpectedResponse: fmt.Sprintf(`{"annotations":{"kubermatic.io/initial-application-installations-request":"[]","kubermatic.io/initial-machinedeployment-request":"","user":"john@acme.com"},"creationTimestamp":"0001-01-01T00:00:00Z","name":"test","id":"%%s","projectID":"my-first-project-ID","user":"john@acme.com","scope":"global","cluster":{"annotations":{"kubermatic.io/initial-application-installations-request":"[]","kubermatic.io/initial-machinedeployment-request":"","user":"john@acme.com"},"spec":{"cloud":{"dc":"fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"}},"nodeDeployment":{"spec":{"replicas":0,"template":{"cloud":{},"operatingSystem":{},"versions":{"kubelet":""}}}}}`, version), + ExpectedResponse: fmt.Sprintf(`{"annotations":{"kubermatic.io/initial-application-installations-request":"[]","kubermatic.io/initial-machinedeployment-request":"","user":"john@acme.com"},"creationTimestamp":"0001-01-01T00:00:00Z","name":"test","id":"%%s","projectID":"my-first-project-ID","user":"john@acme.com","scope":"global","cluster":{"annotations":{"kubermatic.io/initial-application-installations-request":"[]","kubermatic.io/initial-machinedeployment-request":"","user":"john@acme.com"},"spec":{"cloud":{"dc":"fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.9"},"exposeStrategy":"NodePort"}},"nodeDeployment":{"spec":{"replicas":0,"template":{"cloud":{},"operatingSystem":{},"versions":{"kubelet":""}}}}}`, version), RewriteClusterID: true, HTTPStatus: http.StatusCreated, ProjectToSync: test.GenDefaultProject().Name, @@ -107,7 +107,7 @@ func TestCreateClusterTemplateEndpoint(t *testing.T) { { Name: "scenario 5: create cluster template in project scope with SSH key", Body: fmt.Sprintf(`{"name":"test","scope":"project","userSshKeys":[{"id":"key-c08aa5c7abf34504f18552846485267d-yafn","name":"test"}],"cluster":{"name":"keen-snyder","spec":{"version":"%s","cloud":{"fake":{"token":"dummy_token"},"dc":"fake-dc"}}}}`, version), - ExpectedResponse: fmt.Sprintf(`{"annotations":{"kubermatic.io/initial-application-installations-request":"[]","kubermatic.io/initial-machinedeployment-request":"","user":"bob@acme.com"},"creationTimestamp":"0001-01-01T00:00:00Z","name":"test","id":"%%s","projectID":"my-first-project-ID","user":"bob@acme.com","scope":"project","userSshKeys":[{"name":"test","id":"key-c08aa5c7abf34504f18552846485267d-yafn"}],"cluster":{"annotations":{"kubermatic.io/initial-application-installations-request":"[]","kubermatic.io/initial-machinedeployment-request":"","user":"bob@acme.com"},"spec":{"cloud":{"dc":"fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"}},"nodeDeployment":{"spec":{"replicas":0,"template":{"cloud":{},"operatingSystem":{},"versions":{"kubelet":""}}}}}`, version), + ExpectedResponse: fmt.Sprintf(`{"annotations":{"kubermatic.io/initial-application-installations-request":"[]","kubermatic.io/initial-machinedeployment-request":"","user":"bob@acme.com"},"creationTimestamp":"0001-01-01T00:00:00Z","name":"test","id":"%%s","projectID":"my-first-project-ID","user":"bob@acme.com","scope":"project","userSshKeys":[{"name":"test","id":"key-c08aa5c7abf34504f18552846485267d-yafn"}],"cluster":{"annotations":{"kubermatic.io/initial-application-installations-request":"[]","kubermatic.io/initial-machinedeployment-request":"","user":"bob@acme.com"},"spec":{"cloud":{"dc":"fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.9"},"exposeStrategy":"NodePort"}},"nodeDeployment":{"spec":{"replicas":0,"template":{"cloud":{},"operatingSystem":{},"versions":{"kubelet":""}}}}}`, version), RewriteClusterID: true, HTTPStatus: http.StatusCreated, ProjectToSync: test.GenDefaultProject().Name, @@ -765,7 +765,7 @@ func TestImportClusterTemplateEndpoint(t *testing.T) { { Name: "scenario 1: import cluster template in user scope", Body: fmt.Sprintf(`{"name":"test","scope":"user","cluster":{"name":"keen-snyder","spec":{"version":"%s","cloud":{"fake":{"token":"dummy_token"},"dc":"fake-dc"}}}}`, version), - ExpectedResponse: fmt.Sprintf(`{"annotations":{"kubermatic.io/initial-application-installations-request":"[]","kubermatic.io/initial-machinedeployment-request":"","user":"bob@acme.com"},"creationTimestamp":"0001-01-01T00:00:00Z","name":"test","id":"%%s","projectID":"my-first-project-ID","user":"bob@acme.com","scope":"user","cluster":{"annotations":{"kubermatic.io/initial-application-installations-request":"[]","kubermatic.io/initial-machinedeployment-request":"","user":"bob@acme.com"},"spec":{"cloud":{"dc":"fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"}},"nodeDeployment":{"spec":{"replicas":0,"template":{"cloud":{},"operatingSystem":{},"versions":{"kubelet":""}}}}}`, version), + ExpectedResponse: fmt.Sprintf(`{"annotations":{"kubermatic.io/initial-application-installations-request":"[]","kubermatic.io/initial-machinedeployment-request":"","user":"bob@acme.com"},"creationTimestamp":"0001-01-01T00:00:00Z","name":"test","id":"%%s","projectID":"my-first-project-ID","user":"bob@acme.com","scope":"user","cluster":{"annotations":{"kubermatic.io/initial-application-installations-request":"[]","kubermatic.io/initial-machinedeployment-request":"","user":"bob@acme.com"},"spec":{"cloud":{"dc":"fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.9"},"exposeStrategy":"NodePort"}},"nodeDeployment":{"spec":{"replicas":0,"template":{"cloud":{},"operatingSystem":{},"versions":{"kubelet":""}}}}}`, version), RewriteClusterID: true, HTTPStatus: http.StatusCreated, ProjectToSync: test.GenDefaultProject().Name, @@ -790,7 +790,7 @@ func TestImportClusterTemplateEndpoint(t *testing.T) { { Name: "scenario 3: import cluster template in project scope with SSH key", Body: fmt.Sprintf(`{"name":"test","scope":"project","userSshKeys":[{"id":"key-c08aa5c7abf34504f18552846485267d-yafn","name":"test"}],"cluster":{"name":"keen-snyder","spec":{"version":"%s","cloud":{"fake":{"token":"dummy_token"},"dc":"fake-dc"}}}}`, version), - ExpectedResponse: fmt.Sprintf(`{"annotations":{"kubermatic.io/initial-application-installations-request":"[]","kubermatic.io/initial-machinedeployment-request":"","user":"bob@acme.com"},"creationTimestamp":"0001-01-01T00:00:00Z","name":"test","id":"%%s","projectID":"my-first-project-ID","user":"bob@acme.com","scope":"project","userSshKeys":[{"name":"test","id":"key-c08aa5c7abf34504f18552846485267d-yafn"}],"cluster":{"annotations":{"kubermatic.io/initial-application-installations-request":"[]","kubermatic.io/initial-machinedeployment-request":"","user":"bob@acme.com"},"spec":{"cloud":{"dc":"fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.6"},"exposeStrategy":"NodePort"}},"nodeDeployment":{"spec":{"replicas":0,"template":{"cloud":{},"operatingSystem":{},"versions":{"kubelet":""}}}}}`, version), + ExpectedResponse: fmt.Sprintf(`{"annotations":{"kubermatic.io/initial-application-installations-request":"[]","kubermatic.io/initial-machinedeployment-request":"","user":"bob@acme.com"},"creationTimestamp":"0001-01-01T00:00:00Z","name":"test","id":"%%s","projectID":"my-first-project-ID","user":"bob@acme.com","scope":"project","userSshKeys":[{"name":"test","id":"key-c08aa5c7abf34504f18552846485267d-yafn"}],"cluster":{"annotations":{"kubermatic.io/initial-application-installations-request":"[]","kubermatic.io/initial-machinedeployment-request":"","user":"bob@acme.com"},"spec":{"cloud":{"dc":"fake-dc","fake":{}},"version":"%s","oidc":{},"enableUserSSHKeyAgent":true,"kubernetesDashboard":{"enabled":true},"containerRuntime":"containerd","clusterNetwork":{"ipFamily":"IPv4","services":{"cidrBlocks":["10.240.16.0/20"]},"pods":{"cidrBlocks":["172.25.0.0/16"]},"nodeCidrMaskSizeIPv4":24,"dnsDomain":"cluster.local","proxyMode":"ipvs","ipvs":{"strictArp":true},"nodeLocalDNSCacheEnabled":true,"konnectivityEnabled":true},"cniPlugin":{"type":"cilium","version":"1.16.9"},"exposeStrategy":"NodePort"}},"nodeDeployment":{"spec":{"replicas":0,"template":{"cloud":{},"operatingSystem":{},"versions":{"kubelet":""}}}}}`, version), RewriteClusterID: true, HTTPStatus: http.StatusCreated, ProjectToSync: test.GenDefaultProject().Name, diff --git a/modules/web/Makefile b/modules/web/Makefile index 445d472136..488139b6f9 100644 --- a/modules/web/Makefile +++ b/modules/web/Makefile @@ -1,6 +1,6 @@ SHELL = /bin/bash -eu -o pipefail export KUBERMATIC_EDITION ?= ee -KUBERMATIC_VERSION?=v2.27.3 +KUBERMATIC_VERSION?=v2.27.4 CC=npm GOOS ?= $(shell go env GOOS) export GOOS diff --git a/modules/web/package-lock.json b/modules/web/package-lock.json index 13785aca57..6d085e5ab0 100644 --- a/modules/web/package-lock.json +++ b/modules/web/package-lock.json @@ -1,12 +1,12 @@ { "name": "kubermatic-dashboard", - "version": "2.27.3", + "version": "2.27.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "kubermatic-dashboard", - "version": "2.27.3", + "version": "2.27.4", "hasInstallScript": true, "license": "proprietary", "dependencies": { diff --git a/modules/web/package.json b/modules/web/package.json index 44ee2bb4ce..a01d5a5ab7 100644 --- a/modules/web/package.json +++ b/modules/web/package.json @@ -1,7 +1,7 @@ { "name": "kubermatic-dashboard", "description": "Kubermatic Dashboard", - "version": "2.27.3", + "version": "2.27.4", "type": "module", "license": "proprietary", "repository": "https://github.com/kubermatic/dashboard", From d2885c31c94e6a7aa95c9c29f65255c22e9965c1 Mon Sep 17 00:00:00 2001 From: Kubermatic Bot <41968677+kubermatic-bot@users.noreply.github.com> Date: Tue, 3 Jun 2025 12:17:14 +0200 Subject: [PATCH 13/42] Use infra management user credentials (if configured) for fetching data for vsphere (#7398) Co-authored-by: Waseem Abbas --- .../services/node-data/provider/vsphere.ts | 24 ++++++--- .../provider/extended/vsphere/component.ts | 51 ++++++++++++++----- 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/modules/web/src/app/core/services/node-data/provider/vsphere.ts b/modules/web/src/app/core/services/node-data/provider/vsphere.ts index 0c9d6d15b9..e0c63741d6 100644 --- a/modules/web/src/app/core/services/node-data/provider/vsphere.ts +++ b/modules/web/src/app/core/services/node-data/provider/vsphere.ts @@ -52,8 +52,12 @@ export class NodeDataVSphereProvider { switchMap(cluster => this._presetService .provider(NodeProvider.VSPHERE) - .username(cluster.spec.cloud.vsphere.username) - .password(cluster.spec.cloud.vsphere.password) + .username( + cluster.spec.cloud.vsphere.infraManagementUser?.username || cluster.spec.cloud.vsphere.username + ) + .password( + cluster.spec.cloud.vsphere.infraManagementUser?.password || cluster.spec.cloud.vsphere.password + ) .credential(this._presetService.preset) .datacenter(this._clusterSpecService.datacenter) .tagCategories(onLoadingCb) @@ -104,8 +108,12 @@ export class NodeDataVSphereProvider { switchMap(cluster => this._presetService .provider(NodeProvider.VSPHERE) - .username(cluster.spec.cloud.vsphere.username) - .password(cluster.spec.cloud.vsphere.password) + .username( + cluster.spec.cloud.vsphere.infraManagementUser?.username || cluster.spec.cloud.vsphere.username + ) + .password( + cluster.spec.cloud.vsphere.infraManagementUser?.password || cluster.spec.cloud.vsphere.password + ) .credential(this._presetService.preset) .datacenter(this._clusterSpecService.datacenter) .tags(category, onLoadingCb) @@ -153,8 +161,12 @@ export class NodeDataVSphereProvider { switchMap(cluster => this._presetService .provider(NodeProvider.VSPHERE) - .username(cluster.spec.cloud.vsphere.username) - .password(cluster.spec.cloud.vsphere.password) + .username( + cluster.spec.cloud.vsphere.infraManagementUser?.username || cluster.spec.cloud.vsphere.username + ) + .password( + cluster.spec.cloud.vsphere.infraManagementUser?.password || cluster.spec.cloud.vsphere.password + ) .credential(this._presetService.preset) .datacenter(this._clusterSpecService.datacenter) .vmGroups(onLoadingCb) diff --git a/modules/web/src/app/wizard/step/provider-settings/provider/extended/vsphere/component.ts b/modules/web/src/app/wizard/step/provider-settings/provider/extended/vsphere/component.ts index c13959f369..a7eaf27e66 100644 --- a/modules/web/src/app/wizard/step/provider-settings/provider/extended/vsphere/component.ts +++ b/modules/web/src/app/wizard/step/provider-settings/provider/extended/vsphere/component.ts @@ -283,9 +283,10 @@ export class VSphereProviderExtendedComponent extends BaseFormValidator implemen hasRequiredCredentials(): boolean { return ( - (!!this._clusterSpecService.cluster.spec.cloud.vsphere && - !!this._clusterSpecService.cluster.spec.cloud.vsphere.username && - !!this._clusterSpecService.cluster.spec.cloud.vsphere.password) || + (!!this._clusterSpecService.cluster.spec.cloud.vsphere?.username && + !!this._clusterSpecService.cluster.spec.cloud.vsphere?.password) || + (!!this._clusterSpecService.cluster.spec.cloud.vsphere?.infraManagementUser?.username && + !!this._clusterSpecService.cluster.spec.cloud.vsphere?.infraManagementUser?.password) || (!!this._clusterSpecService.cluster.spec.cloud.vsphere && !!this._presets.preset) ); } @@ -297,8 +298,8 @@ export class VSphereProviderExtendedComponent extends BaseFormValidator implemen private _handleClusterChange(cluster: Cluster): void { let markAsChanged = false; - const username = cluster.spec.cloud.vsphere.username; - const password = cluster.spec.cloud.vsphere.password; + const username = cluster.spec.cloud.vsphere.infraManagementUser?.username || cluster.spec.cloud.vsphere.username; + const password = cluster.spec.cloud.vsphere.infraManagementUser?.password || cluster.spec.cloud.vsphere.password; if (username !== this._username) { this._username = username; @@ -351,8 +352,14 @@ export class VSphereProviderExtendedComponent extends BaseFormValidator implemen private _networkListObservable(): Observable { return this._presets .provider(NodeProvider.VSPHERE) - .username(this._clusterSpecService.cluster.spec.cloud.vsphere.username) - .password(this._clusterSpecService.cluster.spec.cloud.vsphere.password) + .username( + this._clusterSpecService.cluster.spec.cloud.vsphere.infraManagementUser?.username || + this._clusterSpecService.cluster.spec.cloud.vsphere.username + ) + .password( + this._clusterSpecService.cluster.spec.cloud.vsphere.infraManagementUser?.password || + this._clusterSpecService.cluster.spec.cloud.vsphere.password + ) .datacenter(this._clusterSpecService.datacenter) .networks(this._onNetworksLoading.bind(this)) .pipe(map(networks => _.sortBy(networks, n => n.name.toLowerCase()))) @@ -380,8 +387,14 @@ export class VSphereProviderExtendedComponent extends BaseFormValidator implemen private _folderListObservable(): Observable { return this._presets .provider(NodeProvider.VSPHERE) - .username(this._clusterSpecService.cluster.spec.cloud.vsphere.username) - .password(this._clusterSpecService.cluster.spec.cloud.vsphere.password) + .username( + this._clusterSpecService.cluster.spec.cloud.vsphere.infraManagementUser?.username || + this._clusterSpecService.cluster.spec.cloud.vsphere.username + ) + .password( + this._clusterSpecService.cluster.spec.cloud.vsphere.infraManagementUser?.password || + this._clusterSpecService.cluster.spec.cloud.vsphere.password + ) .datacenter(this._clusterSpecService.datacenter) .folders(this._onFoldersLoading.bind(this)) .pipe( @@ -395,8 +408,14 @@ export class VSphereProviderExtendedComponent extends BaseFormValidator implemen private _tagCategoryListObservable(): Observable { return this._presets .provider(NodeProvider.VSPHERE) - .username(this._clusterSpecService.cluster.spec.cloud.vsphere.username) - .password(this._clusterSpecService.cluster.spec.cloud.vsphere.password) + .username( + this._clusterSpecService.cluster.spec.cloud.vsphere.infraManagementUser?.username || + this._clusterSpecService.cluster.spec.cloud.vsphere.username + ) + .password( + this._clusterSpecService.cluster.spec.cloud.vsphere.infraManagementUser?.password || + this._clusterSpecService.cluster.spec.cloud.vsphere.password + ) .credential(this._presets.preset) .datacenter(this._clusterSpecService.datacenter) .tagCategories(this._onTagCategoryLoading.bind(this)) @@ -436,8 +455,14 @@ export class VSphereProviderExtendedComponent extends BaseFormValidator implemen private _datastoresObservable(): Observable { return this._presets .provider(NodeProvider.VSPHERE) - .username(this._clusterSpecService.cluster.spec.cloud.vsphere.username) - .password(this._clusterSpecService.cluster.spec.cloud.vsphere.password) + .username( + this._clusterSpecService.cluster.spec.cloud.vsphere.infraManagementUser?.username || + this._clusterSpecService.cluster.spec.cloud.vsphere.username + ) + .password( + this._clusterSpecService.cluster.spec.cloud.vsphere.infraManagementUser?.password || + this._clusterSpecService.cluster.spec.cloud.vsphere.password + ) .datacenter(this._clusterSpecService.datacenter) .datastores(() => this._setIsLoadingDatastores(true)) .pipe( From 251567da635d95ea6e0a3f11c0512b55c7bd602f Mon Sep 17 00:00:00 2001 From: Christoph Mewes Date: Tue, 3 Jun 2025 13:53:14 +0200 Subject: [PATCH 14/42] [release/v2.27] bump KKP (#7400) * [release/v2.27] bump KKP dependency * [release/v2.27] codegen --- modules/api/cmd/kubermatic-api/swagger.json | 5 +++++ modules/api/go.mod | 4 ++-- modules/api/go.sum | 8 ++++---- .../utils/apiclient/models/datacenter_spec_kubevirt.go | 3 +++ 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/modules/api/cmd/kubermatic-api/swagger.json b/modules/api/cmd/kubermatic-api/swagger.json index b05eeeca6a..f76a76d8ce 100644 --- a/modules/api/cmd/kubermatic-api/swagger.json +++ b/modules/api/cmd/kubermatic-api/swagger.json @@ -31019,6 +31019,11 @@ "type": "object", "title": "DatacenterSpecKubevirt describes a kubevirt datacenter.", "properties": { + "ccmLoadBalancerEnabled": { + "description": "Optional: indicates if the ccm should create and manage the clusters load balancers.", + "type": "boolean", + "x-go-name": "CCMLoadBalancerEnabled" + }, "ccmZoneAndRegionEnabled": { "description": "Optional: indicates if region and zone labels from the cloud provider should be fetched.", "type": "boolean", diff --git a/modules/api/go.mod b/modules/api/go.mod index 846477df8c..5c70807593 100644 --- a/modules/api/go.mod +++ b/modules/api/go.mod @@ -70,8 +70,8 @@ require ( google.golang.org/api v0.209.0 gopkg.in/yaml.v3 v3.0.1 k8c.io/kubeone v1.7.3 - k8c.io/kubermatic/v2 v2.27.4-0.20250506093457-a0144c86d480 - k8c.io/machine-controller v1.61.1 + k8c.io/kubermatic/v2 v2.27.5-0.20250602165514-9b87866aaf37 + k8c.io/machine-controller v1.61.2 k8c.io/operating-system-manager v1.6.5 k8c.io/reconciler v0.5.0 k8s.io/api v0.31.3 diff --git a/modules/api/go.sum b/modules/api/go.sum index 7518a614ba..4f0de8b3f0 100644 --- a/modules/api/go.sum +++ b/modules/api/go.sum @@ -1147,10 +1147,10 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8c.io/kubeone v1.7.2 h1:uLH19VEp1S5j4f3UwQP4CLHErJ23UiJM2MnbufNWDgI= k8c.io/kubeone v1.7.2/go.mod h1:9v2VFz/+l36cW65kd5YufEYHunbKlJ6P8SBakj05xgM= -k8c.io/kubermatic/v2 v2.27.4-0.20250506093457-a0144c86d480 h1:/7pobTFIaXpB5VfHVWbUaWGPMZvmo9T9uOSLTKtTlZA= -k8c.io/kubermatic/v2 v2.27.4-0.20250506093457-a0144c86d480/go.mod h1:6gxHUzvh33Vwyq4kcg5BjxsiHSATdPH44LGpk1TxHPU= -k8c.io/machine-controller v1.61.1 h1:Zy3kg9t0WrDN0Wo3y/pAJp7jdkThcJt070f0fAL9MVc= -k8c.io/machine-controller v1.61.1/go.mod h1:ZGDFyUeEp66RHcNB5Ki/OJyFdZFgo9dkHJ9s6YJWPcg= +k8c.io/kubermatic/v2 v2.27.5-0.20250602165514-9b87866aaf37 h1:NH12w8hDcVDHvJcqtW8pNwqFwRhAwQ696AkDwTnx1m4= +k8c.io/kubermatic/v2 v2.27.5-0.20250602165514-9b87866aaf37/go.mod h1:TNysG/oe6ZZgfUlVxSdR3yHYMxxdJRLcFHV6+8aDnEg= +k8c.io/machine-controller v1.61.2 h1:F1DQgrTRICkFUoxM3MReQ78HVG9ztXF0xPYPYSXQnJc= +k8c.io/machine-controller v1.61.2/go.mod h1:ZGDFyUeEp66RHcNB5Ki/OJyFdZFgo9dkHJ9s6YJWPcg= k8c.io/operating-system-manager v1.6.5 h1:F7oBJKEv2t3uzG8k4e9s9j9vCAvXN1FGEkqV0+dMh60= k8c.io/operating-system-manager v1.6.5/go.mod h1:Dh9IZp5pb5G3s2r6ZrHUb+gTuHw5AmbIFYuFxIcgU7o= k8c.io/reconciler v0.5.0 h1:BHpelg1UfI/7oBFctqOq8sX6qzflXpl3SlvHe7e8wak= diff --git a/modules/api/pkg/test/e2e/utils/apiclient/models/datacenter_spec_kubevirt.go b/modules/api/pkg/test/e2e/utils/apiclient/models/datacenter_spec_kubevirt.go index 8621589981..9f259541bf 100644 --- a/modules/api/pkg/test/e2e/utils/apiclient/models/datacenter_spec_kubevirt.go +++ b/modules/api/pkg/test/e2e/utils/apiclient/models/datacenter_spec_kubevirt.go @@ -19,6 +19,9 @@ import ( // swagger:model DatacenterSpecKubevirt type DatacenterSpecKubevirt struct { + // Optional: indicates if the ccm should create and manage the clusters load balancers. + CCMLoadBalancerEnabled bool `json:"ccmLoadBalancerEnabled,omitempty"` + // Optional: indicates if region and zone labels from the cloud provider should be fetched. CCMZoneAndRegionEnabled bool `json:"ccmZoneAndRegionEnabled,omitempty"` From 9590f8877b0702c3dfde4265f058ae6f68ccab55 Mon Sep 17 00:00:00 2001 From: Kubermatic Bot <41968677+kubermatic-bot@users.noreply.github.com> Date: Tue, 10 Jun 2025 18:33:19 +0200 Subject: [PATCH 15/42] Enable CustomDiskSize value based on diskSize existence (#7417) Co-authored-by: Khizer Rehan --- .../src/app/node-data/basic/provider/openstack/component.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/web/src/app/node-data/basic/provider/openstack/component.ts b/modules/web/src/app/node-data/basic/provider/openstack/component.ts index 8a8e8c9cf2..44dd8738e7 100644 --- a/modules/web/src/app/node-data/basic/provider/openstack/component.ts +++ b/modules/web/src/app/node-data/basic/provider/openstack/component.ts @@ -282,9 +282,11 @@ export class OpenstackBasicNodeDataComponent extends BaseFormValidator implement `PT${this._nodeDataService.nodeData.spec.cloud.openstack.instanceReadyCheckTimeout}`.toUpperCase() ).asSeconds(); + const diskSize = this._nodeDataService.nodeData.spec.cloud.openstack.diskSize; this.form.get(Controls.UseFloatingIP).setValue(this._nodeDataService.nodeData.spec.cloud.openstack.useFloatingIP); this.form.get(Controls.Image).setValue(this._nodeDataService.nodeData.spec.cloud.openstack.image); - this.form.get(Controls.CustomDiskSize).setValue(this._nodeDataService.nodeData.spec.cloud.openstack.diskSize); + this.form.get(Controls.CustomDiskSize).setValue(diskSize); + this.form.get(Controls.UseCustomDisk).setValue(!!diskSize); this.form.get(Controls.InstanceReadyCheckPeriod).setValue(instanceReadyCheckPeriod); this.form.get(Controls.InstanceReadyCheckTimeout).setValue(instanceReadyCheckTimeout); From d1b19002e4c9ef65c0f0543c2f39712de24972f8 Mon Sep 17 00:00:00 2001 From: Kubermatic Bot <41968677+kubermatic-bot@users.noreply.github.com> Date: Mon, 16 Jun 2025 13:30:26 +0200 Subject: [PATCH 16/42] make subnets required if they are existed (#7428) Co-authored-by: ahmadhamzh --- .../web/src/app/node-data/basic/provider/kubevirt/component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/web/src/app/node-data/basic/provider/kubevirt/component.ts b/modules/web/src/app/node-data/basic/provider/kubevirt/component.ts index 53c740fcad..4ac7576ed6 100644 --- a/modules/web/src/app/node-data/basic/provider/kubevirt/component.ts +++ b/modules/web/src/app/node-data/basic/provider/kubevirt/component.ts @@ -304,7 +304,7 @@ export class KubeVirtBasicNodeDataComponent } get isSubnetsRequired(): boolean { - return !!this._clusterSpecService.cluster?.spec?.cloud?.kubevirt?.vpcName; + return !!this._clusterSpecService.cluster?.spec?.cloud?.kubevirt?.vpcName || this.subnets?.length > 0; } getInstanceTypeOptions(group: string): KubeVirtInstanceType[] { From b16ecf476170a074e7ddc902e6be3431dc3db4ae Mon Sep 17 00:00:00 2001 From: Kubermatic Bot <41968677+kubermatic-bot@users.noreply.github.com> Date: Thu, 19 Jun 2025 10:30:44 +0200 Subject: [PATCH 17/42] [release/v2.27] Fix clickable documentation links in disabled checkbox hints (#7438) * Add missing mat-hints and enable anchor click in disabled state * Fix doc fragment path * Revert redundant mat-hints and use OIDC page url link to existing anchor links * Update MatHint message for both feature flags --------- Co-authored-by: Khizer Rehan --- .../src/app/settings/admin/defaults/component.ts | 5 +++++ .../web/src/app/settings/admin/defaults/style.scss | 5 +++++ .../src/app/settings/admin/defaults/template.html | 13 ++++++++----- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/modules/web/src/app/settings/admin/defaults/component.ts b/modules/web/src/app/settings/admin/defaults/component.ts index 54cbb3a730..fc8014d2bc 100644 --- a/modules/web/src/app/settings/admin/defaults/component.ts +++ b/modules/web/src/app/settings/admin/defaults/component.ts @@ -191,9 +191,14 @@ export class DefaultsComponent implements OnInit, OnDestroy { return false; } + getDocumentationLink(): string { + return `https://docs.kubermatic.com/kubermatic/${this.editionVersion}/tutorials-howtos/oidc-provider-configuration/share-clusters-via-delegated-oidc-authentication/`; + } + private _checkLabels(staticLabels: StaticLabel[]): boolean { return staticLabels.every(label => label?.key && label.values?.length); } + private _verifyEnableKubernetesDashboardRequirements() { // Note: Kubernetes Dashboard feature requires both feature gates from admin side to be enabled. if ((!this.isOIDCKubeCfgEndpointEnabled || !this.isOpenIDAuthPluginEnabled) && this.settings.enableDashboard) { diff --git a/modules/web/src/app/settings/admin/defaults/style.scss b/modules/web/src/app/settings/admin/defaults/style.scss index 40d87d52a4..5c6091e44e 100644 --- a/modules/web/src/app/settings/admin/defaults/style.scss +++ b/modules/web/src/app/settings/admin/defaults/style.scss @@ -83,3 +83,8 @@ .static-labels { width: 75%; } + +.km-clickable-link { + cursor: pointer; + pointer-events: auto; +} diff --git a/modules/web/src/app/settings/admin/defaults/template.html b/modules/web/src/app/settings/admin/defaults/template.html index c03b0f0b9f..96c2436179 100644 --- a/modules/web/src/app/settings/admin/defaults/template.html +++ b/modules/web/src/app/settings/admin/defaults/template.html @@ -170,10 +170,11 @@ [disabled]="!isKubernetesDashboardFeatureGatesEnabled()" id="km-enable-kubernetes-dashboard-setting"> - This feature is disabled. Visit the - This feature requires both OIDC Kubeconfig and OpenID Auth Plugin feature flags to be enabled. Visit the + documentation @@ -198,9 +199,10 @@ (change)="onOIDCKubeconfigSettingsChange()" id="km-enable-oidc-setting"> This feature is disabled. Visit the - documentation @@ -254,9 +256,10 @@ (change)="onSettingsChange()" id="km-disabled-admin-kubeconfig-setting"> This feature requires the OIDC kubeconfig feature to be enabled. Visit the - documentation @@ -273,7 +276,7 @@ class="entry-label"> Enable WebTerminal
+ matTooltip='Show/Hide "Web Terminal" button on cluster details and allow/block web terminal pod access through terminal.'>
Date: Thu, 19 Jun 2025 14:26:43 +0200 Subject: [PATCH 18/42] removing TenantID filter for listing openstack networks (#7441) Co-authored-by: ahmadhamzh --- modules/api/pkg/provider/cloud/openstack/provider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api/pkg/provider/cloud/openstack/provider.go b/modules/api/pkg/provider/cloud/openstack/provider.go index a31bb05a34..1faab2f043 100644 --- a/modules/api/pkg/provider/cloud/openstack/provider.go +++ b/modules/api/pkg/provider/cloud/openstack/provider.go @@ -125,7 +125,7 @@ func GetNetworks(ctx context.Context, authURL, region string, credentials *resou return nil, fmt.Errorf("couldn't get auth client: %w", err) } - networks, err := getAllNetworks(authClient, osnetworks.ListOpts{TenantID: credentials.ProjectID}) + networks, err := getAllNetworks(authClient, osnetworks.ListOpts{}) if err != nil { return nil, fmt.Errorf("couldn't get networks: %w", err) } From 8e218a6d495bb091c9186e72fbc25dd7b4fc146b Mon Sep 17 00:00:00 2001 From: Kubermatic Bot <41968677+kubermatic-bot@users.noreply.github.com> Date: Thu, 19 Jun 2025 16:15:43 +0200 Subject: [PATCH 19/42] [release/v2.27] Set default backup sync period to null if value is empty (#7445) * set null when Backup Sync Period value is cleared * Fix Linting issue --------- Co-authored-by: Khizer Rehan --- .../list/backup-storage-location/add-dialog/component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/web/src/app/dynamic/enterprise/cluster-backups/list/backup-storage-location/add-dialog/component.ts b/modules/web/src/app/dynamic/enterprise/cluster-backups/list/backup-storage-location/add-dialog/component.ts index c57db5bbf4..09db894f62 100644 --- a/modules/web/src/app/dynamic/enterprise/cluster-backups/list/backup-storage-location/add-dialog/component.ts +++ b/modules/web/src/app/dynamic/enterprise/cluster-backups/list/backup-storage-location/add-dialog/component.ts @@ -167,7 +167,8 @@ export class AddBackupStorageLocationDialogComponent implements OnInit, OnDestro prefix: this.form.get(Controls.Prefix).value, caCert: this.form.get(Controls.CaCert).value, }, - backupSyncPeriod: this.form.get(Controls.BackupSyncPeriod).value, + backupSyncPeriod: + this.form.get(Controls.BackupSyncPeriod).value === '' ? null : this.form.get(Controls.BackupSyncPeriod).value, config: { region: this.form.get(Controls.Region).value, s3Url: this.form.get(Controls.Endpoints).value, From 28a0acf8ad8577fd9413bf09592782170641b06c Mon Sep 17 00:00:00 2001 From: Waleed Malik Date: Thu, 26 Jun 2025 01:34:50 +0500 Subject: [PATCH 20/42] KubeLB: Fix a bug with enforcement (#7453) (#7455) Signed-off-by: Waleed Malik --- .../app/cluster/details/cluster/edit-cluster/component.ts | 6 ++++++ modules/web/src/app/wizard/step/cluster/component.ts | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/modules/web/src/app/cluster/details/cluster/edit-cluster/component.ts b/modules/web/src/app/cluster/details/cluster/edit-cluster/component.ts index 03e09aa56a..4485db3c71 100644 --- a/modules/web/src/app/cluster/details/cluster/edit-cluster/component.ts +++ b/modules/web/src/app/cluster/details/cluster/edit-cluster/component.ts @@ -245,6 +245,12 @@ export class EditClusterComponent implements OnInit, OnDestroy { this.datacenter = datacenter; this.isKubeLBEnabled = !!(datacenter.spec.kubelb?.enforced || datacenter.spec.kubelb?.enabled); this.isKubeLBEnforced = !!datacenter.spec.kubelb?.enforced; + + // If KubeLB is enforced, we need to enable the kubelb control + if (this.isKubeLBEnforced) { + this.form.get(Controls.KubeLB).setValue(true); + } + this.isCSIDriverDisabled = datacenter.spec.disableCsiDriver; this._provider = datacenter.spec.provider; this.isAllowedIPRangeSupported = (NODEPORTS_IPRANGES_SUPPORTED_PROVIDERS as string[]).includes( diff --git a/modules/web/src/app/wizard/step/cluster/component.ts b/modules/web/src/app/wizard/step/cluster/component.ts index 2322604988..c49900a437 100644 --- a/modules/web/src/app/wizard/step/cluster/component.ts +++ b/modules/web/src/app/wizard/step/cluster/component.ts @@ -271,6 +271,11 @@ export class ClusterStepComponent extends StepBase implements OnInit, ControlVal this.isKubeLBEnabled = !!(datacenter.spec.kubelb?.enforced || datacenter.spec.kubelb?.enabled); this.isKubeLBEnforced = !!datacenter.spec.kubelb?.enforced; + // If KubeLB is enforced, we need to enable the kubelb control + if (this.isKubeLBEnforced) { + this.form.get(Controls.KubeLB).setValue(true); + } + if (datacenter.spec.kubelb?.enableGatewayAPI) { this.form.get(Controls.KubeLBEnableGatewayAPI).setValue(true); } From b3f0b0a781ab93b777051f35fd693550f6baa8cc Mon Sep 17 00:00:00 2001 From: Archana Sawant Date: Thu, 26 Jun 2025 13:28:49 +0530 Subject: [PATCH 21/42] [release/v2.27] Bump Go version to 1.23.10 (#7450) Signed-off-by: archups --- .prow/api.yaml | 8 ++++---- .prow/common.yaml | 2 +- .prow/frontend.yaml | 6 +++--- hack/verify-spelling.sh | 2 +- modules/api/hack/gen-api-client.sh | 2 +- modules/api/hack/update-swagger.sh | 2 +- modules/api/hack/verify-licenses.sh | 2 +- modules/web/containers/chrome-headless/Dockerfile | 2 +- modules/web/containers/custom-dashboard/Dockerfile | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.prow/api.yaml b/.prow/api.yaml index b0ab1fcd6d..d88814a992 100644 --- a/.prow/api.yaml +++ b/.prow/api.yaml @@ -32,7 +32,7 @@ presubmits: preset-goproxy: "true" spec: containers: - - image: quay.io/kubermatic/build:go-1.23-node-20-kind-0.26-8 + - image: quay.io/kubermatic/build:go-1.23-node-20-kind-0.26-12 command: - "./hack/ci/run-api-e2e.sh" env: @@ -60,7 +60,7 @@ presubmits: preset-goproxy: "true" spec: containers: - - image: quay.io/kubermatic/build:go-1.23-node-20-8 + - image: quay.io/kubermatic/build:go-1.23-node-20-12 command: - make args: @@ -81,7 +81,7 @@ presubmits: preset-goproxy: "true" spec: containers: - - image: quay.io/kubermatic/build:go-1.23-node-20-8 + - image: quay.io/kubermatic/build:go-1.23-node-20-12 command: - make - api-lint @@ -101,7 +101,7 @@ presubmits: preset-goproxy: "true" spec: containers: - - image: quay.io/kubermatic/build:go-1.23-node-20-8 + - image: quay.io/kubermatic/build:go-1.23-node-20-12 command: - make - api-verify diff --git a/.prow/common.yaml b/.prow/common.yaml index 9e619cb13f..3d392413ff 100644 --- a/.prow/common.yaml +++ b/.prow/common.yaml @@ -21,7 +21,7 @@ presubmits: preset-goproxy: "true" spec: containers: - - image: quay.io/kubermatic/build:go-1.23-node-20-kind-0.26-8 + - image: quay.io/kubermatic/build:go-1.23-node-20-kind-0.26-12 command: - ./hack/ci/verify.sh resources: diff --git a/.prow/frontend.yaml b/.prow/frontend.yaml index 2c6ac1e976..6aef62dfb9 100644 --- a/.prow/frontend.yaml +++ b/.prow/frontend.yaml @@ -215,7 +215,7 @@ presubmits: preset-goproxy: "true" spec: containers: - - image: quay.io/kubermatic/build:go-1.23-node-20-kind-0.26-8 + - image: quay.io/kubermatic/build:go-1.23-node-20-kind-0.26-12 command: - make - web-check-dependencies @@ -231,7 +231,7 @@ presubmits: preset-goproxy: "true" spec: containers: - - image: quay.io/kubermatic/build:go-1.23-node-20-8 + - image: quay.io/kubermatic/build:go-1.23-node-20-12 command: - make - web-lint @@ -251,7 +251,7 @@ presubmits: preset-goproxy: "true" spec: containers: - - image: quay.io/kubermatic/build:go-1.23-node-20-kind-0.26-8 + - image: quay.io/kubermatic/build:go-1.23-node-20-kind-0.26-12 command: - make - web-check diff --git a/hack/verify-spelling.sh b/hack/verify-spelling.sh index 8850e6c0a4..e7ff16ff8b 100755 --- a/hack/verify-spelling.sh +++ b/hack/verify-spelling.sh @@ -19,7 +19,7 @@ set -euo pipefail cd $(dirname $0)/.. source hack/lib.sh -CONTAINERIZE_IMAGE=quay.io/kubermatic/build:go-1.23-node-20-8 containerize ./hack/verify-spelling.sh +CONTAINERIZE_IMAGE=quay.io/kubermatic/build:go-1.23-node-20-12 containerize ./hack/verify-spelling.sh echodate "Running codespell..." diff --git a/modules/api/hack/gen-api-client.sh b/modules/api/hack/gen-api-client.sh index 229620ebe2..2c9ca0d0ec 100755 --- a/modules/api/hack/gen-api-client.sh +++ b/modules/api/hack/gen-api-client.sh @@ -24,7 +24,7 @@ source hack/lib.sh API=modules/api -CONTAINERIZE_IMAGE=quay.io/kubermatic/build:go-1.23-node-20-8 containerize ./modules/api/hack/gen-api-client.sh +CONTAINERIZE_IMAGE=quay.io/kubermatic/build:go-1.23-node-20-12 containerize ./modules/api/hack/gen-api-client.sh cd $API/cmd/kubermatic-api/ SWAGGER_FILE="swagger.json" diff --git a/modules/api/hack/update-swagger.sh b/modules/api/hack/update-swagger.sh index 60952f686c..5a81f67eee 100755 --- a/modules/api/hack/update-swagger.sh +++ b/modules/api/hack/update-swagger.sh @@ -21,7 +21,7 @@ source hack/lib.sh API=modules/api -CONTAINERIZE_IMAGE=quay.io/kubermatic/build:go-1.23-node-20-8 containerize ./$API/hack/update-swagger.sh +CONTAINERIZE_IMAGE=quay.io/kubermatic/build:go-1.23-node-20-12 containerize ./$API/hack/update-swagger.sh echodate "Generating swagger spec" cd $API/cmd/kubermatic-api/ diff --git a/modules/api/hack/verify-licenses.sh b/modules/api/hack/verify-licenses.sh index dcb9bf8090..37e690564a 100755 --- a/modules/api/hack/verify-licenses.sh +++ b/modules/api/hack/verify-licenses.sh @@ -21,7 +21,7 @@ source hack/lib.sh API=modules/api -CONTAINERIZE_IMAGE=quay.io/kubermatic/build:go-1.23-node-20-8 containerize ./$API/hack/verify-licenses.sh +CONTAINERIZE_IMAGE=quay.io/kubermatic/build:go-1.23-node-20-12 containerize ./$API/hack/verify-licenses.sh cd $API go mod vendor diff --git a/modules/web/containers/chrome-headless/Dockerfile b/modules/web/containers/chrome-headless/Dockerfile index 8b07e1b9d5..366dd78bd1 100644 --- a/modules/web/containers/chrome-headless/Dockerfile +++ b/modules/web/containers/chrome-headless/Dockerfile @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM quay.io/kubermatic/build:go-1.23-node-20-kind-0.26-8 +FROM quay.io/kubermatic/build:go-1.23-node-20-kind-0.26-12 LABEL maintainer="support@kubermatic.com" diff --git a/modules/web/containers/custom-dashboard/Dockerfile b/modules/web/containers/custom-dashboard/Dockerfile index c3fbafdf6f..05b3af546d 100644 --- a/modules/web/containers/custom-dashboard/Dockerfile +++ b/modules/web/containers/custom-dashboard/Dockerfile @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM quay.io/kubermatic/build:go-1.23-node-20-kind-0.26-8 +FROM quay.io/kubermatic/build:go-1.23-node-20-kind-0.26-12 LABEL maintainer="support@kubermatic.com" ENV NG_CLI_ANALYTICS=ci From 9de69dcd5677f16feac3cc1642a26a57458dd6cd Mon Sep 17 00:00:00 2001 From: Kubermatic Bot <41968677+kubermatic-bot@users.noreply.github.com> Date: Fri, 27 Jun 2025 15:07:53 +0200 Subject: [PATCH 22/42] Rework validation for KubeLB (#7461) Signed-off-by: Waleed Malik Co-authored-by: Waleed Malik --- .../src/app/cluster/details/cluster/edit-cluster/component.ts | 4 ++++ .../app/cluster/details/cluster/edit-cluster/template.html | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/web/src/app/cluster/details/cluster/edit-cluster/component.ts b/modules/web/src/app/cluster/details/cluster/edit-cluster/component.ts index 4485db3c71..67465343e4 100644 --- a/modules/web/src/app/cluster/details/cluster/edit-cluster/component.ts +++ b/modules/web/src/app/cluster/details/cluster/edit-cluster/component.ts @@ -593,6 +593,10 @@ export class EditClusterComponent implements OnInit, OnDestroy { } } + isKubeLBEnabledForCluster(): boolean { + return !!this.cluster?.spec?.kubelb?.enabled; + } + ngOnDestroy(): void { this._unsubscribe.next(); this._unsubscribe.complete(); diff --git a/modules/web/src/app/cluster/details/cluster/edit-cluster/template.html b/modules/web/src/app/cluster/details/cluster/edit-cluster/template.html index b391178faf..04afaf481f 100644 --- a/modules/web/src/app/cluster/details/cluster/edit-cluster/template.html +++ b/modules/web/src/app/cluster/details/cluster/edit-cluster/template.html @@ -255,7 +255,7 @@ Kubermatic KubeLB Date: Fri, 27 Jun 2025 19:21:53 +0500 Subject: [PATCH 23/42] Fix KubeLB checkbox state tooltip and rendering issues (#7458) (#7460) * Fix KubeLB checkbox state, tooltip behavior, and flickering on datacenter chang * Beautify html (cherry picked from commit 73494fd3e316ffabc21b8bad815ab786f93aa646) --- .../cluster/edit-cluster/template.html | 19 +++++++++++-------- .../src/app/wizard/step/cluster/component.ts | 12 +++++------- .../src/app/wizard/step/cluster/template.html | 15 +++++++++------ 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/modules/web/src/app/cluster/details/cluster/edit-cluster/template.html b/modules/web/src/app/cluster/details/cluster/edit-cluster/template.html index 04afaf481f..80d997e09c 100644 --- a/modules/web/src/app/cluster/details/cluster/edit-cluster/template.html +++ b/modules/web/src/app/cluster/details/cluster/edit-cluster/template.html @@ -253,16 +253,19 @@ Kubernetes Dashboard - - Kubermatic KubeLB +
+ + Kubermatic KubeLB + - + [matTooltip]="isKubeLBEnforced ? 'Kubermatic KubeLB is enforced by your admin in the chosen datacenter.' : 'Enable to use Kubermatic KubeLB for managing load balancers in your cluster. This allows automatic provisioning and management of load balancers for your services.'"> +
- +
Use LoadBalancer Class diff --git a/modules/web/src/app/wizard/step/cluster/component.ts b/modules/web/src/app/wizard/step/cluster/component.ts index c49900a437..ca6b86233a 100644 --- a/modules/web/src/app/wizard/step/cluster/component.ts +++ b/modules/web/src/app/wizard/step/cluster/component.ts @@ -274,15 +274,13 @@ export class ClusterStepComponent extends StepBase implements OnInit, ControlVal // If KubeLB is enforced, we need to enable the kubelb control if (this.isKubeLBEnforced) { this.form.get(Controls.KubeLB).setValue(true); + } else { + const kubeLBValue = datacenter.spec.kubelb?.enabled || false; + this.form.get(Controls.KubeLB).setValue(kubeLBValue); } - if (datacenter.spec.kubelb?.enableGatewayAPI) { - this.form.get(Controls.KubeLBEnableGatewayAPI).setValue(true); - } - - if (datacenter.spec.kubelb?.useLoadBalancerClass) { - this.form.get(Controls.KubeLBUseLoadBalancerClass).setValue(true); - } + this.form.get(Controls.KubeLBEnableGatewayAPI).setValue(!!datacenter.spec.kubelb?.enableGatewayAPI); + this.form.get(Controls.KubeLBUseLoadBalancerClass).setValue(!!datacenter.spec.kubelb?.useLoadBalancerClass); this.isCSIDriverDisabled = datacenter.spec.disableCsiDriver; this.enforcedAuditWebhookSettings = datacenter.spec.enforcedAuditWebhookSettings; diff --git a/modules/web/src/app/wizard/step/cluster/template.html b/modules/web/src/app/wizard/step/cluster/template.html index b54439fee3..c22e19152e 100644 --- a/modules/web/src/app/wizard/step/cluster/template.html +++ b/modules/web/src/app/wizard/step/cluster/template.html @@ -463,15 +463,18 @@

IPv6

[matTooltip]="isCSIDriverDisabled ? 'CSI Driver is disabled by your admin in the chosen datacenter.' : 'Disable default CSI storage driver, if available.'">
- - Kubermatic KubeLB +
+ + Kubermatic KubeLB + - +
- +
Use LoadBalancer Class From 0c355e67228ab448947d521ac6cad7fa51d7215d Mon Sep 17 00:00:00 2001 From: Archana Sawant Date: Fri, 27 Jun 2025 21:16:54 +0530 Subject: [PATCH 24/42] [release/v2.27] Bump KKP and MC dependencies (#7462) Signed-off-by: archups --- modules/api/go.mod | 4 ++-- modules/api/go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/api/go.mod b/modules/api/go.mod index 5c70807593..19f6e931ef 100644 --- a/modules/api/go.mod +++ b/modules/api/go.mod @@ -70,8 +70,8 @@ require ( google.golang.org/api v0.209.0 gopkg.in/yaml.v3 v3.0.1 k8c.io/kubeone v1.7.3 - k8c.io/kubermatic/v2 v2.27.5-0.20250602165514-9b87866aaf37 - k8c.io/machine-controller v1.61.2 + k8c.io/kubermatic/v2 v2.27.6-0.20250627130054-13ad5101d4e5 + k8c.io/machine-controller v1.61.3 k8c.io/operating-system-manager v1.6.5 k8c.io/reconciler v0.5.0 k8s.io/api v0.31.3 diff --git a/modules/api/go.sum b/modules/api/go.sum index 4f0de8b3f0..60416ce96f 100644 --- a/modules/api/go.sum +++ b/modules/api/go.sum @@ -1147,10 +1147,10 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8c.io/kubeone v1.7.2 h1:uLH19VEp1S5j4f3UwQP4CLHErJ23UiJM2MnbufNWDgI= k8c.io/kubeone v1.7.2/go.mod h1:9v2VFz/+l36cW65kd5YufEYHunbKlJ6P8SBakj05xgM= -k8c.io/kubermatic/v2 v2.27.5-0.20250602165514-9b87866aaf37 h1:NH12w8hDcVDHvJcqtW8pNwqFwRhAwQ696AkDwTnx1m4= -k8c.io/kubermatic/v2 v2.27.5-0.20250602165514-9b87866aaf37/go.mod h1:TNysG/oe6ZZgfUlVxSdR3yHYMxxdJRLcFHV6+8aDnEg= -k8c.io/machine-controller v1.61.2 h1:F1DQgrTRICkFUoxM3MReQ78HVG9ztXF0xPYPYSXQnJc= -k8c.io/machine-controller v1.61.2/go.mod h1:ZGDFyUeEp66RHcNB5Ki/OJyFdZFgo9dkHJ9s6YJWPcg= +k8c.io/kubermatic/v2 v2.27.6-0.20250627130054-13ad5101d4e5 h1:Nm5jMyFo+Q0a+zoyIdxKLrK4tEbropTLRFuWSdeD9Qo= +k8c.io/kubermatic/v2 v2.27.6-0.20250627130054-13ad5101d4e5/go.mod h1:TNysG/oe6ZZgfUlVxSdR3yHYMxxdJRLcFHV6+8aDnEg= +k8c.io/machine-controller v1.61.3 h1:oQwk7/LB71zd95d9ZeuSZ2FV4ywgSIwfeSnvOfkCs0o= +k8c.io/machine-controller v1.61.3/go.mod h1:ZGDFyUeEp66RHcNB5Ki/OJyFdZFgo9dkHJ9s6YJWPcg= k8c.io/operating-system-manager v1.6.5 h1:F7oBJKEv2t3uzG8k4e9s9j9vCAvXN1FGEkqV0+dMh60= k8c.io/operating-system-manager v1.6.5/go.mod h1:Dh9IZp5pb5G3s2r6ZrHUb+gTuHw5AmbIFYuFxIcgU7o= k8c.io/reconciler v0.5.0 h1:BHpelg1UfI/7oBFctqOq8sX6qzflXpl3SlvHe7e8wak= From e3d14624743bd18a5fe1802468e9eb997d671cd5 Mon Sep 17 00:00:00 2001 From: Waleed Malik Date: Fri, 27 Jun 2025 23:04:54 +0500 Subject: [PATCH 25/42] Fix defaulting for KubeLB enabled (#7467) Signed-off-by: Waleed Malik --- modules/web/src/app/wizard/step/cluster/component.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/modules/web/src/app/wizard/step/cluster/component.ts b/modules/web/src/app/wizard/step/cluster/component.ts index ca6b86233a..a759012851 100644 --- a/modules/web/src/app/wizard/step/cluster/component.ts +++ b/modules/web/src/app/wizard/step/cluster/component.ts @@ -272,12 +272,7 @@ export class ClusterStepComponent extends StepBase implements OnInit, ControlVal this.isKubeLBEnforced = !!datacenter.spec.kubelb?.enforced; // If KubeLB is enforced, we need to enable the kubelb control - if (this.isKubeLBEnforced) { - this.form.get(Controls.KubeLB).setValue(true); - } else { - const kubeLBValue = datacenter.spec.kubelb?.enabled || false; - this.form.get(Controls.KubeLB).setValue(kubeLBValue); - } + this.form.get(Controls.KubeLB).setValue(this.isKubeLBEnforced); this.form.get(Controls.KubeLBEnableGatewayAPI).setValue(!!datacenter.spec.kubelb?.enableGatewayAPI); this.form.get(Controls.KubeLBUseLoadBalancerClass).setValue(!!datacenter.spec.kubelb?.useLoadBalancerClass); From e240d33946073696648a5a14a8ddfce5558b2bf8 Mon Sep 17 00:00:00 2001 From: Kubermatic Bot <41968677+kubermatic-bot@users.noreply.github.com> Date: Mon, 30 Jun 2025 10:20:04 +0200 Subject: [PATCH 26/42] Fix e2e:mock script (#7468) Signed-off-by: Waleed Malik Co-authored-by: Waleed Malik --- modules/web/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/web/package.json b/modules/web/package.json index a01d5a5ab7..fd951d898d 100644 --- a/modules/web/package.json +++ b/modules/web/package.json @@ -27,7 +27,7 @@ "cy": "cypress run --record false -b chrome -e KUBERMATIC_EDITION=\"${KUBERMATIC_EDITION:=ee}\",MOCKS=\"${CYPRESS_MOCKS:='false'}\"", "e2e": "start-server-and-test start:e2e http-get://localhost:8000 cy", "e2e:local": "start-server-and-test start:e2e:local http-get://localhost:8000 cy", - "e2e:mock": "start-server-and-test start:e2e:mock http-get://localhost:8000 cy", + "e2e:mock": "CYPRESS_MOCKS=true start-server-and-test start:e2e:mock http-get://localhost:8000 cy", "check": "npm run check:ts && npm run check:scss && npm run check:dependency-licenses", "check:ts": "gts lint", "check:scss": "stylelint \"src/**/*.scss\"", From f373e11b9b9c367dd6726a0dec22de0129b7d7b6 Mon Sep 17 00:00:00 2001 From: Khizer Rehan Date: Mon, 30 Jun 2025 17:11:03 +0500 Subject: [PATCH 27/42] [release/v2.27] Remove flakiness from ssh-keys integration tests (#7427) (#7469) * Remove flakiness from ssh-keys integration tests (#7427) * Move Fixture inside cypress/fixture folder * Add Wait Fix test to access sidenav before accessing ssh keys pags * Remove comment on reload * Fix to reload page when warning not exists * Fix v2 tests and added CNI Plugin version fixture (cherry picked from commit f9e60528de31868c217cb80ec6c72597f49ac260) * fix cypress config compatible with Node 20 and Cypress 13 --- modules/web/cypress.config.ts | 11 +++--- .../stories/admin-settings/defaults.spec.ts | 35 +++++++++++++----- .../dynamic-datacenters.spec.ts | 15 +++++--- .../cypress/e2e/v2/stories/ssh-keys.spec.ts | 37 +++++++++++++------ .../web/cypress/fixtures/cni-versions.json | 9 +++++ modules/web/{ => cypress}/fixtures/db.json | 0 modules/web/cypress/fixtures/routes.json | 4 ++ .../web/cypress/intercept/settings/admin.ts | 10 +++-- modules/web/cypress/pages/v2/projects/page.ts | 5 ++- modules/web/cypress/pages/v2/wizard/page.ts | 16 +++++--- modules/web/cypress/types/condition.ts | 1 + modules/web/cypress/utils/endpoint.ts | 1 + modules/web/cypress/utils/mocks.ts | 1 + modules/web/fixtures/OWNERS | 13 ------- modules/web/fixtures/routes.json | 3 -- modules/web/package.json | 2 +- .../step/provider-datacenter/template.html | 1 + 17 files changed, 105 insertions(+), 59 deletions(-) create mode 100644 modules/web/cypress/fixtures/cni-versions.json rename modules/web/{ => cypress}/fixtures/db.json (100%) create mode 100644 modules/web/cypress/fixtures/routes.json delete mode 100644 modules/web/fixtures/OWNERS delete mode 100644 modules/web/fixtures/routes.json diff --git a/modules/web/cypress.config.ts b/modules/web/cypress.config.ts index ea84eb2e1b..f6a34c83ff 100644 --- a/modules/web/cypress.config.ts +++ b/modules/web/cypress.config.ts @@ -13,11 +13,12 @@ // limitations under the License. import {defineConfig} from 'cypress'; -import failFast from 'cypress-fail-fast/plugin'; +import failFast from 'cypress-fail-fast/plugin.js'; import {deleteAsync} from 'del'; -function runnableTestsRegex(...fileName: string[]): string { - return `cypress/e2e/**/!(${fileName.reduce((prevName, name) => `${prevName}|${name}`)}).spec.ts`; +function runnableTestsRegex(...fileName) { + const joinedNames = fileName.join('|'); + return `cypress/e2e/**/!(${joinedNames}).spec.ts`; } export default defineConfig({ @@ -42,8 +43,8 @@ export default defineConfig({ const isEnterpriseEdition = config.env.KUBERMATIC_EDITION === 'ee'; // TODO: Update once more tests are rewritten // const ignored: string[] = ['cypress/**/!(service-accounts|ssh-keys).spec.ts']; - const ignored: string[] = [ - runnableTestsRegex('service-accounts', 'ssh-keys', 'edition', 'members', 'defaults', 'dynamic-datacenters'), + const ignored = [ + runnableTestsRegex('service-accounts', 'edition', 'members', 'defaults', 'dynamic-datacenters', 'ssh-keys'), ]; // if (isAPIMocked) { diff --git a/modules/web/cypress/e2e/v2/stories/admin-settings/defaults.spec.ts b/modules/web/cypress/e2e/v2/stories/admin-settings/defaults.spec.ts index 7ab012d202..22f2807682 100644 --- a/modules/web/cypress/e2e/v2/stories/admin-settings/defaults.spec.ts +++ b/modules/web/cypress/e2e/v2/stories/admin-settings/defaults.spec.ts @@ -74,16 +74,33 @@ describe('Admin Settings - Defaults Story', () => { }); it('should create a new cluster and wait for it to start', () => { - Pages.Wizard.create( - clusterName, - Provider.Digitalocean, - Digitalocean.Frankfurt, - preset, - initialMachineDeploymentName, - initialMachineDeploymentReplicas - ); + Pages.Clusters.List.visit(); + Pages.Wizard.visit(); + + // Check if providers are available, if not reload the page + cy.get('km-wizard-provider-step', {timeout: 10000}) + .should(Condition.BeVisible) + .then($body => { + const $warning = $body.find('#km-wizard-seed-warning'); + if ($warning.length > 0) { + cy.reload(); + } + }) + .then(() => { + Pages.Wizard.create( + clusterName, + Provider.Digitalocean, + Digitalocean.Frankfurt, + preset, + initialMachineDeploymentName, + initialMachineDeploymentReplicas + ); + }); + Pages.expect(View.Clusters.Default); - Pages.Clusters.Details.Elements.machineDeploymentList.should(Condition.Contain, initialMachineDeploymentName); + Pages.Clusters.Details.Elements.machineDeploymentList.should(Condition.Contain, initialMachineDeploymentName, { + timeout: 20000, + }); }); it('should make sure default admin settings work', () => { diff --git a/modules/web/cypress/e2e/v2/stories/admin-settings/dynamic-datacenters.spec.ts b/modules/web/cypress/e2e/v2/stories/admin-settings/dynamic-datacenters.spec.ts index 1b25126943..d5cf8931ad 100644 --- a/modules/web/cypress/e2e/v2/stories/admin-settings/dynamic-datacenters.spec.ts +++ b/modules/web/cypress/e2e/v2/stories/admin-settings/dynamic-datacenters.spec.ts @@ -49,15 +49,18 @@ describe('Admin Settings - Datacenters Story', () => { Pages.expect(View.AdminSettings.DynamicDatacenters); }); - it('should create new datacenter', () => { + it('should successfully create and then delete a datacenter', () => { Pages.AdminSettings.DynamicDatacenters.create(datacenterName, provider, seedName, country, location); - Pages.AdminSettings.DynamicDatacenters.Buttons.deleteDatacenter(datacenterName).should(Condition.Exist); - }); + cy.wait('@createDatacenter'); + Pages.AdminSettings.DynamicDatacenters.Buttons.deleteDatacenter(datacenterName) + .should(Condition.Exist) + .and(Condition.BeVisible) + .and(Condition.BeEnabled); - it('should delete created datacenter', () => { - Pages.AdminSettings.DynamicDatacenters.create(datacenterName, provider, seedName, country, location); - Pages.AdminSettings.DynamicDatacenters.Buttons.deleteDatacenter(datacenterName).should(Condition.Exist); Pages.AdminSettings.DynamicDatacenters.delete(datacenterName); + cy.wait('@deleteDatacenter'); + + cy.wait('@getDatacenters'); Pages.AdminSettings.DynamicDatacenters.Buttons.deleteDatacenter(datacenterName).should(Condition.NotExist); }); diff --git a/modules/web/cypress/e2e/v2/stories/ssh-keys.spec.ts b/modules/web/cypress/e2e/v2/stories/ssh-keys.spec.ts index 9c73cc43ae..ff21de2419 100644 --- a/modules/web/cypress/e2e/v2/stories/ssh-keys.spec.ts +++ b/modules/web/cypress/e2e/v2/stories/ssh-keys.spec.ts @@ -59,19 +59,33 @@ describe('SSH Key Management Story', () => { Pages.expect(View.Clusters.Default); }); - it('should create the cluster with ssh key', {retries: 3}, () => { + it('should create the cluster with ssh key', () => { + Pages.Clusters.List.visit(); Pages.Wizard.visit(); - Pages.Wizard.create( - clusterName, - Provider.kubeadm, - BringYourOwn.Frankfurt, - undefined, - undefined, - undefined, - sshKeyName - ); + + // Check if providers are available, if not reload the page + cy.get('km-wizard-provider-step', {timeout: 20000}) + .should(Condition.BeVisible) + .then($body => { + const $warning = $body.find('#km-wizard-seed-warning'); + if ($warning.length > 0) { + cy.reload(); + } + }) + .then(() => { + Pages.Wizard.create( + clusterName, + Provider.kubeadm, + BringYourOwn.Frankfurt, + undefined, + undefined, + undefined, + sshKeyName + ); + }); + Pages.expect(View.Clusters.Default); - Pages.Clusters.Details.Elements.sshKeys(sshKeyName).should(Condition.Exist); + Pages.Clusters.Details.Elements.sshKeys(sshKeyName).should(Condition.Exist, {timeout: 10000}); }); it('should remove the ssh key from the cluster', () => { @@ -87,6 +101,7 @@ describe('SSH Key Management Story', () => { }); it('should delete the ssh key', () => { + Pages.Members.accessSideNavItem(); Pages.SSHKeys.visit(); Pages.expect(View.SSHKeys.Default); Pages.SSHKeys.delete(sshKeyName); diff --git a/modules/web/cypress/fixtures/cni-versions.json b/modules/web/cypress/fixtures/cni-versions.json new file mode 100644 index 0000000000..be55af7cbd --- /dev/null +++ b/modules/web/cypress/fixtures/cni-versions.json @@ -0,0 +1,9 @@ +{ + "cniPluginType": "canal", + "cniDefaultVersion": "v3.29", + "versions": [ + "v3.27", + "v3.28", + "v3.29" + ] +} diff --git a/modules/web/fixtures/db.json b/modules/web/cypress/fixtures/db.json similarity index 100% rename from modules/web/fixtures/db.json rename to modules/web/cypress/fixtures/db.json diff --git a/modules/web/cypress/fixtures/routes.json b/modules/web/cypress/fixtures/routes.json new file mode 100644 index 0000000000..7c2fd4ab48 --- /dev/null +++ b/modules/web/cypress/fixtures/routes.json @@ -0,0 +1,4 @@ +{ + "/api/v1/*": "/$1", + "/api/v2/*": "/$1" +} diff --git a/modules/web/cypress/intercept/settings/admin.ts b/modules/web/cypress/intercept/settings/admin.ts index d6e2b382ba..5f07dd222e 100644 --- a/modules/web/cypress/intercept/settings/admin.ts +++ b/modules/web/cypress/intercept/settings/admin.ts @@ -20,16 +20,18 @@ export class AdminSettings { private static _dynamicDatacentersFixture = Fixtures.Settings.DatacenterList; constructor() { - cy.intercept(Endpoints.Administrator.Settings, req => req.reply({body: AdminSettings._adminSettingsFixture})); + cy.intercept(Endpoints.Administrator.Settings, req => req.reply({body: AdminSettings._adminSettingsFixture})).as( + 'getAdminSettings' + ); cy.intercept(RequestType.GET, Endpoints.Resource.Datacenter.List, req => req.reply({fixture: AdminSettings._dynamicDatacentersFixture}) - ); + ).as('getDatacenters'); cy.intercept(RequestType.POST, Endpoints.Resource.Datacenter.Create, req => req.reply({fixture: Fixtures.Settings.Datacenter}) - ); + ).as('createDatacenter'); cy.intercept(RequestType.DELETE, Endpoints.Resource.Datacenter.Delete, req => req.reply({fixture: Fixtures.Settings.Datacenter}) - ); + ).as('deleteDatacenter'); } onChange(settings: Partial): void { diff --git a/modules/web/cypress/pages/v2/projects/page.ts b/modules/web/cypress/pages/v2/projects/page.ts index 38c322fbf9..7fb1a82e25 100644 --- a/modules/web/cypress/pages/v2/projects/page.ts +++ b/modules/web/cypress/pages/v2/projects/page.ts @@ -56,8 +56,9 @@ export class Projects extends PageOptions implements Page { } create(name: string): void { - this.Buttons.openDialog.click(); - this.Elements.addDialogInput.type(name).then(_ => this._strategy?.onCreate()); + this.Buttons.openDialog.click({force: true}); + this.Elements.addDialogInput.should(Condition.BeVisible).should(Condition.BeEnabled); + this.Elements.addDialogInput.type(name, {force: true}).then(_ => this._strategy?.onCreate()); this.Buttons.addDialogConfirm.click(); } diff --git a/modules/web/cypress/pages/v2/wizard/page.ts b/modules/web/cypress/pages/v2/wizard/page.ts index 199e6ba619..96ac6b7682 100644 --- a/modules/web/cypress/pages/v2/wizard/page.ts +++ b/modules/web/cypress/pages/v2/wizard/page.ts @@ -29,7 +29,7 @@ export class Wizard extends PageOptions implements Page { } visit(): void { - this.Buttons.open.click(); + this.Buttons.open.should(Condition.Exist).click(); } create( @@ -49,7 +49,9 @@ export class Wizard extends PageOptions implements Page { this.Buttons.datacenter(datacenter) .click() .then(_ => this._strategy?.onCreate(provider)); - this.Elements.clusterNameInput.type(name).should(Condition.HaveValue, name); + + const shortWait = 200; + this.Elements.clusterNameInput.type(name).should(Condition.HaveValue, name).blur().wait(shortWait); if (sshKeyName) { this.Buttons.sshKeysSelect.click(); @@ -68,20 +70,24 @@ export class Wizard extends PageOptions implements Page { return; } - this.Buttons.presetSelect.click(); + // Preset selection is required for all providers except kubeadm + this.Buttons.presetSelect.click({force: true}); this.Buttons.presetSelectOption(presetName!).click(); this.Buttons.nextStep(WizardStep.ProviderSettings).click(); + // Initial node settings if (mdName) { this.Elements.nodeNameInput.type(mdName).should(Condition.HaveValue, mdName); } - if (replicas) { this.Elements.nodeCountInput.clear().type(replicas).should(Condition.HaveValue, replicas); } - this.Buttons.nextStep(WizardStep.NodeSettings).click(); + + // Applications step this.Buttons.nextStep(WizardStep.Applications).click(); + + // Summary step this.Buttons.create.click({force: true}); } } diff --git a/modules/web/cypress/types/condition.ts b/modules/web/cypress/types/condition.ts index 49decd50fe..17e2d55b47 100644 --- a/modules/web/cypress/types/condition.ts +++ b/modules/web/cypress/types/condition.ts @@ -26,6 +26,7 @@ export enum Condition { NotBe = 'not.be', NotBeChecked = 'not.be.checked', NotBeVisible = 'not.be.visible', + NotBeDisabled = 'not.be.disabled', NotContain = 'not.contain', NotExist = 'not.exist', NotHaveClass = 'not.have.class', diff --git a/modules/web/cypress/utils/endpoint.ts b/modules/web/cypress/utils/endpoint.ts index a33f54aa41..4e109edb15 100644 --- a/modules/web/cypress/utils/endpoint.ts +++ b/modules/web/cypress/utils/endpoint.ts @@ -119,4 +119,5 @@ export namespace Endpoint { export const AdmissionPlugins = '**/api/**/admission/plugins/*'; export const Versions = '**/providers/*/versions'; export const FeatureGates = '**/featuregates'; + export const CNIPluginVersions = '**/cni/*/versions'; } diff --git a/modules/web/cypress/utils/mocks.ts b/modules/web/cypress/utils/mocks.ts index d13c34fec9..e173a65465 100644 --- a/modules/web/cypress/utils/mocks.ts +++ b/modules/web/cypress/utils/mocks.ts @@ -193,6 +193,7 @@ export class Mocks { {m: RequestType.GET, p: Endpoint.GatekeeperConfig, r: Mocks.gatekeeperConfig}, {m: RequestType.POST, p: Endpoint.GatekeeperConfig, r: Mocks.defaultGatekeeperConfig}, {m: RequestType.GET, p: Endpoint.FeatureGates, r: {fixture: 'feature-gates.json'}}, + {m: RequestType.GET, p: Endpoint.CNIPluginVersions, r: {fixture: 'cni-versions.json'}}, ]; static enabled(): boolean { diff --git a/modules/web/fixtures/OWNERS b/modules/web/fixtures/OWNERS deleted file mode 100644 index edaf888ef8..0000000000 --- a/modules/web/fixtures/OWNERS +++ /dev/null @@ -1,13 +0,0 @@ -# See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md - -approvers: - - sig-ui - -reviewers: - - sig-ui - -labels: - - sig-ui - -options: - no_parent_owners: true diff --git a/modules/web/fixtures/routes.json b/modules/web/fixtures/routes.json deleted file mode 100644 index 21ae47deaf..0000000000 --- a/modules/web/fixtures/routes.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "/api/v1/*": "/$1" -} diff --git a/modules/web/package.json b/modules/web/package.json index fd951d898d..65b7a753cc 100644 --- a/modules/web/package.json +++ b/modules/web/package.json @@ -18,7 +18,7 @@ "start:e2e": "npm run vi && ng s -c=e2e-\"${KUBERMATIC_EDITION:=ee}\"", "start:e2e:local": "npm run vi && ng s -c=e2e-local-\"${KUBERMATIC_EDITION:=ee}\" --proxy-config=proxy-local.conf.cjs", "start:e2e:mock": "concurrently \"npm run start:mock-server\" \"npm run vi && ng s -c=e2e-mock-\"${KUBERMATIC_EDITION:=ee}\" --proxy-config=proxy-local.conf.cjs\"", - "start:mock-server": "json-server --watch fixtures/db.json --routes fixtures/routes.json --port 8080 -q", + "start:mock-server": "json-server --watch cypress/fixtures/db.json --routes cypress/fixtures/routes.json --port 8080 -q", "build": "npm run vi && ng b -c=production-\"${KUBERMATIC_EDITION:=ee}\"", "build:themes": "npm run build && ./hack/extract-themes.sh", "test": "jest", diff --git a/modules/web/src/app/wizard/step/provider-datacenter/template.html b/modules/web/src/app/wizard/step/provider-datacenter/template.html index 5d5f50271d..f3106e4e18 100644 --- a/modules/web/src/app/wizard/step/provider-datacenter/template.html +++ b/modules/web/src/app/wizard/step/provider-datacenter/template.html @@ -18,6 +18,7 @@ fxLayout="column">
You need to deploy a seed cluster before you can create user clusters. From 5570373e1df5037b6be1ecb5b4f03762bef7c7de Mon Sep 17 00:00:00 2001 From: Kubermatic Bot <41968677+kubermatic-bot@users.noreply.github.com> Date: Mon, 30 Jun 2025 18:11:04 +0200 Subject: [PATCH 28/42] [release/v2.27] Clear tunnelingAgentIP when expose strategy is not Tunneling (#7471) * Reset tunnelingAgentIP based on exposeStrategy * Handle resetting when not of tunneling expose strategy * Reset is handled/patched from cluster component when expose strategy is changed --------- Co-authored-by: Khizer Rehan --- modules/web/src/app/wizard/component.ts | 5 +---- modules/web/src/app/wizard/step/cluster/component.ts | 12 +++++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/modules/web/src/app/wizard/component.ts b/modules/web/src/app/wizard/component.ts index ecb1b15497..2ea01f0a01 100644 --- a/modules/web/src/app/wizard/component.ts +++ b/modules/web/src/app/wizard/component.ts @@ -24,7 +24,7 @@ import {NotificationService} from '@core/services/notification'; import {ProjectService} from '@core/services/project'; import {WizardService} from '@core/services/wizard/wizard'; import {SaveClusterTemplateDialogComponent} from '@shared/components/save-cluster-template/component'; -import {Cluster, CreateClusterModel, ExposeStrategy} from '@shared/entity/cluster'; +import {Cluster, CreateClusterModel} from '@shared/entity/cluster'; import {Project} from '@shared/entity/project'; import {OPERATING_SYSTEM_PROFILE_ANNOTATION} from '@shared/entity/machine-deployment'; import {NodeData} from '@shared/model/NodeSpecChange'; @@ -286,9 +286,6 @@ export class WizardComponent implements OnInit, OnDestroy { }, applications: applications, }; - if (cluster.spec.exposeStrategy !== ExposeStrategy.tunneling) { - clusterModel.cluster.spec.clusterNetwork.tunnelingAgentIP = null; - } if (nodeData.operatingSystemProfile) { clusterModel.nodeDeployment.annotations = { ...clusterModel.nodeDeployment.annotations, diff --git a/modules/web/src/app/wizard/step/cluster/component.ts b/modules/web/src/app/wizard/step/cluster/component.ts index a759012851..c1a20fd6af 100644 --- a/modules/web/src/app/wizard/step/cluster/component.ts +++ b/modules/web/src/app/wizard/step/cluster/component.ts @@ -410,9 +410,15 @@ export class ClusterStepComponent extends StepBase implements OnInit, ControlVal this.form.get(Controls.APIServerAllowedIPRanges).valueChanges ) .pipe(takeUntil(this._unsubscribe)) - .subscribe( - _ => (this._clusterSpecService.cluster.spec.apiServerAllowedIPRanges = this.getAPIServerAllowedIPRange()) - ); + .subscribe(_ => { + this._clusterSpecService.cluster.spec.apiServerAllowedIPRanges = this.getAPIServerAllowedIPRange(); + + const exposeStrategy = this.form.get(Controls.ExposeStrategy).value; + const clusterNetwork = this._clusterSpecService.cluster.spec.clusterNetwork; + if (exposeStrategy !== ExposeStrategy.tunneling) { + clusterNetwork.tunnelingAgentIP = null; + } + }); merge(this.form.get(Controls.CNIPlugin).valueChanges, this.form.get(Controls.CNIPluginVersion).valueChanges) .pipe(takeUntil(this._unsubscribe)) From f0c7bac732f0cd5ad776bee5ca489cea9e365669 Mon Sep 17 00:00:00 2001 From: Khizer Rehan Date: Mon, 7 Jul 2025 19:21:22 +0500 Subject: [PATCH 29/42] Prevent project viewer from creating cluster templates (#7446) (#7482) * Fix permission config key for cluster template obj * Refactor code to remove redundancy and display tooltip in disabled state * Improve Readability of code to allow deletion of template if user is is owner itself * Handle Avoid deletion of clustertemplates when role as a viewer * Update Error/Test Messages for viewer role (cherry picked from commit 9c39de9a1bdab072a12a559f81d13cbec417d1a3) --- .../v2/cluster_template/cluster_template.go | 10 +- .../cluster_template/cluster_template_test.go | 98 +++++++++++++++++++ .../provider/kubernetes/cluster_template.go | 8 +- .../web/src/app/cluster-template/component.ts | 65 ++++++------ .../src/app/cluster-template/template.html | 20 ++-- .../src/assets/config/userGroupConfig.json | 6 +- 6 files changed, 152 insertions(+), 55 deletions(-) diff --git a/modules/api/pkg/handler/v2/cluster_template/cluster_template.go b/modules/api/pkg/handler/v2/cluster_template/cluster_template.go index ca83921429..fbc9112350 100644 --- a/modules/api/pkg/handler/v2/cluster_template/cluster_template.go +++ b/modules/api/pkg/handler/v2/cluster_template/cluster_template.go @@ -335,7 +335,7 @@ func getClusterTemplate(ctx context.Context, projectProvider provider.ProjectPro return nil, common.KubernetesErrorToHTTPError(err) } - userInfo, err := userInfoGetter(ctx, "") + userInfo, err := userInfoGetter(ctx, projectID) if err != nil { return nil, common.KubernetesErrorToHTTPError(err) } @@ -436,7 +436,7 @@ func createOrUpdateClusterTemplate(ctx context.Context, userInfoGetter provider. if err != nil { return nil, common.KubernetesErrorToHTTPError(err) } - adminUserInfo, err := userInfoGetter(ctx, "") + userInfo, err := userInfoGetter(ctx, projectID) if err != nil { return nil, common.KubernetesErrorToHTTPError(err) } @@ -483,7 +483,7 @@ func createOrUpdateClusterTemplate(ctx context.Context, userInfoGetter provider. newClusterTemplate.Annotations[kubermaticv1.InitialMachineDeploymentRequestAnnotation] = partialCluster.Annotations[kubermaticv1.InitialMachineDeploymentRequestAnnotation] - newClusterTemplate.Annotations[kubermaticv1.ClusterTemplateUserAnnotationKey] = adminUserInfo.Email + newClusterTemplate.Annotations[kubermaticv1.ClusterTemplateUserAnnotationKey] = userInfo.Email newClusterTemplate.Labels[kubermaticv1.ClusterTemplateProjectLabelKey] = project.Name newClusterTemplate.Labels[kubermaticv1.ClusterTemplateScopeLabelKey] = scope newClusterTemplate.Labels[kubermaticv1.ClusterTemplateHumanReadableNameLabelKey] = name @@ -516,7 +516,7 @@ func createOrUpdateClusterTemplate(ctx context.Context, userInfoGetter provider. } } - ct, err := clusterTemplateProvider.CreateorUpdate(ctx, adminUserInfo, newClusterTemplate, scope, project.Name, isUpdateRequest) + ct, err := clusterTemplateProvider.CreateorUpdate(ctx, userInfo, newClusterTemplate, scope, project.Name, isUpdateRequest) if err != nil { return nil, common.KubernetesErrorToHTTPError(err) } @@ -536,7 +536,7 @@ func DeleteEndpoint(projectProvider provider.ProjectProvider, privilegedProjectP return nil, common.KubernetesErrorToHTTPError(err) } - userInfo, err := userInfoGetter(ctx, "") + userInfo, err := userInfoGetter(ctx, req.ProjectID) if err != nil { return nil, common.KubernetesErrorToHTTPError(err) } diff --git a/modules/api/pkg/handler/v2/cluster_template/cluster_template_test.go b/modules/api/pkg/handler/v2/cluster_template/cluster_template_test.go index 25bddc7402..7c684dad81 100644 --- a/modules/api/pkg/handler/v2/cluster_template/cluster_template_test.go +++ b/modules/api/pkg/handler/v2/cluster_template/cluster_template_test.go @@ -146,6 +146,48 @@ func TestCreateClusterTemplateEndpoint(t *testing.T) { ), ExistingAPIUser: test.GenDefaultAPIUser(), }, + // scenario 7 + { + Name: "scenario 7: viewer can't create cluster template in user scope", + Body: fmt.Sprintf(`{"name":"test","scope":"user","cluster":{"name":"keen-snyder","spec":{"version":"%s","cloud":{"fake":{"token":"dummy_token"},"dc":"fake-dc"}}}}`, version), + ExpectedResponse: `{"error":{"code":403,"message":"user viewer@acme.com has viewer role and cannot create or update cluster templates regardless of any scope"}}`, + HTTPStatus: http.StatusForbidden, + ProjectToSync: test.GenDefaultProject().Name, + ExistingKubermaticObjs: test.GenDefaultKubermaticObjects( + test.GenTestSeed(), + test.GenBinding("my-first-project-ID", "viewer@acme.com", "viewers"), + test.GenUser("", "Viewer", "viewer@acme.com"), + ), + ExistingAPIUser: test.GenAPIUser("Viewer", "viewer@acme.com"), + }, + // scenario 8 + { + Name: "scenario 8: viewer can't create cluster template in project scope", + Body: fmt.Sprintf(`{"name":"test","scope":"project","cluster":{"name":"keen-snyder","spec":{"version":"%s","cloud":{"fake":{"token":"dummy_token"},"dc":"fake-dc"}}}}`, version), + ExpectedResponse: `{"error":{"code":403,"message":"user viewer@acme.com has viewer role and cannot create or update cluster templates regardless of any scope"}}`, + HTTPStatus: http.StatusForbidden, + ProjectToSync: test.GenDefaultProject().Name, + ExistingKubermaticObjs: test.GenDefaultKubermaticObjects( + test.GenTestSeed(), + test.GenBinding("my-first-project-ID", "viewer@acme.com", "viewers"), + test.GenUser("", "Viewer", "viewer@acme.com"), + ), + ExistingAPIUser: test.GenAPIUser("Viewer", "viewer@acme.com"), + }, + // scenario 9 + { + Name: "scenario 9: viewer can't create cluster template in global scope", + Body: fmt.Sprintf(`{"name":"test","scope":"global","cluster":{"name":"keen-snyder","spec":{"version":"%s","cloud":{"fake":{"token":"dummy_token"},"dc":"fake-dc"}}}}`, version), + ExpectedResponse: `{"error":{"code":500,"message":"the global scope is reserved only for admins"}}`, + HTTPStatus: http.StatusInternalServerError, + ProjectToSync: test.GenDefaultProject().Name, + ExistingKubermaticObjs: test.GenDefaultKubermaticObjects( + test.GenTestSeed(), + test.GenBinding("my-first-project-ID", "viewer@acme.com", "viewers"), + test.GenUser("", "Viewer", "viewer@acme.com"), + ), + ExistingAPIUser: test.GenAPIUser("Viewer", "viewer@acme.com"), + }, } dummyKubermaticConfiguration := kubermaticv1.KubermaticConfiguration{ @@ -548,6 +590,20 @@ func TestDeleteClusterTemplates(t *testing.T) { ), ExistingAPIUser: test.GenDefaultAPIUser(), }, + // scenario 6 + { + Name: "scenario 6: viewer can't delete cluster template", + TemplateID: "ctID6", + ExpectedResponse: `{"error":{"code":403,"message":"user viewer@acme.com has viewer role and cannot delete cluster templates regardless of any scope"}}`, + HTTPStatus: http.StatusForbidden, + ExistingKubermaticObjs: test.GenDefaultKubermaticObjects( + test.GenTestSeed(), + test.GenBinding("my-first-project-ID", "viewer@acme.com", "viewers"), + test.GenUser("", "Viewer", "viewer@acme.com"), + test.GenClusterTemplate("ct6", "ctID6", test.GenDefaultProject().Name, kubermaticv1.UserClusterTemplateScope, "viewer@acme.com"), + ), + ExistingAPIUser: test.GenAPIUser("Viewer", "viewer@acme.com"), + }, } for _, tc := range testcases { @@ -829,6 +885,48 @@ func TestImportClusterTemplateEndpoint(t *testing.T) { ), ExistingAPIUser: test.GenDefaultAPIUser(), }, + // scenario 5 + { + Name: "scenario 5: viewer can't import cluster template in user scope", + Body: fmt.Sprintf(`{"name":"test","scope":"user","cluster":{"name":"keen-snyder","spec":{"version":"%s","cloud":{"fake":{"token":"dummy_token"},"dc":"fake-dc"}}}}`, version), + ExpectedResponse: `{"error":{"code":403,"message":"user viewer@acme.com has viewer role and cannot create or update cluster templates regardless of any scope"}}`, + HTTPStatus: http.StatusForbidden, + ProjectToSync: test.GenDefaultProject().Name, + ExistingKubermaticObjs: test.GenDefaultKubermaticObjects( + test.GenTestSeed(), + test.GenBinding("my-first-project-ID", "viewer@acme.com", "viewers"), + test.GenUser("", "Viewer", "viewer@acme.com"), + ), + ExistingAPIUser: test.GenAPIUser("Viewer", "viewer@acme.com"), + }, + // scenario 6 + { + Name: "scenario 6: viewer can't import cluster template in project scope", + Body: fmt.Sprintf(`{"name":"test","scope":"project","cluster":{"name":"keen-snyder","spec":{"version":"%s","cloud":{"fake":{"token":"dummy_token"},"dc":"fake-dc"}}}}`, version), + ExpectedResponse: `{"error":{"code":403,"message":"user viewer@acme.com has viewer role and cannot create or update cluster templates regardless of any scope"}}`, + HTTPStatus: http.StatusForbidden, + ProjectToSync: test.GenDefaultProject().Name, + ExistingKubermaticObjs: test.GenDefaultKubermaticObjects( + test.GenTestSeed(), + test.GenBinding("my-first-project-ID", "viewer@acme.com", "viewers"), + test.GenUser("", "Viewer", "viewer@acme.com"), + ), + ExistingAPIUser: test.GenAPIUser("Viewer", "viewer@acme.com"), + }, + // scenario 7 + { + Name: "scenario 7: viewer can't import cluster template in global scope", + Body: fmt.Sprintf(`{"name":"test","scope":"global","cluster":{"name":"keen-snyder","spec":{"version":"%s","cloud":{"fake":{"token":"dummy_token"},"dc":"fake-dc"}}}}`, version), + ExpectedResponse: `{"error":{"code":500,"message":"the global scope is reserved only for admins"}}`, + HTTPStatus: http.StatusInternalServerError, + ProjectToSync: test.GenDefaultProject().Name, + ExistingKubermaticObjs: test.GenDefaultKubermaticObjects( + test.GenTestSeed(), + test.GenBinding("my-first-project-ID", "viewer@acme.com", "viewers"), + test.GenUser("", "Viewer", "viewer@acme.com"), + ), + ExistingAPIUser: test.GenAPIUser("Viewer", "viewer@acme.com"), + }, } dummyKubermaticConfiguration := kubermaticv1.KubermaticConfiguration{ diff --git a/modules/api/pkg/provider/kubernetes/cluster_template.go b/modules/api/pkg/provider/kubernetes/cluster_template.go index 81ea42038d..4ed77d5fec 100644 --- a/modules/api/pkg/provider/kubernetes/cluster_template.go +++ b/modules/api/pkg/provider/kubernetes/cluster_template.go @@ -67,8 +67,8 @@ func (p *ClusterTemplateProvider) CreateorUpdate(ctx context.Context, userInfo * return nil, errors.New("the global scope is reserved only for admins") } - if userInfo.Roles.Has("viewers") && userInfo.Roles.Len() == 1 && scope != kubermaticv1.UserClusterTemplateScope { - return nil, fmt.Errorf("viewer is not allowed to create cluster template for the %s scope", scope) + if userInfo.Roles.Has("viewers") && userInfo.Roles.Len() == 1 { + return nil, utilerrors.New(http.StatusForbidden, fmt.Sprintf("user %s has viewer role and cannot create or update cluster templates regardless of any scope", userInfo.Email)) } if scope == kubermaticv1.ProjectClusterTemplateScope && projectID == "" { @@ -180,6 +180,10 @@ func (p *ClusterTemplateProvider) Delete(ctx context.Context, userInfo *provider return utilerrors.New(http.StatusForbidden, fmt.Sprintf("user %s can't delete template %s", userInfo.Email, templateID)) } + if userInfo.Roles.Has("viewers") && userInfo.Roles.Len() == 1 { + return utilerrors.New(http.StatusForbidden, fmt.Sprintf("user %s has viewer role and cannot delete cluster templates regardless of any scope", userInfo.Email)) + } + return p.clientPrivileged.Delete(ctx, result) } diff --git a/modules/web/src/app/cluster-template/component.ts b/modules/web/src/app/cluster-template/component.ts index 7f771aff6a..6d45b9d5a8 100644 --- a/modules/web/src/app/cluster-template/component.ts +++ b/modules/web/src/app/cluster-template/component.ts @@ -49,6 +49,8 @@ import {filter, startWith, switchMap, take, takeUntil, tap} from 'rxjs/operators styleUrls: ['./style.scss'], }) export class ClusterTemplateComponent implements OnInit, OnChanges, OnDestroy { + readonly Permission = Permission; + templates: ClusterTemplate[] = []; templateDatacenterMap: Map = new Map(); isInitializing = true; @@ -81,6 +83,9 @@ export class ClusterTemplateComponent implements OnInit, OnChanges, OnDestroy { private readonly _notificationService: NotificationService ) {} + get isAdmin(): boolean { + return !!this.currentUser?.isAdmin; + } ngOnInit(): void { this._setupList(); this._loadUser(); @@ -178,51 +183,26 @@ export class ClusterTemplateComponent implements OnInit, OnChanges, OnDestroy { return Cluster.getProvider(cluster); } - canCreate(): boolean { - return MemberUtils.hasPermission( - this.currentUser, - this._currentGroupConfig, - View.ClusterTemplates, - Permission.Create + can(permission: Permission): boolean { + return ( + this.currentUser && + this._currentGroupConfig && + MemberUtils.hasPermission(this.currentUser, this._currentGroupConfig, View.ClusterTemplates, permission) ); } - canEdit(template: ClusterTemplate): boolean { - switch (template.scope) { - case ClusterTemplateScope.Global: - return this.currentUser.isAdmin; - case ClusterTemplateScope.User: - return this.currentUser.isAdmin || this.currentUser.email === template.user; - case ClusterTemplateScope.Project: - return MemberUtils.hasPermission( - this.currentUser, - this._currentGroupConfig, - View.ClusterTemplates, - Permission.Edit - ); - } - } - create(): void { this._router.navigate([`/projects/${this._selectedProject.id}/wizard`], { state: {mode: WizardMode.CreateClusterTemplate}, }); } + canEdit(template: ClusterTemplate): boolean { + return this._canModify(template, Permission.Edit); + } + canDelete(template: ClusterTemplate): boolean { - switch (template.scope) { - case ClusterTemplateScope.Global: - return this.currentUser.isAdmin; - case ClusterTemplateScope.User: - return this.currentUser.isAdmin || this.currentUser.email === template.user; - case ClusterTemplateScope.Project: - return MemberUtils.hasPermission( - this.currentUser, - this._currentGroupConfig, - View.ClusterTemplates, - Permission.Delete - ); - } + return this._canModify(template, Permission.Delete); } delete(template: ClusterTemplate): void { @@ -288,4 +268,19 @@ export class ClusterTemplateComponent implements OnInit, OnChanges, OnDestroy { component.projectId = id; }); } + + private _canModify(template: ClusterTemplate, permission: Permission): boolean { + switch (template.scope) { + case ClusterTemplateScope.Global: + return this.isAdmin; + case ClusterTemplateScope.User: + return this.isAdmin || this._isTemplateOwner(template); + case ClusterTemplateScope.Project: + return this.can(permission); + } + } + + private _isTemplateOwner(template: ClusterTemplate): boolean { + return this.currentUser?.email === template.user; + } } diff --git a/modules/web/src/app/cluster-template/template.html b/modules/web/src/app/cluster-template/template.html index 8570921e91..a65f429fa8 100644 --- a/modules/web/src/app/cluster-template/template.html +++ b/modules/web/src/app/cluster-template/template.html @@ -29,16 +29,16 @@
- + + +
diff --git a/modules/web/src/assets/config/userGroupConfig.json b/modules/web/src/assets/config/userGroupConfig.json index af5d5791de..635c5cf3e6 100644 --- a/modules/web/src/assets/config/userGroupConfig.json +++ b/modules/web/src/assets/config/userGroupConfig.json @@ -30,7 +30,7 @@ "create": true, "delete": true }, - "clusterTemplates": { + "clustertemplates": { "view": true, "edit": true, "create": true, @@ -110,7 +110,7 @@ "create": true, "delete": true }, - "clusterTemplates": { + "clustertemplates": { "view": true, "edit": true, "create": true, @@ -190,7 +190,7 @@ "create": false, "delete": false }, - "clusterTemplates": { + "clustertemplates": { "view": true, "edit": false, "create": false, From a0505236caf1f9b8d068159b5340a1e4668d957b Mon Sep 17 00:00:00 2001 From: Archana Sawant Date: Wed, 9 Jul 2025 02:00:24 +0530 Subject: [PATCH 30/42] [release/v2.27] Bump KKP and OSM dependencies for KKP patch release (#7490) Signed-off-by: archups --- Makefile | 2 +- modules/api/Makefile | 2 +- modules/api/go.mod | 4 ++-- modules/api/go.sum | 8 ++++---- modules/web/Makefile | 2 +- modules/web/package-lock.json | 4 ++-- modules/web/package.json | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 21c2ae1501..5df2d45f85 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ SHELL = /bin/bash -eu -o pipefail export KUBERMATIC_EDITION ?= ee -KUBERMATIC_VERSION?=v2.27.4 +KUBERMATIC_VERSION?=v2.27.6 DOCKER_REPO ?= quay.io/kubermatic REPO = $(DOCKER_REPO)/dashboard$(shell [[ "$(KUBERMATIC_EDITION)" != "ce" ]] && printf -- '-%s' ${KUBERMATIC_EDITION}) IMAGE_TAG=$(shell echo $$(git rev-parse HEAD)|tr -d '\n') diff --git a/modules/api/Makefile b/modules/api/Makefile index 1587555afa..45bbd154ea 100644 --- a/modules/api/Makefile +++ b/modules/api/Makefile @@ -1,6 +1,6 @@ SHELL = /bin/bash -eu -o pipefail export KUBERMATIC_EDITION ?= ee -KUBERMATIC_VERSION?=v2.27.4 +KUBERMATIC_VERSION?=v2.27.6 DOCKER_REPO ?= quay.io/kubermatic REPO = $(DOCKER_REPO)/dashboard$(shell [[ "$(KUBERMATIC_EDITION)" != "ce" ]] && printf -- '-%s' ${KUBERMATIC_EDITION}) IMAGE_TAG=$(shell echo $$(git rev-parse HEAD)|tr -d '\n') diff --git a/modules/api/go.mod b/modules/api/go.mod index 19f6e931ef..7cf9becf04 100644 --- a/modules/api/go.mod +++ b/modules/api/go.mod @@ -70,9 +70,9 @@ require ( google.golang.org/api v0.209.0 gopkg.in/yaml.v3 v3.0.1 k8c.io/kubeone v1.7.3 - k8c.io/kubermatic/v2 v2.27.6-0.20250627130054-13ad5101d4e5 + k8c.io/kubermatic/v2 v2.27.6-0.20250708191423-b2ed9845b758 k8c.io/machine-controller v1.61.3 - k8c.io/operating-system-manager v1.6.5 + k8c.io/operating-system-manager v1.6.7 k8c.io/reconciler v0.5.0 k8s.io/api v0.31.3 k8s.io/apiextensions-apiserver v0.31.3 diff --git a/modules/api/go.sum b/modules/api/go.sum index 60416ce96f..9179480ff5 100644 --- a/modules/api/go.sum +++ b/modules/api/go.sum @@ -1147,12 +1147,12 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8c.io/kubeone v1.7.2 h1:uLH19VEp1S5j4f3UwQP4CLHErJ23UiJM2MnbufNWDgI= k8c.io/kubeone v1.7.2/go.mod h1:9v2VFz/+l36cW65kd5YufEYHunbKlJ6P8SBakj05xgM= -k8c.io/kubermatic/v2 v2.27.6-0.20250627130054-13ad5101d4e5 h1:Nm5jMyFo+Q0a+zoyIdxKLrK4tEbropTLRFuWSdeD9Qo= -k8c.io/kubermatic/v2 v2.27.6-0.20250627130054-13ad5101d4e5/go.mod h1:TNysG/oe6ZZgfUlVxSdR3yHYMxxdJRLcFHV6+8aDnEg= +k8c.io/kubermatic/v2 v2.27.6-0.20250708191423-b2ed9845b758 h1:HOSTr0Xt0u8D1wsdpGBwziri18cJemhgNC1o6uXfD/4= +k8c.io/kubermatic/v2 v2.27.6-0.20250708191423-b2ed9845b758/go.mod h1:TNysG/oe6ZZgfUlVxSdR3yHYMxxdJRLcFHV6+8aDnEg= k8c.io/machine-controller v1.61.3 h1:oQwk7/LB71zd95d9ZeuSZ2FV4ywgSIwfeSnvOfkCs0o= k8c.io/machine-controller v1.61.3/go.mod h1:ZGDFyUeEp66RHcNB5Ki/OJyFdZFgo9dkHJ9s6YJWPcg= -k8c.io/operating-system-manager v1.6.5 h1:F7oBJKEv2t3uzG8k4e9s9j9vCAvXN1FGEkqV0+dMh60= -k8c.io/operating-system-manager v1.6.5/go.mod h1:Dh9IZp5pb5G3s2r6ZrHUb+gTuHw5AmbIFYuFxIcgU7o= +k8c.io/operating-system-manager v1.6.7 h1:n7rViYgFeccEUmMeFAqvtyappr/0kxAVtKJNe66x+KU= +k8c.io/operating-system-manager v1.6.7/go.mod h1:Dh9IZp5pb5G3s2r6ZrHUb+gTuHw5AmbIFYuFxIcgU7o= k8c.io/reconciler v0.5.0 h1:BHpelg1UfI/7oBFctqOq8sX6qzflXpl3SlvHe7e8wak= k8c.io/reconciler v0.5.0/go.mod h1:pT1+SVcVXJQeBJhpJBXQ5XW64QnKKeYTnVlQf0dGE0k= k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= diff --git a/modules/web/Makefile b/modules/web/Makefile index 488139b6f9..a53e86cc64 100644 --- a/modules/web/Makefile +++ b/modules/web/Makefile @@ -1,6 +1,6 @@ SHELL = /bin/bash -eu -o pipefail export KUBERMATIC_EDITION ?= ee -KUBERMATIC_VERSION?=v2.27.4 +KUBERMATIC_VERSION?=v2.27.6 CC=npm GOOS ?= $(shell go env GOOS) export GOOS diff --git a/modules/web/package-lock.json b/modules/web/package-lock.json index 6d085e5ab0..e95bff0c22 100644 --- a/modules/web/package-lock.json +++ b/modules/web/package-lock.json @@ -1,12 +1,12 @@ { "name": "kubermatic-dashboard", - "version": "2.27.4", + "version": "2.27.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "kubermatic-dashboard", - "version": "2.27.4", + "version": "2.27.6", "hasInstallScript": true, "license": "proprietary", "dependencies": { diff --git a/modules/web/package.json b/modules/web/package.json index 65b7a753cc..305311da5d 100644 --- a/modules/web/package.json +++ b/modules/web/package.json @@ -1,7 +1,7 @@ { "name": "kubermatic-dashboard", "description": "Kubermatic Dashboard", - "version": "2.27.4", + "version": "2.27.6", "type": "module", "license": "proprietary", "repository": "https://github.com/kubermatic/dashboard", From 3a9025de31168439bb4959779f977991143baf87 Mon Sep 17 00:00:00 2001 From: Archana Sawant Date: Thu, 10 Jul 2025 18:38:30 +0530 Subject: [PATCH 31/42] [release/v2.27] Bump KKP dependencies (#7497) Signed-off-by: archups --- modules/api/go.mod | 2 +- modules/api/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/api/go.mod b/modules/api/go.mod index 7cf9becf04..5ed053b982 100644 --- a/modules/api/go.mod +++ b/modules/api/go.mod @@ -70,7 +70,7 @@ require ( google.golang.org/api v0.209.0 gopkg.in/yaml.v3 v3.0.1 k8c.io/kubeone v1.7.3 - k8c.io/kubermatic/v2 v2.27.6-0.20250708191423-b2ed9845b758 + k8c.io/kubermatic/v2 v2.27.6-0.20250709121724-073944c20a13 k8c.io/machine-controller v1.61.3 k8c.io/operating-system-manager v1.6.7 k8c.io/reconciler v0.5.0 diff --git a/modules/api/go.sum b/modules/api/go.sum index 9179480ff5..0e84219b9d 100644 --- a/modules/api/go.sum +++ b/modules/api/go.sum @@ -1147,8 +1147,8 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8c.io/kubeone v1.7.2 h1:uLH19VEp1S5j4f3UwQP4CLHErJ23UiJM2MnbufNWDgI= k8c.io/kubeone v1.7.2/go.mod h1:9v2VFz/+l36cW65kd5YufEYHunbKlJ6P8SBakj05xgM= -k8c.io/kubermatic/v2 v2.27.6-0.20250708191423-b2ed9845b758 h1:HOSTr0Xt0u8D1wsdpGBwziri18cJemhgNC1o6uXfD/4= -k8c.io/kubermatic/v2 v2.27.6-0.20250708191423-b2ed9845b758/go.mod h1:TNysG/oe6ZZgfUlVxSdR3yHYMxxdJRLcFHV6+8aDnEg= +k8c.io/kubermatic/v2 v2.27.6-0.20250709121724-073944c20a13 h1:T1AF+alEZRH3AC79IqgR+maqZxWx2RLWKyRpZQhAYrk= +k8c.io/kubermatic/v2 v2.27.6-0.20250709121724-073944c20a13/go.mod h1:TNysG/oe6ZZgfUlVxSdR3yHYMxxdJRLcFHV6+8aDnEg= k8c.io/machine-controller v1.61.3 h1:oQwk7/LB71zd95d9ZeuSZ2FV4ywgSIwfeSnvOfkCs0o= k8c.io/machine-controller v1.61.3/go.mod h1:ZGDFyUeEp66RHcNB5Ki/OJyFdZFgo9dkHJ9s6YJWPcg= k8c.io/operating-system-manager v1.6.7 h1:n7rViYgFeccEUmMeFAqvtyappr/0kxAVtKJNe66x+KU= From 0c9de0120417e798ffad391b7ac38241db29db77 Mon Sep 17 00:00:00 2001 From: Moath Qasim Date: Thu, 10 Jul 2025 17:40:24 +0200 Subject: [PATCH 32/42] skip setting kubevirt cpu value when an instance type is chosen (#7493) (#7499) (cherry picked from commit 004e0bca63e117dd632f8a3f8863eebf0d2c4f47) Signed-off-by: moadqassem --- modules/api/pkg/resources/machine/common.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/modules/api/pkg/resources/machine/common.go b/modules/api/pkg/resources/machine/common.go index 0f7f844e17..a226843d27 100644 --- a/modules/api/pkg/resources/machine/common.go +++ b/modules/api/pkg/resources/machine/common.go @@ -619,14 +619,18 @@ func GetKubevirtProviderConfig(cluster *kubermaticv1.Cluster, nodeSpec apiv1.Nod }, } - if dc.Spec.Kubevirt.EnableDedicatedCPUs { - vcpus, err := strconv.ParseInt(nodeSpec.Cloud.Kubevirt.CPUs, 0, 64) - if err != nil { - return nil, err + // if users have chosen to use an instance type instead of custom cpu value, we should skip setting the cpu value + // explicitly as the value will be ignored either ways and the instance type resources will be used instead. + if nodeSpec.Cloud.Kubevirt.Instancetype == nil { + if dc.Spec.Kubevirt.EnableDedicatedCPUs { + vcpus, err := strconv.ParseInt(nodeSpec.Cloud.Kubevirt.CPUs, 0, 64) + if err != nil { + return nil, err + } + config.VirtualMachine.Template.VCPUs.Cores = int(vcpus) + } else { + config.VirtualMachine.Template.CPUs = providerconfig.ConfigVarString{Value: nodeSpec.Cloud.Kubevirt.CPUs} } - config.VirtualMachine.Template.VCPUs.Cores = int(vcpus) - } else { - config.VirtualMachine.Template.CPUs = providerconfig.ConfigVarString{Value: nodeSpec.Cloud.Kubevirt.CPUs} } var subnet string From cc989130038e3f5a1a04493ecbda2ef2260c2964 Mon Sep 17 00:00:00 2001 From: Kubermatic Bot <41968677+kubermatic-bot@users.noreply.github.com> Date: Mon, 21 Jul 2025 09:38:16 +0200 Subject: [PATCH 33/42] [release/v2.27] (fix): OpenStack - pass ConfigDrive value to JSON patch during machine updates (#7502) * Pass ConfigDrive value to JSON patch during machine updates through UI Signed-off-by: Burak Sekili <32663655+buraksekili@users.noreply.github.com> * add swaggers Signed-off-by: Burak Sekili <32663655+buraksekili@users.noreply.github.com> * update tests Signed-off-by: Burak Sekili <32663655+buraksekili@users.noreply.github.com> * update swagger.json Signed-off-by: Burak Sekili <32663655+buraksekili@users.noreply.github.com> * update unit tests to check configDrive in OpenStack Signed-off-by: Burak Sekili <32663655+buraksekili@users.noreply.github.com> * fix linter Signed-off-by: Burak Sekili <32663655+buraksekili@users.noreply.github.com> --------- Signed-off-by: Burak Sekili <32663655+buraksekili@users.noreply.github.com> Co-authored-by: Burak Sekili <32663655+buraksekili@users.noreply.github.com> --- modules/api/cmd/kubermatic-api/swagger.json | 5 +++++ modules/api/pkg/api/v1/types.go | 5 +++++ modules/api/pkg/api/v1/types_test.go | 2 +- modules/api/pkg/machine/convert.go | 3 +++ modules/api/pkg/resources/machine/common.go | 1 + .../test/e2e/utils/apiclient/models/openstack_node_spec.go | 3 +++ 6 files changed, 18 insertions(+), 1 deletion(-) diff --git a/modules/api/cmd/kubermatic-api/swagger.json b/modules/api/cmd/kubermatic-api/swagger.json index f76a76d8ce..5a4542eada 100644 --- a/modules/api/cmd/kubermatic-api/swagger.json +++ b/modules/api/cmd/kubermatic-api/swagger.json @@ -37599,6 +37599,11 @@ "type": "string", "x-go-name": "AvailabilityZone" }, + "configDrive": { + "description": "ConfigDrive enables a configuration drive that will be attached to the instance when it boots.", + "type": "boolean", + "x-go-name": "ConfigDrive" + }, "diskSize": { "description": "if set, the rootDisk will be a volume. If not, the rootDisk will be on ephemeral storage and its size will be derived from the flavor", "type": "integer", diff --git a/modules/api/pkg/api/v1/types.go b/modules/api/pkg/api/v1/types.go index 6ec23eface..7bd83ca4e1 100644 --- a/modules/api/pkg/api/v1/types.go +++ b/modules/api/pkg/api/v1/types.go @@ -1933,6 +1933,9 @@ type OpenstackNodeSpec struct { // UUID of the server group, used to configure affinity or anti-affinity of the VM instances relative to hypervisor // required: false ServerGroup string `json:"serverGroup"` + // ConfigDrive enables a configuration drive that will be attached to the instance when it boots. + // required: false + ConfigDrive bool `json:"configDrive"` } func (spec *OpenstackNodeSpec) MarshalJSON() ([]byte, error) { @@ -1960,6 +1963,7 @@ func (spec *OpenstackNodeSpec) MarshalJSON() ([]byte, error) { InstanceReadyCheckPeriod string `json:"instanceReadyCheckPeriod"` InstanceReadyCheckTimeout string `json:"instanceReadyCheckTimeout"` ServerGroup string `json:"serverGroup"` + ConfigDrive bool `json:"configDrive"` }{ Flavor: spec.Flavor, Image: spec.Image, @@ -1970,6 +1974,7 @@ func (spec *OpenstackNodeSpec) MarshalJSON() ([]byte, error) { InstanceReadyCheckPeriod: spec.InstanceReadyCheckPeriod, InstanceReadyCheckTimeout: spec.InstanceReadyCheckTimeout, ServerGroup: spec.ServerGroup, + ConfigDrive: spec.ConfigDrive, } return json.Marshal(&res) diff --git a/modules/api/pkg/api/v1/types_test.go b/modules/api/pkg/api/v1/types_test.go index f4be4ae569..490940b0ed 100644 --- a/modules/api/pkg/api/v1/types_test.go +++ b/modules/api/pkg/api/v1/types_test.go @@ -389,7 +389,7 @@ func TestOpenstackNodeSpec_MarshalJSON(t *testing.T) { Flavor: "test-flavor", Image: "test-image", }, - "{\"flavor\":\"test-flavor\",\"image\":\"test-image\",\"diskSize\":null,\"availabilityZone\":\"\",\"instanceReadyCheckPeriod\":\"\",\"instanceReadyCheckTimeout\":\"\",\"serverGroup\":\"\"}", + "{\"flavor\":\"test-flavor\",\"image\":\"test-image\",\"diskSize\":null,\"availabilityZone\":\"\",\"instanceReadyCheckPeriod\":\"\",\"instanceReadyCheckTimeout\":\"\",\"serverGroup\":\"\",\"configDrive\":false}", }, } diff --git a/modules/api/pkg/machine/convert.go b/modules/api/pkg/machine/convert.go index 291aaf36f9..1e5e32dbc7 100644 --- a/modules/api/pkg/machine/convert.go +++ b/modules/api/pkg/machine/convert.go @@ -215,6 +215,9 @@ func GetAPIV2NodeCloudSpec(machineSpec clusterv1alpha1.MachineSpec) (*apiv1.Node if config.RootDiskSizeGB != nil && *config.RootDiskSizeGB > 0 { cloudSpec.Openstack.RootDiskSizeGB = config.RootDiskSizeGB } + if cd := config.ConfigDrive.Value; cd != nil { + cloudSpec.Openstack.ConfigDrive = *cd + } case providerconfig.CloudProviderHetzner: config := &hetzner.RawConfig{} if err := json.Unmarshal(decodedProviderSpec.CloudProviderSpec.Raw, &config); err != nil { diff --git a/modules/api/pkg/resources/machine/common.go b/modules/api/pkg/resources/machine/common.go index a226843d27..fd4666afa6 100644 --- a/modules/api/pkg/resources/machine/common.go +++ b/modules/api/pkg/resources/machine/common.go @@ -391,6 +391,7 @@ func GetOpenstackProviderConfig(c *kubermaticv1.Cluster, nodeSpec apiv1.NodeSpec InstanceReadyCheckTimeout: providerconfig.ConfigVarString{Value: nodeSpec.Cloud.Openstack.InstanceReadyCheckTimeout}, TrustDevicePath: providerconfig.ConfigVarBool{Value: ptr.To(false)}, ServerGroup: providerconfig.ConfigVarString{Value: nodeSpec.Cloud.Openstack.ServerGroup}, + ConfigDrive: providerconfig.ConfigVarBool{Value: ptr.To(nodeSpec.Cloud.Openstack.ConfigDrive)}, } config.SecurityGroups = []providerconfig.ConfigVarString{} diff --git a/modules/api/pkg/test/e2e/utils/apiclient/models/openstack_node_spec.go b/modules/api/pkg/test/e2e/utils/apiclient/models/openstack_node_spec.go index bb706a94af..23b0b7c038 100644 --- a/modules/api/pkg/test/e2e/utils/apiclient/models/openstack_node_spec.go +++ b/modules/api/pkg/test/e2e/utils/apiclient/models/openstack_node_spec.go @@ -22,6 +22,9 @@ type OpenstackNodeSpec struct { // if not set, the default AZ from the Datacenter spec will be used AvailabilityZone string `json:"availabilityZone,omitempty"` + // ConfigDrive enables a configuration drive that will be attached to the instance when it boots. + ConfigDrive bool `json:"configDrive,omitempty"` + // instance flavor // Required: true Flavor *string `json:"flavor"` From 9c3035de0233359187f1fcb578c99b9463ed6026 Mon Sep 17 00:00:00 2001 From: Kubermatic Bot <41968677+kubermatic-bot@users.noreply.github.com> Date: Mon, 4 Aug 2025 11:04:07 +0200 Subject: [PATCH 34/42] add config drive option to MDs on open stack provider (#7518) Co-authored-by: ahmadhamzh --- .../basic/provider/openstack/component.ts | 33 +++++++++++++++---- .../basic/provider/openstack/style.scss | 4 --- .../basic/provider/openstack/template.html | 20 +++++++---- .../datacenter-data-dialog/component.ts | 29 +++++++++++++++- .../datacenter-data-dialog/template.html | 16 +++++++-- .../components/cluster-summary/template.html | 3 ++ .../web/src/app/shared/entity/datacenter.ts | 1 + modules/web/src/app/shared/entity/node.ts | 1 + 8 files changed, 87 insertions(+), 20 deletions(-) diff --git a/modules/web/src/app/node-data/basic/provider/openstack/component.ts b/modules/web/src/app/node-data/basic/provider/openstack/component.ts index 44dd8738e7..f127726180 100644 --- a/modules/web/src/app/node-data/basic/provider/openstack/component.ts +++ b/modules/web/src/app/node-data/basic/provider/openstack/component.ts @@ -33,7 +33,7 @@ import {ClusterSpecService} from '@core/services/cluster-spec'; import {DatacenterService} from '@core/services/datacenter'; import {NodeDataService} from '@core/services/node-data/service'; import {FilteredComboboxComponent} from '@shared/components/combobox/component'; -import {DatacenterOperatingSystemOptions} from '@shared/entity/datacenter'; +import {Datacenter, DatacenterOperatingSystemOptions} from '@shared/entity/datacenter'; import {NodeCloudSpec, NodeSpec, OpenstackNodeSpec} from '@shared/entity/node'; import {OpenstackAvailabilityZone, OpenstackFlavor, OpenstackServerGroup} from '@shared/entity/provider/openstack'; import {OperatingSystem} from '@shared/model/NodeProviderConstants'; @@ -53,6 +53,7 @@ enum Controls { InstanceReadyCheckPeriod = 'instanceReadyCheckPeriod', InstanceReadyCheckTimeout = 'instanceReadyCheckTimeout', ServerGroup = 'serverGroup', + EnableConfigDrive = 'enableConfigDrive', } enum FlavorState { @@ -117,6 +118,7 @@ export class OpenstackBasicNodeDataComponent extends BaseFormValidator implement selectedServerGroupID = ''; serverGroupLabel = ServerGroupState.Empty; isEnterpriseEdition = DynamicModule.isEnterpriseEdition; + isInWizardMode: boolean; private _quotaCalculationService: QuotaCalculationService; @@ -168,8 +170,11 @@ export class OpenstackBasicNodeDataComponent extends BaseFormValidator implement [Controls.InstanceReadyCheckPeriod]: this._builder.control(this._instanceReadyCheckPeriodDefault), [Controls.InstanceReadyCheckTimeout]: this._builder.control(this._instanceReadyCheckTimeoutDefault), [Controls.ServerGroup]: this._builder.control(''), + [Controls.EnableConfigDrive]: this._builder.control(false), }); + this.isInWizardMode = this._nodeDataService.isInWizardMode(); + this._init(); this._nodeDataService.nodeData = this._getNodeData(); @@ -179,7 +184,8 @@ export class OpenstackBasicNodeDataComponent extends BaseFormValidator implement this.form.get(Controls.Image).valueChanges, this.form.get(Controls.UseFloatingIP).valueChanges, this.form.get(Controls.InstanceReadyCheckPeriod).valueChanges, - this.form.get(Controls.InstanceReadyCheckTimeout).valueChanges + this.form.get(Controls.InstanceReadyCheckTimeout).valueChanges, + this.form.get(Controls.EnableConfigDrive).valueChanges ) .pipe(takeUntil(this._unsubscribe)) .subscribe(_ => { @@ -194,6 +200,7 @@ export class OpenstackBasicNodeDataComponent extends BaseFormValidator implement .subscribe(dc => { this._setDefaultImage(OperatingSystem.Ubuntu); this._enforceFloatingIP(dc.spec.openstack.enforceFloatingIP); + this._enforceConfigDrive(dc); }); this._nodeDataService.operatingSystemChanges @@ -243,6 +250,10 @@ export class OpenstackBasicNodeDataComponent extends BaseFormValidator implement return this.form.get(Controls.UseFloatingIP).disabled; } + isConfigDriveEnforced(): boolean { + return this.form.get(Controls.EnableConfigDrive).disabled; + } + isDiskSizeRequired(): boolean { return this.form.get(Controls.CustomDiskSize).hasValidator(Validators.required); } @@ -289,6 +300,9 @@ export class OpenstackBasicNodeDataComponent extends BaseFormValidator implement this.form.get(Controls.UseCustomDisk).setValue(!!diskSize); this.form.get(Controls.InstanceReadyCheckPeriod).setValue(instanceReadyCheckPeriod); this.form.get(Controls.InstanceReadyCheckTimeout).setValue(instanceReadyCheckTimeout); + this.form + .get(Controls.EnableConfigDrive) + .setValue(this._nodeDataService.nodeData.spec.cloud.openstack?.configDrive ?? false); this._defaultOS = this._nodeDataService.operatingSystem; this._defaultImage = this._nodeDataService.nodeData.spec.cloud.openstack.image; @@ -415,15 +429,19 @@ export class OpenstackBasicNodeDataComponent extends BaseFormValidator implement return; } - if ( - !this._nodeDataService.isInWizardMode() && - !this._clusterSpecService.cluster.spec.cloud.openstack.floatingIPPool - ) { + if (!this.isInWizardMode && !this._clusterSpecService.cluster.spec.cloud.openstack.floatingIPPool) { this.form.get(Controls.UseFloatingIP).setValue(false); this.form.get(Controls.UseFloatingIP).disable(); } } + private _enforceConfigDrive(dc: Datacenter): void { + if (dc.spec.openstack.enableConfigDrive) { + this.form.get(Controls.EnableConfigDrive).setValue(true); + this.form.get(Controls.EnableConfigDrive).disable(); + } + } + private _getNodeData(): NodeData { return { spec: { @@ -435,6 +453,7 @@ export class OpenstackBasicNodeDataComponent extends BaseFormValidator implement instanceReadyCheckPeriod: `${this.form.get(Controls.InstanceReadyCheckPeriod).value}s`, instanceReadyCheckTimeout: `${this.form.get(Controls.InstanceReadyCheckTimeout).value}s`, serverGroup: this.selectedServerGroupID, + configDrive: this.form.get(Controls.EnableConfigDrive)?.value ?? false, } as OpenstackNodeSpec, } as NodeCloudSpec, } as NodeSpec, @@ -458,7 +477,7 @@ export class OpenstackBasicNodeDataComponent extends BaseFormValidator implement }; if ( - !this._nodeDataService.isInWizardMode() && + !this.isInWizardMode && !this._initialQuotaCalculationPayload && !!this._nodeDataService.nodeData.creationTimestamp ) { diff --git a/modules/web/src/app/node-data/basic/provider/openstack/style.scss b/modules/web/src/app/node-data/basic/provider/openstack/style.scss index 19b8d3b4e5..741d2775ea 100644 --- a/modules/web/src/app/node-data/basic/provider/openstack/style.scss +++ b/modules/web/src/app/node-data/basic/provider/openstack/style.scss @@ -22,7 +22,3 @@ align-self: center; margin-left: 5px; } - -.use-floating-ip-checkbox { - padding-bottom: 20px; -} diff --git a/modules/web/src/app/node-data/basic/provider/openstack/template.html b/modules/web/src/app/node-data/basic/provider/openstack/template.html index 184d799f0a..02ec9e5397 100644 --- a/modules/web/src/app/node-data/basic/provider/openstack/template.html +++ b/modules/web/src/app/node-data/basic/provider/openstack/template.html @@ -96,15 +96,23 @@ - - Allocate Floating IP +
+ + Allocate Floating IP + - +
+
+ + Enable Config Drive + + +
(); readonly controls = Controls; + readonly Provider = Provider; readonly domainRegex = '^(?!-)[A-Za-z0-9-]+([\\-.][a-z0-9]+)*\\.[A-Za-z]{2,6}$'; readonly countryCodes: string[] = countryCodeLookup.countries.map(country => country.iso2); readonly providers = INTERNAL_NODE_PROVIDERS; @@ -130,10 +132,32 @@ export class DatacenterDataDialogComponent implements OnInit, OnDestroy { this._initRequiredEmailsInput(); this._initProviderConfigEditor(); + if (this.form.get(Controls.Provider).value === Provider.OpenStack) { + this.form.addControl( + Controls.EnableConfigDrive, + new FormControl(this.data.isEditing ? this.data.datacenter.spec.openstack.enableConfigDrive : false) + ); + } + if (this.form.get(Controls.EnforceAuditWebhookBackend).value) { this._initAuditWebhookBackendControls(this.data.datacenter.spec.enforcedAuditWebhookSettings); } + this.form + .get(Controls.Provider) + .valueChanges.pipe(takeUntil(this._unsubscribe)) + .subscribe((provider: Provider) => { + if (provider === Provider.OpenStack) { + this.form.addControl( + Controls.EnableConfigDrive, + new FormControl(this.data.isEditing ? this.data.datacenter.spec.openstack.enableConfigDrive : false) + ); + } else { + this.form.removeControl(Controls.EnableConfigDrive); + } + this.form.updateValueAndValidity(); + }); + this.form .get(Controls.EnforceAuditLogging) .valueChanges.pipe(takeUntil(this._unsubscribe)) @@ -208,6 +232,9 @@ export class DatacenterDataDialogComponent implements OnInit, OnDestroy { }; datacenter.spec[datacenter.spec.provider] = this._getProviderConfig(); + if (datacenter.spec.provider === Provider.OpenStack) { + datacenter.spec[datacenter.spec.provider].enableConfigDrive = this.form.get(Controls.EnableConfigDrive)?.value; + } // Nullify old provider value (it is needed to make edit work as it uses JSON Merge Patch). if (this.data.isEditing && datacenter.spec.provider !== this.data.datacenter.spec.provider) { diff --git a/modules/web/src/app/settings/admin/dynamic-datacenters/datacenter-data-dialog/template.html b/modules/web/src/app/settings/admin/dynamic-datacenters/datacenter-data-dialog/template.html index 44cc65d3a8..3efc3cf38e 100644 --- a/modules/web/src/app/settings/admin/dynamic-datacenters/datacenter-data-dialog/template.html +++ b/modules/web/src/app/settings/admin/dynamic-datacenters/datacenter-data-dialog/template.html @@ -187,9 +187,21 @@ + Provider Configuration + + + +
+ Enable Config Drive + + +
+
+
- -
Provider Configuration
If not specified default configuration will be used. diff --git a/modules/web/src/app/shared/components/cluster-summary/template.html b/modules/web/src/app/shared/components/cluster-summary/template.html index 23958c847c..928b45f070 100644 --- a/modules/web/src/app/shared/components/cluster-summary/template.html +++ b/modules/web/src/app/shared/components/cluster-summary/template.html @@ -1181,6 +1181,9 @@

IPv6

+ +
diff --git a/modules/web/src/app/shared/entity/datacenter.ts b/modules/web/src/app/shared/entity/datacenter.ts index e7cd2a0e3e..06620fdc2f 100644 --- a/modules/web/src/app/shared/entity/datacenter.ts +++ b/modules/web/src/app/shared/entity/datacenter.ts @@ -134,6 +134,7 @@ export class OpenStackDatacenterSpec { region: string; images: DatacenterOperatingSystemOptions; enforceFloatingIP: boolean; + enableConfigDrive?: boolean; } export class EquinixDatacenterSpec { diff --git a/modules/web/src/app/shared/entity/node.ts b/modules/web/src/app/shared/entity/node.ts index 2904ca0caa..00bcb18406 100644 --- a/modules/web/src/app/shared/entity/node.ts +++ b/modules/web/src/app/shared/entity/node.ts @@ -263,6 +263,7 @@ export class OpenstackNodeSpec { availabilityZone?: string; instanceReadyCheckPeriod: string; instanceReadyCheckTimeout: string; + configDrive?: boolean; } export class EquinixNodeSpec { From 7fb5560842f96505c1a660af546f43ea05343ad6 Mon Sep 17 00:00:00 2001 From: Archana Sawant Date: Tue, 12 Aug 2025 22:26:53 +0530 Subject: [PATCH 35/42] [release/v2.27] Bump KKP and other dependencies for KKP patch release v2.27.7 (#7527) * [release/v2.27] Bump KKP and other dependencies for KKP patch release v2.27.7 Signed-off-by: archups * Bump KKP dependencies Signed-off-by: archups --------- Signed-off-by: archups --- Makefile | 2 +- modules/api/Makefile | 2 +- modules/api/cmd/kubermatic-api/swagger.json | 13 ++-- modules/api/go.mod | 32 ++++----- modules/api/go.sum | 65 ++++++++++--------- .../models/config_map_key_selector.go | 1 - .../models/local_object_reference.go | 12 +++- .../apiclient/models/pod_dns_config_option.go | 2 + .../apiclient/models/secret_key_selector.go | 1 - modules/web/Makefile | 2 +- modules/web/package-lock.json | 4 +- modules/web/package.json | 2 +- 12 files changed, 76 insertions(+), 62 deletions(-) diff --git a/Makefile b/Makefile index 5df2d45f85..f9fc9becc1 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ SHELL = /bin/bash -eu -o pipefail export KUBERMATIC_EDITION ?= ee -KUBERMATIC_VERSION?=v2.27.6 +KUBERMATIC_VERSION?=v2.27.7 DOCKER_REPO ?= quay.io/kubermatic REPO = $(DOCKER_REPO)/dashboard$(shell [[ "$(KUBERMATIC_EDITION)" != "ce" ]] && printf -- '-%s' ${KUBERMATIC_EDITION}) IMAGE_TAG=$(shell echo $$(git rev-parse HEAD)|tr -d '\n') diff --git a/modules/api/Makefile b/modules/api/Makefile index 45bbd154ea..5f00fca0a2 100644 --- a/modules/api/Makefile +++ b/modules/api/Makefile @@ -1,6 +1,6 @@ SHELL = /bin/bash -eu -o pipefail export KUBERMATIC_EDITION ?= ee -KUBERMATIC_VERSION?=v2.27.6 +KUBERMATIC_VERSION?=v2.27.7 DOCKER_REPO ?= quay.io/kubermatic REPO = $(DOCKER_REPO)/dashboard$(shell [[ "$(KUBERMATIC_EDITION)" != "ce" ]] && printf -- '-%s' ${KUBERMATIC_EDITION}) IMAGE_TAG=$(shell echo $$(git rev-parse HEAD)|tr -d '\n') diff --git a/modules/api/cmd/kubermatic-api/swagger.json b/modules/api/cmd/kubermatic-api/swagger.json index 5a4542eada..bf7f223210 100644 --- a/modules/api/cmd/kubermatic-api/swagger.json +++ b/modules/api/cmd/kubermatic-api/swagger.json @@ -30306,7 +30306,7 @@ "x-go-name": "Key" }, "name": { - "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nTODO: Add other useful fields. apiVersion, kind, uid?\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n+optional\n+default=\"\"\n+kubebuilder:default=\"\"\nTODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896.", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n+optional\n+default=\"\"\n+kubebuilder:default=\"\"\nTODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896.", "type": "string", "x-go-name": "Name" }, @@ -35409,11 +35409,12 @@ "x-go-package": "k8s.io/apimachinery/pkg/apis/meta/v1" }, "LocalObjectReference": { - "description": "LocalObjectReference contains enough information to let you locate the\nreferenced object inside the same namespace.\n+structType=atomic", + "description": "New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs.\n1. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular\nrestrictions like, \"must refer only to types A and B\" or \"UID not honored\" or \"name must be restricted\".\nThose cannot be well described when embedded.\n2. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen.\n3. We cannot easily change it. Because this type is embedded in many locations, updates to this type\nwill affect numerous schemas. Don't make new APIs embed an underspecified API type they do not control.\n\nInstead of using this type, create a locally provided and used type that is well-focused on your reference.\nFor example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 .\n+structType=atomic", "type": "object", + "title": "LocalObjectReference contains enough information to let you locate the\nreferenced object inside the same namespace.", "properties": { "name": { - "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nTODO: Add other useful fields. apiVersion, kind, uid?\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n+optional\n+default=\"\"\n+kubebuilder:default=\"\"\nTODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896.", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n+optional\n+default=\"\"\n+kubebuilder:default=\"\"\nTODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896.", "type": "string", "x-go-name": "Name" } @@ -38074,12 +38075,12 @@ "title": "PodDNSConfigOption defines DNS resolver options of a pod.", "properties": { "name": { - "description": "Required.", + "description": "Name is this DNS resolver option's name.\nRequired.", "type": "string", "x-go-name": "Name" }, "value": { - "description": "+optional", + "description": "Value is this DNS resolver option's value.\n+optional", "type": "string", "x-go-name": "Value" } @@ -39382,7 +39383,7 @@ "x-go-name": "Key" }, "name": { - "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nTODO: Add other useful fields. apiVersion, kind, uid?\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n+optional\n+default=\"\"\n+kubebuilder:default=\"\"\nTODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896.", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n+optional\n+default=\"\"\n+kubebuilder:default=\"\"\nTODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896.", "type": "string", "x-go-name": "Name" }, diff --git a/modules/api/go.mod b/modules/api/go.mod index 5ed053b982..4058e233bf 100644 --- a/modules/api/go.mod +++ b/modules/api/go.mod @@ -70,19 +70,19 @@ require ( google.golang.org/api v0.209.0 gopkg.in/yaml.v3 v3.0.1 k8c.io/kubeone v1.7.3 - k8c.io/kubermatic/v2 v2.27.6-0.20250709121724-073944c20a13 + k8c.io/kubermatic/v2 v2.27.7-0.20250812141153-bbd4cbc83ed5 k8c.io/machine-controller v1.61.3 - k8c.io/operating-system-manager v1.6.7 + k8c.io/operating-system-manager v1.6.8 k8c.io/reconciler v0.5.0 - k8s.io/api v0.31.3 - k8s.io/apiextensions-apiserver v0.31.3 - k8s.io/apimachinery v0.31.3 - k8s.io/apiserver v0.31.3 + k8s.io/api v0.32.2 + k8s.io/apiextensions-apiserver v0.32.2 + k8s.io/apimachinery v0.32.2 + k8s.io/apiserver v0.32.2 k8s.io/client-go v1.5.2 - k8s.io/code-generator v0.31.3 + k8s.io/code-generator v0.32.2 k8s.io/klog/v2 v2.130.1 - k8s.io/kubectl v0.31.3 - k8s.io/metrics v0.31.3 + k8s.io/kubectl v0.32.2 + k8s.io/metrics v0.32.2 k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078 kubevirt.io/api v1.4.0 kubevirt.io/containerized-data-importer-api v1.61.0 @@ -99,12 +99,13 @@ replace github.com/nutanix-cloud-native/prism-go-client => github.com/nutanix-cl replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38 -replace k8s.io/client-go => k8s.io/client-go v0.31.1 +replace k8s.io/client-go => k8s.io/client-go v0.32.2 // Needs to be the same as https://github.com/kubermatic/kubermatic/blob/main/pkg/resources/resources.go#L643 replace k8c.io/kubeone => k8c.io/kubeone v1.7.2 require ( + cel.dev/expr v0.19.0 // indirect cloud.google.com/go/auth v0.11.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect cloud.google.com/go/compute/metadata v0.5.2 // indirect @@ -177,7 +178,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/cel-go v0.21.0 // indirect + github.com/google/cel-go v0.22.0 // indirect github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.1-0.20210504230335-f78f29fc09ea // indirect @@ -193,7 +194,6 @@ require ( github.com/hashicorp/hcl v1.0.1-vault-5 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/huandu/xstrings v1.5.0 // indirect - github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jessevdk/go-flags v1.5.0 // indirect github.com/jhump/protoreflect v1.16.0 // indirect @@ -283,12 +283,12 @@ require ( gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - helm.sh/helm/v3 v3.16.3 // indirect + helm.sh/helm/v3 v3.17.4 // indirect k8s.io/autoscaler/vertical-pod-autoscaler v1.2.1 // indirect - k8s.io/component-base v0.31.3 // indirect - k8s.io/gengo/v2 v2.0.0-20240826214909-a7b603a56eb7 // indirect + k8s.io/component-base v0.32.2 // indirect + k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 // indirect k8s.io/klog v1.0.0 // indirect - k8s.io/kube-aggregator v0.31.3 // indirect + k8s.io/kube-aggregator v0.32.2 // indirect k8s.io/kube-openapi v0.31.2 // indirect kubevirt.io/controller-lifecycle-operator-sdk/api v0.2.4 // indirect sigs.k8s.io/gateway-api v1.1.0 // indirect diff --git a/modules/api/go.sum b/modules/api/go.sum index 0e84219b9d..0509eb265f 100644 --- a/modules/api/go.sum +++ b/modules/api/go.sum @@ -1,3 +1,5 @@ +cel.dev/expr v0.19.0 h1:lXuo+nDhpyJSpWxpPVi5cPUwzKb+dsdOiw6IreM5yt0= +cel.dev/expr v0.19.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go/auth v0.11.0 h1:Ic5SZz2lsvbYcWT5dfjNWgw6tTlGi2Wc8hyQSC9BstA= cloud.google.com/go/auth v0.11.0/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= @@ -368,8 +370,10 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= -github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/cel-go v0.22.0 h1:b3FJZxpiv1vTMo2/5RDUqAHPxkT8mmMfJIrq1llbf7g= +github.com/google/cel-go v0.22.0/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= @@ -449,8 +453,6 @@ github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= -github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= -github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= @@ -1141,43 +1143,44 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -helm.sh/helm/v3 v3.16.3 h1:kb8bSxMeRJ+knsK/ovvlaVPfdis0X3/ZhYCSFRP+YmY= -helm.sh/helm/v3 v3.16.3/go.mod h1:zeVWGDR4JJgiRbT3AnNsjYaX8OTJlIE9zC+Q7F7iUSU= +helm.sh/helm/v3 v3.17.4 h1:GK+vgn9gKCyoH44+f3B5zpA78iH3AK4ywIInDEmmn/g= +helm.sh/helm/v3 v3.17.4/go.mod h1:+uJKMH/UiMzZQOALR3XUf3BLIoczI2RKKD6bMhPh4G8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8c.io/kubeone v1.7.2 h1:uLH19VEp1S5j4f3UwQP4CLHErJ23UiJM2MnbufNWDgI= k8c.io/kubeone v1.7.2/go.mod h1:9v2VFz/+l36cW65kd5YufEYHunbKlJ6P8SBakj05xgM= -k8c.io/kubermatic/v2 v2.27.6-0.20250709121724-073944c20a13 h1:T1AF+alEZRH3AC79IqgR+maqZxWx2RLWKyRpZQhAYrk= -k8c.io/kubermatic/v2 v2.27.6-0.20250709121724-073944c20a13/go.mod h1:TNysG/oe6ZZgfUlVxSdR3yHYMxxdJRLcFHV6+8aDnEg= +k8c.io/kubermatic/v2 v2.27.7-0.20250812141153-bbd4cbc83ed5 h1:+b8pvS8vPeHsYF975yZJOYhVIyy8smABnwIzE+LGzpM= +k8c.io/kubermatic/v2 v2.27.7-0.20250812141153-bbd4cbc83ed5/go.mod h1:nxAZ8IXPYie34FBSdGxl285HdoOL0GqwO8qy3QtCaBs= k8c.io/machine-controller v1.61.3 h1:oQwk7/LB71zd95d9ZeuSZ2FV4ywgSIwfeSnvOfkCs0o= k8c.io/machine-controller v1.61.3/go.mod h1:ZGDFyUeEp66RHcNB5Ki/OJyFdZFgo9dkHJ9s6YJWPcg= -k8c.io/operating-system-manager v1.6.7 h1:n7rViYgFeccEUmMeFAqvtyappr/0kxAVtKJNe66x+KU= -k8c.io/operating-system-manager v1.6.7/go.mod h1:Dh9IZp5pb5G3s2r6ZrHUb+gTuHw5AmbIFYuFxIcgU7o= +k8c.io/operating-system-manager v1.6.8 h1:KGDc4Xd3pLJ6/AmmJXgYY5xXnCU8QX6HJ9H/Ok8Ons8= +k8c.io/operating-system-manager v1.6.8/go.mod h1:Dh9IZp5pb5G3s2r6ZrHUb+gTuHw5AmbIFYuFxIcgU7o= k8c.io/reconciler v0.5.0 h1:BHpelg1UfI/7oBFctqOq8sX6qzflXpl3SlvHe7e8wak= k8c.io/reconciler v0.5.0/go.mod h1:pT1+SVcVXJQeBJhpJBXQ5XW64QnKKeYTnVlQf0dGE0k= k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= -k8s.io/api v0.31.3 h1:umzm5o8lFbdN/hIXbrK9oRpOproJO62CV1zqxXrLgk8= -k8s.io/api v0.31.3/go.mod h1:UJrkIp9pnMOI9K2nlL6vwpxRzzEX5sWgn8kGQe92kCE= -k8s.io/apiextensions-apiserver v0.31.3 h1:+GFGj2qFiU7rGCsA5o+p/rul1OQIq6oYpQw4+u+nciE= -k8s.io/apiextensions-apiserver v0.31.3/go.mod h1:2DSpFhUZZJmn/cr/RweH1cEVVbzFw9YBu4T+U3mf1e4= +k8s.io/api v0.32.2 h1:bZrMLEkgizC24G9eViHGOPbW+aRo9duEISRIJKfdJuw= +k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y= +k8s.io/apiextensions-apiserver v0.32.2 h1:2YMk285jWMk2188V2AERy5yDwBYrjgWYggscghPCvV4= +k8s.io/apiextensions-apiserver v0.32.2/go.mod h1:GPwf8sph7YlJT3H6aKUWtd0E+oyShk/YHWQHf/OOgCA= k8s.io/apimachinery v0.23.3/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= -k8s.io/apimachinery v0.31.3 h1:6l0WhcYgasZ/wk9ktLq5vLaoXJJr5ts6lkaQzgeYPq4= -k8s.io/apimachinery v0.31.3/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= -k8s.io/apiserver v0.31.3 h1:+1oHTtCB+OheqFEz375D0IlzHZ5VeQKX1KGXnx+TTuY= -k8s.io/apiserver v0.31.3/go.mod h1:PrxVbebxrxQPFhJk4powDISIROkNMKHibTg9lTRQ0Qg= +k8s.io/apimachinery v0.32.2 h1:yoQBR9ZGkA6Rgmhbp/yuT9/g+4lxtsGYwW6dR6BDPLQ= +k8s.io/apimachinery v0.32.2/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/apiserver v0.32.2 h1:WzyxAu4mvLkQxwD9hGa4ZfExo3yZZaYzoYvvVDlM6vw= +k8s.io/apiserver v0.32.2/go.mod h1:PEwREHiHNU2oFdte7BjzA1ZyjWjuckORLIK/wLV5goM= k8s.io/autoscaler/vertical-pod-autoscaler v1.2.1 h1:t5t0Rsn4b7iQfiVlGdWSEnEx8pjrSM96Sn4Dvo1QH/Q= k8s.io/autoscaler/vertical-pod-autoscaler v1.2.1/go.mod h1:9ywHbt0kTrLyeNGgTNm7WEns34PmBMEr+9bDKTxW6wQ= -k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= -k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg= +k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA= +k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94= k8s.io/code-generator v0.23.3/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= -k8s.io/code-generator v0.31.3 h1:Pj0fYOBms+ZrsulLi4DMsCEx1jG8fWKRLy44onHsLBI= -k8s.io/code-generator v0.31.3/go.mod h1:/umCIlT84g1+Yu5ZXtP1KGSRTnGiIzzX5AzUAxsNlts= -k8s.io/component-base v0.31.3 h1:DMCXXVx546Rfvhj+3cOm2EUxhS+EyztH423j+8sOwhQ= -k8s.io/component-base v0.31.3/go.mod h1:xME6BHfUOafRgT0rGVBGl7TuSg8Z9/deT7qq6w7qjIU= +k8s.io/code-generator v0.32.2 h1:CIvyPrLWP7cMgrqval2qYT839YAwCDeSvGfXgWSNpHQ= +k8s.io/code-generator v0.32.2/go.mod h1:plh7bWk7JztAUkHM4zpbdy0KOMdrhsePcZL2HLWFH7Y= +k8s.io/component-base v0.32.2 h1:1aUL5Vdmu7qNo4ZsE+569PV5zFatM9hl+lb3dEea2zU= +k8s.io/component-base v0.32.2/go.mod h1:PXJ61Vx9Lg+P5mS8TLd7bCIr+eMJRQTyXe8KvkrvJq0= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/gengo/v2 v2.0.0-20240826214909-a7b603a56eb7 h1:cErOOTkQ3JW19o4lo91fFurouhP8NcoBvb7CkvhZZpk= k8s.io/gengo/v2 v2.0.0-20240826214909-a7b603a56eb7/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= +k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 h1:si3PfKm8dDYxgfbeA6orqrtLkvvIeH8UqffFJDl0bz4= +k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= @@ -1187,14 +1190,14 @@ k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= 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.31.3 h1:DqHPdTglJHgOfB884AaroyxrML/aL82ASYOh65m7MSk= -k8s.io/kube-aggregator v0.31.3/go.mod h1:Kx59Xjnf0SnY47qf9Or++4y3XCHQ3kR0xk1Di6KFiFU= +k8s.io/kube-aggregator v0.32.2 h1:kg9pke+i2qRbJwX1UKwZV4fsCRvmbaCEFk38R4FqHmw= +k8s.io/kube-aggregator v0.32.2/go.mod h1:rRm+xY1yIFIt3zBc727nG5SBLYywywD87klfIAw+7+c= k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38 h1:1dWzkmJrrprYvjGwh9kEUxmcUV/CtNU8QM7h1FLWQOo= k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38/go.mod h1:coRQXBK9NxO98XUv3ZD6AK3xzHCxV6+b7lrquKwaKzA= -k8s.io/kubectl v0.31.3 h1:3r111pCjPsvnR98oLLxDMwAeM6OPGmPty6gSKaLTQes= -k8s.io/kubectl v0.31.3/go.mod h1:lhMECDCbJN8He12qcKqs2QfmVo9Pue30geovBVpH5fs= -k8s.io/metrics v0.31.3 h1:DkT9I3gFlb2/z+/4BMY7WrQ/PnbukuV4Yli82v/KBCM= -k8s.io/metrics v0.31.3/go.mod h1:2w9gpd8z+13oJmaPR6p3kDyrDqnxSyoKpnOw2qLIdhI= +k8s.io/kubectl v0.32.2 h1:TAkag6+XfSBgkqK9I7ZvwtF0WVtUAvK8ZqTt+5zi1Us= +k8s.io/kubectl v0.32.2/go.mod h1:+h/NQFSPxiDZYX/WZaWw9fwYezGLISP0ud8nQKg+3g8= +k8s.io/metrics v0.32.2 h1:7t/rZzTHFrGa9f94XcgLlm3ToAuJtdlHANcJEHlYl9g= +k8s.io/metrics v0.32.2/go.mod h1:VL3nJpzcgB6L5nSljkkzoE0nilZhVgcjCfNRgoylaIQ= k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078 h1:jGnCPejIetjiy2gqaJ5V0NLwTpF4wbQ6cZIItJCSHno= diff --git a/modules/api/pkg/test/e2e/utils/apiclient/models/config_map_key_selector.go b/modules/api/pkg/test/e2e/utils/apiclient/models/config_map_key_selector.go index abbe5f40d8..d4aa875b49 100644 --- a/modules/api/pkg/test/e2e/utils/apiclient/models/config_map_key_selector.go +++ b/modules/api/pkg/test/e2e/utils/apiclient/models/config_map_key_selector.go @@ -26,7 +26,6 @@ type ConfigMapKeySelector struct { // This field is effectively required, but due to backwards compatibility is // allowed to be empty. Instances of this type with an empty value here are // almost certainly wrong. - // TODO: Add other useful fields. apiVersion, kind, uid? // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names // +optional // +default="" diff --git a/modules/api/pkg/test/e2e/utils/apiclient/models/local_object_reference.go b/modules/api/pkg/test/e2e/utils/apiclient/models/local_object_reference.go index a6e756e73e..fde6405ded 100644 --- a/modules/api/pkg/test/e2e/utils/apiclient/models/local_object_reference.go +++ b/modules/api/pkg/test/e2e/utils/apiclient/models/local_object_reference.go @@ -14,6 +14,17 @@ import ( // LocalObjectReference LocalObjectReference contains enough information to let you locate the // referenced object inside the same namespace. +// +// New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. +// 1. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular +// restrictions like, "must refer only to types A and B" or "UID not honored" or "name must be restricted". +// Those cannot be well described when embedded. +// 2. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. +// 3. We cannot easily change it. Because this type is embedded in many locations, updates to this type +// will affect numerous schemas. Don't make new APIs embed an underspecified API type they do not control. +// +// Instead of using this type, create a locally provided and used type that is well-focused on your reference. +// For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 . // +structType=atomic // // swagger:model LocalObjectReference @@ -23,7 +34,6 @@ type LocalObjectReference struct { // This field is effectively required, but due to backwards compatibility is // allowed to be empty. Instances of this type with an empty value here are // almost certainly wrong. - // TODO: Add other useful fields. apiVersion, kind, uid? // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names // +optional // +default="" diff --git a/modules/api/pkg/test/e2e/utils/apiclient/models/pod_dns_config_option.go b/modules/api/pkg/test/e2e/utils/apiclient/models/pod_dns_config_option.go index e6f14ad76d..3748049fdf 100644 --- a/modules/api/pkg/test/e2e/utils/apiclient/models/pod_dns_config_option.go +++ b/modules/api/pkg/test/e2e/utils/apiclient/models/pod_dns_config_option.go @@ -17,9 +17,11 @@ import ( // swagger:model PodDNSConfigOption type PodDNSConfigOption struct { + // Name is this DNS resolver option's name. // Required. Name string `json:"name,omitempty"` + // Value is this DNS resolver option's value. // +optional Value string `json:"value,omitempty"` } diff --git a/modules/api/pkg/test/e2e/utils/apiclient/models/secret_key_selector.go b/modules/api/pkg/test/e2e/utils/apiclient/models/secret_key_selector.go index 461868460d..5f8fe2e533 100644 --- a/modules/api/pkg/test/e2e/utils/apiclient/models/secret_key_selector.go +++ b/modules/api/pkg/test/e2e/utils/apiclient/models/secret_key_selector.go @@ -26,7 +26,6 @@ type SecretKeySelector struct { // This field is effectively required, but due to backwards compatibility is // allowed to be empty. Instances of this type with an empty value here are // almost certainly wrong. - // TODO: Add other useful fields. apiVersion, kind, uid? // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names // +optional // +default="" diff --git a/modules/web/Makefile b/modules/web/Makefile index a53e86cc64..3e6b131e51 100644 --- a/modules/web/Makefile +++ b/modules/web/Makefile @@ -1,6 +1,6 @@ SHELL = /bin/bash -eu -o pipefail export KUBERMATIC_EDITION ?= ee -KUBERMATIC_VERSION?=v2.27.6 +KUBERMATIC_VERSION?=v2.27.7 CC=npm GOOS ?= $(shell go env GOOS) export GOOS diff --git a/modules/web/package-lock.json b/modules/web/package-lock.json index e95bff0c22..e50c79b853 100644 --- a/modules/web/package-lock.json +++ b/modules/web/package-lock.json @@ -1,12 +1,12 @@ { "name": "kubermatic-dashboard", - "version": "2.27.6", + "version": "2.27.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "kubermatic-dashboard", - "version": "2.27.6", + "version": "2.27.7", "hasInstallScript": true, "license": "proprietary", "dependencies": { diff --git a/modules/web/package.json b/modules/web/package.json index 305311da5d..a0a604826a 100644 --- a/modules/web/package.json +++ b/modules/web/package.json @@ -1,7 +1,7 @@ { "name": "kubermatic-dashboard", "description": "Kubermatic Dashboard", - "version": "2.27.6", + "version": "2.27.7", "type": "module", "license": "proprietary", "repository": "https://github.com/kubermatic/dashboard", From 85ccfcc3f51db9930b10b873af0b11a94cdd92ed Mon Sep 17 00:00:00 2001 From: Kubermatic Bot <41968677+kubermatic-bot@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:06:26 +0200 Subject: [PATCH 36/42] moving webterminal clean jop to seed (#7546) Co-authored-by: ahmadhamzh --- .../api/pkg/handler/routes_v1_websocket.go | 6 +- modules/api/pkg/handler/websocket/terminal.go | 57 ++++++++++++------- modules/api/pkg/watcher/types.go | 1 + 3 files changed, 43 insertions(+), 21 deletions(-) diff --git a/modules/api/pkg/handler/routes_v1_websocket.go b/modules/api/pkg/handler/routes_v1_websocket.go index 67811dc80c..b577d423ff 100644 --- a/modules/api/pkg/handler/routes_v1_websocket.go +++ b/modules/api/pkg/handler/routes_v1_websocket.go @@ -81,7 +81,7 @@ var upgrader = websocket.Upgrader{ type WebsocketSettingsWriter func(ctx context.Context, providers watcher.Providers, ws *websocket.Conn) type WebsocketUserWriter func(ctx context.Context, providers watcher.Providers, ws *websocket.Conn, userEmail string) -type WebsocketTerminalWriter func(ctx context.Context, ws *websocket.Conn, client ctrlruntimeclient.Client, k8sClient kubernetes.Interface, cfg *rest.Config, userEmailID string, cluster *kubermaticv1.Cluster, options *kubermaticv1.WebTerminalOptions) +type WebsocketTerminalWriter func(ctx context.Context, ws *websocket.Conn, client, seedClient ctrlruntimeclient.Client, k8sClient kubernetes.Interface, cfg *rest.Config, userEmailID string, cluster *kubermaticv1.Cluster, options *kubermaticv1.WebTerminalOptions) const ( maxNumberOfTerminalActiveConnectionsPerUser = 5 @@ -108,6 +108,7 @@ func getProviders(r Routing) watcher.Providers { PrivilegedProjectProvider: r.privilegedProjectProvider, UserInfoGetter: r.userInfoGetter, SeedsGetter: r.seedsGetter, + SeedClientGetter: r.seedsClientGetter, ClusterProviderGetter: r.clusterProviderGetter, } } @@ -278,6 +279,7 @@ func getTerminalWatchHandler(writer WebsocketTerminalWriter, providers watcher.P if err != nil { return } + seedClient := privilegedClusterProvider.GetSeedClusterAdminRuntimeClient() ws, err := upgrader.Upgrade(w, req, nil) if err != nil { @@ -312,7 +314,7 @@ func getTerminalWatchHandler(writer WebsocketTerminalWriter, providers watcher.P return } - writer(ctx, ws, client, k8sClient, cfg, userEmailID, cluster, settings.Spec.WebTerminalOptions) + writer(ctx, ws, client, seedClient, k8sClient, cfg, userEmailID, cluster, settings.Spec.WebTerminalOptions) } } diff --git a/modules/api/pkg/handler/websocket/terminal.go b/modules/api/pkg/handler/websocket/terminal.go index 07ffa0b9ed..fc838976db 100644 --- a/modules/api/pkg/handler/websocket/terminal.go +++ b/modules/api/pkg/handler/websocket/terminal.go @@ -295,7 +295,7 @@ func expirationCheckRoutine(ctx context.Context, clusterClient ctrlruntimeclient // startProcess is called by terminal session creation. // Executed cmd in the container specified in request and connects it up with the ptyHandler (a session). -func startProcess(ctx context.Context, client ctrlruntimeclient.Client, k8sClient kubernetes.Interface, cfg *rest.Config, userEmailID string, cluster *kubermaticv1.Cluster, options *kubermaticv1.WebTerminalOptions, cmd []string, ptyHandler PtyHandler, websocketConn *websocket.Conn) error { +func startProcess(ctx context.Context, client, seedClient ctrlruntimeclient.Client, k8sClient kubernetes.Interface, cfg *rest.Config, userEmailID string, cluster *kubermaticv1.Cluster, options *kubermaticv1.WebTerminalOptions, cmd []string, ptyHandler PtyHandler, websocketConn *websocket.Conn) error { userAppName := userAppName(userEmailID) // check if WEB terminal Pod exists, if not create pod := &corev1.Pod{} @@ -307,7 +307,7 @@ func startProcess(ctx context.Context, client ctrlruntimeclient.Client, k8sClien return err } // create NetworkPolicy, Pod and cleanup Job - if err := createOrUpdateResources(ctx, client, userEmailID, userAppName, cluster, options); err != nil { + if err := createOrUpdateResources(ctx, client, seedClient, userEmailID, userAppName, cluster, options); err != nil { return err } } @@ -387,7 +387,7 @@ func startProcess(ctx context.Context, client ctrlruntimeclient.Client, k8sClien return nil } -func createOrUpdateResources(ctx context.Context, client ctrlruntimeclient.Client, userEmailID, userAppName string, cluster *kubermaticv1.Cluster, options *kubermaticv1.WebTerminalOptions) error { +func createOrUpdateResources(ctx context.Context, client, seedClient ctrlruntimeclient.Client, userEmailID, userAppName string, cluster *kubermaticv1.Cluster, options *kubermaticv1.WebTerminalOptions) error { webTerminalNetworkPolicy, err := genWebTerminalNetworkPolicy(userAppName, cluster, options) if err != nil { return err @@ -408,7 +408,7 @@ func createOrUpdateResources(ctx context.Context, client ctrlruntimeclient.Clien } } - if err := client.Create(ctx, genWebTerminalCleanupJob(userAppName, userEmailID)); err != nil { + if err := seedClient.Create(ctx, genWebTerminalCleanupJob(userAppName, userEmailID, cluster.Name)); err != nil { if !apierrors.IsAlreadyExists(err) { return err } @@ -570,11 +570,12 @@ func genWebTerminalPod(userAppName, userEmailID string, options *kubermaticv1.We return pod } -func genWebTerminalCleanupJob(userAppName, userEmailID string) *batchv1.Job { +func genWebTerminalCleanupJob(userAppName, userEmailID string, clusterID string) *batchv1.Job { + namespace := fmt.Sprintf("cluster-%s", clusterID) return &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ Name: fmt.Sprintf("cleanup-%s", userAppName), - Namespace: metav1.NamespaceSystem, + Namespace: namespace, Labels: map[string]string{ resources.AppLabelKey: appName, }, @@ -586,7 +587,7 @@ func genWebTerminalCleanupJob(userAppName, userEmailID string) *batchv1.Job { TTLSecondsAfterFinished: resources.Int32(0), Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ - Volumes: getVolumes(userEmailID, userAppName), + Volumes: getWebTerminalCleanJobVolumes(), InitContainers: []corev1.Container{}, Containers: []corev1.Container{ { @@ -594,10 +595,11 @@ func genWebTerminalCleanupJob(userAppName, userEmailID string) *batchv1.Job { Image: webTerminalImage, Command: []string{"/bin/bash", "-c"}, Args: []string{` - EXP=$(<$EXPIRATION); + + EXP=$(kubectl get configmap $USER_APP_NAME -n $NAMESPACE_SYSTEM -o jsonpath='{.data.ExpirationTimestamp}') + EXP_REFRESHES=$(kubectl get configmap $USER_APP_NAME -n $NAMESPACE_SYSTEM -o jsonpath='{.data.ExpirationRefreshes}') NOW=$(date +"%s"); REMAINING_TIME_SEC=$((EXP-NOW)); - EXP_REFRESHES=$(<$EXPIRATION_REFRESHES); if (( EXP_REFRESHES <= MAX_EXPIRATION_REFRESHES && REMAINING_TIME_SEC > 0 )); then sleep $REMAINING_TIME_SEC; exit 1; # exit as failed, so the job will be restarted to check if the expiration was updated @@ -620,14 +622,6 @@ func genWebTerminalCleanupJob(userAppName, userEmailID string) *batchv1.Job { Name: "NAMESPACE_SYSTEM", Value: metav1.NamespaceSystem, }, - { - Name: "EXPIRATION", - Value: "/etc/config/expiration", - }, - { - Name: "EXPIRATION_REFRESHES", - Value: "/etc/config/expiration-refreshes", - }, { Name: "MAX_EXPIRATION_REFRESHES", Value: strconv.Itoa(maxNumberOfExpirationRefreshes), @@ -637,7 +631,7 @@ func genWebTerminalCleanupJob(userAppName, userEmailID string) *batchv1.Job { Value: "\\$ ", }, }, - VolumeMounts: getVolumeMounts(), + VolumeMounts: getWebTerminalCleanJobVolumeMounts(), SecurityContext: &corev1.SecurityContext{ AllowPrivilegeEscalation: resources.Bool(false), }, @@ -692,6 +686,30 @@ func getVolumes(userEmailID, userAppName string) []corev1.Volume { return vs } +func getWebTerminalCleanJobVolumes() []corev1.Volume { + vs := []corev1.Volume{ + { + Name: resources.AdminKubeconfigSecretName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: resources.AdminKubeconfigSecretName, + }, + }, + }, + } + return vs +} + +func getWebTerminalCleanJobVolumeMounts() []corev1.VolumeMount { + return []corev1.VolumeMount{ + { + Name: resources.AdminKubeconfigSecretName, + MountPath: "/etc/kubernetes/kubeconfig", + ReadOnly: true, + }, + } +} + func getVolumeMounts() []corev1.VolumeMount { return []corev1.VolumeMount{ { @@ -712,10 +730,11 @@ func getVolumeMounts() []corev1.VolumeMount { } // Terminal is called for any new websocket connection. -func Terminal(ctx context.Context, ws *websocket.Conn, client ctrlruntimeclient.Client, k8sClient kubernetes.Interface, cfg *rest.Config, userEmailID string, cluster *kubermaticv1.Cluster, options *kubermaticv1.WebTerminalOptions) { +func Terminal(ctx context.Context, ws *websocket.Conn, client, seedClient ctrlruntimeclient.Client, k8sClient kubernetes.Interface, cfg *rest.Config, userEmailID string, cluster *kubermaticv1.Cluster, options *kubermaticv1.WebTerminalOptions) { if err := startProcess( ctx, client, + seedClient, k8sClient, cfg, userEmailID, diff --git a/modules/api/pkg/watcher/types.go b/modules/api/pkg/watcher/types.go index b202c84cdd..35c901ce71 100644 --- a/modules/api/pkg/watcher/types.go +++ b/modules/api/pkg/watcher/types.go @@ -32,6 +32,7 @@ type Providers struct { PrivilegedProjectProvider provider.PrivilegedProjectProvider UserInfoGetter provider.UserInfoGetter SeedsGetter provider.SeedsGetter + SeedClientGetter provider.SeedClientGetter ClusterProviderGetter provider.ClusterProviderGetter } From 0512114486674abe41486cea91c57a775b098a96 Mon Sep 17 00:00:00 2001 From: Archana Sawant Date: Wed, 3 Sep 2025 20:25:26 +0530 Subject: [PATCH 37/42] [release/v2.27] Bump Go build image version (#7541) * [release/v2.27] Bump Go build image version Signed-off-by: archups * Fix Go build images tag in prow jobs Signed-off-by: archups --------- Signed-off-by: archups --- .prow/api.yaml | 8 ++++---- .prow/common.yaml | 2 +- .prow/frontend.yaml | 6 +++--- hack/verify-spelling.sh | 2 +- modules/api/hack/gen-api-client.sh | 2 +- modules/api/hack/update-swagger.sh | 2 +- modules/api/hack/verify-licenses.sh | 2 +- modules/web/containers/chrome-headless/Dockerfile | 2 +- modules/web/containers/custom-dashboard/Dockerfile | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) mode change 100644 => 100755 modules/web/containers/chrome-headless/Dockerfile diff --git a/.prow/api.yaml b/.prow/api.yaml index d88814a992..245fe3d57e 100644 --- a/.prow/api.yaml +++ b/.prow/api.yaml @@ -32,7 +32,7 @@ presubmits: preset-goproxy: "true" spec: containers: - - image: quay.io/kubermatic/build:go-1.23-node-20-kind-0.26-12 + - image: quay.io/kubermatic/build:go-1.23-node-20-kind-0.29-13 command: - "./hack/ci/run-api-e2e.sh" env: @@ -60,7 +60,7 @@ presubmits: preset-goproxy: "true" spec: containers: - - image: quay.io/kubermatic/build:go-1.23-node-20-12 + - image: quay.io/kubermatic/build:go-1.23-node-20-13 command: - make args: @@ -81,7 +81,7 @@ presubmits: preset-goproxy: "true" spec: containers: - - image: quay.io/kubermatic/build:go-1.23-node-20-12 + - image: quay.io/kubermatic/build:go-1.23-node-20-13 command: - make - api-lint @@ -101,7 +101,7 @@ presubmits: preset-goproxy: "true" spec: containers: - - image: quay.io/kubermatic/build:go-1.23-node-20-12 + - image: quay.io/kubermatic/build:go-1.23-node-20-13 command: - make - api-verify diff --git a/.prow/common.yaml b/.prow/common.yaml index 3d392413ff..6e5833f97a 100644 --- a/.prow/common.yaml +++ b/.prow/common.yaml @@ -21,7 +21,7 @@ presubmits: preset-goproxy: "true" spec: containers: - - image: quay.io/kubermatic/build:go-1.23-node-20-kind-0.26-12 + - image: quay.io/kubermatic/build:go-1.23-node-20-kind-0.29-13 command: - ./hack/ci/verify.sh resources: diff --git a/.prow/frontend.yaml b/.prow/frontend.yaml index 6aef62dfb9..941df0b9e0 100644 --- a/.prow/frontend.yaml +++ b/.prow/frontend.yaml @@ -215,7 +215,7 @@ presubmits: preset-goproxy: "true" spec: containers: - - image: quay.io/kubermatic/build:go-1.23-node-20-kind-0.26-12 + - image: quay.io/kubermatic/build:go-1.23-node-20-kind-0.29-13 command: - make - web-check-dependencies @@ -231,7 +231,7 @@ presubmits: preset-goproxy: "true" spec: containers: - - image: quay.io/kubermatic/build:go-1.23-node-20-12 + - image: quay.io/kubermatic/build:go-1.23-node-20-13 command: - make - web-lint @@ -251,7 +251,7 @@ presubmits: preset-goproxy: "true" spec: containers: - - image: quay.io/kubermatic/build:go-1.23-node-20-kind-0.26-12 + - image: quay.io/kubermatic/build:go-1.23-node-20-kind-0.29-13 command: - make - web-check diff --git a/hack/verify-spelling.sh b/hack/verify-spelling.sh index e7ff16ff8b..0fd9cabb3f 100755 --- a/hack/verify-spelling.sh +++ b/hack/verify-spelling.sh @@ -19,7 +19,7 @@ set -euo pipefail cd $(dirname $0)/.. source hack/lib.sh -CONTAINERIZE_IMAGE=quay.io/kubermatic/build:go-1.23-node-20-12 containerize ./hack/verify-spelling.sh +CONTAINERIZE_IMAGE=quay.io/kubermatic/build:go-1.23-node-20-13 containerize ./hack/verify-spelling.sh echodate "Running codespell..." diff --git a/modules/api/hack/gen-api-client.sh b/modules/api/hack/gen-api-client.sh index 2c9ca0d0ec..a17ae8fde7 100755 --- a/modules/api/hack/gen-api-client.sh +++ b/modules/api/hack/gen-api-client.sh @@ -24,7 +24,7 @@ source hack/lib.sh API=modules/api -CONTAINERIZE_IMAGE=quay.io/kubermatic/build:go-1.23-node-20-12 containerize ./modules/api/hack/gen-api-client.sh +CONTAINERIZE_IMAGE=quay.io/kubermatic/build:go-1.23-node-20-13 containerize ./modules/api/hack/gen-api-client.sh cd $API/cmd/kubermatic-api/ SWAGGER_FILE="swagger.json" diff --git a/modules/api/hack/update-swagger.sh b/modules/api/hack/update-swagger.sh index 5a81f67eee..fc7c5f6692 100755 --- a/modules/api/hack/update-swagger.sh +++ b/modules/api/hack/update-swagger.sh @@ -21,7 +21,7 @@ source hack/lib.sh API=modules/api -CONTAINERIZE_IMAGE=quay.io/kubermatic/build:go-1.23-node-20-12 containerize ./$API/hack/update-swagger.sh +CONTAINERIZE_IMAGE=quay.io/kubermatic/build:go-1.23-node-20-13 containerize ./$API/hack/update-swagger.sh echodate "Generating swagger spec" cd $API/cmd/kubermatic-api/ diff --git a/modules/api/hack/verify-licenses.sh b/modules/api/hack/verify-licenses.sh index 37e690564a..ddc87e67fc 100755 --- a/modules/api/hack/verify-licenses.sh +++ b/modules/api/hack/verify-licenses.sh @@ -21,7 +21,7 @@ source hack/lib.sh API=modules/api -CONTAINERIZE_IMAGE=quay.io/kubermatic/build:go-1.23-node-20-12 containerize ./$API/hack/verify-licenses.sh +CONTAINERIZE_IMAGE=quay.io/kubermatic/build:go-1.23-node-20-13 containerize ./$API/hack/verify-licenses.sh cd $API go mod vendor diff --git a/modules/web/containers/chrome-headless/Dockerfile b/modules/web/containers/chrome-headless/Dockerfile old mode 100644 new mode 100755 index 366dd78bd1..4d7521ce16 --- a/modules/web/containers/chrome-headless/Dockerfile +++ b/modules/web/containers/chrome-headless/Dockerfile @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM quay.io/kubermatic/build:go-1.23-node-20-kind-0.26-12 +FROM quay.io/kubermatic/build:go-1.23-node-20-kind-0.29-13 LABEL maintainer="support@kubermatic.com" diff --git a/modules/web/containers/custom-dashboard/Dockerfile b/modules/web/containers/custom-dashboard/Dockerfile index 05b3af546d..437d454da5 100644 --- a/modules/web/containers/custom-dashboard/Dockerfile +++ b/modules/web/containers/custom-dashboard/Dockerfile @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM quay.io/kubermatic/build:go-1.23-node-20-kind-0.26-12 +FROM quay.io/kubermatic/build:go-1.23-node-20-kind-0.29-13 LABEL maintainer="support@kubermatic.com" ENV NG_CLI_ANALYTICS=ci From 701be9d44c9d9bb862073a5bb29fd2f4f154acb5 Mon Sep 17 00:00:00 2001 From: Ahmad Hamzh <85109141+ahmadhamzh@users.noreply.github.com> Date: Thu, 4 Sep 2025 12:09:30 +0300 Subject: [PATCH 38/42] Refresh and update token for webterminal kubeconfig when token is expired (#7508) (#7550) * Add a goroutine to check and update the token * Add expired session state * Add local history files to .gitignore * enhancement --- modules/api/.gitignore | 1 + modules/api/pkg/handler/auth/oidc.go | 32 ++++ modules/api/pkg/handler/common/kubeconfig.go | 13 +- .../api/pkg/handler/middleware/middleware.go | 4 +- .../api/pkg/handler/routes_v1_websocket.go | 13 +- modules/api/pkg/handler/test/fake_auth.go | 12 ++ modules/api/pkg/handler/websocket/terminal.go | 143 +++++++++++++++++- modules/api/pkg/provider/auth/types/types.go | 3 + modules/api/pkg/watcher/types.go | 1 + modules/web/.gitignore | 1 + .../shared/components/terminal/component.ts | 76 ++++++---- .../shared/components/terminal/template.html | 6 +- .../terminal/terminal-status-bar/component.ts | 19 +++ .../terminal-status-bar/template.html | 34 +++-- 14 files changed, 305 insertions(+), 53 deletions(-) diff --git a/modules/api/.gitignore b/modules/api/.gitignore index 8a9e9bc654..443a441758 100644 --- a/modules/api/.gitignore +++ b/modules/api/.gitignore @@ -29,6 +29,7 @@ .settings/ .vscode/* .nvmrc +.history # Misc. /.sass-cache diff --git a/modules/api/pkg/handler/auth/oidc.go b/modules/api/pkg/handler/auth/oidc.go index d1fe7fb817..d4f4ab840b 100644 --- a/modules/api/pkg/handler/auth/oidc.go +++ b/modules/api/pkg/handler/auth/oidc.go @@ -174,6 +174,38 @@ func (o *OpenIDClient) Exchange(ctx context.Context, code, overwriteRedirectURI return oidcToken, nil } +func (o *OpenIDClient) RefreshAccessToken(ctx context.Context, refreshToken string) (authtypes.OIDCToken, error) { + tok := &oauth2.Token{RefreshToken: refreshToken} + tokenSource := o.oauth2Config("", "email"). + TokenSource(ctx, tok) + + tokens, err := tokenSource.Token() + if err != nil { + fmt.Println("token refresh failed: %w", err) + return authtypes.OIDCToken{}, fmt.Errorf("token refresh failed: %w", err) + } + + rt := tokens.RefreshToken + if rt == "" { + rt = refreshToken + } + + oidcToken := authtypes.OIDCToken{ + AccessToken: tokens.AccessToken, + RefreshToken: rt, + Expiry: tokens.Expiry, + } + + if rawIDToken, ok := tokens.Extra("id_token").(string); ok { + oidcToken.IDToken = rawIDToken + } + + if oidcToken.IDToken == "" { + return authtypes.OIDCToken{}, fmt.Errorf("token refresh failed: id_token not found in the response") + } + return oidcToken, nil +} + func (o *OpenIDClient) GetRedirectURI(path string) (string, error) { u, err := url.Parse(o.redirectURI) if err != nil { diff --git a/modules/api/pkg/handler/common/kubeconfig.go b/modules/api/pkg/handler/common/kubeconfig.go index 7a088f7970..489feb38a5 100644 --- a/modules/api/pkg/handler/common/kubeconfig.go +++ b/modules/api/pkg/handler/common/kubeconfig.go @@ -582,15 +582,10 @@ func createKubeconfigSecret(ctx context.Context, client ctrlruntimeclient.Client resources.KubeconfigSecretKey: kubeconfig, } - // return if already exists - if existingSecret.Name != "" { - return nil - } - - return createSecret(ctx, client, kubeconfigSecretName, email, secretData) + return createSecret(ctx, client, kubeconfigSecretName, email, secretData, existingSecret.Name) } -func createSecret(ctx context.Context, client ctrlruntimeclient.Client, name, email string, secretData map[string][]byte) error { +func createSecret(ctx context.Context, client ctrlruntimeclient.Client, name, email string, secretData map[string][]byte, existingSecretName string) error { secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: name, @@ -600,6 +595,10 @@ func createSecret(ctx context.Context, client ctrlruntimeclient.Client, name, em Type: corev1.SecretTypeOpaque, Data: secretData, } + + if existingSecretName != "" { + return client.Update(ctx, secret) + } return client.Create(ctx, secret) } diff --git a/modules/api/pkg/handler/middleware/middleware.go b/modules/api/pkg/handler/middleware/middleware.go index 3c6fbfa653..baba4a22fc 100644 --- a/modules/api/pkg/handler/middleware/middleware.go +++ b/modules/api/pkg/handler/middleware/middleware.go @@ -1031,7 +1031,7 @@ func OIDCProviders(clusterProviderGetter provider.ClusterProviderGetter, oidcIss return func(ctx context.Context, request interface{}) (response interface{}, err error) { seedCluster := request.(seedClusterGetter).GetSeedCluster() - oidcIssuerVerifier, err := getOIDCIssuerVerifier(ctx, clusterProviderGetter, oidcIssuerVerifierGetter, seedsGetter, seedCluster.ClusterID) + oidcIssuerVerifier, err := GetOIDCIssuerVerifier(ctx, clusterProviderGetter, oidcIssuerVerifierGetter, seedsGetter, seedCluster.ClusterID) if err != nil { return nil, err } @@ -1041,7 +1041,7 @@ func OIDCProviders(clusterProviderGetter provider.ClusterProviderGetter, oidcIss } } -func getOIDCIssuerVerifier( +func GetOIDCIssuerVerifier( ctx context.Context, clusterProviderGetter provider.ClusterProviderGetter, oidcIssuerVerifierGetter provider.OIDCIssuerVerifierGetter, diff --git a/modules/api/pkg/handler/routes_v1_websocket.go b/modules/api/pkg/handler/routes_v1_websocket.go index b577d423ff..ef4dde303b 100644 --- a/modules/api/pkg/handler/routes_v1_websocket.go +++ b/modules/api/pkg/handler/routes_v1_websocket.go @@ -81,7 +81,7 @@ var upgrader = websocket.Upgrader{ type WebsocketSettingsWriter func(ctx context.Context, providers watcher.Providers, ws *websocket.Conn) type WebsocketUserWriter func(ctx context.Context, providers watcher.Providers, ws *websocket.Conn, userEmail string) -type WebsocketTerminalWriter func(ctx context.Context, ws *websocket.Conn, client, seedClient ctrlruntimeclient.Client, k8sClient kubernetes.Interface, cfg *rest.Config, userEmailID string, cluster *kubermaticv1.Cluster, options *kubermaticv1.WebTerminalOptions) +type WebsocketTerminalWriter func(ctx context.Context, ws *websocket.Conn, client, seedClient ctrlruntimeclient.Client, k8sClient kubernetes.Interface, cfg *rest.Config, userEmailID string, cluster *kubermaticv1.Cluster, options *kubermaticv1.WebTerminalOptions, oidcIssuerVerifier authtypes.OIDCIssuerVerifier, kubeconfigSecret *corev1.Secret) const ( maxNumberOfTerminalActiveConnectionsPerUser = 5 @@ -110,6 +110,7 @@ func getProviders(r Routing) watcher.Providers { SeedsGetter: r.seedsGetter, SeedClientGetter: r.seedsClientGetter, ClusterProviderGetter: r.clusterProviderGetter, + OIDCIssuerVerifierGetter: r.oidcIssuerVerifierGetter, } } @@ -251,6 +252,12 @@ func getTerminalWatchHandler(writer WebsocketTerminalWriter, providers watcher.P if err != nil { return } + + oidcIssuerVerifier, err := middleware.GetOIDCIssuerVerifier(ctx, providers.ClusterProviderGetter, providers.OIDCIssuerVerifierGetter, providers.SeedsGetter, clusterID) + if err != nil { + return + } + privilegedClusterProvider := clusterProvider.(provider.PrivilegedClusterProvider) user, err := providers.UserProvider.UserByEmail(ctx, authenticatedUser.Email) @@ -298,8 +305,8 @@ func getTerminalWatchHandler(writer WebsocketTerminalWriter, providers watcher.P connectionsPerUser.increaseActiveConnections(userProjectClusterUniqueKey) defer connectionsPerUser.decreaseActiveConnections(userProjectClusterUniqueKey) + kubeconfigSecret := &corev1.Secret{} if !wsh.WaitFor(ctx, 5*time.Second, waitForKubeconfigSecretTimeout, func(ctx context.Context) bool { - kubeconfigSecret := &corev1.Secret{} if err := client.Get(ctx, ctrlruntimeclient.ObjectKey{ Namespace: metav1.NamespaceSystem, Name: handlercommon.KubeconfigSecretName(userEmailID), @@ -314,7 +321,7 @@ func getTerminalWatchHandler(writer WebsocketTerminalWriter, providers watcher.P return } - writer(ctx, ws, client, seedClient, k8sClient, cfg, userEmailID, cluster, settings.Spec.WebTerminalOptions) + writer(ctx, ws, client, seedClient, k8sClient, cfg, userEmailID, cluster, settings.Spec.WebTerminalOptions, oidcIssuerVerifier, kubeconfigSecret) } } diff --git a/modules/api/pkg/handler/test/fake_auth.go b/modules/api/pkg/handler/test/fake_auth.go index 5bf7add35c..019f05ae52 100644 --- a/modules/api/pkg/handler/test/fake_auth.go +++ b/modules/api/pkg/handler/test/fake_auth.go @@ -139,6 +139,18 @@ func (o *IssuerVerifier) Exchange(ctx context.Context, code, overwriteRedirectUR }, nil } +// RefreshAccessToken simulates refreshing an access token. +func (o *IssuerVerifier) RefreshAccessToken(ctx context.Context, refToken string) (authtypes.OIDCToken, error) { + if refToken != refreshToken { + return authtypes.OIDCToken{}, errors.New("invalid refresh token") + } + + return authtypes.OIDCToken{ + IDToken: IDToken, + RefreshToken: refreshToken, + }, nil +} + // Verify parses a raw ID Token, verifies it's been signed by the provider, performs // any additional checks depending on the Config, and returns the payload as TokenClaims. func (o *IssuerVerifier) Verify(ctx context.Context, token string) (authtypes.TokenClaims, error) { diff --git a/modules/api/pkg/handler/websocket/terminal.go b/modules/api/pkg/handler/websocket/terminal.go index fc838976db..1615a28672 100644 --- a/modules/api/pkg/handler/websocket/terminal.go +++ b/modules/api/pkg/handler/websocket/terminal.go @@ -19,6 +19,7 @@ package websocket import ( "context" "crypto/md5" + "encoding/base64" "encoding/hex" "encoding/json" "errors" @@ -26,11 +27,13 @@ import ( "io" "net/http" "strconv" + "strings" "time" "github.com/gorilla/websocket" handlercommon "k8c.io/dashboard/v2/pkg/handler/common" + authtypes "k8c.io/dashboard/v2/pkg/provider/auth/types" kubermaticv1 "k8c.io/kubermatic/v2/pkg/apis/kubermatic/v1" "k8c.io/kubermatic/v2/pkg/log" "k8c.io/kubermatic/v2/pkg/resources" @@ -44,6 +47,8 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "k8s.io/client-go/tools/remotecommand" "k8s.io/kubectl/pkg/scheme" ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client" @@ -59,6 +64,7 @@ const ( maxNumberOfExpirationRefreshes = 48 // it means maximum 24h for a pod lifetime of 30 minutes remainingExpirationTimeForWarning = 5 * time.Minute // should be lesser than "podLifetime" expirationCheckInterval = 1 * time.Minute // should be lesser than "remainingExpirationTimeForWarning" + remainingTokenExpirationTime = 5 * time.Minute expirationTimestampKey = "ExpirationTimestamp" expirationRefreshesKey = "ExpirationRefreshes" pingInterval = 30 * time.Second @@ -73,6 +79,9 @@ type TerminalConnStatus string const ( KubeconfigSecretMissing TerminalConnStatus = "KUBECONFIG_SECRET_MISSING" + KubeconfigSecretInvalid TerminalConnStatus = "KUBECONFIG_SECRET_INVALID" + WebTerminalTokenExpired TerminalConnStatus = "WEBTERMINAL_TOKEN_EXPIRED" + WebTerminalTokenValid TerminalConnStatus = "WEBTERMINAL_TOKEN_VALID" WebterminalPodPending TerminalConnStatus = "WEBTERMINAL_POD_PENDING" WebterminalPodFailed TerminalConnStatus = "WEBTERMINAL_POD_FAILED" ConnectionPoolExceeded TerminalConnStatus = "CONNECTION_POOL_EXCEEDED" @@ -267,6 +276,85 @@ func pingRoutine(websocketConn *websocket.Conn) { } } +func checkTokenAndRefreshIfNeeded(ctx context.Context, clusterClient ctrlruntimeclient.Client, oidcIssuerVerifier authtypes.OIDCIssuerVerifier, kubeconfigSecret *corev1.Secret, websocketConn *websocket.Conn) { + for { + // Update the kubeconfig secret in case it gets recreated. + if err := clusterClient.Get(ctx, ctrlruntimeclient.ObjectKey{ + Namespace: metav1.NamespaceSystem, + Name: kubeconfigSecret.Name, + }, kubeconfigSecret); err != nil { + log.Logger.Debug(err) + _ = SendMessage(websocketConn, string(KubeconfigSecretMissing)) + time.Sleep(5 * time.Second) + continue + } + cfg, err := clientcmd.Load(kubeconfigSecret.Data["kubeconfig"]) + if err != nil { + log.Logger.Debug(err) + _ = SendMessage(websocketConn, string(KubeconfigSecretInvalid)) + time.Sleep(5 * time.Second) + continue + } + + user, err := getFirstUser(cfg.AuthInfos) + if err != nil { + log.Logger.Debug(err) + _ = SendMessage(websocketConn, string(KubeconfigSecretInvalid)) + time.Sleep(5 * time.Second) + continue + } + + tokenExpirationTime, err := GetTokenExpiration(user.AuthProvider.Config["id-token"]) + if err != nil { + log.Logger.Debug(err) + onFailedUpdatingToken(websocketConn, tokenExpirationTime) + continue + } + + // Only refresh the token when it’s close to expiring. + if time.Until(tokenExpirationTime) <= remainingTokenExpirationTime { + refreshToken := user.AuthProvider.Config["refresh-token"] + + newToken, err := oidcIssuerVerifier.RefreshAccessToken(ctx, refreshToken) + if err != nil { + log.Logger.Debug(err) + onFailedUpdatingToken(websocketConn, tokenExpirationTime) + continue + } + + user.AuthProvider.Config["id-token"] = newToken.IDToken + user.AuthProvider.Config["refresh-token"] = newToken.RefreshToken + updatedKubeconfig, err := clientcmd.Write(*cfg) + if err != nil { + log.Logger.Debug(err) + onFailedUpdatingToken(websocketConn, tokenExpirationTime) + continue + } + kubeconfigSecret.Data["kubeconfig"] = updatedKubeconfig + if err := clusterClient.Update(ctx, kubeconfigSecret); err != nil { + log.Logger.Debug(err) + onFailedUpdatingToken(websocketConn, tokenExpirationTime) + continue + } + tokenExpirationTime, err = GetTokenExpiration(user.AuthProvider.Config["id-token"]) + if err != nil { + log.Logger.Debug(err) + onFailedUpdatingToken(websocketConn, tokenExpirationTime) + continue + } + } + + // check again 5 min before the token expiration or at the token expiration time + _ = SendMessage(websocketConn, string(WebTerminalTokenValid)) + sleepDuration := time.Until(tokenExpirationTime) - remainingTokenExpirationTime + if sleepDuration > 0 { + time.Sleep(sleepDuration) + } else { + time.Sleep(time.Until(tokenExpirationTime)) + } + } +} + func expirationCheckRoutine(ctx context.Context, clusterClient ctrlruntimeclient.Client, userEmailID string, websocketConn *websocket.Conn) { for { time.Sleep(expirationCheckInterval) @@ -295,7 +383,7 @@ func expirationCheckRoutine(ctx context.Context, clusterClient ctrlruntimeclient // startProcess is called by terminal session creation. // Executed cmd in the container specified in request and connects it up with the ptyHandler (a session). -func startProcess(ctx context.Context, client, seedClient ctrlruntimeclient.Client, k8sClient kubernetes.Interface, cfg *rest.Config, userEmailID string, cluster *kubermaticv1.Cluster, options *kubermaticv1.WebTerminalOptions, cmd []string, ptyHandler PtyHandler, websocketConn *websocket.Conn) error { +func startProcess(ctx context.Context, client, seedClient ctrlruntimeclient.Client, k8sClient kubernetes.Interface, cfg *rest.Config, userEmailID string, cluster *kubermaticv1.Cluster, options *kubermaticv1.WebTerminalOptions, oidcIssuerVerifier authtypes.OIDCIssuerVerifier, kubeconfigSecret *corev1.Secret, cmd []string, ptyHandler PtyHandler, websocketConn *websocket.Conn) error { userAppName := userAppName(userEmailID) // check if WEB terminal Pod exists, if not create pod := &corev1.Pod{} @@ -354,6 +442,8 @@ func startProcess(ctx context.Context, client, seedClient ctrlruntimeclient.Clie go expirationCheckRoutine(ctx, client, userEmailID, websocketConn) + go checkTokenAndRefreshIfNeeded(ctx, client, oidcIssuerVerifier, kubeconfigSecret, websocketConn) + req := k8sClient.CoreV1().RESTClient().Post(). Resource("pods"). Name(userAppName). @@ -730,7 +820,7 @@ func getVolumeMounts() []corev1.VolumeMount { } // Terminal is called for any new websocket connection. -func Terminal(ctx context.Context, ws *websocket.Conn, client, seedClient ctrlruntimeclient.Client, k8sClient kubernetes.Interface, cfg *rest.Config, userEmailID string, cluster *kubermaticv1.Cluster, options *kubermaticv1.WebTerminalOptions) { +func Terminal(ctx context.Context, ws *websocket.Conn, client, seedClient ctrlruntimeclient.Client, k8sClient kubernetes.Interface, cfg *rest.Config, userEmailID string, cluster *kubermaticv1.Cluster, options *kubermaticv1.WebTerminalOptions, oidcIssuerVerifier authtypes.OIDCIssuerVerifier, kubeconfigSecret *corev1.Secret) { if err := startProcess( ctx, client, @@ -740,6 +830,8 @@ func Terminal(ctx context.Context, ws *websocket.Conn, client, seedClient ctrlru userEmailID, cluster, options, + oidcIssuerVerifier, + kubeconfigSecret, []string{"bash", "-c", "cd /data/terminal && /bin/bash"}, TerminalSession{ websocketConn: ws, @@ -776,3 +868,50 @@ func SendMessage(wsConn *websocket.Conn, message string) error { Data: message, }) } + +// The kubeconfig secret must contain a single user, so we return the first one. +func getFirstUser(authInfos map[string]*clientcmdapi.AuthInfo) (*clientcmdapi.AuthInfo, error) { + for _, u := range authInfos { + return u, nil + } + return nil, fmt.Errorf("no user found in kubeconfig") +} + +func GetTokenExpiration(token string) (time.Time, error) { + parts := strings.Split(token, ".") + if len(parts) < 2 { + return time.Time{}, fmt.Errorf("invalid token format") + } + + payload, err := base64.RawURLEncoding.DecodeString(parts[1]) + if err != nil { + return time.Time{}, fmt.Errorf("failed to decode token: %w", err) + } + + var claims map[string]interface{} + if err := json.Unmarshal(payload, &claims); err != nil { + return time.Time{}, fmt.Errorf("failed to unmarshal claims: %w", err) + } + + expFloat, ok := claims["exp"].(float64) + if !ok { + return time.Time{}, fmt.Errorf("exp claim missing") + } + + return time.Unix(int64(expFloat), 0), nil +} + +func onFailedUpdatingToken(websocketConn *websocket.Conn, tokenExpirationTime time.Time) { + // If the token is already expired, send a message to the client and check every 5 seconds if the user authenticates again (update the kubeconfig secret). + if time.Until(tokenExpirationTime) <= 0 { + log.Logger.Debug("Token is already expired, and cannot be refreshed") + _ = SendMessage(websocketConn, string(WebTerminalTokenExpired)) + time.Sleep(5 * time.Second) + } else { + // If the token is still valid but can't be refreshed, try again after 1 minute. + if time.Until(tokenExpirationTime) <= 1*time.Minute { + time.Sleep(time.Until(tokenExpirationTime)) // wait until the token is expired + } + time.Sleep(1 * time.Minute) + } +} diff --git a/modules/api/pkg/provider/auth/types/types.go b/modules/api/pkg/provider/auth/types/types.go index b029e20096..cc4f2c3eed 100644 --- a/modules/api/pkg/provider/auth/types/types.go +++ b/modules/api/pkg/provider/auth/types/types.go @@ -52,6 +52,9 @@ type OIDCIssuer interface { // Exchange converts an authorization code into a token. Exchange(ctx context.Context, code, overwriteRedirectURI string) (OIDCToken, error) + // RefreshAccessToken uses a refresh token to obtain a new OIDC token. + RefreshAccessToken(ctx context.Context, refreshToken string) (OIDCToken, error) + // OIDCConfig returns the issuers OIDC config OIDCConfig() *OIDCConfiguration } diff --git a/modules/api/pkg/watcher/types.go b/modules/api/pkg/watcher/types.go index 35c901ce71..64a7c78b6e 100644 --- a/modules/api/pkg/watcher/types.go +++ b/modules/api/pkg/watcher/types.go @@ -34,6 +34,7 @@ type Providers struct { SeedsGetter provider.SeedsGetter SeedClientGetter provider.SeedClientGetter ClusterProviderGetter provider.ClusterProviderGetter + OIDCIssuerVerifierGetter provider.OIDCIssuerVerifierGetter } type SettingsWatcher interface { diff --git a/modules/web/.gitignore b/modules/web/.gitignore index f9f3844afa..b30118bdee 100644 --- a/modules/web/.gitignore +++ b/modules/web/.gitignore @@ -34,6 +34,7 @@ .settings/ .vscode/* .nvmrc +.history # Misc. /.sass-cache diff --git a/modules/web/src/app/shared/components/terminal/component.ts b/modules/web/src/app/shared/components/terminal/component.ts index ac89c0807c..5cb644a245 100644 --- a/modules/web/src/app/shared/components/terminal/component.ts +++ b/modules/web/src/app/shared/components/terminal/component.ts @@ -62,6 +62,8 @@ enum Operations { enum ErrorOperations { KubeConfigSecretMissing = 'KUBECONFIG_SECRET_MISSING', + KubeConfigSecretInvalid = 'KUBECONFIG_SECRET_INVALID', + WebTerminalTokenExpired = 'WEBTERMINAL_TOKEN_EXPIRED', WebTerminalPodPending = 'WEBTERMINAL_POD_PENDING', ConnectionPoolExceeded = 'CONNECTION_POOL_EXCEEDED', RefreshesLimitExceeded = 'REFRESHES_LIMIT_EXCEEDED', @@ -70,6 +72,7 @@ enum ErrorOperations { enum MessageTypes { Ping = 'PING', Pong = 'PONG', + WebTerminalTokenValid = 'WEBTERMINAL_TOKEN_VALID', } @Component({ selector: 'km-terminal', @@ -89,6 +92,7 @@ export class TerminalComponent implements OnChanges, OnInit, OnDestroy, AfterVie terminal: Terminal; isConnectionLost: boolean; isSessionExpiring: boolean; + isTokenExpired: boolean; isLoadingTerminal = true; isDexAuthenticationPageOpened = false; showCloseButtonOnToolbar: boolean; @@ -245,6 +249,11 @@ export class TerminalComponent implements OnChanges, OnInit, OnDestroy, AfterVie this.isSessionExpiring = false; } + onTokenExpired(): void { + const url = this._getWebTerminalProxyURL(); + window.open(url, '_blank'); + } + private _getWebTerminalProxyURL(): string { const userId = this._user?.id; return this._clusterService.getWebTerminalProxyURL(this.projectId, this.cluster.id, userId); @@ -276,38 +285,51 @@ export class TerminalComponent implements OnChanges, OnInit, OnDestroy, AfterVie this._initializeTerminalOnSuccess.complete(); this._logToTerminal(frame.Data); } else if (frame.Op === Operations.Msg) { - if (frame.Data === ErrorOperations.KubeConfigSecretMissing) { - if (!this.isDexAuthenticationPageOpened) { - const url = this._getWebTerminalProxyURL(); - window.open(url, '_blank'); - this.isDexAuthenticationPageOpened = true; - } - this.message = 'Please wait, authenticate in order to access web terminal...'; - } else if (frame.Data === ErrorOperations.WebTerminalPodPending) { - this.message = 'Please wait, provisioning your Web Terminal pod...'; - } else if (frame.Data === ErrorOperations.ConnectionPoolExceeded) { - const message = `Oops! There could be ${this.MAX_SESSION_SUPPORTED} concurrent session per user. Please either discard or close other sessions`; - if (this.terminal) { - this._logToTerminal(message); - } else { - this.message = message; - } - } else if (frame.Data === ErrorOperations.RefreshesLimitExceeded) { - const clusterName = this.cluster && this.cluster.name; - this._logToTerminal( - `Oops! Max limit of ${this.MAX_EXPIRATION_REFRESHES} refreshes is reached. You are not allowed to extend the session for \x1b[1;34m${clusterName}\x1B[0m\n\n\r` - ); - } else if (frame.Data === MessageTypes.Ping) { - if (this.terminal) { - // Note: Periodic exchange of messages with server to keep the web Terminal connection alive - this._keepConnectionAlive(MessageTypes.Pong); - } - } + this._handelOperationsError(frame.Data); + this._handelMessageTypes(frame.Data); } else if (frame.Op === Operations.Expiration) { this.isSessionExpiring = true; } } + private _handelOperationsError(data: string): void { + if (data === ErrorOperations.KubeConfigSecretMissing) { + if (!this.isDexAuthenticationPageOpened) { + const url = this._getWebTerminalProxyURL(); + window.open(url, '_blank'); + this.isDexAuthenticationPageOpened = true; + } + this.message = 'Please wait, authenticate in order to access web terminal...'; + } else if (data === ErrorOperations.WebTerminalPodPending) { + this.message = 'Please wait, provisioning your Web Terminal pod...'; + } else if (data === ErrorOperations.WebTerminalTokenExpired || data === ErrorOperations.KubeConfigSecretInvalid) { + this.isTokenExpired = true; + } else if (data === ErrorOperations.ConnectionPoolExceeded) { + const message = `Oops! There could be ${this.MAX_SESSION_SUPPORTED} concurrent session per user. Please either discard or close other sessions`; + if (this.terminal) { + this._logToTerminal(message); + } else { + this.message = message; + } + } else if (data === ErrorOperations.RefreshesLimitExceeded) { + const clusterName = this.cluster && this.cluster.name; + this._logToTerminal( + `Oops! Max limit of ${this.MAX_EXPIRATION_REFRESHES} refreshes is reached. You are not allowed to extend the session for \x1b[1;34m${clusterName}\x1B[0m\n\n\r` + ); + } + } + + private _handelMessageTypes(data: string): void { + if (data === MessageTypes.WebTerminalTokenValid) { + this.isTokenExpired = false; + } else if (data === MessageTypes.Ping) { + if (this.terminal) { + // Note: Periodic exchange of messages with server to keep the web Terminal connection alive + this._keepConnectionAlive(MessageTypes.Pong); + } + } + } + private _logToTerminal(message: string): void { if (this.terminal) { this.terminal.write(message); diff --git a/modules/web/src/app/shared/components/terminal/template.html b/modules/web/src/app/shared/components/terminal/template.html index c92240b20b..0670842634 100644 --- a/modules/web/src/app/shared/components/terminal/template.html +++ b/modules/web/src/app/shared/components/terminal/template.html @@ -38,11 +38,13 @@ (openInNewTab)="openInSeparateView()" (close)="onClose()"> - + (extendSession)="onExtendSession()" + (tokenExpired)="onTokenExpired()">
diff --git a/modules/web/src/app/shared/components/terminal/terminal-status-bar/component.ts b/modules/web/src/app/shared/components/terminal/terminal-status-bar/component.ts index ab24d7610d..9fdcb20c27 100644 --- a/modules/web/src/app/shared/components/terminal/terminal-status-bar/component.ts +++ b/modules/web/src/app/shared/components/terminal/terminal-status-bar/component.ts @@ -14,6 +14,12 @@ import {Component, EventEmitter, Input, Output} from '@angular/core'; +enum TerminalState { + Expired = 'expired', + ConnectionLost = 'connectionLost', + Expiring = 'expiring', +} + @Component({ selector: 'km-terminal-status-bar', templateUrl: './template.html', @@ -22,8 +28,17 @@ import {Component, EventEmitter, Input, Output} from '@angular/core'; export class TerminalStatusBarComponent { @Input() isConnectionLost: boolean; @Input() isSessionExpiring: boolean; + @Input() isTokenExpired: boolean; @Output() reconnect = new EventEmitter(); @Output() extendSession = new EventEmitter(); + @Output() tokenExpired = new EventEmitter(); + terminalState = TerminalState; + + get sessionState(): TerminalState { + if (this.isTokenExpired) return TerminalState.Expired; + if (this.isConnectionLost) return TerminalState.ConnectionLost; + return TerminalState.Expiring; + } onExtendSession(): void { this.extendSession.emit(); @@ -32,4 +47,8 @@ export class TerminalStatusBarComponent { onReconnect(): void { this.reconnect.emit(); } + + onTokenExpired(): void { + this.tokenExpired.emit(); + } } diff --git a/modules/web/src/app/shared/components/terminal/terminal-status-bar/template.html b/modules/web/src/app/shared/components/terminal/terminal-status-bar/template.html index a7a91e2418..631d6f75d3 100644 --- a/modules/web/src/app/shared/components/terminal/terminal-status-bar/template.html +++ b/modules/web/src/app/shared/components/terminal/terminal-status-bar/template.html @@ -21,23 +21,37 @@
-
+
+

+ Session expired. Authenticate again and wait a few seconds for the new kubeconfig to mount. +

- -

The connection to your Web Terminal was lost.

-
+

+ The connection to your Web Terminal was lost. +

- -

The web session will expire soon.

-
+

+ The web session will expire soon. +

-
- + + -