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

Implement context input #29

Merged
merged 2 commits into from
Nov 27, 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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Wait for, acquire, and release a distributed lock via S3 storage.
# expires: 15m
# timeout: {matches expires}
# timeout-poll: 5s
# context: "{workflow} #{run}"

- run: echo "Lock held, do work here"
```
Expand Down
6 changes: 6 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ inputs:
How long to wait between attempts for the lock. Default is 5s.
required: true
default: 5s
context:
description:
Additional context to write as the body of the lock file. Concurrent
operations waiting on this lock will display it.
required: true
default: "${{ github.workflow }} #${{ github.run_number }}"
outputs:
key:
description: "Key of the S3 object representing the lock"
Expand Down
2 changes: 1 addition & 1 deletion dist/acquire/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/release/index.js

Large diffs are not rendered by default.

30 changes: 25 additions & 5 deletions src/S3Lock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
S3Client,
ListObjectsV2Command,
DeleteObjectCommand,
GetObjectCommand,
} from "@aws-sdk/client-s3";
import { Upload } from "@aws-sdk/lib-storage";

Expand All @@ -29,13 +30,13 @@ export class S3Lock {
this.s3 = new S3Client();
}

async acquireLock(): Promise<AcquireLockResult> {
async acquireLock(body: string): Promise<AcquireLockResult> {
const key = createObjectKey(this.prefix, this.expires);

core.debug(`[s3] Upload ${key}`);
const upload = new Upload({
client: this.s3,
params: { Bucket: this.bucket, Key: key, Body: "" },
params: { Bucket: this.bucket, Key: key, Body: body },
});
await upload.done();

Expand Down Expand Up @@ -70,17 +71,36 @@ export class S3Lock {
return { tag: "not-acquired", blockingKey: keys[0] };
}

objectKeyDetails(key: string): string {
async objectKeyDetails(key: string): Promise<string> {
const end = key.slice(this.prefix.length);
const { uuid, createdAt, expiresAt } = S3LockExt.fromString(end);
const created = Duration.since(createdAt);
const expires = Duration.until(expiresAt);

return [
let context;

try {
const obj = await this.s3.send(
new GetObjectCommand({
Bucket: this.bucket,
Key: key,
}),
);
context = await obj.Body?.transformToString();
} catch (ex) {
core.warning(`Unable to read object body ${ex}`);
}

const contextLines =
context && context === "" ? [] : [`Context: ${color.gray(context)}`];

const messageLines = [
`${uuid}`,
`Created: ${color.gray(createdAt)} (${color.cyan(created)} ago)`,
`Expires: ${color.gray(expiresAt)} (${color.cyan(expires)} from now)`,
].join("\n ");
].concat(contextLines);

return messageLines.join("\n ");
}

static async releaseLock(bucket: string, key: string): Promise<void> {
Expand Down
9 changes: 5 additions & 4 deletions src/acquire.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@ import * as color from "./color";

async function run() {
try {
const { name, bucket, expires, timeout, timeoutPoll } = getInputs();
const { name, bucket, expires, timeout, timeoutPoll, context } =
getInputs();

const timer = new Timer(timeout);
const s3Lock = new S3Lock(bucket, name, expires);

while (true) {
let result = await s3Lock.acquireLock();
let result = await s3Lock.acquireLock(context);

if (result.tag === "acquired") {
const key = result.acquiredKey;
const keyDetails = s3Lock.objectKeyDetails(key);
const keyDetails = await s3Lock.objectKeyDetails(key);
core.info(
`Lock ${color.bold(name)} ${color.green(
"acquired",
Expand All @@ -30,7 +31,7 @@ async function run() {
}

const key = result.blockingKey;
const keyDetails = s3Lock.objectKeyDetails(key);
const keyDetails = await s3Lock.objectKeyDetails(key);

if (timer.expired()) {
core.error(
Expand Down
4 changes: 3 additions & 1 deletion src/inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type Inputs = {
expires: Duration;
timeout: Duration;
timeoutPoll: Duration;
context: string;
};

export function getInputs(): Inputs {
Expand All @@ -19,10 +20,11 @@ export function getInputs(): Inputs {
const rawExpires = core.getInput("expires", { required: true });
const rawTimeout = core.getInput("timeout", { required: false });
const rawTimeoutPoll = core.getInput("timeout-poll", { required: true });
const context = core.getInput("context", { required: false });

const expires = Duration.parse(rawExpires);
const timeout = rawTimeout === "" ? expires : Duration.parse(rawTimeout);
const timeoutPoll = Duration.parse(rawTimeoutPoll);

return { name, bucket, expires, timeout, timeoutPoll };
return { name, bucket, expires, timeout, timeoutPoll, context };
}