Skip to content

Commit cdc83bb

Browse files
Adding base image info as labels DockerV0 (#14788)
* Adding base image info as labels * Simplifying testing * Removed debugging message * Using the the commons functions to annotate the base image info * Fixed bad merge * Removed unused import * Updated the setup for tests Co-authored-by: Ajinkya <[email protected]>
1 parent 13ee7c2 commit cdc83bb

File tree

7 files changed

+121
-18
lines changed

7 files changed

+121
-18
lines changed

Tasks/DockerV0/Strings/resources.resjson/en-US/resources.resjson

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
"loc.input.help.action": "Select a Docker action.",
1717
"loc.input.label.dockerFile": "Docker File",
1818
"loc.input.help.dockerFile": "Path to the Dockerfile.",
19+
"loc.input.label.addBaseImageData": "Add base image metadata to image(s)",
20+
"loc.input.help.addBaseImageData": "By default base image data like base image name and digest are added which helps with traceability. You can opt out of this default behavior by using this input.",
1921
"loc.input.label.buildArguments": "Build Arguments",
2022
"loc.input.help.buildArguments": "Build-time variables for the Docker file. Specify each name=value pair on a new line.",
2123
"loc.input.label.defaultContext": "Use Default Build Context",

Tasks/DockerV0/Tests/L0.ts

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ describe('Docker Suite', function() {
2121
delete process.env[shared.TestEnvVars.additionalImageTags];
2222
delete process.env[shared.TestEnvVars.enforceDockerNamingConvention];
2323
delete process.env[shared.TestEnvVars.memory];
24+
delete process.env[shared.TestEnvVars.addBaseImageData];
2425
});
2526
after(function () {
2627
});
@@ -29,6 +30,7 @@ describe('Docker Suite', function() {
2930
let tp = path.join(__dirname, 'TestSetup.js');
3031
let tr : ttm.MockTestRunner = new ttm.MockTestRunner(tp);
3132
process.env[shared.TestEnvVars.action] = shared.ActionTypes.buildImage;
33+
process.env[shared.TestEnvVars.addBaseImageData] = "false";
3234
tr.run();
3335

3436
assert(tr.invokedToolCount == 1, 'should have invoked tool one times. actual: ' + tr.invokedToolCount);
@@ -44,6 +46,7 @@ describe('Docker Suite', function() {
4446
let tr : ttm.MockTestRunner = new ttm.MockTestRunner(tp);
4547
process.env[shared.TestEnvVars.action] = shared.ActionTypes.buildImage;
4648
process.env[shared.TestEnvVars.memory] = "2GB";
49+
process.env[shared.TestEnvVars.addBaseImageData] = "false";
4750
tr.run();
4851

4952
assert(tr.invokedToolCount == 1, 'should have invoked tool one times. actual: ' + tr.invokedToolCount);
@@ -60,6 +63,7 @@ describe('Docker Suite', function() {
6063
process.env[shared.TestEnvVars.action] = shared.ActionTypes.buildImage;
6164
process.env[shared.TestEnvVars.imageName] = 'test/Te st:2';
6265
process.env[shared.TestEnvVars.enforceDockerNamingConvention] = 'true';
66+
process.env[shared.TestEnvVars.addBaseImageData] = "false";
6367
tr.run();
6468

6569
assert(tr.invokedToolCount == 1, 'should have invoked tool one times. actual: ' + tr.invokedToolCount);
@@ -76,6 +80,7 @@ describe('Docker Suite', function() {
7680
process.env[shared.TestEnvVars.action] = shared.ActionTypes.buildImage;
7781
process.env[shared.TestEnvVars.imageName] = 'test/Te st:2';
7882
process.env[shared.TestEnvVars.enforceDockerNamingConvention] = 'false';
83+
process.env[shared.TestEnvVars.addBaseImageData] = "false";
7984
tr.run();
8085

8186
assert(tr.invokedToolCount == 1, 'should have invoked tool one times. actual: ' + tr.invokedToolCount);
@@ -93,6 +98,7 @@ describe('Docker Suite', function() {
9398
process.env[shared.TestEnvVars.imageName] = 'test/Test:2';
9499
process.env[shared.TestEnvVars.additionalImageTags] = '6';
95100
process.env[shared.TestEnvVars.enforceDockerNamingConvention] = 'true';
101+
process.env[shared.TestEnvVars.addBaseImageData] = "false";
96102
tr.run();
97103

98104
assert(tr.invokedToolCount == 1, 'should have invoked tool one times. actual: ' + tr.invokedToolCount);
@@ -109,6 +115,7 @@ describe('Docker Suite', function() {
109115
let tr : ttm.MockTestRunner = new ttm.MockTestRunner(tp);
110116
process.env[shared.TestEnvVars.action] = shared.ActionTypes.buildImage;
111117
process.env[shared.TestEnvVars.includeLatestTag] = "true";
118+
process.env[shared.TestEnvVars.addBaseImageData] = "false";
112119
tr.run();
113120

114121
assert(tr.invokedToolCount == 1, 'should have invoked tool one times. actual: ' + tr.invokedToolCount);
@@ -168,6 +175,7 @@ describe('Docker Suite', function() {
168175
let tp = path.join(__dirname, 'TestSetup.js');
169176
let tr : ttm.MockTestRunner = new ttm.MockTestRunner(tp);
170177
process.env[shared.TestEnvVars.action] = shared.ActionTypes.pushImage;
178+
process.env[shared.TestEnvVars.addBaseImageData] = "false";
171179
tr.run();
172180

173181
assert(tr.invokedToolCount == 1, 'should have invoked tool one times. actual: ' + tr.invokedToolCount);
@@ -211,6 +219,7 @@ describe('Docker Suite', function() {
211219
let tr : ttm.MockTestRunner = new ttm.MockTestRunner(tp);
212220
process.env[shared.TestEnvVars.action] = shared.ActionTypes.buildImage;
213221
process.env[shared.TestEnvVars.containerType] = shared.ContainerTypes.AzureContainerRegistry;
222+
process.env[shared.TestEnvVars.addBaseImageData] = "false";
214223
tr.run();
215224

216225
assert(tr.invokedToolCount == 1, 'should have invoked tool one times. actual: ' + tr.invokedToolCount);
@@ -227,10 +236,9 @@ describe('Docker Suite', function() {
227236
process.env[shared.TestEnvVars.action] = shared.ActionTypes.buildImage;
228237
process.env[shared.TestEnvVars.containerType] = shared.ContainerTypes.AzureContainerRegistry;
229238
process.env[shared.TestEnvVars.qualifyImageName] = "true";
239+
process.env[shared.TestEnvVars.addBaseImageData] = "false";
230240
tr.run();
231241

232-
//console.log(tr.stdout);
233-
234242
assert(tr.invokedToolCount == 1, 'should have invoked tool one times. actual: ' + tr.invokedToolCount);
235243
assert(tr.stderr.length == 0 || tr.errorIssues.length, 'should not have written to stderr');
236244
assert(tr.succeeded, 'task should have succeeded');
@@ -243,6 +251,7 @@ describe('Docker Suite', function() {
243251
let tp = path.join(__dirname, 'TestSetup.js');
244252
let tr : ttm.MockTestRunner = new ttm.MockTestRunner(tp);
245253
process.env[shared.TestEnvVars.action] = shared.ActionTypes.buildImage;
254+
process.env[shared.TestEnvVars.addBaseImageData] = "false";
246255
tr.run();
247256

248257
assert(tr.succeeded, 'task should have succeeded');
@@ -255,6 +264,7 @@ describe('Docker Suite', function() {
255264
let tp = path.join(__dirname, 'TestSetup.js');
256265
process.env[shared.TestEnvVars.imageName] = "testuser/standardbuild:11";
257266
process.env[shared.TestEnvVars.action] = shared.ActionTypes.buildImage;
267+
process.env[shared.TestEnvVars.addBaseImageData] = "false";
258268
let tr : ttm.MockTestRunner = new ttm.MockTestRunner(tp);
259269
tr.run();
260270

@@ -270,6 +280,38 @@ describe('Docker Suite', function() {
270280
let tp = path.join(__dirname, 'TestSetup.js');
271281
process.env[shared.TestEnvVars.imageName] = "testuser/buildkit:11";
272282
process.env[shared.TestEnvVars.action] = shared.ActionTypes.buildImage;
283+
process.env[shared.TestEnvVars.addBaseImageData] = "false";
284+
let tr : ttm.MockTestRunner = new ttm.MockTestRunner(tp);
285+
tr.run();
286+
287+
assert(tr.invokedToolCount == 1, 'should have invoked tool one time. actual: ' + tr.invokedToolCount);
288+
assert(tr.stderr.length == 0 || tr.errorIssues.length, 'should not have written to stderr');
289+
assert(tr.succeeded, 'task should have succeeded');
290+
assert(tr.stdout.indexOf("set DOCKER_TASK_BUILT_IMAGES=6c3ada3eb420") != -1, "docker build should have stored the image id.")
291+
console.log(tr.stderr);
292+
done();
293+
});
294+
295+
it('Docker build should add labels with base image info', (done:Mocha.Done) => {
296+
let tp = path.join(__dirname, 'TestSetup.js');
297+
process.env[shared.TestEnvVars.imageName] = "testuser/imagewithannotations:11";
298+
process.env[shared.TestEnvVars.action] = shared.ActionTypes.buildImage;
299+
let tr : ttm.MockTestRunner = new ttm.MockTestRunner(tp);
300+
tr.run();
301+
302+
assert(tr.invokedToolCount == 3, 'should have invoked tool three time. actual: ' + tr.invokedToolCount);
303+
assert(tr.stderr.length == 0 || tr.errorIssues.length, 'should not have written to stderr');
304+
assert(tr.succeeded, 'task should have succeeded');
305+
assert(tr.stdout.indexOf(`[command]docker build -f ${shared.formatPath("dir1/DockerFile")} -t testuser/imagewithannotations:11 ${shared.DockerCommandArgs.BuildLabels} --label ${shared.BaseImageLabels.name} --label ${shared.BaseImageLabels.digest}`) != -1, "docker build should run");
306+
console.log(tr.stderr);
307+
done();
308+
});
309+
310+
it('Docker build should store the id of the image that was built with builkit.', (done:Mocha.Done) => {
311+
let tp = path.join(__dirname, 'TestSetup.js');
312+
process.env[shared.TestEnvVars.imageName] = "testuser/buildkit:11";
313+
process.env[shared.TestEnvVars.action] = shared.ActionTypes.buildImage;
314+
process.env[shared.TestEnvVars.addBaseImageData] = "false";
273315
let tr : ttm.MockTestRunner = new ttm.MockTestRunner(tp);
274316
tr.run();
275317

Tasks/DockerV0/Tests/TestSetup.ts

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as shared from './TestShared';
66
const DefaultWorkingDirectory: string = shared.formatPath("a/w");
77
const ImageNamesPath = shared.formatPath("dir/image_names.txt");
88
const DockerFilePath = shared.formatPath('dir1/DockerFile');
9+
const Dockerfile: string = `FROM ubuntu\nCMD ["echo","Hello World!"]`
910

1011
let taskPath = path.join(__dirname, '..', 'container.js');
1112
let tr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath);
@@ -24,12 +25,14 @@ tr.setInput('azureContainerRegistry', '{"loginServer":"ajgtestacr1.azurecr.io",
2425
tr.setInput('additionalImageTags', process.env[shared.TestEnvVars.additionalImageTags] || '');
2526
tr.setInput('enforceDockerNamingConvention', process.env[shared.TestEnvVars.enforceDockerNamingConvention]);
2627
tr.setInput('memory', process.env[shared.TestEnvVars.memory] || '');
28+
tr.setInput ('addBaseImageData', process.env[shared.TestEnvVars.addBaseImageData] || "true");
2729

2830
console.log("Inputs have been set");
2931

32+
process.env["SYSTEM_HOSTTYPE"] = "__hostType__";
3033
process.env["RELEASE_RELEASENAME"] = "Release-1";
3134
process.env["SYSTEM_DEFAULTWORKINGDIRECTORY"] = DefaultWorkingDirectory;
32-
process.env["SYSTEM_TEAMFOUNDATIONCOLLECTIONURI"] = "https://abc.visualstudio.com/";
35+
process.env["SYSTEM_TEAMFOUNDATIONCOLLECTIONURI"] = shared.teamFoundationCollectionURI;
3336
process.env["SYSTEM_SERVERTYPE"] = "hosted";
3437
process.env["ENDPOINT_AUTH_dockerhubendpoint"] = "{\"parameters\":{\"username\":\"test\", \"password\":\"regpassword\", \"email\":\"[email protected]\",\"registry\":\"https://index.docker.io/v1/\"},\"scheme\":\"UsernamePassword\"}";
3538
process.env["ENDPOINT_AUTH_SCHEME_AzureRMSpn"] = "ServicePrincipal";
@@ -76,27 +79,27 @@ let a = {
7679
// Add extra answer definitions that need to be dynamically generated
7780
a.exist[DockerFilePath] = true;
7881

79-
a.exec[`docker build -f ${DockerFilePath} -t test/test:2`] = {
82+
a.exec[`docker build -f ${DockerFilePath} -t test/test:2 ${shared.DockerCommandArgs.BuildLabels}`] = {
8083
"code": 0,
8184
"stdout": "successfully build test/test:2 image"
8285
};
83-
a.exec[`docker build -f ${DockerFilePath} -t test/test:2 -m 2GB`] = {
86+
a.exec[`docker build -f ${DockerFilePath} -t test/test:2 -m 2GB ${shared.DockerCommandArgs.BuildLabels}`] = {
8487
"code": 0,
8588
"stdout": "successfully build test/test:2 image"
8689
};
87-
a.exec[`docker build -f ${DockerFilePath} -t test/Te st:2`] = {
90+
a.exec[`docker build -f ${DockerFilePath} -t test/Te st:2 ${shared.DockerCommandArgs.BuildLabels}`] = {
8891
"code": 1,
8992
"stdout": "test/Te st:2 not valid imagename"
9093
};
91-
a.exec[`docker build -f ${DockerFilePath} -t test/test:2 -t test/test`] = {
94+
a.exec[`docker build -f ${DockerFilePath} -t test/test:2 -t test/test ${shared.DockerCommandArgs.BuildLabels}`] = {
9295
"code": 0,
9396
"stdout": "successfully build test/test image with latest tag"
9497
};
95-
a.exec[`docker build -f ${DockerFilePath} -t ajgtestacr1.azurecr.io/test/test:2`] = {
98+
a.exec[`docker build -f ${DockerFilePath} -t ajgtestacr1.azurecr.io/test/test:2 ${shared.DockerCommandArgs.BuildLabels}`] = {
9699
"code": 0,
97100
"stdout": "successfully build ajgtestacr1.azurecr.io/test/test image with latest tag"
98101
};
99-
a.exec[`docker build -f ${DockerFilePath} -t ${shared.ImageNamesFileImageName}`] = {
102+
a.exec[`docker build -f ${DockerFilePath} -t ${shared.ImageNamesFileImageName} ${shared.DockerCommandArgs.BuildLabels}`] = {
100103
"code": 0
101104
};
102105
a.exec[`docker tag test/test:2 ajgtestacr1.azurecr.io/test/test:2`] = {
@@ -111,19 +114,38 @@ a.exec[`docker run --rm ${shared.ImageNamesFileImageName}`] = {
111114
a.exec[`docker push ${shared.ImageNamesFileImageName}:latest`] = {
112115
"code": 0
113116
};
114-
a.exec[`docker build -f ${DockerFilePath} -t test/test:2 -t test/test:6`] = {
117+
a.exec[`docker build -f ${DockerFilePath} -t test/test:2 -t test/test:6 ${shared.DockerCommandArgs.BuildLabels}`] = {
115118
"code": 0,
116119
"stdout": "successfully build test/test:2 and test/test:6 image"
117120
};
118-
a.exec[`docker build -f ${DockerFilePath} -t testuser/standardbuild:11`] = {
121+
a.exec[`docker build -f ${DockerFilePath} -t testuser/standardbuild:11 ${shared.DockerCommandArgs.BuildLabels}`] = {
119122
"code": 0,
120123
"stdout": "Successfully built c834e0094587\n Successfully tagged testuser/testrepo:11."
121124
};
122-
123-
a.exec[`docker build -f ${DockerFilePath} -t testuser/buildkit:11`] = {
125+
a.exec[`docker build -f ${DockerFilePath} -t testuser/buildkit:11 ${shared.DockerCommandArgs.BuildLabels}`] = {
124126
"code": 0,
125127
"stdout": " => => writing image sha256:6c3ada3eb42094510e0083bba6ae805540e36c96871d7be0c926b2f8cbeea68c\n => => naming to docker.io/library/testuser/buildkit:11"
126128
};
129+
a.exec[`docker build -f ${DockerFilePath} -t testuser/imagewithannotations:11 ${shared.DockerCommandArgs.BuildLabels} --label ${shared.BaseImageLabels.name} --label ${shared.BaseImageLabels.digest}`] = {
130+
"code": 0,
131+
"stdout": "successfully built image and tagged testuser/imagewithannotations:11."
132+
};
133+
a.exec[`docker pull ${shared.BaseImageName}`] = {
134+
"code":0,
135+
"stdout": "Pull complete"
136+
};
137+
a.exec[`docker inspect ${shared.BaseImageName}`] = {
138+
"code":0,
139+
"stdout": `[{
140+
"Id": "sha256:302aba9ce190db9e247d710f4794cc303b169035de2048e76b82c9edbddbef4e",
141+
"RepoTags": [
142+
"alpine:latest"
143+
],
144+
"RepoDigests": [
145+
"ubuntu@sha256:826f70e0ac33e99a72cf20fb0571245a8fee52d68cb26d8bc58e53bfa65dcdfa"
146+
]
147+
}]`
148+
};
127149
tr.setAnswers(<any>a);
128150

129151
// Create mock for fs module
@@ -133,6 +155,8 @@ fsClone.readFileSync = function(filePath, options) {
133155
switch (filePath) {
134156
case ImageNamesPath:
135157
return shared.ImageNamesFileImageName;
158+
case DockerFilePath:
159+
return Dockerfile;
136160
default:
137161
return fs.readFileSync(filePath, options);
138162
}

Tasks/DockerV0/Tests/TestShared.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ export let TestEnvVars = {
99
imageName: "__imageName__",
1010
additionalImageTags: "__additionalImageTags__",
1111
enforceDockerNamingConvention: "__enforceDockerNamingConvention__",
12-
memory: "__memory__"
12+
memory: "__memory__",
13+
addBaseImageData: "__addBaseImageData__"
1314
};
1415

1516
export let OperatingSystems = {
@@ -32,7 +33,17 @@ export let ContainerTypes = {
3233
}
3334

3435
export let ImageNamesFileImageName = "test_image";
36+
export let BaseImageName = "ubuntu";
37+
export let BaseImageLabels = {
38+
name:"image.base.ref.name=ubuntu",
39+
digest:"image.base.digest=sha256:826f70e0ac33e99a72cf20fb0571245a8fee52d68cb26d8bc58e53bfa65dcdfa"
40+
};
41+
42+
export let teamFoundationCollectionURI = "https://abc.visualstudio.com/";
3543

44+
export let DockerCommandArgs = {
45+
BuildLabels: `--label com.visualstudio.abc.image.system.teamfoundationcollectionuri=${teamFoundationCollectionURI}`,
46+
}
3647
/**
3748
* Formats the given path to be appropriate for the operating system.
3849
* @param canonicalPath A non-rooted path using a forward slash (/) as a directory separator.

Tasks/DockerV0/containerbuild.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
"use strict";
2-
32
import * as path from "path";
43
import * as tl from "azure-pipelines-task-lib/task";
54
import ContainerConnection from "azure-pipelines-tasks-docker-common-v2/containerconnection";
5+
import * as pipelineUtils from "azure-pipelines-tasks-docker-common-v2/pipelineutils";
66
import * as fileUtils from "azure-pipelines-tasks-docker-common-v2/fileutils";
77
import * as sourceUtils from "azure-pipelines-tasks-docker-common-v2/sourceutils";
88
import * as imageUtils from "azure-pipelines-tasks-docker-common-v2/containerimageutils";
@@ -14,8 +14,8 @@ export function run(connection: ContainerConnection): any {
1414

1515
var dockerfilepath = tl.getInput("dockerFile", true);
1616
let dockerFile = fileUtils.findDockerFile(dockerfilepath);
17-
18-
if(!tl.exist(dockerFile)) {
17+
18+
if (!tl.exist(dockerFile)) {
1919
throw new Error(tl.loc('ContainerDockerFileNotFound', dockerfilepath));
2020
}
2121

@@ -25,7 +25,7 @@ export function run(connection: ContainerConnection): any {
2525
command.arg(["--build-arg", buildArgument]);
2626
});
2727

28-
var imageName = utils.getImageName();
28+
var imageName = utils.getImageName();
2929
var qualifyImageName = tl.getBoolInput("qualifyImageName");
3030
if (qualifyImageName) {
3131
imageName = connection.getQualifiedImageNameIfRequired(imageName);
@@ -55,13 +55,21 @@ export function run(connection: ContainerConnection): any {
5555
command.arg(["-m", memory]);
5656
}
5757

58+
const addBaseImageInfo = tl.getBoolInput("addBaseImageData");
59+
const labelsArgument = pipelineUtils.getDefaultLabels(false, addBaseImageInfo, dockerFile, connection);
60+
61+
labelsArgument.forEach(label => {
62+
command.arg(["--label", label]);
63+
});
64+
5865
var context: string;
5966
var defaultContext = tl.getBoolInput("defaultContext");
6067
if (defaultContext) {
6168
context = path.dirname(dockerFile);
6269
} else {
6370
context = tl.getPathInput("context");
6471
}
72+
6573
command.arg(context);
6674

6775
let output: string = "";

Tasks/DockerV0/task.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,14 @@
9494
"visibleRule": "action = Build an image",
9595
"helpMarkDown": "Path to the Dockerfile."
9696
},
97+
{
98+
"name": "addBaseImageData",
99+
"type": "boolean",
100+
"label": "Add base image metadata to image(s)",
101+
"groupName": "commands",
102+
"defaultValue": "true",
103+
"helpMarkDown": "By default base image data like base image name and digest are added which helps with traceability. You can opt out of this default behavior by using this input."
104+
},
97105
{
98106
"name": "buildArguments",
99107
"type": "multiLine",

Tasks/DockerV0/task.loc.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,14 @@
9494
"visibleRule": "action = Build an image",
9595
"helpMarkDown": "ms-resource:loc.input.help.dockerFile"
9696
},
97+
{
98+
"name": "addBaseImageData",
99+
"type": "boolean",
100+
"label": "ms-resource:loc.input.label.addBaseImageData",
101+
"groupName": "commands",
102+
"defaultValue": "true",
103+
"helpMarkDown": "ms-resource:loc.input.help.addBaseImageData"
104+
},
97105
{
98106
"name": "buildArguments",
99107
"type": "multiLine",

0 commit comments

Comments
 (0)