55
66import { getResourceGroupFromId } from '@microsoft/vscode-azext-azureutils' ;
77import { AzExtFsExtra , GenericParentTreeItem , GenericTreeItem , activityFailContext , activityFailIcon , activitySuccessContext , activitySuccessIcon , nonNullValue } from '@microsoft/vscode-azext-utils' ;
8+ import { randomUUID } from 'crypto' ;
9+ import { tmpdir } from 'os' ;
810import * as path from 'path' ;
911import * as tar from 'tar' ;
10- import { type Progress } from 'vscode' ;
12+ import { ThemeColor , ThemeIcon , type Progress } from 'vscode' ;
13+ import { ext } from '../../../../extensionVariables' ;
1114import { ExecuteActivityOutputStepBase , type ExecuteActivityOutput } from '../../../../utils/activity/ExecuteActivityOutputStepBase' ;
1215import { createActivityChildContext } from '../../../../utils/activity/activityUtils' ;
1316import { createContainerRegistryManagementClient } from '../../../../utils/azureClients' ;
@@ -16,12 +19,14 @@ import { type BuildImageInAzureImageSourceContext } from './BuildImageInAzureIma
1619
1720const 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 ( / \. t a r \. g z / , '.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 = / ^ ( F R O M .* ) \s - - p l a t f o r m = \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