feat: support scale and cb in docker
This commit is contained in:
parent
372665f978
commit
37bd108f40
@ -206,7 +206,7 @@
|
|||||||
createDeploymentGroup(name: String!) : DeploymentGroup
|
createDeploymentGroup(name: String!) : DeploymentGroup
|
||||||
updateDeploymentGroup(id: ID!, name: String!) : DeploymentGroup
|
updateDeploymentGroup(id: ID!, name: String!) : DeploymentGroup
|
||||||
|
|
||||||
provisionManifest(deploymentGroupId: ID!, type: ManifestType!, format: ManifestFormat!, raw: String!) : Version
|
provisionManifest(deploymentGroupId: ID!, type: ManifestType!, format: ManifestFormat!, raw: String!) : Manifest
|
||||||
scale(service: ID!, replicas: Int!) : Version
|
scale(service: ID!, replicas: Int!) : Version
|
||||||
|
|
||||||
stopServices(ids: [ID]!) : [Service]
|
stopServices(ids: [ID]!) : [Service]
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"env": {
|
|
||||||
"test": {
|
|
||||||
"plugins": ["istanbul"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -55,7 +55,7 @@ docker-compose-api
|
|||||||
```js
|
```js
|
||||||
const client = new DockerComposeClient();
|
const client = new DockerComposeClient();
|
||||||
|
|
||||||
const res = await client.provision({
|
client.provision({
|
||||||
projectName: 'docker-compose-client',
|
projectName: 'docker-compose-client',
|
||||||
manifest: `
|
manifest: `
|
||||||
hello:
|
hello:
|
||||||
@ -65,6 +65,8 @@ const res = await client.provision({
|
|||||||
node:
|
node:
|
||||||
image: node:latest
|
image: node:latest
|
||||||
`
|
`
|
||||||
|
}, (err, res, more) => {
|
||||||
|
// can be called multiple times, check 'more' if that is the case
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
42
packages/docker-compose-client/lib/index.js
Normal file
42
packages/docker-compose-client/lib/index.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
const { Client } = require('zerorpc');
|
||||||
|
const EventEmitter = require('events');
|
||||||
|
|
||||||
|
module.exports = class DockerComposeClient extends EventEmitter {
|
||||||
|
constructor(endpoint = 'tcp://0.0.0.0:4242', timeout = 60 * 30) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.client = new Client({
|
||||||
|
heartbeatInterval: 60 * 4 * 1000, // 4m
|
||||||
|
timeout // 30m
|
||||||
|
});
|
||||||
|
|
||||||
|
this.client.on('error', err => this.emit('error', err));
|
||||||
|
this.client.connect(endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
_invoke(method, options, manifest, cb) {
|
||||||
|
return this.client.invoke(method, options, manifest, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
return this.client.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
provision({ projectName, manifest }, cb) {
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
return this._invoke('up', { project_name: projectName }, manifest, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
scale({ projectName, services, manifest }, cb) {
|
||||||
|
const options = {
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
project_name: projectName,
|
||||||
|
services: Object.keys(services).map(name => ({
|
||||||
|
name,
|
||||||
|
num: services[name]
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
return this._invoke('scale', options, manifest, cb);
|
||||||
|
}
|
||||||
|
};
|
@ -3,48 +3,21 @@
|
|||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"repository": "github:yldio/joyent-portal",
|
"repository": "github:yldio/joyent-portal",
|
||||||
"main": "src/index.js",
|
"main": "lib",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint . --fix",
|
"lint": "eslint . --fix",
|
||||||
"lint-ci": "eslint . --format junit --output-file $CIRCLE_TEST_REPORTS/lint/docker-compose-client.xml",
|
"lint-ci": "eslint . --format junit --output-file $CIRCLE_TEST_REPORTS/lint/docker-compose-client.xml",
|
||||||
"test": "cross-env NODE_ENV=test nyc --reporter=lcov --reporter=text ava",
|
"test": "lab -t 100",
|
||||||
"test-ci": "cross-env NODE_ENV=test nyc --report-dir=$CIRCLE_ARTIFACTS/docker-compose-client --reporter=lcov --reporter=text ava --tap | tap-xunit > $CIRCLE_TEST_REPORTS/test/docker-compose-client.xml"
|
"test-ci": "lab -t 100 -r console -o stdout -r tap -o $CIRCLE_TEST_REPORTS/test/docker-compose-client.xml"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"apr-awaitify": "^1.0.4",
|
|
||||||
"zerorpc": "^0.9.7"
|
"zerorpc": "^0.9.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"apr-intercept": "^1.0.4",
|
"code": "^4.0.0",
|
||||||
"ava": "0.19.1",
|
|
||||||
"babel-plugin-istanbul": "^4.1.3",
|
|
||||||
"babel-register": "^6.24.1",
|
|
||||||
"cross-env": "^5.0.0",
|
|
||||||
"eslint": "^3.19.0",
|
"eslint": "^3.19.0",
|
||||||
"eslint-config-joyent-portal": "1.0.0",
|
"eslint-config-joyent-portal": "1.0.0",
|
||||||
"js-yaml": "^3.8.4",
|
"js-yaml": "^3.8.4",
|
||||||
"nyc": "^10.3.2",
|
"lab": "^13.1.0"
|
||||||
"tap-xunit": "^1.7.0"
|
|
||||||
},
|
|
||||||
"nyc": {
|
|
||||||
"sourceMap": false,
|
|
||||||
"instrument": false
|
|
||||||
},
|
|
||||||
"babel": {
|
|
||||||
"sourceMaps": "inline",
|
|
||||||
"env": {
|
|
||||||
"test": {
|
|
||||||
"plugins": [
|
|
||||||
"istanbul"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ava": {
|
|
||||||
"tap": true,
|
|
||||||
"require": [
|
|
||||||
"babel-register"
|
|
||||||
],
|
|
||||||
"babel": "inherit"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
const { Client } = require('zerorpc');
|
|
||||||
const { EventEmitter } = require('events');
|
|
||||||
const awaitify = require('apr-awaitify');
|
|
||||||
|
|
||||||
class DockerComposeClient extends EventEmitter {
|
|
||||||
constructor(endpoint = 'tcp://0.0.0.0:4242') {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.client = new Client({
|
|
||||||
heartbeatInterval: 60 * 4 * 1000, // 4m
|
|
||||||
timeout: 60 * 30 // 30m
|
|
||||||
});
|
|
||||||
|
|
||||||
this.client.connect(endpoint);
|
|
||||||
this.client.on('error', err => this.emit('error', err));
|
|
||||||
|
|
||||||
this._invoke = awaitify(this._invoke.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Why isn't client.connect async with error??
|
|
||||||
_invoke(name, ...args) {
|
|
||||||
return this.client.invoke(name, ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
close() {
|
|
||||||
return this.client.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
provision({ projectName, manifest }) {
|
|
||||||
// eslint-disable-next-line camelcase
|
|
||||||
return this._invoke('up', { project_name: projectName }, manifest);
|
|
||||||
}
|
|
||||||
|
|
||||||
scale({ projectName, services, manifest }) {
|
|
||||||
return this._invoke(
|
|
||||||
'scale',
|
|
||||||
{
|
|
||||||
// eslint-disable-next-line camelcase
|
|
||||||
project_name: projectName,
|
|
||||||
services: Object.keys(services).map(name => ({
|
|
||||||
name,
|
|
||||||
num: services[name]
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
manifest
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = DockerComposeClient;
|
|
@ -1,11 +1,23 @@
|
|||||||
const { name } = require('../package.json');
|
'use strict';
|
||||||
|
|
||||||
|
const { expect } = require('code');
|
||||||
|
const Lab = require('lab');
|
||||||
|
const Package = require('../package.json');
|
||||||
const { safeLoad } = require('js-yaml');
|
const { safeLoad } = require('js-yaml');
|
||||||
const { Server } = require('zerorpc');
|
const { Server } = require('zerorpc');
|
||||||
const intercept = require('apr-intercept');
|
|
||||||
const test = require('ava');
|
|
||||||
|
|
||||||
|
|
||||||
|
// Test shortcuts
|
||||||
|
|
||||||
|
const lab = exports.lab = Lab.script();
|
||||||
|
const after = lab.after;
|
||||||
|
const it = lab.it;
|
||||||
|
|
||||||
|
|
||||||
|
const projectName = Package.name;
|
||||||
|
const endpoint = 'tcp://0.0.0.0:4040';
|
||||||
const DockerComposeClient = require('../');
|
const DockerComposeClient = require('../');
|
||||||
const client = new DockerComposeClient();
|
const client = new DockerComposeClient(endpoint);
|
||||||
|
|
||||||
const server = new Server({
|
const server = new Server({
|
||||||
// eslint-disable-next-line object-shorthand
|
// eslint-disable-next-line object-shorthand
|
||||||
@ -63,58 +75,65 @@ const server = new Server({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
server.bind('tcp://0.0.0.0:4242');
|
server.bind(endpoint);
|
||||||
|
|
||||||
test('provision', async t => {
|
it('provision()', (done) => {
|
||||||
const [err, res] = await intercept(
|
const manifest = `
|
||||||
client.provision({
|
|
||||||
projectName: name,
|
|
||||||
manifest: `
|
|
||||||
hello:
|
hello:
|
||||||
image: hello-world:latest
|
image: hello-world:latest
|
||||||
world:
|
world:
|
||||||
image: consul:latest
|
image: consul:latest
|
||||||
node:
|
node:
|
||||||
image: node:latest
|
image: node:latest
|
||||||
`
|
`;
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
t.ifError(err);
|
client.provision({ projectName, manifest }, (err, res) => {
|
||||||
|
expect(err).to.not.exist();
|
||||||
|
|
||||||
t.deepEqual(res, {
|
expect(res.projectName).to.equal(projectName);
|
||||||
projectName: name
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('scale', async t => {
|
it('scale()', (done) => {
|
||||||
const [err, res] = await intercept(
|
const manifest = `
|
||||||
|
hello:
|
||||||
|
image: hello-world:latest
|
||||||
|
world:
|
||||||
|
image: consul:latest
|
||||||
|
node:
|
||||||
|
image: node:latest
|
||||||
|
`;
|
||||||
|
|
||||||
client.scale({
|
client.scale({
|
||||||
projectName: name,
|
projectName,
|
||||||
services: {
|
services: {
|
||||||
hello: 2,
|
hello: 2,
|
||||||
world: 3
|
world: 3
|
||||||
},
|
},
|
||||||
manifest: `
|
manifest
|
||||||
hello:
|
}, (err, res) => {
|
||||||
image: hello-world:latest
|
expect(err).to.not.exist();
|
||||||
world:
|
|
||||||
image: consul:latest
|
|
||||||
node:
|
|
||||||
image: node:latest
|
|
||||||
`
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
t.ifError(err);
|
expect(res).to.equal({
|
||||||
|
projectName,
|
||||||
t.deepEqual(res, {
|
|
||||||
projectName: name,
|
|
||||||
services: [{ name: 'hello', num: 2 }, { name: 'world', num: 3 }]
|
services: [{ name: 'hello', num: 2 }, { name: 'world', num: 3 }]
|
||||||
});
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.after(() => {
|
it('handles errors', (done) => {
|
||||||
|
client.once('error', (err) => {
|
||||||
|
expect(err).to.exist();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
client.client.emit('error', new Error('test'));
|
||||||
|
});
|
||||||
|
|
||||||
|
after((done) => {
|
||||||
client.close();
|
client.close();
|
||||||
server.close();
|
server.close();
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const EventEmitter = require('events');
|
const EventEmitter = require('events');
|
||||||
|
const DockerClient = require('docker-compose-client');
|
||||||
const Hoek = require('hoek');
|
const Hoek = require('hoek');
|
||||||
const Penseur = require('penseur');
|
const Penseur = require('penseur');
|
||||||
const VAsync = require('vasync');
|
const VAsync = require('vasync');
|
||||||
@ -34,6 +35,11 @@ module.exports = class Data extends EventEmitter {
|
|||||||
|
|
||||||
// Penseur will assert that the options are correct
|
// Penseur will assert that the options are correct
|
||||||
this._db = new Penseur.Db(settings.name, settings.db);
|
this._db = new Penseur.Db(settings.name, settings.db);
|
||||||
|
this._docker = new DockerClient(settings.dockerHost);
|
||||||
|
|
||||||
|
this._docker.on('error', (err) => {
|
||||||
|
this.emit('error', err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
connect (cb) {
|
connect (cb) {
|
||||||
@ -163,12 +169,24 @@ module.exports = class Data extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getDeploymentGroup (query, cb) {
|
getDeploymentGroup (query, cb) {
|
||||||
|
this._db.deployment_groups.sync(() => {
|
||||||
this._db.deployment_groups.single(query, (err, deploymentGroup) => {
|
this._db.deployment_groups.single(query, (err, deploymentGroup) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
cb(null, Transform.fromDeploymentGroup(deploymentGroup || {}));
|
if (!deploymentGroup) {
|
||||||
|
return cb(null, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!deploymentGroup.service_ids || !deploymentGroup.service_ids.length) {
|
||||||
|
return cb(null, Transform.fromDeploymentGroup(deploymentGroup));
|
||||||
|
}
|
||||||
|
|
||||||
|
this._db.services.get(deploymentGroup.service_ids, (err, services) => {
|
||||||
|
cb(err, Transform.fromDeploymentGroup(deploymentGroup, services));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,13 +262,168 @@ module.exports = class Data extends EventEmitter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scale ({ id, replicas }, cb) {
|
||||||
|
Hoek.assert(id, 'service id is required');
|
||||||
|
Hoek.assert(typeof replicas === 'number' && replicas >= 0, 'replicas must be a number no less than 0');
|
||||||
|
|
||||||
|
|
||||||
|
// get the service then get the deployment group
|
||||||
|
// use the deployment group to find the current version and manifest
|
||||||
|
// scale the service
|
||||||
|
// update the machine ids and instances
|
||||||
|
|
||||||
|
this._db.services.single({ id }, (err, service) => {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!service) {
|
||||||
|
return cb(new Error(`service not found for id: ${id}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
this._db.deployment_groups.single({ id: service.deployment_group_id }, (err, deployment_group) => {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!deployment_group) {
|
||||||
|
return cb(new Error(`deployment group not found for service with service id: ${id}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
this._db.versions.single({ id: deployment_group.version_id }, (err, version) => {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!version) {
|
||||||
|
return cb(new Error(`version not found for service with service id: ${id}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
this._db.manifests.single({ id: version.manifest_id }, (err, manifest) => {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!manifest) {
|
||||||
|
return cb(new Error(`manifest not found for service with service id: ${id}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
this._scale({ service, deployment_group, version, manifest, replicas }, cb);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_scale ({ service, deployment_group, version, manifest, replicas }, cb) {
|
||||||
|
let isFinished = false;
|
||||||
|
const finish = () => {
|
||||||
|
if (isFinished) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isFinished = true;
|
||||||
|
const machineIds = [];
|
||||||
|
for (let i = 1; i <= replicas; ++i) {
|
||||||
|
machineIds.push(`${deployment_group.name}_${service.name}_${i}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._db.instances.remove(service.instance_ids, (err) => {
|
||||||
|
// emit error instead of returning early, this is a best effort to cleanup data
|
||||||
|
if (err) {
|
||||||
|
this.emit('error', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
VAsync.forEachParallel({
|
||||||
|
func: (machineId, next) => {
|
||||||
|
const clientInstance = {
|
||||||
|
machineId,
|
||||||
|
status: 'CREATED',
|
||||||
|
name: service.name
|
||||||
|
};
|
||||||
|
this.createInstance(clientInstance, next);
|
||||||
|
},
|
||||||
|
inputs: machineIds
|
||||||
|
}, (err, results) => {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const instanceIds = results.successes.map((instance) => {
|
||||||
|
return instance.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
this._db.services.update(service.id, { instance_ids: instanceIds }, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientVersion = {
|
||||||
|
deploymentGroupId: deployment_group.id,
|
||||||
|
manifestId: manifest.id,
|
||||||
|
plan: {
|
||||||
|
running: true,
|
||||||
|
actions: [{
|
||||||
|
type: 'CREATE',
|
||||||
|
service: service.name,
|
||||||
|
machines: machineIds
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const scale = version.service_scales.find((scale) => {
|
||||||
|
return scale.service_name === service.name;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (scale) {
|
||||||
|
scale.replicas = replicas;
|
||||||
|
} else {
|
||||||
|
version.service_scales.push({
|
||||||
|
service_name: service.name,
|
||||||
|
replicas
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clientVersion.scales = version.service_scales.map(Transform.fromScale);
|
||||||
|
|
||||||
|
this.createVersion(clientVersion, cb);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
provisionName: deployment_group.name,
|
||||||
|
services: {},
|
||||||
|
manifest: manifest.raw
|
||||||
|
};
|
||||||
|
options.services[service.name] = replicas;
|
||||||
|
this._docker.scale(options, (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// manifests
|
// manifests
|
||||||
|
|
||||||
provisionManifest (clientManifest, cb) {
|
provisionManifest (clientManifest, cb) {
|
||||||
|
// get deployment group to verify it exists and get the name
|
||||||
// insert manifest
|
// insert manifest
|
||||||
// callback with manifest
|
// callback with manifest
|
||||||
// provision services
|
// provision containers and save service data
|
||||||
|
|
||||||
|
this.getDeploymentGroup({ id: clientManifest.deploymentGroupId }, (err, deploymentGroup) => {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!deploymentGroup) {
|
||||||
|
return cb(new Error('Deployment group not found for manifest'));
|
||||||
|
}
|
||||||
|
|
||||||
const manifest = Transform.toManifest(clientManifest);
|
const manifest = Transform.toManifest(clientManifest);
|
||||||
this._db.manifests.insert(manifest, (err, key) => {
|
this._db.manifests.insert(manifest, (err, key) => {
|
||||||
@ -259,21 +432,35 @@ module.exports = class Data extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
|
let isHandled = false;
|
||||||
|
this._docker.provision({ projectName: deploymentGroup.name, manifest: clientManifest.raw }, (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
this.emit('error', err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// callback can execute multiple times, ensure responses are only handled once
|
||||||
|
if (isHandled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isHandled = true;
|
||||||
const options = {
|
const options = {
|
||||||
manifestServices: manifest.json.services || manifest.json,
|
manifestServices: manifest.json.services || manifest.json,
|
||||||
deploymentGroupId: clientManifest.deploymentGroupId,
|
deploymentGroup,
|
||||||
manifestId: key
|
manifestId: key
|
||||||
};
|
};
|
||||||
this.provisionServices(options);
|
this.provisionServices(options);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
manifest.id = key;
|
manifest.id = key;
|
||||||
cb(null, Transform.fromManifest(manifest));
|
cb(null, Transform.fromManifest(manifest));
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getManifest ({ id }, cb) {
|
getManifest ({ id }, cb) {
|
||||||
console.log(id);
|
|
||||||
this._db.manifests.single({ id }, (err, manifest) => {
|
this._db.manifests.single({ id }, (err, manifest) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
@ -298,8 +485,7 @@ module.exports = class Data extends EventEmitter {
|
|||||||
|
|
||||||
// services
|
// services
|
||||||
|
|
||||||
provisionServices ({ manifestServices, deploymentGroupId, manifestId }, cb) {
|
provisionServices ({ manifestServices, deploymentGroup, manifestId }, cb) {
|
||||||
// call to docker and create containers
|
|
||||||
// insert instance information
|
// insert instance information
|
||||||
// insert service information
|
// insert service information
|
||||||
// insert version information -- will update deploymentGroups
|
// insert version information -- will update deploymentGroups
|
||||||
@ -310,14 +496,12 @@ module.exports = class Data extends EventEmitter {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: call out to docker for each service and provision a new instance
|
|
||||||
|
|
||||||
VAsync.forEachPipeline({
|
VAsync.forEachPipeline({
|
||||||
func: (serviceName, next) => {
|
func: (serviceName, next) => {
|
||||||
const manifestService = manifestServices[serviceName];
|
const manifestService = manifestServices[serviceName];
|
||||||
const clientInstance = {
|
const clientInstance = {
|
||||||
name: serviceName,
|
name: serviceName,
|
||||||
machineId: 'unknown',
|
machineId: `${deploymentGroup.name}_${serviceName}_1`,
|
||||||
status: 'CREATED'
|
status: 'CREATED'
|
||||||
};
|
};
|
||||||
this.createInstance(clientInstance, (err, createdInstance) => {
|
this.createInstance(clientInstance, (err, createdInstance) => {
|
||||||
@ -329,7 +513,7 @@ module.exports = class Data extends EventEmitter {
|
|||||||
hash: manifestService.image,
|
hash: manifestService.image,
|
||||||
name: serviceName,
|
name: serviceName,
|
||||||
slug: serviceName,
|
slug: serviceName,
|
||||||
deploymentGroupId,
|
deploymentGroupId: deploymentGroup.id,
|
||||||
instances: [createdInstance]
|
instances: [createdInstance]
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -381,19 +565,19 @@ module.exports = class Data extends EventEmitter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const clientVersion = {
|
const clientVersion = {
|
||||||
deploymentGroupId,
|
deploymentGroupId: deploymentGroup.id,
|
||||||
manifestId,
|
manifestId,
|
||||||
scales,
|
scales,
|
||||||
plan,
|
plan,
|
||||||
serviceIds
|
serviceIds
|
||||||
};
|
};
|
||||||
|
|
||||||
this.createVersion(clientVersion, (err) => {
|
this.createVersion(clientVersion, (err, version) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
cb();
|
cb(null, version);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -9,13 +9,14 @@
|
|||||||
"bootstrap": "node ./bootstrap-data",
|
"bootstrap": "node ./bootstrap-data",
|
||||||
"lint": "belly-button --fix",
|
"lint": "belly-button --fix",
|
||||||
"lint-ci": "belly-button",
|
"lint-ci": "belly-button",
|
||||||
"test": "lab -t 40",
|
"test": "lab -c",
|
||||||
"test-ci": "echo 0"
|
"test-ci": "lab -c -r console -o stdout -r tap -o $CIRCLE_TEST_REPORTS/test/portal-data.xml"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "wyatt",
|
"author": "wyatt",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"docker-compose-client": "^1.0.7",
|
||||||
"hoek": "^4.1.1",
|
"hoek": "^4.1.1",
|
||||||
"penseur": "^7.8.1",
|
"penseur": "^7.8.1",
|
||||||
"vasync": "^1.6.4",
|
"vasync": "^1.6.4",
|
||||||
|
@ -1,24 +1,2 @@
|
|||||||
version: '2.1'
|
hello:
|
||||||
|
image: hello-world:latest
|
||||||
services:
|
|
||||||
|
|
||||||
# Service definition for Consul cluster with a minimum of 3 nodes.
|
|
||||||
# Nodes will use Triton CNS for the service (passed in via the CONSUL
|
|
||||||
# env var) to find each other and bootstrap the cluster.
|
|
||||||
consul:
|
|
||||||
image: autopilotpattern/consul:${TAG:-latest}
|
|
||||||
labels:
|
|
||||||
- triton.cns.services=consul
|
|
||||||
restart: always
|
|
||||||
mem_limit: 128m
|
|
||||||
ports:
|
|
||||||
- 8500
|
|
||||||
env_file:
|
|
||||||
- _env
|
|
||||||
network_mode: bridge
|
|
||||||
command: >
|
|
||||||
/usr/local/bin/containerpilot
|
|
||||||
/bin/consul agent -server
|
|
||||||
-bootstrap-expect 3
|
|
||||||
-config-dir=/etc/consul
|
|
||||||
-ui-dir /ui
|
|
||||||
|
@ -378,7 +378,7 @@ describe('versions', () => {
|
|||||||
expect(result.scales).to.equal(clientVersion.scales);
|
expect(result.scales).to.equal(clientVersion.scales);
|
||||||
data.getVersions({ manifestId: clientVersion.manifestId }, (err, versions) => {
|
data.getVersions({ manifestId: clientVersion.manifestId }, (err, versions) => {
|
||||||
expect(err).to.not.exist();
|
expect(err).to.not.exist();
|
||||||
expect(versions.length).to.equal(2);
|
expect(versions.length).to.equal(1);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -502,8 +502,7 @@ describe('manifests', () => {
|
|||||||
deploymentGroupId: deploymentGroup.id,
|
deploymentGroupId: deploymentGroup.id,
|
||||||
type: 'compose',
|
type: 'compose',
|
||||||
format: 'yml',
|
format: 'yml',
|
||||||
raw: 'docker compose raw contents',
|
raw: internals.composeFile
|
||||||
json: { services: [] }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
data.provisionManifest(clientManifest, (err, result) => {
|
data.provisionManifest(clientManifest, (err, result) => {
|
||||||
@ -677,3 +676,35 @@ describe('packages', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// skipping by default since it takes so long
|
||||||
|
describe.skip('scale()', () => {
|
||||||
|
it('creates new instances of a service', { timeout: 180000 }, (done) => {
|
||||||
|
const data = new PortalData(internals.options);
|
||||||
|
data.connect((err) => {
|
||||||
|
expect(err).to.not.exist();
|
||||||
|
data.createDeploymentGroup({ name: 'something' }, (err, deploymentGroup) => {
|
||||||
|
expect(err).to.not.exist();
|
||||||
|
const clientManifest = {
|
||||||
|
deploymentGroupId: deploymentGroup.id,
|
||||||
|
type: 'compose',
|
||||||
|
format: 'yml',
|
||||||
|
raw: internals.composeFile
|
||||||
|
};
|
||||||
|
data.provisionManifest(clientManifest, (err, manifest) => {
|
||||||
|
expect(err).to.not.exist();
|
||||||
|
setTimeout(() => {
|
||||||
|
data.getDeploymentGroup({ id: deploymentGroup.id }, (err, deploymentGroup) => {
|
||||||
|
expect(err).to.not.exist();
|
||||||
|
data.scale({ id: deploymentGroup.services[0].id, replicas: 2 }, (err, version) => {
|
||||||
|
expect(err).to.not.exist();
|
||||||
|
expect(version).to.exist();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, 80000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -51,6 +51,10 @@ ansi-styles@^2.2.1:
|
|||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
|
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
|
||||||
|
|
||||||
|
apr-awaitify@^1.0.4:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/apr-awaitify/-/apr-awaitify-1.0.4.tgz#a72074a0d333e090bb120be9f710fd106b48a90a"
|
||||||
|
|
||||||
argparse@^1.0.7:
|
argparse@^1.0.7:
|
||||||
version "1.0.9"
|
version "1.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
|
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
|
||||||
@ -97,6 +101,10 @@ belly-button@^3.1.0:
|
|||||||
glob "7.x.x"
|
glob "7.x.x"
|
||||||
insync "2.x.x"
|
insync "2.x.x"
|
||||||
|
|
||||||
|
bindings@~1.2.1:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.2.1.tgz#14ad6113812d2d37d72e67b4cacb4bb726505f11"
|
||||||
|
|
||||||
"bluebird@>= 2.3.2 < 3":
|
"bluebird@>= 2.3.2 < 3":
|
||||||
version "2.11.0"
|
version "2.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1"
|
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1"
|
||||||
@ -244,6 +252,13 @@ diff@3.x.x:
|
|||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9"
|
resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9"
|
||||||
|
|
||||||
|
docker-compose-client@^1.0.7:
|
||||||
|
version "1.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/docker-compose-client/-/docker-compose-client-1.0.7.tgz#a2f351aff998fd5323b9b6bb27d4400fff95e43c"
|
||||||
|
dependencies:
|
||||||
|
apr-awaitify "^1.0.4"
|
||||||
|
zerorpc "^0.9.7"
|
||||||
|
|
||||||
doctrine@^2.0.0:
|
doctrine@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63"
|
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63"
|
||||||
@ -737,10 +752,24 @@ ms@2.0.0:
|
|||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
|
|
||||||
|
msgpack@1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/msgpack/-/msgpack-1.0.2.tgz#923e2c5cffa65c8418e9b228d1124793969c429c"
|
||||||
|
dependencies:
|
||||||
|
nan "^2.0.9"
|
||||||
|
|
||||||
mute-stream@0.0.5:
|
mute-stream@0.0.5:
|
||||||
version "0.0.5"
|
version "0.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0"
|
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0"
|
||||||
|
|
||||||
|
nan@^2.0.9:
|
||||||
|
version "2.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45"
|
||||||
|
|
||||||
|
nan@~2.3.0:
|
||||||
|
version "2.3.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/nan/-/nan-2.3.5.tgz#822a0dc266290ce4cd3a12282ca3e7e364668a08"
|
||||||
|
|
||||||
natural-compare@^1.4.0:
|
natural-compare@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||||
@ -1059,6 +1088,10 @@ uglify-to-browserify@~1.0.0:
|
|||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
|
resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
|
||||||
|
|
||||||
|
underscore@1.3.3:
|
||||||
|
version "1.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.3.3.tgz#47ac53683daf832bfa952e1774417da47817ae42"
|
||||||
|
|
||||||
user-home@^2.0.0:
|
user-home@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f"
|
resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f"
|
||||||
@ -1069,6 +1102,10 @@ util-deprecate@~1.0.1:
|
|||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||||
|
|
||||||
|
uuid@^3.0.0:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1"
|
||||||
|
|
||||||
vasync@^1.6.4:
|
vasync@^1.6.4:
|
||||||
version "1.6.4"
|
version "1.6.4"
|
||||||
resolved "https://registry.yarnpkg.com/vasync/-/vasync-1.6.4.tgz#dfe93616ad0e7ae801b332a9d88bfc5cdc8e1d1f"
|
resolved "https://registry.yarnpkg.com/vasync/-/vasync-1.6.4.tgz#dfe93616ad0e7ae801b332a9d88bfc5cdc8e1d1f"
|
||||||
@ -1126,3 +1163,19 @@ yargs@~3.10.0:
|
|||||||
cliui "^2.1.0"
|
cliui "^2.1.0"
|
||||||
decamelize "^1.0.0"
|
decamelize "^1.0.0"
|
||||||
window-size "0.1.0"
|
window-size "0.1.0"
|
||||||
|
|
||||||
|
zerorpc@^0.9.7:
|
||||||
|
version "0.9.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/zerorpc/-/zerorpc-0.9.7.tgz#64ddb32ce8c934bea5434ec81ca22e971045a860"
|
||||||
|
dependencies:
|
||||||
|
msgpack "1.0.2"
|
||||||
|
underscore "1.3.3"
|
||||||
|
uuid "^3.0.0"
|
||||||
|
zmq "2.x"
|
||||||
|
|
||||||
|
zmq@2.x:
|
||||||
|
version "2.15.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/zmq/-/zmq-2.15.3.tgz#66c6de82cc36b09734b820703776490a6fbbe624"
|
||||||
|
dependencies:
|
||||||
|
bindings "~1.2.1"
|
||||||
|
nan "~2.3.0"
|
||||||
|
Loading…
Reference in New Issue
Block a user