Skip to content

Commit

Permalink
Fix: Truncate Buildkite Annotation (#1645)
Browse files Browse the repository at this point in the history
Co-authored-by: Aaron Moat <[email protected]>
  • Loading branch information
GeordieEK and AaronMoat authored Sep 6, 2024
1 parent 92b669e commit 3695627
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/quick-icons-taste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'skuba': patch
---

api: Truncate Buildkite annotations over 1 MiB to resolve `buildkite-agent` crash
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,6 @@
"entryPoint": "src/index.ts",
"template": null,
"type": "package",
"version": "8.1.0"
"version": "8.2.1"
}
}
43 changes: 42 additions & 1 deletion src/api/buildkite/annotate.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import * as execModule from '../../utils/exec';
import { log } from '../../utils/logging';

import { annotate } from './annotate';
import { MAX_SIZE, TRUNCATION_WARNING, annotate } from './annotate';

const exec = jest.spyOn(execModule, 'exec');
const hasCommand = jest.spyOn(execModule, 'hasCommand');
const mockWarn = jest.spyOn(log, 'warn').mockImplementation(() => undefined);

beforeEach(() => {
jest.clearAllMocks();
Expand All @@ -28,6 +30,8 @@ const setEnvironmentVariables = () => {

describe('annotate', () => {
const markdown = '**Message**';
const endOfMessage = 'EndMessage';
const oversizeMarkdown = 'a'.repeat(MAX_SIZE).concat(endOfMessage);

describe.each`
description | opts
Expand All @@ -54,6 +58,43 @@ describe('annotate', () => {
expect(exec).not.toHaveBeenCalled();
});

it('warns about truncation when annotation exceeds the maximum size', async () => {
setEnvironmentVariables();
await annotate(oversizeMarkdown);

const lastCall = exec.mock.calls[exec.mock.calls.length - 1];
if (!lastCall) {
throw new Error('Expected exec to have been called at least once');
}

const lastArgument = lastCall[lastCall.length - 1];

if (typeof lastArgument !== 'string') {
throw new Error('Expected the last argument to be a string');
}

expect(lastArgument.endsWith(TRUNCATION_WARNING)).toBe(true);
});

it('logs the full message when annotation is truncated', async () => {
setEnvironmentVariables();
await annotate(oversizeMarkdown);

const lastCall = mockWarn.mock.calls[exec.mock.calls.length - 1];
if (!lastCall) {
throw new Error('Expected log.warn to have been called at least once');
}

const lastArgument = lastCall[lastCall.length - 1];

if (typeof lastArgument !== 'string') {
throw new Error('Expected the last argument to be a string');
}

// Check for the end of message in case there's a failure, entire message isn't printed (it's too large)
expect(lastArgument.endsWith(endOfMessage)).toBe(true);
});

it('skips when `buildkite-agent` is not present', async () => {
hasCommand.mockResolvedValue(false);

Expand Down
17 changes: 16 additions & 1 deletion src/api/buildkite/annotate.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { exec, hasCommand } from '../../utils/exec';
import { log } from '../../utils/logging';

export type AnnotationStyle = 'success' | 'info' | 'warning' | 'error';

Expand All @@ -24,6 +25,10 @@ interface AnnotationOptions {
style?: AnnotationStyle;
}

// Buildkite annotation currently only supports 1MiB of data
export const MAX_SIZE = 1024 * 1024; // 1MiB in bytes
export const TRUNCATION_WARNING = '... [Truncated due to size limit]';

/**
* Asynchronously uploads a Buildkite annotation.
*
Expand All @@ -44,6 +49,16 @@ export const annotate = async (
return;
}

// Check if the annotation exceeds the maximum size
let truncatedMarkdown = markdown;
if (markdown.length > MAX_SIZE) {
// Notify user of truncation, leave space for message
const remainingSpace = MAX_SIZE - TRUNCATION_WARNING.length;
truncatedMarkdown = markdown.slice(0, remainingSpace) + TRUNCATION_WARNING;
// Log full message to the build log
log.warn(`Annotation truncated, full message is: ${markdown}`);
}

// Always scope to the current Buildkite step.
const context = [
opts.scopeContextToStep && process.env.BUILDKITE_STEP_ID,
Expand All @@ -59,6 +74,6 @@ export const annotate = async (
'annotate',
...(context ? ['--context', context] : []),
...(style ? ['--style', style] : []),
markdown,
truncatedMarkdown,
);
};

0 comments on commit 3695627

Please sign in to comment.