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..ec31103 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 { getOperatorVersion, checkGatedFeatureCompatibility, BUILD_MEDIA_RAW_SUPPORT } from '../utils/feature-versioning'; const MEDIA_TYPES = { RAW: { @@ -38,17 +39,28 @@ 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); }, data() { return { seedImagesList: [], managedOsVersions: [], filteredManagedOsVersions: [], + operatorVersion: '', buildMediaOsVersions: [], buildMediaTypes: [ { label: MEDIA_TYPES.ISO.label, value: MEDIA_TYPES.ISO.type }, @@ -79,6 +91,15 @@ export default { } }, computed: { + isRawDiskImageBuildSupported() { + 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; + }, registrationEndpointsOptions() { const activeRegEndpoints = this.registrationEndpointList.filter(item => item.state === 'active'); @@ -91,10 +112,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 +167,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 +180,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 +233,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..475650b --- /dev/null +++ b/pkg/elemental/utils/feature-versioning.ts @@ -0,0 +1,76 @@ +import semver from 'semver'; + +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'; + +interface FeaturesGatingConfig { + area: string, + mode: string[], + minOperatorVersion: string, + 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'; + +const FEATURES_GATING:FeaturesGatingConfig[] = [ + { + area: ELEMENTAL_SCHEMA_IDS.MACHINE_REGISTRATIONS, + mode: [_CREATE], + minOperatorVersion: '1.6.0', + features: [MACH_REG_CONFIG_DEFAULTS] + }, + { + area: ELEMENTAL_TYPES.DASHBOARD, + mode: [_VIEW], + minOperatorVersion: '1.6.0', + features: [BUILD_MEDIA_RAW_SUPPORT] + }, + { + area: ELEMENTAL_SCHEMA_IDS.MACHINE_REGISTRATIONS, + mode: [_VIEW], + minOperatorVersion: '1.6.0', + features: [BUILD_MEDIA_RAW_SUPPORT] + } +]; + +/** + * Get the current elemental-operator version + * @param any Vue store + * @returns Promise + */ +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 elementalOperatorDeployment?.metadata?.labels?.['app.kubernetes.io/version'] || '0.1.0'; + } + + return '0.1.0'; +} + +/** + * 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 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)); + + if (!gatedFeature?.minOperatorVersion || !operatorVersion) { + return false; + } + + return semver.gte(operatorVersion, gatedFeature?.minOperatorVersion); + } + + return false; +} 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"