ui/lib/shared/addon/components/node-driver/driver-amazonec2/component.js

512 lines
14 KiB
JavaScript

import $ from 'jquery';
import { scheduleOnce } from '@ember/runloop';
import EmberObject, { computed, get, set, setProperties } from '@ember/object';
import { alias, equal } from '@ember/object/computed';
import { inject as service } from '@ember/service';
import Component from '@ember/component';
import NodeDriver from 'shared/mixins/node-driver';
import layout from './template';
let RANCHER_GROUP = 'rancher-nodes';
export const INSTANCE_TYPES = [
{group: 'T2 - Burstable', name: 't2.nano'},
{group: 'T2 - Burstable', name: 't2.micro'},
{group: 'T2 - Burstable', name: 't2.small'},
{group: 'T2 - Burstable', name: 't2.medium'},
{group: 'T2 - Burstable', name: 't2.large'},
{group: 'T2 - Burstable', name: 't2.xlarge'},
{group: 'T2 - Burstable', name: 't2.2xlarge'},
{group: 'M5 - General Purpose', name: 'm5.large'},
{group: 'M5 - General Purpose', name: 'm5.xlarge'},
{group: 'M5 - General Purpose', name: 'm5.2xlarge'},
{group: 'M5 - General Purpose', name: 'm5.4xlarge'},
{group: 'M5 - General Purpose', name: 'm5.12xlarge'},
{group: 'M5 - General Purpose', name: 'm5.24xlarge'},
{group: 'M4 - General Purpose', name: 'm4.large'},
{group: 'M4 - General Purpose', name: 'm4.xlarge'},
{group: 'M4 - General Purpose', name: 'm4.2xlarge'},
{group: 'M4 - General Purpose', name: 'm4.4xlarge'},
{group: 'M4 - General Purpose', name: 'm4.10xlarge'},
{group: 'M4 - General Purpose', name: 'm4.16xlarge'},
{group: 'M3 - General Purpose', name: 'm3.medium'},
{group: 'M3 - General Purpose', name: 'm3.large'},
{group: 'M3 - General Purpose', name: 'm3.xlarge'},
{group: 'M3 - General Purpose', name: 'm3.2xlarge'},
{group: 'C5 - High CPU', name: 'c5.large'},
{group: 'C5 - High CPU', name: 'c5.xlarge'},
{group: 'C5 - High CPU', name: 'c5.2xlarge'},
{group: 'C5 - High CPU', name: 'c5.4xlarge'},
{group: 'C5 - High CPU', name: 'c5.9xlarge'},
{group: 'C5 - High CPU', name: 'c5.18xlarge'},
{group: 'C4 - High CPU', name: 'c4.large'},
{group: 'C4 - High CPU', name: 'c4.xlarge'},
{group: 'C4 - High CPU', name: 'c4.2xlarge'},
{group: 'C4 - High CPU', name: 'c4.4xlarge'},
{group: 'C4 - High CPU', name: 'c4.8xlarge'},
{group: 'C3 - High CPU', name: 'c3.large'},
{group: 'C3 - High CPU', name: 'c3.xlarge'},
{group: 'C3 - High CPU', name: 'c3.2xlarge'},
{group: 'C3 - High CPU', name: 'c3.4xlarge'},
{group: 'C3 - High CPU', name: 'c3.8xlarge'},
{group: 'R4 - High Memory', name: 'r4.large'},
{group: 'R4 - High Memory', name: 'r4.xlarge'},
{group: 'R4 - High Memory', name: 'r4.2xlarge'},
{group: 'R4 - High Memory', name: 'r4.4xlarge'},
{group: 'R4 - High Memory', name: 'r4.8xlarge'},
{group: 'R4 - High Memory', name: 'r4.16xlarge'},
{group: 'R3 - High Memory', name: 'r3.large'},
{group: 'R3 - High Memory', name: 'r3.xlarge'},
{group: 'R3 - High Memory', name: 'r3.2xlarge'},
{group: 'R3 - High Memory', name: 'r3.4xlarge'},
{group: 'R3 - High Memory', name: 'r3.8xlarge'},
{group: 'D2 - High Density Storage', name: 'd2.xlarge'},
{group: 'D2 - High Density Storage', name: 'd2.2xlarge'},
{group: 'D2 - High Density Storage', name: 'd2.4xlarge'},
{group: 'D2 - High Density Storage', name: 'd2.8xlarge'},
{group: 'I3 - High I/O Storage', name: 'i3.large'},
{group: 'I3 - High I/O Storage', name: 'i3.xlarge'},
{group: 'I3 - High I/O Storage', name: 'i3.2xlarge'},
{group: 'I3 - High I/O Storage', name: 'i3.4xlarge'},
{group: 'I3 - High I/O Storage', name: 'i3.8xlarge'},
{group: 'I3 - High I/O Storage', name: 'i3.16xlarge'},
{group: 'F1 - FPGA', name: 'f1.2xlarge'},
{group: 'F1 - FPGA', name: 'f1.16xlarge'},
{group: 'G3 - GPU', name: 'g3.4xlarge'},
{group: 'G3 - GPU', name: 'g3.8xlarge'},
{group: 'G3 - GPU', name: 'g3.16xlarge'},
{group: 'P2 - GPU', name: 'p2.xlarge'},
{group: 'P2 - GPU', name: 'p2.8xlarge'},
{group: 'P2 - GPU', name: 'p2.16xlarge'},
{group: 'X1 - Really High Memory', name: 'x1.16xlarge'},
{group: 'X1 - Really High Memory', name: 'x1.32xlarge'},
];
// These need to match the supported list in docker-machine:
// https://github.com/docker/machine/blob/master/drivers/amazonec2/region.go
export const REGIONS = [
"ap-northeast-1",
"ap-northeast-2",
"ap-southeast-1",
"ap-southeast-2",
"ap-south-1",
"ca-central-1",
"cn-north-1",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"eu-central-1",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-1",
"us-west-2",
"us-gov-west-1",
];
function nameFromResource(r, idField) {
let id = r[idField];
let out = id;
if ( r && r.Tags && r.Tags.length )
{
let match = r.Tags.filterBy('Key','Name')[0];
if ( match )
{
out = match.Value + ' (' + id + ')';
}
}
return out;
}
function tagsFromResource(r) {
let out = [];
if ( r && r.Tags && r.Tags.length )
{
r.Tags.forEach(tag => {
if( tag.Key !== 'Name' ) {
out.push(`${tag.Key}=${tag.Value}`)
}
})
}
return out;
}
export default Component.extend(NodeDriver, {
layout,
prefs : service(),
model : null,
driverName : 'amazonec2',
config : alias('model.amazonec2Config'),
clients : null,
allSubnets : null,
allSecurityGroups : null,
selectedSecurityGroup : null,
defaultSecurityGroup : null,
defaultSecurityGroupName : RANCHER_GROUP,
whichSecurityGroup : 'default',
isCustomSecurityGroup : equal('whichSecurityGroup','custom'),
instanceTypes : INSTANCE_TYPES,
regionChoices : REGIONS,
step : 1,
bootstrap: function() {
let pref = get(this, 'prefs.amazonec2')||{};
let config = get(this, 'globalStore').createRecord({
type : 'amazonec2Config',
region : 'us-west-2',
instanceType : 't2.micro',
securityGroup : '',
zone : 'a',
rootSize : '16',
accessKey : pref.accessKey||'',
secretKey : pref.secretKey||'',
});
set(this, 'model.amazonec2Config', config);
},
validate() {
let errors = [];
if ( !get(this, 'model.name') ) {
errors.push('Name is required');
}
set(this, 'errors', errors);
return errors.length === 0;
},
init: function() {
this._super(...arguments);
setProperties(this, {
editing: false,
clients: EmberObject.create(),
allSubnets: []
})
let cur = get(this, 'config.securityGroup');
if ( cur === '' ) { // TODO 2.0 should this be null 403 Vince/Wes/Daishan
setProperties(this, {
whichSecurityGroup : 'default',
selectedSecurityGroup : null,
});
} else {
setProperties(this, {
whichSecurityGroup : 'custom',
selectedSecurityGroup : cur,
});
}
},
willDestroyElement: function() {
setProperties(this, {
step : 1,
machineId : null,
clients : null,
allSubnets : null,
allSecurityGroups : null,
whichSecurityGroup : 'default',
});
},
stepDidChange: function() {
scheduleOnce('afterRender', this, function() {
document.body.scrollTop = document.body.scrollHeight;
});
}.observes('context.step'),
actions: {
awsLogin: function(cb) {
let self = this;
setProperties(this, {
'errors':null,
'config.accessKey': (get(this, 'config.accessKey')||'').trim(),
'config.secretKey': (get(this, 'config.secretKey')||'').trim(),
});
let subnets = [];
let rName = get(this, 'config.region');
let ec2 = new AWS.EC2({
accessKeyId : get(this, 'config.accessKey'),
secretAccessKey : get(this, 'config.secretKey'),
region : rName,
});
let vpcNames = {};
let vpcTags = {};
ec2.describeVpcs({}, (err, vpcs) => {
if ( err ) {
let errors = self.get('errors')||[];
errors.pushObject(err);
set(this, 'errors', errors);
cb();
return;
}
vpcs.Vpcs.forEach((vpc) => {
vpcNames[vpc.VpcId] = nameFromResource(vpc, 'VpcId');
vpcTags[vpc.VpcId] = tagsFromResource(vpc);
});
ec2.describeSubnets({}, (err, data) => {
if ( err ) {
let errors = self.get('errors')||[];
errors.pushObject(err);
set(this, 'errors', errors);
cb();
return;
}
get(this, 'clients').set(rName, ec2);
data.Subnets.forEach((subnet) => {
if ( (subnet.State||'').toLowerCase() !== 'available' )
{
return;
}
subnets.pushObject(EmberObject.create({
subnetName: nameFromResource(subnet, 'SubnetId'),
subnetId: subnet.SubnetId,
subnetTags: tagsFromResource(subnet),
vpcName: vpcNames[subnet.VpcId] || subnet.VpcId,
vpcId: subnet.VpcId,
vpcTags: vpcTags[subnet.VpcId] || [],
zone: subnet.AvailabilityZone,
region: rName
}));
});
setProperties(this, {
'allSubnets': subnets,
'step': 2,
});
cb();
});
});
},
selectSubnet: function(cb) {
set(this, 'errors',null);
if ( !get(this, 'selectedZone') ) {
set(this, 'errors', ['Select an Availability Zone']);
return;
}
if ( !get(this, 'selectedSubnet') ) {
set(this, 'errors', ['Select a VPC or Subnet']);
return;
}
let ec2 = get(this, 'clients').get(get(this, 'config.region'));
let filter = {Name: 'vpc-id', Values: [ get(this, 'config.vpcId')]};
ec2.describeSecurityGroups({Filters: [filter]}, (err, data) => {
if ( err ) {
set(this, 'errors', [err]);
cb();
return;
}
let groups = [];
data.SecurityGroups.forEach((group) => {
let tags = {};
// Skip launch-wizard groups
if ( (group.GroupName||'').match(/^launch-wizard-.*$/) ) {
return;
}
(group.Tags||[]).forEach((tag) => {
tags[tag.Key] = tag.Value;
});
let obj = {
id : group.GroupId,
name : group.GroupName,
description : group.Description,
};
groups.push(obj);
});
setProperties(this, {
allSecurityGroups: groups,
step: 3,
});
});
},
multiSecurityGroupSelect: function() {
let options = Array.prototype.slice.call($('.existing-security-groups')[0], 0);
let selectedOptions = [];
options.filterBy('selected', true).forEach((cap) => {
return selectedOptions.push(cap.value);
});
set(this, 'selectedSecurityGroup', selectedOptions);
},
selectSecurityGroup: function(cb) {
set(this, 'errors',null);
if ( get(this, 'isCustomSecurityGroup') ) {
set(this, 'config.securityGroup', get(this, 'selectedSecurityGroup'));
} else {
set(this, 'config.securityGroup', '');
}
setProperties(this, {
step: 4,
});
cb();
},
},
selectedZone: computed('config.{region,zone}', {
get: function() {
let config = get(this, 'config');
if ( config.get('region') && config.get('zone') ) {
return config.get('region') + config.get('zone');
} else {
return null;
}
},
set: function(key, val) {
let config = get(this, 'config');
config.setProperties({
region : val.substr(0, val.length - 1),
zone : val.substr(val.length - 1),
});
let selectedSubnet = get(this, 'selectedSubnet');
if ( get(this, 'subnetChoices').filterBy('value', selectedSubnet).length === 0 ) {
config.setProperties({
region : val.substr(0, val.length - 1),
zone : val.substr(val.length - 1),
vpcId : null,
subnetId : null,
});
}
if ( config.get('region') && config.get('zone') ) {
return config.get('region') + config.get('zone');
} else {
return null;
}
}
}),
zoneChoices: function() {
const choices = (get(this, 'allSubnets')||[]).map((subnet) => {return subnet.get('zone');}).sort().uniq();
if ( choices.length ) {
set(this, 'selectedZone', choices[0]);
}
return choices;
}.property('allSubnets.@each.{zone}'),
subnetChoices: function() {
let out = [];
let seenVpcs = [];
(get(this, 'allSubnets')||[]).filterBy('zone', get(this, 'selectedZone')).forEach((subnet) => {
let vpcName = subnet.get('vpcName');
let vpcId = subnet.get('vpcId');
let vpcTags = subnet.get('vpcTags');
let subnetId = subnet.get('subnetId');
let subnetName = subnet.get('subnetName');
let subnetTags = subnet.get('subnetTags');
if ( seenVpcs.indexOf(vpcId) === -1 ) {
seenVpcs.pushObject(vpcId);
out.pushObject({
sortKey : vpcId,
label : vpcName,
value : vpcId,
isVpc : true,
tags : vpcTags
});
}
out.pushObject({
sortKey : `${vpcId} ${subnetName}`,
label : subnetName,
value : subnetId,
isVpc : false,
tags : subnetTags
});
});
return out.sortBy('sortKey');
}.property('selectedZone','allSubnets.@each.{subnetId,vpcId,zone}'),
selectedSubnet: computed('config.{subnetId,vpcId}', {
set: function(key, val) {
let config = get(this, 'config');
if ( arguments.length > 1 ) {
if ( val && val.length ) {
if ( val.indexOf('vpc-') === 0 ) {
config.setProperties({
vpcId : val,
subnetId : null,
});
} else {
let subnet = this.subnetById(val);
config.setProperties({
vpcId : subnet.vpcId,
subnetId : subnet.subnetId,
});
}
} else {
config.setProperties({
vpcId : null,
subnetId : null,
});
}
}
return config.get('subnetId') || config.get('vpcId');
},
get: function() {
let config = get(this, 'config');
return config.get('subnetId') || config.get('vpcId');
},
}),
subnetById: function(id) {
return (get(this, 'allSubnets')||[]).filterBy('subnetId',id)[0];
},
});