diff --git a/slackme/README.md b/slackme/README.md index 92f91af..017f06a 100644 --- a/slackme/README.md +++ b/slackme/README.md @@ -61,3 +61,21 @@ $ slackme npm run long-build auth_provider_id = "slack" } ``` + +## Examples + +### Custom Slack Message + +- `$COMMAND` is replaced with the command the user executed. +- `$DURATION` is replaced with a human-readable duration the command took to execute. + +```hcl +module "slackme" { + source = "https://registry.coder.com/modules/slackme" + agent_id = coder_agent.example.id + auth_provider_id = "slack" + slack_message = < { await runTerraformInit(import.meta.dir); @@ -19,25 +18,6 @@ describe("slackme", async () => { auth_provider_id: "foo", }); - const setupContainer = async (image = "alpine") => { - const state = await runTerraformApply(import.meta.dir, { - agent_id: "foo", - auth_provider_id: "foo", - }); - const instance = findResourceInstance(state, "coder_script"); - const id = await runContainer(image); - return { id, instance }; - }; - - const writeCoder = async (id: string, script: string) => { - const exec = await execContainer(id, [ - "sh", - "-c", - `echo '${script}' > /usr/bin/coder && chmod +x /usr/bin/coder`, - ]); - expect(exec.exitCode).toBe(0); - }; - it("writes to path as executable", async () => { const { instance, id } = await setupContainer(); await writeCoder(id, "exit 0"); @@ -55,7 +35,7 @@ describe("slackme", async () => { expect(exec.exitCode).toBe(0); exec = await execContainer(id, ["sh", "-c", "slackme"]); expect(exec.stdout.trim()).toStartWith( - "slackme — Send a Slack notification when a command finishes", + "slackme — Send a Slack notification when a command finishes" ); }); @@ -68,31 +48,122 @@ describe("slackme", async () => { expect(exec.stdout.trim()).toEndWith("some-url"); }); - it("curls url when authenticated", async () => { - let url: URL; - const fakeSlackHost = serve({ - fetch: (req) => { - url = new URL(req.url); - if (url.pathname === "/api/chat.postMessage") - return createJSONResponse({ - ok: true, - }); - return createJSONResponse({}, 404); - }, - port: 0, + it("default output", async () => { + await assertSlackMessage({ + command: "echo test", + durationMS: 2, + output: "👨‍💻 `echo test` completed in 2ms", }); + }); - const { instance, id } = await setupContainer("alpine/curl"); - await writeCoder(id, "echo 'token'"); - let exec = await execContainer(id, ["sh", "-c", instance.script]); - expect(exec.exitCode).toBe(0); - exec = await execContainer(id, [ - "sh", - "-c", - `SLACK_URL="http://${fakeSlackHost.hostname}:${fakeSlackHost.port}" slackme echo test`, - ]); - expect(exec.stdout.trim()).toEndWith("test"); - expect(url.pathname).toEqual("/api/chat.postMessage"); - expect(url.searchParams.get("channel")).toEqual("token"); + it("formats multiline message", async () => { + await assertSlackMessage({ + command: "echo test", + format: `this command: +\`$COMMAND\` +executed`, + output: `this command: +\`echo test\` +executed`, + }); + }); + + it("formats execution with milliseconds", async () => { + await assertSlackMessage({ + command: "echo test", + format: `$COMMAND took $DURATION`, + durationMS: 150, + output: "echo test took 150ms", + }); + }); + + it("formats execution with seconds", async () => { + await assertSlackMessage({ + command: "echo test", + format: `$COMMAND took $DURATION`, + durationMS: 15000, + output: "echo test took 15.0s", + }); + }); + + it("formats execution with minutes", async () => { + await assertSlackMessage({ + command: "echo test", + format: `$COMMAND took $DURATION`, + durationMS: 120000, + output: "echo test took 2m 0.0s", + }); + }); + + it("formats execution with hours", async () => { + await assertSlackMessage({ + command: "echo test", + format: `$COMMAND took $DURATION`, + durationMS: 60000 * 60, + output: "echo test took 1hr 0m 0.0s", + }); }); }); + +const setupContainer = async ( + image = "alpine", + vars: Record = {} +) => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + auth_provider_id: "foo", + ...vars, + }); + const instance = findResourceInstance(state, "coder_script"); + const id = await runContainer(image); + return { id, instance }; +}; + +const writeCoder = async (id: string, script: string) => { + const exec = await execContainer(id, [ + "sh", + "-c", + `echo '${script}' > /usr/bin/coder && chmod +x /usr/bin/coder`, + ]); + expect(exec.exitCode).toBe(0); +}; + +const assertSlackMessage = async (opts: { + command: string; + format?: string; + durationMS?: number; + output: string; +}) => { + let url: URL; + const fakeSlackHost = serve({ + fetch: (req) => { + url = new URL(req.url); + if (url.pathname === "/api/chat.postMessage") + return createJSONResponse({ + ok: true, + }); + return createJSONResponse({}, 404); + }, + port: 0, + }); + const { instance, id } = await setupContainer( + "alpine/curl", + opts.format && { + slack_message: opts.format, + } + ); + await writeCoder(id, "echo 'token'"); + let exec = await execContainer(id, ["sh", "-c", instance.script]); + expect(exec.exitCode).toBe(0); + exec = await execContainer(id, [ + "sh", + "-c", + `DURATION_MS=${opts.durationMS || 0} SLACK_URL="http://${ + fakeSlackHost.hostname + }:${fakeSlackHost.port}" slackme ${opts.command}`, + ]); + expect(exec.stderr.trim()).toBe(""); + expect(url.pathname).toEqual("/api/chat.postMessage"); + expect(url.searchParams.get("channel")).toEqual("token"); + expect(url.searchParams.get("text")).toEqual(opts.output); +}; diff --git a/slackme/main.tf b/slackme/main.tf index ee67ae2..9eccfae 100644 --- a/slackme/main.tf +++ b/slackme/main.tf @@ -22,7 +22,7 @@ variable "auth_provider_id" { variable "slack_message" { type = string description = "The message to send to Slack." - default = "Your command completed!" + default = "👨‍💻 `$COMMAND` completed in $DURATION" } resource "coder_script" "install_slackme" { @@ -37,7 +37,7 @@ resource "coder_script" "install_slackme" { cat > $CODER_DIR/slackme <