From 60fe93d29606ecea3a3929918a6d11c7edc5a6ed Mon Sep 17 00:00:00 2001 From: Anthony OGIER Date: Wed, 20 Aug 2014 16:45:53 +0200 Subject: [PATCH 1/3] Build parent images checking the Dockerfile directive. Fixes #1 --- lib/decking.js | 80 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 26 deletions(-) diff --git a/lib/decking.js b/lib/decking.js index 22fce74..7f2639f 100644 --- a/lib/decking.js +++ b/lib/decking.js @@ -169,44 +169,72 @@ Decking.prototype._build = function(image, done) { } var targetPath = target + "/Dockerfile"; - this.logger.log("Building image " + image + " from " + targetPath); // @TODO for now, always assume we want to build from a Dockerfile // @TODO need a lot of careful validation here - var readStream = fs.createReadStream(targetPath); - var writeStream = fs.createWriteStream("./Dockerfile"); var self = this; + var buildDep = false; - // ensure we don't try and create the tarball until the local Dockerfile exists - writeStream.on("close", function() { - var options = {t: image}; - if(self.hasArg("--no-cache")) { - self.logger.log("Not using image cache"); - options.nocache = true; + if(self.hasArg("--no-dependencies")) { + self.logger.log("Not building dependencies"); + } else { + // Detecting local dependency + var dockerfileContent = fs.readFileSync(targetPath); + var fromRe = /^\s*from\s*(\S*)/i; + var fromMatch = fromRe.exec(dockerfileContent); + if (fromMatch) { + var dependency = fromMatch[1]; + if (this.config.images[dependency]) { + // the dependency is declared in the local images, let's build that one first + buildDep = true; + self.logger.log("Found dependency : " + dependency); + self._build(dependency, function(err) { + if (err) return done(err); + continueFn(); + }); + } } + } - self.logger.log("Uploading compressed context..."); + if (!buildDep) continueFn(); - // @TODO allow user to specifiy --exclude params to avoid unnecessarily huge tarballs - var tar = child_process.spawn("tar", ["-c", "-", "./"]); + function continueFn() { + self.logger.log("Building image " + image + " from " + targetPath); - return self.docker.buildImage(tar.stdout, options, function(err, res) { - fs.unlink("./Dockerfile", function(err) { - if(err) return self.logger.log("[WARN] Could not remove Dockerfile"); - }); - if(err) return done(err); - if(res.headers["content-type"] === "application/json") { - res.pipe(JSONStream.parse("stream")).pipe(process.stdout); - } else { - // we don't need an if/else but let's keep it for clarity; it'd be too easy to - // skim-read the code and misinterpret the first pipe otherwise - res.pipe(process.stdout); + var readStream = fs.createReadStream(targetPath); + var writeStream = fs.createWriteStream("./Dockerfile"); + + // ensure we don't try and create the tarball until the local Dockerfile exists + writeStream.on("close", function() { + var options = {t: image}; + if(self.hasArg("--no-cache")) { + self.logger.log("Not using image cache"); + options.nocache = true; } - return res.on("end", done); + + self.logger.log("Uploading compressed context..."); + + // @TODO allow user to specifiy --exclude params to avoid unnecessarily huge tarballs + var tar = child_process.spawn("tar", ["-c", "-", "./"]); + + return self.docker.buildImage(tar.stdout, options, function(err, res) { + fs.unlink("./Dockerfile", function(err) { + if(err) return self.logger.log("[WARN] Could not remove Dockerfile"); + }); + if(err) return done(err); + if(res.headers["content-type"] === "application/json") { + res.pipe(JSONStream.parse("stream")).pipe(process.stdout); + } else { + // we don't need an if/else but let's keep it for clarity; it'd be too easy to + // skim-read the code and misinterpret the first pipe otherwise + res.pipe(process.stdout); + } + return res.on("end", done); + }); }); - }); - readStream.pipe(writeStream); + readStream.pipe(writeStream); + } }; // ---------- From bb4c94022b99988664e1e2d9ea72dadf15864d31 Mon Sep 17 00:00:00 2001 From: Anthony OGIER Date: Thu, 21 Aug 2014 10:09:36 +0200 Subject: [PATCH 2/3] When building image, detect build errors sent by Docker and display them. Also cancel the build chain in case of building dependencies. Inspired by https://github.com/Runnable/docker-image-builder/blob/master/lib/DockerBuilder.js --- lib/decking.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/decking.js b/lib/decking.js index 7f2639f..cbc1ec4 100644 --- a/lib/decking.js +++ b/lib/decking.js @@ -229,7 +229,26 @@ Decking.prototype._build = function(image, done) { // skim-read the code and misinterpret the first pipe otherwise res.pipe(process.stdout); } - return res.on("end", done); + var data, error; + res.on("error", done); + res.on("data", function(raw) { + data = (data ? data : '') + raw.toString(); + try { + var jsonData = JSON.parse(data); + } catch (e) { + return; + } + if (!jsonData.stream && jsonData.error) { + error = jsonData.error; + } + data = undefined; + }); + return res.on("end", function() { + if (error) { + error = new Error("Docker build error: " + error); + } + done(error); + }); }); }); From fddea654437417d322ed90e6dd6876dd50d0914c Mon Sep 17 00:00:00 2001 From: Anthony OGIER Date: Thu, 21 Aug 2014 12:51:20 +0200 Subject: [PATCH 3/3] Support building in Dockerfile dir --- lib/decking.js | 47 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/lib/decking.js b/lib/decking.js index cbc1ec4..8061902 100644 --- a/lib/decking.js +++ b/lib/decking.js @@ -167,8 +167,8 @@ Decking.prototype._build = function(image, done) { if(!target) { throw new Error("Image " + image + " does not exist in decking.json"); } - - var targetPath = target + "/Dockerfile"; + var targetDir = (typeof target ==="string" ? target : target.path); + var targetPath = targetDir + "/Dockerfile"; // @TODO for now, always assume we want to build from a Dockerfile // @TODO need a lot of careful validation here @@ -201,11 +201,19 @@ Decking.prototype._build = function(image, done) { function continueFn() { self.logger.log("Building image " + image + " from " + targetPath); - var readStream = fs.createReadStream(targetPath); - var writeStream = fs.createWriteStream("./Dockerfile"); + var buildIn = "current-dir"; + // default with what's passed on build command + if (self.hasArg("--build-in-dockerfile-dir")) { + buildIn = "dockerfile-dir"; + } else if (self.hasArg("--build-in-current-dir")) { + buildIn = "current-dir"; + } + if (typeof target !== "string") { + // taking into account override option in the image definition + buildIn = target["build-in"]; + } - // ensure we don't try and create the tarball until the local Dockerfile exists - writeStream.on("close", function() { + function build() { var options = {t: image}; if(self.hasArg("--no-cache")) { self.logger.log("Not using image cache"); @@ -215,12 +223,15 @@ Decking.prototype._build = function(image, done) { self.logger.log("Uploading compressed context..."); // @TODO allow user to specifiy --exclude params to avoid unnecessarily huge tarballs - var tar = child_process.spawn("tar", ["-c", "-", "./"]); + var tarOptions = (buildIn === "dockerfile-dir" ? {cwd: targetDir} : null); + var tar = child_process.spawn("tar", ["-c", "-", "./"], tarOptions); return self.docker.buildImage(tar.stdout, options, function(err, res) { - fs.unlink("./Dockerfile", function(err) { - if(err) return self.logger.log("[WARN] Could not remove Dockerfile"); - }); + if (buildIn === "current-dir") { + fs.unlink("./Dockerfile", function(err) { + if(err) return self.logger.log("[WARN] Could not remove Dockerfile"); + }); + } if(err) return done(err); if(res.headers["content-type"] === "application/json") { res.pipe(JSONStream.parse("stream")).pipe(process.stdout); @@ -250,9 +261,21 @@ Decking.prototype._build = function(image, done) { done(error); }); }); - }); + } + + if (buildIn === "current-dir") { + var readStream = fs.createReadStream(targetPath); + var writeStream = fs.createWriteStream("./Dockerfile"); - readStream.pipe(writeStream); + // ensure we don't try and create the tarball until the local Dockerfile exists + writeStream.on("close", function() { + build(); + }); + + readStream.pipe(writeStream); + } else if (buildIn === "dockerfile-dir") { + build(); + } } };