Skip to content
This repository has been archived by the owner on Feb 12, 2022. It is now read-only.

Commit

Permalink
push multiple, recursive, choose nearest, warn multiple, fail ambiguo…
Browse files Browse the repository at this point in the history
…us Dockerfile matches
  • Loading branch information
hunterloftis committed Oct 4, 2016
1 parent 4d70559 commit d7cf202
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 29 deletions.
6 changes: 3 additions & 3 deletions commands/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const cli = require('heroku-cli-util');
const co = require('co');
const child = require('child_process');
const log = require('../lib/log');

module.exports = function(topic) {
return {
Expand All @@ -26,6 +27,7 @@ function* login(context, heroku) {
}
catch (err) {
cli.error(`Error: docker login exited with ${ err }`);
cli.hush(err.stack || err);
}
}

Expand All @@ -37,9 +39,7 @@ function dockerLogin(registry, password, verbose) {
`--password=${ password }`,
registry
];
if (verbose) {
console.log(['> docker'].concat(args).join(' '));
}
log(verbose, args);
child.spawn('docker', args, { stdio: 'inherit' })
.on('exit', (code, signal) => {
if (signal || code) reject(signal || code);
Expand Down
5 changes: 2 additions & 3 deletions commands/logout.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const cli = require('heroku-cli-util');
const co = require('co');
const child = require('child_process');
const log = require('../lib/log');

module.exports = function(topic) {
return {
Expand Down Expand Up @@ -34,9 +35,7 @@ function dockerLogout(registry, verbose) {
'logout',
registry
];
if (verbose) {
console.log(['> docker'].concat(args).join(' '));
}
log(verbose, args);
child.spawn('docker', args, { stdio: 'inherit' })
.on('exit', (code, signal) => {
if (signal || code) reject(signal || code);
Expand Down
114 changes: 91 additions & 23 deletions commands/push.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
const cli = require('heroku-cli-util');
const co = require('co');
const child = require('child_process');
const log = require('../lib/log');
const fs = require('fs');
const path = require('path');
const glob = require('glob');

const DOCKERFILE_REGEX = /\/Dockerfile(.\w*)?$/;

module.exports = function(topic) {
return {
Expand All @@ -11,46 +17,57 @@ module.exports = function(topic) {
description: 'Builds, then pushes a Docker image to deploy your Heroku app',
needsApp: true,
needsAuth: true,
args: [{ name: 'process', optional: true }],
flags: [{ name: 'verbose', char: 'v', hasValue: false }],
variableArgs: true,
flags: [
{ name: 'verbose', char: 'v', hasValue: false }
],
run: cli.command(co.wrap(push))
};
};

function* push(context, heroku) {
let herokuHost = process.env.HEROKU_HOST || 'heroku.com';
let registry = `registry.${ herokuHost }`;
let proc = context.args.process || 'web';
let resource = `${ registry }/${ context.app }/${ proc }`;
let dockerfiles = getDockerfiles(context.cwd, true);
let possibleJobs = getJobs(`${ registry }/${ context.app }`, context.args, dockerfiles);
let jobs = chooseJobs(possibleJobs);

if (!jobs.length) {
cli.warn('No images to push');
process.exit();
}

try {
let build = yield buildImage(resource, context.cwd, context.flags.verbose);
for (let job of jobs) {
cli.log(`Building ${ job.name } (${ job.dockerfile })`);
yield buildImage(job.dockerfile, job.resource, context.flags.verbose);
}
}
catch (err) {
cli.error(`Error: docker build exited with ${ err }`);
cli.hush(err.stack || err);
process.exit(1);
}

try {
let push = yield pushImage(resource, context.flags.verbose);
cli.log(`Pushing ${ jobs.length } images...`);
for (let job of jobs) {
cli.log(`Pushing ${ job.name } (${ job.dockerfile })`);
yield pushImage(job.resource, context.flags.verbose);
}
}
catch (err) {
cli.error(`Error: docker push exited with ${ err }`);
cli.hush(err.stack || err);
process.exit(1);
}
}

function buildImage(resource, cwd, verbose) {
function buildImage(dockerfile, resource, verbose) {
return new Promise((resolve, reject) => {
let args = [
'build',
'-t',
resource,
cwd
];
if (verbose) {
console.log(['> docker'].concat(args).join(' '));
}
let cwd = path.dirname(dockerfile);
let args = [ 'build', '-f', dockerfile, '-t', resource, cwd ];
log(verbose, args);
child.spawn('docker', args, { stdio: 'inherit' })
.on('exit', (code, signal) => {
if (signal || code) reject(signal || code);
Expand All @@ -61,17 +78,68 @@ function buildImage(resource, cwd, verbose) {

function pushImage(resource, verbose) {
return new Promise((resolve, reject) => {
let args = [
'push',
resource
];
if (verbose) {
console.log(['> docker'].concat(args).join(' '));
}
let args = [ 'push', resource ];
log(verbose, args);
child.spawn('docker', args, { stdio: 'inherit' })
.on('exit', (code, signal) => {
if (signal || code) reject(signal || code);
else resolve();
});
});
}

function getDockerfiles(dir, recursive) {
let match = recursive ? '**/Dockerfile?(.)*' : 'Dockerfile?(.)*';
let dockerfiles = glob.sync(match, { cwd: dir, nonull: false, nodir: true });
return dockerfiles.map(file => path.join(dir, file));
}

function getJobs(resourceRoot, procs, dockerfiles) {
return dockerfiles
// convert all Dockerfiles into job Objects
.map((dockerfile) => {
let match = dockerfile.match(DOCKERFILE_REGEX);
if (!match) return;
let proc = (match[1] || '.web').slice(1);
return {
name: proc,
resource: `${ resourceRoot }/${ proc }`,
dockerfile: dockerfile,
postfix: path.basename(dockerfile) === 'Dockerfile' ? 0 : 1,
depth: path.normalize(dockerfile).split(path.sep).length
};
})
// if process types have been specified, filter non matches out
.filter(job => {
return job && (!procs.length || procs.indexOf(job.name) !== -1);
})
// prefer closer Dockerfiles, then prefer Dockerfile over Dockerfile.web
.sort((a, b) => {
return a.depth - b.depth || a.postfix - b.postfix;
})
// group all Dockerfiles for the same process type together
.reduce((jobs, job) => {
jobs[job.name] = jobs[job.name] || [];
jobs[job.name].push(job);
return jobs;
}, {});
}

function chooseJobs(jobs) {
return Object.keys(jobs).map(name => {
let group = jobs[name];
if (group.length > 1) {
let ambiguous = group.map(job => job.dockerfile);
if (group[1].depth === group[0].depth) {
if (group[1].postfix === group[0].postfix) {
cli.error(`Cannot build with ambiguous Dockerfiles:\n${ ambiguous.join('\n') }`);
process.exit(1);
}
}
cli.warn(`Using nearest match for the '${ group[0].name }' process:`);
cli.warn(`${ ambiguous[0] } (used)`);
cli.warn(`${ ambiguous.slice(1).join('\n') }`);
}
return group[0];
});
}
6 changes: 6 additions & 0 deletions lib/log.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use strict';

module.exports = (visible, args) => {
if (!visible) return;
console.log(`> docker ${ args.join(' ') }`);
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"camelcase": "1.0.2",
"co": "4.6.0",
"dotenv": "^1.1.0",
"glob": "7.1.0",
"heroku-cli-util": "^1.8.1",
"heroku-client": "^1.9.1",
"is-there": "4.0.0",
Expand Down

0 comments on commit d7cf202

Please sign in to comment.