-
Notifications
You must be signed in to change notification settings - Fork 4
feat: expose mount and commit as separate sub-actions #53
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| name: "Blacksmith Sticky Disk — Commit" | ||
| author: Aditya Maru | ||
| description: "Unmount and commit a Blacksmith sticky disk. Run as a regular step after stickydisk/mount." | ||
| branding: | ||
| icon: folder-plus | ||
| color: black | ||
| inputs: | ||
| expose-id: | ||
| description: "The expose ID from the mount step" | ||
| required: true | ||
| key: | ||
| description: "The sticky disk key (must match the mount step)" | ||
| required: true | ||
| path: | ||
| description: "The mount path (must match the mount step, tilde OK)" | ||
| required: true | ||
| runs: | ||
| using: "node24" | ||
| main: "../dist/commit/index.js" |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| name: "Blacksmith Sticky Disk — Mount" | ||
| author: Aditya Maru | ||
| description: "Mount a Blacksmith sticky disk without auto-committing on teardown. Use stickydisk/commit to commit explicitly." | ||
| branding: | ||
| icon: folder-plus | ||
| color: black | ||
| inputs: | ||
| key: | ||
| description: "A unique key to identify the sticky disk" | ||
| required: true | ||
| path: | ||
| description: "The path at which to mount the sticky disk" | ||
| required: true | ||
| outputs: | ||
| expose-id: | ||
| description: "The expose ID for the mounted disk (pass to stickydisk/commit)" | ||
| key: | ||
| description: "The sticky disk key (pass-through for convenience)" | ||
| path: | ||
| description: "The mount path (pass-through for convenience)" | ||
| runs: | ||
| using: "node24" | ||
| main: "../dist/index.js" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| /** | ||
| * Standalone commit entry point for `stickydisk/commit`. | ||
| * | ||
| * Reads expose-id, key, and path from action inputs (not state), | ||
| * then runs the same unmount → flush → commit logic as post.ts. | ||
| */ | ||
| import * as core from "@actions/core"; | ||
| import { promisify } from "util"; | ||
| import { exec } from "child_process"; | ||
| import { createStickyDiskClient } from "./utils"; | ||
|
|
||
| const execAsync = promisify(exec); | ||
|
|
||
| async function commitStickydisk( | ||
| exposeId: string, | ||
| stickyDiskKey: string, | ||
| fsDiskUsageBytes: number | null, | ||
| ): Promise<void> { | ||
| core.info( | ||
| `Committing sticky disk ${stickyDiskKey} with expose ID ${exposeId}`, | ||
| ); | ||
|
|
||
| try { | ||
| const client = await createStickyDiskClient(); | ||
|
|
||
| const commitRequest: Record<string, unknown> = { | ||
| exposeId, | ||
| stickyDiskKey, | ||
| vmId: process.env.BLACKSMITH_VM_ID || "", | ||
| shouldCommit: true, | ||
| repoName: process.env.GITHUB_REPO_NAME || "", | ||
| stickyDiskToken: process.env.BLACKSMITH_STICKYDISK_TOKEN || "", | ||
| }; | ||
|
|
||
| if (fsDiskUsageBytes !== null && fsDiskUsageBytes > 0) { | ||
| commitRequest.fsDiskUsageBytes = BigInt(fsDiskUsageBytes); | ||
| } | ||
|
|
||
| await client.commitStickyDisk(commitRequest, { timeoutMs: 30000 }); | ||
| core.info( | ||
| `Successfully committed sticky disk ${stickyDiskKey} with expose ID ${exposeId}`, | ||
| ); | ||
| } catch (error) { | ||
| core.warning( | ||
| `Error committing sticky disk: ${error instanceof Error ? error.message : String(error)}`, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| async function getDeviceFromMount(mountPoint: string): Promise<string | null> { | ||
| try { | ||
| const { stdout } = await execAsync(`findmnt -n -o SOURCE "${mountPoint}"`); | ||
| const device = stdout.trim(); | ||
| if (device) return device; | ||
| } catch { | ||
| /* fall through */ | ||
| } | ||
| try { | ||
| const { stdout } = await execAsync(`mount | grep " ${mountPoint} "`); | ||
| const match = stdout.match(/^(\/dev\/\S+)/); | ||
| if (match) return match[1]; | ||
| } catch { | ||
| /* fall through */ | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| const FLUSH_TIMEOUT_SECS = 10; | ||
|
|
||
| async function flushBlockDevice(devicePath: string): Promise<void> { | ||
| const deviceName = devicePath.replace("/dev/", ""); | ||
| if (!deviceName) return; | ||
|
|
||
| const startTime = Date.now(); | ||
| try { | ||
| await execAsync( | ||
| `timeout ${FLUSH_TIMEOUT_SECS} sudo blockdev --flushbufs ${devicePath}`, | ||
| ); | ||
| core.info(`guest flush duration: ${Date.now() - startTime}ms, device: ${devicePath}`); | ||
| } catch { | ||
| core.info(`guest flush failed for ${devicePath} after ${Date.now() - startTime}ms`); | ||
| } | ||
| } | ||
|
|
||
| /** Resolve leading `~` to $HOME since mount paths are always expanded. */ | ||
| function resolveTilde(p: string): string { | ||
| if (p === "~" || p.startsWith("~/")) { | ||
| return (process.env.HOME ?? "/home/runner") + p.slice(1); | ||
| } | ||
| return p; | ||
| } | ||
|
|
||
| async function run(): Promise<void> { | ||
| const stickyDiskPath = resolveTilde(core.getInput("path", { required: true })); | ||
| const exposeId = core.getInput("expose-id", { required: true }); | ||
| const stickyDiskKey = core.getInput("key", { required: true }); | ||
|
|
||
| core.info(`Committing stickydisk: path=${stickyDiskPath} key=${stickyDiskKey} expose-id=${exposeId}`); | ||
|
|
||
| try { | ||
| // Verify mount | ||
| const { stdout: mountOutput } = await execAsync( | ||
| `mount | grep "${stickyDiskPath}"`, | ||
| ); | ||
| if (!mountOutput) { | ||
| core.warning(`${stickyDiskPath} is not mounted, skipping commit`); | ||
| return; | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing try-catch around grep makes mount check dead codeMedium Severity The Reviewed by Cursor Bugbot for commit 8f34fb4. Configure here. |
||
|
|
||
| const devicePath = await getDeviceFromMount(stickyDiskPath); | ||
|
|
||
| // Sync and measure usage | ||
| await execAsync("sync"); | ||
| let fsDiskUsageBytes: number | null = null; | ||
| try { | ||
| const { stdout } = await execAsync( | ||
| `df -B1 --output=used "${stickyDiskPath}" | tail -n1`, | ||
| ); | ||
| const parsed = parseInt(stdout.trim(), 10); | ||
| if (!isNaN(parsed) && parsed > 0) { | ||
| fsDiskUsageBytes = parsed; | ||
| core.info(`Filesystem usage: ${fsDiskUsageBytes} bytes`); | ||
| } | ||
| } catch { | ||
| /* non-fatal */ | ||
| } | ||
|
|
||
| // Drop caches for clean unmount | ||
| await execAsync("sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'"); | ||
|
|
||
| // Unmount with retries | ||
| for (let attempt = 1; attempt <= 10; attempt++) { | ||
| try { | ||
| await execAsync(`sudo umount "${stickyDiskPath}"`); | ||
| core.info(`Successfully unmounted ${stickyDiskPath}`); | ||
| break; | ||
| } catch (error) { | ||
| if (attempt === 10) throw error; | ||
| core.warning(`Unmount failed, retrying (${attempt}/10)...`); | ||
| await new Promise((resolve) => setTimeout(resolve, 300)); | ||
| } | ||
| } | ||
|
|
||
| // Flush block device | ||
| if (devicePath) { | ||
| await flushBlockDevice(devicePath); | ||
| } | ||
|
|
||
| // Commit | ||
| await commitStickydisk(exposeId, stickyDiskKey, fsDiskUsageBytes); | ||
| } catch (error) { | ||
| core.warning( | ||
| `Failed to commit sticky disk at ${stickyDiskPath}: ${error instanceof Error ? error.message : String(error)}`, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| run(); | ||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicated functions between commit-main.ts and post.ts
Medium Severity
commitStickydisk,getDeviceFromMount, andflushBlockDeviceare duplicated betweencommit-main.tsandpost.ts. The copies already diverge —commit-main.tsis missing theexposeId/stickyDiskKeyvalidation guard frompost.ts'scommitStickydisk, and itsflushBlockDeviceomits block device stat collection and exit-code parsing. Future bug fixes applied to one file risk being missed in the other. These shared functions belong in a common module likeutils.ts.Reviewed by Cursor Bugbot for commit 8f34fb4. Configure here.