-
Notifications
You must be signed in to change notification settings - Fork 19
Multiple dockerfiles #6
Changes from 9 commits
4d70559
d7cf202
e66a79b
a66925e
502e3f1
b4c8b85
9522087
c00e780
99f90f8
c940247
be01673
be7651f
5b148f9
fc6fc7b
a3b4a22
632a3c8
832be4e
db8f6db
0773479
4f8e25e
7377b6c
cbcf60e
f18dae3
df8ce5f
d744280
fb84194
ec54401
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,49 +1,48 @@ | ||
'use strict'; | ||
const cli = require('heroku-cli-util') | ||
const child = require('child_process') | ||
const log = require('../lib/log') | ||
|
||
const cli = require('heroku-cli-util'); | ||
const co = require('co'); | ||
const child = require('child_process'); | ||
|
||
module.exports = function(topic) { | ||
module.exports = function (topic) { | ||
return { | ||
topic: topic, | ||
command: 'login', | ||
flags: [{ name: 'verbose', char: 'v', hasValue: false }], | ||
description: 'Logs in to the Heroku Docker registry', | ||
flags: [{name: 'verbose', char: 'v', hasValue: false}], | ||
description: 'logs in to the Heroku Docker registry', | ||
help: `Usage: | ||
heroku container:login`, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing the $ sign and this example shold show some output |
||
needsApp: false, | ||
needsAuth: true, | ||
run: cli.command(co.wrap(login)) | ||
}; | ||
}; | ||
run: cli.command(login) | ||
} | ||
} | ||
|
||
function* login(context, heroku) { | ||
let herokuHost = process.env.HEROKU_HOST || 'heroku.com'; | ||
let registry = `registry.${ herokuHost }`; | ||
let password = context.auth.password; | ||
async function login (context, heroku) { | ||
let herokuHost = process.env.HEROKU_HOST || 'heroku.com' | ||
let registry = `registry.${ herokuHost }` | ||
let password = context.auth.password | ||
|
||
try { | ||
let user = yield dockerLogin(registry, password, context.flags.verbose); | ||
let user = await dockerLogin(registry, password, context.flags.verbose) | ||
} | ||
catch (err) { | ||
cli.error(`Error: docker login exited with ${ err }`); | ||
cli.error(`Error: docker login exited with ${ err }`) | ||
cli.hush(err.stack || err) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should exit with a nonzero code, it looks like it's exiting with 0 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||
} | ||
} | ||
|
||
function dockerLogin(registry, password, verbose) { | ||
function dockerLogin (registry, password, verbose) { | ||
return new Promise((resolve, reject) => { | ||
let args = [ | ||
'login', | ||
'--username=_', | ||
`--password=${ password }`, | ||
registry | ||
]; | ||
if (verbose) { | ||
console.log(['> docker'].concat(args).join(' ')); | ||
} | ||
child.spawn('docker', args, { stdio: 'inherit' }) | ||
] | ||
log(verbose, args) | ||
child.spawn('docker', args, {stdio: 'inherit'}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should abstract this into 1 spawn function that returns a promise |
||
.on('exit', (code, signal) => { | ||
if (signal || code) reject(signal || code); | ||
else resolve(); | ||
}); | ||
}); | ||
if (signal || code) reject(signal || code) | ||
else resolve() | ||
}) | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,27 @@ | ||
'use strict'; | ||
|
||
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 { | ||
topic: topic, | ||
command: 'logout', | ||
flags: [{ name: 'verbose', char: 'v', hasValue: false }], | ||
description: 'Logs out from the Heroku Docker registry', | ||
description: 'logs out from the Heroku Docker registry', | ||
help: `Usage: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the CLI already automatically adds a "usage" help bit. Just take the help attribute out here since it's not adding anything useful |
||
heroku container:logout`, | ||
needsApp: false, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you add help with an output example? |
||
needsAuth: false, | ||
run: cli.command(co.wrap(logout)) | ||
run: cli.command(logout) | ||
}; | ||
}; | ||
|
||
function* logout(context, heroku) { | ||
async function logout(context, heroku) { | ||
let herokuHost = process.env.HEROKU_HOST || 'heroku.com'; | ||
let registry = `registry.${ herokuHost }`; | ||
|
||
try { | ||
let user = yield dockerLogout(registry, context.flags.verbose); | ||
let user = await dockerLogout(registry, context.flags.verbose); | ||
} | ||
catch (err) { | ||
cli.error(`Error: docker logout exited with ${ err }`); | ||
|
@@ -34,9 +34,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); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,77 +1,75 @@ | ||
'use strict'; | ||
const cli = require('heroku-cli-util') | ||
const Sanbashi = require('../lib/sanbashi') | ||
|
||
const cli = require('heroku-cli-util'); | ||
const co = require('co'); | ||
const child = require('child_process'); | ||
|
||
module.exports = function(topic) { | ||
let usage = ` | ||
${cli.color.bold.underline.magenta('Usage:')} | ||
${ cli.color.white('heroku container:push web')} # Pushes Dockerfile in current directory | ||
${ cli.color.white('heroku container:push web worker')} # Pushes Dockerfile.web and Dockerfile.worker found in the current directory | ||
${ cli.color.white('heroku container:push web worker --recursive')} # Pushes Dockerfile.web and Dockerfile.worker found in the current directory or subdirectories | ||
${ cli.color.white('heroku container:push --recursive')} # Pushes Dockerfile.* found in current directory or subdirectories` | ||
|
||
module.exports = function (topic) { | ||
return { | ||
topic: topic, | ||
command: 'push', | ||
description: 'Builds, then pushes a Docker image to deploy your Heroku app', | ||
description: 'builds, then pushes Docker images to deploy your Heroku app', | ||
needsApp: true, | ||
needsAuth: true, | ||
args: [{ name: 'process', optional: true }], | ||
flags: [{ name: 'verbose', char: 'v', hasValue: false }], | ||
run: cli.command(co.wrap(push)) | ||
}; | ||
}; | ||
variableArgs: true, | ||
help: usage, | ||
flags: [ | ||
{ | ||
name: 'verbose', | ||
char: 'v', | ||
hasValue: false | ||
}, | ||
{ | ||
name: 'recursive', | ||
char: 'R', | ||
hasValue: false | ||
} | ||
], | ||
run: cli.command(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 push = async function (context, heroku) { | ||
const recurse = !!context.flags.recursive | ||
if (context.args.length === 0 && !recurse) { | ||
cli.error( `Error: Requires either --recursive or one or more process types\n ${usage} `) | ||
process.exit(1) | ||
} | ||
let herokuHost = process.env.HEROKU_HOST || 'heroku.com' | ||
let registry = `registry.${ herokuHost }` | ||
let dockerfiles = Sanbashi.getDockerfiles(process.cwd(), recurse) | ||
let possibleJobs = Sanbashi.getJobs(`${ registry }/${ context.app }`, context.args, dockerfiles) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. path.join here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this a path? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. whoops, no it isn't There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So we should concat manually? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah this code is fine |
||
let jobs = await Sanbashi.chooseJobs(possibleJobs) | ||
if (!jobs.length) { | ||
cli.warn('No images to push') | ||
process.exit(1) | ||
} | ||
|
||
try { | ||
let build = yield buildImage(resource, context.cwd, context.flags.verbose); | ||
for (let job of jobs) { | ||
cli.log(cli.color.bold.white.bgMagenta(`\n=== Building ${job.name} (${job.dockerfile})`)) | ||
await Sanbashi.buildImage(job.dockerfile, job.resource, context.flags.verbose) | ||
} | ||
} | ||
catch (err) { | ||
cli.error(`Error: docker build exited with ${ err }`); | ||
process.exit(1); | ||
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); | ||
for (let job of jobs) { | ||
cli.log(cli.color.bold.white.bgMagenta(`\n=== Pushing ${job.name} (${job.dockerfile })`)) | ||
await Sanbashi.pushImage(job.resource, context.flags.verbose) | ||
} | ||
} | ||
catch (err) { | ||
cli.error(`Error: docker push exited with ${ err }`); | ||
process.exit(1); | ||
cli.error(`Error: docker push exited with ${ err }`) | ||
cli.hush(err.stack || err) | ||
process.exit(1) | ||
} | ||
} | ||
|
||
function buildImage(resource, cwd, verbose) { | ||
return new Promise((resolve, reject) => { | ||
let args = [ | ||
'build', | ||
'-t', | ||
resource, | ||
cwd | ||
]; | ||
if (verbose) { | ||
console.log(['> docker'].concat(args).join(' ')); | ||
} | ||
child.spawn('docker', args, { stdio: 'inherit' }) | ||
.on('exit', (code, signal) => { | ||
if (signal || code) reject(signal || code); | ||
else resolve(); | ||
}); | ||
}); | ||
} | ||
|
||
function pushImage(resource, verbose) { | ||
return new Promise((resolve, reject) => { | ||
let args = [ | ||
'push', | ||
resource | ||
]; | ||
if (verbose) { | ||
console.log(['> docker'].concat(args).join(' ')); | ||
} | ||
child.spawn('docker', args, { stdio: 'inherit' }) | ||
.on('exit', (code, signal) => { | ||
if (signal || code) reject(signal || code); | ||
else resolve(); | ||
}); | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
module.exports = (visible, args) => { | ||
if (!visible) return; | ||
console.log(`> docker ${ args.join(' ') }`); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
// 'sanbashi' is a word in Japanese that refers to a pier or dock, usually very large in size, such as found in Tokyo's O-daiba region | ||
|
||
let Glob = require('glob') | ||
let Path = require('path') | ||
let Inquirer = require('inquirer') | ||
const Child = require('child_process') | ||
const log = require('./log') | ||
|
||
const DOCKERFILE_REGEX = /\bDockerfile(.\w*)?$/ | ||
let Sanbashi = function(){} | ||
|
||
Sanbashi.getDockerfiles = function(rootdir, recursive) { | ||
let match = recursive ? './**/Dockerfile?(.)*' : 'Dockerfile*' | ||
let dockerfiles = Glob.sync(match, { | ||
cwd: rootdir, | ||
nonull: false, | ||
nodir: true | ||
}) | ||
if (recursive) { | ||
dockerfiles = dockerfiles.filter(df => df.match(/Dockerfile\.[\w]+/)) | ||
} | ||
return dockerfiles.map(file => Path.join(rootdir, file)) | ||
} | ||
|
||
Sanbashi.getJobs = function(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 | ||
}, {}) | ||
} | ||
|
||
Sanbashi.chooseJobs = async function(jobs) { | ||
let chosenJobs = [] | ||
for(let processType in jobs){ | ||
let group = jobs[processType] | ||
if (group.length > 1) { | ||
let prompt = { | ||
type: 'list', | ||
name: processType, | ||
choices: group.map(j => j.dockerfile), | ||
message: `Found multiple Dockerfiles with process type ${processType}. Please choose one to build and push ` | ||
} | ||
let answer = await Inquirer.prompt(prompt) | ||
chosenJobs.push(group.find(o => o.dockerfile === answer[processType])) | ||
} else { | ||
chosenJobs.push(group[0]) | ||
} | ||
} | ||
return chosenJobs | ||
} | ||
|
||
Sanbashi.buildImage = function(dockerfile, resource, verbose) { | ||
return new Promise((resolve, reject) => { | ||
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) | ||
else resolve() | ||
}) | ||
}) | ||
} | ||
|
||
Sanbashi.pushImage = function(resource, verbose) { | ||
return new Promise((resolve, reject) => { | ||
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() | ||
}) | ||
}) | ||
} | ||
|
||
module.exports = Sanbashi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you put the help back in with an output example?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To which command are you referring? the basic
heroku containers
just prints the version. What would you like added -- the sum of all the help for the other commands?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
an example of it printing the version then, we're supposed to have output examples for every command. I realize that isn't the most useful thing in the world, but it clearly demonstrates what a command is for.
this is the (soon-to-be enforced) rule for core plugins anyways, but we should do it on any plugin we can