From 31e0a7973efb80b65ad56f76de5ab891adf36d7a Mon Sep 17 00:00:00 2001 From: Vincent Fiduccia Date: Tue, 15 Jun 2021 03:11:23 -0700 Subject: [PATCH] Generic cloud credential and machine drivers --- assets/translations/en-us.yaml | 2 + cloud-credential/generic.vue | 75 ++++++++++++ components/Questions/index.vue | 16 +-- .../MachinePool.vue | 9 +- .../SelectCredential.vue | 19 +-- edit/provisioning.cattle.io.cluster/index.vue | 11 +- edit/provisioning.cattle.io.cluster/rke2.vue | 2 +- edit/secret/index.vue | 48 ++++++-- machine-config/generic.vue | 103 ++++++++++++++++ models/secret.js | 51 +++++++- pages/c/_cluster/apps/charts/install.vue | 2 +- pages/c/_cluster/manager/secret.vue | 14 ++- store/plugins.js | 114 +++++++++++++++++- 13 files changed, 417 insertions(+), 49 deletions(-) create mode 100644 cloud-credential/generic.vue create mode 100644 machine-config/generic.vue diff --git a/assets/translations/en-us.yaml b/assets/translations/en-us.yaml index d624f74d31..d067689453 100644 --- a/assets/translations/en-us.yaml +++ b/assets/translations/en-us.yaml @@ -1106,6 +1106,8 @@ cluster: rke2-ingress-nginx: 'NGINX Ingress Controller' rke2-kube-proxy: 'Kube Proxy' rke2-metrics-server: 'Metrics Server' + selectCredential: + genericDescription: "{vendor} has no built-in support for this driver. We've taken a guess, but consult the driver's documentation for the fields required for authentication." tabs: ace: Authorized Endpoint advanced: Advanced diff --git a/cloud-credential/generic.vue b/cloud-credential/generic.vue new file mode 100644 index 0000000000..b247e8da57 --- /dev/null +++ b/cloud-credential/generic.vue @@ -0,0 +1,75 @@ + + + diff --git a/components/Questions/index.vue b/components/Questions/index.vue index 2ce3b38d33..b29c91f597 100644 --- a/components/Questions/index.vue +++ b/components/Questions/index.vue @@ -39,15 +39,15 @@ export function componentForQuestion(q) { return 'string'; } -export function schemaToQuestions(schema) { - const keys = Object.keys(schema.resourceFields); +export function schemaToQuestions(fields) { + const keys = Object.keys(fields); const out = []; for ( const k of keys ) { out.push({ variable: k, label: k, - ...schema.resourceFields[k], + ...fields[k], }); } @@ -192,12 +192,14 @@ export default { computed: { allQuestions() { - if ( this.source.type === 'schema' ) { - return schemaToQuestions(this.source); - } else if ( this.source.questions?.questions ) { + if ( this.source.questions?.questions ) { return this.chartVersion.questions.questions; + } else if ( this.source.type === 'schema' && this.source.resourceFields ) { + return schemaToQuestions(this.source.resourceFields); + } else if ( typeof this.source === 'object' ) { + return schemaToQuestions(this.source); } else { - throw new Error('Must specify sourec as a chartVersion or Schema resource'); + return []; } }, diff --git a/edit/provisioning.cattle.io.cluster/MachinePool.vue b/edit/provisioning.cattle.io.cluster/MachinePool.vue index 636cefd6d2..751d135938 100644 --- a/edit/provisioning.cattle.io.cluster/MachinePool.vue +++ b/edit/provisioning.cattle.io.cluster/MachinePool.vue @@ -47,7 +47,13 @@ export default { computed: { configComponent() { - return importMachineConfig(this.provider); + const haveProviders = this.$store.getters['plugins/machineDrivers']; + + if ( haveProviders.includes(this.provider) ) { + return importMachineConfig(this.provider); + } + + return importMachineConfig('generic'); } }, }; @@ -84,6 +90,7 @@ export default { :uuid="uuid" :mode="mode" :value="value.config" + :provider="provider" :credential-id="credentialId" @error="e=>errors = e" /> diff --git a/edit/provisioning.cattle.io.cluster/SelectCredential.vue b/edit/provisioning.cattle.io.cluster/SelectCredential.vue index b980f7767f..f2a887ec3f 100644 --- a/edit/provisioning.cattle.io.cluster/SelectCredential.vue +++ b/edit/provisioning.cattle.io.cluster/SelectCredential.vue @@ -85,16 +85,9 @@ export default { driverName() { let driver = this.provider; - const azureDrivers = ['azure', 'aks']; // Map providers that share a common credential to one driver - if ( driver === 'amazonec2' || driver === 'amazoneks' ) { - driver = 'aws'; - } - - if ( azureDrivers.includes(driver) ) { - driver = 'azure'; - } + driver = this.$store.getters['plugins/credentialDriverFor'](driver); return driver; }, @@ -135,7 +128,13 @@ export default { }, createComponent() { - return importCloudCredential(this.driverName); + const haveDrivers = this.$store.getters['plugins/credentialDrivers']; + + if ( haveDrivers.includes(this.driverName) ) { + return importCloudCredential(this.driverName); + } + + return importCloudCredential('generic'); }, validationPassed() { @@ -244,6 +243,8 @@ export default { :is="createComponent" ref="create" v-model="newCredential" + mode="create" + :driver-name="driverName" @validationChanged="createValidationChanged" /> diff --git a/edit/provisioning.cattle.io.cluster/index.vue b/edit/provisioning.cattle.io.cluster/index.vue index c8559a3a26..c59ab907ac 100644 --- a/edit/provisioning.cattle.io.cluster/index.vue +++ b/edit/provisioning.cattle.io.cluster/index.vue @@ -175,11 +175,8 @@ export default { const out = []; const templates = this.templateOptions; - const vueMachineTypes = getters['plugins/machineDrivers']; const vueKontainerTypes = getters['plugins/clusterDrivers']; - const machineTypes = this.nodeDrivers.filter(x => x.spec.active).map((x) => { - return !x.spec.builtin ? x.spec.displayName : x.id; - }); + const machineTypes = this.nodeDrivers.filter(x => x.spec.active && x.state === 'active').map(x => x.spec.displayName || x.id); this.kontainerDrivers.filter(x => (isImport ? x.showImport : x.showCreate)).forEach((obj) => { if ( vueKontainerTypes.includes(obj.driverName) ) { @@ -210,7 +207,7 @@ export default { addType('custom', 'custom1', false, '/g/clusters/add/launch/custom'); } else { machineTypes.forEach((id) => { - addType(id, 'rke2', !vueMachineTypes.includes(id)); + addType(id, 'rke2', false); }); addType('custom', 'custom2', false); @@ -263,6 +260,10 @@ export default { entry.types.push(row); } + for ( const k in out ) { + out[k].types = sortBy(out[k].types, 'label'); + } + return sortBy(Object.values(out), 'sort'); }, }, diff --git a/edit/provisioning.cattle.io.cluster/rke2.vue b/edit/provisioning.cattle.io.cluster/rke2.vue index d84cc28e69..4378ed4eb0 100644 --- a/edit/provisioning.cattle.io.cluster/rke2.vue +++ b/edit/provisioning.cattle.io.cluster/rke2.vue @@ -1157,7 +1157,7 @@ export default { v-if="versionInfo[v.name].questions" v-model="chartValues[v.name]" :mode="mode" - :chart-version="versionInfo[v.name]" + :source="versionInfo[v.name]" :target-namespace="value.metadata.namespace" /> import { TYPES } from '@/models/secret'; -import { NAMESPACE } from '@/config/types'; +import { MANAGEMENT, NAMESPACE } from '@/config/types'; import CreateEditView from '@/mixins/create-edit-view'; import NameNsDescription from '@/components/form/NameNsDescription'; import CruResource from '@/components/CruResource'; import { CLOUD_CREDENTIAL, _CREATE, _EDIT, _FLAGGED } from '@/config/query-params'; +import Loading from '@/components/Loading'; import Tabbed from '@/components/Tabbed'; import Tab from '@/components/Tabbed/Tab'; import Labels from '@/components/form/Labels'; import { HIDE_SENSITIVE } from '@/store/prefs'; import { CAPI } from '@/config/labels-annotations'; -import { clear } from '@/utils/array'; +import { clear, uniq } from '@/utils/array'; import { importCloudCredential } from '@/utils/dynamic-importer'; import { NAME as MANAGER } from '@/config/product/manager'; import SelectIconGrid from '@/components/SelectIconGrid'; import { DEFAULT_WORKSPACE } from '@/models/provisioning.cattle.io.cluster'; +import { sortBy } from '@/utils/sort'; +import { ucFirst } from '@/utils/string'; const creatableTypes = [ TYPES.OPAQUE, @@ -28,6 +31,7 @@ export default { name: 'CruSecret', components: { + Loading, NameNsDescription, CruResource, Tabbed, @@ -38,6 +42,12 @@ export default { mixins: [CreateEditView], + async fetch() { + if ( this.isCloud ) { + this.nodeDrivers = await this.$store.dispatch('management/findAll', { type: MANAGEMENT.NODE_DRIVER }); + } + }, + data() { const newCloudCred = this.$route.query[CLOUD_CREDENTIAL] === _FLAGGED; const editCloudCred = this.mode === _EDIT && this.value._type === TYPES.CLOUD_CREDENTIAL; @@ -47,7 +57,11 @@ export default { this.value.metadata.namespace = DEFAULT_WORKSPACE; } - return { isCloud }; + return { + isCloud, + nodeDrivers: null, + + }; }, computed: { @@ -70,14 +84,21 @@ export default { return require(`@/edit/secret/${ this.typeKey }`).default; }, - cloudComponent() { + driverName() { const driver = this.value.metadata?.annotations?.[CAPI.CREDENTIAL_DRIVER]; - if ( driver ) { + return driver; + }, + + cloudComponent() { + const driver = this.driverName; + const haveProviders = this.$store.getters['plugins/credentialDrivers']; + + if ( haveProviders.includes(driver) ) { return importCloudCredential(driver); } - return null; + return importCloudCredential('generic'); }, // array of id, label, description, initials for type selection step @@ -86,7 +107,13 @@ export default { // Cloud credentials if ( this.isCloud ) { - for ( const id of this.$store.getters['plugins/credentialDrivers'] ) { + const machineTypes = uniq(this.nodeDrivers + .filter(x => x.spec.active) + .map(x => x.spec.displayName || x.id) + .map(x => this.$store.getters['plugins/credentialDriverFor'](x)) + ); + + for ( const id of machineTypes ) { let bannerImage, bannerAbbrv; try { @@ -114,7 +141,7 @@ export default { } } - return out; + return sortBy(out, 'label'); }, namespaces() { @@ -206,7 +233,7 @@ export default { }, initialDisplayFor(type) { - const fallback = (this.typeDisplay(type) || '').replace(/[^A-Z]/g, '') || type; + const fallback = (ucFirst(this.typeDisplay(type) || '').replace(/[^A-Z]/g, '') || type).substr(0, 3); return this.$store.getters['i18n/withFallback'](`secret.initials."${ type }"`, null, fallback); }, @@ -216,7 +243,9 @@ export default {