ui/lib/shared/addon/components/cluster-driver/driver-googlegke/component.js

804 lines
22 KiB
JavaScript

import Component from '@ember/component';
import {
computed, get, observer, set, setProperties
} from '@ember/object';
import { equal } from '@ember/object/computed';
import { inject as service } from '@ember/service';
import { isEmpty } from '@ember/utils';
import $ from 'jquery';
import { all, reject } from 'rsvp';
import ClusterDriver from 'shared/mixins/cluster-driver';
import { sortableNumericSuffix } from 'shared/utils/util';
import layout from './template';
export default Component.extend(ClusterDriver, {
intl: service(),
settings: service(),
versionChoiceService: service('version-choices'),
google: service(),
layout,
configField: 'googleKubernetesEngineConfig',
step: 1,
clusterAdvanced: false,
diskTypeContent: null,
eipIdContent: null,
hideNewField: false,
imageTypeContent: null,
initialMasterVersion: null,
locationType: null,
machineTypes: null,
maintenanceWindowTimes: null,
nodeAdvanced: false,
scopeConfig: null,
versions: null,
zones: null,
isNew: equal('mode', 'new'),
editing: equal('mode', 'edit'),
init() {
this._super(...arguments);
// defaults
setProperties(this, {
maintenanceWindowTimes: this.google.maintenanceWindows,
imageTypeContent: this.google.imageTypes,
diskTypeContent: this.google.diskTypes,
locationType: this.google.defaultZoneType,
eipIdContent: [],
scopeConfig: {},
});
let config = get(this, 'cluster.googleKubernetesEngineConfig');
if ( !config ) {
config = get(this, 'globalStore').createRecord({
type: 'googleKubernetesEngineConfig',
diskSizeGb: 100,
enableAlphaFeature: false,
nodeCount: 3,
machineType: 'n1-standard-2',
zone: 'us-central1-f',
clusterIpv4Cidr: '',
minNodeCount: 1,
maxNodeCount: 5,
imageType: 'UBUNTU',
diskType: 'pd-standard',
region: 'us-west2',
taints: [],
useIpAliases: true,
ipPolicyCreateSubnetwork: true,
});
setProperties(this, {
'cluster.googleKubernetesEngineConfig': config,
oauthScopesSelection: this.google.oauthScopeOptions.DEFAULT,
scopeConfig: {
userInfo: 'none',
computeEngine: 'none',
storage: 'devstorage.read_only',
taskQueue: 'none',
bigQuery: 'none',
cloudSQL: 'none',
cloudDatastore: 'none',
stackdriverLoggingAPI: 'logging.write',
stackdriverMonitoringAPI: 'monitoring',
cloudPlatform: 'none',
bigtableData: 'none',
bigtableAdmin: 'none',
cloudPub: 'none',
serviceControl: 'none',
serviceManagement: 'service.management.readonly',
stackdriverTrace: 'trace.append',
cloudSourceRepositories: 'none',
cloudDebugger: 'none'
},
resourceLabels: [],
labels: [],
taints: [],
})
} else {
const {
resourceLabels = [], labels = [], taints = [], imageType
} = config
if (!imageType) {
set(this, 'hideNewField', true)
}
let map = {}
if (resourceLabels) {
resourceLabels.map((t = '') => {
const split = t.split('=')
set(map, split[0], split[1])
})
set(this, 'resourceLabels', map)
}
if (labels) {
labels.map((t = '') => {
const split = t.split('=')
set(map, split[0], split[1])
})
set(this, 'labels', map)
}
if (taints) {
let _taints = taints.map((t = '') => {
const splitEffect = t.split(':')
const splitLabel = (splitEffect[1] || '').split('=')
return {
effect: splitEffect[0],
key: splitLabel[0],
value: splitLabel[1],
}
})
set(this, 'taints', _taints)
} else {
set(this, 'taints', [])
}
if (!get(this, 'oauthScopesSelection')) {
const oauthScopes = get(config, 'oauthScopes')
const { oauthScopesSelection, scopeConfig } = this.google.unmapOauthScopes(oauthScopes);
set(this, 'oauthScopesSelection', oauthScopesSelection);
if (scopeConfig) {
set(this, 'scopeConfig', scopeConfig);
}
}
}
setProperties(this, {
initialMasterVersion: get(this, 'config.masterVersion'),
regionChoices: this.google.regions.map((region) => {
return { name: region }
}),
locationType: get(this, 'config.zone') ? this.google.defaultZoneType : this.google.defaultRegionType,
})
},
actions: {
clickNext() {
if (isEmpty(get(this, 'config.projectId'))) {
set(this, 'config.projectId', this.google.parseProjectId(get(this, 'config')));
}
$('BUTTON[type="submit"]').click();
},
checkServiceAccount(cb) {
set(this, 'errors', []);
return all([
this.fetchZones(),
this.fetchVersions(),
this.fetchMachineTypes(),
this.fetchNetworks(),
this.fetchSubnetworks(),
this.fetchServiceAccounts(),
]).then(() => {
set(this, 'step', 2);
cb(true);
}).catch(() => {
cb(false);
});
},
setLabels(section) {
const out = []
for (let key in section) {
out.pushObject(`${ key }=${ section[key] }`)
}
set(this, 'config.resourceLabels', out);
},
setNodeLabels(section) {
const out = []
for (let key in section) {
out.pushObject(`${ key }=${ section[key] }`)
}
set(this, 'config.labels', out);
},
updateNameservers(nameservers) {
set(this, 'config.masterAuthorizedNetworkCidrBlocks', nameservers);
},
setTaints(value) {
set(this, 'config.taints', value);
},
},
credentialChanged: observer('config.credential', function() {
if (this.saving) {
return;
}
set(this, 'config.projectId', this.google.parseProjectId(get(this, 'config')));
}),
zoneChanged: observer('config.zone', 'zones.[]', 'locationType', 'config.region', function() {
if (this.saving) {
return;
}
const zones = get(this, 'zones') || [];
const currentZone = zones.findBy('name', get(this, 'config.zone'));
if ( !currentZone || currentZone.status.toLowerCase() !== 'up' ) {
const newZone = zones.filter((x) => x.name.startsWith('us-')).find((x) => x.status.toLowerCase() === 'up');
if ( newZone ) {
set(this, 'config.zone', newZone.name);
}
}
this.fetchVersions();
this.fetchMachineTypes();
this.fetchNetworks();
this.fetchSubnetworks();
this.fetchServiceAccounts();
}),
machineTypeChanged: observer('config.machineTypes', 'machineTypes.[]', function() {
if (this.saving) {
return;
}
const types = get(this, 'machineTypes') || [];
const current = types.findBy('name', get(this, 'config.machineType'));
if ( !current ) {
set(this, 'config.machineType', get(types, 'firstObject.name'));
}
}),
versionChanged: observer('config.masterVersion', 'versionChoices.[]', function() {
const current = get(this, 'config.masterVersion');
if (this.saving && current) {
return;
}
const versions = get(this, 'versionChoices') || [];
const exists = versions.findBy('value', current);
if ( !exists ) {
set(this, 'config.masterVersion', get(versions, 'firstObject.value'));
}
}),
networkChange: observer('config.network', 'subNetworkContent.[]', function() {
if (this.saving) {
return;
}
const subNetworkContent = get(this, 'subNetworkContent') || []
if (subNetworkContent.length > 1) {
const firstNonNullSubnetMatch = subNetworkContent.find((sn) => !isEmpty(sn.value));
setProperties(this, {
'config.subNetwork': firstNonNullSubnetMatch.value,
'config.ipPolicyCreateSubnetwork': false,
})
const secondaryIpRangeContent = get(this, 'secondaryIpRangeContent') || []
if (secondaryIpRangeContent.length > 0) {
const value = secondaryIpRangeContent[0] && secondaryIpRangeContent[0].value
setProperties(this, {
'config.ipPolicyClusterSecondaryRangeName': value,
'config.ipPolicyServicesSecondaryRangeName': value,
})
}
}
}),
subnetworkChange: observer('config.subNetwork', function() {
if (this.saving) {
return;
}
const { config: { subNetwork } } = this;
if (isEmpty(subNetwork)) {
set(this, 'config.ipPolicyCreateSubnetwork', true);
} else {
set(this, 'config.ipPolicyCreateSubnetwork', false);
}
}),
secondaryIpRangeContentChange: observer('secondaryIpRangeContent.[]', 'config.useIpAliases', function() {
if (this.saving) {
return;
}
const secondaryIpRangeContent = get(this, 'secondaryIpRangeContent') || []
if (secondaryIpRangeContent.length === 0) {
set(this, 'config.ipPolicyCreateSubnetwork', true)
}
}),
useIpAliasesChange: observer('config.useIpAliases', function() {
if (this.saving) {
return;
}
if (get(this, 'config.useIpAliases')) {
if (!isEmpty(this.config.subNetwork)) {
set(this, 'config.ipPolicyCreateSubnetwork', false);
}
} else {
setProperties(this, {
'config.enablePrivateNodes': false,
'config.ipPolicyCreateSubnetwork': false,
});
}
}),
enablePrivateNodesChange: observer('config.enablePrivateNodes', function() {
if (this.saving) {
return;
}
const config = get(this, 'config')
if (!get(config, 'enablePrivateNodes')) {
setProperties(config, {
enablePrivateEndpoint: false,
masterIpv4CidrBlock: '',
})
}
}),
zoneChoices: computed('zones.[]', function() {
let out = (get(this, 'zones') || []).slice();
out.forEach((obj) => {
set(obj, 'sortName', sortableNumericSuffix(obj.name));
set(obj, 'displayName', `${ obj.name } (${ obj.description })`);
set(obj, 'disabled', obj.status.toLowerCase() !== 'up');
});
return out.sortBy('sortName')
}),
machineChoices: computed('machineTypes.[]', function() {
let out = (get(this, 'machineTypes') || []).slice();
out.forEach((obj) => {
set(obj, 'sortName', sortableNumericSuffix(obj.name));
set(obj, 'displayName', `${ obj.name } (${ obj.description })`);
});
return out.sortBy('sortName')
}),
editedMachineChoice: computed('config.machineType', 'machineChoices', function() {
return get(this, 'machineChoices').findBy('name', get(this, 'config.machineType'));
}),
versionChoices: computed('versions.validMasterVersions.[]', 'config.masterVersion', function() {
const {
versions: { validMasterVersions = [] },
config: { masterVersion },
mode,
} = this;
return this.versionChoiceService.parseCloudProviderVersionChoices(validMasterVersions, masterVersion, mode);
}),
locationContent: computed('config.zone', 'zoneChoices', function() {
const zone = get(this, 'config.zone')
if ( !zone ) {
return [];
}
const arr = zone.split('-')
const locationName = `${ arr[0] }-${ arr[1] }`
const zoneChoices = get(this, 'zoneChoices')
return zoneChoices.filter((z) => (z.name || '').startsWith(locationName) && z.name !== zone)
}),
networkContent: computed('networks', 'config.zone', function() {
return get(this, 'networks')
}),
subNetworkContent: computed('subNetworks.[]', 'config.network', 'config.zone', function() {
const {
config: { network: networkName },
networkContent,
subNetworks = [],
} = this;
const filteredSubnets = (subNetworks || []).filter((s) => {
const network = networkContent.findBy('selfLink', s.network);
const networkDisplayName = network.name;
if (networkDisplayName === networkName) {
return true
}
});
const mappedSubnets = filteredSubnets.map((o) => {
const network = networkContent.findBy('selfLink', o.network);
const networkDisplayName = network.name;
return {
label: `${ o.name }(${ o.ipCidrRange })`,
value: o.name,
secondaryIpRanges: o.secondaryIpRanges,
networkDisplayName
}
});
const defaultSubnetAry = [{
label: this.intl.t('clusterNew.googlegke.ipPolicyCreateSubnetwork.autoLabel'),
value: '',
}];
return [...defaultSubnetAry, ...mappedSubnets];
}),
secondaryIpRangeContent: computed('subNetworkContent.[]', 'config.{network,subNetwork}', function() {
const { subNetworkContent = [], config: { subNetwork } } = this;
const subNetworkMatch = subNetworkContent.findBy('value', subNetwork);
if (subNetworkMatch) {
const { secondaryIpRanges = [] } = subNetworkMatch;
return secondaryIpRanges.map((s) => {
return {
label: `${ s.rangeName }(${ s.ipCidrRange })`,
value: s.rangeName,
}
});
}
return [];
}),
serviceAccountContent: computed('serviceAccounts', function() {
const serviceAccounts = get(this, 'serviceAccounts')
return serviceAccounts
}),
maintenanceWindowChoice: computed('maintenanceWindowTimes.[]', 'config.maintenanceWindow', function() {
return get(this, 'maintenanceWindowTimes').findBy('value', get(this, 'config.maintenanceWindow')) || { label: 'Any Time' };
}),
shouldAllowDisableCreateSubNetwork: computed('config.subNetwork', function() {
const {
config: { subNetwork },
secondaryIpRangeContent,
editing,
} = this;
if (isEmpty(subNetwork)) {
return true;
}
if (editing && isEmpty(secondaryIpRangeContent)) {
return true;
}
return false;
}),
fetchZones() {
if (this.saved) {
return;
}
return get(this, 'globalStore').rawRequest({
url: '/meta/gkeZones',
method: 'POST',
data: {
credentials: get(this, 'config.credential'),
projectId: get(this, 'config.projectId'),
}
}).then((xhr) => {
const out = xhr.body.items;
const locations = get(this, 'config.locations') || []
if (locations.length > 0) {
out.map((o) => {
if (locations.includes(o.name)) {
set(o, 'checked', true)
}
})
}
set(this, 'zones', out);
return out;
}).catch((xhr) => {
set(this, 'errors', [xhr.body.error]);
return reject();
});
},
fetchVersions() {
if (this.saved) {
return;
}
const locationType = get(this, 'locationType');
const region = locationType === 'regional' ? get(this, 'config.region') : undefined;
const zone = locationType === 'zonal' ? get(this, 'config.zone') : undefined;
return get(this, 'globalStore').rawRequest({
url: '/meta/gkeVersions',
method: 'POST',
data: {
credentials: get(this, 'config.credential'),
projectId: get(this, 'config.projectId'),
zone,
region
}
}).then((xhr) => {
const out = xhr.body;
set(this, 'versions', out);
this.versionChanged();
return out;
}).catch((xhr) => {
set(this, 'errors', [xhr.body.error]);
return reject();
});
},
fetchMachineTypes() {
if (this.saved) {
return;
}
return get(this, 'globalStore').rawRequest({
url: '/meta/gkeMachineTypes',
method: 'POST',
data: {
credentials: get(this, 'config.credential'),
projectId: get(this, 'config.projectId'),
zone: get(this, 'config.zone') || `${ get(this, 'config.region') }-b`,
}
}).then((xhr) => {
const out = xhr.body.items;
set(this, 'machineTypes', out);
return out;
}).catch((xhr) => {
set(this, 'errors', [xhr.body.error]);
return reject();
});
},
fetchNetworks() {
if (this.saved) {
return;
}
return get(this, 'globalStore').rawRequest({
url: '/meta/gkeNetworks',
method: 'POST',
data: {
credentials: get(this, 'config.credential'),
projectId: get(this, 'config.projectId'),
zone: get(this, 'config.zone'),
}
}).then((xhr) => {
const out = xhr.body.items || [];
set(this, 'networks', out);
if (get(this, 'mode') === 'new') {
set(this, 'config.network', out[0] && out[0].name)
}
return out;
}).catch((xhr) => {
set(this, 'errors', [xhr.body.error]);
return reject();
});
},
fetchSubnetworks() {
if (this.saved) {
return;
}
const zone = get(this, 'config.zone')
const locationType = get(this, 'locationType');
return get(this, 'globalStore').rawRequest({
url: '/meta/gkeSubnetworks',
method: 'POST',
data: {
credentials: get(this, 'config.credential'),
projectId: get(this, 'config.projectId'),
region: locationType === this.google.defaultZoneType ? `${ zone.split('-')[0] }-${ zone.split('-')[1] }` : get(this, 'config.region'),
}
}).then((xhr) => {
const out = xhr.body.items || [];
set(this, 'subNetworks', out);
return out;
}).catch((xhr) => {
set(this, 'errors', [xhr.body.error]);
return reject();
});
},
fetchServiceAccounts() {
if (this.saved) {
return;
}
return get(this, 'globalStore').rawRequest({
url: '/meta/gkeServiceAccounts',
method: 'POST',
data: {
credentials: get(this, 'config.credential'),
projectId: get(this, 'config.projectId'),
zone: get(this, 'config.zone'),
}
}).then((xhr) => {
const out = xhr.body.accounts || [];
set(this, 'serviceAccounts', out);
const filter = out.filter((o) => o.displayName === 'Compute Engine default service account')
if (get(this, 'mode') === 'new') {
set(this, 'config.serviceAccount', filter[0] && filter[0].uniqueId)
}
return out;
}).catch((xhr) => {
set(this, 'errors', [xhr.body.error]);
return reject();
});
},
validate() {
const model = get(this, 'cluster');
const errors = model.validationErrors();
const { intl, config = {} } = this
let {
minNodeCount, maxNodeCount, enableNodepoolAutoscaling, nodeCount
} = config
if ( enableNodepoolAutoscaling ) {
if ( nodeCount && maxNodeCount && minNodeCount ) {
nodeCount = parseInt(nodeCount, 10);
maxNodeCount = parseInt(maxNodeCount, 10);
minNodeCount = parseInt(minNodeCount, 10);
if (maxNodeCount < minNodeCount) {
errors.pushObject(intl.t('clusterNew.googlegke.maxNodeCount.minError'))
}
if ( enableNodepoolAutoscaling && ( maxNodeCount < nodeCount ) ) {
errors.pushObject(intl.t('clusterNew.googlegke.nodeCount.outsideError'))
}
if ( enableNodepoolAutoscaling && ( minNodeCount > nodeCount ) ) {
errors.pushObject(intl.t('clusterNew.googlegke.nodeCount.outsideError'))
}
} else {
if ( !nodeCount ) {
errors.pushObject(intl.t('clusterNew.googlegke.nodeCount.required'))
}
if ( !maxNodeCount ) {
errors.pushObject(intl.t('clusterNew.googlegke.maxNodeCount.required'))
}
if ( !minNodeCount ) {
errors.pushObject(intl.t('clusterNew.googlegke.minNodeCount.required'))
}
}
}
if (!get(this, 'cluster.name')) {
errors.pushObject(intl.t('clusterNew.name.required'))
}
const taints = get(this, 'taints') || []
if (taints.length > 0) {
const filter = taints.filter((t) => !t.key || !t.value)
if (filter.length > 0) {
errors.pushObject(intl.t('clusterNew.googlegke.taints.required'))
}
}
set(this, 'errors', errors);
return errors.length === 0;
},
willSave() {
const config = get(this, 'config') || {}
const locationType = get(this, 'locationType');
if ( locationType === this.google.defaultZoneType ) {
set(config, 'region', null);
} else {
set(config, 'zone', null);
}
if (!get(config, 'enableNodepoolAutoscaling')) {
setProperties(config, {
minNodeCount: 0,
maxNodeCount: 0,
})
}
if (get(this, 'config.useIpAliases') && get(config, 'ipPolicyCreateSubnetwork') && get(config, 'ipPolicyClusterIpv4CidrBlock')) {
set(config, 'clusterIpv4Cidr', '')
}
if (!get(config, 'enableMasterAuthorizedNetwork')) {
delete config.masterAuthorizedNetworkCidrBlocks
}
if (!get(config, 'resourceLabels')) {
delete config.resourceLabels
}
const locationContent = get(this, 'locationContent')
const locations = locationContent.filter((l) => l.checked).map((l) => l.name)
if (locations.length > 0) {
locations.push(get(config, 'zone'))
set(config, 'locations', locations)
} else {
delete config.locations
}
const oauthScopesSelection = get(this, 'oauthScopesSelection');
const scopeConfig = get(this, 'scopeConfig');
set(config, 'oauthScopes', this.google.mapOauthScopes(oauthScopesSelection, scopeConfig));
const taints = get(this, 'taints') || []
if (taints.length > 0) {
set(config, 'taints', taints.map((t) => {
return `${ t.effect }:${ t.key }=${ t.value }`
}))
} else {
set(config, 'taints', [])
}
set(config, 'issueClientCertificate', true)
return this._super(...arguments);
},
});