_expand_template(options) { var data = { _keep_key(key) { return "#{" + key + "}"; }, system: { name: this.name, }, manifest: { dir : this.manifest.manifestDirName, path: this.manifest.manifestPath, project_name: this.manifest.manifestDirName, }, azk: { version : version, default_domain: config('agent:balancer:host'), default_dns : net.nameServers(), balancer_port : config('agent:balancer:port'), balancer_ip : config('agent:balancer:ip'), }, env: process.env, }; var template = this._replace_keep_keys(JSON.stringify(options)); return JSON.parse(utils.template(template, data)); }
return async(this, function* () { // Get agent status var agent = yield lazy.Client.status(); var require_vm = config("agent:requires_vm"); // Load configs from agent if (agent.agent) { yield Helpers.requireAgent(this.ui); } // Mount data to render var data = { version: Azk.version, docker: require_vm && !agent.agent ? { Version: this.ui.c.red("down") } : yield this.docker.version(), use_vm: require_vm ? this.ui.c.green("yes") : this.ui.c.yellow("no"), agent_running: agent.agent ? this.ui.c.green("up") : this.ui.c.red("down"), }; if (require_vm && agent.agent) { var ip = config('agent:vm:ip'); data.use_vm = data.use_vm + ', ip: ' + this.ui.c.yellow(ip); } var render = opts.logo ? this.render_with_logo : this.render_normal; this.ui.output(render.apply(this, [data])); return 0; });
return Tools.async_status("balancer", this, function* (change_status) { if (!this.isRunnig()) { var socket = config('paths:memcached_socket'); var ip = net.calculateGatewayIp(config("agent:vm:ip")) var port = yield net.getPort(); // Memcached change_status("starting_memcached"); yield this.start_memcached(socket); change_status("started_memcached"); // Hipache change_status("starting_hipache"); yield this.start_hipache(ip, port, socket); change_status("started_hipache"); // Dns server change_status("starting_dns"); yield this.start_dns(ip, port); change_status("started_dns"); // Socat change_status("starting_socat"); yield this.start_socat(ip, port); change_status("started_socat"); } });
return async(function* () { // Copy structure var tmp = yield h.copyToTmp(h.fixture_path('test-app')); var default_img = config('docker:image_default'); var command = "socat TCP4-LISTEN:$PORT EXEC:`pwd`/src/bashttpd"; var provision = ["ls -l ./src", "./src/bashttpd", "exit 0"]; // Data merge data = _.merge({ systems: { example: { depends: ["db", "api"], workdir: '/azk/<%= manifest.dir %>', image: default_img, mount_folders: true, balancer: true, command, provision, envs: { ECHO_DATA: "data" } }, api: { depends: ["db"], workdir: '/azk/<%= manifest.dir %>', image: default_img, mount_folders: true, balancer: true, command, provision, envs: { ECHO_DATA: "data" } }, db: { workdir: '/azk/<%= manifest.dir %>', image: default_img, persistent_folders: [ "/data" ], mount_folders: false, balancer: false, command, provision, }, empty: { image: config('docker:image_empty'), command: "/bin/false" }, }, default: 'example', bins: [ { name: "console", command: ["bundler", "exec"] } ] }, data); // Read and write var generator = new Generator({}); generator.render(data, path.join(tmp, config('manifest'))); // Return a new project dir return tmp; });
it('should default', function () { var name_servers_options = { resolv_path: false }; var default_nameservers = config('agent:dns:defaultserver'); var full_default_nameservers = [ config("agent:dns:ip") ].concat(default_nameservers); h.expect(full_default_nameservers).to.eql(net_utils.nameServers(name_servers_options)); h.expect(full_default_nameservers).to.eql(config(cache_key)); });
// TODO: Add test static resolvePath(target, point = config('agent:vm:mount_point')) { target = Utils.resolve(target); if (config('agent:requires_vm')) { target = path.join(point, target); } return target; }
return async(this, function* () { var host; if (config('agent:requires_vm')) { host = config('agent:vm:ip'); } else { host = port_data.gateway; } // Wait for available var wait_opts = { timeout: timeout, context: `${system.name}_connect`, retry_if: () => { return container.inspect().then((data) => { return data.State.Running; }); }, }; var address = `tcp://${host}:${port_data.port}`; publish("system.run._wait_available.status", _.merge(port_data, { uri : address, timeout: timeout, name: system.portName(port_data.name), type: "wait_port", system: system.name })); var running = yield net.waitService(address, wait_opts); if (!running) { var data = yield container.inspect(); var exitCode = data.State.ExitCode; var log = t('errors.run_timeout_error', { system: system.name, port: port_data && port_data.port, timeout: timeout, hostname: lazy.colors.underline(system.url), }); // Format command var command = system.printableCommand(data, image_conf); if (exitCode === 0) { throw new SystemRunError( system.name, container, command, exitCode, log ); } else { yield this.throwRunError(system, container, command, null, true, options); } } return true; });
.then(() => { var ssh_url = `${config('agent:vm:user')}@${config('agent:vm:ip')}`; var ssh_opts = "StrictHostKeyChecking=no -o LogLevel=quiet -o UserKnownHostsFile=/dev/null"; var args = (params['ssh-args'] || []).join(`" "`); var script = `ssh -i ${config('agent:vm:ssh_key')} -o ${ssh_opts} ${ssh_url} "${args}"`; log.debug(script); return this.ui.execSh(script); });
return generate_manifest(dir, data).then(function(manifest) { var system = manifest.systemDefault; var re_default = RegExp(h.escapeRegExp(`${system.name}.${config('agent:balancer:host')}`)); var re_custom = RegExp(h.escapeRegExp(`custom.${config('agent:balancer:host')}`)); h.expect(system).to.have.deep.property('hosts').and.length(2); h.expect(system).to.have.deep.property('hosts[0]').and.match(re_default); h.expect(system).to.have.deep.property('hosts[1]').and.match(re_custom); });
return image.inspect().then((image_data) => { var options = system.daemonOptions({ image_data }); h.expect(options).to.have.property("working_dir", "/data"); h.expect(options).to.have.property("command").and.eql(image_data.Config.Cmd); h.expect(options).to.have.deep.property("ports.80/tcp").and.eql([{ HostIp: config('agent:dns:ip') }]); h.expect(options).to.have.deep.property("ports.53/udp").and.eql([{ HostIp: config('agent:dns:ip') }]); });
it("should expand options with template", function() { var system = manifest.system('expand-test'); var provision = system.options.provision; h.expect(provision).to.include(`system.name: ${system.name}`); h.expect(provision).to.include(`system.persistent_folders: /data`); h.expect(provision).to.include(`manifest.dir: ${manifest.manifestDirName}`); h.expect(provision).to.include(`manifest.path: ${manifest.manifestPath}`); h.expect(provision).to.include(`manifest.project_name: ${manifest.manifestDirName}`); h.expect(provision).to.include(`azk.default_domain: ${config('agent:balancer:host')}`); h.expect(provision).to.include(`azk.balancer_port: ${config('agent:balancer:port').toString()}`); h.expect(provision).to.include(`azk.balancer_ip: ${config('agent:balancer:ip')}`); });
return async(this, function* (notify) { var host; if (config('agent:requires_vm')) { host = config('agent:vm:ip'); } else { host = port_data.gateway; } // Wait for available var wait_opts = { timeout: timeout, context: `${system.name}_connect`, retry_if: () => { return container.inspect().then((data) => { return data.State.Running; }); }, }; notify(_.merge(port_data, { name: system.portName(port_data.name), type: "wait_port", system: system.name })); var address = `tcp://${host}:${port_data.port}`; var running = yield net.waitService(address, retry, wait_opts); if (!running) { var data = yield container.inspect(); var exitCode = data.State.ExitCode; if (exitCode === 0) { throw new SystemRunError( system.name, container, data.Config.Cmd.join(' '), exitCode, Q(t('errors.run_timeout_error', { system: system.name, port: port_data && port_data.port, retry: retry, timeout: timeout, hostname: system.url.underline, })) ); } else { yield this.throwRunError(system, container, null, true); } } return true; });
export function extend(Helpers) { var t_regexs = [ RegExp(`${Helpers.escapeRegExp(config('docker:image_empty'))}`), RegExp(`${Helpers.escapeRegExp(config('docker:repository'))}`), ] var filter_tags = (tag) => { return _.some(t_regexs, (regex) => { return tag.match(regex) }); } Helpers.remove_containers = function() { return defer((done) => { return docker.azkListContainers({ all: true }).then((containers) => { done.notify(t('test.remove_containers', containers.length)); return Q.all(_.map(containers, (container) => { var c = docker.getContainer(container.Id); return c.kill().then(() => { return c.remove({ force: true }); }); })); }); }); } Helpers.remove_images = function() { return defer((done) => { return docker.listImages().then((images) => { var tags = _.flatten(_.map( images, (image) => { return image.RepoTags } )); tags = _.filter(tags, filter_tags); done.notify(t('test.remove_images', tags.length)); return Q.all(_.map(tags, (tag) => { return docker.getImage(tag).remove(); })); }); }); } // Remove all containers before run before(function() { this.timeout(0); var progress = (event) => console.log(` ${event}`); var funcs = [ Helpers.remove_containers, Helpers.remove_images, () => console.log("\n") ] return funcs.reduce(Q.when, Q()).progress(progress); }); }
it("should expand options with template", function() { var system = manifest.system('expand-test'); var mount_folders = system.raw_mount_folders; h.expect(mount_folders).to.eql({ "system_name": system.name, "persistent_folder": "/data", "manifest_dir": manifest.manifestDirName, "manifest_project_name": manifest.manifestDirName, "azk_default_domain": config('agent:balancer:host'), "azk_balancer_port": config('agent:balancer:port').toString(), "azk_balancer_ip": config('agent:balancer:ip'), }) });
return image.inspect().then((image_data) => { var options = system.daemonOptions({ image_data }); h.expect(options).to.deep.have.property("ports.5000/tcp").and.eql([{ HostIp: config('agent:dns:ip') }]); h.expect(options).to.deep.have.property("ports.80/tcp").and.eql([{ HostIp: config('agent:dns:ip') }]); h.expect(options).to.deep.have.property("ports.53/tcp").and.eql([{ HostIp: config('agent:dns:ip') }]); });
it("should map system ports to docker ports", function() { var system = manifest.system('ports-test'); var options = system.daemonOptions(); h.expect(options).to.deep.have.property("ports.80/tcp").and.eql([{ HostIp: config('agent:dns:ip') }]); h.expect(options).to.deep.have.property("ports.53/udp").and.eql([{ HostIp: config('agent:dns:ip') }]); h.expect(options).to.deep.have.property("ports.443/tcp").and.eql([{ HostIp: config('agent:dns:ip'), HostPort: "443" }]); });
return async(this, function* () { var manifest = config("manifest"); var cwd = opts.path || this.cwd; var file = path.join(cwd, manifest); var generator = new Generator(this); if (fs.existsSync(file) && !opts.force) { this.fail(this.tKeyPath("already"), manifest); return 1; } var systems = generator.findSystems(cwd); if (_.isEmpty(systems)) { this.fail(this.tKeyPath("not_found")); systems = { [example_system.name]: example_system }; } generator.render({ systems }, file); this.ok(this.tKeyPath('generated'), manifest); // Only show tips if is a git dir if (fs.existsSync(path.join(cwd, ".git"))) this.tOutput(this.tKeyPath('github')); return 0; });
action(opts) { if (opts.filename) { return this.showFilename(); } var manifest = config("manifest"); var cwd = opts.path || this.cwd; var file = path.join(cwd, manifest); var generator = new lazy.Generator(this); if (fs.existsSync(file) && !opts.force) { this.fail(this.tKeyPath("already_exists"), manifest); return 1; } var systemsData = generator.findSystems(cwd); log.debug('generator.findSystems(\'%s\')', cwd); if (_.isEmpty(systemsData)) { this.fail(this.tKeyPath("not_found")); systemsData = { [lazy.example_system.name]: lazy.example_system }; } generator.render({ systems: systemsData }, file); this.ok(this.tKeyPath('generated'), manifest); // Only show tips if is a git dir if (fs.existsSync(path.join(cwd, ".git"))) { this.tOutput(this.tKeyPath('github')); } return 0; }
return _.reduce(ports, (ports, port, name) => { // skip disable if (isBlank(port)) { return ports; } port = XRegExp.exec(port, regex_port); port.protocol = port.protocol || "tcp"; // TODO: Add support a bind ip var conf = { HostIp: config("agent:dns:ip") }; if (_.isEmpty(port.private)) { port.private = port.public; port.public = null; } if (!_.isEmpty(port.public)) { conf.HostPort = port.public; } ports[name] = { config : conf, key : name, name : port.private + "/" + port.protocol, private: port.private }; return ports; }, {});
_mounts_to_volumes(mounts) { var volumes = {}; // persistent folder var persist_base = config('paths:persistent_folders'); persist_base = path.join(persist_base, this.manifest.namespace); return _.reduce(mounts, (volumes, mount, point) => { if (_.isString(mount)) { mount = { type: 'path', value: mount } } var target = null; switch(mount.type) { case 'path': target = mount.value; if (!target.match(/^\//)) { target = path.resolve(this.manifest.manifestPath, target); } target = (fs.existsSync(target)) ? utils.docker.resolvePath(target) : null; break; case 'persistent': target = path.join(persist_base, mount.value); break; } if (!_.isEmpty(target)) { volumes[point] = target; } return volumes; }, volumes); }
it("should bind port", function() { h.expect(instances).to.have.deep.property('[0].Ports[0]'); var port = instances[0].Ports[0]; h.expect(port).to.have.property('IP', config('agent:vm:ip')); h.expect(port).to.have.property('PublicPort'); h.expect(port).to.have.property('Type', 'tcp'); });
get cache_dir() { return path.join( this.cwd, config('azk_dir'), this.file_relative ) }
var generateAndReturnManifest = (project) => { var manifest = path.join(project, config('manifest')); generator.render({ systems: generator.findSystems(project), }, manifest); return new Manifest(project); }
it('should generate a multiple hosts', function() { var data = _.clone(default_data); data.systems.front.http = [ '#{system.name}.#{azk.default_domain}', 'custom.#{azk.default_domain}', ]; var manifest = generate_manifest(dir, data); var system = manifest.systemDefault; var re_default = RegExp(h.escapeRegExp(`${system.name}.${config('agent:balancer:host')}`)); var re_custom = RegExp(h.escapeRegExp(`custom.${config('agent:balancer:host')}`)); h.expect(system).to.have.deep.property('hosts').and.length(2); h.expect(system).to.have.deep.property('hosts[0]').and.match(re_default); h.expect(system).to.have.deep.property('hosts[1]').and.match(re_custom); });
app.get('/configs', (req, res) => { var keys = config('agent:config_keys'); res.json(_.reduce(keys, (acc, key) => { acc[key] = config(key); return acc; }, {})); });
return async(this, function* () { var action = _.head((this.route && this.route.actions)) || options.action; var vm_name = config("agent:vm:name"); var vm_info = yield lazy.VM.info(vm_name); var promise = this[`action_${action}`](vm_info, options); var _subscription = subscribe('vm.action.status', (data) => { Helpers.vmStartProgress(this.ui)(data); }); return promise .then(function (result) { _subscription.unsubscribe(); return result; }) .catch(options.fail || ((error) => { if (error instanceof RequiredError) { this.ui.fail(error.key); return 1; } _subscription.unsubscribe(); throw error; })); });
it("should support custom options", function() { // Customized options var custom = { mounts : { "/azk" : { type: 'path', value: '.' }, "/data" : { type: 'persistent', value: 'data' }, }, workdir : "/azk", envs : { FOO: "BAR" }, sequencies: { daemon: 2 } }; var options = system.daemonOptions(custom); var mounts = options.volumes; var folder = path.join( config("paths:persistent_folders"), manifest.namespace, "data" ); h.expect(options).to.have.property("working_dir", "/azk"); h.expect(options).to.have.property("volumes") h.expect(options).to.have.deep.property("annotations.azk.seq", 2); h.expect(options).to.have.property("env").and.eql({ ECHO_DATA : "data", FROM_DOT_ENV : "azk is beautiful", HTTP_PORT : "5000", FOO : "BAR" }); h.expect(mounts).to.have.property( "/azk", utils.docker.resolvePath(manifest.manifestPath) ); h.expect(mounts).to.have.property("/data", folder); });
constructor(cwd, file = null, required = false) { if (typeof file == "boolean") { [required, file] = [file, null]; } if (required && !cwd) { throw new Error(t("manifest.required_path")); } this.images = {}; this.systems = {}; this.bins = {}; this._default = null; this.file = file || Manifest.find_manifest(cwd); if (required && !this._exist()) { throw new ManifestRequiredError(cwd); } // Create cache for application status if (_.isEmpty(this.cache_dir) && this._exist()) { this.cache_dir = path.join(this.cwd, config('azk_dir'), this._file_relative()); } this.meta = new Meta(this); if (this._exist()) { this.parse(); } }
return async(this, function* () { if (daemon && sync_data.options.daemon === false || !daemon && sync_data.options.shell !== true) { return promiseResolve(); } if (config('agent:requires_vm')) { sync_data.options = _.defaults(sync_data.options, { use_vm: true, ssh: lazy.Client.ssh_opts() }); } var clean_sync_folder = yield this._clean_sync_folder(system, host_folder); if (clean_sync_folder !== 0) { // TODO: throw proper error throw new NotBeenImplementedError('SyncError'); } var pub_data = { system : system.name, host_folder : host_folder, guest_folder: sync_data.guest_folder, options : sync_data.options }; publish(topic, _.assign({ type : "sync_start" }, pub_data)); return lazy.Client .watch(host_folder, sync_data.guest_folder, sync_data.options) .then(() => { publish(topic, _.assign({ type : "sync_done" }, pub_data)); }); });
return defer((done) => { var child_process = require('child_process'); log.debug("Launching agent in daemon mode"); var child = child_process.fork(__filename, [], { silent : true, detached: true, cwd : config('paths:azk_root'), env : _.extend({ }, process.env), }, (err, stdout, stderr) => { if (err) done.reject(err.stack); }); var exit = () => { done.resolve(1); } var msg_cb = (msg) => { log.debug('agent child msg: %s', msg); this.change_status(msg.status, msg.data); if (msg.status == "started") { child.removeListener('exit', exit); child.removeListener('message', msg_cb); child.unref(); return done.resolve(0); } }; child.on('exit', exit); child.on('message', msg_cb); });