Skip to content

Commit eda6cf8

Browse files
authored
Use label=disable instead of z flag (microsoft/vscode-remote-release#10585) (#1045)
1 parent c3e6670 commit eda6cf8

File tree

8 files changed

+98
-10
lines changed

8 files changed

+98
-10
lines changed

.github/workflows/dev-containers.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,13 @@ jobs:
5252
"src/test/cli.exec.buildKit.2.test.ts",
5353
"src/test/cli.exec.nonBuildKit.1.test.ts",
5454
"src/test/cli.exec.nonBuildKit.2.test.ts",
55+
"src/test/cli.podman.test.ts",
5556
"src/test/cli.test.ts",
5657
"src/test/cli.up.test.ts",
5758
"src/test/imageMetadata.test.ts",
5859
"src/test/container-features/containerFeaturesOCIPush.test.ts",
5960
# Run all except the above:
60-
"--exclude src/test/container-features/containerFeaturesOrder.test.ts --exclude src/test/container-features/registryCompatibilityOCI.test.ts --exclude src/test/container-features/containerFeaturesOCIPush.test.ts --exclude src/test/container-features/e2e.test.ts --exclude src/test/container-features/featuresCLICommands.test.ts --exclude src/test/cli.build.test.ts --exclude src/test/cli.exec.buildKit.1.test.ts --exclude src/test/cli.exec.buildKit.2.test.ts --exclude src/test/cli.exec.nonBuildKit.1.test.ts --exclude src/test/cli.exec.nonBuildKit.2.test.ts --exclude src/test/cli.test.ts --exclude src/test/cli.up.test.ts --exclude src/test/imageMetadata.test.ts 'src/test/**/*.test.ts'",
61+
"--exclude src/test/container-features/containerFeaturesOrder.test.ts --exclude src/test/container-features/registryCompatibilityOCI.test.ts --exclude src/test/container-features/containerFeaturesOCIPush.test.ts --exclude src/test/container-features/e2e.test.ts --exclude src/test/container-features/featuresCLICommands.test.ts --exclude src/test/cli.build.test.ts --exclude src/test/cli.exec.buildKit.1.test.ts --exclude src/test/cli.exec.buildKit.2.test.ts --exclude src/test/cli.exec.nonBuildKit.1.test.ts --exclude src/test/cli.exec.nonBuildKit.2.test.ts --exclude src/test/cli.podman.test.ts --exclude src/test/cli.test.ts --exclude src/test/cli.up.test.ts --exclude src/test/imageMetadata.test.ts 'src/test/**/*.test.ts'",
6162
]
6263
steps:
6364
- name: Checkout
@@ -68,6 +69,12 @@ jobs:
6869
node-version: '18.x'
6970
registry-url: 'https://npm.pkg.github.com'
7071
scope: '@microsoft'
72+
- name: Tools Info
73+
run: |
74+
docker info
75+
docker buildx version
76+
podman info
77+
podman buildx version
7178
- name: Install Dependencies
7279
run: |
7380
yarn install --frozen-lockfile

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
Notable changes.
44

5+
## July 2025
6+
7+
### [0.80.0]
8+
- Podman: Use label=disable instead of z flag (https://github.com/microsoft/vscode-remote-release/issues/10585)
9+
510
## June 2025
611

712
### [0.79.0]

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@devcontainers/cli",
33
"description": "Dev Containers CLI",
4-
"version": "0.79.0",
4+
"version": "0.80.0",
55
"bin": {
66
"devcontainer": "devcontainer.js"
77
},

src/spec-configuration/containerFeaturesConfiguration.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -289,9 +289,8 @@ function escapeQuotesForShell(input: string) {
289289
return input.replace(new RegExp(`'`, 'g'), `'\\''`);
290290
}
291291

292-
export function getFeatureLayers(featuresConfig: FeaturesConfig, containerUser: string, remoteUser: string, isBuildah = false, useBuildKitBuildContexts = false, contentSourceRootPath = '/tmp/build-features') {
292+
export function getFeatureLayers(featuresConfig: FeaturesConfig, containerUser: string, remoteUser: string, useBuildKitBuildContexts = false, contentSourceRootPath = '/tmp/build-features') {
293293

294-
const useSELinuxLabel = process.platform === 'linux' && isBuildah;
295294
const builtinsEnvFile = `${path.posix.join(FEATURES_CONTAINER_TEMP_DEST_FOLDER, 'devcontainer-features.builtin.env')}`;
296295
let result = `RUN \\
297296
echo "_CONTAINER_USER_HOME=$(${getEntPasswdShellCommand(containerUser)} | cut -d: -f6)" >> ${builtinsEnvFile} && \\
@@ -313,7 +312,7 @@ RUN chmod -R 0755 ${dest} \\
313312
314313
`;
315314
} else {
316-
result += `RUN --mount=type=bind,from=dev_containers_feature_content_source,source=${source},target=/tmp/build-features-src/${folder}${useSELinuxLabel ? ',z' : ''} \\
315+
result += `RUN --mount=type=bind,from=dev_containers_feature_content_source,source=${source},target=/tmp/build-features-src/${folder} \\
317316
cp -ar /tmp/build-features-src/${folder} ${FEATURES_CONTAINER_TEMP_DEST_FOLDER} \\
318317
&& chmod -R 0755 ${dest} \\
319318
&& cd ${dest} \\
@@ -341,7 +340,7 @@ RUN chmod -R 0755 ${dest} \\
341340
`;
342341
} else {
343342
result += `
344-
RUN --mount=type=bind,from=dev_containers_feature_content_source,source=${source},target=/tmp/build-features-src/${feature.consecutiveId}${useSELinuxLabel ? ',z' : ''} \\
343+
RUN --mount=type=bind,from=dev_containers_feature_content_source,source=${source},target=/tmp/build-features-src/${feature.consecutiveId} \\
345344
cp -ar /tmp/build-features-src/${feature.consecutiveId} ${FEATURES_CONTAINER_TEMP_DEST_FOLDER} \\
346345
&& chmod -R 0755 ${dest} \\
347346
&& cd ${dest} \\

src/spec-node/containerFeatures.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { FeaturesConfig, getContainerFeaturesBaseDockerFile, getFeatureInstallWr
1212
import { readLocalFile } from '../spec-utils/pfs';
1313
import { includeAllConfiguredFeatures } from '../spec-utils/product';
1414
import { createFeaturesTempFolder, DockerResolverParameters, getCacheFolder, getFolderImageName, getEmptyContextFolder, SubstitutedConfig } from './utils';
15-
import { isEarlierVersion, parseVersion } from '../spec-common/commonUtils';
15+
import { isEarlierVersion, parseVersion, runCommandNoPty } from '../spec-common/commonUtils';
1616
import { getDevcontainerMetadata, getDevcontainerMetadataLabel, getImageBuildInfoFromImage, ImageBuildInfo, ImageMetadataEntry, imageMetadataLabel, MergedDevContainerConfig } from './imageMetadata';
1717
import { supportsBuildContexts } from './dockerfileUtils';
1818
import { ContainerError } from '../spec-common/errors';
@@ -92,6 +92,10 @@ export async function extendImage(params: DockerResolverParameters, config: Subs
9292
for (const buildContext in featureBuildInfo.buildKitContexts) {
9393
args.push('--build-context', `${buildContext}=${featureBuildInfo.buildKitContexts[buildContext]}`);
9494
}
95+
96+
for (const securityOpt of featureBuildInfo.securityOpts) {
97+
args.push('--security-opt', securityOpt);
98+
}
9599
} else {
96100
// Not using buildx
97101
args.push(
@@ -186,6 +190,7 @@ export interface ImageBuildOptions {
186190
dockerfilePrefixContent: string;
187191
buildArgs: Record<string, string>;
188192
buildKitContexts: Record<string, string>;
193+
securityOpts: string[];
189194
}
190195

191196
function getImageBuildOptions(params: DockerResolverParameters, config: SubstitutedConfig<DevContainerConfig>, dstFolder: string, baseName: string, imageBuildInfo: ImageBuildInfo): ImageBuildOptions {
@@ -204,6 +209,7 @@ ${getDevcontainerMetadataLabel(getDevcontainerMetadata(imageBuildInfo.metadata,
204209
_DEV_CONTAINERS_BASE_IMAGE: baseName,
205210
} as Record<string, string>,
206211
buildKitContexts: {} as Record<string, string>,
212+
securityOpts: [],
207213
};
208214
}
209215

@@ -234,7 +240,7 @@ async function getFeaturesBuildOptions(params: DockerResolverParameters, devCont
234240
const minRequiredVersion = [0, 8, 0];
235241
const useBuildKitBuildContexts = buildKitVersionParsed ? !isEarlierVersion(buildKitVersionParsed, minRequiredVersion) : false;
236242
const buildContentImageName = 'dev_container_feature_content_temp';
237-
const isBuildah = !!params.buildKitVersion?.versionString.toLowerCase().includes('buildah');
243+
const disableSELinuxLabels = useBuildKitBuildContexts && await isUsingSELinuxLabels(params);
238244

239245
const omitPropertyOverride = params.common.skipPersistingCustomizationsFromFeatures ? ['customizations'] : [];
240246
const imageMetadata = getDevcontainerMetadata(imageBuildInfo.metadata, devContainerConfig, featuresConfig, omitPropertyOverride, getOmitDevcontainerPropertyOverride(params.common));
@@ -251,7 +257,7 @@ async function getFeaturesBuildOptions(params: DockerResolverParameters, devCont
251257
const contentSourceRootPath = useBuildKitBuildContexts ? '.' : '/tmp/build-features/';
252258
const dockerfile = getContainerFeaturesBaseDockerFile(contentSourceRootPath)
253259
.replace('#{nonBuildKitFeatureContentFallback}', useBuildKitBuildContexts ? '' : `FROM ${buildContentImageName} as dev_containers_feature_content_source`)
254-
.replace('#{featureLayer}', getFeatureLayers(featuresConfig, containerUser, remoteUser, isBuildah, useBuildKitBuildContexts, contentSourceRootPath))
260+
.replace('#{featureLayer}', getFeatureLayers(featuresConfig, containerUser, remoteUser, useBuildKitBuildContexts, contentSourceRootPath))
255261
.replace('#{containerEnv}', generateContainerEnvsV1(featuresConfig))
256262
.replace('#{devcontainerMetadata}', getDevcontainerMetadataLabel(imageMetadata))
257263
.replace('#{containerEnvMetadata}', generateContainerEnvs(devContainerConfig.config.containerEnv, true))
@@ -343,9 +349,32 @@ ARG _DEV_CONTAINERS_BASE_IMAGE=placeholder
343349
_DEV_CONTAINERS_FEATURE_CONTENT_SOURCE: buildContentImageName,
344350
},
345351
buildKitContexts: useBuildKitBuildContexts ? { dev_containers_feature_content_source: dstFolder } : {},
352+
securityOpts: disableSELinuxLabels ? ['label=disable'] : [],
346353
};
347354
}
348355

356+
async function isUsingSELinuxLabels(params: DockerResolverParameters): Promise<boolean> {
357+
try {
358+
const { common } = params;
359+
const { cliHost, output } = common;
360+
return params.isPodman && cliHost.platform === 'linux'
361+
&& (await runCommandNoPty({
362+
exec: cliHost.exec,
363+
cmd: 'getenforce',
364+
output,
365+
print: true,
366+
})).stdout.toString().trim() !== 'Disabled'
367+
&& (await dockerCLI({
368+
...toExecParameters(params),
369+
print: true,
370+
}, 'info', '-f', '{{.Host.Security.SELinuxEnabled}}')).stdout.toString().trim() === 'true';
371+
} catch {
372+
// If we can't run the commands, assume SELinux is not enabled.
373+
return false;
374+
375+
}
376+
}
377+
349378
export function findContainerUsers(imageMetadata: SubstitutedConfig<ImageMetadataEntry[]>, composeServiceUser: string | undefined, imageUser: string) {
350379
const reversed = imageMetadata.config.slice().reverse();
351380
const containerUser = reversed.find(entry => entry.containerUser)?.containerUser || composeServiceUser || imageUser;

src/spec-node/dockerCompose.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ export async function buildAndExtendDockerCompose(configWithRaw: SubstitutedConf
188188

189189
// determine whether we need to extend with features
190190
const version = parseVersion((await params.dockerComposeCLI()).version);
191-
const supportsAdditionalBuildContexts = version && !isEarlierVersion(version, [2, 17, 0]);
191+
const supportsAdditionalBuildContexts = !params.isPodman && version && !isEarlierVersion(version, [2, 17, 0]);
192192
const optionalBuildKitParams = supportsAdditionalBuildContexts ? params : { ...params, buildKitVersion: undefined };
193193
const extendImageBuildInfo = await getExtendImageBuildInfo(optionalBuildKitParams, configWithRaw, baseName, imageBuildInfo, composeService.user, additionalFeatures, canAddLabelsToContainer);
194194

src/spec-node/singleContainer.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@ async function buildAndExtendImage(buildParams: DockerResolverParameters, config
180180
for (const buildArg in featureBuildInfo.buildArgs) {
181181
additionalBuildArgs.push('--build-arg', `${buildArg}=${featureBuildInfo.buildArgs[buildArg]}`);
182182
}
183+
184+
for (const securityOpt of featureBuildInfo.securityOpts) {
185+
additionalBuildArgs.push('--security-opt', securityOpt);
186+
}
183187
}
184188

185189
const args: string[] = [];

src/test/cli.podman.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as assert from 'assert';
7+
import * as path from 'path';
8+
import { shellExec } from './testUtils';
9+
10+
const pkg = require('../../package.json');
11+
12+
describe('Dev Containers CLI using Podman', function () {
13+
this.timeout('240s');
14+
15+
const tmp = path.relative(process.cwd(), path.join(__dirname, 'tmp'));
16+
const cli = `npx --prefix ${tmp} devcontainer`;
17+
18+
before('Install', async () => {
19+
await shellExec(`rm -rf ${tmp}/node_modules`);
20+
await shellExec(`mkdir -p ${tmp}`);
21+
await shellExec(`npm --prefix ${tmp} install devcontainers-cli-${pkg.version}.tgz`);
22+
});
23+
24+
describe('Command up using Podman', () => {
25+
26+
it('should execute successfully with valid config with features', async () => {
27+
const res = await shellExec(`${cli} up --docker-path podman --workspace-folder ${__dirname}/configs/image-with-features`);
28+
const response = JSON.parse(res.stdout);
29+
assert.equal(response.outcome, 'success');
30+
const containerId: string = response.containerId;
31+
assert.ok(containerId, 'Container id not found.');
32+
await shellExec(`podman rm -f ${containerId}`);
33+
});
34+
35+
it('should execute successfully with valid config with features', async () => {
36+
const res = await shellExec(`${cli} up --docker-path podman --workspace-folder ${__dirname}/configs/dockerfile-with-features`);
37+
const response = JSON.parse(res.stdout);
38+
assert.equal(response.outcome, 'success');
39+
const containerId: string = response.containerId;
40+
assert.ok(containerId, 'Container id not found.');
41+
await shellExec(`podman rm -f ${containerId}`);
42+
});
43+
});
44+
});

0 commit comments

Comments
 (0)