Skip to content

Commit 16fb106

Browse files
authored
Automatically remove the --platform flag from the Dockerfile on deployment (#601)
1 parent 8448595 commit 16fb106

File tree

1 file changed

+83
-12
lines changed

1 file changed

+83
-12
lines changed

src/commands/image/imageSource/buildImageInAzure/UploadSourceCodeStep.ts

Lines changed: 83 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55

66
import { getResourceGroupFromId } from '@microsoft/vscode-azext-azureutils';
77
import { AzExtFsExtra, GenericParentTreeItem, GenericTreeItem, activityFailContext, activityFailIcon, activitySuccessContext, activitySuccessIcon, nonNullValue } from '@microsoft/vscode-azext-utils';
8+
import { randomUUID } from 'crypto';
9+
import { tmpdir } from 'os';
810
import * as path from 'path';
911
import * as tar from 'tar';
10-
import { type Progress } from 'vscode';
12+
import { ThemeColor, ThemeIcon, type Progress } from 'vscode';
13+
import { ext } from '../../../../extensionVariables';
1114
import { ExecuteActivityOutputStepBase, type ExecuteActivityOutput } from '../../../../utils/activity/ExecuteActivityOutputStepBase';
1215
import { createActivityChildContext } from '../../../../utils/activity/activityUtils';
1316
import { createContainerRegistryManagementClient } from '../../../../utils/azureClients';
@@ -16,12 +19,14 @@ import { type BuildImageInAzureImageSourceContext } from './BuildImageInAzureIma
1619

1720
const vcsIgnoreList = ['.git', '.gitignore', '.bzr', 'bzrignore', '.hg', '.hgignore', '.svn'];
1821

19-
export class UploadSourceCodeStep extends ExecuteActivityOutputStepBase<BuildImageInAzureImageSourceContext> {
22+
export class UploadSourceCodeStep<T extends BuildImageInAzureImageSourceContext> extends ExecuteActivityOutputStepBase<T> {
2023
public priority: number = 430;
24+
/** Path to a directory containing a custom Dockerfile that we sometimes build and upload for the user */
25+
private _customDockerfileDirPath?: string;
2126
/** Relative path of src folder from rootFolder and what gets deployed */
2227
private _sourceFilePath: string;
2328

24-
protected async executeCore(context: BuildImageInAzureImageSourceContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise<void> {
29+
protected async executeCore(context: T, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise<void> {
2530
this._sourceFilePath = context.rootFolder.uri.fsPath === context.srcPath ? '.' : path.relative(context.rootFolder.uri.fsPath, context.srcPath);
2631
context.telemetry.properties.sourceDepth = this._sourceFilePath === '.' ? '0' : String(this._sourceFilePath.split(path.sep).length);
2732

@@ -35,7 +40,31 @@ export class UploadSourceCodeStep extends ExecuteActivityOutputStepBase<BuildIma
3540
let items = await AzExtFsExtra.readDirectory(source);
3641
items = items.filter(i => !vcsIgnoreList.includes(i.name));
3742

38-
await tar.c({ cwd: source, gzip: true, file: context.tarFilePath }, items.map(i => path.relative(source, i.fsPath)));
43+
await this.buildCustomDockerfileIfNecessary(context);
44+
if (this._customDockerfileDirPath) {
45+
// Create an uncompressed tarball with the base project
46+
const tempTarFilePath: string = context.tarFilePath.replace(/\.tar\.gz/, '.tar');
47+
await tar.c({ cwd: source, file: tempTarFilePath }, items.map(i => path.relative(source, i.fsPath)));
48+
49+
// Append/Overwrite the original Dockerfile with the custom one that was made
50+
await tar.r({ cwd: this._customDockerfileDirPath, file: tempTarFilePath }, [path.relative(source, context.dockerfilePath)]);
51+
52+
// Create the final compressed version
53+
// Note: Noticed some hanging issues when using the async version to add existing tar archives;
54+
// however, the issues seem to disappear when utilizing the sync version
55+
tar.c({ cwd: tmpdir(), gzip: true, sync: true, file: context.tarFilePath }, [`@${path.basename(tempTarFilePath)}`]);
56+
57+
try {
58+
// Remove temporarily created resources
59+
await AzExtFsExtra.deleteResource(tempTarFilePath);
60+
await AzExtFsExtra.deleteResource(this._customDockerfileDirPath, { recursive: true });
61+
} catch {
62+
// Swallow error, don't halt the deploy process just because we couldn't delete the temp files, provide a warning instead
63+
ext.outputChannel.appendLog(localize('errorDeletingTempFiles', 'Warning: Could not remove some of the following temporary files: "{0}", "{1}". Try removing these manually at a later time.', tempTarFilePath, this._customDockerfileDirPath));
64+
}
65+
} else {
66+
await tar.c({ cwd: source, gzip: true, file: context.tarFilePath }, items.map(i => path.relative(source, i.fsPath)));
67+
}
3968

4069
const sourceUploadLocation = await context.client.registries.getBuildSourceUploadUrl(context.resourceGroupName, context.registryName);
4170
const uploadUrl: string = nonNullValue(sourceUploadLocation.uploadUrl);
@@ -48,22 +77,64 @@ export class UploadSourceCodeStep extends ExecuteActivityOutputStepBase<BuildIma
4877
context.uploadedSourceLocation = relativePath;
4978
}
5079

51-
public shouldExecute(context: BuildImageInAzureImageSourceContext): boolean {
80+
public shouldExecute(context: T): boolean {
5281
return !context.uploadedSourceLocation;
5382
}
5483

55-
protected createSuccessOutput(context: BuildImageInAzureImageSourceContext): ExecuteActivityOutput {
84+
/**
85+
* Checks and creates a custom Dockerfile if necessary to be used in place of the original
86+
* @populates this._customDockerfileDirPath
87+
*/
88+
private async buildCustomDockerfileIfNecessary(context: T): Promise<void> {
89+
// Build a custom Dockerfile if it has ACR's unsupported `--platform` flag
90+
// See: https://github.com/Azure/acr/issues/697
91+
const platformRegex: RegExp = /^(FROM.*)\s--platform=\S+(.*)$/gm;
92+
let dockerfileContent: string = await AzExtFsExtra.readFile(context.dockerfilePath);
93+
94+
if (!platformRegex.test(dockerfileContent)) {
95+
return;
96+
}
97+
98+
ext.outputChannel.appendLog(localize('removePlatformFlag', 'Detected a "--platform" flag in the Dockerfile. This flag is not supported in ACR. Attempting to provide a Dockerfile with the "--platform" flag removed.'));
99+
dockerfileContent = dockerfileContent.replace(platformRegex, '$1$2');
100+
101+
const customDockerfileDirPath: string = path.join(tmpdir(), randomUUID());
102+
const dockerfileRelativePath: string = path.relative(context.srcPath, context.dockerfilePath);
103+
const customDockerfilePath = path.join(customDockerfileDirPath, dockerfileRelativePath);
104+
await AzExtFsExtra.writeFile(customDockerfilePath, dockerfileContent);
105+
106+
this._customDockerfileDirPath = customDockerfileDirPath;
107+
}
108+
109+
protected createSuccessOutput(context: T): ExecuteActivityOutput {
110+
const baseTreeItemOptions = {
111+
contextValue: createActivityChildContext(['uploadSourceCodeStepSuccessItem', activitySuccessContext]),
112+
label: localize('uploadSourceCodeLabel', 'Upload source code from "{1}" directory to registry "{0}"', context.registry?.name, this._sourceFilePath),
113+
iconPath: activitySuccessIcon,
114+
};
115+
116+
let parentTreeItem: GenericParentTreeItem | undefined;
117+
if (this._customDockerfileDirPath) {
118+
parentTreeItem = new GenericParentTreeItem(undefined, {
119+
...baseTreeItemOptions,
120+
loadMoreChildrenImpl: () => {
121+
const removePlatformFlagItem = new GenericTreeItem(undefined, {
122+
contextValue: createActivityChildContext(['removePlatformFlagItem']),
123+
label: localize('removePlatformFlag', 'Remove unsupported ACR "--platform" flag'),
124+
iconPath: new ThemeIcon('dash', new ThemeColor('terminal.ansiWhite')),
125+
});
126+
return Promise.resolve([removePlatformFlagItem]);
127+
}
128+
});
129+
}
130+
56131
return {
57-
item: new GenericTreeItem(undefined, {
58-
contextValue: createActivityChildContext(['uploadSourceCodeStepSuccessItem', activitySuccessContext]),
59-
label: localize('uploadSourceCodeLabel', 'Upload source code from "{1}" directory to registry "{0}"', context.registry?.name, this._sourceFilePath),
60-
iconPath: activitySuccessIcon
61-
}),
132+
item: parentTreeItem ?? new GenericTreeItem(undefined, { ...baseTreeItemOptions }),
62133
message: localize('uploadedSourceCodeSuccess', 'Uploaded source code from "{1}" directory to registry "{0}" for remote build.', context.registry?.name, this._sourceFilePath)
63134
};
64135
}
65136

66-
protected createFailOutput(context: BuildImageInAzureImageSourceContext): ExecuteActivityOutput {
137+
protected createFailOutput(context: T): ExecuteActivityOutput {
67138
return {
68139
item: new GenericParentTreeItem(undefined, {
69140
contextValue: createActivityChildContext(['uploadSourceCodeStepFailItem', activityFailContext]),

0 commit comments

Comments
 (0)