ui/lib/nodes/addon/components/node-driver/driver-exoscale/component.js

492 lines
12 KiB
JavaScript

import { alias, equal, gte } from '@ember/object/computed';
import Driver from 'shared/mixins/node-driver';
import { get, set } from '@ember/object';
import { ajaxPromise } from 'ember-api-store/utils/ajax-promise';
import Component from '@ember/component';
import layout from './template';
let RANCHER_GROUP = 'rancher-machine';
let DEFAULT_TEMPLATE = 'Linux Ubuntu 16.04 LTS 64-bit';
let DEFAULT_ZONE = 'ch-dk-2';
let RANCHER_INGRESS_RULES = [
{
startport: 22,
endport: 22,
cidrlist: '0.0.0.0/0',
protocol: 'TCP'
},
{
startport: 80,
endport: 80,
cidrlist: '0.0.0.0/0',
protocol: 'TCP'
},
{
startport: 443,
endport: 443,
cidrlist: '0.0.0.0/0',
protocol: 'TCP'
},
{
startport: 2376,
endport: 2376,
cidrlist: '0.0.0.0/0',
protocol: 'TCP'
},
{
startport: 2379,
endport: 2380,
cidrlist: '0.0.0.0/0',
protocol: 'TCP'
},
{
startport: 8472,
endport: 8472,
cidrlist: '0.0.0.0/0',
protocol: 'UDP'
},
{
startport: 9099,
endport: 9099,
cidrlist: '0.0.0.0/0',
protocol: 'TCP'
},
{
startport: 10250,
endport: 10250,
cidrlist: '0.0.0.0/0',
protocol: 'TCP'
},
{
startport: 10254,
endport: 10254,
cidrlist: '0.0.0.0/0',
protocol: 'TCP'
},
{
startport: 30000,
endport: 32767,
cidrlist: '0.0.0.0/0',
protocol: 'TCP'
},
{
startport: 30000,
endport: 32767,
cidrlist: '0.0.0.0/0',
protocol: 'UDP'
},
{
startport: 6443,
endport: 6443,
cidrlist: '0.0.0.0/0',
protocol: 'TCP'
},
{
icmptype: 8,
icmpcode: 0,
cidrlist: '0.0.0.0/0',
protocol: 'ICMP'
}
];
export default Component.extend(Driver, {
layout,
driverName: 'exoscale',
model: null,
step: 1,
allInstanceProfiles: null,
allTemplates: null,
allZones: null,
selectedZone: null,
defaultZone: DEFAULT_ZONE,
defaultSecurityGroup: RANCHER_GROUP,
allSecurityGroups: [],
securityGroup: [],
whichSecurityGroup: 'default',
exoscaleApi: 'api.exoscale.ch/compute',
config: alias('model.exoscaleConfig'),
isCustomSecurityGroup: equal('whichSecurityGroup', 'custom'),
isStep1: equal('step', 1),
isStep2: equal('step', 2),
isStep3: equal('step', 3),
isStep4: equal('step', 4),
isStep5: equal('step', 5),
isStep6: equal('step', 6),
isStep7: equal('step', 7),
isStep8: equal('step', 8),
isGteStep3: gte('step', 3),
isGteStep4: gte('step', 4),
isGteStep5: gte('step', 5),
isGteStep6: gte('step', 6),
isGteStep7: gte('step', 7),
isGteStep8: gte('step', 8),
willDestroyElement() {
set(this, 'errors', null);
set(this, 'step', 1);
},
actions: {
/* Login step */
exoscaleLogin() {
set(this, 'errors', null);
set(this, 'step', 2);
set(this,
'config.apiKey',
(get(this, 'config.apiKey') || '').trim()
);
set(this,
'config.apiSecretKey',
(get(this, 'config.apiSecretKey') || '').trim()
);
this.apiRequest('listZones').then((res) => {
let zones = [];
let defaultZone = null;
(res.listzonesresponse.zone || []).forEach((zone) => {
let obj = {
id: zone.id,
name: zone.name,
isDefault: zone.name === get(this, 'defaultZone')
};
zones.push(obj);
if (zone.isDefault && !defaultZone) {
defaultZone = obj;
}
});
set(this, 'step', 3);
set(this, 'allZones', zones);
set(this, 'defaultZone', defaultZone);
set(this,
'selectedZone',
get(this, 'config.zone') ||
get(this, 'allZones.firstObject.id')
);
}, (err) => {
let errors = get(this, 'errors') || [];
errors.pushObject(
this.apiErrorMessage(
err,
'listzonesresponse',
'While requesting zones',
'Authentication failure: please check the provided access credentials'
)
);
set(this, 'errors', errors);
set(this, 'step', 1);
}
);
},
/* Zone selection */
selectZone() {
set(this, 'errors', null);
set(this, 'config.zone', get(this, 'selectedZone'));
(get(this, 'allZones') || []).forEach((zone) => {
if (zone.id === get(this, 'selectedZone')) {
set(this, 'config.availabilityZone', zone.name);
}
});
set(this, 'step', 4);
this.allSecurityGroups = []
this.apiRequest('listSecurityGroups').then((res) => {
// Retrieve the list of security groups.
(res.listsecuritygroupsresponse.securitygroup || []).forEach((group) => {
let obj = {
id: group.id,
name: group.name,
description: group.description,
isDefault: group.name === get(this, 'defaultSecurityGroup')
};
this.allSecurityGroups.push(obj);
}
);
// Move to next step
set(this, 'step', 5);
set(this,
'securityGroup',
[get(this, 'config.securityGroup')]);
}, (err) => {
let errors = get(this, 'errors') || [];
errors.pushObject(
this.apiErrorMessage(
err,
'listsecuritygroupsresponse',
'While requesting security groups',
'Authentication failure: please check the provided access credentials'
)
);
set(this, 'errors', errors);
set(this, 'step', 3);
}
);
},
/* Security group selection */
selectSecurityGroup() {
set(this, 'errors', null);
/* When selecting a custom security group, we don't have to do anything more */
if (get(this, 'isCustomSecurityGroup')) {
set(this,
'config.securityGroup',
[get(this, 'securityGroup')]
);
this.fetchInstanceSettings();
return;
}
/* Otherwise, do we need to create the default security group? */
set(this,
'config.securityGroup',
[get(this, 'defaultSecurityGroup')]
);
let group = get(this, 'defaultSecurityGroup');
/* Check if default security group allready exist in all security group*/
var i;
for (i = 0; i < this.allSecurityGroups.length; i++) {
if (this.allSecurityGroups[i].name === group) {
set(this, 'config.securityGroup', [group]);
this.fetchInstanceSettings();
/* Already exists, we assume that it contains the appropriate rules */
return;
}
}
/* We need to create the security group */
set(this, 'step', 6);
this.apiRequest('createSecurityGroup', {
name: get(this, 'defaultSecurityGroup'),
description: `${ get(this, 'settings.appName') } default security group`
}).then((res) => {
return async.eachSeries(
RANCHER_INGRESS_RULES,
(item, cb) => {
item.securitygroupid =
res.createsecuritygroupresponse.securitygroup.id;
this.apiRequest('authorizeSecurityGroupIngress', item).then(() => {
return cb();
}, (err) => {
return cb(err);
}
);
}, (err) => {
if (err) {
let errors = get(this, 'errors') || [];
errors.pushObject(
this.apiErrorMessage(
err,
'authorizesecuritygroupingressresponse',
'While setting default security group',
'Unable to configure the default security group'
)
);
set(this, 'errors', errors);
set(this, 'step', 5);
} else {
this.fetchInstanceSettings();
}
}
);
}, (err) => {
let errors = get(this, 'errors') || [];
errors.pushObject(
this.apiErrorMessage(
err,
'createsecuritygroupresponse',
'While creating default security group',
'Unable to create the default security group'
)
);
set(this, 'errors', errors);
set(this, 'step', 5);
}
);
}
},
afterInit: function() {
this._super();
let cur = get(this, 'config.securityGroup');
if (cur === RANCHER_GROUP) {
this.setProperties({
whichSecurityGroup: 'default',
securityGroup: null
});
} else {
this.setProperties({
whichSecurityGroup: 'custom',
securityGroup: [cur]
});
}
}.on('init'),
bootstrap() {
let config = get(this, 'globalStore').createRecord({
type: 'exoscaleConfig',
apiKey: '',
apiSecretKey: '',
diskSize: 50,
image: DEFAULT_TEMPLATE,
instanceProfile: 'Medium',
securityGroup: RANCHER_GROUP
});
const model = get(this, 'model');
set(model, 'exoscaleConfig', config);
},
fetchInstanceSettings() {
set(this, 'step', 7);
/* First, get a list of templates to get available disk sizes */
this.apiRequest('listTemplates', {
templatefilter: 'featured',
zoneid: get(this, 'config.zone')
}).then((res) => {
set(this,
'allTemplates',
res.listtemplatesresponse.template
.filter((item) => item.name.startsWith('Linux'))
.map((item) => item.name)
.sort()
.uniq()
);
/* Also get the instance types */
return this.apiRequest('listServiceOfferings', { issystem: 'false' }).then((res) => {
set(this,
'allInstanceProfiles',
res.listserviceofferingsresponse.serviceoffering
.sort((a, b) => {
if (a.memory < b.memory) {
return -1;
}
if (b.memory < a.memory) {
return 1;
}
return 0;
})
.map((item) => ({
name: item.name,
displaytext: item.displaytext
}))
);
set(this, 'step', 8);
}, (err) => {
let errors = get(this, 'errors') || [];
errors.pushObject(
this.apiErrorMessage(
err,
'listserviceofferingsresponse',
'While getting list of instance types',
'Unable to get list of instance types'
)
);
set(this, 'errors', errors);
set(this, 'step', 5);
}
);
}, (err) => {
let errors = get(this, 'errors') || [];
errors.pushObject(
this.apiErrorMessage(
err,
'listtemplatesresponse',
'While getting list of available images',
'Unable to get list of available images'
)
);
set(this, 'errors', errors);
set(this, 'step', 5);
}
);
},
apiErrorMessage(err, kind, prefix, def) {
let answer = (err.xhr || {}).responseJSON || {};
let text = (answer[kind] || {}).errortext;
if (text) {
return `${ prefix }: ${ text }`;
} else {
return def;
}
},
apiRequest(command, params) {
let url = `${ get(this, 'app.proxyEndpoint') }/${ this.exoscaleApi }`;
params = params || {};
params.command = command;
params.apiKey = get(this, 'config.apiKey');
params.response = 'json';
return ajaxPromise(
{
url,
method: 'POST',
dataType: 'json',
headers: {
Accept: 'application/json',
'X-API-Headers-Restrict': 'Content-Length'
},
beforeSend: (xhr, settings) => {
// Append 'rancher:' to Content-Type
xhr.setRequestHeader(
'Content-Type',
`rancher:${ settings.contentType }`
);
// Compute the signature
let qs = settings.data
.split('&')
.map((q) => q.replace(/\+/g, '%20'))
.map(Function.prototype.call, String.prototype.toLowerCase)
.sort()
.join('&');
settings.data +=
`&signature=${
encodeURIComponent(
AWS.util.crypto.hmac(
get(this, 'config.apiSecretKey'),
qs,
'base64',
'sha1'
)
) }`;
return true;
},
data: params
},
true
);
}
});