diff --git a/.icons/marimo.svg b/.icons/marimo.svg new file mode 100644 index 00000000..db76c169 --- /dev/null +++ b/.icons/marimo.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/marimo/README.md b/marimo/README.md new file mode 100644 index 00000000..3fc84a74 --- /dev/null +++ b/marimo/README.md @@ -0,0 +1,23 @@ +--- +display_name: marimo +description: A module that adds marimo in your Coder template. +icon: ../.icons/marimo.svg +maintainer_github: coder +verified: true +tags: [marimo, python, notebook, reactive, helper, ide, web] +--- + +# marimo + +A module that adds [marimo](https://github.com/marimo-team/marimo) in your Coder template. + +marimo is a reactive Python notebook that's reproducible, git-friendly, and deployable as scripts or apps. + +```tf +module "marimo" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/modules/marimo/coder" + version = "1.0.0" + agent_id = coder_agent.example.id +} +``` \ No newline at end of file diff --git a/marimo/main.test.ts b/marimo/main.test.ts new file mode 100644 index 00000000..b76446a5 --- /dev/null +++ b/marimo/main.test.ts @@ -0,0 +1,83 @@ +import { describe, expect, it } from "bun:test"; +import { + execContainer, + executeScriptInContainer, + findResourceInstance, + runContainer, + runTerraformApply, + runTerraformInit, + testRequiredVariables, + type TerraformState, +} from "../test"; + +// executes the coder script after installing pip +const executeScriptInContainerWithPipx = async ( + state: TerraformState, + image: string, + shell = "sh", +): Promise<{ + exitCode: number; + stdout: string[]; + stderr: string[]; +}> => { + const instance = findResourceInstance(state, "coder_script"); + const id = await runContainer(image); + const respPipx = await execContainer(id, [shell, "-c", "apk add pipx"]); + const resp = await execContainer(id, [shell, "-c", instance.script]); + const stdout = resp.stdout.trim().split("\n"); + const stderr = resp.stderr.trim().split("\n"); + return { + exitCode: resp.exitCode, + stdout, + stderr, + }; +}; + +// executes the coder script after installing uv +const executeScriptInContainerWithUv = async ( + state: TerraformState, + image: string, + shell = "sh", +): Promise<{ + exitCode: number; + stdout: string[]; + stderr: string[]; +}> => { + const instance = findResourceInstance(state, "coder_script"); + const id = await runContainer(image); + const respPipx = await execContainer(id, [ + shell, + "-c", + "apk --no-cache add uv gcc musl-dev linux-headers && uv venv", + ]); + const resp = await execContainer(id, [shell, "-c", instance.script]); + const stdout = resp.stdout.trim().split("\n"); + const stderr = resp.stderr.trim().split("\n"); + return { + exitCode: resp.exitCode, + stdout, + stderr, + }; +}; + +describe("marimo", async () => { + await runTerraformInit(import.meta.dir); + + testRequiredVariables(import.meta.dir, { + agent_id: "foo", + }); + + it("fails without installers", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + }); + const output = await executeScriptInContainer(state, "alpine"); + expect(output.exitCode).toBe(1); + expect(output.stdout).toEqual([ + "Checking for a supported installer", + "No valid installer is not installed", + "Please install pipx or uv in your Dockerfile/VM image before running this script", + ]); + }); + +}); \ No newline at end of file diff --git a/marimo/main.tf b/marimo/main.tf new file mode 100644 index 00000000..b03a028c --- /dev/null +++ b/marimo/main.tf @@ -0,0 +1,75 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + coder = { + source = "coder/coder" + version = ">= 0.17" + } + } +} + +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +# Add required variables for your modules and remove any unneeded variables +variable "agent_id" { + type = string + description = "The ID of a Coder agent." +} + +variable "log_path" { + type = string + description = "The path to log marimo to." + default = "/tmp/marimo.log" +} + +variable "port" { + type = number + description = "The port to run marimo on." + default = 18888 +} + +variable "share" { + type = string + default = "owner" + validation { + condition = var.share == "owner" || var.share == "authenticated" || var.share == "public" + error_message = "Incorrect value. Please set either 'owner', 'authenticated', or 'public'." + } +} + +variable "subdomain" { + type = bool + description = "Determines whether marimo will be accessed via its own subdomain or whether it will be accessed via a path on Coder." + default = true +} + +variable "order" { + type = number + description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)." + default = null +} + +resource "coder_script" "marimo" { + agent_id = var.agent_id + display_name = "marimo" + icon = "/icon/marimo.svg" + script = templatefile("${path.module}/run.sh", { + LOG_PATH : var.log_path, + PORT : var.port + BASE_URL : var.subdomain ? "" : "/@${data.coder_workspace_owner.me.name}/${data.coder_workspace.me.name}/apps/marimo" + }) + run_on_start = true +} + +resource "coder_app" "marimo" { + agent_id = var.agent_id + slug = "marimo" # sync with the usage in URL + display_name = "marimo" + url = var.subdomain ? "http://localhost:${var.port}" : "http://localhost:${var.port}/@${data.coder_workspace_owner.me.name}/${data.coder_workspace.me.name}/apps/marimo" + icon = "/icon/marimo.svg" + subdomain = var.subdomain + share = var.share + order = var.order +} \ No newline at end of file diff --git a/marimo/run.sh b/marimo/run.sh new file mode 100755 index 00000000..59266495 --- /dev/null +++ b/marimo/run.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env sh +INSTALLER="" +check_available_installer() { + # check if pipx is installed + echo "Checking for a supported installer" + if command -v pipx > /dev/null 2>&1; then + echo "pipx is installed" + INSTALLER="pipx" + return + fi + # check if uv is installed + if command -v uv > /dev/null 2>&1; then + echo "uv is installed" + INSTALLER="uv" + return + fi + echo "No valid installer is not installed" + echo "Please install pipx or uv in your Dockerfile/VM image before running this script" + exit 1 +} + +if [ -n "${BASE_URL}" ]; then + BASE_URL_FLAG="--base-url=${BASE_URL}" +fi + +BOLD='\033[0;1m' + +# check if marimo is installed +if ! command -v marimo > /dev/null 2>&1; then + # install marimo + check_available_installer + printf "$${BOLD}Installing marimo!\n" + case $INSTALLER in + uv) + uv pip install -q marimo[recommended] \ + && printf "%s\n" "🥳 marimo has been installed" + MARIMOPATH="$HOME/.venv/bin/" + ;; + pipx) + pipx install marimo[recommended] \ + && printf "%s\n" "🥳 marimo has been installed" + MARIMOPATH="$HOME/.local/bin" + ;; + esac +else + printf "%s\n\n" "🥳 marimo is already installed" +fi + +printf "👷 Starting marimo in background..." +printf "check logs at ${LOG_PATH}" +$MARIMOPATH/marimo run \ + "$BASE_URL_FLAG" \ + --host="*" \ + --port="${PORT}" \ + --headless \ + --include-code \ + > "${LOG_PATH}" 2>&1 & \ No newline at end of file