diff --git a/shell/assets/translations/en-us.yaml b/shell/assets/translations/en-us.yaml
index 9ef3dd017c3..478c43599ed 100644
--- a/shell/assets/translations/en-us.yaml
+++ b/shell/assets/translations/en-us.yaml
@@ -355,9 +355,8 @@ addClusterMemberDialog:
title: Add Cluster Member
addonConfigConfirmation:
- title: Add-On Config Reset
- body: Changing the Kubernetes Version can reset the Add-On Config values. You should check that the values are as expected before continuing.
-
+ title: Add-On Reset
+ body: Changing the Kubernetes Version can reset Add-On values. You should check that the values are as expected before continuing.
addProjectMemberDialog:
title: Add Project Member
@@ -1108,38 +1107,38 @@ cis:
cluster:
addonChart:
rancher-vsphere-cpi:
- label: vSphere CPI
- configuration: vSphere CPI Configuration
+ label: "Add-on: vSphere CPI"
+ configuration: vSphere CPI
rancher-vsphere-csi:
- label: vSphere CSI
- configuration: vSphere CSI Configuration
+ label: "Add-on: vSphere CSI"
+ configuration: vSphere CSI
rke2-calico:
- label: Calico
- configuration: Calico Configuration
+ label: "Add-on: Calico"
+ configuration: Calico
rke2-calico-crd:
- label: Calico
- configuration: Calico Configuration
+ label: "Add-on: Calico"
+ configuration: Calico
rke2-canal:
- label: Canal
- configuration: Canal Configuration
+ label: "Add-on: Canal"
+ configuration: Canal
rke2-cilium:
- label: Cilium
- configuration: Cilium Configuration
+ label: "Add-on: Cilium"
+ configuration: Cilium
rke2-coredns:
- label: CoreDNS
- configuration: CoreDNS Configuration
+ label: "Add-on: CoreDNS"
+ configuration: CoreDNS
rke2-ingress-nginx:
- label: NGINX
- configuration: NGINX Ingress Configuration
+ label: "Add-on: NGINX"
+ configuration: NGINX Ingress
rke2-kube-proxy:
- label: Kube Proxy
- configuration: Kube Proxy Configuration
+ label: "Add-on: Kube Proxy"
+ configuration: Kube Proxy
rke2-metrics-server:
- label: Metrics Server
- configuration: Metrics Server Configuration
+ label: "Add-on: Metrics Server"
+ configuration: Metrics Server
rke2-multus:
- label: Multus
- configuration: Multus Configuration
+ label: "Add-on: Multus"
+ configuration: Multus
agentEnvVars:
label: Agent Environment
detail: Add additional environment variables to the agent container. This is most commonly useful for configuring a HTTP proxy.
@@ -1155,7 +1154,7 @@ cluster:
label: Google
rancher-vsphere:
label: vSphere
- note: 'Important: Configure the vSphere Cloud Provider and Storage Provider options in the tabs on the left.'
+ note: 'Important: Configure the vSphere Cloud Provider and Storage Provider options in the Add-on tabs.'
harvester:
label: Harvester
copyConfig: Copy KubeConfig to Clipboard
@@ -1657,7 +1656,7 @@ cluster:
serverOs:
label: OS
addOns:
- dependencyBanner: Add-On Configurations can vary between Kubernetes versions. Changing the Kubernetes version may reset the values below.
+ dependencyBanner: Add-On Configuration can vary between Kubernetes versions. Changing the Kubernetes version may reset the values below.
additionalManifest:
title: Additional Manifest
tooltip: 'Additional Kubernetes Manifest YAML to be applied to the cluster on startup.'
diff --git a/shell/edit/provisioning.cattle.io.cluster/rke2.vue b/shell/edit/provisioning.cattle.io.cluster/rke2.vue
index 682e098d420..2f51dbb24f8 100644
--- a/shell/edit/provisioning.cattle.io.cluster/rke2.vue
+++ b/shell/edit/provisioning.cattle.io.cluster/rke2.vue
@@ -75,6 +75,7 @@ import MemberRoles from '@shell/edit/provisioning.cattle.io.cluster/MemberRoles'
import Basics from '@shell/edit/provisioning.cattle.io.cluster/Basics';
import AddOnConfig from '@shell/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig';
import AddOnAdditionalManifest from '@shell/edit/provisioning.cattle.io.cluster/tabs/AddOnAdditionalManifest';
+import VsphereUtils from '@shell/utils/v-sphere';
const HARVESTER = 'harvester';
const HARVESTER_CLOUD_PROVIDER = 'harvester-cloud-provider';
@@ -839,6 +840,8 @@ export default {
created() {
this.registerBeforeHook(this.saveMachinePools, 'save-machine-pools', 1);
this.registerBeforeHook(this.setRegistryConfig, 'set-registry-config');
+ this.registerBeforeHook(this.handleVsphereCpiSecret, 'sync-vsphere-cpi');
+ this.registerBeforeHook(this.handleVsphereCsiSecret, 'sync-vsphere-csi');
this.registerAfterHook(this.cleanupMachinePools, 'cleanup-machine-pools');
this.registerAfterHook(this.saveRoleBindings, 'save-role-bindings');
@@ -851,6 +854,13 @@ export default {
methods: {
set,
+ async handleVsphereCpiSecret() {
+ return VsphereUtils.handleVsphereCpiSecret(this);
+ },
+ async handleVsphereCsiSecret() {
+ return VsphereUtils.handleVsphereCsiSecret(this);
+ },
+
/**
* Initialize all the cluster specs
*/
diff --git a/shell/utils/cluster.js b/shell/utils/cluster.js
index 67f78f5cddf..591b1f98c34 100644
--- a/shell/utils/cluster.js
+++ b/shell/utils/cluster.js
@@ -94,7 +94,7 @@ export function abbreviateClusterName(input) {
export function labelForAddon(name, configuration = true) {
const addon = camelToTitle(name.replace(/^(rke|rke2|rancher)-/, ''));
- const fallback = `${ addon } ${ configuration ? 'Configuration' : '' }`;
+ const fallback = `${ configuration ? '' : 'Add-on: ' }${ addon }`;
const key = `cluster.addonChart."${ name }"${ configuration ? '.configuration' : '.label' }`;
return this.$store.getters['i18n/withFallback'](key, null, fallback);
diff --git a/shell/utils/v-sphere.ts b/shell/utils/v-sphere.ts
new file mode 100644
index 00000000000..e37ca189d75
--- /dev/null
+++ b/shell/utils/v-sphere.ts
@@ -0,0 +1,237 @@
+import merge from 'lodash/merge';
+import { SECRET } from '@shell/config/types';
+
+type Rke2Component = {
+ versionInfo: any;
+ userChartValues: any;
+ chartVersionKey: (chartName: string) => string;
+ value: any;
+ isEdit: boolean;
+ $store: any,
+}
+
+type SecretDetails = {
+ generateName: string,
+ upstreamClusterName: string,
+ upstreamNamespace: string,
+ downstreamName: string,
+ downstreamNamespace: string,
+ json?: object,
+}
+type Values = any;
+
+type ChartValues = {
+ defaultValues: Values,
+ userValues: Values,
+ combined: Values,
+};
+
+const rootGenerateName = 'vsphere-secret-';
+
+type SecretJson = any;
+
+class VSphereUtils {
+ private async findSecret(
+ { $store }: Rke2Component, {
+ generateName, upstreamClusterName, upstreamNamespace, downstreamName, downstreamNamespace
+ }: SecretDetails): Promise {
+ const secrets = await $store.dispatch('management/request', { url: `/v1/${ SECRET }/${ upstreamNamespace }?filter=metadata.name=${ generateName }` });
+
+ const applicableSecret = secrets.data?.filter((s: any) => {
+ return s.metadata.annotations['provisioning.cattle.io/sync-target-namespace'] === downstreamNamespace &&
+ s.metadata.annotations['provisioning.cattle.io/sync-target-name'] === downstreamName &&
+ s.metadata.annotations['rke.cattle.io/object-authorized-for-clusters'].includes(upstreamClusterName);
+ });
+
+ if (applicableSecret.length > 1) {
+ return Promise.reject(new Error(`Found multiple matching secrets (${ upstreamNamespace }/${ upstreamNamespace } for ${ upstreamClusterName }), this will cause synchronizing mishaps. Consider removing stale secrets from old clusters`));
+ }
+
+ return applicableSecret[0];
+ }
+
+ private async findOrCreateSecret(
+ rke2Component: Rke2Component,
+ {
+ generateName, upstreamClusterName, upstreamNamespace, downstreamName, downstreamNamespace, json
+ }: SecretDetails
+ ) {
+ const { $store } = rke2Component;
+
+ const secretJson = await this.findSecret(rke2Component, {
+ generateName,
+ upstreamClusterName,
+ upstreamNamespace,
+ downstreamName,
+ downstreamNamespace
+ }) || json;
+
+ return await $store.dispatch('management/create', secretJson);
+ }
+
+ private findChartValues({
+ versionInfo,
+ userChartValues,
+ chartVersionKey,
+ }: Rke2Component, chartName: string): ChartValues | undefined {
+ const chartValues = versionInfo[chartName]?.values;
+
+ if (!chartValues) {
+ return;
+ }
+ const userValues = userChartValues[chartVersionKey(chartName)];
+
+ return {
+ defaultValues: chartValues,
+ userValues,
+ combined: merge({}, chartValues || {}, userValues || {})
+ };
+ }
+
+ /**
+ * Create upstream vsphere cpi secret to sync downstream
+ */
+ async handleVsphereCpiSecret(rke2Component: Rke2Component) {
+ const generateName = `${ rootGenerateName }cpi-`;
+ const downstreamName = 'vsphere-cpi-creds';
+ const downstreamNamespace = 'kube-system';
+ const { value } = rke2Component;
+
+ // check values for cpi chart has 'use our method' checkbox
+ const { userValues, combined } = this.findChartValues(rke2Component, 'rancher-vsphere-cpi') || {};
+
+ if (!combined?.vCenter?.credentialsSecret?.generate) {
+ return;
+ }
+
+ // find values needed in cpi chart value - https://github.com/rancher/vsphere-charts/blob/main/charts/rancher-vsphere-cpi/questions.yaml#L16-L42
+ const { username, password, host } = combined.vCenter;
+
+ if (!username || !password || !host) {
+ throw new Error('vSphere CPI username, password and host are all required when generating a new secret');
+ }
+
+ // create secret as per https://github.com/rancher/vsphere-charts/blob/main/charts/rancher-vsphere-cpi/templates/secret.yaml
+ const upstreamClusterName = value.metadata.name;
+ const upstreamNamespace = value.metadata.namespace;
+ const secret = await this.findOrCreateSecret(rke2Component, {
+ generateName,
+ upstreamClusterName,
+ upstreamNamespace,
+ downstreamName,
+ downstreamNamespace,
+ json: {
+ type: SECRET,
+ metadata: {
+ namespace: upstreamNamespace,
+ generateName,
+ labels: {
+ 'vsphere-cpi-infra': 'secret',
+ component: 'rancher-vsphere-cpi-cloud-controller-manager'
+ },
+ annotations: {
+ 'provisioning.cattle.io/sync-target-namespace': downstreamNamespace,
+ 'provisioning.cattle.io/sync-target-name': downstreamName,
+ 'rke.cattle.io/object-authorized-for-clusters': upstreamClusterName,
+ 'provisioning.cattle.io/sync-bootstrap': 'true'
+ }
+ },
+ }
+ });
+
+ secret.setData(`${ host }.username`, username);
+ secret.setData(`${ host }.password`, password);
+
+ await secret.save();
+
+ // reset cpi chart values
+ if (!userValues.vCenter.credentialsSecret) {
+ userValues.vCenter.credentialsSecret = {};
+ }
+ userValues.vCenter.credentialsSecret.generate = false;
+ userValues.vCenter.credentialsSecret.name = downstreamName;
+ userValues.vCenter.username = '';
+ userValues.vCenter.password = '';
+ }
+
+ /**
+ * Create upstream vsphere csi secret to sync downstream
+ */
+ async handleVsphereCsiSecret(rke2Component: Rke2Component) {
+ const generateName = `${ rootGenerateName }csi-`;
+ const downstreamName = 'vsphere-csi-creds';
+ const downstreamNamespace = 'kube-system';
+ const { value } = rke2Component;
+
+ // check values for cpi chart has 'use our method' checkbox
+ const { userValues, combined } = this.findChartValues(rke2Component, 'rancher-vsphere-csi') || {};
+
+ if (!combined?.vCenter?.configSecret?.generate) {
+ return;
+ }
+
+ // find values needed in cpi chart value - https://github.com/rancher/vsphere-charts/blob/main/charts/rancher-vsphere-csi/questions.yaml#L1-L36
+ const {
+ username, password, host, datacenters, port, insecureFlag
+ } = combined.vCenter;
+
+ if (!username || !password || !host || !datacenters) {
+ throw new Error('vSphere CSI username, password, host and datacenters are all required when generating a new secret');
+ }
+
+ // This is a copy of https://github.com/rancher/vsphere-charts/blob/a5c99d716df960dc50cf417d9ecffad6b55ca0ad/charts/rancher-vsphere-csi/values.yaml#L12-L21
+ // Which makes it's way into the secret via https://github.com/rancher/vsphere-charts/blob/main/charts/rancher-vsphere-csi/templates/secret.yaml#L8
+ let configTemplateString = ' |\n [Global]\n cluster-id = {{ required \".Values.vCenter.clusterId must be provided\" (default .Values.vCenter.clusterId .Values.global.cattle.clusterId) | quote }}\n user = {{ .Values.vCenter.username | quote }}\n password = {{ .Values.vCenter.password | quote }}\n port = {{ .Values.vCenter.port | quote }}\n insecure-flag = {{ .Values.vCenter.insecureFlag | quote }}\n\n [VirtualCenter {{ .Values.vCenter.host | quote }}]\n datacenters = {{ .Values.vCenter.datacenters | quote }}';
+
+ configTemplateString = configTemplateString.replace('{{ required \".Values.vCenter.clusterId must be provided\" (default .Values.vCenter.clusterId .Values.global.cattle.clusterId) | quote }}', `"{{clusterId}}"`);
+ configTemplateString = configTemplateString.replace('{{ .Values.vCenter.username | quote }}', `"${ username }"`);
+ configTemplateString = configTemplateString.replace('{{ .Values.vCenter.password | quote }}', `"${ password }"`);
+ configTemplateString = configTemplateString.replace('{{ .Values.vCenter.port | quote }}', `"${ port }"`);
+ configTemplateString = configTemplateString.replace('{{ .Values.vCenter.insecureFlag | quote }}', `"${ insecureFlag }"`);
+ configTemplateString = configTemplateString.replace('{{ .Values.vCenter.host | quote }}', `"${ host }"`);
+ configTemplateString = configTemplateString.replace('{{ .Values.vCenter.datacenters | quote }}', `"${ datacenters }"`);
+ // create secret as per https://github.com/rancher/vsphere-charts/blob/main/charts/rancher-vsphere-csi/templates/secret.yaml
+ const upstreamClusterName = value.metadata.name;
+ const upstreamNamespace = value.metadata.namespace;
+
+ const secret = await this.findOrCreateSecret(rke2Component, {
+ generateName,
+ upstreamClusterName,
+ upstreamNamespace,
+ downstreamName,
+ downstreamNamespace,
+ json: {
+ type: SECRET,
+ metadata: {
+ namespace: upstreamNamespace,
+ generateName,
+ annotations: {
+ 'provisioning.cattle.io/sync-target-namespace': downstreamNamespace,
+ 'provisioning.cattle.io/sync-target-name': downstreamName,
+ 'rke.cattle.io/object-authorized-for-clusters': upstreamClusterName,
+ 'provisioning.cattle.io/sync-bootstrap': 'true'
+ }
+ },
+ }
+ });
+
+ secret.setData(`csi-vsphere.conf`, configTemplateString);
+
+ await secret.save();
+
+ // reset csi chart values
+ if (!userValues.vCenter.configSecret) {
+ userValues.vCenter.configSecret = {};
+ }
+ userValues.vCenter.configSecret.generate = false;
+ userValues.vCenter.configSecret.name = downstreamName;
+ userValues.vCenter.username = '';
+ userValues.vCenter.password = '';
+ userValues.vCenter.host = '';
+ userValues.vCenter.datacenters = '';
+ }
+}
+
+const utils = new VSphereUtils();
+
+export default utils;