This commit is contained in:
Ilya Dmitrichenko
2015-02-17 14:16:38 +00:00
parent a4d871a100
commit a5915438fc
16 changed files with 1094 additions and 0 deletions

View File

@@ -0,0 +1,250 @@
var _ = require('underscore');
var fs = require('fs');
var cp = require('child_process');
var yaml = require('js-yaml');
var openssl = require('openssl-wrapper');
var clr = require('colors');
var inspect = require('util').inspect;
var util = require('./util.js');
var coreos_image_ids = {
'stable': '2b171e93f07c4903bcad35bda10acf22__CoreOS-Stable-557.2.0',
'beta': '2b171e93f07c4903bcad35bda10acf22__CoreOS-Beta-557.2.0',
'alpha': '2b171e93f07c4903bcad35bda10acf22__CoreOS-Alpha-584.0.0',
};
var conf = {};
var hosts = {
collection: [],
ssh_port_counter: 2200,
};
var task_queue = [];
exports.run_task_queue = function (dummy) {
var tasks = {
todo: task_queue,
done: [],
};
var pop_task = function() {
console.log(clr.yellow('azure_wrapper/task:'), clr.grey(inspect(tasks)));
var ret = {};
ret.current = tasks.todo.shift();
ret.remaining = tasks.todo.length;
return ret;
};
(function iter (task) {
if (task.current === undefined) {
if (conf.destroying === undefined) {
create_ssh_conf();
save_state();
}
return;
} else {
if (task.current.length !== 0) {
console.log(clr.yellow('azure_wrapper/exec:'), clr.blue(inspect(task.current)));
cp.fork('node_modules/azure-cli/bin/azure', task.current)
.on('exit', function (code, signal) {
tasks.done.push({
code: code,
signal: signal,
what: task.current.join(' '),
remaining: task.remaining,
});
if (code !== 0 && conf.destroying === undefined) {
console.log(clr.red('azure_wrapper/fail: Exiting due to an error.'));
save_state();
console.log(clr.cyan('azure_wrapper/info: You probably want to destroy and re-run.'));
process.abort();
} else {
iter(pop_task());
}
});
} else {
iter(pop_task());
}
}
})(pop_task());
};
var save_state = function () {
var file_name = util.join_output_file_path(conf.name, 'deployment.yml');
try {
conf.hosts = hosts.collection;
fs.writeFileSync(file_name, yaml.safeDump(conf));
console.log(clr.yellow('azure_wrapper/info: Saved state into `%s`'), file_name);
} catch (e) {
console.log(clr.red(e));
}
};
var load_state = function (file_name) {
try {
conf = yaml.safeLoad(fs.readFileSync(file_name, 'utf8'));
console.log(clr.yellow('azure_wrapper/info: Loaded state from `%s`'), file_name);
return conf;
} catch (e) {
console.log(clr.red(e));
}
};
var create_ssh_key = function (prefix) {
var opts = {
x509: true,
nodes: true,
newkey: 'rsa:2048',
subj: '/O=Weaveworks, Inc./L=London/C=GB/CN=weave.works',
keyout: util.join_output_file_path(prefix, 'ssh.key'),
out: util.join_output_file_path(prefix, 'ssh.pem'),
};
openssl.exec('req', opts, function (err, buffer) {
if (err) console.log(clr.red(err));
fs.chmod(opts.keyout, '0600', function (err) {
if (err) console.log(clr.red(err));
});
});
return {
key: opts.keyout,
pem: opts.out,
}
}
var create_ssh_conf = function () {
var file_name = util.join_output_file_path(conf.name, 'ssh_conf');
var ssh_conf_head = [
"Host *",
"\tHostname " + conf.resources['service'] + ".cloudapp.net",
"\tUser core",
"\tCompression yes",
"\tLogLevel FATAL",
"\tStrictHostKeyChecking no",
"\tUserKnownHostsFile /dev/null",
"\tIdentitiesOnly yes",
"\tIdentityFile " + conf.resources['ssh_key']['key'],
"\n",
];
fs.writeFileSync(file_name, ssh_conf_head.concat(_.map(hosts.collection, function (host) {
return _.template("Host <%= name %>\n\tPort <%= port %>\n")(host);
})).join('\n'));
console.log(clr.yellow('azure_wrapper/info:'), clr.green('Saved SSH config, you can use it like so: `ssh -F ', file_name, '<hostname>`'));
console.log(clr.yellow('azure_wrapper/info:'), clr.green('The hosts in this deployment are:\n'), _.map(hosts.collection, function (host) { return host.name; }));
};
var get_location = function () {
if (process.env['AZ_LOCATION']) {
return '--location=' + process.env['AZ_LOCATION'];
} else {
return '--location=West Europe';
}
}
var get_vm_size = function () {
if (process.env['AZ_VM_SIZE']) {
return '--vm-size=' + process.env['AZ_VM_SIZE'];
} else {
return '--vm-size=Small';
}
}
exports.queue_default_network = function () {
task_queue.push([
'network', 'vnet', 'create',
get_location(),
'--address-space=172.16.0.0',
conf.resources['vnet'],
]);
};
exports.queue_machines = function (name_prefix, coreos_update_channel, cloud_config_creator) {
var x = conf.nodes[name_prefix];
var vm_create_base_args = [
'vm', 'create',
get_location(),
get_vm_size(),
'--connect=' + conf.resources['service'],
'--virtual-network-name=' + conf.resources['vnet'],
'--no-ssh-password',
'--ssh-cert=' + conf.resources['ssh_key']['pem'],
];
var cloud_config = cloud_config_creator(x, conf);
var next_host = function (n) {
hosts.ssh_port_counter += 1;
var host = { name: util.hostname(n, name_prefix), port: hosts.ssh_port_counter };
if (cloud_config instanceof Array) {
host.cloud_config_file = cloud_config[n];
} else {
host.cloud_config_file = cloud_config;
}
hosts.collection.push(host);
return _.map([
"--vm-name=<%= name %>",
"--ssh=<%= port %>",
"--custom-data=<%= cloud_config_file %>",
], function (arg) { return _.template(arg)(host); });
};
task_queue = task_queue.concat(_(x).times(function (n) {
if (conf.resizing && n < conf.old_size) {
return [];
} else {
return vm_create_base_args.concat(next_host(n), [
coreos_image_ids[coreos_update_channel], 'core',
]);
}
}));
};
exports.create_config = function (name, nodes) {
conf = {
name: name,
nodes: nodes,
weave_salt: util.rand_string(),
resources: {
vnet: [name, 'internal-vnet', util.rand_suffix].join('-'),
service: [name, 'service-cluster', util.rand_suffix].join('-'),
ssh_key: create_ssh_key(name),
}
};
};
exports.destroy_cluster = function (state_file) {
load_state(state_file);
if (conf.hosts === undefined) {
console.log(clr.red('azure_wrapper/fail: Nothing to delete.'));
process.abort();
}
conf.destroying = true;
task_queue = _.map(conf.hosts, function (host) {
return ['vm', 'delete', '--quiet', '--blob-delete', host.name];
});
task_queue.push(['network', 'vnet', 'delete', '--quiet', conf.resources['vnet']]);
exports.run_task_queue();
};
exports.load_state_for_resizing = function (state_file, node_type, new_nodes) {
load_state(state_file);
if (conf.hosts === undefined) {
console.log(clr.red('azure_wrapper/fail: Nothing to look at.'));
process.abort();
}
conf.resizing = true;
conf.old_size = conf.nodes[node_type];
conf.old_state_file = state_file;
conf.nodes[node_type] += new_nodes;
hosts.collection = conf.hosts;
hosts.ssh_port_counter += conf.hosts.length;
}

View File

@@ -0,0 +1,43 @@
var _ = require('underscore');
var fs = require('fs');
var yaml = require('js-yaml');
var colors = require('colors/safe');
var write_cloud_config_from_object = function (data, output_file) {
try {
fs.writeFileSync(output_file, [
'#cloud-config',
yaml.safeDump(data),
].join("\n"));
return output_file;
} catch (e) {
console.log(colors.red(e));
}
};
exports.generate_environment_file_entry_from_object = function (hostname, environ) {
var data = {
hostname: hostname,
environ_array: _.map(environ, function (value, key) {
return [key.toUpperCase(), JSON.stringify(value.toString())].join('=');
}),
};
return {
permissions: '0600',
owner: 'root',
content: _.template("<%= environ_array.join('\\n') %>\n")(data),
path: _.template("/etc/weave.<%= hostname %>.env")(data),
};
};
exports.process_template = function (input_file, output_file, processor) {
var data = {};
try {
data = yaml.safeLoad(fs.readFileSync(input_file, 'utf8'));
} catch (e) {
console.log(colors.red(e));
}
return write_cloud_config_from_object(processor(_.clone(data)), output_file);
};

View File

@@ -0,0 +1,44 @@
var _ = require('underscore');
var util = require('../util.js');
var cloud_config = require('../cloud_config.js');
exports.create_etcd_cloud_config = function (node_count, conf) {
var elected_node = 0;
var input_file = './cloud_config_templates/kubernetes-cluster-etcd-node-template.yml';
return _(node_count).times(function (n) {
var output_file = util.join_output_file_path('kubernetes-cluster-etcd-node-' + n, 'generated.yml');
return cloud_config.process_template(input_file, output_file, function(data) {
if (n !== elected_node) {
data.coreos.etcd.peers = [
util.hostname(elected_node, 'etcd'), 7001
].join(':');
}
return data;
});
});
};
exports.create_node_cloud_config = function (node_count, conf) {
var elected_node = 0;
var input_file = './cloud_config_templates/kubernetes-cluster-main-nodes-template.yml';
var output_file = util.join_output_file_path('kubernetes-cluster-main-nodes', 'generated.yml');
var make_node_config = function (n) {
return cloud_config.generate_environment_file_entry_from_object(util.hostname(n, 'kube'), {
weave_password: conf.weave_salt,
weave_peers: n === elected_node ? "" : util.hostname(elected_node, 'kube'),
breakout_route: util.ipv4([10, 2, 0, 0], 16),
bridge_address_cidr: util.ipv4([10, 2, n, 1], 24),
});
};
return cloud_config.process_template(input_file, output_file, function(data) {
data.write_files = data.write_files.concat(_(node_count).times(make_node_config));
return data;
});
};

View File

@@ -0,0 +1,29 @@
var _ = require('underscore');
var util = require('../util.js');
var cloud_config = require('../cloud_config.js');
var write_basic_weave_cluster_cloud_config = function (env_files) {
var input_file = './cloud_config_templates/basic-weave-cluster-template.yml';
var output_file = util.join_output_file_path('basic-weave-cluster', 'generated.yml');
return cloud_config.process_template(input_file, output_file, function(data) {
data.write_files = env_files;
return data;
});
};
exports.create_basic_cloud_config = function (node_count, conf) {
var elected_node = 0;
var make_node_config = function (n) {
return cloud_config.generate_environment_file_entry_from_object(util.hostname(n), {
weavedns_addr: util.ipv4([10, 10, 1, 10+n], 24),
weave_password: conf.weave_salt,
weave_peers: n === elected_node ? "" : util.hostname(elected_node),
});
};
return write_basic_weave_cluster_cloud_config(_(node_count).times(make_node_config));
};

View File

@@ -0,0 +1,33 @@
var _ = require('underscore');
_.mixin(require('underscore.string').exports());
exports.ipv4 = function (ocets, prefix) {
return {
ocets: ocets,
prefix: prefix,
toString: function () {
return [ocets.join('.'), prefix].join('/');
}
}
};
exports.hostname = function hostname (n, prefix) {
return _.template("<%= pre %>-<%= seq %>")({
pre: prefix || 'core',
seq: _.pad(n, 2, '0'),
});
};
exports.rand_string = function () {
var crypto = require('crypto');
var shasum = crypto.createHash('sha256');
shasum.update(crypto.randomBytes(256));
return shasum.digest('hex');
};
exports.rand_suffix = exports.rand_string().substring(50);
exports.join_output_file_path = function(prefix, suffix) {
return './output/' + [prefix, exports.rand_suffix, suffix].join('_');
};