Skip to content

Commit

Permalink
Merge pull request #169 from aalves08/gate-disk-image-build-to-operat…
Browse files Browse the repository at this point in the history
…or-version

working on feature versioning mechanism
  • Loading branch information
aalves08 authored Apr 8, 2024
2 parents 48de581 + b449e8a commit bc9132e
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 23 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
41 changes: 35 additions & 6 deletions pkg/elemental/components/BuildMedia.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -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');
Expand All @@ -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) {
Expand Down Expand Up @@ -146,21 +167,26 @@ 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,
namespace: machineRegNamespace
}
},
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' });
Expand Down Expand Up @@ -207,7 +233,10 @@ export default {
:options="registrationEndpointsOptions"
/>
</div>
<div class="col span-2">
<div
v-if="isRawDiskImageBuildSupported"
class="col span-2"
>
<LabeledSelect
v-model="buildMediaTypeSelected"
class="mr-20"
Expand Down
8 changes: 7 additions & 1 deletion pkg/elemental/components/DashboardView.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script>
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';
Expand All @@ -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';
Expand Down Expand Up @@ -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) {
Expand All @@ -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 }`]: [],
Expand Down Expand Up @@ -283,6 +287,8 @@ export default {
<div class="mt-20 mb-20">
<BuildMedia
:registration-endpoint-list="registrationEndpoints"
:resource="resource"
:mode="mode"
/>
</div>
<!-- Tables -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ export default {
type: String,
required: true
},
resource: {
type: String,
required: true
},
},
data() {
return {
Expand Down Expand Up @@ -115,6 +119,8 @@ export default {
<BuildMedia
:display-reg-endpoints="false"
:registration-endpoint="`${value.metadata.namespace}/${value.metadata.name}`"
:resource="resource"
:mode="mode"
/>
</div>
<div
Expand Down
31 changes: 28 additions & 3 deletions pkg/elemental/edit/elemental.cattle.io.machineregistration.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import { exceptionToErrorsArray } from '@shell/utils/error';
import Tabbed from '@shell/components/Tabbed/index.vue';
import Tab from '@shell/components/Tabbed/Tab.vue';
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 {
name: 'MachineRegistrationEditView',
components: {
Expand All @@ -39,12 +42,33 @@ export default {
type: String,
required: true
},
resource: {
type: String,
required: true
},
},
async fetch() {
// 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);
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;
}
this.cloudConfig = typeof this.value.spec === 'string' ? this.value.spec : saferDump(this.value.spec);
this.rerender = true;
}
},
data() {
return {
cloudConfig: typeof this.value.spec === 'string' ? this.value.spec : saferDump(this.value.spec),
yamlErrors: null,
isFormValid: true
rerender: false,
cloudConfig: typeof this.value.spec === 'string' ? this.value.spec : saferDump(this.value.spec),
newCloudConfigcompatibilityCheck: false,
yamlErrors: null,
isFormValid: true
};
},
watch: {
Expand Down Expand Up @@ -195,6 +219,7 @@ export default {
<div class="col span-6">
<h3>{{ t('elemental.machineRegistration.create.cloudConfiguration') }}</h3>
<YamlEditor
:key="rerender"
ref="yamleditor"
v-model="cloudConfig"
class="mb-20"
Expand Down
16 changes: 13 additions & 3 deletions pkg/elemental/models/elemental.cattle.io.machineregistration.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,17 @@ import { downloadFile } from '@shell/utils/download';
import { ELEMENTAL_DEFAULT_NAMESPACE } from '../types';
import ElementalResource from './elemental-resource';

const DEFAULT_CREATION_YAML = `config:
export const OLD_DEFAULT_CREATION_YAML = `config:
cloud-config:
users:
- name: root
passwd: root
elemental:
install:
poweroff: true
device: /dev/nvme0n1`;

export const DEFAULT_CREATION_YAML = `config:
cloud-config:
users:
- name: root
Expand All @@ -32,8 +42,8 @@ const DEFAULT_CREATION_YAML = `config:

export default class MachineRegistration extends ElementalResource {
applyDefaults(vm, mode) {
if ( !this.spec ) {
Vue.set(this, 'spec', DEFAULT_CREATION_YAML);
if ( !this.spec || mode === _CREATE ) {
Vue.set(this, 'spec', {});
}
if ( !this.metadata || mode === _CREATE ) {
Vue.set(this, 'metadata', { namespace: ELEMENTAL_DEFAULT_NAMESPACE });
Expand Down
5 changes: 3 additions & 2 deletions pkg/elemental/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,18 @@ export default {
// this covers scenario where Elemental Operator is deleted from Apps and we lose the Elemental Admin role for Standard Users...
if (this.$store.getters['management/canList'](ELEMENTAL_SCHEMA_IDS.MACHINE_REGISTRATIONS)) {
let installedApps;
// needed to check if operator is installed
if (this.$store.getters['management/canList'](CATALOG.APP)) {
installedApps = await this.$store.dispatch('management/findAll', { type: CATALOG.APP });
}
const elementalSchema = this.$store.getters['management/schemaFor'](ELEMENTAL_SCHEMA_IDS.MACHINE_INVENTORIES)
const elementalSchema = this.$store.getters['management/schemaFor'](ELEMENTAL_SCHEMA_IDS.MACHINE_INVENTORIES);
// 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 = installedApps?.length && !installedApps?.find(item => 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) {
Expand Down
76 changes: 76 additions & 0 deletions pkg/elemental/utils/feature-versioning.ts
Original file line number Diff line number Diff line change
@@ -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<string | void>
*/
export async function getOperatorVersion(store: any): Promise<string | void> {
// 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;
}
Loading

0 comments on commit bc9132e

Please sign in to comment.