Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the sha of the the current commit to the watermark as the fragment of the watermark image url. #1309

Merged
merged 4 commits into from
Jan 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 11 additions & 29 deletions src/cml.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@ const {
preventcacheUri,
waitForever
} = require('./utils');

const { Watermark } = require('./watermark');
const { GITHUB_REPOSITORY, CI_PROJECT_URL, BITBUCKET_REPO_UUID } = process.env;

const WATERMARK_IMAGE = 'https://cml.dev/watermark.png';
const GIT_USER_NAME = 'Olivaw[bot]';
const GIT_USER_EMAIL = '[email protected]';
const GIT_REMOTE = 'origin';
Expand Down Expand Up @@ -181,36 +180,16 @@ class CML {
const drv = this.getDriver();

if (rmWatermark && update)
throw new Error('watermarks are mandatory for updateable comments');
throw new Error('watermarks are mandatory for updatable comments');

// Create the watermark.
const genWatermark = (opts = {}) => {
const { label = '', workflow, run } = opts;
// Replace {workflow} and {run} placeholders in label with actual values.
const lbl = label.replace('{workflow}', workflow).replace('{run}', run);

let title = `CML watermark ${lbl}`.trim();
// Github appears to escape underscores and asterisks in markdown content.
// Without escaping them, the watermark content in comments retrieved
// from github will not match the input.
const patterns = [
[/_/g, '\\_'], // underscore
[/\*/g, '\\*'], // asterisk
[/\[/g, '\\['], // opening square bracket
[/</g, '\\<'] // opening angle bracket
];
title = patterns.reduce(
(label, pattern) => label.replace(pattern[0], pattern[1]),
title
);
return `![](${WATERMARK_IMAGE} "${title}")`;
};
const watermark = rmWatermark
? ''
: genWatermark({
? null
: new Watermark({
label: watermarkTitle,
workflow: drv.workflowId,
run: drv.runId
run: drv.runId,
sha: commitSha || (await this.triggerSha())
});

let userReport = testReport;
Expand All @@ -222,7 +201,10 @@ class CML {
if (!watch) throw err;
}

let report = `${userReport}\n\n${watermark}`;
let report = userReport;
if (watermark) {
report = watermark.appendTo(userReport);
}

const publishLocalFiles = async (tree) => {
const nodes = [];
Expand Down Expand Up @@ -294,7 +276,7 @@ class CML {
let comment;
const updatableComment = (comments) => {
return comments.reverse().find(({ body }) => {
return body.includes(watermark);
return !watermark || watermark.isIn(body);
});
};

Expand Down
65 changes: 65 additions & 0 deletions src/watermark.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const WATERMARK_IMAGE = 'https://cml.dev/watermark.png';

class Watermark {
constructor(opts = {}) {
const { label = '', workflow, run, sha = false } = opts;
// Replace {workflow} and {run} placeholders in label with actual values.
this.label = label.replace('{workflow}', workflow).replace('{run}', run);

this.sha = sha;
}

// Appends the watermark (in markdown) to the report.
appendTo(report) {
return `${report}\n\n${this.toString()}`;
}

// Returns whether the watermark is present in the specified text.
// When checking for presence, the commit sha in the watermark is ignored.
isIn(text) {
const title = escapeRegExp(this.title);
const url = escapeRegExp(this.url({ sha: false }));
const pattern = `!\\[\\]\\(${url}#[0-9a-fA-F]+ "${title}"\\)`;
const re = new RegExp(pattern);
return re.test(text);
}

// String representation of the watermark.
toString() {
// Replace {workflow} and {run} placeholders in label with actual values.
return `![](${this.url()} "${this.title}")`;
}

get title() {
let title = `CML watermark ${this.label}`.trim();
// Github appears to escape underscores and asterisks in markdown content.
// Without escaping them, the watermark content in comments retrieved
// from github will not match the input.
const patterns = [
[/_/g, '\\_'], // underscore
[/\*/g, '\\*'], // asterisk
[/\[/g, '\\['], // opening square bracket
[/</g, '\\<'] // opening angle bracket
];
title = patterns.reduce(
(label, pattern) => label.replace(pattern[0], pattern[1]),
title
);
return title;
}

url(opts = {}) {
const { sha = this.sha } = opts;
const watermarkUrl = new URL(WATERMARK_IMAGE);
if (sha) {
watermarkUrl.hash = sha;
}
return watermarkUrl.toString();
}
}

function escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

module.exports = { Watermark };
118 changes: 118 additions & 0 deletions src/watermark.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
const { Watermark } = require('../src/watermark');

describe('watermark tests', () => {
beforeEach(() => {
jest.resetModules();
});

test('append watermark to report', async () => {
const watermark = new Watermark({
workflow: 'workflow-id',
run: 'run-id',
sha: 'deadbeef'
});
const report = watermark.appendTo('some report');
expect(report).toEqual(
'some report\n\n![](https://cml.dev/watermark.png#deadbeef "CML watermark")'
);
});

test('append watermark with label to report', async () => {
const watermark = new Watermark({
label: 'some-label',
workflow: 'workflow-id',
run: 'run-id',
sha: 'deadbeef'
});
const report = watermark.appendTo('some report');
expect(report).toEqual(
'some report\n\n![](https://cml.dev/watermark.png#deadbeef "CML watermark some-label")'
);
});

test('append watermark with workflow placeholder to report', async () => {
const watermark = new Watermark({
label: 'some-label-{workflow}',
workflow: 'workflow-id',
run: 'run-id',
sha: 'deadbeef'
});
const report = watermark.appendTo('some report');
expect(report).toEqual(
'some report\n\n![](https://cml.dev/watermark.png#deadbeef "CML watermark some-label-workflow-id")'
);
});

test('append watermark with run placeholder to report', async () => {
const watermark = new Watermark({
label: 'some-label-{run}',
workflow: 'workflow-id',
run: 'run-id',
sha: 'deadbeef'
});
const report = watermark.appendTo('some report');
expect(report).toEqual(
'some report\n\n![](https://cml.dev/watermark.png#deadbeef "CML watermark some-label-run-id")'
);
});

test('check for presence of the watermark in a report', async () => {
const watermark = new Watermark({
workflow: 'workflow-id',
run: 'run-id',
sha: 'deadbeef'
});
const report = watermark.appendTo('some report');
expect(watermark.isIn(report)).toEqual(true);
});

test('check for presence of the watermark with special chars in a report', async () => {
const watermark = new Watermark({
label: 'custom_1[*]-vm',
workflow: 'workflow-id',
run: 'run-id',
sha: 'deadbeef'
});
const report = watermark.appendTo('some report');
expect(watermark.isIn(report)).toEqual(true);
});

test('check for presence of the watermark in a report, different sha', async () => {
const watermark = new Watermark({
label: 'some-label-{run}',
run: 'run-id',
sha: 'deadbeef'
});
const report = watermark.appendTo('some report');

// Watermark with a different sha, created by a CI run on
// a different HEAD commit.
const newWatermark = new Watermark({
label: 'some-label-{run}',
run: 'run-id',
sha: 'abcd'
});

expect(newWatermark.isIn(report)).toEqual(true);
});

test('check for watermark mismatch', async () => {
const watermark = new Watermark({
label: 'some-label-{workflow}',
workflow: 'workflow-id',
sha: 'deadbeef'
});
const report = watermark.appendTo('some report');

// Watermark with a different sha and different
// workflow.
const newWatermark = new Watermark({
workflow: 'different-workflow-id',
label: 'some-label-{workflow}',
run: 'run-id',
sha: 'abcd'
});

expect(newWatermark.isIn(report)).toEqual(false);
});
});