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

766 lines
20 KiB
JavaScript

import Component from '@ember/component'
import ClusterDriver from 'shared/mixins/cluster-driver';
import layout from './template';
import {
get, set, computed, observer, setProperties
} from '@ember/object';
import { sortableNumericSuffix } from 'shared/utils/util';
import { reject, all } from 'rsvp';
import { inject as service } from '@ember/service';
import { equal } from '@ember/object/computed'
const times = [
{
value: null,
label: 'Any Time',
},
{
value: '00:00',
label: '12:00AM',
},
{
value: '03:00',
label: '3:00AM',
},
{
value: '06:00',
label: '6:00AM',
},
{
value: '09:00',
label: '9:00AM',
},
{
value: '12:00',
label: '12:00PM',
},
{
value: '15:00',
label: '3:00PM',
},
{
value: '19:00',
label: '7:00PM',
},
{
value: '21:00',
label: '9:00PM',
},
]
const imageType = [
{
label: 'clusterNew.googlegke.imageType.UBUNTU',
value: 'UBUNTU',
},
{
label: 'clusterNew.googlegke.imageType.COS',
value: 'COS'
},
]
const diskType = [
{
label: 'clusterNew.googlegke.diskType.pd-standard',
value: 'pd-standard',
},
{
label: 'clusterNew.googlegke.diskType.pd-ssd',
value: 'pd-ssd',
}
]
const REGIONS = ['asia-east1', 'asia-east2', 'asia-northeast1', 'asia-northeast2', 'asia-south1', 'asia-southeast1', 'australia-southeast1', 'europe-north1', 'europe-west1', 'europe-west2', 'europe-west3', 'europe-west4', 'europe-west6', 'northamerica-northeast1', 'southamerica-east1', 'us-central1', 'us-east1', 'us-east4', 'us-west1', 'us-west2']
const DEFAULT_AUTH_SCOPES = ['devstorage.read_only', 'logging.write', 'monitoring', 'servicecontrol', 'service.management.readonly', 'trace.append']
const ZONE_TYPE = 'zonal';
const REGION_TYPE = 'regional';
export default Component.extend(ClusterDriver, {
intl: service(),
layout,
configField: 'googleKubernetesEngineConfig',
step: 1,
zones: null,
versions: null,
machineTypes: null,
initialMasterVersion: null,
maintenanceWindowTimes: times,
eipIdContent: [],
imageTypeContent: imageType,
clusterAdvanced: false,
nodeAdvanced: false,
diskTypeContent: diskType,
scopeConfig: {},
hideNewField: false,
locationType: ZONE_TYPE,
isNew: equal('mode', 'new'),
editing: equal('mode', 'edit'),
init() {
this._super(...arguments);
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: [],
});
setProperties(this, {
'cluster.googleKubernetesEngineConfig': config,
oauthScopesSelection: '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', [])
}
}
setProperties(this, {
initialMasterVersion: get(this, 'config.masterVersion'),
regionChoices: REGIONS.map((region) => {
return { name: region }
}),
locationType: get(this, 'config.zone') ? ZONE_TYPE : REGION_TYPE,
})
},
actions: {
clickNext() {
this.$('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;
}
const str = get(this, 'config.credential');
if ( str ) {
try {
const obj = JSON.parse(str);
// Note: this is a Google project id, not ours.
const projectId = obj.project_id;
set(this, 'config.projectId', projectId);
} catch (e) {
}
}
}),
zoneChanged: observer('config.zone', 'zones.[]', 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[versions.indexOf(current)];
if ( !exists ) {
set(this, 'config.masterVersion', versions[0]);
}
}),
networkChange: observer('config.network', 'subNetworkContent.[]', function() {
if (this.saving) {
return;
}
const subNetworkContent = get(this, 'subNetworkContent') || []
if (subNetworkContent.length > 0) {
set(this, 'config.subNetwork', subNetworkContent[0] && subNetworkContent[0].value)
const secondaryIpRangeContent = get(this, 'secondaryIpRangeContent') || []
if (secondaryIpRangeContent.length > 0) {
const value = secondaryIpRangeContent[0] && secondaryIpRangeContent[0].value
setProperties(this, {
'config.ipPolicyClusterSecondaryRangeName': value,
'config.ipPolicyServicesSecondaryRangeName': value,
})
}
}
}),
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')) {
set(this, 'config.enablePrivateNodes', 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('machineChoices', 'config', function() {
return get(this, 'machineChoices').findBy('name', get(this, 'config.machineType'));
}),
versionChoices: computed('versions.validMasterVersions.[]', 'config.masterVersion', function() {
return get(this, 'versions.validMasterVersions');
}),
locationContent: computed('config.zone', 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', function() {
return get(this, 'networks')
}),
subNetworkContent: computed('subNetworks.[]', 'config.network', function() {
const subNetworks = get(this, 'subNetworks') || []
const networkName = get(this, 'config.network')
const out = subNetworks.filter((s) => {
const { network = '' } = s
const arr = network.split('/') || []
const networkDisplayName = arr[arr.length - 1]
if (networkDisplayName === networkName) {
return true
}
})
return out.map((o) => {
return {
label: `${ o.name }(${ o.ipCidrRange })`,
value: o.name,
secondaryIpRanges: o.secondaryIpRanges,
}
})
}),
secondaryIpRangeContent: computed('subNetworkContent.[]', 'config.network', function() {
const subNetworkContent = get(this, 'subNetworkContent')
const { secondaryIpRanges = [] } = subNetworkContent
return secondaryIpRanges.map((s) => {
return {
lable: `${ s.rangeName }(${ s.ipCidrRange })`,
value: s.rangeName,
}
})
}),
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' };
}),
fetchZones() {
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() {
return get(this, 'globalStore').rawRequest({
url: '/meta/gkeVersions',
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;
set(this, 'versions', out);
this.versionChanged();
return out;
}).catch((xhr) => {
set(this, 'errors', [xhr.body.error]);
return reject();
});
},
fetchMachineTypes() {
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() {
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() {
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 === ZONE_TYPE ? `${ 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() {
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 === ZONE_TYPE ) {
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
}
if (get(this, 'oauthScopesSelection') === 'default') {
set(config, 'oauthScopes', DEFAULT_AUTH_SCOPES.map((a) => `https://www.googleapis.com/auth/${ a }`))
} else if (get(this, 'oauthScopesSelection') === 'full') {
set(config, 'oauthScopes', ['https://www.googleapis.com/auth/cloud-platform'])
} else if (get(this, 'oauthScopesSelection') === 'custom') {
const scopeConfig = get(this, 'scopeConfig') || {}
let arr = []
Object.keys(scopeConfig).map((key) => {
if (scopeConfig[key] !== 'none') {
arr.pushObject(`https://www.googleapis.com/auth/${ scopeConfig[key] }`)
}
})
set(config, 'oauthScopes', arr)
}
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);
},
});