Skip to content

Commit

Permalink
feat(github-actions): update job and service containers (#16770)
Browse files Browse the repository at this point in the history
  • Loading branch information
maxbrunet authored Aug 5, 2022
1 parent d1cc6cd commit 8dd40fe
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 13 deletions.
11 changes: 11 additions & 0 deletions lib/modules/manager/github-actions/__fixtures__/workflow_1.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,14 @@ jobs:
steps:
- name: Node 6 test
uses: docker://node:6@sha256:7b65413af120ec5328077775022c78101f103258a1876ec2f83890bce416e896
container-job:
runs-on: ubuntu-latest
container: node:16-bullseye
services:
redis:
image: redis:5
postgres: postgres:10
container-job-with-image-keyword:
runs-on: ubuntu-latest
container:
image: node:16-bullseye
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ Array [
"depName": "replicated/dockerfilelint",
"depType": "docker",
"replaceString": "replicated/dockerfilelint",
"versioning": "docker",
},
Object {
"autoReplaceStringTemplate": "{{depName}}/cli@{{#if newDigest}}{{newDigest}}{{#if newValue}} # tag={{newValue}}{{/if}}{{/if}}{{#unless newDigest}}{{newValue}}{{/unless}}",
Expand All @@ -178,7 +177,42 @@ Array [
"depName": "node",
"depType": "docker",
"replaceString": "node:6@sha256:7b65413af120ec5328077775022c78101f103258a1876ec2f83890bce416e896",
"versioning": "docker",
},
Object {
"autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}",
"currentDigest": undefined,
"currentValue": "16-bullseye",
"datasource": "docker",
"depName": "node",
"depType": "container",
"replaceString": "node:16-bullseye",
},
Object {
"autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}",
"currentDigest": undefined,
"currentValue": "5",
"datasource": "docker",
"depName": "redis",
"depType": "service",
"replaceString": "redis:5",
},
Object {
"autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}",
"currentDigest": undefined,
"currentValue": "10",
"datasource": "docker",
"depName": "postgres",
"depType": "service",
"replaceString": "postgres:10",
},
Object {
"autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}",
"currentDigest": undefined,
"currentValue": "16-bullseye",
"datasource": "docker",
"depName": "node",
"depType": "container",
"replaceString": "node:16-bullseye",
},
]
`;
29 changes: 23 additions & 6 deletions lib/modules/manager/github-actions/extract.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,44 @@ import { extractPackageFile } from '.';
describe('modules/manager/github-actions/extract', () => {
describe('extractPackageFile()', () => {
it('returns null for empty', () => {
expect(extractPackageFile('nothing here')).toBeNull();
expect(
extractPackageFile('nothing here', 'empty-workflow.yml')
).toBeNull();
});

it('returns null for invalid yaml', () => {
expect(
extractPackageFile('nothing here: [', 'invalid-workflow.yml')
).toBeNull();
});

it('extracts multiple docker image lines from yaml configuration file', () => {
const res = extractPackageFile(Fixtures.get('workflow_1.yml'));
const res = extractPackageFile(
Fixtures.get('workflow_1.yml'),
'workflow_1.yml'
);
expect(res?.deps).toMatchSnapshot();
expect(res?.deps.filter((d) => d.datasource === 'docker')).toHaveLength(
2
6
);
});

it('extracts multiple action tag lines from yaml configuration file', () => {
const res = extractPackageFile(Fixtures.get('workflow_2.yml'));
const res = extractPackageFile(
Fixtures.get('workflow_2.yml'),
'workflow_2.yml'
);
expect(res?.deps).toMatchSnapshot();
expect(
res?.deps.filter((d) => d.datasource === 'github-tags')
).toHaveLength(8);
});

it('extracts multiple action tag lines with double quotes and comments', () => {
const res = extractPackageFile(Fixtures.get('workflow_3.yml'));
const res = extractPackageFile(
Fixtures.get('workflow_3.yml'),
'workflow_3.yml'
);
expect(res?.deps).toMatchSnapshot([
{
currentValue: 'v0.13.1',
Expand Down Expand Up @@ -78,7 +95,7 @@ describe('modules/manager/github-actions/extract', () => {
- name: "quoted, no comment, outdated"
uses: "actions/setup-java@v2"`;

const res = extractPackageFile(yamlContent);
const res = extractPackageFile(yamlContent, 'workflow.yml');
expect(res?.deps).toMatchObject([
{
depName: 'actions/setup-node',
Expand Down
68 changes: 63 additions & 5 deletions lib/modules/manager/github-actions/extract.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
import { load } from 'js-yaml';
import { logger } from '../../../logger';
import { newlineRegex, regEx } from '../../../util/regex';
import { GithubTagsDatasource } from '../../datasource/github-tags';
import * as dockerVersioning from '../../versioning/docker';
import { getDep } from '../dockerfile/extract';
import type { PackageDependency, PackageFile } from '../types';
import type { Container, Workflow } from './types';

const dockerRe = regEx(/^\s+uses: docker:\/\/([^"]+)\s*$/);
const dockerActionRe = regEx(/^\s+uses: ['"]?docker:\/\/([^'"]+)\s*$/);
const actionRe = regEx(
/^\s+-?\s+?uses: (?<replaceString>['"]?(?<depName>[\w-]+\/[\w-]+)(?<path>\/.*)?@(?<currentValue>[^\s'"]+)['"]?(?:\s+#\s+(?:renovate:\s+)?tag=(?<tag>\S+))?)/
);

// SHA1 or SHA256, see https://github.blog/2020-10-19-git-2-29-released/
const shaRe = regEx(/^[a-z0-9]{40}|[a-z0-9]{64}$/);

export function extractPackageFile(content: string): PackageFile | null {
logger.trace('github-actions.extractPackageFile()');
function extractWithRegex(content: string): PackageDependency[] {
logger.trace('github-actions.extractWithRegex()');
const deps: PackageDependency[] = [];
for (const line of content.split(newlineRegex)) {
if (line.trim().startsWith('#')) {
continue;
}

const dockerMatch = dockerRe.exec(line);
const dockerMatch = dockerActionRe.exec(line);
if (dockerMatch) {
const [, currentFrom] = dockerMatch;
const dep = getDep(currentFrom);
dep.depType = 'docker';
dep.versioning = dockerVersioning.id;
deps.push(dep);
continue;
}
Expand Down Expand Up @@ -68,6 +69,63 @@ export function extractPackageFile(content: string): PackageFile | null {
deps.push(dep);
}
}
return deps;
}

function extractContainer(container: string | Container): PackageDependency {
let dep: PackageDependency;
if (typeof container === 'string') {
dep = getDep(container);
} else {
dep = getDep(container?.image);
}
return dep;
}

function extractWithYAMLParser(
content: string,
filename: string
): PackageDependency[] {
logger.trace('github-actions.extractWithYAMLParser()');
const deps: PackageDependency[] = [];

let pkg: Workflow;
try {
pkg = load(content, { json: true }) as Workflow;
} catch (err) {
logger.debug(
{ filename, err },
'Failed to parse GitHub Actions Workflow YAML'
);
return [];
}

for (const job of Object.values(pkg.jobs ?? {})) {
if (job.container !== undefined) {
const dep = extractContainer(job.container);
dep.depType = 'container';
deps.push(dep);
}

for (const service of Object.values(job.services ?? {})) {
const dep = extractContainer(service);
dep.depType = 'service';
deps.push(dep);
}
}

return deps;
}

export function extractPackageFile(
content: string,
filename: string
): PackageFile | null {
logger.trace('github-actions.extractPackageFile()');
const deps = [
...extractWithRegex(content),
...extractWithYAMLParser(content, filename),
];
if (!deps.length) {
return null;
}
Expand Down
31 changes: 31 additions & 0 deletions lib/modules/manager/github-actions/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Container describes a Docker container used within a {@link Job}.
* {@link https://docs.github.com/en/actions/using-containerized-services}
*
* @param image - The Docker image to use as the container to run the action. The value can be the Docker Hub image name or a registry name.
*/
export interface Container {
image: string;
}

/**
* Job describes a single job within a {@link Workflow}.
* {@link https://docs.github.com/en/actions/using-jobs}
*
* @param container - {@link https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idcontainer}
* @param services - {@link https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idservices}
*/
export interface Job {
container?: string | Container;
services?: Record<string, string | Container>;
}

/**
* Workflow describes a GitHub Actions Workflow.
* {@link https://docs.github.com/en/actions/using-workflows}
*
* @param jobs - {@link https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobs}
*/
export interface Workflow {
jobs: Record<string, Job>;
}

0 comments on commit 8dd40fe

Please sign in to comment.