From 376d76334f9375baa5390825d3c3a31fd2b9e64e Mon Sep 17 00:00:00 2001 From: Peter Jausovec Date: Tue, 24 Nov 2015 15:54:09 -0800 Subject: [PATCH] Creating a simple docker-compose.yml file as part of the experience. --- generators/app/aspnetHelper.js | 14 +++ generators/app/dockerComposeHelper.js | 87 +++++++++++++++++++ generators/app/golangHelper.js | 18 ++++ generators/app/index.js | 22 +++-- generators/app/nodejsHelper.js | 24 +++++ .../app/templates/_dockerTaskGeneric.cmd | 12 +++ .../app/templates/_dockerTaskGeneric.sh | 13 +++ .../app/templates/_dockerTaskGolang.cmd | 12 +++ generators/app/templates/_dockerTaskGolang.sh | 11 +++ test/aspnettests.js | 29 +++++++ test/golangtests.js | 45 ++++++++++ test/nodejstests.js | 49 +++++++++++ 12 files changed, 330 insertions(+), 6 deletions(-) create mode 100644 generators/app/dockerComposeHelper.js diff --git a/generators/app/aspnetHelper.js b/generators/app/aspnetHelper.js index a490684..edfabe7 100644 --- a/generators/app/aspnetHelper.js +++ b/generators/app/aspnetHelper.js @@ -8,6 +8,7 @@ var path = require('path'); var process = require('process'); var fs = require('fs'); var DockerfileHelper = require('./dockerfileHelper.js'); +var DockerComposeHelper = require('./dockerComposeHelper.js'); /** * Represents a helper for ASP.NET projects. @@ -39,6 +40,19 @@ AspNetHelper.prototype.createDockerfile = function() { return _dockerfileHelper.createDockerfileContents(); } +/** + * Creates docker-compose file contents. + * @returns {string} +*/ +AspNetHelper.prototype.createDockerComposeFile = function() { + var _dockerComposeHelper = new DockerComposeHelper(); + _dockerComposeHelper.addAppName(this._imageName); + _dockerComposeHelper.addDockerfile('Dockerfile'); + _dockerComposeHelper.addBuildContext('.'); + _dockerComposeHelper.addPort(this._portNumber + ':' + this._portNumber); + return _dockerComposeHelper.createContents(); +} + /** * Gets the Docker image name. * @returns {string} diff --git a/generators/app/dockerComposeHelper.js b/generators/app/dockerComposeHelper.js new file mode 100644 index 0000000..8d400ca --- /dev/null +++ b/generators/app/dockerComposeHelper.js @@ -0,0 +1,87 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ +'use strict' + +/** + * Helper for creating docker-compose file contents. + * @constructor + */ +var DockerComposeHelper = function() { + this._commandMap = []; +} + +DockerComposeHelper.prototype.addAppName = function(name) { + this._addCommand('', name); +} + +DockerComposeHelper.prototype.addDockerfile = function(fileName) { + this._addCommand('dockerfile', fileName); +} + +DockerComposeHelper.prototype.addBuildContext = function(context) { + this._addCommand('build', context); +} + +DockerComposeHelper.prototype.addPort = function(ports) { + this._addCommand('ports', ports); +} + +DockerComposeHelper.prototype.addVolume = function(volume) { + this._addCommand('volumes', volume); +} + +/** + * Iterates through the array of commands and creates the contents. +*/ +DockerComposeHelper.prototype.createContents = function() { + var contents = ''; + + for (var i = 0; i < this._commandMap.length; i ++) { + var item = this._commandMap[i]; + + switch (item.commandName) { + case '': { + // [VALUE]: + contents += item.value + ':\n'; + break; + } + + case 'ports': { + if (contents.indexOf(item.commandName) === -1) { + // Add 'ports:' first. + contents += ' ' + item.commandName + ':\n'; + } + + contents += ' - "' + item.value + '"\n'; + break; + } + case 'volumes': { + if (contents.indexOf(item.commandName) === -1) { + // Add 'volumes:' first. + contents += ' ' + item.commandName + ':\n'; + } + + contents += ' - ' + item.value + '\n'; + break; + } + + default: { + // [COMMANDNAME]: [VALUE] + contents += ' ' + item.commandName + ': ' + item.value + '\n'; + break; + } + } + } + + return contents; +} + +/** + * Adds the command name and value to the list of commands. +*/ +DockerComposeHelper.prototype._addCommand = function(commandName, value) { + this._commandMap.push({ commandName: commandName, value: value }); +} + +module.exports = DockerComposeHelper; \ No newline at end of file diff --git a/generators/app/golangHelper.js b/generators/app/golangHelper.js index a74d96d..26e01b5 100644 --- a/generators/app/golangHelper.js +++ b/generators/app/golangHelper.js @@ -7,6 +7,7 @@ var util = require('./utils.js'); var path = require('path'); var process = require('process'); var DockerfileHelper = require('./dockerfileHelper.js'); +var DockerComposeHelper = require('./dockerComposeHelper.js'); /** * Represents a helper for Golang projects. @@ -36,6 +37,23 @@ GolangHelper.prototype.createDockerfile = function() { return _dockerfileHelper.createDockerfileContents(); } +/** + * Creates docker-compose file contents. + * @returns {string} +*/ +GolangHelper.prototype.createDockerComposeFile = function() { + var _dockerComposeHelper = new DockerComposeHelper(); + _dockerComposeHelper.addAppName(this._imageName); + _dockerComposeHelper.addDockerfile('Dockerfile'); + _dockerComposeHelper.addBuildContext('.'); + + if (this._isWeb) { + _dockerComposeHelper.addPort(this._portNumber + ':' + this._portNumber); + } + + return _dockerComposeHelper.createContents(); +} + /** * Gets the Docker image name. * @returns {string} diff --git a/generators/app/index.js b/generators/app/index.js index c50894b..8294c23 100644 --- a/generators/app/index.js +++ b/generators/app/index.js @@ -25,6 +25,7 @@ var error = false; var portNumber = 3000; var imageName = ''; var DOCKERFILE_NAME = 'Dockerfile'; +var DOCKERCOMPOSE_NAME = 'docker-compose.yml'; // Node.js variables var addNodemon = false; @@ -130,8 +131,11 @@ function handleNodeJs(yo) { return; } - var contents = nodeJs.createDockerfile(); - yo.fs.write(yo.destinationPath(DOCKERFILE_NAME), new Buffer(contents)); + var dockerfileContents = nodeJs.createDockerfile(); + yo.fs.write(yo.destinationPath(DOCKERFILE_NAME), new Buffer(dockerfileContents)); + + var dockerComposeContents = nodeJs.createDockerComposeFile(); + yo.fs.write(yo.destinationPath('docker-compose.yml'), new Buffer(dockerComposeContents)); yo.fs.copyTpl( yo.templatePath(nodeJs.getTemplateScriptName()), @@ -147,9 +151,12 @@ function handleNodeJs(yo) { */ function handleGolang(yo) { var golang = new GolangHelper(isGoWeb, portNumber, imageName); - var contents = golang.createDockerfile(); - yo.fs.write(yo.destinationPath(DOCKERFILE_NAME), new Buffer(contents)); + var dockerfileContents = golang.createDockerfile(); + yo.fs.write(yo.destinationPath(DOCKERFILE_NAME), new Buffer(dockerfileContents)); + + var dockerComposeContents = golang.createDockerComposeFile(); + yo.fs.write(yo.destinationPath(DOCKERCOMPOSE_NAME), new Buffer(dockerComposeContents)); yo.fs.copyTpl( yo.templatePath(golang.getTemplateScriptName()), @@ -177,8 +184,11 @@ function handleAspNet(yo) { done(); }.bind(yo)); - var contents = aspNet.createDockerfile(); - yo.fs.write(yo.destinationPath(DOCKERFILE_NAME), new Buffer(contents)); + var dockerfileContents = aspNet.createDockerfile(); + yo.fs.write(yo.destinationPath(DOCKERFILE_NAME), new Buffer(dockerfileContents)); + + var dockerComposeContents = aspNet.createDockerComposeFile(); + yo.fs.write(yo.destinationPath(DOCKERCOMPOSE_NAME), new Buffer(dockerComposeContents)); yo.fs.copyTpl( yo.templatePath(aspNet.getTemplateScriptName()), diff --git a/generators/app/nodejsHelper.js b/generators/app/nodejsHelper.js index be2db0e..5c25cae 100644 --- a/generators/app/nodejsHelper.js +++ b/generators/app/nodejsHelper.js @@ -7,6 +7,7 @@ var util = require('./utils.js'); var path = require('path'); var process = require('process'); var DockerfileHelper = require('./dockerfileHelper.js'); +var DockerComposeHelper = require('./dockerComposeHelper.js'); /** * Represents a helper for Node.js projects. @@ -48,6 +49,29 @@ NodejsHelper.prototype.createDockerfile = function() { return _dockerfileHelper.createDockerfileContents(); } +/** + * Creates docker-compose file contents. + * @returns {string} +*/ +NodejsHelper.prototype.createDockerComposeFile = function() { + var _dockerComposeHelper = new DockerComposeHelper(); + _dockerComposeHelper.addAppName(this._imageName); + _dockerComposeHelper.addDockerfile('Dockerfile'); + _dockerComposeHelper.addBuildContext('.'); + _dockerComposeHelper.addPort(this._portNumber + ':' + this._portNumber); + + if (this._useNodemon) { + if (util.isWindows()) { + var sourcePath = '/' + process.cwd().replace(path.sep, '/'); + _dockerComposeHelper.addVolume(sourcePath + ':/src'); + } else { + _dockerComposeHelper.addVolume('.:/src'); + } + } + + return _dockerComposeHelper.createContents(); +} + /** * Gets the Docker image name. * @returns {string} diff --git a/generators/app/templates/_dockerTaskGeneric.cmd b/generators/app/templates/_dockerTaskGeneric.cmd index 81ba656..6be4241 100644 --- a/generators/app/templates/_dockerTaskGeneric.cmd +++ b/generators/app/templates/_dockerTaskGeneric.cmd @@ -3,6 +3,11 @@ set imageName="<%= imageName %>" set containerPort=<%= portNumber %> set publicPort=<%= portNumber %> +if /I "%1" == "compose" ( + call :compose + goto :eof +) + if /I "%1" == "build" ( call :buildImage goto :eof @@ -45,6 +50,12 @@ REM Builds the Docker image. docker build -t %imageName% . goto :eof +REM Runs docker-compose. +:compose + echo Composing. + docker-compose up -d +goto :eof + REM Runs the container. :runContainer REM Check if container is already running, stop it and run a new one. @@ -78,6 +89,7 @@ REM Shows the usage for the script. echo build: Builds a Docker image (%imageName%). echo run: Runs a container based on an existing Docker image (%imageName%). echo buildrun: Builds a Docker image and runs the container. + echo compose: Runs docker-compose. echo clean: Removes the image %imageName% and kills all containers based on that image. echo Example: echo dockerTask.cmd build diff --git a/generators/app/templates/_dockerTaskGeneric.sh b/generators/app/templates/_dockerTaskGeneric.sh index 3939568..296b115 100644 --- a/generators/app/templates/_dockerTaskGeneric.sh +++ b/generators/app/templates/_dockerTaskGeneric.sh @@ -15,6 +15,13 @@ buildImage () { docker build -t $imageName . } +# Runs docker-compose. +compose () { + echo "Composing." + docker-compose up -d + _openSite +} + # Runs the container. runContainer () { # Check if container is already running, stop it and run a new one. @@ -22,7 +29,9 @@ runContainer () { # Create a container from the image. <%= containerRunCommand %> +} +_openSite () { printf 'Opening site' until $(curl --output /dev/null --silent --head --fail http://$(docker-machine ip $(docker-machine active)):$publicPort); do printf '.' @@ -42,6 +51,7 @@ showUsage () { echo " build: Builds a Docker image ('$imageName')." echo " run: Runs a container based on an existing Docker image ('$imageName')." echo " buildrun: Builds a Docker image and runs the container." + echo " compose: Runs docker-compose." echo " clean: Removes the image '$imageName' and kills all containers based on that image." echo "" echo "Example:" @@ -55,6 +65,9 @@ if [ $# -eq 0 ]; then showUsage else case "$1" in + "compose") + compose + ;; "build") buildImage ;; diff --git a/generators/app/templates/_dockerTaskGolang.cmd b/generators/app/templates/_dockerTaskGolang.cmd index 910da7c..4b1dcf7 100644 --- a/generators/app/templates/_dockerTaskGolang.cmd +++ b/generators/app/templates/_dockerTaskGolang.cmd @@ -2,6 +2,11 @@ set imageName="<%= imageName %>" set dockerHostName="<%= dockerHostName %>" +if /I "%1" == "compose" ( + call :compose + goto :eof +) + if /I "%1" == "build" ( call :buildImage goto :eof @@ -44,6 +49,12 @@ REM Builds the Docker image. docker build -t %imageName% . goto :eof +REM Runs docker-compose. +:compose + echo Composing. + docker-compose up -d +goto :eof + REM Runs the container. :runContainer REM Check if container is already running, stop it and run a new one. @@ -67,6 +78,7 @@ REM Shows the usage for the script. echo build: Builds a Docker image (%imageName%). echo run: Runs a container based on an existing Docker image (%imageName%). echo buildrun: Builds a Docker image and runs the container. + echo compose: Runs docker-compose. echo clean: Removes the image %imageName% and kills all containers based on that image. echo Example: echo dockerTask.cmd build diff --git a/generators/app/templates/_dockerTaskGolang.sh b/generators/app/templates/_dockerTaskGolang.sh index c9a0182..7b3533c 100644 --- a/generators/app/templates/_dockerTaskGolang.sh +++ b/generators/app/templates/_dockerTaskGolang.sh @@ -13,6 +13,13 @@ buildImage () { docker build -t $imageName . } +# Runs docker-compose. +compose () { + echo "Composing." + docker-compose up -d + <%= openWebSiteCommand %> +} + # Runs the container. runContainer () { # Check if container is already running, stop it and run a new one. @@ -32,6 +39,7 @@ showUsage () { echo " build: Builds a Docker image ('$imageName')." echo " run: Runs a container based on an existing Docker image ('$imageName')." echo " buildrun: Builds a Docker image and runs the container." + echo " compose: Runs docker-compose." echo " clean: Removes the image '$imageName' and kills all containers based on that image." echo "" echo "Example:" @@ -45,6 +53,9 @@ if [ $# -eq 0 ]; then showUsage else case "$1" in + "compose") + compose + ;; "build") buildImage ;; diff --git a/test/aspnettests.js b/test/aspnettests.js index 686ce71..f1b992c 100644 --- a/test/aspnettests.js +++ b/test/aspnettests.js @@ -54,6 +54,7 @@ describe('aspnet generator', function() { assert.file([ 'Dockerfile', 'dockerTask.sh', + 'docker-compose.yml' ]); }); done(); @@ -202,5 +203,33 @@ describe('aspnet generator', function() { 'dockerTask.sh', 'docker run -di -p $publicPort:$containerPort $imageName'); }); done(); + }), + it('creates docker-compose with correct contents ', function(done) { + var portNumber = 1234; + var imageName = 'aspnetimagename'; + var aspNetVersion = '1.0.0-beta8'; + + helpers.run(path.join(__dirname, '../generators/app')) + .inTmpDir(function(dir) { + createTestProjectJson(dir); + }) + .withLocalConfig(function() { + return { + "appInsightsOptIn": false, + "runningTests": true + }; + }) + .withPrompts(createAspNetPrompts(aspNetVersion, portNumber, imageName)) + .on('end', function() { + assert.fileContent( + 'docker-compose.yml', imageName + ':'); + assert.fileContent( + 'docker-compose.yml', 'dockerfile: Dockerfile'); + assert.fileContent( + 'docker-compose.yml', 'build: .'); + assert.fileContent( + 'docker-compose.yml', '- "' + portNumber + ':' + portNumber +'"'); + }); + done(); }) }); \ No newline at end of file diff --git a/test/golangtests.js b/test/golangtests.js index 564894c..211fb69 100644 --- a/test/golangtests.js +++ b/test/golangtests.js @@ -33,6 +33,7 @@ describe('golang generator', function() { assert.file([ 'Dockerfile', 'dockerTask.sh', + 'docker-compose.yml' ]); }); done(); @@ -126,5 +127,49 @@ describe('golang generator', function() { 'dockerTask.sh', 'docker run -di ' + imageName); }); done(); + }), + it('creates docker-compose with correct contents (non-Web project)', function(done) { + var portNumber = 1234; + var imageName = 'golangimagename'; + helpers.run(path.join(__dirname, '../generators/app')) + .withLocalConfig(function() { + return { + "appInsightsOptIn": false, + "runningTests": true + }; + }) + .withPrompts(createGolangPrompts(false, portNumber, imageName)) + .on('end', function() { + assert.fileContent( + 'docker-compose.yml', imageName + ':'); + assert.fileContent( + 'docker-compose.yml', 'dockerfile: Dockerfile'); + assert.fileContent( + 'docker-compose.yml', 'build: .'); + }); + done(); + }), + it('creates docker-compose with correct contents (Web project)', function(done) { + var portNumber = 1234; + var imageName = 'golangimagename'; + helpers.run(path.join(__dirname, '../generators/app')) + .withLocalConfig(function() { + return { + "appInsightsOptIn": false, + "runningTests": true + }; + }) + .withPrompts(createGolangPrompts(false, portNumber, imageName)) + .on('end', function() { + assert.fileContent( + 'docker-compose.yml', imageName + ':'); + assert.fileContent( + 'docker-compose.yml', 'dockerfile: Dockerfile'); + assert.fileContent( + 'docker-compose.yml', 'build: .'); + assert.fileContent( + 'docker-compose.yml', '- "' + portNumber + ':' + portNumber +'"'); + }); + done(); }) }); \ No newline at end of file diff --git a/test/nodejstests.js b/test/nodejstests.js index a66ab22..681e4c1 100644 --- a/test/nodejstests.js +++ b/test/nodejstests.js @@ -33,6 +33,7 @@ describe('node.js generator', function() { assert.file([ 'Dockerfile', 'dockerTask.sh', + 'docker-compose.yml' ]); }); done(); @@ -124,5 +125,53 @@ describe('node.js generator', function() { 'dockerTask.sh', 'docker run -di -p $publicPort:$containerPort $imageName'); }); done(); + }), + it ('create docker-compose file with correct contents (with Nodemon)', function (done) { + var portNumber = 1234; + var imageName = 'nodejsimagename'; + helpers.run(path.join(__dirname, '../generators/app')) + .withLocalConfig(function() { + return { + "appInsightsOptIn": false, + "runningTests": true + }; + }) + .withPrompts(createNodeJsPrompts(true, portNumber, imageName)) + .on('end', function() { + assert.fileContent( + 'docker-compose.yml', imageName + ':'); + assert.fileContent( + 'docker-compose.yml', 'dockerfile: Dockerfile'); + assert.fileContent( + 'docker-compose.yml', 'build: .'); + assert.fileContent( + 'docker-compose.yml', '- .:/src'); + assert.fileContent( + 'docker-compose.yml', '- "' + portNumber + ':' + portNumber + '"'); + }); + done(); + }), + it ('create docker-compose file with correct contents (without Nodemon)', function (done) { + var portNumber = 1234; + var imageName = 'nodejsimagename'; + helpers.run(path.join(__dirname, '../generators/app')) + .withLocalConfig(function() { + return { + "appInsightsOptIn": false, + "runningTests": true + }; + }) + .withPrompts(createNodeJsPrompts(false, portNumber, imageName)) + .on('end', function() { + assert.fileContent( + 'docker-compose.yml', imageName + ':'); + assert.fileContent( + 'docker-compose.yml', 'dockerfile: Dockerfile'); + assert.fileContent( + 'docker-compose.yml', 'build: .'); + assert.fileContent( + 'docker-compose.yml', '- "' + portNumber + ':' + portNumber + '"'); + }); + done(); }) }); \ No newline at end of file