dashboard/shell/utils/v-sphere.ts

290 lines
11 KiB
TypeScript

import merge from 'lodash/merge';
import { SECRET } from '@shell/config/types';
import { PROVISIONING_PRE_BOOTSTRAP } from '@shell/store/features';
export const VMWARE_VSPHERE = 'vmwarevsphere';
type Rke2Component = {
versionInfo: any;
userChartValues: any;
chartVersionKey: (chartName: string) => string;
value: any;
isEdit: boolean;
provider: string;
$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<SecretJson | undefined> {
// Fetch secrets in a specific namespace and partially matching the name to the generate name
const url = $store.getters['management/urlOptions'](
`/v1/${ SECRET }/${ upstreamNamespace }`,
{ filter: { 'metadata.name': generateName } }
);
const secrets = await $store.dispatch('management/request', { url });
// Filter by specific annotations
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 there's more than one the user should tidy up... as it'll cause mayhem with the actual sync from local --> downstream cluster
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 || {})
};
}
/**
* Check that system is setup to handle vsphere secrets syncing downstream
*
* Do this via checking the provider and that the required FF is enabled.
*/
private handleVsphereSecret({ $store, provider }: { $store: any, provider: string}): boolean {
if (provider !== VMWARE_VSPHERE) {
return false;
}
const isPrebootstrapEnabled = $store.getters['features/get'](PROVISIONING_PRE_BOOTSTRAP);
if (!isPrebootstrapEnabled) {
return false;
}
return true;
}
/**
* Create upstream vsphere cpi secret to sync downstream
*/
async handleVsphereCpiSecret(rke2Component: Rke2Component) {
if (!this.handleVsphereSecret(rke2Component)) {
return;
}
const generateName = `${ rootGenerateName }cpi-`;
const downstreamName = 'rancher-vsphere-cpi-credentials';
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) {
if (userValues?.vCenter?.username) {
userValues.vCenter.username = '';
}
if (userValues?.vCenter?.password) {
userValues.vCenter.password = '';
}
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) {
if (!this.handleVsphereSecret(rke2Component)) {
return;
}
const generateName = `${ rootGenerateName }csi-`;
const downstreamName = 'rancher-vsphere-csi-credentials';
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) {
if (userValues?.vCenter?.username) {
userValues.vCenter.username = '';
}
if (userValues?.vCenter?.password) {
userValues.vCenter.password = '';
}
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 = ' [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;