From 694b0eef40e4286b589010d6ef0fccba3afa4aae Mon Sep 17 00:00:00 2001 From: Alexandre Alves Date: Tue, 26 Mar 2024 17:34:11 +0000 Subject: [PATCH 1/3] working on feature versioning mechanism --- package.json | 4 +- pkg/elemental/components/BuildMedia.vue | 49 +++++++++-- pkg/elemental/components/DashboardView.vue | 8 +- ...lemental.cattle.io.machineregistration.vue | 6 ++ ...lemental.cattle.io.machineregistration.vue | 33 ++++++- ...elemental.cattle.io.machineregistration.js | 16 +++- pkg/elemental/pages/index.vue | 5 +- pkg/elemental/utils/feature-versioning.ts | 86 +++++++++++++++++++ yarn.lock | 51 +++++++++-- 9 files changed, 235 insertions(+), 23 deletions(-) create mode 100644 pkg/elemental/utils/feature-versioning.ts diff --git a/package.json b/package.json index 1b7d30b..4379632 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,9 @@ "css-loader": "4.3.0" }, "devDependencies": { - "@types/node": "18.11.9" + "@types/node": "18.11.9", + "@types/semver": "^7.5.8", + "semver": "^7.6.0" }, "scripts": { "dev": "NODE_ENV=dev ./node_modules/.bin/vue-cli-service serve", diff --git a/pkg/elemental/components/BuildMedia.vue b/pkg/elemental/components/BuildMedia.vue index f196979..e95f864 100644 --- a/pkg/elemental/components/BuildMedia.vue +++ b/pkg/elemental/components/BuildMedia.vue @@ -4,6 +4,7 @@ import { Banner } from '@components/Banner'; import AsyncButton from '@shell/components/AsyncButton'; import { randomStr, CHARSET } from '@shell/utils/string'; import { ELEMENTAL_SCHEMA_IDS } from '../config/elemental-types'; +import { semverVersionCheck, getOperatorVersion, getGatedFeatures, BUILD_MEDIA_RAW_SUPPORT } from '../utils/feature-versioning'; const MEDIA_TYPES = { RAW: { @@ -38,17 +39,32 @@ export default { registrationEndpoint: { type: String, default: '' + }, + resource: { + type: String, + default: '' + }, + mode: { + type: String, + default: '' } }, async fetch() { this.seedImagesList = await this.$store.dispatch('management/findAll', { type: ELEMENTAL_SCHEMA_IDS.SEED_IMAGE }); this.managedOsVersions = await this.$store.dispatch('management/findAll', { type: ELEMENTAL_SCHEMA_IDS.MANAGED_OS_VERSIONS }); + + this.operatorVersion = await getOperatorVersion(this.$store); + this.gatedFeatures = getGatedFeatures(this.resource, this.mode, BUILD_MEDIA_RAW_SUPPORT); + this.buildMediaGatingVersion = this.gatedFeatures?.[0]?.minOperatorVersion || ''; }, data() { return { seedImagesList: [], managedOsVersions: [], filteredManagedOsVersions: [], + operatorVersion: '', + gatedFeatures: [], + buildMediaGatingVersion: '', buildMediaOsVersions: [], buildMediaTypes: [ { label: MEDIA_TYPES.ISO.label, value: MEDIA_TYPES.ISO.type }, @@ -79,6 +95,19 @@ export default { } }, computed: { + isRawDiskImageBuildSupported() { + if (this.operatorVersion && this.buildMediaGatingVersion) { + const check = semverVersionCheck(this.operatorVersion, this.buildMediaGatingVersion); + + if (!check) { + this.buildMediaTypeSelected = MEDIA_TYPES.ISO.type; // eslint-disable-line vue/no-side-effects-in-computed-properties + } + + return check; + } + + return false; + }, registrationEndpointsOptions() { const activeRegEndpoints = this.registrationEndpointList.filter(item => item.state === 'active'); @@ -91,10 +120,10 @@ export default { }, isBuildMediaBtnEnabled() { if (this.displayRegEndpoints) { - return this.registrationEndpointSelected && this.buildMediaOsVersionSelected && this.buildMediaTypeSelected; + return this.isRawDiskImageBuildSupported ? this.registrationEndpointSelected && this.buildMediaOsVersionSelected && this.buildMediaTypeSelected : this.registrationEndpointSelected && this.buildMediaOsVersionSelected; } - return this.buildMediaOsVersionSelected && this.buildMediaTypeSelected; + return this.isRawDiskImageBuildSupported ? this.buildMediaOsVersionSelected && this.buildMediaTypeSelected : this.buildMediaOsVersionSelected; }, seedImageFound() { if (this.seedImage) { @@ -146,13 +175,12 @@ export default { const machineRegName = this.displayRegEndpoints ? this.registrationEndpointSelected.split('/')[1] : this.registrationEndpoint.split('/')[1]; const machineRegNamespace = this.displayRegEndpoints ? this.registrationEndpointSelected.split('/')[0] : this.registrationEndpoint.split('/')[0]; - const seedImageModel = await this.$store.dispatch('management/create', { + const seedImageObject = { metadata: { name: `media-image-reg-${ machineRegName }-${ randomStr(8, CHARSET.ALPHA_LOWER ) }`, namespace: 'fleet-default' }, spec: { - type: this.buildMediaTypeSelected, baseImage: this.buildMediaOsVersionSelected, registrationRef: { name: machineRegName, @@ -160,7 +188,13 @@ export default { } }, type: ELEMENTAL_SCHEMA_IDS.SEED_IMAGE, - }); + }; + + if (this.isRawDiskImageBuildSupported) { + seedImageObject.spec.type = this.buildMediaTypeSelected; + } + + const seedImageModel = await this.$store.dispatch('management/create', seedImageObject); try { this.seedImage = await seedImageModel.save({ url: `/v1/${ ELEMENTAL_SCHEMA_IDS.SEED_IMAGE }`, method: 'POST' }); @@ -207,7 +241,10 @@ export default { :options="registrationEndpointsOptions" /> -
+
import { allHash } from '@shell/utils/promise'; import { CAPI, CATALOG } from '@shell/config/types'; +import { _VIEW } from '@shell/config/query-params'; import { NAME } from '@shell/config/table-headers'; import ResourceTable from '@shell/components/ResourceTable'; import PercentageBar from '@shell/components/PercentageBar'; @@ -10,6 +11,7 @@ import { ELEMENTAL_CLUSTER_PROVIDER, KIND } from '../config/elemental-types'; +import { ELEMENTAL_TYPES } from '../types'; import { createElementalRoute } from '../utils/custom-routing'; import { filterForElementalClusters } from '../utils/elemental-utils'; import BuildMedia from './BuildMedia'; @@ -54,7 +56,7 @@ export default { // we need to check for the length of the response // due to some issue with a standard-user, which can list apps // but the list comes up empty [] - const isElementalOperatorNotInstalledOnApps = allDispatches.installedApps && allDispatches.installedApps.length && !allDispatches.installedApps.find(item => item.id.includes('elemental-operator')); + const isElementalOperatorNotInstalledOnApps = allDispatches.installedApps && allDispatches.installedApps.length && !allDispatches.installedApps.find(item => item.id.includes('elemental-operator') && !item.id.includes('elemental-operator-crds')); // check if CRD is there but operator isn't if (allDispatches.elementalSchema && isElementalOperatorNotInstalledOnApps) { @@ -65,6 +67,8 @@ export default { return { ELEMENTAL_CLUSTERS: 'elementalClusters', isElementalOpNotInstalledAndHasSchema: false, + resource: ELEMENTAL_TYPES.DASHBOARD, + mode: _VIEW, resourcesData: { [`${ ELEMENTAL_SCHEMA_IDS.MACHINE_REGISTRATIONS }`]: [], [`${ ELEMENTAL_SCHEMA_IDS.MACHINE_INVENTORIES }`]: [], @@ -283,6 +287,8 @@ export default {
diff --git a/pkg/elemental/detail/elemental.cattle.io.machineregistration.vue b/pkg/elemental/detail/elemental.cattle.io.machineregistration.vue index 2f8b940..15a39b5 100644 --- a/pkg/elemental/detail/elemental.cattle.io.machineregistration.vue +++ b/pkg/elemental/detail/elemental.cattle.io.machineregistration.vue @@ -32,6 +32,10 @@ export default { type: String, required: true }, + resource: { + type: String, + required: true + }, }, data() { return { @@ -115,6 +119,8 @@ export default {

{{ t('elemental.machineRegistration.create.cloudConfiguration') }}

item.id.includes('elemental-operator')); + const isElementalOperatorNotInstalledOnApps = installedApps?.length && !installedApps?.find(item => item.id.includes('elemental-operator') && !item.id.includes('elemental-operator-crds')); // check if operator is installed if (!elementalSchema || isElementalOperatorNotInstalledOnApps) { diff --git a/pkg/elemental/utils/feature-versioning.ts b/pkg/elemental/utils/feature-versioning.ts new file mode 100644 index 0000000..8b1804f --- /dev/null +++ b/pkg/elemental/utils/feature-versioning.ts @@ -0,0 +1,86 @@ +import semver from 'semver'; + +import { _CREATE, _EDIT, _VIEW } from '@shell/config/query-params'; +import { CATALOG } from '@shell/config/types'; +import { ELEMENTAL_SCHEMA_IDS } from '../config/elemental-types'; +import { ELEMENTAL_TYPES } from '../types'; + +interface FeaturesGatingConfig { + area: string, + mode: string[], + minOperatorVersion: string, + features: string[], +} + +export const MACH_REG_CONFIG_DEFAULTS:string = 'machine-reg-config-defaults'; +export const BUILD_MEDIA_RAW_SUPPORT:string = 'build-media-raw-support'; + +const FEATURES_GATING:FeaturesGatingConfig[] = [ + { + area: ELEMENTAL_SCHEMA_IDS.MACHINE_REGISTRATIONS, + mode: [_CREATE], + minOperatorVersion: '103.0.0', + features: [MACH_REG_CONFIG_DEFAULTS] + }, + { + area: ELEMENTAL_TYPES.DASHBOARD, + mode: [_VIEW], + minOperatorVersion: '103.0.0', + features: [BUILD_MEDIA_RAW_SUPPORT] + }, + { + area: ELEMENTAL_SCHEMA_IDS.MACHINE_REGISTRATIONS, + mode: [_VIEW], + minOperatorVersion: '103.0.0', + features: [BUILD_MEDIA_RAW_SUPPORT] + } +]; + +/** + * Get the current elemental-operator version + * @param any store + * @param any alreadyInstalledApps + * @returns Promise + */ +export async function getOperatorVersion(store: any, alreadyInstalledApps:any = null, isRoot:Boolean = false): Promise { + if (alreadyInstalledApps) { + const operator = alreadyInstalledApps?.find((item: any) => item.id.includes('elemental-operator') && !item.id.includes('elemental-operator-crds')); + + return operator?.versionDisplay; + } + + // needed to check if operator is installed + if (store.getters['management/canList'](CATALOG.APP)) { + const installedApps = await store.dispatch('management/findAll', { type: CATALOG.APP }, { root: isRoot }); + const operator = installedApps?.find((item: any) => item.id.includes('elemental-operator') && !item.id.includes('elemental-operator-crds')); + + return operator?.versionDisplay; + } + + return ''; +} + +/** + * Get all of the gated features based on resource + mode + string + * @param string + * @param string + * @param string + * @returns FeaturesGatingConfig[] | [] | void + */ +export function getGatedFeatures(resource: string, mode: string, feature: string): FeaturesGatingConfig[] | [] | void { + if (resource && mode) { + return FEATURES_GATING.filter(feat => feat.area === resource && feat.mode.includes(mode) && feat.features.includes(feature)); + } + + return []; +} + +/** + * Determines if a given feature is enabled by doing a semver version comparison + * @param string + * @param string + * @returns Boolean | void + */ +export function semverVersionCheck(operatorVersion: string, limitVersion: string): Boolean | void { + return semver.gte(operatorVersion, limitVersion); +} diff --git a/yarn.lock b/yarn.lock index 47662b7..dcc4c25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2969,10 +2969,10 @@ dependencies: lodash.debounce "4.0.8" -"@rancher/shell@0.5.1": - version "0.5.1" - resolved "https://registry.yarnpkg.com/@rancher/shell/-/shell-0.5.1.tgz#4ee59f4f60b6bba54995f00e289dcd64d97f162a" - integrity sha512-oCmXZ9MDw9jh7plGmYT9ZnB2gHqs5FLgB22HEgUl46R7t94Tms3Z5VivN1JMS77R++C3jdC/EVpjgisKslK4Pg== +"@rancher/shell@0.5.2": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@rancher/shell/-/shell-0.5.2.tgz#ab11c8aa03001ba36734ec94f333d48f56846cdd" + integrity sha512-u48zCEhimZKh7WIJDGo7dLbkIV/PbRUE88/hkiQ1+xi5gZRgqO/rFCfiALzJKl35d7cCTXqWNodJnTpvwRn8JA== dependencies: "@aws-sdk/client-ec2" "3.1.0" "@aws-sdk/client-eks" "3.1.0" @@ -3487,6 +3487,11 @@ resolved "https://registry.yarnpkg.com/@types/relateurl/-/relateurl-0.2.33.tgz#fa174c30100d91e88d7b0ba60cefd7e8c532516f" integrity sha512-bTQCKsVbIdzLqZhLkF5fcJQreE4y1ro4DIyVrlDNSCJRRwHhB8Z+4zXXa8jN6eDvc2HbRsEYgbvrnGvi54EpSw== +"@types/semver@^7.5.8": + version "7.5.8" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" + integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== + "@types/send@*": version "0.17.4" resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" @@ -14658,6 +14663,13 @@ semver@^7.0.0, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semve dependencies: lru-cache "^6.0.0" +semver@^7.6.0: + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + dependencies: + lru-cache "^6.0.0" + semver@~7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" @@ -15329,7 +15341,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -15347,6 +15359,15 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" +string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -15397,7 +15418,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -15418,6 +15439,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -17064,7 +17092,7 @@ worker-rpc@^0.1.0: dependencies: microevent.ts "~0.1.1" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -17091,6 +17119,15 @@ wrap-ansi@^6.0.0, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From f0bf9f7c6cea90a4ca07ab8c2a589c10d2e7068e Mon Sep 17 00:00:00 2001 From: Alexandre Alves Date: Fri, 5 Apr 2024 15:19:27 +0100 Subject: [PATCH 2/3] updates to feature version logic based on backend release --- pkg/elemental/components/BuildMedia.vue | 8 ++-- ...lemental.cattle.io.machineregistration.vue | 9 ++-- pkg/elemental/utils/feature-versioning.ts | 43 ++++++++----------- 3 files changed, 27 insertions(+), 33 deletions(-) diff --git a/pkg/elemental/components/BuildMedia.vue b/pkg/elemental/components/BuildMedia.vue index e95f864..67104a7 100644 --- a/pkg/elemental/components/BuildMedia.vue +++ b/pkg/elemental/components/BuildMedia.vue @@ -4,7 +4,7 @@ import { Banner } from '@components/Banner'; import AsyncButton from '@shell/components/AsyncButton'; import { randomStr, CHARSET } from '@shell/utils/string'; import { ELEMENTAL_SCHEMA_IDS } from '../config/elemental-types'; -import { semverVersionCheck, getOperatorVersion, getGatedFeatures, BUILD_MEDIA_RAW_SUPPORT } from '../utils/feature-versioning'; +import { semverVersionCheck, getOperatorVersion, getGatedFeature, BUILD_MEDIA_RAW_SUPPORT } from '../utils/feature-versioning'; const MEDIA_TYPES = { RAW: { @@ -54,8 +54,8 @@ export default { this.managedOsVersions = await this.$store.dispatch('management/findAll', { type: ELEMENTAL_SCHEMA_IDS.MANAGED_OS_VERSIONS }); this.operatorVersion = await getOperatorVersion(this.$store); - this.gatedFeatures = getGatedFeatures(this.resource, this.mode, BUILD_MEDIA_RAW_SUPPORT); - this.buildMediaGatingVersion = this.gatedFeatures?.[0]?.minOperatorVersion || ''; + this.gatedFeature = getGatedFeature(this.resource, this.mode, BUILD_MEDIA_RAW_SUPPORT); + this.buildMediaGatingVersion = this.gatedFeature?.minOperatorVersion || ''; }, data() { return { @@ -63,7 +63,7 @@ export default { managedOsVersions: [], filteredManagedOsVersions: [], operatorVersion: '', - gatedFeatures: [], + gatedFeature: {}, buildMediaGatingVersion: '', buildMediaOsVersions: [], buildMediaTypes: [ diff --git a/pkg/elemental/edit/elemental.cattle.io.machineregistration.vue b/pkg/elemental/edit/elemental.cattle.io.machineregistration.vue index aef398b..093c1aa 100644 --- a/pkg/elemental/edit/elemental.cattle.io.machineregistration.vue +++ b/pkg/elemental/edit/elemental.cattle.io.machineregistration.vue @@ -16,7 +16,7 @@ import { exceptionToErrorsArray } from '@shell/utils/error'; import Tabbed from '@shell/components/Tabbed/index.vue'; import Tab from '@shell/components/Tabbed/Tab.vue'; -import { semverVersionCheck, getOperatorVersion, getGatedFeatures, MACH_REG_CONFIG_DEFAULTS } from '../utils/feature-versioning'; +import { semverVersionCheck, getOperatorVersion, getGatedFeature, MACH_REG_CONFIG_DEFAULTS } from '../utils/feature-versioning'; import { OLD_DEFAULT_CREATION_YAML, DEFAULT_CREATION_YAML } from '../models/elemental.cattle.io.machineregistration'; export default { @@ -51,8 +51,8 @@ export default { // in CREATE mode, since YAMLEditor doesn't live update, we need to force a re-render of the component for it to update if (this.mode === _CREATE) { const operatorVersion = await getOperatorVersion(this.$store); - const gatedFeatures = getGatedFeatures(this.resource, this.mode, MACH_REG_CONFIG_DEFAULTS); - const minOperatorVersion = gatedFeatures?.[0]?.minOperatorVersion || ''; + const gatedFeature = getGatedFeature(this.resource, this.mode, MACH_REG_CONFIG_DEFAULTS); + const minOperatorVersion = gatedFeature?.minOperatorVersion || ''; this.newCloudConfigcompatibilityCheck = semverVersionCheck(operatorVersion, minOperatorVersion); @@ -70,7 +70,8 @@ export default { cloudConfig: typeof this.value.spec === 'string' ? this.value.spec : saferDump(this.value.spec), newCloudConfigcompatibilityCheck: false, yamlErrors: null, - isFormValid: true + isFormValid: true, + gatedFeature: {} }; }, watch: { diff --git a/pkg/elemental/utils/feature-versioning.ts b/pkg/elemental/utils/feature-versioning.ts index 8b1804f..3e75c9f 100644 --- a/pkg/elemental/utils/feature-versioning.ts +++ b/pkg/elemental/utils/feature-versioning.ts @@ -1,7 +1,7 @@ import semver from 'semver'; -import { _CREATE, _EDIT, _VIEW } from '@shell/config/query-params'; -import { CATALOG } from '@shell/config/types'; +import { _CREATE, _VIEW } from '@shell/config/query-params'; +import { WORKLOAD_TYPES } from '@shell/config/types'; import { ELEMENTAL_SCHEMA_IDS } from '../config/elemental-types'; import { ELEMENTAL_TYPES } from '../types'; @@ -19,19 +19,19 @@ const FEATURES_GATING:FeaturesGatingConfig[] = [ { area: ELEMENTAL_SCHEMA_IDS.MACHINE_REGISTRATIONS, mode: [_CREATE], - minOperatorVersion: '103.0.0', + minOperatorVersion: '1.6.0', features: [MACH_REG_CONFIG_DEFAULTS] }, { area: ELEMENTAL_TYPES.DASHBOARD, mode: [_VIEW], - minOperatorVersion: '103.0.0', + minOperatorVersion: '1.6.0', features: [BUILD_MEDIA_RAW_SUPPORT] }, { area: ELEMENTAL_SCHEMA_IDS.MACHINE_REGISTRATIONS, mode: [_VIEW], - minOperatorVersion: '103.0.0', + minOperatorVersion: '1.6.0', features: [BUILD_MEDIA_RAW_SUPPORT] } ]; @@ -42,37 +42,30 @@ const FEATURES_GATING:FeaturesGatingConfig[] = [ * @param any alreadyInstalledApps * @returns Promise */ -export async function getOperatorVersion(store: any, alreadyInstalledApps:any = null, isRoot:Boolean = false): Promise { - if (alreadyInstalledApps) { - const operator = alreadyInstalledApps?.find((item: any) => item.id.includes('elemental-operator') && !item.id.includes('elemental-operator-crds')); +export async function getOperatorVersion(store: any): Promise { + // needed to check operator version installed (on the deployment) + if (store.getters['management/canList'](WORKLOAD_TYPES.DEPLOYMENT)) { + const elementalOperatorDeployment = await store.dispatch('management/find', { type: WORKLOAD_TYPES.DEPLOYMENT, id: 'cattle-elemental-system/elemental-operator' }); - return operator?.versionDisplay; + return elementalOperatorDeployment?.metadata?.labels?.['app.kubernetes.io/version'] || '0.1.0'; } - // needed to check if operator is installed - if (store.getters['management/canList'](CATALOG.APP)) { - const installedApps = await store.dispatch('management/findAll', { type: CATALOG.APP }, { root: isRoot }); - const operator = installedApps?.find((item: any) => item.id.includes('elemental-operator') && !item.id.includes('elemental-operator-crds')); - - return operator?.versionDisplay; - } - - return ''; + return '0.1.0'; } /** - * Get all of the gated features based on resource + mode + string + * Get the gated feature based on resource + mode + string * @param string * @param string * @param string - * @returns FeaturesGatingConfig[] | [] | void + * @returns FeaturesGatingConfig | {} | void */ -export function getGatedFeatures(resource: string, mode: string, feature: string): FeaturesGatingConfig[] | [] | void { +export function getGatedFeature(resource: string, mode: string, feature: string): FeaturesGatingConfig | {} | void { if (resource && mode) { - return FEATURES_GATING.filter(feat => feat.area === resource && feat.mode.includes(mode) && feat.features.includes(feature)); + return FEATURES_GATING.find(feat => feat.area === resource && feat.mode.includes(mode) && feat.features.includes(feature)); } - return []; + return {}; } /** @@ -81,6 +74,6 @@ export function getGatedFeatures(resource: string, mode: string, feature: string * @param string * @returns Boolean | void */ -export function semverVersionCheck(operatorVersion: string, limitVersion: string): Boolean | void { - return semver.gte(operatorVersion, limitVersion); +export function semverVersionCheck(operatorVersion: string, operatorMinVersion: string): Boolean | void { + return semver.gte(operatorVersion, operatorMinVersion); } From b449e8ad0483fdcc7dbb7f353cc78943f884e05a Mon Sep 17 00:00:00 2001 From: Alexandre Alves Date: Mon, 8 Apr 2024 10:33:14 +0100 Subject: [PATCH 3/3] address pr comments --- pkg/elemental/components/BuildMedia.vue | 18 +++------ ...lemental.cattle.io.machineregistration.vue | 9 ++--- pkg/elemental/utils/feature-versioning.ts | 39 +++++++++---------- 3 files changed, 26 insertions(+), 40 deletions(-) diff --git a/pkg/elemental/components/BuildMedia.vue b/pkg/elemental/components/BuildMedia.vue index 67104a7..ec31103 100644 --- a/pkg/elemental/components/BuildMedia.vue +++ b/pkg/elemental/components/BuildMedia.vue @@ -4,7 +4,7 @@ import { Banner } from '@components/Banner'; import AsyncButton from '@shell/components/AsyncButton'; import { randomStr, CHARSET } from '@shell/utils/string'; import { ELEMENTAL_SCHEMA_IDS } from '../config/elemental-types'; -import { semverVersionCheck, getOperatorVersion, getGatedFeature, BUILD_MEDIA_RAW_SUPPORT } from '../utils/feature-versioning'; +import { getOperatorVersion, checkGatedFeatureCompatibility, BUILD_MEDIA_RAW_SUPPORT } from '../utils/feature-versioning'; const MEDIA_TYPES = { RAW: { @@ -54,8 +54,6 @@ export default { this.managedOsVersions = await this.$store.dispatch('management/findAll', { type: ELEMENTAL_SCHEMA_IDS.MANAGED_OS_VERSIONS }); this.operatorVersion = await getOperatorVersion(this.$store); - this.gatedFeature = getGatedFeature(this.resource, this.mode, BUILD_MEDIA_RAW_SUPPORT); - this.buildMediaGatingVersion = this.gatedFeature?.minOperatorVersion || ''; }, data() { return { @@ -63,8 +61,6 @@ export default { managedOsVersions: [], filteredManagedOsVersions: [], operatorVersion: '', - gatedFeature: {}, - buildMediaGatingVersion: '', buildMediaOsVersions: [], buildMediaTypes: [ { label: MEDIA_TYPES.ISO.label, value: MEDIA_TYPES.ISO.type }, @@ -96,17 +92,13 @@ export default { }, computed: { isRawDiskImageBuildSupported() { - if (this.operatorVersion && this.buildMediaGatingVersion) { - const check = semverVersionCheck(this.operatorVersion, this.buildMediaGatingVersion); + const check = checkGatedFeatureCompatibility(this.resource, this.mode, BUILD_MEDIA_RAW_SUPPORT, this.operatorVersion); - if (!check) { - this.buildMediaTypeSelected = MEDIA_TYPES.ISO.type; // eslint-disable-line vue/no-side-effects-in-computed-properties - } - - return check; + if (!check) { + this.buildMediaTypeSelected = MEDIA_TYPES.ISO.type; // eslint-disable-line vue/no-side-effects-in-computed-properties } - return false; + return check; }, registrationEndpointsOptions() { const activeRegEndpoints = this.registrationEndpointList.filter(item => item.state === 'active'); diff --git a/pkg/elemental/edit/elemental.cattle.io.machineregistration.vue b/pkg/elemental/edit/elemental.cattle.io.machineregistration.vue index 093c1aa..88f74b9 100644 --- a/pkg/elemental/edit/elemental.cattle.io.machineregistration.vue +++ b/pkg/elemental/edit/elemental.cattle.io.machineregistration.vue @@ -16,7 +16,7 @@ import { exceptionToErrorsArray } from '@shell/utils/error'; import Tabbed from '@shell/components/Tabbed/index.vue'; import Tab from '@shell/components/Tabbed/Tab.vue'; -import { semverVersionCheck, getOperatorVersion, getGatedFeature, MACH_REG_CONFIG_DEFAULTS } from '../utils/feature-versioning'; +import { getOperatorVersion, checkGatedFeatureCompatibility, MACH_REG_CONFIG_DEFAULTS } from '../utils/feature-versioning'; import { OLD_DEFAULT_CREATION_YAML, DEFAULT_CREATION_YAML } from '../models/elemental.cattle.io.machineregistration'; export default { @@ -51,10 +51,8 @@ export default { // in CREATE mode, since YAMLEditor doesn't live update, we need to force a re-render of the component for it to update if (this.mode === _CREATE) { const operatorVersion = await getOperatorVersion(this.$store); - const gatedFeature = getGatedFeature(this.resource, this.mode, MACH_REG_CONFIG_DEFAULTS); - const minOperatorVersion = gatedFeature?.minOperatorVersion || ''; - this.newCloudConfigcompatibilityCheck = semverVersionCheck(operatorVersion, minOperatorVersion); + this.newCloudConfigcompatibilityCheck = checkGatedFeatureCompatibility(this.resource, this.mode, MACH_REG_CONFIG_DEFAULTS, operatorVersion); if (!this.value.spec) { this.value.spec = this.newCloudConfigcompatibilityCheck ? DEFAULT_CREATION_YAML : OLD_DEFAULT_CREATION_YAML; @@ -70,8 +68,7 @@ export default { cloudConfig: typeof this.value.spec === 'string' ? this.value.spec : saferDump(this.value.spec), newCloudConfigcompatibilityCheck: false, yamlErrors: null, - isFormValid: true, - gatedFeature: {} + isFormValid: true }; }, watch: { diff --git a/pkg/elemental/utils/feature-versioning.ts b/pkg/elemental/utils/feature-versioning.ts index 3e75c9f..475650b 100644 --- a/pkg/elemental/utils/feature-versioning.ts +++ b/pkg/elemental/utils/feature-versioning.ts @@ -12,6 +12,7 @@ interface FeaturesGatingConfig { features: string[], } +// features to be gated to specific operator versions export const MACH_REG_CONFIG_DEFAULTS:string = 'machine-reg-config-defaults'; export const BUILD_MEDIA_RAW_SUPPORT:string = 'build-media-raw-support'; @@ -38,8 +39,7 @@ const FEATURES_GATING:FeaturesGatingConfig[] = [ /** * Get the current elemental-operator version - * @param any store - * @param any alreadyInstalledApps + * @param any Vue store * @returns Promise */ export async function getOperatorVersion(store: any): Promise { @@ -54,26 +54,23 @@ export async function getOperatorVersion(store: any): Promise { } /** - * Get the gated feature based on resource + mode + string - * @param string - * @param string - * @param string - * @returns FeaturesGatingConfig | {} | void + * Check the gated feature compatibility with the current Elemental Operator version installed + * @param string resource type (ex: Deployment) + * @param string UI mode (ex: edit, create, view) + * @param string Elemental feature (ex: Build media, cloud config) + * @param string Elemental Operator version + * @returns Boolean */ -export function getGatedFeature(resource: string, mode: string, feature: string): FeaturesGatingConfig | {} | void { - if (resource && mode) { - return FEATURES_GATING.find(feat => feat.area === resource && feat.mode.includes(mode) && feat.features.includes(feature)); - } +export function checkGatedFeatureCompatibility(resource: string, mode: string, feature: string, operatorVersion: string): Boolean { + if (resource && mode && feature) { + const gatedFeature = FEATURES_GATING.find(feat => feat.area === resource && feat.mode.includes(mode) && feat.features.includes(feature)); - return {}; -} + if (!gatedFeature?.minOperatorVersion || !operatorVersion) { + return false; + } -/** - * Determines if a given feature is enabled by doing a semver version comparison - * @param string - * @param string - * @returns Boolean | void - */ -export function semverVersionCheck(operatorVersion: string, operatorMinVersion: string): Boolean | void { - return semver.gte(operatorVersion, operatorMinVersion); + return semver.gte(operatorVersion, gatedFeature?.minOperatorVersion); + } + + return false; }