From 610947eefba9093651484baaf3be9312cf12e1cd Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Tue, 10 Oct 2023 17:35:40 -0500 Subject: [PATCH] Improve portability --- slackme/main.test.ts | 94 ++++++++++++++++++++++++++++++++++++++++++++ slackme/main.tf | 31 +++++++++------ slackme/slackme.sh | 34 ++++++++++------ test.ts | 2 + 4 files changed, 138 insertions(+), 23 deletions(-) create mode 100644 slackme/main.test.ts diff --git a/slackme/main.test.ts b/slackme/main.test.ts new file mode 100644 index 0000000..9be3e8e --- /dev/null +++ b/slackme/main.test.ts @@ -0,0 +1,94 @@ +import { describe, expect, it } from "bun:test"; +import { + createJSONResponse, + execContainer, + executeScriptInContainer, + findResourceInstance, + runContainer, + runTerraformApply, + runTerraformInit, + testRequiredVariables, +} from "../test"; +import { serve } from "bun"; + +describe("slackme", async () => { + await runTerraformInit(import.meta.dir); + + testRequiredVariables(import.meta.dir, { + agent_id: "foo", + 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"); + let exec = await execContainer(id, ["sh", "-c", instance.script]); + expect(exec.exitCode).toBe(0); + exec = await execContainer(id, ["sh", "-c", "which slackme"]); + expect(exec.exitCode).toBe(0); + expect(exec.stdout.trim()).toEqual("/usr/bin/slackme"); + }); + + it("prints usage with no command", async () => { + const { instance, id } = await setupContainer(); + await writeCoder(id, "echo 👋"); + let exec = await execContainer(id, ["sh", "-c", instance.script]); + 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" + ); + }); + + it("displays url when not authenticated", async () => { + const { instance, id } = await setupContainer(); + await writeCoder(id, "echo 'some-url' && exit 1"); + let exec = await execContainer(id, ["sh", "-c", instance.script]); + expect(exec.exitCode).toBe(0); + exec = await execContainer(id, ["sh", "-c", "slackme echo test"]); + 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, + }); + + 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"); + }); +}); diff --git a/slackme/main.tf b/slackme/main.tf index 74d54c6..ee67ae2 100644 --- a/slackme/main.tf +++ b/slackme/main.tf @@ -15,23 +15,32 @@ variable "agent_id" { } variable "auth_provider_id" { - type = string - description = "The ID of an external auth provider." + type = string + description = "The ID of an external auth provider." +} + +variable "slack_message" { + type = string + description = "The message to send to Slack." + default = "Your command completed!" } resource "coder_script" "install_slackme" { - agent_id = var.agent_id - display_name = "install_slackme" - run_on_start = true - script = < $CODER_DIR/slackme < +Example: slackme npm run long-build +EOF +} + +if [ $# -eq 0 ]; then + usage + exit 1 +fi + +BOT_TOKEN=$(coder external-auth access-token $PROVIDER_ID) if [ $? -ne 0 ]; then - echo "Authenticate to run commands in the background:" - # The output contains the URL if failed. - echo $BOT_TOKEN + printf "Authenticate with Slack to be notified when a command finishes:\n$BOT_TOKEN\n" exit 1 fi -USER_ID=\$(coder external-auth access-token $PROVIDER_ID --extra "authed_user.id") - +USER_ID=$(coder external-auth access-token $PROVIDER_ID --extra "authed_user.id") if [ $? -ne 0 ]; then - echo "Failed to get authenticated user ID:" - echo $USER_ID + printf "Failed to get authenticated user ID:\n$USER_ID\n" exit 1 fi -echo "We'll notify you when done!" - # Run all arguments as a command $@ +set -e curl --silent -o /dev/null --header "Authorization: Bearer $BOT_TOKEN" \ - "https://slack.com/api/chat.postMessage?channel=$USER_ID&text=Your%20command%20finished!&pretty=1" + "$SLACK_URL/api/chat.postMessage?channel=$USER_ID&text=$SLACK_MESSAGE&pretty=1" diff --git a/test.ts b/test.ts index 6546490..fbe5414 100644 --- a/test.ts +++ b/test.ts @@ -13,6 +13,8 @@ export const runContainer = async ( "-d", "--label", "modules-test=true", + "--network", + "host", "--entrypoint", "sh", image,