feat(manager/dockerfile): add support for registryAliases (#17139)

Co-authored-by: Rhys Arkins <rhys@arkins.net>
Co-authored-by: Niko Hass <nikohass571@gmail.com>
Co-authored-by: Niko Haß <48032410+nikohass@users.noreply.github.com>
Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
This commit is contained in:
Benjamin Appel 2022-08-17 15:57:53 +02:00 committed by GitHub
parent a3c8e2087d
commit 551a40ccab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 604 additions and 84 deletions

View file

@ -2525,6 +2525,11 @@ This feature works with the following managers:
- [`helmv3`](/modules/manager/helmv3/) - [`helmv3`](/modules/manager/helmv3/)
- [`helmfile`](/modules/manager/helmfile/) - [`helmfile`](/modules/manager/helmfile/)
- [`gitlabci`](/modules/manager/gitlabci/) - [`gitlabci`](/modules/manager/gitlabci/)
- [`dockerfile`](/modules/manager/dockerfile)
- [`docker-compose`](/modules/manager/docker-compose)
- [`kubernetes`](/modules/manager/kubernetes)
- [`ansible`](/modules/manager/ansible)
- [`droneci`](/modules/manager/droneci)
## registryUrls ## registryUrls

View file

@ -822,7 +822,17 @@ const options: RenovateOptions[] = [
type: 'string', type: 'string',
format: 'uri', format: 'uri',
}, },
supportedManagers: ['helm-requirements', 'helmv3', 'helmfile', 'gitlabci'], supportedManagers: [
'helm-requirements',
'helmv3',
'helmfile',
'gitlabci',
'dockerfile',
'docker-compose',
'kubernetes',
'ansible',
'droneci',
],
}, },
{ {
name: 'defaultRegistryUrls', name: 'defaultRegistryUrls',

View file

@ -4,19 +4,110 @@ import { extractPackageFile } from '.';
describe('modules/manager/ansible/extract', () => { describe('modules/manager/ansible/extract', () => {
describe('extractPackageFile()', () => { describe('extractPackageFile()', () => {
it('returns null for empty', () => { it('returns null for empty', () => {
expect(extractPackageFile('nothing here')).toBeNull(); expect(extractPackageFile('nothing here', '', {})).toBeNull();
}); });
it('extracts multiple image lines from docker_container', () => { it('extracts multiple image lines from docker_container', () => {
const res = extractPackageFile(Fixtures.get('main1.yaml')); const res = extractPackageFile(Fixtures.get('main1.yaml'), '', {});
expect(res?.deps).toMatchSnapshot(); expect(res?.deps).toMatchSnapshot();
expect(res?.deps).toHaveLength(9); expect(res?.deps).toHaveLength(9);
}); });
it('extracts multiple image lines from docker_service', () => { it('extracts multiple image lines from docker_service', () => {
const res = extractPackageFile(Fixtures.get('main2.yaml')); const res = extractPackageFile(Fixtures.get('main2.yaml'), '', {});
expect(res?.deps).toMatchSnapshot(); expect(res?.deps).toMatchSnapshot();
expect(res?.deps).toHaveLength(4); expect(res?.deps).toHaveLength(4);
}); });
it('extracts image and replaces registry', () => {
const res = extractPackageFile(
`---
- name: Re-create a redis container
docker_container:
name: myredis
image: quay.io/redis:0.0.1`,
'',
{
registryAliases: {
'quay.io': 'my-quay-mirror.registry.com',
},
}
);
expect(res).toEqual({
deps: [
{
autoReplaceStringTemplate:
'quay.io/redis:{{#if newValue}}{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentDigest: undefined,
currentValue: '0.0.1',
datasource: 'docker',
depName: 'my-quay-mirror.registry.com/redis',
replaceString: 'quay.io/redis:0.0.1',
versioning: 'docker',
},
],
});
});
it('extracts image but no replacement', () => {
const res = extractPackageFile(
`---
- name: Re-create a redis container
docker_container:
name: myredis
image: quay.io/redis:0.0.1`,
'',
{
registryAliases: {
'index.docker.io': 'my-docker-mirror.registry.com',
},
}
);
expect(res).toEqual({
deps: [
{
autoReplaceStringTemplate:
'{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentDigest: undefined,
currentValue: '0.0.1',
datasource: 'docker',
depName: 'quay.io/redis',
replaceString: 'quay.io/redis:0.0.1',
versioning: 'docker',
},
],
});
});
it('extracts image and no double replacement', () => {
const res = extractPackageFile(
`---
- name: Re-create a redis container
docker_container:
name: myredis
image: quay.io/redis:0.0.1`,
'',
{
registryAliases: {
'quay.io': 'my-quay-mirror.registry.com',
'my-quay-mirror.registry.com': 'quay.io',
},
}
);
expect(res).toEqual({
deps: [
{
autoReplaceStringTemplate:
'quay.io/redis:{{#if newValue}}{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentDigest: undefined,
currentValue: '0.0.1',
datasource: 'docker',
depName: 'my-quay-mirror.registry.com/redis',
replaceString: 'quay.io/redis:0.0.1',
versioning: 'docker',
},
],
});
});
}); });
}); });

View file

@ -2,9 +2,13 @@ import { logger } from '../../../logger';
import { newlineRegex, regEx } from '../../../util/regex'; import { newlineRegex, regEx } from '../../../util/regex';
import * as dockerVersioning from '../../versioning/docker'; import * as dockerVersioning from '../../versioning/docker';
import { getDep } from '../dockerfile/extract'; import { getDep } from '../dockerfile/extract';
import type { PackageDependency, PackageFile } from '../types'; import type { ExtractConfig, PackageDependency, PackageFile } from '../types';
export function extractPackageFile(content: string): PackageFile | null { export function extractPackageFile(
content: string,
_filename: string,
config: ExtractConfig
): PackageFile | null {
logger.trace('ansible.extractPackageFile()'); logger.trace('ansible.extractPackageFile()');
let deps: PackageDependency[] = []; let deps: PackageDependency[] = [];
const re = regEx(/^\s*image:\s*'?"?([^\s'"]+)'?"?\s*$/); const re = regEx(/^\s*image:\s*'?"?([^\s'"]+)'?"?\s*$/);
@ -12,7 +16,7 @@ export function extractPackageFile(content: string): PackageFile | null {
const match = re.exec(line); const match = re.exec(line);
if (match) { if (match) {
const currentFrom = match[1]; const currentFrom = match[1];
const dep = getDep(currentFrom); const dep = getDep(currentFrom, true, config.registryAliases);
logger.debug( logger.debug(
{ {
depName: dep.depName, depName: dep.depName,

View file

@ -9,37 +9,37 @@ const yamlFile3DefaultValue = Fixtures.get('docker-compose.3-default-val.yml');
describe('modules/manager/docker-compose/extract', () => { describe('modules/manager/docker-compose/extract', () => {
describe('extractPackageFile()', () => { describe('extractPackageFile()', () => {
it('returns null for empty', () => { it('returns null for empty', () => {
expect(extractPackageFile('')).toBeNull(); expect(extractPackageFile('', '', {})).toBeNull();
}); });
it('returns null for non-object YAML', () => { it('returns null for non-object YAML', () => {
expect(extractPackageFile('nothing here')).toBeNull(); expect(extractPackageFile('nothing here', '', {})).toBeNull();
}); });
it('returns null for malformed YAML', () => { it('returns null for malformed YAML', () => {
expect(extractPackageFile('nothing here\n:::::::')).toBeNull(); expect(extractPackageFile('nothing here\n:::::::', '', {})).toBeNull();
}); });
it('extracts multiple image lines for version 1', () => { it('extracts multiple image lines for version 1', () => {
const res = extractPackageFile(yamlFile1); const res = extractPackageFile(yamlFile1, '', {});
expect(res?.deps).toMatchSnapshot(); expect(res?.deps).toMatchSnapshot();
expect(res?.deps).toHaveLength(8); expect(res?.deps).toHaveLength(8);
}); });
it('extracts multiple image lines for version 3', () => { it('extracts multiple image lines for version 3', () => {
const res = extractPackageFile(yamlFile3); const res = extractPackageFile(yamlFile3, '', {});
expect(res?.deps).toMatchSnapshot(); expect(res?.deps).toMatchSnapshot();
expect(res?.deps).toHaveLength(8); expect(res?.deps).toHaveLength(8);
}); });
it('extracts multiple image lines for version 3 without set version key', () => { it('extracts multiple image lines for version 3 without set version key', () => {
const res = extractPackageFile(yamlFile3NoVersion); const res = extractPackageFile(yamlFile3NoVersion, '', {});
expect(res?.deps).toMatchSnapshot(); expect(res?.deps).toMatchSnapshot();
expect(res?.deps).toHaveLength(8); expect(res?.deps).toHaveLength(8);
}); });
it('extracts default variable values for version 3', () => { it('extracts default variable values for version 3', () => {
const res = extractPackageFile(yamlFile3DefaultValue); const res = extractPackageFile(yamlFile3DefaultValue, '', {});
expect(res?.deps).toMatchInlineSnapshot(` expect(res?.deps).toMatchInlineSnapshot(`
[ [
{ {
@ -54,5 +54,96 @@ describe('modules/manager/docker-compose/extract', () => {
`); `);
expect(res?.deps).toHaveLength(1); expect(res?.deps).toHaveLength(1);
}); });
it('extracts image and replaces registry', () => {
const res = extractPackageFile(
`
version: "3"
services:
nginx:
image: quay.io/nginx:0.0.1
`,
'',
{
registryAliases: {
'quay.io': 'my-quay-mirror.registry.com',
},
}
);
expect(res).toEqual({
deps: [
{
autoReplaceStringTemplate:
'quay.io/nginx:{{#if newValue}}{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentDigest: undefined,
currentValue: '0.0.1',
datasource: 'docker',
depName: 'my-quay-mirror.registry.com/nginx',
replaceString: 'quay.io/nginx:0.0.1',
},
],
});
});
it('extracts image but no replacement', () => {
const res = extractPackageFile(
`
version: "3"
services:
nginx:
image: quay.io/nginx:0.0.1
`,
'',
{
registryAliases: {
'index.docker.io': 'my-docker-mirror.registry.com',
},
}
);
expect(res).toEqual({
deps: [
{
autoReplaceStringTemplate:
'{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentDigest: undefined,
currentValue: '0.0.1',
datasource: 'docker',
depName: 'quay.io/nginx',
replaceString: 'quay.io/nginx:0.0.1',
},
],
});
});
it('extracts image and no double replacement', () => {
const res = extractPackageFile(
`
version: "3"
services:
nginx:
image: quay.io/nginx:0.0.1
`,
'',
{
registryAliases: {
'quay.io': 'my-quay-mirror.registry.com',
'my-quay-mirror.registry.com': 'quay.io',
},
}
);
expect(res).toEqual({
deps: [
{
autoReplaceStringTemplate:
'quay.io/nginx:{{#if newValue}}{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentDigest: undefined,
currentValue: '0.0.1',
datasource: 'docker',
depName: 'my-quay-mirror.registry.com/nginx',
replaceString: 'quay.io/nginx:0.0.1',
},
],
});
});
}); });
}); });

View file

@ -3,7 +3,7 @@ import { load } from 'js-yaml';
import { logger } from '../../../logger'; import { logger } from '../../../logger';
import { newlineRegex, regEx } from '../../../util/regex'; import { newlineRegex, regEx } from '../../../util/regex';
import { getDep } from '../dockerfile/extract'; import { getDep } from '../dockerfile/extract';
import type { PackageFile } from '../types'; import type { ExtractConfig, PackageFile } from '../types';
import type { DockerComposeConfig } from './types'; import type { DockerComposeConfig } from './types';
class LineMapper { class LineMapper {
@ -30,7 +30,8 @@ class LineMapper {
export function extractPackageFile( export function extractPackageFile(
content: string, content: string,
fileName?: string fileName: string,
extractConfig: ExtractConfig
): PackageFile | null { ): PackageFile | null {
logger.debug('docker-compose.extractPackageFile()'); logger.debug('docker-compose.extractPackageFile()');
let config: DockerComposeConfig; let config: DockerComposeConfig;
@ -71,7 +72,7 @@ export function extractPackageFile(
const deps = Object.values(services || {}) const deps = Object.values(services || {})
.filter((service) => is.string(service?.image) && !service?.build) .filter((service) => is.string(service?.image) && !service?.build)
.map((service) => { .map((service) => {
const dep = getDep(service.image); const dep = getDep(service.image, true, extractConfig.registryAliases);
const lineNumber = lineMapper.pluckLineNumber(service.image); const lineNumber = lineMapper.pluckLineNumber(service.image);
// istanbul ignore if // istanbul ignore if
if (!lineNumber) { if (!lineNumber) {

View file

@ -11,12 +11,12 @@ const d4 = Fixtures.get('4.Dockerfile');
describe('modules/manager/dockerfile/extract', () => { describe('modules/manager/dockerfile/extract', () => {
describe('extractPackageFile()', () => { describe('extractPackageFile()', () => {
it('handles no FROM', () => { it('handles no FROM', () => {
const res = extractPackageFile('no from!'); const res = extractPackageFile('no from!', '', {});
expect(res).toBeNull(); expect(res).toBeNull();
}); });
it('handles naked dep', () => { it('handles naked dep', () => {
const res = extractPackageFile('FROM node\n')?.deps; const res = extractPackageFile('FROM node\n', '', {})?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
{ {
@ -33,7 +33,7 @@ describe('modules/manager/dockerfile/extract', () => {
}); });
it('is case insensitive', () => { it('is case insensitive', () => {
const res = extractPackageFile('From node\n')?.deps; const res = extractPackageFile('From node\n', '', {})?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
{ {
@ -50,7 +50,7 @@ describe('modules/manager/dockerfile/extract', () => {
}); });
it('handles tag', () => { it('handles tag', () => {
const res = extractPackageFile('FROM node:8.9.0-alpine\n')?.deps; const res = extractPackageFile('FROM node:8.9.0-alpine\n', '', {})?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
{ {
@ -68,7 +68,9 @@ describe('modules/manager/dockerfile/extract', () => {
it('handles digest', () => { it('handles digest', () => {
const res = extractPackageFile( const res = extractPackageFile(
'FROM node@sha256:eb85fc5b1198f5e1ec025ea07586bdbbf397e7d82df66c90d7511f533517e063\n' 'FROM node@sha256:eb85fc5b1198f5e1ec025ea07586bdbbf397e7d82df66c90d7511f533517e063\n',
'',
{}
)?.deps; )?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
@ -87,7 +89,9 @@ describe('modules/manager/dockerfile/extract', () => {
it('handles tag and digest', () => { it('handles tag and digest', () => {
const res = extractPackageFile( const res = extractPackageFile(
'FROM node:8.9.0@sha256:eb85fc5b1198f5e1ec025ea07586bdbbf397e7d82df66c90d7511f533517e063\n' 'FROM node:8.9.0@sha256:eb85fc5b1198f5e1ec025ea07586bdbbf397e7d82df66c90d7511f533517e063\n',
'',
{}
)?.deps; )?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
@ -105,7 +109,11 @@ describe('modules/manager/dockerfile/extract', () => {
}); });
it('handles from as', () => { it('handles from as', () => {
const res = extractPackageFile('FROM node:8.9.0-alpine as base\n')?.deps; const res = extractPackageFile(
'FROM node:8.9.0-alpine as base\n',
'',
{}
)?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
{ {
@ -123,7 +131,9 @@ describe('modules/manager/dockerfile/extract', () => {
it('handles comments', () => { it('handles comments', () => {
const res = extractPackageFile( const res = extractPackageFile(
'# some comment\n# another\n\nFROM node\n' '# some comment\n# another\n\nFROM node\n',
'',
{}
)?.deps; )?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
@ -142,7 +152,9 @@ describe('modules/manager/dockerfile/extract', () => {
it('handles custom hosts', () => { it('handles custom hosts', () => {
const res = extractPackageFile( const res = extractPackageFile(
'FROM registry2.something.info/node:8\n' 'FROM registry2.something.info/node:8\n',
'',
{}
)?.deps; )?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
@ -161,7 +173,9 @@ describe('modules/manager/dockerfile/extract', () => {
it('handles custom hosts and suffix', () => { it('handles custom hosts and suffix', () => {
const res = extractPackageFile( const res = extractPackageFile(
'FROM registry2.something.info/node:8-alpine\n' 'FROM registry2.something.info/node:8-alpine\n',
'',
{}
)?.deps; )?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
@ -180,7 +194,9 @@ describe('modules/manager/dockerfile/extract', () => {
it('handles custom hosts with port', () => { it('handles custom hosts with port', () => {
const res = extractPackageFile( const res = extractPackageFile(
'FROM registry2.something.info:5005/node:8\n' 'FROM registry2.something.info:5005/node:8\n',
'',
{}
)?.deps; )?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
@ -201,7 +217,9 @@ describe('modules/manager/dockerfile/extract', () => {
it('handles custom hosts with port without tag', () => { it('handles custom hosts with port without tag', () => {
const res = extractPackageFile( const res = extractPackageFile(
'FROM registry2.something.info:5005/node\n' 'FROM registry2.something.info:5005/node\n',
'',
{}
)?.deps; )?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
@ -220,7 +238,7 @@ describe('modules/manager/dockerfile/extract', () => {
}); });
it('handles quay hosts with port', () => { it('handles quay hosts with port', () => {
const res = extractPackageFile('FROM quay.io:1234/node\n')?.deps; const res = extractPackageFile('FROM quay.io:1234/node\n', '', {})?.deps;
expect(res?.[0]).toMatchInlineSnapshot(` expect(res?.[0]).toMatchInlineSnapshot(`
{ {
"autoReplaceStringTemplate": "{{packageName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", "autoReplaceStringTemplate": "{{packageName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}",
@ -236,7 +254,7 @@ describe('modules/manager/dockerfile/extract', () => {
}); });
it('handles namespaced images', () => { it('handles namespaced images', () => {
const res = extractPackageFile('FROM mynamespace/node:8\n')?.deps; const res = extractPackageFile('FROM mynamespace/node:8\n', '', {})?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
{ {
@ -254,7 +272,9 @@ describe('modules/manager/dockerfile/extract', () => {
it('handles custom hosts with namespace', () => { it('handles custom hosts with namespace', () => {
const res = extractPackageFile( const res = extractPackageFile(
'FROM registry2.something.info/someaccount/node:8\n' 'FROM registry2.something.info/someaccount/node:8\n',
'',
{}
)?.deps; )?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
@ -273,7 +293,9 @@ describe('modules/manager/dockerfile/extract', () => {
it('handles abnormal spacing', () => { it('handles abnormal spacing', () => {
const res = extractPackageFile( const res = extractPackageFile(
'FROM registry.allmine.info:5005/node:8.7.0\n\n' 'FROM registry.allmine.info:5005/node:8.7.0\n\n',
'',
{}
)?.deps; )?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
@ -292,7 +314,9 @@ describe('modules/manager/dockerfile/extract', () => {
it('extracts multiple FROM tags', () => { it('extracts multiple FROM tags', () => {
const res = extractPackageFile( const res = extractPackageFile(
'FROM node:6.12.3 as frontend\n\n# comment\nENV foo=bar\nFROM python:3.6-slim\n' 'FROM node:6.12.3 as frontend\n\n# comment\nENV foo=bar\nFROM python:3.6-slim\n',
'',
{}
)?.deps; )?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
@ -320,13 +344,15 @@ describe('modules/manager/dockerfile/extract', () => {
}); });
it('skips scratches', () => { it('skips scratches', () => {
const res = extractPackageFile('FROM scratch\nADD foo\n'); const res = extractPackageFile('FROM scratch\nADD foo\n', '', {});
expect(res).toBeNull(); expect(res).toBeNull();
}); });
it('skips named multistage FROM tags', () => { it('skips named multistage FROM tags', () => {
const res = extractPackageFile( const res = extractPackageFile(
'FROM node:6.12.3 as frontend\n\n# comment\nENV foo=bar\nFROM frontend\n' 'FROM node:6.12.3 as frontend\n\n# comment\nENV foo=bar\nFROM frontend\n',
'',
{}
)?.deps; )?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
@ -346,7 +372,9 @@ describe('modules/manager/dockerfile/extract', () => {
it('handles COPY --from', () => { it('handles COPY --from', () => {
const res = extractPackageFile( const res = extractPackageFile(
'FROM scratch\nCOPY --from=gcr.io/k8s-skaffold/skaffold:v0.11.0 /usr/bin/skaffold /usr/bin/skaffold\n' 'FROM scratch\nCOPY --from=gcr.io/k8s-skaffold/skaffold:v0.11.0 /usr/bin/skaffold /usr/bin/skaffold\n',
'',
{}
)?.deps; )?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
@ -365,7 +393,9 @@ describe('modules/manager/dockerfile/extract', () => {
it('skips named multistage COPY --from tags', () => { it('skips named multistage COPY --from tags', () => {
const res = extractPackageFile( const res = extractPackageFile(
'FROM node:6.12.3 as frontend\n\n# comment\nENV foo=bar\nCOPY --from=frontend /usr/bin/node /usr/bin/node\n' 'FROM node:6.12.3 as frontend\n\n# comment\nENV foo=bar\nCOPY --from=frontend /usr/bin/node /usr/bin/node\n',
'',
{}
)?.deps; )?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
@ -385,7 +415,9 @@ describe('modules/manager/dockerfile/extract', () => {
it('skips index reference COPY --from tags', () => { it('skips index reference COPY --from tags', () => {
const res = extractPackageFile( const res = extractPackageFile(
'FROM node:6.12.3 as frontend\n\n# comment\nENV foo=bar\nCOPY --from=0 /usr/bin/node /usr/bin/node\n' 'FROM node:6.12.3 as frontend\n\n# comment\nENV foo=bar\nCOPY --from=0 /usr/bin/node /usr/bin/node\n',
'',
{}
)?.deps; )?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
@ -405,7 +437,9 @@ describe('modules/manager/dockerfile/extract', () => {
it('detects ["stage"] and ["final"] deps of docker multi-stage build.', () => { it('detects ["stage"] and ["final"] deps of docker multi-stage build.', () => {
const res = extractPackageFile( const res = extractPackageFile(
'FROM node:8.15.1-alpine as skippedfrom\nFROM golang:1.7.3 as builder\n\n# comment\nWORKDIR /go/src/github.com/alexellis/href-counter/\nRUN go get -d -v golang.org/x/net/html \nCOPY app.go .\nRUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .\n\nFROM alpine:latest \nRUN apk --no-cache add ca-certificates\nWORKDIR /root/\nCOPY --from=builder /go/src/github.com/alexellis/href-counter/app .\nCMD ["./app"]\n' 'FROM node:8.15.1-alpine as skippedfrom\nFROM golang:1.7.3 as builder\n\n# comment\nWORKDIR /go/src/github.com/alexellis/href-counter/\nRUN go get -d -v golang.org/x/net/html \nCOPY app.go .\nRUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .\n\nFROM alpine:latest \nRUN apk --no-cache add ca-certificates\nWORKDIR /root/\nCOPY --from=builder /go/src/github.com/alexellis/href-counter/app .\nCMD ["./app"]\n',
'',
{}
)?.deps; )?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
@ -447,7 +481,7 @@ describe('modules/manager/dockerfile/extract', () => {
}); });
it('extracts images on adjacent lines', () => { it('extracts images on adjacent lines', () => {
const res = extractPackageFile(d1)?.deps; const res = extractPackageFile(d1, '', {})?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
{ {
@ -474,7 +508,7 @@ describe('modules/manager/dockerfile/extract', () => {
}); });
it('extracts images from all sorts of (maybe multiline) FROM and COPY --from statements', () => { it('extracts images from all sorts of (maybe multiline) FROM and COPY --from statements', () => {
const res = extractPackageFile(d2)?.deps; const res = extractPackageFile(d2, '', {})?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
{ {
@ -564,7 +598,7 @@ describe('modules/manager/dockerfile/extract', () => {
}); });
it('handles calico/node', () => { it('handles calico/node', () => {
const res = extractPackageFile('FROM calico/node\n')?.deps; const res = extractPackageFile('FROM calico/node\n', '', {})?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
{ {
@ -581,7 +615,7 @@ describe('modules/manager/dockerfile/extract', () => {
}); });
it('handles ubuntu', () => { it('handles ubuntu', () => {
const res = extractPackageFile('FROM ubuntu:18.04\n')?.deps; const res = extractPackageFile('FROM ubuntu:18.04\n', '', {})?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
{ {
@ -599,7 +633,7 @@ describe('modules/manager/dockerfile/extract', () => {
}); });
it('handles debian with codename', () => { it('handles debian with codename', () => {
const res = extractPackageFile('FROM debian:buster\n')?.deps; const res = extractPackageFile('FROM debian:buster\n', '', {})?.deps;
expect(res).toEqual([ expect(res).toEqual([
{ {
autoReplaceStringTemplate: autoReplaceStringTemplate:
@ -616,7 +650,7 @@ describe('modules/manager/dockerfile/extract', () => {
}); });
it('handles debian with prefixes', () => { it('handles debian with prefixes', () => {
const res = extractPackageFile('FROM amd64/debian:10\n')?.deps; const res = extractPackageFile('FROM amd64/debian:10\n', '', {})?.deps;
expect(res).toEqual([ expect(res).toEqual([
{ {
autoReplaceStringTemplate: autoReplaceStringTemplate:
@ -634,7 +668,7 @@ describe('modules/manager/dockerfile/extract', () => {
}); });
it('handles prefixes', () => { it('handles prefixes', () => {
const res = extractPackageFile('FROM amd64/ubuntu:18.04\n')?.deps; const res = extractPackageFile('FROM amd64/ubuntu:18.04\n', '', {})?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
{ {
@ -654,7 +688,9 @@ describe('modules/manager/dockerfile/extract', () => {
it('handles implausible line continuation', () => { it('handles implausible line continuation', () => {
const res = extractPackageFile( const res = extractPackageFile(
'FROM alpine:3.5\n\nRUN something \\' 'FROM alpine:3.5\n\nRUN something \\',
'',
{}
)?.deps; )?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
@ -672,7 +708,7 @@ describe('modules/manager/dockerfile/extract', () => {
}); });
it('handles multi-line FROM with space after escape character', () => { it('handles multi-line FROM with space after escape character', () => {
const res = extractPackageFile('FROM \\ \nnginx:1.20\n')?.deps; const res = extractPackageFile('FROM \\ \nnginx:1.20\n', '', {})?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
{ {
@ -689,7 +725,11 @@ describe('modules/manager/dockerfile/extract', () => {
}); });
it('handles FROM without ARG default value', () => { it('handles FROM without ARG default value', () => {
const res = extractPackageFile('ARG img_base\nFROM $img_base\n')?.deps; const res = extractPackageFile(
'ARG img_base\nFROM $img_base\n',
'',
{}
)?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
{ {
@ -705,7 +745,9 @@ describe('modules/manager/dockerfile/extract', () => {
it('handles FROM with empty ARG default value', () => { it('handles FROM with empty ARG default value', () => {
const res = extractPackageFile( const res = extractPackageFile(
'ARG patch1=""\nARG patch2=\nFROM nginx:1.20${patch1}$patch2\n' 'ARG patch1=""\nARG patch2=\nFROM nginx:1.20${patch1}$patch2\n',
'',
{}
)?.deps; )?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
@ -724,7 +766,9 @@ describe('modules/manager/dockerfile/extract', () => {
it('handles FROM with version in ARG value', () => { it('handles FROM with version in ARG value', () => {
const res = extractPackageFile( const res = extractPackageFile(
'ARG\tVARIANT="1.60.0-bullseye"\nFROM\trust:${VARIANT}\n' 'ARG\tVARIANT="1.60.0-bullseye"\nFROM\trust:${VARIANT}\n',
'',
{}
)?.deps; )?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
@ -743,7 +787,9 @@ describe('modules/manager/dockerfile/extract', () => {
it('handles FROM with version in ARG default value', () => { it('handles FROM with version in ARG default value', () => {
const res = extractPackageFile( const res = extractPackageFile(
'ARG IMAGE_VERSION=${IMAGE_VERSION:-ubuntu:xenial}\nfrom ${IMAGE_VERSION} as base\n' 'ARG IMAGE_VERSION=${IMAGE_VERSION:-ubuntu:xenial}\nfrom ${IMAGE_VERSION} as base\n',
'',
{}
)?.deps; )?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
@ -763,7 +809,9 @@ describe('modules/manager/dockerfile/extract', () => {
it('handles FROM with digest in ARG default value', () => { it('handles FROM with digest in ARG default value', () => {
const res = extractPackageFile( const res = extractPackageFile(
'ARG sha_digest=sha256:ab37242e81cbc031b2600eef4440fe87055a05c14b40686df85078cc5086c98f\n' + 'ARG sha_digest=sha256:ab37242e81cbc031b2600eef4440fe87055a05c14b40686df85078cc5086c98f\n' +
' FROM gcr.io/distroless/java17@$sha_digest' ' FROM gcr.io/distroless/java17@$sha_digest',
'',
{}
)?.deps; )?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
@ -782,7 +830,9 @@ describe('modules/manager/dockerfile/extract', () => {
it('handles FROM with overwritten ARG value', () => { it('handles FROM with overwritten ARG value', () => {
const res = extractPackageFile( const res = extractPackageFile(
'ARG base=nginx:1.19\nFROM $base as stage1\nARG base=nginx:1.20\nFROM --platform=amd64 $base as stage2\n' 'ARG base=nginx:1.19\nFROM $base as stage1\nARG base=nginx:1.20\nFROM --platform=amd64 $base as stage2\n',
'',
{}
)?.deps; )?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
@ -810,7 +860,9 @@ describe('modules/manager/dockerfile/extract', () => {
it('handles FROM with multiple ARG values', () => { it('handles FROM with multiple ARG values', () => {
const res = extractPackageFile( const res = extractPackageFile(
'ARG CUDA=9.2\nARG LINUX_VERSION ubuntu16.04\nFROM nvidia/cuda:${CUDA}-devel-${LINUX_VERSION}\n' 'ARG CUDA=9.2\nARG LINUX_VERSION ubuntu16.04\nFROM nvidia/cuda:${CUDA}-devel-${LINUX_VERSION}\n',
'',
{}
)?.deps; )?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
@ -828,12 +880,16 @@ describe('modules/manager/dockerfile/extract', () => {
}); });
it('skips scratch if provided in ARG value', () => { it('skips scratch if provided in ARG value', () => {
const res = extractPackageFile('ARG img="scratch"\nFROM $img as base\n'); const res = extractPackageFile(
'ARG img="scratch"\nFROM $img as base\n',
'',
{}
);
expect(res).toBeNull(); expect(res).toBeNull();
}); });
it('extracts images from multi-line ARG statements', () => { it('extracts images from multi-line ARG statements', () => {
const res = extractPackageFile(d3)?.deps; const res = extractPackageFile(d3, '', {})?.deps;
expect(res).toEqual([ expect(res).toEqual([
{ {
autoReplaceStringTemplate: autoReplaceStringTemplate:
@ -875,7 +931,9 @@ describe('modules/manager/dockerfile/extract', () => {
it('ignores parser directives in wrong order', () => { it('ignores parser directives in wrong order', () => {
const res = extractPackageFile( const res = extractPackageFile(
'# dummy\n# escape = `\n\nFROM\\\nnginx:1.20' '# dummy\n# escape = `\n\nFROM\\\nnginx:1.20',
'',
{}
)?.deps; )?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
@ -893,7 +951,7 @@ describe('modules/manager/dockerfile/extract', () => {
}); });
it('handles an alternative escape character', () => { it('handles an alternative escape character', () => {
const res = extractPackageFile(d4)?.deps; const res = extractPackageFile(d4, '', {})?.deps;
expect(res).toEqual([ expect(res).toEqual([
{ {
autoReplaceStringTemplate: autoReplaceStringTemplate:
@ -954,7 +1012,9 @@ describe('modules/manager/dockerfile/extract', () => {
it('handles FROM with version in ARG default value and quotes', () => { it('handles FROM with version in ARG default value and quotes', () => {
const res = extractPackageFile( const res = extractPackageFile(
'ARG REF_NAME=${REF_NAME:-"gcr.io/distroless/static-debian11:nonroot@sha256:abc"}\nfrom ${REF_NAME}' 'ARG REF_NAME=${REF_NAME:-"gcr.io/distroless/static-debian11:nonroot@sha256:abc"}\nfrom ${REF_NAME}',
'',
{}
)?.deps; )?.deps;
expect(res).toMatchInlineSnapshot(` expect(res).toMatchInlineSnapshot(`
[ [
@ -973,7 +1033,9 @@ describe('modules/manager/dockerfile/extract', () => {
it('handles version in ARG and digest in FROM with CRLF linefeed', () => { it('handles version in ARG and digest in FROM with CRLF linefeed', () => {
const res = extractPackageFile( const res = extractPackageFile(
'ARG IMAGE_TAG=14.04\r\n#something unrelated\r\nFROM ubuntu:$IMAGE_TAG@sha256:abc\r\n' 'ARG IMAGE_TAG=14.04\r\n#something unrelated\r\nFROM ubuntu:$IMAGE_TAG@sha256:abc\r\n',
'',
{}
)?.deps; )?.deps;
expect(res).toEqual([ expect(res).toEqual([
{ {
@ -999,7 +1061,9 @@ describe('modules/manager/dockerfile/extract', () => {
'ARG NODE_IMAGE_NAME=node\n' + 'ARG NODE_IMAGE_NAME=node\n' +
'ARG NODE_IMAGE_TAG="16.14.2-alpine3.14"\n' + 'ARG NODE_IMAGE_TAG="16.14.2-alpine3.14"\n' +
'ARG DUMMY_PREFIX=\n' + 'ARG DUMMY_PREFIX=\n' +
'FROM ${DUMMY_PREFIX}${NODE_IMAGE_HOST}${NODE_IMAGE_NAME}:${NODE_IMAGE_TAG}${NODE_IMAGE_HASH} as yarn\n' 'FROM ${DUMMY_PREFIX}${NODE_IMAGE_HOST}${NODE_IMAGE_NAME}:${NODE_IMAGE_TAG}${NODE_IMAGE_HASH} as yarn\n',
'',
{}
)?.deps; )?.deps;
expect(res).toEqual([ expect(res).toEqual([
{ {
@ -1024,6 +1088,82 @@ describe('modules/manager/dockerfile/extract', () => {
}); });
}); });
it('handles empty optional parameters', () => {
const res = extractPackageFile(
'FROM quay.io/myName/myPackage:0.6.2\n',
'',
{}
);
expect(res).toEqual({
deps: [
{
autoReplaceStringTemplate:
'{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentDigest: undefined,
currentValue: '0.6.2',
datasource: 'docker',
depName: 'quay.io/myName/myPackage',
depType: 'final',
replaceString: 'quay.io/myName/myPackage:0.6.2',
},
],
});
});
it('handles registry alias', () => {
const res = extractPackageFile(
'FROM quay.io/myName/myPackage:0.6.2\n',
'Dockerfile',
{
registryAliases: {
'quay.io': 'my-quay-mirror.registry.com',
'index.docker.io': 'my-docker-mirror.registry.com',
},
}
);
expect(res).toEqual({
deps: [
{
autoReplaceStringTemplate:
'quay.io/myName/myPackage:{{#if newValue}}{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentDigest: undefined,
currentValue: '0.6.2',
datasource: 'docker',
depName: 'my-quay-mirror.registry.com/myName/myPackage',
depType: 'final',
replaceString: 'quay.io/myName/myPackage:0.6.2',
},
],
});
});
it('handles empty registry', () => {
const res = extractPackageFile(
'FROM myName/myPackage:0.6.2\n',
'Dockerfile',
{
registryAliases: {
'quay.io': 'my-quay-mirror.registry.com',
'index.docker.io': 'my-docker-mirror.registry.com',
},
}
);
expect(res).toEqual({
deps: [
{
autoReplaceStringTemplate:
'{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentDigest: undefined,
currentValue: '0.6.2',
datasource: 'docker',
depName: 'myName/myPackage',
depType: 'final',
replaceString: 'myName/myPackage:0.6.2',
},
],
});
});
describe('getDep()', () => { describe('getDep()', () => {
it('rejects null', () => { it('rejects null', () => {
expect(getDep(null)).toEqual({ skipReason: 'invalid-value' }); expect(getDep(null)).toEqual({ skipReason: 'invalid-value' });

View file

@ -4,7 +4,7 @@ import { escapeRegExp, newlineRegex, regEx } from '../../../util/regex';
import { DockerDatasource } from '../../datasource/docker'; import { DockerDatasource } from '../../datasource/docker';
import * as debianVersioning from '../../versioning/debian'; import * as debianVersioning from '../../versioning/debian';
import * as ubuntuVersioning from '../../versioning/ubuntu'; import * as ubuntuVersioning from '../../versioning/ubuntu';
import type { PackageDependency, PackageFile } from '../types'; import type { ExtractConfig, PackageDependency, PackageFile } from '../types';
const variableMarker = '$'; const variableMarker = '$';
@ -226,7 +226,11 @@ export function getDep(
return dep; return dep;
} }
export function extractPackageFile(content: string): PackageFile | null { export function extractPackageFile(
content: string,
_filename: string,
config: ExtractConfig
): PackageFile | null {
const deps: PackageDependency[] = []; const deps: PackageDependency[] = [];
const stageNames: string[] = []; const stageNames: string[] = [];
const args: Record<string, string> = {}; const args: Record<string, string> = {};
@ -322,7 +326,7 @@ export function extractPackageFile(content: string): PackageFile | null {
} else if (fromImage && stageNames.includes(fromImage)) { } else if (fromImage && stageNames.includes(fromImage)) {
logger.debug({ image: fromImage }, 'Skipping alias FROM'); logger.debug({ image: fromImage }, 'Skipping alias FROM');
} else { } else {
const dep = getDep(fromImage); const dep = getDep(fromImage, true, config.registryAliases);
processDepForAutoReplace(dep, lineNumberRanges, lines, lineFeed); processDepForAutoReplace(dep, lineNumberRanges, lines, lineFeed);
logger.trace( logger.trace(
{ {
@ -350,7 +354,11 @@ export function extractPackageFile(content: string): PackageFile | null {
'Skipping alias COPY --from' 'Skipping alias COPY --from'
); );
} else if (Number.isNaN(Number(copyFromMatch.groups.image))) { } else if (Number.isNaN(Number(copyFromMatch.groups.image))) {
const dep = getDep(copyFromMatch.groups.image); const dep = getDep(
copyFromMatch.groups.image,
true,
config.registryAliases
);
const lineNumberRanges: number[][] = [ const lineNumberRanges: number[][] = [
[lineNumberInstrStart, lineNumber], [lineNumberInstrStart, lineNumber],
]; ];

View file

@ -0,0 +1,8 @@
kind: pipeline
name: Test
steps:
- name: mix
image: quay.io/elixir:1.8.1-alpine
environment:
DB_HOST: mysql

View file

@ -2,16 +2,85 @@ import { Fixtures } from '../../../../test/fixtures';
import { extractPackageFile } from '.'; import { extractPackageFile } from '.';
const droneciRegistryAlias = Fixtures.get('.drone2.yml');
describe('modules/manager/droneci/extract', () => { describe('modules/manager/droneci/extract', () => {
describe('extractPackageFile()', () => { describe('extractPackageFile()', () => {
it('returns null for empty', () => { it('returns null for empty', () => {
expect(extractPackageFile('nothing here')).toBeNull(); expect(extractPackageFile('nothing here', '', {})).toBeNull();
}); });
it('extracts multiple image lines', () => { it('extracts multiple image lines', () => {
const res = extractPackageFile(Fixtures.get('.drone.yml')); const res = extractPackageFile(Fixtures.get('.drone.yml'), '', {});
expect(res?.deps).toMatchSnapshot(); expect(res?.deps).toMatchSnapshot();
expect(res?.deps).toHaveLength(6); expect(res?.deps).toHaveLength(6);
}); });
}); });
it('extracts image and replaces registry', () => {
const res = extractPackageFile(droneciRegistryAlias, '', {
registryAliases: {
'quay.io': 'my-quay-mirror.registry.com',
},
});
expect(res).toEqual({
deps: [
{
autoReplaceStringTemplate:
'quay.io/elixir:{{#if newValue}}{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentDigest: undefined,
currentValue: '1.8.1-alpine',
datasource: 'docker',
depName: 'my-quay-mirror.registry.com/elixir',
replaceString: 'quay.io/elixir:1.8.1-alpine',
depType: 'docker',
},
],
});
});
it('extracts image but no replacement', () => {
const res = extractPackageFile(droneciRegistryAlias, '', {
registryAliases: {
'index.docker.io': 'my-docker-mirror.registry.com',
},
});
expect(res).toEqual({
deps: [
{
autoReplaceStringTemplate:
'{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentDigest: undefined,
currentValue: '1.8.1-alpine',
datasource: 'docker',
depName: 'quay.io/elixir',
replaceString: 'quay.io/elixir:1.8.1-alpine',
depType: 'docker',
},
],
});
});
it('extracts image and no double replacement', () => {
const res = extractPackageFile(droneciRegistryAlias, '', {
registryAliases: {
'quay.io': 'my-quay-mirror.registry.com',
'my-quay-mirror.registry.com': 'quay.io',
},
});
expect(res).toEqual({
deps: [
{
autoReplaceStringTemplate:
'quay.io/elixir:{{#if newValue}}{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentDigest: undefined,
currentValue: '1.8.1-alpine',
datasource: 'docker',
depName: 'my-quay-mirror.registry.com/elixir',
replaceString: 'quay.io/elixir:1.8.1-alpine',
depType: 'docker',
},
],
});
});
}); });

View file

@ -1,9 +1,13 @@
import { logger } from '../../../logger'; import { logger } from '../../../logger';
import { newlineRegex, regEx } from '../../../util/regex'; import { newlineRegex, regEx } from '../../../util/regex';
import { getDep } from '../dockerfile/extract'; import { getDep } from '../dockerfile/extract';
import type { PackageDependency, PackageFile } from '../types'; import type { ExtractConfig, PackageDependency, PackageFile } from '../types';
export function extractPackageFile(content: string): PackageFile | null { export function extractPackageFile(
content: string,
_filename: string,
config: ExtractConfig
): PackageFile | null {
const deps: PackageDependency[] = []; const deps: PackageDependency[] = [];
try { try {
const lines = content.split(newlineRegex); const lines = content.split(newlineRegex);
@ -34,7 +38,7 @@ export function extractPackageFile(content: string): PackageFile | null {
currentFrom += finalLineMatch.groups.currentFrom; currentFrom += finalLineMatch.groups.currentFrom;
replaceString += '\n' + finalLineMatch.groups.replaceString; replaceString += '\n' + finalLineMatch.groups.replaceString;
const dep = getDep(currentFrom); const dep = getDep(currentFrom, true, config.registryAliases);
dep.depType = 'docker'; dep.depType = 'docker';
dep.replaceString = replaceString; dep.replaceString = replaceString;
if (dep.autoReplaceStringTemplate) { if (dep.autoReplaceStringTemplate) {
@ -54,7 +58,11 @@ export function extractPackageFile(content: string): PackageFile | null {
/^\s* image:\s*'?"?(?<currentFrom>[^\s'"]+)'?"?\s*$/ /^\s* image:\s*'?"?(?<currentFrom>[^\s'"]+)'?"?\s*$/
).exec(line); ).exec(line);
if (match?.groups) { if (match?.groups) {
const dep = getDep(match.groups.currentFrom); const dep = getDep(
match.groups.currentFrom,
true,
config.registryAliases
);
dep.depType = 'docker'; dep.depType = 'docker';
deps.push(dep); deps.push(dep);
} }

View file

@ -0,0 +1,12 @@
apiVersion: v1
kind: Pod
metadata:
name: node-pod
labels:
app: node
spec:
containers:
- name: node
image: quay.io/node:0.0.1
ports:
- containerPort: 80

View file

@ -4,16 +4,17 @@ import { extractPackageFile } from '.';
const kubernetesImagesFile = Fixtures.get('kubernetes.yaml'); const kubernetesImagesFile = Fixtures.get('kubernetes.yaml');
const kubernetesConfigMapFile = Fixtures.get('configmap.yaml'); const kubernetesConfigMapFile = Fixtures.get('configmap.yaml');
const kubernetesArraySyntaxFile = Fixtures.get('array-syntax.yaml'); const kubernetesArraySyntaxFile = Fixtures.get('array-syntax.yaml');
const kubernetesRegistryAlias = Fixtures.get('kubernetes.registry-alias.yaml');
const otherYamlFile = Fixtures.get('gitlab-ci.yaml'); const otherYamlFile = Fixtures.get('gitlab-ci.yaml');
describe('modules/manager/kubernetes/extract', () => { describe('modules/manager/kubernetes/extract', () => {
describe('extractPackageFile()', () => { describe('extractPackageFile()', () => {
it('returns null for empty', () => { it('returns null for empty', () => {
expect(extractPackageFile('', 'file.yaml')).toBeNull(); expect(extractPackageFile('', 'file.yaml', {})).toBeNull();
}); });
it('returns only API version', () => { it('returns only API version', () => {
const res = extractPackageFile(kubernetesConfigMapFile, 'file.yaml'); const res = extractPackageFile(kubernetesConfigMapFile, 'file.yaml', {});
expect(res?.deps).toStrictEqual([ expect(res?.deps).toStrictEqual([
{ {
currentValue: 'v1', currentValue: 'v1',
@ -23,7 +24,7 @@ describe('modules/manager/kubernetes/extract', () => {
}); });
it('extracts multiple Kubernetes configurations', () => { it('extracts multiple Kubernetes configurations', () => {
const res = extractPackageFile(kubernetesImagesFile, 'file.yaml'); const res = extractPackageFile(kubernetesImagesFile, 'file.yaml', {});
expect(res?.deps).toStrictEqual([ expect(res?.deps).toStrictEqual([
{ {
autoReplaceStringTemplate: autoReplaceStringTemplate:
@ -55,7 +56,11 @@ describe('modules/manager/kubernetes/extract', () => {
}); });
it('extracts image line in a YAML array', () => { it('extracts image line in a YAML array', () => {
const res = extractPackageFile(kubernetesArraySyntaxFile, 'file.yaml'); const res = extractPackageFile(
kubernetesArraySyntaxFile,
'file.yaml',
{}
);
expect(res?.deps).toStrictEqual([ expect(res?.deps).toStrictEqual([
{ {
autoReplaceStringTemplate: autoReplaceStringTemplate:
@ -75,7 +80,7 @@ describe('modules/manager/kubernetes/extract', () => {
}); });
it('ignores non-Kubernetes YAML files', () => { it('ignores non-Kubernetes YAML files', () => {
expect(extractPackageFile(otherYamlFile, 'file.yaml')).toBeNull(); expect(extractPackageFile(otherYamlFile, 'file.yaml', {})).toBeNull();
}); });
it('handles invalid YAML files', () => { it('handles invalid YAML files', () => {
@ -83,7 +88,71 @@ describe('modules/manager/kubernetes/extract', () => {
kind: ConfigMap kind: ConfigMap
< <
`; `;
expect(extractPackageFile(invalidYaml, 'file.yaml')).toBeNull(); expect(extractPackageFile(invalidYaml, 'file.yaml', {})).toBeNull();
});
it('extracts images and replaces registries', () => {
const res = extractPackageFile(kubernetesRegistryAlias, 'file.yaml', {
registryAliases: {
'quay.io': 'my-quay-mirror.registry.com',
},
});
expect(res).toEqual({
deps: [
{
autoReplaceStringTemplate:
'quay.io/node:{{#if newValue}}{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentDigest: undefined,
currentValue: '0.0.1',
datasource: 'docker',
depName: 'my-quay-mirror.registry.com/node',
replaceString: 'quay.io/node:0.0.1',
},
],
});
});
it('extracts images but does no replacement', () => {
const res = extractPackageFile(kubernetesRegistryAlias, 'file.yaml', {
registryAliases: {
'index.docker.io': 'my-docker-mirror.registry.com',
},
});
expect(res).toEqual({
deps: [
{
autoReplaceStringTemplate:
'{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentDigest: undefined,
currentValue: '0.0.1',
datasource: 'docker',
depName: 'quay.io/node',
replaceString: 'quay.io/node:0.0.1',
},
],
});
});
it('extracts images and does no double replacements', () => {
const res = extractPackageFile(kubernetesRegistryAlias, 'file.yaml', {
registryAliases: {
'quay.io': 'my-quay-mirror.registry.com',
'my-quay-mirror.registry.com': 'quay.io',
},
});
expect(res).toEqual({
deps: [
{
autoReplaceStringTemplate:
'quay.io/node:{{#if newValue}}{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentDigest: undefined,
currentValue: '0.0.1',
datasource: 'docker',
depName: 'my-quay-mirror.registry.com/node',
replaceString: 'quay.io/node:0.0.1',
},
],
});
}); });
}); });
}); });

View file

@ -3,12 +3,13 @@ import { loadAll } from 'js-yaml';
import { logger } from '../../../logger'; import { logger } from '../../../logger';
import { newlineRegex, regEx } from '../../../util/regex'; import { newlineRegex, regEx } from '../../../util/regex';
import { getDep } from '../dockerfile/extract'; import { getDep } from '../dockerfile/extract';
import type { PackageDependency, PackageFile } from '../types'; import type { ExtractConfig, PackageDependency, PackageFile } from '../types';
import type { KubernetesConfiguration } from './types'; import type { KubernetesConfiguration } from './types';
export function extractPackageFile( export function extractPackageFile(
content: string, content: string,
fileName: string fileName: string,
config: ExtractConfig
): PackageFile | null { ): PackageFile | null {
logger.trace('kubernetes.extractPackageFile()'); logger.trace('kubernetes.extractPackageFile()');
@ -20,21 +21,24 @@ export function extractPackageFile(
} }
const deps: PackageDependency[] = [ const deps: PackageDependency[] = [
...extractImages(content), ...extractImages(content, config),
...extractApis(content, fileName), ...extractApis(content, fileName),
]; ];
return deps.length ? { deps } : null; return deps.length ? { deps } : null;
} }
function extractImages(content: string): PackageDependency[] { function extractImages(
content: string,
config: ExtractConfig
): PackageDependency[] {
const deps: PackageDependency[] = []; const deps: PackageDependency[] = [];
for (const line of content.split(newlineRegex)) { for (const line of content.split(newlineRegex)) {
const match = regEx(/^\s*-?\s*image:\s*'?"?([^\s'"]+)'?"?\s*$/).exec(line); const match = regEx(/^\s*-?\s*image:\s*'?"?([^\s'"]+)'?"?\s*$/).exec(line);
if (match) { if (match) {
const currentFrom = match[1]; const currentFrom = match[1];
const dep = getDep(currentFrom); const dep = getDep(currentFrom, true, config.registryAliases);
logger.debug( logger.debug(
{ {
depName: dep.depName, depName: dep.depName,