Skip to content

Commit

Permalink
Adding docker-common-v2
Browse files Browse the repository at this point in the history
- It depends on [email protected] as oppsed to [email protected] used in docker-common
  • Loading branch information
savenkat committed Jun 13, 2019
1 parent 58d20ec commit 8ae8988
Show file tree
Hide file tree
Showing 25 changed files with 5,437 additions and 0 deletions.
384 changes: 384 additions & 0 deletions Tasks/Common/docker-common-v2/containerconnection.ts

Large diffs are not rendered by default.

75 changes: 75 additions & 0 deletions Tasks/Common/docker-common-v2/containerimageutils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"use strict";
import * as tl from "azure-pipelines-task-lib/task";
import * as fs from 'fs';

export function hasRegistryComponent(imageName: string): boolean {
var periodIndex = imageName.indexOf("."),
colonIndex = imageName.indexOf(":"),
slashIndex = imageName.indexOf("/");
return ((periodIndex > 0 && periodIndex < slashIndex) ||
(colonIndex > 0 && colonIndex < slashIndex));
}

export function imageNameWithoutTag(imageName: string): string {
var endIndex = 0;
if (hasRegistryComponent(imageName)) {
// Contains a registry component that may include ":", so omit
// this part of the name from the main delimiter determination
endIndex = imageName.indexOf("/");
}
endIndex = imageName.indexOf(":", endIndex);
return generateValidImageName(endIndex < 0 ? imageName : imageName.substr(0, endIndex));
}

export function generateValidImageName(imageName: string): string {
imageName = imageName.toLowerCase();
imageName = imageName.replace(/ /g,"");
return imageName;
}

export function getBaseImageNameFromDockerFile(dockerFilePath: string): string {
const dockerFileContent = fs.readFileSync(dockerFilePath, 'utf-8').toString();
return getBaseImageName(dockerFileContent);
}

export function getBaseImageName(contents: string): string {
var lines = contents.split(/[\r?\n]/);
var i;
for (i = 0; i < lines.length; i++) {
var index = lines[i].toUpperCase().indexOf("FROM");
if (index != -1) {
var rest = lines[i].substring(index + 4);
var imageName = rest.trim();
return imageName;
}
}

return null;
}

export function getResourceName(image: string, digest: string) {
var match = image.match(/^(?:([^\/]+)\/)?(?:([^\/]+)\/)?([^@:\/]+)(?:[@:](.+))?$/);
if (!match) {
return null;
}

var registry = match[1];
var namespace = match[2];
var repository = match[3];
var tag = match[4];

if (!namespace && registry && !/[:.]/.test(registry)) {
namespace = registry
registry = 'docker.io'
}

if (!namespace && !registry) {
registry = 'docker.io'
namespace = 'library'
}

registry = registry ? registry + '/' : '';
namespace = namespace ? namespace + '/' : '';

return "https://" + registry + namespace + repository + "@sha256:" + digest;
}
194 changes: 194 additions & 0 deletions Tasks/Common/docker-common-v2/dockercommandutils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
"use strict";

import * as tl from "azure-pipelines-task-lib/task";
import * as Q from "q";
import ContainerConnection from "./containerconnection";
import * as pipelineUtils from "./pipelineutils";

const matchPatternForSize = new RegExp(/[\d\.]+/);

export function build(connection: ContainerConnection, dockerFile: string, context: string, commandArguments: string, labelArguments: string[], tagArguments: string[], onCommandOut: (output) => any): any {
var command = connection.createCommand();

command.arg("build");
command.arg(["-f", dockerFile]);

if (labelArguments) {
labelArguments.forEach(label => {
command.arg(["--label", label]);
});
}

command.line(commandArguments);

if (tagArguments) {
tagArguments.forEach(tagArgument => {
command.arg(["-t", tagArgument]);
});
}

command.arg(context);

// setup variable to store the command output
let output = "";
command.on("stdout", data => {
output += data;
});

return connection.execCommand(command).then(() => {
// Return the std output of the command by calling the delegate
onCommandOut(output);
});
}

export function command(connection: ContainerConnection, dockerCommand: string, commandArguments: string, onCommandOut: (output) => any): any {
let command = connection.createCommand();
command.arg(dockerCommand);
command.line(commandArguments);

// setup variable to store the command output
let output = "";
command.on("stdout", data => {
output += data;
});

return connection.execCommand(command).then(() => {
// Return the std output of the command by calling the delegate
onCommandOut(output);
});
}

export function push(connection: ContainerConnection, image: string, commandArguments: string, onCommandOut: (image, output) => any): any {
var command = connection.createCommand();
command.arg("push");
command.arg(image);
command.line(commandArguments);

// setup variable to store the command output
let output = "";
command.on("stdout", data => {
output += data;
});

return connection.execCommand(command).then(() => {
// Return the std output of the command by calling the delegate
onCommandOut(image, output + "\n");
});
}

export function getCommandArguments(args: string): string {
return args ? args.replace(/\n/g, " ") : "";
}

export async function getLayers(connection: ContainerConnection, imageId: string): Promise<any> {
var layers = [];
var history = await getHistory(connection, imageId);
if (!history) {
return null;
}

var lines = history.split(/[\r?\n]/);

lines.forEach(line => {
line = line.trim();
if (line.length != 0) {
layers.push(parseHistory(line));
}
});

return layers.reverse();
}

export function getImageSize(layers: { [key: string]: string }[]): string {
let imageSize = 0;
for (const layer of layers) {
for (let key in layer) {
if (key.toLowerCase() === "size") {
const layerSize = extractSizeInBytes(layer[key]);
imageSize += layerSize;
}
}
}

return imageSize.toString() + "B";
}

export function extractSizeInBytes(size: string): number {
const sizeStringValue = size.match(matchPatternForSize);
if (sizeStringValue && sizeStringValue.length > 0) {
const sizeIntValue = parseFloat(sizeStringValue[0]);
const sizeUnit = size.substring(sizeIntValue.toString().length);
switch (sizeUnit.toLowerCase()) {
case "b": return sizeIntValue;
case "kb": return sizeIntValue * 1024;
case "mb": return sizeIntValue * 1024 * 1024;
case "gb": return sizeIntValue * 1024 * 1024 * 1024;
case "tb": return sizeIntValue * 1024 * 1024 * 1024 * 1024;
case "pb": return sizeIntValue * 1024 * 1024 * 1024 * 1024 * 1024;
}
}

return 0;
}

function parseHistory(input: string) {
const NOP = '#(nop)';
let directive = "UNSPECIFIED";
let argument = "";
let index: number = input.indexOf(NOP);
const createdByMatch = "; createdBy:";
const indexCreatedBy = input.indexOf(createdByMatch);
if (index != -1) {
argument = input.substr(index + 6).trim();
directive = argument.substr(0, argument.indexOf(' '));
argument = argument.substr(argument.indexOf(' ') + 1).trim();
}
else {
directive = 'RUN';
argument = input.substring(indexCreatedBy + createdByMatch.length, input.length - 1);
}

let createdAt: string = "";
let layerSize: string = "";
const createdAtMatch = "createdAt:";
const layerSizeMatch = "; layerSize:";
const indexCreatedAt = input.indexOf(createdAtMatch);
const indexLayerSize = input.indexOf(layerSizeMatch);
if (indexCreatedAt >= 0 && indexLayerSize >= 0) {
createdAt = input.substring(indexCreatedAt + createdAtMatch.length, indexLayerSize);
layerSize = input.substring(indexLayerSize + layerSizeMatch.length, indexCreatedBy);
}

return { "directive": directive, "arguments": argument, "createdOn": createdAt, "size": layerSize };
}

async function getHistory(connection: ContainerConnection, image: string): Promise<string> {
var command = connection.createCommand();
command.arg("history");
command.arg(["--format", "createdAt:{{.CreatedAt}}; layerSize:{{.Size}}; createdBy:{{.CreatedBy}}"]);
command.arg("--no-trunc");
command.arg(image);

const defer = Q.defer();
// setup variable to store the command output
let output = "";
command.on("stdout", data => {
output += data;
});

try {
connection.execCommand(command).then(() => {
defer.resolve();
});
}
catch (e) {
// Swallow any exceptions encountered in executing command
// such as --format flag not supported in old docker cli versions
output = null;
defer.resolve();
tl.warning("Not publishing to image meta data store as get history failed with error " + e);
}

await defer.promise;
return output;
}
45 changes: 45 additions & 0 deletions Tasks/Common/docker-common-v2/fileutils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"use strict";

import * as del from "del";
import * as fs from "fs";
import * as path from "path";
import * as url from "url";
import * as tl from "azure-pipelines-task-lib/task";

// http://www.daveeddy.com/2013/03/26/synchronous-file-io-in-nodejs/
// We needed a true Sync file write for config file
export function writeFileSync(filePath: string, data: string): number {
try
{
const fd = fs.openSync(filePath, 'w');
var bitesWritten = fs.writeSync(fd, data);
fs.fsyncSync(fd);
tl.debug(tl.loc("FileContentSynced", data));
fs.closeSync(fd);
return bitesWritten;
} catch(e)
{
tl.error(tl.loc('CantWriteDataToFile', filePath, e));
throw e;
}
}

export function findDockerFile(dockerfilepath: string) : string {
if (dockerfilepath.indexOf('*') >= 0 || dockerfilepath.indexOf('?') >= 0) {
tl.debug(tl.loc('ContainerPatternFound'));
let workingDirectory = tl.getVariable('System.DefaultWorkingDirectory');
let allFiles = tl.find(workingDirectory);
let matchingResultsFiles = tl.match(allFiles, dockerfilepath, workingDirectory, { matchBase: true });

if (!matchingResultsFiles || matchingResultsFiles.length == 0) {
throw new Error(tl.loc('ContainerDockerFileNotFound', dockerfilepath));
}

return matchingResultsFiles[0];
}
else
{
tl.debug(tl.loc('ContainerPatternNotFound'));
return dockerfilepath;
}
}
17 changes: 17 additions & 0 deletions Tasks/Common/docker-common-v2/gitutils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"use strict";

import * as cp from "child_process";
import * as tl from "azure-pipelines-task-lib/task";

export function tagsAt(commit: string): string[] {
var git = tl.which("git", true);
var args = ["tag", "--points-at", commit];
var gitDir = tl.getVariable("Build.Repository.LocalPath");
console.log("[command]" + git + " " + args.join(" "));
var result = (cp.execFileSync(git, args, {
encoding: "utf8",
cwd: gitDir
}) as string).trim();
console.log(result);
return result.length ? result.split("\n") : [];
}
28 changes: 28 additions & 0 deletions Tasks/Common/docker-common-v2/globals/del/del.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Type definitions for del v2.2.0
// Project: https://github.com/sindresorhus/del
// Definitions by: Asana <https://asana.com>, Aya Morisawa <https://github.com/AyaMorisawa>
// Definitions: https://github.com/borisyankov/DefinitelyTyped

/// <reference path="../glob/glob.d.ts"/>

declare module "del" {
import glob = require("glob");

function Del(pattern: string): Promise<string[]>;
function Del(pattern: string, options: Del.Options): Promise<string[]>;

function Del(patterns: string[]): Promise<string[]>;
function Del(patterns: string[], options: Del.Options): Promise<string[]>;

module Del {
function sync(pattern: string, options?: Options): string[];
function sync(patterns: string[], options?: Options): string[];

interface Options extends glob.IOptions {
force?: boolean;
dryRun?: boolean;
}
}

export = Del;
}
Loading

0 comments on commit 8ae8988

Please sign in to comment.