Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
fb962d2
Add 'platform' input and support for multiple architecture builds
natescherer Aug 29, 2022
478f05a
Merge branch 'main' into feature/multiple-architectures
natescherer Sep 1, 2022
bc22514
Switch to --no-push to builds with --platform
natescherer Sep 1, 2022
fbd54bf
Merge branch 'main' into feature/multiple-architectures
natescherer Sep 2, 2022
c160b9a
Switch to pushing manifests on platform builds
natescherer Sep 2, 2022
186e2ad
Remove --no-push which was dropped in newer CLI version
natescherer Sep 2, 2022
4b1b9f8
Set push to false explicitly
natescherer Sep 2, 2022
41b40ee
Add multi-platform push via skopeo and devcontainer --output
natescherer Sep 8, 2022
3e27c55
Merge branch 'feature/multiple-architectures' into main
natescherer Oct 3, 2022
9703d94
Merge pull request #2 from natescherer/main
natescherer Oct 3, 2022
c0caf10
Start documentation
natescherer Oct 3, 2022
08bfe75
Rebuild after upstream sync
natescherer Oct 3, 2022
0019faa
Update to ensure "latest" tag is truly applied as default
natescherer Oct 19, 2022
2102c7f
Add documentation
natescherer Oct 19, 2022
22daedd
Add platform to AzDO task
natescherer Oct 19, 2022
bfffbb4
Fix versions and publisher
natescherer Oct 19, 2022
de1b1b2
Fix bad indentation
natescherer Oct 19, 2022
8b923e4
Simplify fallback to latest when imageTag blank
natescherer Oct 19, 2022
74be822
scripts/build-local.sh
stuartleeks Oct 28, 2022
0588172
Add platform-with-runcmd tests
natescherer Oct 28, 2022
5a927d4
Update pr-checks
natescherer Nov 3, 2022
07ef2d2
Merge branch 'main' into feature/multiple-architectures
stuartleeks Nov 3, 2022
003715a
Merge branch 'main' into feature/multiple-architectures
stuartleeks Nov 3, 2022
8e5e453
Run build-local.sh for azdo-task/README.md
natescherer Nov 3, 2022
1227e56
Revert version bump
stuartleeks Nov 3, 2022
c2ec738
Revert AzDO task version bump
natescherer Nov 4, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .azure-devops/azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,38 @@ jobs:
inputs:
subFolder: github-tests/Dockerfile/build-only

- job: test_platform_with_runcmd
displayName: Test with platform and runCmd
steps:
- script: |
docker login -u $ACR_USERNAME -p $ACR_TOKEN $(ACR_NAME).azurecr.io
displayName: 'Log in to Azure Container Registry'
env:
ACR_NAME: $(ACR_NAME)
ACR_TOKEN: $(ACR_TOKEN)
ACR_USERNAME: $(ACR_USERNAME)

- script: |
printenv | sort
env:
IMAGE_TAG: $(IMAGE_TAG)

- script: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
displayName: Set up QEMU

- script: docker buildx create --use
displayName: Set up docker buildx

- task: DevcontainersCi@0
inputs:
imageName: '$(ACR_NAME).azurecr.io/devcontainers-ci/azdo-devcontainer-build-run/test/platform-with-runcmd'
subFolder: github-tests/Dockerfile/platform-with-runcmd
platform: linux/amd64,linux/arm64
runCmd: echo $HOSTNAME && [[ $HOSTNAME == "my-host" ]]

- script: |
echo "'runCmdOutput' value: $runCmdOutput"
if [["$runCmdOutput" = *my-host*]]; then
echo "'runCmdOutput' output of test_simple job doesn't contain expected value 'my-host'"
exit 1
fi
50 changes: 50 additions & 0 deletions .github/workflows/ci_common.yml
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ jobs:
- test-compose-features
- test-simple
- test-no-run
- test-platform-with-runcmd
runs-on: ubuntu-latest
if: ${{ inputs.prHeadSha }}
steps:
Expand Down Expand Up @@ -756,3 +757,52 @@ jobs:
uses: ./
with:
subFolder: github-tests/Dockerfile/build-only

test-platform-with-runcmd:
name: Run GitHub platform and runCmd test
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout
uses: actions/checkout@v2
with:
persist-credentials: false
# if the following value is missing (i.e. not triggered via comment workflow)
# then the default checkout will apply
ref: ${{ inputs.prRef }}

- name: Set up QEMU for multi-architecture builds
uses: docker/setup-qemu-action@v1

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1

- name: Login to GitHub Container Registry
uses: docker/login-action@v1
if: ${{ needs.build.outputs.image_push_option == 'filter' }}
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Run test
uses: ./
id: platform-with-runcmd
with:
subFolder: github-tests/Dockerfile/platform-with-runcmd
imageName: ghcr.io/devcontainers/ci/tests/platform-with-runcmd
platform: linux/amd64,linux/arm64
runCmd: echo $HOSTNAME && [[ $HOSTNAME == "my-host" ]]
push: ${{ needs.build.outputs.image_push_option }}
eventFilterForPush: |
push
pull_request
- name: Validate runCmdOutput output
run: |
echo "'runCmdOutput' value: $runCmdOutput"
if [["$runCmdOutput" = *my-host*]]; then
echo "'runCmdOutput' output of platform-with-runcmd step doesn't contain expected value 'my-host'"
exit 1
fi
env:
runCmdOutput: ${{ steps.platform-with-runcmd.outputs.runCmdOutput }}
3 changes: 3 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ inputs:
imageTag:
required: false
description: Image tag (defaults to latest)
platform:
require: false
description: Platforms for which the image should be built. If omitted, defaults to the platform of the GitHub Actions Runner. Multiple platforms should be comma separated.
runCmd:
required: false
description: Specify the command to run after building the dev container image. Can be omitted to skip starting the container.
Expand Down
2 changes: 2 additions & 0 deletions azdo-task/DevcontainersCi/dist/dev-container-cli.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ export interface DevContainerCliBuildResult extends DevContainerCliSuccessResult
export interface DevContainerCliBuildArgs {
workspaceFolder: string;
imageName?: string;
platform?: string;
additionalCacheFroms?: string[];
userDataFolder?: string;
output?: string;
}
declare function devContainerBuild(args: DevContainerCliBuildArgs, log: (data: string) => void): Promise<DevContainerCliBuildResult | DevContainerCliError>;
export interface DevContainerCliUpResult extends DevContainerCliSuccessResult {
Expand Down
138 changes: 134 additions & 4 deletions azdo-task/DevcontainersCi/dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ const path_1 = __importDefault(__nccwpck_require__(5622));
const envvars_1 = __nccwpck_require__(9243);
const dev_container_cli_1 = __nccwpck_require__(4624);
const docker_1 = __nccwpck_require__(3758);
const skopeo_1 = __nccwpck_require__(9898);
const exec_1 = __nccwpck_require__(7757);
function runMain() {
var _a, _b, _c, _d, _e, _f, _g;
Expand All @@ -250,12 +251,21 @@ function runMain() {
const checkoutPath = (_a = task.getInput('checkoutPath')) !== null && _a !== void 0 ? _a : '';
const imageName = task.getInput('imageName');
const imageTag = task.getInput('imageTag');
const platform = task.getInput('platform');
const subFolder = (_b = task.getInput('subFolder')) !== null && _b !== void 0 ? _b : '.';
const runCommand = task.getInput('runCmd');
const envs = (_d = (_c = task.getInput('env')) === null || _c === void 0 ? void 0 : _c.split('\n')) !== null && _d !== void 0 ? _d : [];
const inputEnvsWithDefaults = envvars_1.populateDefaults(envs);
const cacheFrom = (_f = (_e = task.getInput('cacheFrom')) === null || _e === void 0 ? void 0 : _e.split('\n')) !== null && _f !== void 0 ? _f : [];
const skipContainerUserIdUpdate = ((_g = task.getInput('skipContainerUserIdUpdate')) !== null && _g !== void 0 ? _g : 'false') === 'true';
if (platform) {
const skopeoInstalled = yield skopeo_1.isSkopeoInstalled();
if (!skopeoInstalled) {
console.log('skopeo not available and is required for multi-platform builds - make sure it is installed on your build agent');
return;
}
}
const buildxOutput = platform ? 'type=oci,dest=/tmp/output.tar' : undefined;
const log = (message) => console.log(message);
const workspaceFolder = path_1.default.resolve(checkoutPath, subFolder);
const fullImageName = imageName
Expand All @@ -276,7 +286,9 @@ function runMain() {
const buildArgs = {
workspaceFolder,
imageName: fullImageName,
platform,
additionalCacheFroms: cacheFrom,
output: buildxOutput,
};
console.log('\n\n');
console.log('***');
Expand Down Expand Up @@ -350,7 +362,7 @@ function runMain() {
}
exports.runMain = runMain;
function runPost() {
var _a, _b, _c, _d, _e;
var _a, _b, _c, _d, _e, _f;
return __awaiter(this, void 0, void 0, function* () {
const pushOption = task.getInput('push');
const imageName = task.getInput('imageName');
Expand Down Expand Up @@ -399,14 +411,85 @@ function runPost() {
}
return;
}
const imageTag = task.getInput('imageTag');
console.log(`Pushing image ''${imageName}:${imageTag !== null && imageTag !== void 0 ? imageTag : 'latest'}...`);
yield docker_1.pushImage(imageName, imageTag);
const imageTag = (_f = task.getInput('imageTag')) !== null && _f !== void 0 ? _f : 'latest';
const platform = task.getInput('platform');
if (platform) {
console.log(`Copying multiplatform image ''${imageName}:${imageTag}...`);
const imageSource = 'oci-archive:/tmp/output.tar';
const imageDest = `docker://${imageName}:${imageTag}`;
yield skopeo_1.copyImage(true, imageSource, imageDest);
}
else {
console.log(`Pushing image ''${imageName}:${imageTag}...`);
yield docker_1.pushImage(imageName, imageTag);
}
});
}
exports.runPost = runPost;


/***/ }),

/***/ 9898:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {

"use strict";

var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.copyImage = exports.isSkopeoInstalled = void 0;
const task = __importStar(__nccwpck_require__(347));
const skopeo = __importStar(__nccwpck_require__(2754));
const exec_1 = __nccwpck_require__(7757);
function isSkopeoInstalled() {
return __awaiter(this, void 0, void 0, function* () {
return yield skopeo.isSkopeoInstalled(exec_1.exec);
});
}
exports.isSkopeoInstalled = isSkopeoInstalled;
function copyImage(all, source, dest) {
return __awaiter(this, void 0, void 0, function* () {
console.log('📌 Copying image...');
try {
yield skopeo.copyImage(exec_1.exec, all, source, dest);
return true;
}
catch (error) {
task.setResult(task.TaskResult.Failed, error);
return false;
}
});
}
exports.copyImage = copyImage;


/***/ }),

/***/ 6526:
Expand Down Expand Up @@ -17008,6 +17091,12 @@ function devContainerBuild(args, log) {
if (args.imageName) {
commandArgs.push('--image-name', args.imageName);
}
if (args.platform) {
commandArgs.push('--platform', args.platform);
}
if (args.output) {
commandArgs.push('--output', args.output);
}
if (args.userDataFolder) {
commandArgs.push("--user-data-folder", args.userDataFolder);
}
Expand Down Expand Up @@ -17509,6 +17598,47 @@ function populateDefaults(envs) {
}


/***/ }),

/***/ 2754:
/***/ ((__unused_webpack_module, __webpack_exports__, __nccwpck_require__) => {

"use strict";
__nccwpck_require__.r(__webpack_exports__);
/* harmony export */ __nccwpck_require__.d(__webpack_exports__, {
/* harmony export */ "isSkopeoInstalled": () => (/* binding */ isSkopeoInstalled),
/* harmony export */ "copyImage": () => (/* binding */ copyImage)
/* harmony export */ });
var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
function isSkopeoInstalled(exec) {
return __awaiter(this, void 0, void 0, function* () {
const { exitCode } = yield exec('skopeo', ['--help'], { silent: true });
return exitCode === 0;
});
}
function copyImage(exec, all, source, dest) {
return __awaiter(this, void 0, void 0, function* () {
const args = ['copy'];
if (all) {
args.push('--all');
}
args.push(source, dest);
const { exitCode } = yield exec('skopeo', args, {});
if (exitCode !== 0) {
throw new Error(`skopeo copy failed with ${exitCode}`);
}
});
}


/***/ }),

/***/ 2357:
Expand Down
2 changes: 1 addition & 1 deletion azdo-task/DevcontainersCi/dist/index.js.map

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions azdo-task/DevcontainersCi/dist/skopeo.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { ExecFunction } from './exec';
export declare function isSkopeoInstalled(exec: ExecFunction): Promise<boolean>;
export declare function copyImage(exec: ExecFunction, all: boolean, source: string, dest: string): Promise<void>;
29 changes: 25 additions & 4 deletions azdo-task/DevcontainersCi/lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const path_1 = __importDefault(require("path"));
const envvars_1 = require("../../../common/src/envvars");
const dev_container_cli_1 = require("../../../common/src/dev-container-cli");
const docker_1 = require("./docker");
const skopeo_1 = require("./skopeo");
const exec_1 = require("./exec");
function runMain() {
var _a, _b, _c, _d, _e, _f, _g;
Expand All @@ -61,12 +62,21 @@ function runMain() {
const checkoutPath = (_a = task.getInput('checkoutPath')) !== null && _a !== void 0 ? _a : '';
const imageName = task.getInput('imageName');
const imageTag = task.getInput('imageTag');
const platform = task.getInput('platform');
const subFolder = (_b = task.getInput('subFolder')) !== null && _b !== void 0 ? _b : '.';
const runCommand = task.getInput('runCmd');
const envs = (_d = (_c = task.getInput('env')) === null || _c === void 0 ? void 0 : _c.split('\n')) !== null && _d !== void 0 ? _d : [];
const inputEnvsWithDefaults = envvars_1.populateDefaults(envs);
const cacheFrom = (_f = (_e = task.getInput('cacheFrom')) === null || _e === void 0 ? void 0 : _e.split('\n')) !== null && _f !== void 0 ? _f : [];
const skipContainerUserIdUpdate = ((_g = task.getInput('skipContainerUserIdUpdate')) !== null && _g !== void 0 ? _g : 'false') === 'true';
if (platform) {
const skopeoInstalled = yield skopeo_1.isSkopeoInstalled();
if (!skopeoInstalled) {
console.log('skopeo not available and is required for multi-platform builds - make sure it is installed on your build agent');
return;
}
}
const buildxOutput = platform ? 'type=oci,dest=/tmp/output.tar' : undefined;
const log = (message) => console.log(message);
const workspaceFolder = path_1.default.resolve(checkoutPath, subFolder);
const fullImageName = imageName
Expand All @@ -87,7 +97,9 @@ function runMain() {
const buildArgs = {
workspaceFolder,
imageName: fullImageName,
platform,
additionalCacheFroms: cacheFrom,
output: buildxOutput,
};
console.log('\n\n');
console.log('***');
Expand Down Expand Up @@ -161,7 +173,7 @@ function runMain() {
}
exports.runMain = runMain;
function runPost() {
var _a, _b, _c, _d, _e;
var _a, _b, _c, _d, _e, _f;
return __awaiter(this, void 0, void 0, function* () {
const pushOption = task.getInput('push');
const imageName = task.getInput('imageName');
Expand Down Expand Up @@ -210,9 +222,18 @@ function runPost() {
}
return;
}
const imageTag = task.getInput('imageTag');
console.log(`Pushing image ''${imageName}:${imageTag !== null && imageTag !== void 0 ? imageTag : 'latest'}...`);
yield docker_1.pushImage(imageName, imageTag);
const imageTag = (_f = task.getInput('imageTag')) !== null && _f !== void 0 ? _f : 'latest';
const platform = task.getInput('platform');
if (platform) {
console.log(`Copying multiplatform image ''${imageName}:${imageTag}...`);
const imageSource = 'oci-archive:/tmp/output.tar';
const imageDest = `docker://${imageName}:${imageTag}`;
yield skopeo_1.copyImage(true, imageSource, imageDest);
}
else {
console.log(`Pushing image ''${imageName}:${imageTag}...`);
yield docker_1.pushImage(imageName, imageTag);
}
});
}
exports.runPost = runPost;
Loading