ui/lib/shared/addon/components/node-group-row/component.js

405 lines
13 KiB
JavaScript

import Component from '@ember/component';
import {
computed, get, observer, set, setProperties
} from '@ember/object';
import { equal } from '@ember/object/computed';
import { on } from '@ember/object/evented';
import { next } from '@ember/runloop';
import { inject as service } from '@ember/service';
import { isEmpty } from '@ember/utils';
import { Promise } from 'rsvp';
import { coerce, minor } from 'semver';
import { INSTANCE_TYPES } from 'shared/utils/amazon';
import { DEFAULT_NODE_GROUP_CONFIG } from 'ui/models/cluster';
import layout from './template';
export default Component.extend({
globalStore: service(),
layout,
classNames: ['row', 'mb-20'],
instanceTypes: INSTANCE_TYPES,
clusterConfig: null,
keyPairs: null,
mode: null,
model: null,
nodeGroupsVersionCollection: null,
originalCluster: null,
versions: null,
launchTemplates: null,
allSelectedTemplateVersions: null,
clusterSaving: false,
clusterSaved: false,
nameIsEditable: true,
showNodeUpgradePreventionReason: false,
upgradeVersion: false,
refreshResourceInstanceTags: true, // simply used for reinit'ing the resource instance tags key value component rather than add weird logic to recomput to the component
editing: equal('mode', 'edit'),
init() {
this._super(...arguments);
if (!this.launchTemplates) {
set(this, 'launchTemplates', []);
}
if (this.editing) {
if (!isEmpty(this.model.nodegroupName)) {
set(this, 'nameIsEditable', false);
}
}
},
actions: {
setTags(section) {
if (this.isDestroyed || this.isDestroying) {
return;
}
set(this, 'model.tags', section);
},
setLabels(section) {
if (this.isDestroyed || this.isDestroying) {
return;
}
set(this, 'model.labels', section);
},
setResourceTags(section) {
if (this.isDestroyed || this.isDestroying) {
return;
}
set(this, 'model.resourceTags', section);
},
},
loadTemplateVersionInfo: observer('model.launchTemplate.{id,version}', async function() {
if (!this.clusterSaving && !this.clusterSaved) {
const { launchTemplate = {} } = this.model;
let defaults = { ...DEFAULT_NODE_GROUP_CONFIG };
// in this case we dont want the defaults for nodegroup items
delete defaults.nodegroupName;
delete defaults.maxSize;
delete defaults.minSize;
delete defaults.desiredSize;
if (isEmpty(launchTemplate)) {
set(this, 'refreshResourceInstanceTags', false);
next(() => {
setProperties(this.model, defaults);
set(this, 'refreshResourceInstanceTags', true)
});
} else if ( !isEmpty(get(launchTemplate, 'id')) ) {
try {
const versions = await this.listTemplateVersions();
const match = versions.findBy('VersionNumber', parseInt(launchTemplate.version, 10)); // newselect doesn't handle numbers as values very well
const { LaunchTemplateData: launchTemplateData } = match;
const overrides = {
imageId: get(launchTemplateData, 'ImageId') ?? null,
instanceType: get(launchTemplateData, 'InstanceType') ?? 't3.medium',
volumeSize: get(launchTemplateData, 'BlockDeviceMappings.Ebs.VolumeSize') ?? 20,
ec2SshKey: get(launchTemplateData, 'KeyName') ?? null,
userData: isEmpty(get(launchTemplateData, 'UserData')) ? null : atob(get(launchTemplateData, 'UserData')),
};
defaults = Object.assign({}, defaults, overrides);
if (get(launchTemplateData, 'InstanceMarketOptions.MarketType') && get(launchTemplateData, 'InstanceMarketOptions.MarketType') === 'spot') {
set(defaults, 'requestSpotInstances', true);
}
if ( !isEmpty(get(launchTemplateData, 'TagSpecifications')) ) {
const resourceInstanceTags = get(launchTemplateData, 'TagSpecifications').findBy('ResourceType', 'instance');
if (!isEmpty(resourceInstanceTags) && !isEmpty(get(resourceInstanceTags, 'Tags'))) {
set(defaults, 'resourceTags', {});
resourceInstanceTags.Tags.forEach((tag) => set(defaults.resourceTags, get(tag, 'Key'), get(tag, 'Value')));
}
}
// if ( !isEmpty(get(launchTemplateData, 'NetworkInterfaces')) ) {
// const subnets = [];
// const interfaces = get(launchTemplateData, 'NetworkInterfaces');
// interfaces.forEach((enterface) => subnets.push(get(enterface, 'SubnetId')))
// }
set(this, 'refreshResourceInstanceTags', false);
next(() => {
setProperties(this.model, defaults);
set(this, 'allSelectedTemplateVersions', versions);
set(this, 'refreshResourceInstanceTags', true)
} );
} catch (err) { }
}
}
}),
selectedLaunchTemplateVersion: computed('model.launchTemplate.version', 'model.launchTemplate.id', {
get() {
return get(this, 'model.launchTemplate.version') ? get(this, 'model.launchTemplate.version').toString() : null;
},
set(_key, value) {
set(this, 'model.launchTemplate.version', parseInt(value, 10));
return value;
},
}),
isRancherLaunchTemplate: computed('model.{launchTemplate,nodegroupName}', 'originalCluster.eksStatus.managedLaunchTemplateID', function() {
const { originalCluster, model } = this;
const { launchTemplate } = model;
const eksStatus = get((originalCluster ?? {}), 'eksStatus') || {};
const { managedLaunchTemplateID = null, managedLaunchTemplateVersions = {} } = eksStatus;
const matchedManagedVersion = get(managedLaunchTemplateVersions, this.model.nodegroupName);
if (isEmpty(launchTemplate) && !isEmpty(managedLaunchTemplateID) && !isEmpty(matchedManagedVersion)) {
return true;
}
return false;
}),
isUserLaunchTemplate: computed('model.launchTemplate', 'originalCluster.eksStatus.managedLaunchTemplateID', function() {
const { model } = this;
const { launchTemplate } = model;
if (!isEmpty(launchTemplate)) {
return true;
}
return false;
}),
isNoLaunchTemplate: computed('isRancherLaunchTemplate', 'isUserLaunchTemplate', function() {
return !this.isRancherLaunchTemplate && !this.isUserLaunchTemplate;
}),
filteredLaunchTemplates: computed('launchTemplates.[]', function() {
const { launchTemplates } = this;
if (isEmpty(launchTemplates)) {
return [];
}
return launchTemplates.filter(({ LaunchTemplateName }) => !LaunchTemplateName.includes('rancher-managed-lt') ).map(({
LaunchTemplateName, LaunchTemplateId, DefaultVersionNumber
}) => {
return {
label: LaunchTemplateName,
value: {
id: LaunchTemplateId,
name: LaunchTemplateName,
defaultVersion: DefaultVersionNumber,
},
};
}).sortBy('label');
}),
selectedTemplateVersion: computed('model.launchTemplate.{id,version}', 'allSelectedTemplateVersions.[]', function() {
const { model, allSelectedTemplateVersions } = this;
const version = get(model, 'launchTemplate.version');
if (isEmpty(model.launchTemplate) || isEmpty(version)) {
return null;
}
const match = (allSelectedTemplateVersions || []).findBy('VersionNumber', parseInt(version, 10));
if (match) {
return match;
}
return null;
}),
selectedLaunchTemplateVersions: computed('model.launchTemplate.{id,name,version}', 'launchTemplates', function() {
const { model, launchTemplates } = this;
const { launchTemplate } = model;
if (isEmpty(launchTemplate) || isEmpty(get(launchTemplate, 'id'))) {
return [];
}
const match = launchTemplates.findBy('LaunchTemplateId', launchTemplate.id);
if (match) {
// this lets us create a range of values 1...XX because the launch template only gives us the 1st and latest numbers but we want all for the version select
// ++ver -> zero based array so we need to +1 that value to match a non-zero based version number system
return Array.from(Array(match.LatestVersionNumber).keys()).map((ver) => ({ label: `${ ++ver }` }));
}
return [];
}),
selectedLaunchTemplate: computed('model.launchTemplate', 'filteredLaunchTemplates.[]', {
get() {
const launchTemplate = get(this, 'model.launchTemplate') ?? false;
if (launchTemplate) {
const out = this.filteredLaunchTemplates.findBy('value.id', launchTemplate.id);
return isEmpty(out) ? null : get(out, 'value');
}
return null;
},
set(key, value) {
const {
id, name, defaultVersion: version
} = value ?? {};
if (isEmpty(value)) {
set(this, 'model.launchTemplate', null);
} else {
set(this, 'model.launchTemplate', {
id,
name,
version
});
}
return value;
}
}),
creating: computed('mode', function() {
const {
mode, originalCluster, model: { nodegroupName }
} = this;
if (mode === 'new') {
return true;
}
const upstreamSpec = get(originalCluster, 'eksStatus.upstreamSpec');
const nodeGroups = upstreamSpec ? get(upstreamSpec, 'nodeGroups') : [];
if (nodegroupName && nodeGroups.length >= 1) {
if (nodeGroups.findBy('nodegroupName', nodegroupName)) {
return false;
}
}
return true;
}),
originalClusterVersion: computed('originalCluster.eksConfig.kubernetesVersion', 'originalCluster.eksStatus.upstreamSpec.kubernetesVersion', function() {
if (!isEmpty(get(this, 'originalCluster.eksConfig.kubernetesVersion'))) {
return get(this, 'originalCluster.eksConfig.kubernetesVersion');
}
if (!isEmpty(get(this, 'originalCluster.eksStatus.upstreamSpec.kubernetesVersion'))) {
return get(this, 'originalCluster.eksStatus.upstreamSpec.kubernetesVersion');
}
return '';
}),
upgradeAvailable: computed('clusterConfig.kubernetesVersion', 'mode', 'model.version', 'originalClusterVersion', 'showNodeUpgradePreventionReason', function() {
const originalClusterVersion = get(this, 'originalClusterVersion');
const clusterVersion = get(this, 'clusterConfig.kubernetesVersion');
const nodeVersion = get(this, 'model.version');
const mode = get(this, 'mode');
const initalClusterMinorVersion = parseInt(minor(coerce(clusterVersion)), 10);
const initalNodeMinorVersion = parseInt(minor(coerce(nodeVersion)), 10);
const diff = initalClusterMinorVersion - initalNodeMinorVersion;
if (mode === 'edit') {
// we must upgrade the cluster first
if (originalClusterVersion !== clusterVersion) {
set(this, 'showNodeUpgradePreventionReason', true);
return false;
}
}
if (diff === 0 && get(this, 'showNodeUpgradePreventionReason')) {
set(this, 'showNodeUpgradePreventionReason', false);
}
return diff === 1;
}),
showGPUWarning: computed('model.launchTemplate.{id,version}', 'selectedTemplateVersion', function() {
const { model, selectedTemplateVersion } = this;
const ltId = get(model, 'launchTemplate.id');
const ltVersion = get(model, 'launchTemplate.version');
const imageId = selectedTemplateVersion ? get(selectedTemplateVersion, 'LaunchTemplateData.ImageId') : undefined;
return ltId && ltVersion && selectedTemplateVersion && imageId;
}),
requestedSpotInstances: on('init', observer('model.requestSpotInstances', function() {
const { model } = this;
if (get(model, 'requestSpotInstances')) {
set(this, 'model.instanceType', null);
} else if (!get(model, 'requestSpotInstances') && get(model, 'instanceType') === null) {
set(this, 'model.instanceType', 't3.medium');
}
})),
clusterVersionDidChange: on('init', observer('clusterConfig.kubernetesVersion', function() {
const { clusterConfig, editing } = this;
if (get(clusterConfig, 'kubernetesVersion') && !editing) {
set(this, 'model.version', clusterConfig.kubernetesVersion);
}
})),
shouldUpgradeVersion: on('init', observer('upgradeVersion', function() {
const { upgradeVersion } = this;
const clusterVersion = get(this, 'clusterConfig.kubernetesVersion');
const nodeVersion = get(this, 'model.version');
if (upgradeVersion && clusterVersion !== nodeVersion) {
set(this, 'model.version', clusterVersion);
}
})),
listTemplateVersions() {
const { launchTemplate } = this.model;
const match = this.launchTemplates.findBy('LaunchTemplateId', launchTemplate.id);
return new Promise((resolve, reject) => {
const ec2 = new AWS.EC2(this.authCreds());
const maxVersion = match.LatestVersionNumber;
ec2.describeLaunchTemplateVersions({
LaunchTemplateId: launchTemplate.id,
MaxVersion: maxVersion.toString(),
MinVersion: '1',
}, (err, data) => {
if (err) {
reject(err);
}
resolve(data.LaunchTemplateVersions);
});
})
},
removeNodeGroup() {
throw new Error('remove node group action is required!');
},
});