diff --git a/lib/modules/manager/dockerfile/extract.spec.ts b/lib/modules/manager/dockerfile/extract.spec.ts index 9c47215ae70a61..fab901c8938369 100644 --- a/lib/modules/manager/dockerfile/extract.spec.ts +++ b/lib/modules/manager/dockerfile/extract.spec.ts @@ -941,6 +941,16 @@ describe('modules/manager/dockerfile/extract', () => { it('handles an alternative escape character', () => { const res = extractPackageFile(d4, '', {})?.deps; expect(res).toEqual([ + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + currentDigest: undefined, + currentValue: '1', + datasource: 'docker', + depName: 'docker/dockerfile', + depType: 'syntax', + replaceString: 'docker/dockerfile:1', + }, { autoReplaceStringTemplate: ' ARG `\n' + @@ -1152,6 +1162,60 @@ describe('modules/manager/dockerfile/extract', () => { }); }); + it('handles # syntax statements', () => { + const res = extractPackageFile( + '# syntax=docker/dockerfile:1.1.7\n' + 'FROM alpine:3.13.5\n', + '', + {}, + ); + expect(res).toEqual({ + deps: [ + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + currentDigest: undefined, + currentValue: '1.1.7', + datasource: 'docker', + depName: 'docker/dockerfile', + depType: 'syntax', + replaceString: 'docker/dockerfile:1.1.7', + }, + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + currentDigest: undefined, + currentValue: '3.13.5', + datasource: 'docker', + depName: 'alpine', + depType: 'final', + replaceString: 'alpine:3.13.5', + }, + ], + }); + }); + + it('ignores # syntax statements after first line', () => { + const res = extractPackageFile( + 'FROM alpine:3.13.5\n' + '# syntax=docker/dockerfile:1.1.7\n', + '', + {}, + ); + expect(res).toEqual({ + deps: [ + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + currentDigest: undefined, + currentValue: '3.13.5', + datasource: 'docker', + depName: 'alpine', + depType: 'final', + replaceString: 'alpine:3.13.5', + }, + ], + }); + }); + describe('getDep()', () => { it('rejects null', () => { expect(getDep(null)).toEqual({ skipReason: 'invalid-value' }); diff --git a/lib/modules/manager/dockerfile/extract.ts b/lib/modules/manager/dockerfile/extract.ts index 2841052c8d6358..fdce4db0522315 100644 --- a/lib/modules/manager/dockerfile/extract.ts +++ b/lib/modules/manager/dockerfile/extract.ts @@ -251,6 +251,7 @@ export function extractPackageFile( let escapeChar = '\\\\'; let lookForEscapeChar = true; + let lookForSyntaxDirective = true; const lineFeed = content.indexOf('\r\n') >= 0 ? '\r\n' : '\n'; const lines = content.split(newlineRegex); @@ -272,6 +273,33 @@ export function extractPackageFile( } } + if (lookForSyntaxDirective) { + const syntaxRegex = regEx( + '^#[ \\t]*syntax[ \\t]*=[ \\t]*(?\\S+)', + 'im', + ); + const syntaxMatch = instruction.match(syntaxRegex); + if (syntaxMatch?.groups?.image) { + const syntaxImage = syntaxMatch.groups.image; + const lineNumberRanges: number[][] = [ + [lineNumberInstrStart, lineNumber], + ]; + const dep = getDep(syntaxImage, true, config.registryAliases); + dep.depType = 'syntax'; + processDepForAutoReplace(dep, lineNumberRanges, lines, lineFeed); + logger.trace( + { + depName: dep.depName, + currentValue: dep.currentValue, + currentDigest: dep.currentDigest, + }, + 'Dockerfile # syntax', + ); + deps.push(dep); + } + lookForSyntaxDirective = false; + } + const lineContinuationRegex = regEx(escapeChar + '[ \\t]*$|^[ \\t]*#', 'm'); let lineLookahead = instruction; while ( @@ -400,7 +428,9 @@ export function extractPackageFile( return null; } for (const d of deps) { - d.depType = 'stage'; + if (!d.depType) { + d.depType = 'stage'; + } } deps[deps.length - 1].depType = 'final'; return { deps };