From 236022f870f2ff3a847dd0e51d38adb0ec869ba4 Mon Sep 17 00:00:00 2001 From: megumin Date: Sun, 1 Sep 2024 12:16:05 +0100 Subject: [PATCH 01/26] feat(git-clone): custom destination folder name (#287) --- git-clone/README.md | 17 +++++++++++++++++ git-clone/main.test.ts | 16 ++++++++++++++++ git-clone/main.tf | 8 +++++++- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/git-clone/README.md b/git-clone/README.md index 255b3f1..5efc50e 100644 --- a/git-clone/README.md +++ b/git-clone/README.md @@ -153,3 +153,20 @@ module "git-clone" { branch_name = "feat/example" } ``` + +## Git clone with different destination folder + +By default, the repository will be cloned into a folder matching the repository name. You can use the `folder_name` attribute to change the name of the destination folder to something else. + +For example, this will clone into the `~/projects/coder/coder-dev` folder: + +```tf +module "git-clone" { + source = "registry.coder.com/modules/git-clone/coder" + version = "1.0.12" + agent_id = coder_agent.example.id + url = "https://github.com/coder/coder" + folder_name = "coder-dev" + base_dir = "~/projects/coder" +} +``` diff --git a/git-clone/main.test.ts b/git-clone/main.test.ts index 87b0e4a..9fbd202 100644 --- a/git-clone/main.test.ts +++ b/git-clone/main.test.ts @@ -79,6 +79,22 @@ describe("git-clone", async () => { expect(state.outputs.branch_name.value).toEqual(""); }); + it("repo_dir should match base_dir/folder_name", async () => { + const url = "git@github.com:coder/coder.git"; + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + base_dir: "/tmp", + folder_name: "foo", + url, + }); + expect(state.outputs.repo_dir.value).toEqual("/tmp/foo"); + expect(state.outputs.folder_name.value).toEqual("foo"); + expect(state.outputs.clone_url.value).toEqual(url); + const https_url = "https://github.com/coder/coder.git"; + expect(state.outputs.web_url.value).toEqual(https_url); + expect(state.outputs.branch_name.value).toEqual(""); + }); + it("branch_name should not include query string", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", diff --git a/git-clone/main.tf b/git-clone/main.tf index 4af5000..0295444 100644 --- a/git-clone/main.tf +++ b/git-clone/main.tf @@ -50,6 +50,12 @@ variable "branch_name" { default = "" } +variable "folder_name" { + description = "The destination folder to clone the repository into." + type = string + default = "" +} + locals { # Remove query parameters and fragments from the URL url = replace(replace(var.url, "/\\?.*/", ""), "/#.*/", "") @@ -64,7 +70,7 @@ locals { # Extract the branch name from the URL branch_name = var.branch_name == "" && local.tree_path != "" ? replace(replace(local.url, local.clone_url, ""), "/.*${local.tree_path}/", "") : var.branch_name # Extract the folder name from the URL - folder_name = replace(basename(local.clone_url), ".git", "") + folder_name = var.folder_name == "" ? replace(basename(local.clone_url), ".git", "") : var.folder_name # Construct the path to clone the repository clone_path = var.base_dir != "" ? join("/", [var.base_dir, local.folder_name]) : join("/", ["~", local.folder_name]) # Construct the web URL From 831f64da5614cb57fb84c5580ee9d565fe60b51d Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Wed, 4 Sep 2024 00:10:38 +0500 Subject: [PATCH 02/26] chore: remove package-lock.json and update deps (#281) --- bun.lockb | Bin 9792 -> 9792 bytes package-lock.json | 264 ---------------------------------------------- package.json | 8 +- 3 files changed, 4 insertions(+), 268 deletions(-) delete mode 100644 package-lock.json diff --git a/bun.lockb b/bun.lockb index d9abc986f834b6eb91780ee472e14151487174cf..7576953c8bd3806e197b8ca78e18dc166cd6122e 100755 GIT binary patch delta 469 zcmV;`0V@8$Ou$T#E+D)DBDoUzDyi{11YiyNK&f>m?eEwqS}GLw4{LQKgWP2!mofs;rpQ7wG4;RxZlqrTve^4Q4Tt%Ph@%vw zJgY0LaWMRn$Uw-my$~P)K(Wcg=hSInKXt}< zD?tLrkWac&vD7~|ie+eFj1-#Obfm&I&~tx2t8{1~Q82{;k;8L7kCu zQq2Un2_XA@o?mhsl-bt@hxwBb0zZ>b0tB*HTH7+!h5E~Z+F)lJQlPeoD0WyDBEMs{(3>R&9+JD8&zS;gxw7S^#&+5?lQ z1TeE$1dIX&000009g`6PCzB8d)CCIw000z|5dtTZ5C{~LN zKvqmGtJ=${dh%viGux6><|di6qsj= zIkr4%kGEJ|!|>gHG|Tq+FQHLu9R!x9`+=xDO!wxqy$~P)K=1m|+f$PH`n)3{(}L3E z46k_h?Cfi){02r?Id_$d@=CyTSjlEkb2A^V<{IkzXI+Gm| z7(k_UoUN=(((FJ>>rK!BzXV;A%BhDtO?YQc^islpq+8gz6squ{{0?QQoweT5kq-=Y z*;jE~0kUL@GI^lUCtH&c0zZ>b0tB*HTGcGfe5E~Z+FfK7TlPeoD0Wgza8x{jH zE;X}w8`A*;F)lN+P95X}0Wgz*ARn_*A^rjcFfKAVvs5Gm0R%BFGBA@|B}E4_E;BB4 JXL^&N6qsGB$qoPj diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 1010942..0000000 --- a/package-lock.json +++ /dev/null @@ -1,264 +0,0 @@ -{ - "name": "modules", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "modules", - "devDependencies": { - "bun-types": "^1.0.18", - "gray-matter": "^4.0.3", - "marked": "^12.0.0", - "prettier": "^3.2.5", - "prettier-plugin-sh": "^0.13.1", - "prettier-plugin-terraform-formatter": "^1.2.1" - }, - "peerDependencies": { - "typescript": "^5.3.3" - } - }, - "node_modules/@types/node": { - "version": "20.12.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.14.tgz", - "integrity": "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/bun-types": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.1.16.tgz", - "integrity": "sha512-LpAh8dQe4NKvhSW390Rkftw0ume0moSkRm575e1JZ1PwI/dXjbXyjpntq+2F0bVW1FV7V6B8EfWx088b+dNurw==", - "dev": true, - "dependencies": { - "@types/node": "~20.12.8", - "@types/ws": "~8.5.10" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gray-matter": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", - "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", - "dev": true, - "dependencies": { - "js-yaml": "^3.13.1", - "kind-of": "^6.0.2", - "section-matter": "^1.0.0", - "strip-bom-string": "^1.0.0" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/marked": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", - "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", - "dev": true, - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mvdan-sh": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/mvdan-sh/-/mvdan-sh-0.10.1.tgz", - "integrity": "sha512-kMbrH0EObaKmK3nVRKUIIya1dpASHIEusM13S4V1ViHFuxuNxCo+arxoa6j/dbV22YBGjl7UKJm9QQKJ2Crzhg==", - "dev": true - }, - "node_modules/prettier": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", - "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", - "dev": true, - "peer": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-plugin-sh": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/prettier-plugin-sh/-/prettier-plugin-sh-0.13.1.tgz", - "integrity": "sha512-ytMcl1qK4s4BOFGvsc9b0+k9dYECal7U29bL/ke08FEUsF/JLN0j6Peo0wUkFDG4y2UHLMhvpyd6Sd3zDXe/eg==", - "dev": true, - "dependencies": { - "mvdan-sh": "^0.10.1", - "sh-syntax": "^0.4.1" - }, - "engines": { - "node": ">=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - }, - "peerDependencies": { - "prettier": "^3.0.0" - } - }, - "node_modules/prettier-plugin-terraform-formatter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prettier-plugin-terraform-formatter/-/prettier-plugin-terraform-formatter-1.2.1.tgz", - "integrity": "sha512-rdzV61Bs/Ecnn7uAS/vL5usTX8xUWM+nQejNLZxt3I1kJH5WSeLEmq7LYu1wCoEQF+y7Uv1xGvPRfl3lIe6+tA==", - "dev": true, - "peerDependencies": { - "prettier": ">= 1.16.0" - }, - "peerDependenciesMeta": { - "prettier": { - "optional": true - } - } - }, - "node_modules/section-matter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", - "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/sh-syntax": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/sh-syntax/-/sh-syntax-0.4.2.tgz", - "integrity": "sha512-/l2UZ5fhGZLVZa16XQM9/Vq/hezGGbdHeVEA01uWjOL1+7Ek/gt6FquW0iKKws4a9AYPYvlz6RyVvjh3JxOteg==", - "dev": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/strip-bom-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", - "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", - "dev": true - }, - "node_modules/typescript": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", - "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true - } - } -} diff --git a/package.json b/package.json index f3136b1..eea421d 100644 --- a/package.json +++ b/package.json @@ -8,15 +8,15 @@ "update-version": "./update-version.sh" }, "devDependencies": { - "bun-types": "^1.0.18", + "bun-types": "^1.1.23", "gray-matter": "^4.0.3", - "marked": "^12.0.0", - "prettier": "^3.2.5", + "marked": "^12.0.2", + "prettier": "^3.3.3", "prettier-plugin-sh": "^0.13.1", "prettier-plugin-terraform-formatter": "^1.2.1" }, "peerDependencies": { - "typescript": "^5.3.3" + "typescript": "^5.5.4" }, "prettier": { "plugins": [ From 834ffde03260156a2c2a5d5e899d6edb66cfd3bb Mon Sep 17 00:00:00 2001 From: Sebastian <85109829+Seppdo@users.noreply.github.com> Date: Thu, 19 Sep 2024 21:28:16 +0200 Subject: [PATCH 03/26] feat(filebrowser): support subdomain = false (#286) Co-authored-by: Muhammad Atif Ali --- filebrowser/README.md | 28 +++++++++++++++++++++------- filebrowser/main.test.ts | 28 ++++++++++++++++++++++++++++ filebrowser/main.tf | 24 ++++++++++++++++++++++-- filebrowser/run.sh | 3 +++ 4 files changed, 74 insertions(+), 9 deletions(-) diff --git a/filebrowser/README.md b/filebrowser/README.md index 2881376..50b503a 100644 --- a/filebrowser/README.md +++ b/filebrowser/README.md @@ -13,9 +13,10 @@ A file browser for your workspace. ```tf module "filebrowser" { - source = "registry.coder.com/modules/filebrowser/coder" - version = "1.0.8" - agent_id = coder_agent.example.id + source = "registry.coder.com/modules/filebrowser/coder" + version = "1.0.8" + agent_id = coder_agent.example.id + agent_name = "main" } ``` @@ -27,10 +28,11 @@ module "filebrowser" { ```tf module "filebrowser" { - source = "registry.coder.com/modules/filebrowser/coder" - version = "1.0.8" - agent_id = coder_agent.example.id - folder = "/home/coder/project" + source = "registry.coder.com/modules/filebrowser/coder" + version = "1.0.8" + agent_id = coder_agent.example.id + agent_name = "main" + folder = "/home/coder/project" } ``` @@ -41,6 +43,18 @@ module "filebrowser" { source = "registry.coder.com/modules/filebrowser/coder" version = "1.0.8" agent_id = coder_agent.example.id + agent_name = "main" database_path = ".config/filebrowser.db" } ``` + +### Serve from the same domain (no subdomain) + +```tf +module "filebrowser" { + source = "registry.coder.com/modules/filebrowser/coder" + agent_id = coder_agent.example.id + agent_name = "main" + subdomain = false +} +``` diff --git a/filebrowser/main.test.ts b/filebrowser/main.test.ts index 79dd99d..ff6d045 100644 --- a/filebrowser/main.test.ts +++ b/filebrowser/main.test.ts @@ -11,11 +11,13 @@ describe("filebrowser", async () => { testRequiredVariables(import.meta.dir, { agent_id: "foo", + agent_name: "main", }); it("fails with wrong database_path", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", + agent_name: "main", database_path: "nofb", }).catch((e) => { if (!e.message.startsWith("\nError: Invalid value for variable")) { @@ -27,6 +29,7 @@ describe("filebrowser", async () => { it("runs with default", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", + agent_name: "main", }); const output = await executeScriptInContainer(state, "alpine"); expect(output.exitCode).toBe(0); @@ -48,6 +51,7 @@ describe("filebrowser", async () => { it("runs with database_path var", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", + agent_name: "main", database_path: ".config/filebrowser.db", }); const output = await executeScriptInContainer(state, "alpine"); @@ -70,6 +74,7 @@ describe("filebrowser", async () => { it("runs with folder var", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", + agent_name: "main", folder: "/home/coder/project", }); const output = await executeScriptInContainer(state, "alpine"); @@ -88,4 +93,27 @@ describe("filebrowser", async () => { "📝 Logs at /tmp/filebrowser.log", ]); }); + + it("runs with subdomain=false", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + agent_name: "main", + subdomain: false, + }); + const output = await executeScriptInContainer(state, "alpine"); + expect(output.exitCode).toBe(0); + expect(output.stdout).toEqual([ + "\u001B[0;1mInstalling filebrowser ", + "", + "🥳 Installation complete! ", + "", + "👷 Starting filebrowser in background... ", + "", + "📂 Serving /root at http://localhost:13339 ", + "", + "Running 'filebrowser --noauth --root /root --port 13339' ", + "", + "📝 Logs at /tmp/filebrowser.log", + ]); + }); }); diff --git a/filebrowser/main.tf b/filebrowser/main.tf index a07072b..e6b88c6 100644 --- a/filebrowser/main.tf +++ b/filebrowser/main.tf @@ -14,6 +14,15 @@ variable "agent_id" { description = "The ID of a Coder agent." } +data "coder_workspace" "me" {} + +data "coder_workspace_owner" "me" {} + +variable "agent_name" { + type = string + description = "The name of the main deployment. (Used to build the subpath for coder_app.)" +} + variable "database_path" { type = string description = "The path to the filebrowser database." @@ -58,6 +67,15 @@ variable "order" { default = null } +variable "subdomain" { + type = bool + description = <<-EOT + Determines whether the app will be accessed via it's own subdomain or whether it will be accessed via a path on Coder. + If wildcards have not been setup by the administrator then apps with "subdomain" set to true will not be accessible. + EOT + default = true +} + resource "coder_script" "filebrowser" { agent_id = var.agent_id display_name = "File Browser" @@ -67,7 +85,9 @@ resource "coder_script" "filebrowser" { PORT : var.port, FOLDER : var.folder, LOG_PATH : var.log_path, - DB_PATH : var.database_path + DB_PATH : var.database_path, + SUBDOMAIN : var.subdomain, + SERVER_BASE_PATH : var.subdomain ? "" : format("/@%s/%s.%s/apps/filebrowser", data.coder_workspace_owner.me.name, data.coder_workspace.me.name, var.agent_name), }) run_on_start = true } @@ -78,7 +98,7 @@ resource "coder_app" "filebrowser" { display_name = "File Browser" url = "http://localhost:${var.port}" icon = "https://raw.githubusercontent.com/filebrowser/logo/master/icon_raw.svg" - subdomain = true + subdomain = var.subdomain share = var.share order = var.order } diff --git a/filebrowser/run.sh b/filebrowser/run.sh index 8744edb..22f13ed 100644 --- a/filebrowser/run.sh +++ b/filebrowser/run.sh @@ -17,6 +17,9 @@ if [ "${DB_PATH}" != "filebrowser.db" ]; then DB_FLAG=" -d ${DB_PATH}" fi +# set baseurl to be able to run if sudomain=false; if subdomain=true the SERVER_BASE_PATH value will be "" +filebrowser config set --baseurl "${SERVER_BASE_PATH}" > ${LOG_PATH} 2>&1 + printf "📂 Serving $${ROOT_DIR} at http://localhost:${PORT} \n\n" printf "Running 'filebrowser --noauth --root $ROOT_DIR --port ${PORT}$${DB_FLAG}' \n\n" From b51932d7ac15ebbdff700b9072e362f94eeac269 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Fri, 20 Sep 2024 05:00:06 -0700 Subject: [PATCH 04/26] feat(dotfiles): Add an optional coder_app to update dotfiles on-demand (#280) Co-authored-by: Chris Golden <551285+cirego@users.noreply.github.com> Co-authored-by: Michael Smith --- dotfiles/main.tf | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/dotfiles/main.tf b/dotfiles/main.tf index bfb67e4..9bc3735 100644 --- a/dotfiles/main.tf +++ b/dotfiles/main.tf @@ -39,9 +39,14 @@ variable "coder_parameter_order" { default = null } -data "coder_parameter" "dotfiles_uri" { - count = var.dotfiles_uri == null ? 1 : 0 +variable "manual_update" { + type = bool + description = "If true, this adds a button to workspace page to refresh dotfiles on demand." + default = false +} +data "coder_parameter" "dotfiles_uri" { + count = var.dotfiles_uri == null ? 1 : 0 type = "string" name = "dotfiles_uri" display_name = "Dotfiles URL" @@ -68,6 +73,18 @@ resource "coder_script" "dotfiles" { run_on_start = true } +resource "coder_app" "dotfiles" { + count = var.manual_update ? 1 : 0 + agent_id = var.agent_id + display_name = "Refresh Dotfiles" + slug = "dotfiles" + icon = "/icon/dotfiles.svg" + command = templatefile("${path.module}/run.sh", { + DOTFILES_URI : local.dotfiles_uri, + DOTFILES_USER : local.user + }) +} + output "dotfiles_uri" { description = "Dotfiles URI" value = local.dotfiles_uri From 120a0e342ed38699102f1f34afddf0d2ce5da3f4 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Fri, 20 Sep 2024 08:20:20 -0700 Subject: [PATCH 05/26] feat(cursor): Add Cursor IDE module (#290) --- .icons/cursor.svg | 1 + cursor/README.md | 35 ++++++++++++++++++ cursor/main.test.ts | 89 +++++++++++++++++++++++++++++++++++++++++++++ cursor/main.tf | 62 +++++++++++++++++++++++++++++++ 4 files changed, 187 insertions(+) create mode 100644 .icons/cursor.svg create mode 100644 cursor/README.md create mode 100644 cursor/main.test.ts create mode 100644 cursor/main.tf diff --git a/.icons/cursor.svg b/.icons/cursor.svg new file mode 100644 index 0000000..c074bf2 --- /dev/null +++ b/.icons/cursor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cursor/README.md b/cursor/README.md new file mode 100644 index 0000000..a62743b --- /dev/null +++ b/cursor/README.md @@ -0,0 +1,35 @@ +--- +display_name: Cursor IDE +description: Add a one-click button to launch Cursor IDE +icon: ../.icons/cursor.svg +maintainer_github: coder +verified: true +tags: [ide, cursor, helper] +--- + +# Cursor IDE + +Add a button to open any workspace with a single click in Cursor IDE. + +Uses the [Coder Remote VS Code Extension](https://github.com/coder/cursor-coder). + +```tf +module "cursor" { + source = "registry.coder.com/modules/cursor/coder" + version = "1.0.18" + agent_id = coder_agent.example.id +} +``` + +## Examples + +### Open in a specific directory + +```tf +module "cursor" { + source = "registry.coder.com/modules/cursor/coder" + version = "1.0.18" + agent_id = coder_agent.example.id + folder = "/home/coder/project" +} +``` diff --git a/cursor/main.test.ts b/cursor/main.test.ts new file mode 100644 index 0000000..cdf70e6 --- /dev/null +++ b/cursor/main.test.ts @@ -0,0 +1,89 @@ +import { describe, expect, it } from "bun:test"; +import { + executeScriptInContainer, + runTerraformApply, + runTerraformInit, + testRequiredVariables, +} from "../test"; + +describe("cursor", async () => { + await runTerraformInit(import.meta.dir); + + testRequiredVariables(import.meta.dir, { + agent_id: "foo", + }); + + it("default output", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + }); + expect(state.outputs.cursor_url.value).toBe( + "cursor://coder.coder-remote/open?owner=default&workspace=default&url=https://mydeployment.coder.com&token=$SESSION_TOKEN", + ); + + const coder_app = state.resources.find( + (res) => res.type === "coder_app" && res.name === "cursor", + ); + + expect(coder_app).not.toBeNull(); + expect(coder_app?.instances.length).toBe(1); + expect(coder_app?.instances[0].attributes.order).toBeNull(); + }); + + it("adds folder", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + folder: "/foo/bar", + }); + expect(state.outputs.cursor_url.value).toBe( + "cursor://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&url=https://mydeployment.coder.com&token=$SESSION_TOKEN", + ); + }); + + it("adds folder and open_recent", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + folder: "/foo/bar", + open_recent: "true", + }); + expect(state.outputs.cursor_url.value).toBe( + "cursor://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN", + ); + }); + + it("adds folder but not open_recent", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + folder: "/foo/bar", + openRecent: "false", + }); + expect(state.outputs.cursor_url.value).toBe( + "cursor://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&url=https://mydeployment.coder.com&token=$SESSION_TOKEN", + ); + }); + + it("adds open_recent", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + open_recent: "true", + }); + expect(state.outputs.cursor_url.value).toBe( + "cursor://coder.coder-remote/open?owner=default&workspace=default&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN", + ); + }); + + it("expect order to be set", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + order: "22", + }); + + const coder_app = state.resources.find( + (res) => res.type === "coder_app" && res.name === "cursor", + ); + + expect(coder_app).not.toBeNull(); + expect(coder_app?.instances.length).toBe(1); + expect(coder_app?.instances[0].attributes.order).toBe(22); + }); +}); diff --git a/cursor/main.tf b/cursor/main.tf new file mode 100644 index 0000000..4d48191 --- /dev/null +++ b/cursor/main.tf @@ -0,0 +1,62 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + coder = { + source = "coder/coder" + version = ">= 0.23" + } + } +} + +variable "agent_id" { + type = string + description = "The ID of a Coder agent." +} + +variable "folder" { + type = string + description = "The folder to open in Cursor IDE." + default = "" +} + +variable "open_recent" { + type = bool + description = "Open the most recent workspace or folder. Falls back to the folder if there is no recent workspace or folder to open." + default = false +} + +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 +} + +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +resource "coder_app" "cursor" { + agent_id = var.agent_id + external = true + icon = "/icon/cursor.svg" + slug = "cursor" + display_name = "Cursor IDE" + order = var.order + url = join("", [ + "cursor://coder.coder-remote/open", + "?owner=", + data.coder_workspace_owner.me.name, + "&workspace=", + data.coder_workspace.me.name, + var.folder != "" ? join("", ["&folder=", var.folder]) : "", + var.open_recent ? "&openRecent" : "", + "&url=", + data.coder_workspace.me.access_url, + "&token=$SESSION_TOKEN", + ]) +} + +output "cursor_url" { + value = coder_app.cursor.url + description = "Cursor IDE Desktop URL." +} From 86038f8d374ebfc7b707eb84a8e79bac2d881699 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Fri, 20 Sep 2024 09:18:38 -0700 Subject: [PATCH 06/26] chore(git-commit-signing): mark the module as official (#291) --- git-commit-signing/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git-commit-signing/README.md b/git-commit-signing/README.md index 37633a2..942f2f3 100644 --- a/git-commit-signing/README.md +++ b/git-commit-signing/README.md @@ -2,8 +2,8 @@ display_name: Git commit signing description: Configures Git to sign commits using your Coder SSH key icon: ../.icons/git.svg -maintainer_github: phorcys420 -verified: false +maintainer_github: coder +verified: true tags: [helper, git] --- From 93c4fb3a8df4add1ca9e82897e26b6e207291c1b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 20 Sep 2024 21:22:43 +0500 Subject: [PATCH 07/26] chore: bump version to 1.0.18 in README.md files (#292) This is an auto-generated PR to update README.md files of all modules with the new tag 1.0.18 Co-authored-by: matifali --- code-server/README.md | 14 +++++++------- dotfiles/README.md | 12 ++++++------ filebrowser/README.md | 6 +++--- git-clone/README.md | 20 ++++++++++---------- windows-rdp/README.md | 6 +++--- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/code-server/README.md b/code-server/README.md index 3692d71..330661c 100644 --- a/code-server/README.md +++ b/code-server/README.md @@ -14,7 +14,7 @@ Automatically install [code-server](https://github.com/coder/code-server) in a w ```tf module "code-server" { source = "registry.coder.com/modules/code-server/coder" - version = "1.0.17" + version = "1.0.18" agent_id = coder_agent.example.id } ``` @@ -28,7 +28,7 @@ module "code-server" { ```tf module "code-server" { source = "registry.coder.com/modules/code-server/coder" - version = "1.0.17" + version = "1.0.18" agent_id = coder_agent.example.id install_version = "4.8.3" } @@ -41,7 +41,7 @@ Install the Dracula theme from [OpenVSX](https://open-vsx.org/): ```tf module "code-server" { source = "registry.coder.com/modules/code-server/coder" - version = "1.0.17" + version = "1.0.18" agent_id = coder_agent.example.id extensions = [ "dracula-theme.theme-dracula" @@ -58,7 +58,7 @@ Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarte ```tf module "code-server" { source = "registry.coder.com/modules/code-server/coder" - version = "1.0.17" + version = "1.0.18" agent_id = coder_agent.example.id extensions = ["dracula-theme.theme-dracula"] settings = { @@ -74,7 +74,7 @@ Just run code-server in the background, don't fetch it from GitHub: ```tf module "code-server" { source = "registry.coder.com/modules/code-server/coder" - version = "1.0.17" + version = "1.0.18" agent_id = coder_agent.example.id extensions = ["dracula-theme.theme-dracula", "ms-azuretools.vscode-docker"] } @@ -89,7 +89,7 @@ Run an existing copy of code-server if found, otherwise download from GitHub: ```tf module "code-server" { source = "registry.coder.com/modules/code-server/coder" - version = "1.0.17" + version = "1.0.18" agent_id = coder_agent.example.id use_cached = true extensions = ["dracula-theme.theme-dracula", "ms-azuretools.vscode-docker"] @@ -101,7 +101,7 @@ Just run code-server in the background, don't fetch it from GitHub: ```tf module "code-server" { source = "registry.coder.com/modules/code-server/coder" - version = "1.0.17" + version = "1.0.18" agent_id = coder_agent.example.id offline = true } diff --git a/dotfiles/README.md b/dotfiles/README.md index 41371ab..6d7673c 100644 --- a/dotfiles/README.md +++ b/dotfiles/README.md @@ -18,7 +18,7 @@ Under the hood, this module uses the [coder dotfiles](https://coder.com/docs/v2/ ```tf module "dotfiles" { source = "registry.coder.com/modules/dotfiles/coder" - version = "1.0.15" + version = "1.0.18" agent_id = coder_agent.example.id } ``` @@ -30,7 +30,7 @@ module "dotfiles" { ```tf module "dotfiles" { source = "registry.coder.com/modules/dotfiles/coder" - version = "1.0.15" + version = "1.0.18" agent_id = coder_agent.example.id } ``` @@ -40,7 +40,7 @@ module "dotfiles" { ```tf module "dotfiles" { source = "registry.coder.com/modules/dotfiles/coder" - version = "1.0.15" + version = "1.0.18" agent_id = coder_agent.example.id user = "root" } @@ -51,13 +51,13 @@ module "dotfiles" { ```tf module "dotfiles" { source = "registry.coder.com/modules/dotfiles/coder" - version = "1.0.15" + version = "1.0.18" agent_id = coder_agent.example.id } module "dotfiles-root" { source = "registry.coder.com/modules/dotfiles/coder" - version = "1.0.15" + version = "1.0.18" agent_id = coder_agent.example.id user = "root" dotfiles_uri = module.dotfiles.dotfiles_uri @@ -71,7 +71,7 @@ You can set a default dotfiles repository for all users by setting the `default_ ```tf module "dotfiles" { source = "registry.coder.com/modules/dotfiles/coder" - version = "1.0.15" + version = "1.0.18" agent_id = coder_agent.example.id default_dotfiles_uri = "https://github.com/coder/dotfiles" } diff --git a/filebrowser/README.md b/filebrowser/README.md index 50b503a..dd26d27 100644 --- a/filebrowser/README.md +++ b/filebrowser/README.md @@ -14,7 +14,7 @@ A file browser for your workspace. ```tf module "filebrowser" { source = "registry.coder.com/modules/filebrowser/coder" - version = "1.0.8" + version = "1.0.18" agent_id = coder_agent.example.id agent_name = "main" } @@ -29,7 +29,7 @@ module "filebrowser" { ```tf module "filebrowser" { source = "registry.coder.com/modules/filebrowser/coder" - version = "1.0.8" + version = "1.0.18" agent_id = coder_agent.example.id agent_name = "main" folder = "/home/coder/project" @@ -41,7 +41,7 @@ module "filebrowser" { ```tf module "filebrowser" { source = "registry.coder.com/modules/filebrowser/coder" - version = "1.0.8" + version = "1.0.18" agent_id = coder_agent.example.id agent_name = "main" database_path = ".config/filebrowser.db" diff --git a/git-clone/README.md b/git-clone/README.md index 5efc50e..6b8871e 100644 --- a/git-clone/README.md +++ b/git-clone/README.md @@ -14,7 +14,7 @@ This module allows you to automatically clone a repository by URL and skip if it ```tf module "git-clone" { source = "registry.coder.com/modules/git-clone/coder" - version = "1.0.12" + version = "1.0.18" agent_id = coder_agent.example.id url = "https://github.com/coder/coder" } @@ -27,7 +27,7 @@ module "git-clone" { ```tf module "git-clone" { source = "registry.coder.com/modules/git-clone/coder" - version = "1.0.12" + version = "1.0.18" agent_id = coder_agent.example.id url = "https://github.com/coder/coder" base_dir = "~/projects/coder" @@ -41,7 +41,7 @@ To use with [Git Authentication](https://coder.com/docs/v2/latest/admin/git-prov ```tf module "git-clone" { source = "registry.coder.com/modules/git-clone/coder" - version = "1.0.12" + version = "1.0.18" agent_id = coder_agent.example.id url = "https://github.com/coder/coder" } @@ -66,7 +66,7 @@ data "coder_parameter" "git_repo" { # Clone the repository for branch `feat/example` module "git_clone" { source = "registry.coder.com/modules/git-clone/coder" - version = "1.0.12" + version = "1.0.18" agent_id = coder_agent.example.id url = data.coder_parameter.git_repo.value } @@ -74,7 +74,7 @@ module "git_clone" { # Create a code-server instance for the cloned repository module "code-server" { source = "registry.coder.com/modules/code-server/coder" - version = "1.0.12" + version = "1.0.18" agent_id = coder_agent.example.id order = 1 folder = "/home/${local.username}/${module.git_clone.folder_name}" @@ -98,7 +98,7 @@ Configuring `git-clone` for a self-hosted GitHub Enterprise Server running at `g ```tf module "git-clone" { source = "registry.coder.com/modules/git-clone/coder" - version = "1.0.12" + version = "1.0.18" agent_id = coder_agent.example.id url = "https://github.example.com/coder/coder/tree/feat/example" git_providers = { @@ -116,7 +116,7 @@ To GitLab clone with a specific branch like `feat/example` ```tf module "git-clone" { source = "registry.coder.com/modules/git-clone/coder" - version = "1.0.12" + version = "1.0.18" agent_id = coder_agent.example.id url = "https://gitlab.com/coder/coder/-/tree/feat/example" } @@ -127,7 +127,7 @@ Configuring `git-clone` for a self-hosted GitLab running at `gitlab.example.com` ```tf module "git-clone" { source = "registry.coder.com/modules/git-clone/coder" - version = "1.0.12" + version = "1.0.18" agent_id = coder_agent.example.id url = "https://gitlab.example.com/coder/coder/-/tree/feat/example" git_providers = { @@ -147,7 +147,7 @@ For example, to clone the `feat/example` branch: ```tf module "git-clone" { source = "registry.coder.com/modules/git-clone/coder" - version = "1.0.12" + version = "1.0.18" agent_id = coder_agent.example.id url = "https://github.com/coder/coder" branch_name = "feat/example" @@ -163,7 +163,7 @@ For example, this will clone into the `~/projects/coder/coder-dev` folder: ```tf module "git-clone" { source = "registry.coder.com/modules/git-clone/coder" - version = "1.0.12" + version = "1.0.18" agent_id = coder_agent.example.id url = "https://github.com/coder/coder" folder_name = "coder-dev" diff --git a/windows-rdp/README.md b/windows-rdp/README.md index e8c5a1c..c4d35fd 100644 --- a/windows-rdp/README.md +++ b/windows-rdp/README.md @@ -15,7 +15,7 @@ Enable Remote Desktop + a web based client on Windows workspaces, powered by [de # AWS example. See below for examples of using this module with other providers module "windows_rdp" { source = "registry.coder.com/modules/windows-rdp/coder" - version = "1.0.16" + version = "1.0.18" count = data.coder_workspace.me.start_count agent_id = resource.coder_agent.main.id resource_id = resource.aws_instance.dev.id @@ -33,7 +33,7 @@ module "windows_rdp" { ```tf module "windows_rdp" { source = "registry.coder.com/modules/windows-rdp/coder" - version = "1.0.16" + version = "1.0.18" count = data.coder_workspace.me.start_count agent_id = resource.coder_agent.main.id resource_id = resource.aws_instance.dev.id @@ -45,7 +45,7 @@ module "windows_rdp" { ```tf module "windows_rdp" { source = "registry.coder.com/modules/windows-rdp/coder" - version = "1.0.16" + version = "1.0.18" count = data.coder_workspace.me.start_count agent_id = resource.coder_agent.main.id resource_id = resource.google_compute_instance.dev[0].id From e11b19d33e180d39e739ed0907663bcd87f0d888 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Mon, 23 Sep 2024 01:33:08 -0700 Subject: [PATCH 08/26] feat(jupyter): switch from pip3 to pipx for Jupyter install (#294) --- jupyter-notebook/run.sh | 14 +++++++------- jupyterlab/main.test.ts | 12 ++++++------ jupyterlab/run.sh | 12 ++++++------ 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/jupyter-notebook/run.sh b/jupyter-notebook/run.sh index 4f8c4a2..0c7a9b8 100755 --- a/jupyter-notebook/run.sh +++ b/jupyter-notebook/run.sh @@ -7,14 +7,14 @@ printf "$${BOLD}Installing jupyter-notebook!\n" # check if jupyter-notebook is installed if ! command -v jupyter-notebook > /dev/null 2>&1; then # install jupyter-notebook - # check if python3 pip is installed - if ! command -v pip3 > /dev/null 2>&1; then - echo "pip3 is not installed" - echo "Please install pip3 in your Dockerfile/VM image before running this script" + # check if pipx is installed + if ! command -v pipx > /dev/null 2>&1; then + echo "pipx is not installed" + echo "Please install pipx in your Dockerfile/VM image before using this module" exit 1 fi - # install jupyter-notebook - pip3 install --upgrade --no-cache-dir --no-warn-script-location jupyter + # install jupyter notebook + pipx install -q notebook echo "🥳 jupyter-notebook has been installed\n\n" else echo "🥳 jupyter-notebook is already installed\n\n" @@ -22,4 +22,4 @@ fi echo "👷 Starting jupyter-notebook in background..." echo "check logs at ${LOG_PATH}" -$HOME/.local/bin/jupyter notebook --NotebookApp.ip='0.0.0.0' --ServerApp.port=${PORT} --no-browser --ServerApp.token='' --ServerApp.password='' > ${LOG_PATH} 2>&1 & +$HOME/.local/bin/jupyter-notebook --NotebookApp.ip='0.0.0.0' --ServerApp.port=${PORT} --no-browser --ServerApp.token='' --ServerApp.password='' > ${LOG_PATH} 2>&1 & diff --git a/jupyterlab/main.test.ts b/jupyterlab/main.test.ts index 2597dc2..122de77 100644 --- a/jupyterlab/main.test.ts +++ b/jupyterlab/main.test.ts @@ -22,7 +22,7 @@ const executeScriptInContainerWithPip = async ( }> => { const instance = findResourceInstance(state, "coder_script"); const id = await runContainer(image); - const respPip = await execContainer(id, [shell, "-c", "apk add py3-pip"]); + 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"); @@ -40,7 +40,7 @@ describe("jupyterlab", async () => { agent_id: "foo", }); - it("fails without pip3", async () => { + it("fails without pipx", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", }); @@ -48,14 +48,14 @@ describe("jupyterlab", async () => { expect(output.exitCode).toBe(1); expect(output.stdout).toEqual([ "\u001B[0;1mInstalling jupyterlab!", - "pip3 is not installed", - "Please install pip3 in your Dockerfile/VM image before running this script", + "pipx is not installed", + "Please install pipx in your Dockerfile/VM image before running this script", ]); }); - // TODO: Add faster test to run with pip3. + // TODO: Add faster test to run with pipx. // currently times out. - // it("runs with pip3", async () => { + // it("runs with pipx", async () => { // ... // const output = await executeScriptInContainerWithPip(state, "alpine"); // ... diff --git a/jupyterlab/run.sh b/jupyterlab/run.sh index b040cec..0245b06 100755 --- a/jupyterlab/run.sh +++ b/jupyterlab/run.sh @@ -7,14 +7,14 @@ printf "$${BOLD}Installing jupyterlab!\n" # check if jupyterlab is installed if ! command -v jupyterlab > /dev/null 2>&1; then # install jupyterlab - # check if python3 pip is installed - if ! command -v pip3 > /dev/null 2>&1; then - echo "pip3 is not installed" - echo "Please install pip3 in your Dockerfile/VM image before running this script" + # check if pipx is installed + if ! command -v pipx > /dev/null 2>&1; then + echo "pipx is not installed" + echo "Please install pipx in your Dockerfile/VM image before running this script" exit 1 fi # install jupyterlab - pip3 install --upgrade --no-cache-dir --no-warn-script-location jupyterlab + pipx install -q jupyterlab echo "🥳 jupyterlab has been installed\n\n" else echo "🥳 jupyterlab is already installed\n\n" @@ -22,4 +22,4 @@ fi echo "👷 Starting jupyterlab in background..." echo "check logs at ${LOG_PATH}" -$HOME/.local/bin/jupyter lab --ServerApp.ip='0.0.0.0' --ServerApp.port=${PORT} --no-browser --ServerApp.token='' --ServerApp.password='' > ${LOG_PATH} 2>&1 & +$HOME/.local/bin/jupyter-lab --ServerApp.ip='0.0.0.0' --ServerApp.port=${PORT} --no-browser --ServerApp.token='' --ServerApp.password='' > ${LOG_PATH} 2>&1 & From 7a9f553564292d479038084891d15cdabd82de62 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Tue, 24 Sep 2024 11:24:02 -0700 Subject: [PATCH 09/26] chore(cursor): update display_name to Cursor Desktop (#300) --- cursor/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cursor/main.tf b/cursor/main.tf index 4d48191..f350f94 100644 --- a/cursor/main.tf +++ b/cursor/main.tf @@ -40,7 +40,7 @@ resource "coder_app" "cursor" { external = true icon = "/icon/cursor.svg" slug = "cursor" - display_name = "Cursor IDE" + display_name = "Cursor Desktop" order = var.order url = join("", [ "cursor://coder.coder-remote/open", From 04535a9cd701a278a05ecef65b500aff8e850a29 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Tue, 24 Sep 2024 12:12:30 -0700 Subject: [PATCH 10/26] chore: add dependabot.yml (#302) --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5ace460 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" From 94e126f2484edbd7c30930e5055f37a11805654f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Sep 2024 00:16:11 +0500 Subject: [PATCH 11/26] chore(deps): bump oven-sh/setup-bun from 1 to 2 (#305) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 960cd03..2b337d8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v1 + - uses: oven-sh/setup-bun@v2 with: bun-version: latest - name: Setup @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v1 + - uses: oven-sh/setup-bun@v2 with: bun-version: latest - name: Setup From ad1189afffa0acc892362b89532f4a206ef9e82c Mon Sep 17 00:00:00 2001 From: Brent Souza Date: Fri, 27 Sep 2024 03:02:57 -0400 Subject: [PATCH 12/26] feat(jfrog): support multiple repositories (#289) Co-authored-by: bsouza Co-authored-by: Muhammad Atif Ali --- jfrog-oauth/.npmrc.tftpl | 5 ++ jfrog-oauth/README.md | 21 +++--- jfrog-oauth/main.test.ts | 128 ++++++++++++++++++++++++++++++++--- jfrog-oauth/main.tf | 88 ++++++++++++++++-------- jfrog-oauth/pip.conf.tftpl | 6 ++ jfrog-oauth/run.sh | 61 ++++++++++------- jfrog-token/.npmrc.tftpl | 5 ++ jfrog-token/README.md | 31 +++++---- jfrog-token/main.test.ts | 135 +++++++++++++++++++++++++++++++++++-- jfrog-token/main.tf | 88 ++++++++++++++++-------- jfrog-token/pip.conf.tftpl | 6 ++ jfrog-token/run.sh | 62 +++++++++-------- terraform_validate.sh | 26 +++---- test.ts | 14 ++-- 14 files changed, 508 insertions(+), 168 deletions(-) create mode 100644 jfrog-oauth/.npmrc.tftpl create mode 100644 jfrog-oauth/pip.conf.tftpl create mode 100644 jfrog-token/.npmrc.tftpl create mode 100644 jfrog-token/pip.conf.tftpl diff --git a/jfrog-oauth/.npmrc.tftpl b/jfrog-oauth/.npmrc.tftpl new file mode 100644 index 0000000..8bb9fb8 --- /dev/null +++ b/jfrog-oauth/.npmrc.tftpl @@ -0,0 +1,5 @@ +email=${ARTIFACTORY_EMAIL} +%{ for REPO in REPOS ~} +${REPO.SCOPE}registry=${JFROG_URL}/artifactory/api/npm/${REPO.NAME} +//${JFROG_HOST}/artifactory/api/npm/${REPO.NAME}/:_authToken=${ARTIFACTORY_ACCESS_TOKEN} +%{ endfor ~} diff --git a/jfrog-oauth/README.md b/jfrog-oauth/README.md index b7f9d58..4423a74 100644 --- a/jfrog-oauth/README.md +++ b/jfrog-oauth/README.md @@ -17,15 +17,16 @@ Install the JF CLI and authenticate package managers with Artifactory using OAut ```tf module "jfrog" { source = "registry.coder.com/modules/jfrog-oauth/coder" - version = "1.0.15" + version = "1.0.19" agent_id = coder_agent.example.id jfrog_url = "https://example.jfrog.io" username_field = "username" # If you are using GitHub to login to both Coder and Artifactory, use username_field = "username" package_managers = { - "npm" : "npm", - "go" : "go", - "pypi" : "pypi" + npm = ["npm", "@scoped:npm-scoped"] + go = ["go", "another-go-repo"] + pypi = ["pypi", "extra-index-pypi"] + docker = ["example-docker-staging.jfrog.io", "example-docker-production.jfrog.io"] } } ``` @@ -44,13 +45,13 @@ Configure the Python pip package manager to fetch packages from Artifactory whil ```tf module "jfrog" { source = "registry.coder.com/modules/jfrog-oauth/coder" - version = "1.0.15" + version = "1.0.19" agent_id = coder_agent.example.id jfrog_url = "https://example.jfrog.io" username_field = "email" package_managers = { - "pypi" : "pypi" + pypi = ["pypi"] } } ``` @@ -72,15 +73,15 @@ The [JFrog extension](https://open-vsx.org/extension/JFrog/jfrog-vscode-extensio ```tf module "jfrog" { source = "registry.coder.com/modules/jfrog-oauth/coder" - version = "1.0.15" + version = "1.0.19" agent_id = coder_agent.example.id jfrog_url = "https://example.jfrog.io" username_field = "username" # If you are using GitHub to login to both Coder and Artifactory, use username_field = "username" configure_code_server = true # Add JFrog extension configuration for code-server package_managers = { - "npm" : "npm", - "go" : "go", - "pypi" : "pypi" + npm = ["npm"] + go = ["go"] + pypi = ["pypi"] } } ``` diff --git a/jfrog-oauth/main.test.ts b/jfrog-oauth/main.test.ts index 3397eeb..da8b9bf 100644 --- a/jfrog-oauth/main.test.ts +++ b/jfrog-oauth/main.test.ts @@ -1,19 +1,129 @@ -import { serve } from "bun"; -import { describe } from "bun:test"; +import { describe, expect, it } from "bun:test"; import { - createJSONResponse, + findResourceInstance, runTerraformInit, + runTerraformApply, testRequiredVariables, } from "../test"; describe("jfrog-oauth", async () => { + type TestVariables = { + agent_id: string; + jfrog_url: string; + package_managers: string; + + username_field?: string; + jfrog_server_id?: string; + external_auth_id?: string; + configure_code_server?: boolean; + }; + await runTerraformInit(import.meta.dir); - testRequiredVariables(import.meta.dir, { - agent_id: "some-agent-id", - jfrog_url: "http://localhost:8081", - package_managers: "{}", + const fakeFrogApi = "localhost:8081/artifactory/api"; + const fakeFrogUrl = "http://localhost:8081"; + const user = "default"; + + it("can run apply with required variables", async () => { + testRequiredVariables(import.meta.dir, { + agent_id: "some-agent-id", + jfrog_url: fakeFrogUrl, + package_managers: "{}", + }); }); -}); -//TODO add more tests + it("generates an npmrc with scoped repos", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "some-agent-id", + jfrog_url: fakeFrogUrl, + package_managers: JSON.stringify({ + npm: ["global", "@foo:foo", "@bar:bar"], + }), + }); + const coderScript = findResourceInstance(state, "coder_script"); + const npmrcStanza = `cat << EOF > ~/.npmrc +email=${user}@example.com +registry=http://${fakeFrogApi}/npm/global +//${fakeFrogApi}/npm/global/:_authToken= +@foo:registry=http://${fakeFrogApi}/npm/foo +//${fakeFrogApi}/npm/foo/:_authToken= +@bar:registry=http://${fakeFrogApi}/npm/bar +//${fakeFrogApi}/npm/bar/:_authToken= + +EOF`; + expect(coderScript.script).toContain(npmrcStanza); + expect(coderScript.script).toContain( + 'jf npmc --global --repo-resolve "global"', + ); + expect(coderScript.script).toContain( + 'if [ -z "YES" ]; then\n not_configured npm', + ); + }); + + it("generates a pip config with extra-indexes", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "some-agent-id", + jfrog_url: fakeFrogUrl, + package_managers: JSON.stringify({ + pypi: ["global", "foo", "bar"], + }), + }); + const coderScript = findResourceInstance(state, "coder_script"); + const pipStanza = `cat << EOF > ~/.pip/pip.conf +[global] +index-url = https://${user}:@${fakeFrogApi}/pypi/global/simple +extra-index-url = + https://${user}:@${fakeFrogApi}/pypi/foo/simple + https://${user}:@${fakeFrogApi}/pypi/bar/simple + +EOF`; + expect(coderScript.script).toContain(pipStanza); + expect(coderScript.script).toContain( + 'jf pipc --global --repo-resolve "global"', + ); + expect(coderScript.script).toContain( + 'if [ -z "YES" ]; then\n not_configured pypi', + ); + }); + + it("registers multiple docker repos", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "some-agent-id", + jfrog_url: fakeFrogUrl, + package_managers: JSON.stringify({ + docker: ["foo.jfrog.io", "bar.jfrog.io", "baz.jfrog.io"], + }), + }); + const coderScript = findResourceInstance(state, "coder_script"); + const dockerStanza = ["foo", "bar", "baz"] + .map((r) => `register_docker "${r}.jfrog.io"`) + .join("\n"); + expect(coderScript.script).toContain(dockerStanza); + expect(coderScript.script).toContain( + 'if [ -z "YES" ]; then\n not_configured docker', + ); + }); + + it("sets goproxy with multiple repos", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "some-agent-id", + jfrog_url: fakeFrogUrl, + package_managers: JSON.stringify({ + go: ["foo", "bar", "baz"], + }), + }); + const proxyEnv = findResourceInstance(state, "coder_env", "goproxy"); + const proxies = ["foo", "bar", "baz"] + .map((r) => `https://${user}:@${fakeFrogApi}/go/${r}`) + .join(","); + expect(proxyEnv["value"]).toEqual(proxies); + + const coderScript = findResourceInstance(state, "coder_script"); + expect(coderScript.script).toContain( + 'jf goc --global --repo-resolve "foo"', + ); + expect(coderScript.script).toContain( + 'if [ -z "YES" ]; then\n not_configured go', + ); + }); +}); diff --git a/jfrog-oauth/main.tf b/jfrog-oauth/main.tf index 767235a..0bc2256 100644 --- a/jfrog-oauth/main.tf +++ b/jfrog-oauth/main.tf @@ -53,23 +53,51 @@ variable "configure_code_server" { } variable "package_managers" { - type = map(string) - description = < /dev/null 2>&1; then echo "✅ JFrog CLI is already installed, skipping installation." @@ -20,52 +35,47 @@ echo "${ARTIFACTORY_ACCESS_TOKEN}" | jf c add --access-token-stdin --url "${JFRO jf c use "${JFROG_SERVER_ID}" # Configure npm to use the Artifactory "npm" repository. -if [ -z "${REPOSITORY_NPM}" ]; then - echo "🤔 no npm repository is set, skipping npm configuration." - echo "You can configure an npm repository by providing the a key for 'npm' in the 'package_managers' input." +if [ -z "${HAS_NPM}" ]; then + not_configured npm else echo "📦 Configuring npm..." jf npmc --global --repo-resolve "${REPOSITORY_NPM}" cat << EOF > ~/.npmrc -email=${ARTIFACTORY_EMAIL} -registry=${JFROG_URL}/artifactory/api/npm/${REPOSITORY_NPM} +${NPMRC} EOF - echo "//${JFROG_HOST}/artifactory/api/npm/${REPOSITORY_NPM}/:_authToken=${ARTIFACTORY_ACCESS_TOKEN}" >> ~/.npmrc + config_complete fi # Configure the `pip` to use the Artifactory "python" repository. -if [ -z "${REPOSITORY_PYPI}" ]; then - echo "🤔 no pypi repository is set, skipping pip configuration." - echo "You can configure a pypi repository by providing the a key for 'pypi' in the 'package_managers' input." +if [ -z "${HAS_PYPI}" ]; then + not_configured pypi else - echo "📦 Configuring pip..." + echo "🐍 Configuring pip..." jf pipc --global --repo-resolve "${REPOSITORY_PYPI}" mkdir -p ~/.pip cat << EOF > ~/.pip/pip.conf -[global] -index-url = https://${ARTIFACTORY_USERNAME}:${ARTIFACTORY_ACCESS_TOKEN}@${JFROG_HOST}/artifactory/api/pypi/${REPOSITORY_PYPI}/simple +${PIP_CONF} EOF + config_complete fi # Configure Artifactory "go" repository. -if [ -z "${REPOSITORY_GO}" ]; then - echo "🤔 no go repository is set, skipping go configuration." - echo "You can configure a go repository by providing the a key for 'go' in the 'package_managers' input." +if [ -z "${HAS_GO}" ]; then + not_configured go else echo "🐹 Configuring go..." jf goc --global --repo-resolve "${REPOSITORY_GO}" + config_complete fi -echo "🥳 Configuration complete!" # Configure the JFrog CLI to use the Artifactory "docker" repository. -if [ -z "${REPOSITORY_DOCKER}" ]; then - echo "🤔 no docker repository is set, skipping docker configuration." - echo "You can configure a docker repository by providing the a key for 'docker' in the 'package_managers' input." +if [ -z "${HAS_DOCKER}" ]; then + not_configured docker else if command -v docker > /dev/null 2>&1; then echo "🔑 Configuring 🐳 docker credentials..." mkdir -p ~/.docker - echo -n "${ARTIFACTORY_ACCESS_TOKEN}" | docker login ${JFROG_HOST} --username ${ARTIFACTORY_USERNAME} --password-stdin + ${REGISTER_DOCKER} else echo "🤔 no docker is installed, skipping docker configuration." fi @@ -96,20 +106,19 @@ echo "📦 Configuring JFrog CLI completion..." SHELLNAME=$(grep "^$USER" /etc/passwd | awk -F':' '{print $7}' | awk -F'/' '{print $NF}') # Generate the completion script jf completion $SHELLNAME --install +begin_stanza="# BEGIN: jf CLI shell completion (added by coder module jfrog-oauth)" # Add the completion script to the user's shell profile if [ "$SHELLNAME" == "bash" ] && [ -f ~/.bashrc ]; then - if ! grep -q "# jf CLI shell completion" ~/.bashrc; then - echo "" >> ~/.bashrc - echo "# BEGIN: jf CLI shell completion (added by coder module jfrog-oauth)" >> ~/.bashrc + if ! grep -q "$begin_stanza" ~/.bashrc; then + printf "%s\n" "$begin_stanza" >> ~/.bashrc echo 'source "$HOME/.jfrog/jfrog_bash_completion"' >> ~/.bashrc echo "# END: jf CLI shell completion" >> ~/.bashrc else echo "🥳 ~/.bashrc already contains jf CLI shell completion configuration, skipping." fi elif [ "$SHELLNAME" == "zsh" ] && [ -f ~/.zshrc ]; then - if ! grep -q "# jf CLI shell completion" ~/.zshrc; then - echo "" >> ~/.zshrc - echo "# BEGIN: jf CLI shell completion (added by coder module jfrog-oauth)" >> ~/.zshrc + if ! grep -q "$begin_stanza" ~/.zshrc; then + printf "\n%s\n" "$begin_stanza" >> ~/.zshrc echo "autoload -Uz compinit" >> ~/.zshrc echo "compinit" >> ~/.zshrc echo 'source "$HOME/.jfrog/jfrog_zsh_completion"' >> ~/.zshrc diff --git a/jfrog-token/.npmrc.tftpl b/jfrog-token/.npmrc.tftpl new file mode 100644 index 0000000..8bb9fb8 --- /dev/null +++ b/jfrog-token/.npmrc.tftpl @@ -0,0 +1,5 @@ +email=${ARTIFACTORY_EMAIL} +%{ for REPO in REPOS ~} +${REPO.SCOPE}registry=${JFROG_URL}/artifactory/api/npm/${REPO.NAME} +//${JFROG_HOST}/artifactory/api/npm/${REPO.NAME}/:_authToken=${ARTIFACTORY_ACCESS_TOKEN} +%{ endfor ~} diff --git a/jfrog-token/README.md b/jfrog-token/README.md index f903f90..146dc7f 100644 --- a/jfrog-token/README.md +++ b/jfrog-token/README.md @@ -15,14 +15,15 @@ Install the JF CLI and authenticate package managers with Artifactory using Arti ```tf module "jfrog" { source = "registry.coder.com/modules/jfrog-token/coder" - version = "1.0.15" + version = "1.0.19" agent_id = coder_agent.example.id jfrog_url = "https://XXXX.jfrog.io" artifactory_access_token = var.artifactory_access_token package_managers = { - "npm" : "npm", - "go" : "go", - "pypi" : "pypi" + npm = ["npm", "@scoped:npm-scoped"] + go = ["go", "another-go-repo"] + pypi = ["pypi", "extra-index-pypi"] + docker = ["example-docker-staging.jfrog.io", "example-docker-production.jfrog.io"] } } ``` @@ -41,14 +42,14 @@ For detailed instructions, please see this [guide](https://coder.com/docs/v2/lat ```tf module "jfrog" { source = "registry.coder.com/modules/jfrog-token/coder" - version = "1.0.15" + version = "1.0.19" agent_id = coder_agent.example.id jfrog_url = "https://YYYY.jfrog.io" artifactory_access_token = var.artifactory_access_token # An admin access token package_managers = { - "npm" : "npm-local", - "go" : "go-local", - "pypi" : "pypi-local" + npm = ["npm-local"] + go = ["go-local"] + pypi = ["pypi-local"] } } ``` @@ -74,15 +75,15 @@ The [JFrog extension](https://open-vsx.org/extension/JFrog/jfrog-vscode-extensio ```tf module "jfrog" { source = "registry.coder.com/modules/jfrog-token/coder" - version = "1.0.15" + version = "1.0.19" agent_id = coder_agent.example.id jfrog_url = "https://XXXX.jfrog.io" artifactory_access_token = var.artifactory_access_token configure_code_server = true # Add JFrog extension configuration for code-server package_managers = { - "npm" : "npm", - "go" : "go", - "pypi" : "pypi" + npm = ["npm"] + go = ["go"] + pypi = ["pypi"] } } ``` @@ -94,15 +95,13 @@ data "coder_workspace" "me" {} module "jfrog" { source = "registry.coder.com/modules/jfrog-token/coder" - version = "1.0.15" + version = "1.0.19" agent_id = coder_agent.example.id jfrog_url = "https://XXXX.jfrog.io" artifactory_access_token = var.artifactory_access_token token_description = "Token for Coder workspace: ${data.coder_workspace_owner.me.name}/${data.coder_workspace.me.name}" package_managers = { - "npm" : "npm", - "go" : "go", - "pypi" : "pypi" + npm = ["npm"] } } ``` diff --git a/jfrog-token/main.test.ts b/jfrog-token/main.test.ts index b3b8df9..a6fe6bc 100644 --- a/jfrog-token/main.test.ts +++ b/jfrog-token/main.test.ts @@ -1,12 +1,29 @@ import { serve } from "bun"; -import { describe } from "bun:test"; +import { describe, expect, it } from "bun:test"; import { createJSONResponse, + findResourceInstance, runTerraformInit, + runTerraformApply, testRequiredVariables, } from "../test"; describe("jfrog-token", async () => { + type TestVariables = { + agent_id: string; + jfrog_url: string; + artifactory_access_token: string; + package_managers: string; + + token_description?: string; + check_license?: boolean; + refreshable?: boolean; + expires_in?: number; + username_field?: string; + jfrog_server_id?: string; + configure_code_server?: boolean; + }; + await runTerraformInit(import.meta.dir); // Run a fake JFrog server so the provider can initialize @@ -32,10 +49,116 @@ describe("jfrog-token", async () => { port: 0, }); - testRequiredVariables(import.meta.dir, { - agent_id: "some-agent-id", - jfrog_url: "http://" + fakeFrogHost.hostname + ":" + fakeFrogHost.port, - artifactory_access_token: "XXXX", - package_managers: "{}", + const fakeFrogApi = `${fakeFrogHost.hostname}:${fakeFrogHost.port}/artifactory/api`; + const fakeFrogUrl = `http://${fakeFrogHost.hostname}:${fakeFrogHost.port}`; + const user = "default"; + const token = "xxx"; + + it("can run apply with required variables", async () => { + testRequiredVariables(import.meta.dir, { + agent_id: "some-agent-id", + jfrog_url: fakeFrogUrl, + artifactory_access_token: "XXXX", + package_managers: "{}", + }); + }); + + it("generates an npmrc with scoped repos", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "some-agent-id", + jfrog_url: fakeFrogUrl, + artifactory_access_token: "XXXX", + package_managers: JSON.stringify({ + npm: ["global", "@foo:foo", "@bar:bar"], + }), + }); + const coderScript = findResourceInstance(state, "coder_script"); + const npmrcStanza = `cat << EOF > ~/.npmrc +email=${user}@example.com +registry=http://${fakeFrogApi}/npm/global +//${fakeFrogApi}/npm/global/:_authToken=xxx +@foo:registry=http://${fakeFrogApi}/npm/foo +//${fakeFrogApi}/npm/foo/:_authToken=xxx +@bar:registry=http://${fakeFrogApi}/npm/bar +//${fakeFrogApi}/npm/bar/:_authToken=xxx + +EOF`; + expect(coderScript.script).toContain(npmrcStanza); + expect(coderScript.script).toContain( + 'jf npmc --global --repo-resolve "global"', + ); + expect(coderScript.script).toContain( + 'if [ -z "YES" ]; then\n not_configured npm', + ); + }); + + it("generates a pip config with extra-indexes", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "some-agent-id", + jfrog_url: fakeFrogUrl, + artifactory_access_token: "XXXX", + package_managers: JSON.stringify({ + pypi: ["global", "foo", "bar"], + }), + }); + const coderScript = findResourceInstance(state, "coder_script"); + const pipStanza = `cat << EOF > ~/.pip/pip.conf +[global] +index-url = https://${user}:${token}@${fakeFrogApi}/pypi/global/simple +extra-index-url = + https://${user}:${token}@${fakeFrogApi}/pypi/foo/simple + https://${user}:${token}@${fakeFrogApi}/pypi/bar/simple + +EOF`; + expect(coderScript.script).toContain(pipStanza); + expect(coderScript.script).toContain( + 'jf pipc --global --repo-resolve "global"', + ); + expect(coderScript.script).toContain( + 'if [ -z "YES" ]; then\n not_configured pypi', + ); + }); + + it("registers multiple docker repos", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "some-agent-id", + jfrog_url: fakeFrogUrl, + artifactory_access_token: "XXXX", + package_managers: JSON.stringify({ + docker: ["foo.jfrog.io", "bar.jfrog.io", "baz.jfrog.io"], + }), + }); + const coderScript = findResourceInstance(state, "coder_script"); + const dockerStanza = ["foo", "bar", "baz"] + .map((r) => `register_docker "${r}.jfrog.io"`) + .join("\n"); + expect(coderScript.script).toContain(dockerStanza); + expect(coderScript.script).toContain( + 'if [ -z "YES" ]; then\n not_configured docker', + ); + }); + + it("sets goproxy with multiple repos", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "some-agent-id", + jfrog_url: fakeFrogUrl, + artifactory_access_token: "XXXX", + package_managers: JSON.stringify({ + go: ["foo", "bar", "baz"], + }), + }); + const proxyEnv = findResourceInstance(state, "coder_env", "goproxy"); + const proxies = ["foo", "bar", "baz"] + .map((r) => `https://${user}:${token}@${fakeFrogApi}/go/${r}`) + .join(","); + expect(proxyEnv["value"]).toEqual(proxies); + + const coderScript = findResourceInstance(state, "coder_script"); + expect(coderScript.script).toContain( + 'jf goc --global --repo-resolve "foo"', + ); + expect(coderScript.script).toContain( + 'if [ -z "YES" ]; then\n not_configured go', + ); }); }); diff --git a/jfrog-token/main.tf b/jfrog-token/main.tf index 90dad61..f6f5f5b 100644 --- a/jfrog-token/main.tf +++ b/jfrog-token/main.tf @@ -80,23 +80,51 @@ variable "configure_code_server" { } variable "package_managers" { - type = map(string) - description = < /dev/null 2>&1; then echo "✅ JFrog CLI is already installed, skipping installation." @@ -11,8 +26,7 @@ else sudo chmod 755 /usr/local/bin/jf fi -# The jf CLI checks $CI when determining whether to use interactive -# flows. +# The jf CLI checks $CI when determining whether to use interactive flows. export CI=true # Authenticate JFrog CLI with Artifactory. echo "${ARTIFACTORY_ACCESS_TOKEN}" | jf c add --access-token-stdin --url "${JFROG_URL}" --overwrite "${JFROG_SERVER_ID}" @@ -20,52 +34,47 @@ echo "${ARTIFACTORY_ACCESS_TOKEN}" | jf c add --access-token-stdin --url "${JFRO jf c use "${JFROG_SERVER_ID}" # Configure npm to use the Artifactory "npm" repository. -if [ -z "${REPOSITORY_NPM}" ]; then - echo "🤔 no npm repository is set, skipping npm configuration." - echo "You can configure an npm repository by providing the a key for 'npm' in the 'package_managers' input." +if [ -z "${HAS_NPM}" ]; then + not_configured npm else echo "📦 Configuring npm..." jf npmc --global --repo-resolve "${REPOSITORY_NPM}" cat << EOF > ~/.npmrc -email=${ARTIFACTORY_EMAIL} -registry=${JFROG_URL}/artifactory/api/npm/${REPOSITORY_NPM} +${NPMRC} EOF - echo "//${JFROG_HOST}/artifactory/api/npm/${REPOSITORY_NPM}/:_authToken=${ARTIFACTORY_ACCESS_TOKEN}" >> ~/.npmrc + config_complete fi # Configure the `pip` to use the Artifactory "python" repository. -if [ -z "${REPOSITORY_PYPI}" ]; then - echo "🤔 no pypi repository is set, skipping pip configuration." - echo "You can configure a pypi repository by providing the a key for 'pypi' in the 'package_managers' input." +if [ -z "${HAS_PYPI}" ]; then + not_configured pypi else echo "🐍 Configuring pip..." jf pipc --global --repo-resolve "${REPOSITORY_PYPI}" mkdir -p ~/.pip cat << EOF > ~/.pip/pip.conf -[global] -index-url = https://${ARTIFACTORY_USERNAME}:${ARTIFACTORY_ACCESS_TOKEN}@${JFROG_HOST}/artifactory/api/pypi/${REPOSITORY_PYPI}/simple +${PIP_CONF} EOF + config_complete fi # Configure Artifactory "go" repository. -if [ -z "${REPOSITORY_GO}" ]; then - echo "🤔 no go repository is set, skipping go configuration." - echo "You can configure a go repository by providing the a key for 'go' in the 'package_managers' input." +if [ -z "${HAS_GO}" ]; then + not_configured go else echo "🐹 Configuring go..." jf goc --global --repo-resolve "${REPOSITORY_GO}" + config_complete fi -echo "🥳 Configuration complete!" # Configure the JFrog CLI to use the Artifactory "docker" repository. -if [ -z "${REPOSITORY_DOCKER}" ]; then - echo "🤔 no docker repository is set, skipping docker configuration." - echo "You can configure a docker repository by providing the a key for 'docker' in the 'package_managers' input." +if [ -z "${HAS_DOCKER}" ]; then + not_configured docker else if command -v docker > /dev/null 2>&1; then echo "🔑 Configuring 🐳 docker credentials..." mkdir -p ~/.docker - echo -n "${ARTIFACTORY_ACCESS_TOKEN}" | docker login ${JFROG_HOST} --username ${ARTIFACTORY_USERNAME} --password-stdin + ${REGISTER_DOCKER} else echo "🤔 no docker is installed, skipping docker configuration." fi @@ -96,20 +105,19 @@ echo "📦 Configuring JFrog CLI completion..." SHELLNAME=$(grep "^$USER" /etc/passwd | awk -F':' '{print $7}' | awk -F'/' '{print $NF}') # Generate the completion script jf completion $SHELLNAME --install +begin_stanza="# BEGIN: jf CLI shell completion (added by coder module jfrog-token)" # Add the completion script to the user's shell profile if [ "$SHELLNAME" == "bash" ] && [ -f ~/.bashrc ]; then - if ! grep -q "# jf CLI shell completion" ~/.bashrc; then - echo "" >> ~/.bashrc - echo "# BEGIN: jf CLI shell completion (added by coder module jfrog-token)" >> ~/.bashrc + if ! grep -q "$begin_stanza" ~/.bashrc; then + printf "%s\n" "$begin_stanza" >> ~/.bashrc echo 'source "$HOME/.jfrog/jfrog_bash_completion"' >> ~/.bashrc echo "# END: jf CLI shell completion" >> ~/.bashrc else echo "🥳 ~/.bashrc already contains jf CLI shell completion configuration, skipping." fi elif [ "$SHELLNAME" == "zsh" ] && [ -f ~/.zshrc ]; then - if ! grep -q "# jf CLI shell completion" ~/.zshrc; then - echo "" >> ~/.zshrc - echo "# BEGIN: jf CLI shell completion (added by coder module jfrog-token)" >> ~/.zshrc + if ! grep -q "$begin_stanza" ~/.zshrc; then + printf "\n%s\n" "$begin_stanza" >> ~/.zshrc echo "autoload -Uz compinit" >> ~/.zshrc echo "compinit" >> ~/.zshrc echo 'source "$HOME/.jfrog/jfrog_zsh_completion"' >> ~/.zshrc diff --git a/terraform_validate.sh b/terraform_validate.sh index 292c94c..492e65a 100755 --- a/terraform_validate.sh +++ b/terraform_validate.sh @@ -4,25 +4,25 @@ set -euo pipefail # Function to run terraform init and validate in a directory run_terraform() { - local dir="$1" - echo "Running terraform init and validate in $dir" - pushd "$dir" - terraform init -upgrade - terraform validate - popd + local dir="$1" + echo "Running terraform init and validate in $dir" + pushd "$dir" + terraform init -upgrade + terraform validate + popd } # Main script main() { - # Get the directory of the script - script_dir=$(dirname "$(readlink -f "$0")") + # Get the directory of the script + script_dir=$(dirname "$(readlink -f "$0")") - # Get all subdirectories in the repository - subdirs=$(find "$script_dir" -mindepth 1 -maxdepth 1 -type d -not -name ".*" | sort) + # Get all subdirectories in the repository + subdirs=$(find "$script_dir" -mindepth 1 -maxdepth 1 -type d -not -name ".*" | sort) - for dir in $subdirs; do - run_terraform "$dir" - done + for dir in $subdirs; do + run_terraform "$dir" + done } # Run the main script diff --git a/test.ts b/test.ts index 6bdf9d9..dd1b1c6 100644 --- a/test.ts +++ b/test.ts @@ -108,6 +108,8 @@ export interface TerraformState { resources: [TerraformStateResource, ...TerraformStateResource[]]; } +type TerraformVariables = Record; + export interface CoderScriptAttributes { script: string; agent_id: string; @@ -145,9 +147,9 @@ export const findResourceInstance = ( * Creates a test-case for each variable provided and ensures that the apply * fails without it. */ -export const testRequiredVariables = >( +export const testRequiredVariables = ( dir: string, - vars: TVars, + vars: Readonly, ) => { // Ensures that all required variables are provided. it("required variables", async () => { @@ -158,7 +160,7 @@ export const testRequiredVariables = >( varNames.forEach((varName) => { // Ensures that every variable provided is required! it("missing variable " + varName, async () => { - const localVars: Record = {}; + const localVars: TerraformVariables = {}; varNames.forEach((otherVarName) => { if (otherVarName !== varName) { localVars[otherVarName] = vars[otherVarName]; @@ -187,11 +189,9 @@ export const testRequiredVariables = >( * fine to run in parallel with other instances of this function, as it uses a * random state file. */ -export const runTerraformApply = async < - TVars extends Readonly>, ->( +export const runTerraformApply = async ( dir: string, - vars: TVars, + vars: Readonly, env?: Record, ): Promise => { const stateFile = `${dir}/${crypto.randomUUID()}.tfstate`; From 162808760d73a8b313a0fd13ad78a23f253e7260 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Fri, 27 Sep 2024 08:07:49 -0700 Subject: [PATCH 13/26] fix(filebrowser): only require agent_name when not on subdomain (#299) --- filebrowser/README.md | 17 +++++++---------- filebrowser/main.test.ts | 5 ----- filebrowser/main.tf | 6 ++++++ 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/filebrowser/README.md b/filebrowser/README.md index dd26d27..e92560b 100644 --- a/filebrowser/README.md +++ b/filebrowser/README.md @@ -13,10 +13,9 @@ A file browser for your workspace. ```tf module "filebrowser" { - source = "registry.coder.com/modules/filebrowser/coder" - version = "1.0.18" - agent_id = coder_agent.example.id - agent_name = "main" + source = "registry.coder.com/modules/filebrowser/coder" + version = "1.0.18" + agent_id = coder_agent.example.id } ``` @@ -28,11 +27,10 @@ module "filebrowser" { ```tf module "filebrowser" { - source = "registry.coder.com/modules/filebrowser/coder" - version = "1.0.18" - agent_id = coder_agent.example.id - agent_name = "main" - folder = "/home/coder/project" + source = "registry.coder.com/modules/filebrowser/coder" + version = "1.0.18" + agent_id = coder_agent.example.id + folder = "/home/coder/project" } ``` @@ -43,7 +41,6 @@ module "filebrowser" { source = "registry.coder.com/modules/filebrowser/coder" version = "1.0.18" agent_id = coder_agent.example.id - agent_name = "main" database_path = ".config/filebrowser.db" } ``` diff --git a/filebrowser/main.test.ts b/filebrowser/main.test.ts index ff6d045..7dd4972 100644 --- a/filebrowser/main.test.ts +++ b/filebrowser/main.test.ts @@ -11,13 +11,11 @@ describe("filebrowser", async () => { testRequiredVariables(import.meta.dir, { agent_id: "foo", - agent_name: "main", }); it("fails with wrong database_path", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", - agent_name: "main", database_path: "nofb", }).catch((e) => { if (!e.message.startsWith("\nError: Invalid value for variable")) { @@ -29,7 +27,6 @@ describe("filebrowser", async () => { it("runs with default", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", - agent_name: "main", }); const output = await executeScriptInContainer(state, "alpine"); expect(output.exitCode).toBe(0); @@ -51,7 +48,6 @@ describe("filebrowser", async () => { it("runs with database_path var", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", - agent_name: "main", database_path: ".config/filebrowser.db", }); const output = await executeScriptInContainer(state, "alpine"); @@ -74,7 +70,6 @@ describe("filebrowser", async () => { it("runs with folder var", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", - agent_name: "main", folder: "/home/coder/project", }); const output = await executeScriptInContainer(state, "alpine"); diff --git a/filebrowser/main.tf b/filebrowser/main.tf index e6b88c6..4fd7459 100644 --- a/filebrowser/main.tf +++ b/filebrowser/main.tf @@ -21,6 +21,12 @@ data "coder_workspace_owner" "me" {} variable "agent_name" { type = string description = "The name of the main deployment. (Used to build the subpath for coder_app.)" + default = "" + validation { + # If subdomain is false, then agent_name must be set. + condition = var.subdomain || var.agent_name != "" + error_message = "The agent_name must be set." + } } variable "database_path" { From fb81c8969f36bd4427b68a74d1ad636517c01160 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Fri, 27 Sep 2024 11:20:57 -0700 Subject: [PATCH 14/26] feat(vault-jwt): Add Vault JWT/OIDC module (#297) Co-authored-by: Mathias Fredriksson --- tsconfig.json | 2 +- vault-jwt/README.md | 77 ++++++++++++++++++++++++++++ vault-jwt/main.test.ts | 12 +++++ vault-jwt/main.tf | 64 +++++++++++++++++++++++ vault-jwt/run.sh | 112 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 vault-jwt/README.md create mode 100644 vault-jwt/main.test.ts create mode 100644 vault-jwt/main.tf create mode 100644 vault-jwt/run.sh diff --git a/tsconfig.json b/tsconfig.json index dd38e58..5de8ae3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "target": "esnext", - "module": "esnext", + "module": "nodenext", "strict": true, "allowSyntheticDefaultImports": true, "moduleResolution": "nodenext", diff --git a/vault-jwt/README.md b/vault-jwt/README.md new file mode 100644 index 0000000..67aa7e5 --- /dev/null +++ b/vault-jwt/README.md @@ -0,0 +1,77 @@ +--- +display_name: Hashicorp Vault Integration (JWT) +description: Authenticates with Vault using a JWT from Coder's OIDC provider +icon: ../.icons/vault.svg +maintainer_github: coder +partner_github: hashicorp +verified: true +tags: [helper, integration, vault, jwt, oidc] +--- + +# Hashicorp Vault Integration (JWT) + +This module lets you authenticate with [Hashicorp Vault](https://www.vaultproject.io/) in your Coder workspaces by reusing the [OIDC](https://coder.com/docs/admin/auth#openid-connect) access token from Coder's OIDC authentication method. This requires configuring the Vault [JWT/OIDC](https://developer.hashicorp.com/vault/docs/auth/jwt#configuration) auth method. + +```tf +module "vault" { + source = "registry.coder.com/modules/vault-jwt/coder" + version = "1.0.19" + agent_id = coder_agent.example.id + vault_addr = "https://vault.example.com" + vault_jwt_role = "coder" # The Vault role to use for authentication +} +``` + +Then you can use the Vault CLI in your workspaces to fetch secrets from Vault: + +```shell +vault kv get -namespace=coder -mount=secrets coder +``` + +or using the Vault API: + +```shell +curl -H "X-Vault-Token: ${VAULT_TOKEN}" -X GET "${VAULT_ADDR}/v1/coder/secrets/data/coder" +``` + +## Examples + +### Configure Vault integration with a non standard auth path (default is "jwt") + +```tf +module "vault" { + source = "registry.coder.com/modules/vault-jwt/coder" + version = "1.0.19" + agent_id = coder_agent.example.id + vault_addr = "https://vault.example.com" + vault_jwt_auth_path = "oidc" + vault_jwt_role = "coder" # The Vault role to use for authentication +} +``` + +### Map workspace owner's group to a Vault role + +```tf +data "coder_workspace_owner" "me" {} + +module "vault" { + source = "registry.coder.com/modules/vault-jwt/coder" + version = "1.0.19" + agent_id = coder_agent.example.id + vault_addr = "https://vault.example.com" + vault_jwt_role = data.coder_workspace_owner.me.groups[0] +} +``` + +### Install a specific version of the Vault CLI + +```tf +module "vault" { + source = "registry.coder.com/modules/vault-jwt/coder" + version = "1.0.19" + agent_id = coder_agent.example.id + vault_addr = "https://vault.example.com" + vault_jwt_role = "coder" # The Vault role to use for authentication + vault_cli_version = "1.17.5" +} +``` diff --git a/vault-jwt/main.test.ts b/vault-jwt/main.test.ts new file mode 100644 index 0000000..2fda3d7 --- /dev/null +++ b/vault-jwt/main.test.ts @@ -0,0 +1,12 @@ +import { describe } from "bun:test"; +import { runTerraformInit, testRequiredVariables } from "../test"; + +describe("vault-jwt", async () => { + await runTerraformInit(import.meta.dir); + + testRequiredVariables(import.meta.dir, { + agent_id: "foo", + vault_addr: "foo", + vault_jwt_role: "foo", + }); +}); diff --git a/vault-jwt/main.tf b/vault-jwt/main.tf new file mode 100644 index 0000000..adcc34d --- /dev/null +++ b/vault-jwt/main.tf @@ -0,0 +1,64 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + coder = { + source = "coder/coder" + version = ">= 0.12.4" + } + } +} + +# Add required variables for your modules and remove any unneeded variables +variable "agent_id" { + type = string + description = "The ID of a Coder agent." +} + +variable "vault_addr" { + type = string + description = "The address of the Vault server." +} + +variable "vault_jwt_auth_path" { + type = string + description = "The path to the Vault JWT auth method." + default = "jwt" +} + +variable "vault_jwt_role" { + type = string + description = "The name of the Vault role to use for authentication." +} + +variable "vault_cli_version" { + type = string + description = "The version of Vault to install." + default = "latest" + validation { + condition = can(regex("^(latest|[0-9]+\\.[0-9]+\\.[0-9]+)$", var.vault_cli_version)) + error_message = "Vault version must be in the format 0.0.0 or latest" + } +} + +resource "coder_script" "vault" { + agent_id = var.agent_id + display_name = "Vault (GitHub)" + icon = "/icon/vault.svg" + script = templatefile("${path.module}/run.sh", { + CODER_OIDC_ACCESS_TOKEN : data.coder_workspace_owner.me.oidc_access_token, + VAULT_JWT_AUTH_PATH : var.vault_jwt_auth_path, + VAULT_JWT_ROLE : var.vault_jwt_role, + VAULT_CLI_VERSION : var.vault_cli_version, + }) + run_on_start = true + start_blocks_login = true +} + +resource "coder_env" "vault_addr" { + agent_id = var.agent_id + name = "VAULT_ADDR" + value = var.vault_addr +} + +data "coder_workspace_owner" "me" {} diff --git a/vault-jwt/run.sh b/vault-jwt/run.sh new file mode 100644 index 0000000..662b378 --- /dev/null +++ b/vault-jwt/run.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash + +# Convert all templated variables to shell variables +VAULT_CLI_VERSION=${VAULT_CLI_VERSION} +VAULT_JWT_AUTH_PATH=${VAULT_JWT_AUTH_PATH} +VAULT_JWT_ROLE=${VAULT_JWT_ROLE} +CODER_OIDC_ACCESS_TOKEN=${CODER_OIDC_ACCESS_TOKEN} + +fetch() { + dest="$1" + url="$2" + if command -v curl > /dev/null 2>&1; then + curl -sSL --fail "$${url}" -o "$${dest}" + elif command -v wget > /dev/null 2>&1; then + wget -O "$${dest}" "$${url}" + elif command -v busybox > /dev/null 2>&1; then + busybox wget -O "$${dest}" "$${url}" + else + printf "curl, wget, or busybox is not installed. Please install curl or wget in your image.\n" + exit 1 + fi +} + +unzip_safe() { + if command -v unzip > /dev/null 2>&1; then + command unzip "$@" + elif command -v busybox > /dev/null 2>&1; then + busybox unzip "$@" + else + printf "unzip or busybox is not installed. Please install unzip in your image.\n" + exit 1 + fi +} + +install() { + # Get the architecture of the system + ARCH=$(uname -m) + if [ "$${ARCH}" = "x86_64" ]; then + ARCH="amd64" + elif [ "$${ARCH}" = "aarch64" ]; then + ARCH="arm64" + else + printf "Unsupported architecture: $${ARCH}\n" + return 1 + fi + # Fetch the latest version of Vault if VAULT_CLI_VERSION is 'latest' + if [ "$${VAULT_CLI_VERSION}" = "latest" ]; then + LATEST_VERSION=$(curl -s https://releases.hashicorp.com/vault/ | grep -v 'rc' | grep -oE 'vault/[0-9]+\.[0-9]+\.[0-9]+' | sed 's/vault\///' | sort -V | tail -n 1) + printf "Latest version of Vault is %s.\n\n" "$${LATEST_VERSION}" + if [ -z "$${LATEST_VERSION}" ]; then + printf "Failed to determine the latest Vault version.\n" + return 1 + fi + VAULT_CLI_VERSION=$${VAULT_CLI_VERSION} + fi + + # Check if the vault CLI is installed and has the correct version + installation_needed=1 + if command -v vault > /dev/null 2>&1; then + CURRENT_VERSION=$(vault version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') + if [ "$${CURRENT_VERSION}" = "$${VAULT_CLI_VERSION}" ]; then + printf "Vault version %s is already installed and up-to-date.\n\n" "$${CURRENT_VERSION}" + installation_needed=0 + fi + fi + + if [ $${installation_needed} -eq 1 ]; then + # Download and install Vault + if [ -z "$${CURRENT_VERSION}" ]; then + printf "Installing Vault CLI ...\n\n" + else + printf "Upgrading Vault CLI from version %s to %s ...\n\n" "$${CURRENT_VERSION}" "${VAULT_CLI_VERSION}" + fi + fetch vault.zip "https://releases.hashicorp.com/vault/$${VAULT_CLI_VERSION}/vault_$${VAULT_CLI_VERSION}_linux_$${ARCH}.zip" + if [ $? -ne 0 ]; then + printf "Failed to download Vault.\n" + return 1 + fi + if ! unzip_safe vault.zip; then + printf "Failed to unzip Vault.\n" + return 1 + fi + rm vault.zip + if sudo mv vault /usr/local/bin/vault 2> /dev/null; then + printf "Vault installed successfully!\n\n" + else + mkdir -p ~/.local/bin + if ! mv vault ~/.local/bin/vault; then + printf "Failed to move Vault to local bin.\n" + return 1 + fi + printf "Please add ~/.local/bin to your PATH to use vault CLI.\n" + fi + fi + return 0 +} + +TMP=$(mktemp -d) +if ! ( + cd "$TMP" + install +); then + echo "Failed to install Vault CLI." + exit 1 +fi +rm -rf "$TMP" + +# Authenticate with Vault +printf "🔑 Authenticating with Vault ...\n\n" +echo "$${CODER_OIDC_ACCESS_TOKEN}" | vault write auth/"$${VAULT_JWT_AUTH_PATH}"/login role="$${VAULT_JWT_ROLE}" jwt=- +printf "🥳 Vault authentication complete!\n\n" +printf "You can now use Vault CLI to access secrets.\n" From bd6747f9bc8e0710f0212422ac97aa8047561d15 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Fri, 27 Sep 2024 11:24:06 -0700 Subject: [PATCH 15/26] chore: move update-version to ci (#301) --- .github/workflows/ci.yaml | 15 ++++++++++ .github/workflows/update-readme.yaml | 42 ---------------------------- cursor/README.md | 4 +-- filebrowser/README.md | 6 ++-- jupyter-notebook/README.md | 2 +- jupyterlab/README.md | 2 +- update-version.sh | 23 +++++++++++---- 7 files changed, 39 insertions(+), 55 deletions(-) delete mode 100644 .github/workflows/update-readme.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2b337d8..4d0c709 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,6 +27,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Needed to get tags - uses: oven-sh/setup-bun@v2 with: bun-version: latest @@ -38,3 +40,16 @@ jobs: uses: crate-ci/typos@v1.17.2 - name: Lint run: bun lint + - name: Check version + shell: bash + run: | + # check for version changes + ./update-version.sh + # Check if any changes were made in README.md files + if [[ -n "$(git status --porcelain -- '**/README.md')" ]]; then + echo "Version mismatch detected. Please run ./update-version.sh and commit the updated README.md files." + git diff -- '**/README.md' + exit 1 + else + echo "No version mismatch detected. All versions are up to date." + fi diff --git a/.github/workflows/update-readme.yaml b/.github/workflows/update-readme.yaml deleted file mode 100644 index 0d0e226..0000000 --- a/.github/workflows/update-readme.yaml +++ /dev/null @@ -1,42 +0,0 @@ -name: Update README on Tag - -on: - workflow_dispatch: - push: - tags: - - 'v*' - -jobs: - update-readme: - permissions: - contents: write - pull-requests: write - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Get the latest tag - id: get-latest-tag - run: echo "TAG=$(git describe --tags --abbrev=0 | sed 's/^v//')" >> $GITHUB_OUTPUT - - - name: Run update script - run: ./update-version.sh - - - name: Create Pull Request - id: create-pr - uses: peter-evans/create-pull-request@v5 - with: - commit-message: 'chore: bump version to ${{ env.TAG }} in README.md files' - title: 'chore: bump version to ${{ env.TAG }} in README.md files' - body: 'This is an auto-generated PR to update README.md files of all modules with the new tag ${{ env.TAG }}' - branch: 'update-readme-branch' - base: 'main' - env: - TAG: ${{ steps.get-latest-tag.outputs.TAG }} - - - name: Auto-approve - uses: hmarr/auto-approve-action@v4 - if: github.ref == 'refs/heads/update-readme-branch' diff --git a/cursor/README.md b/cursor/README.md index a62743b..c2997be 100644 --- a/cursor/README.md +++ b/cursor/README.md @@ -16,7 +16,7 @@ Uses the [Coder Remote VS Code Extension](https://github.com/coder/cursor-coder) ```tf module "cursor" { source = "registry.coder.com/modules/cursor/coder" - version = "1.0.18" + version = "1.0.19" agent_id = coder_agent.example.id } ``` @@ -28,7 +28,7 @@ module "cursor" { ```tf module "cursor" { source = "registry.coder.com/modules/cursor/coder" - version = "1.0.18" + version = "1.0.19" agent_id = coder_agent.example.id folder = "/home/coder/project" } diff --git a/filebrowser/README.md b/filebrowser/README.md index e92560b..8665d82 100644 --- a/filebrowser/README.md +++ b/filebrowser/README.md @@ -14,7 +14,7 @@ A file browser for your workspace. ```tf module "filebrowser" { source = "registry.coder.com/modules/filebrowser/coder" - version = "1.0.18" + version = "1.0.19" agent_id = coder_agent.example.id } ``` @@ -28,7 +28,7 @@ module "filebrowser" { ```tf module "filebrowser" { source = "registry.coder.com/modules/filebrowser/coder" - version = "1.0.18" + version = "1.0.19" agent_id = coder_agent.example.id folder = "/home/coder/project" } @@ -39,7 +39,7 @@ module "filebrowser" { ```tf module "filebrowser" { source = "registry.coder.com/modules/filebrowser/coder" - version = "1.0.18" + version = "1.0.19" agent_id = coder_agent.example.id database_path = ".config/filebrowser.db" } diff --git a/jupyter-notebook/README.md b/jupyter-notebook/README.md index 6338f11..83d36cb 100644 --- a/jupyter-notebook/README.md +++ b/jupyter-notebook/README.md @@ -16,7 +16,7 @@ A module that adds Jupyter Notebook in your Coder template. ```tf module "jupyter-notebook" { source = "registry.coder.com/modules/jupyter-notebook/coder" - version = "1.0.8" + version = "1.0.19" agent_id = coder_agent.example.id } ``` diff --git a/jupyterlab/README.md b/jupyterlab/README.md index 3d04cf3..ed73b56 100644 --- a/jupyterlab/README.md +++ b/jupyterlab/README.md @@ -16,7 +16,7 @@ A module that adds JupyterLab in your Coder template. ```tf module "jupyterlab" { source = "registry.coder.com/modules/jupyterlab/coder" - version = "1.0.8" + version = "1.0.19" agent_id = coder_agent.example.id } ``` diff --git a/update-version.sh b/update-version.sh index 5deb63b..b062736 100755 --- a/update-version.sh +++ b/update-version.sh @@ -1,20 +1,24 @@ #!/usr/bin/env bash -# This script updates the version number in the README.md files of all modules -# to the latest tag in the repository. It is intended to be run from the root +# This script increments the version number in the README.md files of all modules +# by 1 patch version. It is intended to be run from the root # of the repository or by using the `bun update-version` command. set -euo pipefail current_tag=$(git describe --tags --abbrev=0) -previous_tag=$(git describe --tags --abbrev=0 $current_tag^) -mapfile -t changed_dirs < <(git diff --name-only "$previous_tag"..."$current_tag" -- ':!**/README.md' ':!**/*.test.ts' | xargs dirname | grep -v '^\.' | sort -u) -LATEST_TAG=$(git describe --abbrev=0 --tags | sed 's/^v//') || exit $? +# Increment the patch version +LATEST_TAG=$(echo "$current_tag" | sed 's/^v//' | awk -F. '{print $1"."$2"."$3+1}') || exit $? +# List directories with changes that are not README.md or test files +mapfile -t changed_dirs < <(git diff --name-only "$current_tag" -- ':!**/README.md' ':!**/*.test.ts' | xargs dirname | grep -v '^\.' | sort -u) + +echo "Directories with changes: ${changed_dirs[*]}" + +# Iterate over directories and update version in README.md for dir in "${changed_dirs[@]}"; do if [[ -f "$dir/README.md" ]]; then - echo "Bumping version in $dir/README.md" file="$dir/README.md" tmpfile=$(mktemp /tmp/tempfile.XXXXXX) awk -v tag="$LATEST_TAG" '{ @@ -25,5 +29,12 @@ for dir in "${changed_dirs[@]}"; do print } }' "$file" > "$tmpfile" && mv "$tmpfile" "$file" + + # Check if the README.md file has changed + if ! git diff --quiet -- "$dir/README.md"; then + echo "Bumping version in $dir/README.md from $current_tag to $LATEST_TAG (incremented)" + else + echo "Version in $dir/README.md is already up to date" + fi fi done From 438c9045673c629e95416960c94a6b6344e3d298 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Fri, 27 Sep 2024 14:35:47 -0500 Subject: [PATCH 16/26] chore: cleanup all test files (#293) ## Changes made - Removed all unused imports, and made sure type imports were labeled correctly - Updated all comparisons to be more strict - Simplified loops to remove unneeded closure functions - Removed all explicit `any` types - Updated how strings were defined to follow general TypeScript best practices ## Notes - We definitely want some kind of linting setup for this repo. I'm going to bring this up when Blueberry has its next team meeting next week --- aws-region/main.test.ts | 1 - azure-region/main.test.ts | 1 - coder-login/main.test.ts | 9 ++------ cursor/main.test.ts | 1 - exoscale-zone/main.test.ts | 1 - github-upload-public-key/main.test.ts | 20 +++++++++++------- jfrog-oauth/main.test.ts | 2 +- jfrog-token/main.test.ts | 2 +- jupyterlab/main.test.ts | 10 ++++----- nodejs/main.test.ts | 2 +- personalize/main.test.ts | 4 ---- slackme/main.test.ts | 8 +++---- test.ts | 30 ++++++++++++++------------- tsconfig.json | 10 ++++++--- vscode-desktop/main.test.ts | 4 ++-- windows-rdp/main.test.ts | 2 +- 16 files changed, 52 insertions(+), 55 deletions(-) diff --git a/aws-region/main.test.ts b/aws-region/main.test.ts index 0693e65..06f8e56 100644 --- a/aws-region/main.test.ts +++ b/aws-region/main.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it } from "bun:test"; import { - executeScriptInContainer, runTerraformApply, runTerraformInit, testRequiredVariables, diff --git a/azure-region/main.test.ts b/azure-region/main.test.ts index bebc0c9..8adbb48 100644 --- a/azure-region/main.test.ts +++ b/azure-region/main.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it } from "bun:test"; import { - executeScriptInContainer, runTerraformApply, runTerraformInit, testRequiredVariables, diff --git a/coder-login/main.test.ts b/coder-login/main.test.ts index d8fba35..aca4321 100644 --- a/coder-login/main.test.ts +++ b/coder-login/main.test.ts @@ -1,10 +1,5 @@ -import { describe, expect, it } from "bun:test"; -import { - executeScriptInContainer, - runTerraformApply, - runTerraformInit, - testRequiredVariables, -} from "../test"; +import { describe } from "bun:test"; +import { runTerraformInit, testRequiredVariables } from "../test"; describe("coder-login", async () => { await runTerraformInit(import.meta.dir); diff --git a/cursor/main.test.ts b/cursor/main.test.ts index cdf70e6..3c16469 100644 --- a/cursor/main.test.ts +++ b/cursor/main.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it } from "bun:test"; import { - executeScriptInContainer, runTerraformApply, runTerraformInit, testRequiredVariables, diff --git a/exoscale-zone/main.test.ts b/exoscale-zone/main.test.ts index ca8eeb7..1751cb1 100644 --- a/exoscale-zone/main.test.ts +++ b/exoscale-zone/main.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it } from "bun:test"; import { - executeScriptInContainer, runTerraformApply, runTerraformInit, testRequiredVariables, diff --git a/github-upload-public-key/main.test.ts b/github-upload-public-key/main.test.ts index fb1b977..6ce16d8 100644 --- a/github-upload-public-key/main.test.ts +++ b/github-upload-public-key/main.test.ts @@ -1,3 +1,4 @@ +import { type Server, serve } from "bun"; import { describe, expect, it } from "bun:test"; import { createJSONResponse, @@ -9,7 +10,6 @@ import { testRequiredVariables, writeCoder, } from "../test"; -import { Server, serve } from "bun"; describe("github-upload-public-key", async () => { await runTerraformInit(import.meta.dir); @@ -21,10 +21,12 @@ describe("github-upload-public-key", async () => { it("creates new key if one does not exist", async () => { const { instance, id, server } = await setupContainer(); await writeCoder(id, "echo foo"); - let exec = await execContainer(id, [ + + const url = server.url.toString().slice(0, -1); + const exec = await execContainer(id, [ "env", - "CODER_ACCESS_URL=" + server.url.toString().slice(0, -1), - "GITHUB_API_URL=" + server.url.toString().slice(0, -1), + `CODER_ACCESS_URL=${url}`, + `GITHUB_API_URL=${url}`, "CODER_OWNER_SESSION_TOKEN=foo", "CODER_EXTERNAL_AUTH_ID=github", "bash", @@ -42,10 +44,12 @@ describe("github-upload-public-key", async () => { const { instance, id, server } = await setupContainer(); // use keyword to make server return a existing key await writeCoder(id, "echo findkey"); - let exec = await execContainer(id, [ + + const url = server.url.toString().slice(0, -1); + const exec = await execContainer(id, [ "env", - "CODER_ACCESS_URL=" + server.url.toString().slice(0, -1), - "GITHUB_API_URL=" + server.url.toString().slice(0, -1), + `CODER_ACCESS_URL=${url}`, + `GITHUB_API_URL=${url}`, "CODER_OWNER_SESSION_TOKEN=foo", "CODER_EXTERNAL_AUTH_ID=github", "bash", @@ -95,7 +99,7 @@ const setupServer = async (): Promise => { } // case: key already exists - if (req.headers.get("Authorization") == "Bearer findkey") { + if (req.headers.get("Authorization") === "Bearer findkey") { return createJSONResponse([ { key: "foo", diff --git a/jfrog-oauth/main.test.ts b/jfrog-oauth/main.test.ts index da8b9bf..7b0c1a5 100644 --- a/jfrog-oauth/main.test.ts +++ b/jfrog-oauth/main.test.ts @@ -116,7 +116,7 @@ EOF`; const proxies = ["foo", "bar", "baz"] .map((r) => `https://${user}:@${fakeFrogApi}/go/${r}`) .join(","); - expect(proxyEnv["value"]).toEqual(proxies); + expect(proxyEnv.value).toEqual(proxies); const coderScript = findResourceInstance(state, "coder_script"); expect(coderScript.script).toContain( diff --git a/jfrog-token/main.test.ts b/jfrog-token/main.test.ts index a6fe6bc..2c85672 100644 --- a/jfrog-token/main.test.ts +++ b/jfrog-token/main.test.ts @@ -151,7 +151,7 @@ EOF`; const proxies = ["foo", "bar", "baz"] .map((r) => `https://${user}:${token}@${fakeFrogApi}/go/${r}`) .join(","); - expect(proxyEnv["value"]).toEqual(proxies); + expect(proxyEnv.value).toEqual(proxies); const coderScript = findResourceInstance(state, "coder_script"); expect(coderScript.script).toContain( diff --git a/jupyterlab/main.test.ts b/jupyterlab/main.test.ts index 122de77..cf9ac1f 100644 --- a/jupyterlab/main.test.ts +++ b/jupyterlab/main.test.ts @@ -1,20 +1,20 @@ import { describe, expect, it } from "bun:test"; import { + execContainer, executeScriptInContainer, + findResourceInstance, + runContainer, runTerraformApply, runTerraformInit, testRequiredVariables, - findResourceInstance, - runContainer, - TerraformState, - execContainer, + type TerraformState, } from "../test"; // executes the coder script after installing pip const executeScriptInContainerWithPip = async ( state: TerraformState, image: string, - shell: string = "sh", + shell = "sh", ): Promise<{ exitCode: number; stdout: string[]; diff --git a/nodejs/main.test.ts b/nodejs/main.test.ts index 07fc7a5..39e48f4 100644 --- a/nodejs/main.test.ts +++ b/nodejs/main.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "bun:test"; +import { describe } from "bun:test"; import { runTerraformInit, testRequiredVariables } from "../test"; describe("nodejs", async () => { diff --git a/personalize/main.test.ts b/personalize/main.test.ts index 9c8134e..b499a0b 100644 --- a/personalize/main.test.ts +++ b/personalize/main.test.ts @@ -1,13 +1,9 @@ -import { readableStreamToText, spawn } from "bun"; import { describe, expect, it } from "bun:test"; import { executeScriptInContainer, runTerraformApply, runTerraformInit, testRequiredVariables, - runContainer, - execContainer, - findResourceInstance, } from "../test"; describe("personalize", async () => { diff --git a/slackme/main.test.ts b/slackme/main.test.ts index eca4f5d..d8d0624 100644 --- a/slackme/main.test.ts +++ b/slackme/main.test.ts @@ -72,7 +72,7 @@ executed`, it("formats execution with milliseconds", async () => { await assertSlackMessage({ command: "echo test", - format: `$COMMAND took $DURATION`, + format: "$COMMAND took $DURATION", durationMS: 150, output: "echo test took 150ms", }); @@ -81,7 +81,7 @@ executed`, it("formats execution with seconds", async () => { await assertSlackMessage({ command: "echo test", - format: `$COMMAND took $DURATION`, + format: "$COMMAND took $DURATION", durationMS: 15000, output: "echo test took 15.0s", }); @@ -90,7 +90,7 @@ executed`, it("formats execution with minutes", async () => { await assertSlackMessage({ command: "echo test", - format: `$COMMAND took $DURATION`, + format: "$COMMAND took $DURATION", durationMS: 120000, output: "echo test took 2m 0.0s", }); @@ -99,7 +99,7 @@ executed`, it("formats execution with hours", async () => { await assertSlackMessage({ command: "echo test", - format: `$COMMAND took $DURATION`, + format: "$COMMAND took $DURATION", durationMS: 60000 * 60, output: "echo test took 1hr 0m 0.0s", }); diff --git a/test.ts b/test.ts index dd1b1c6..5437374 100644 --- a/test.ts +++ b/test.ts @@ -1,6 +1,6 @@ import { readableStreamToText, spawn } from "bun"; -import { afterEach, expect, it } from "bun:test"; -import { readFile, unlink } from "fs/promises"; +import { expect, it } from "bun:test"; +import { readFile, unlink } from "node:fs/promises"; export const runContainer = async ( image: string, @@ -21,7 +21,8 @@ export const runContainer = async ( "-c", init, ]); - let containerID = await readableStreamToText(proc.stdout); + + const containerID = await readableStreamToText(proc.stdout); const exitCode = await proc.exited; if (exitCode !== 0) { throw new Error(containerID); @@ -36,7 +37,7 @@ export const runContainer = async ( export const executeScriptInContainer = async ( state: TerraformState, image: string, - shell: string = "sh", + shell = "sh", ): Promise<{ exitCode: number; stdout: string[]; @@ -116,6 +117,9 @@ export interface CoderScriptAttributes { url: string; } +export type ResourceInstance = + T extends "coder_script" ? CoderScriptAttributes : Record; + /** * finds the first instance of the given resource type in the given state. If * name is specified, it will only find the instance with the given name. @@ -124,10 +128,7 @@ export const findResourceInstance = ( state: TerraformState, type: T, name?: string, - // if type is "coder_script" return CoderScriptAttributes -): T extends "coder_script" - ? CoderScriptAttributes - : Record => { +): ResourceInstance => { const resource = state.resources.find( (resource) => resource.type === type && (name ? resource.name === name : true), @@ -140,7 +141,8 @@ export const findResourceInstance = ( `Resource ${type} has ${resource.instances.length} instances`, ); } - return resource.instances[0].attributes as any; + + return resource.instances[0].attributes as ResourceInstance; }; /** @@ -157,15 +159,15 @@ export const testRequiredVariables = ( }); const varNames = Object.keys(vars); - varNames.forEach((varName) => { + for (const varName of varNames) { // Ensures that every variable provided is required! - it("missing variable " + varName, async () => { + it(`missing variable: ${varName}`, async () => { const localVars: TerraformVariables = {}; - varNames.forEach((otherVarName) => { + for (const otherVarName of varNames) { if (otherVarName !== varName) { localVars[otherVarName] = vars[otherVarName]; } - }); + } try { await runTerraformApply(dir, localVars); @@ -181,7 +183,7 @@ export const testRequiredVariables = ( } throw new Error(`${varName} is not a required variable!`); }); - }); + } }; /** diff --git a/tsconfig.json b/tsconfig.json index 5de8ae3..c7a5d26 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,14 @@ { "compilerOptions": { - "target": "esnext", - "module": "nodenext", + // If we were just compiling for the tests, we could safely target ESNext at + // all times, but just because we've been starting to add more runtime logic + // files to some of the modules, erring on the side of caution by having a + // older compilation target + "target": "ES6", + "module": "ESNext", "strict": true, "allowSyntheticDefaultImports": true, - "moduleResolution": "nodenext", + "moduleResolution": "node", "types": ["bun-types"] } } diff --git a/vscode-desktop/main.test.ts b/vscode-desktop/main.test.ts index 207b492..7aa144e 100644 --- a/vscode-desktop/main.test.ts +++ b/vscode-desktop/main.test.ts @@ -22,7 +22,7 @@ describe("vscode-desktop", async () => { ); const coder_app = state.resources.find( - (res) => res.type == "coder_app" && res.name == "vscode", + (res) => res.type === "coder_app" && res.name === "vscode", ); expect(coder_app).not.toBeNull(); @@ -79,7 +79,7 @@ describe("vscode-desktop", async () => { }); const coder_app = state.resources.find( - (res) => res.type == "coder_app" && res.name == "vscode", + (res) => res.type === "coder_app" && res.name === "vscode", ); expect(coder_app).not.toBeNull(); diff --git a/windows-rdp/main.test.ts b/windows-rdp/main.test.ts index 61075d9..ba5e21a 100644 --- a/windows-rdp/main.test.ts +++ b/windows-rdp/main.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "bun:test"; import { - TerraformState, + type TerraformState, runTerraformApply, runTerraformInit, testRequiredVariables, From 8a0ac3435cab714289490702131304f51775f44d Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 8 Oct 2024 05:16:01 +0000 Subject: [PATCH 17/26] Add owner to Gateway link (#310) Without this, it is not possible to reliably connect to another user's workspace (for admins, mainly) when duplicate workspace names are involved. --- jetbrains-gateway/README.md | 8 ++++---- jetbrains-gateway/main.test.ts | 20 ++++++++++++++++++++ jetbrains-gateway/main.tf | 3 +++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/jetbrains-gateway/README.md b/jetbrains-gateway/README.md index b2c0e0f..99bc5bd 100644 --- a/jetbrains-gateway/README.md +++ b/jetbrains-gateway/README.md @@ -14,7 +14,7 @@ This module adds a JetBrains Gateway Button to open any workspace with a single ```tf module "jetbrains_gateway" { source = "registry.coder.com/modules/jetbrains-gateway/coder" - version = "1.0.13" + version = "1.0.20" agent_id = coder_agent.example.id agent_name = "example" folder = "/home/coder/example" @@ -32,7 +32,7 @@ module "jetbrains_gateway" { ```tf module "jetbrains_gateway" { source = "registry.coder.com/modules/jetbrains-gateway/coder" - version = "1.0.13" + version = "1.0.20" agent_id = coder_agent.example.id agent_name = "example" folder = "/home/coder/example" @@ -46,7 +46,7 @@ module "jetbrains_gateway" { ```tf module "jetbrains_gateway" { source = "registry.coder.com/modules/jetbrains-gateway/coder" - version = "1.0.13" + version = "1.0.20" agent_id = coder_agent.example.id agent_name = "example" folder = "/home/coder/example" @@ -61,7 +61,7 @@ module "jetbrains_gateway" { ```tf module "jetbrains_gateway" { source = "registry.coder.com/modules/jetbrains-gateway/coder" - version = "1.0.13" + version = "1.0.20" agent_id = coder_agent.example.id agent_name = "example" folder = "/home/coder/example" diff --git a/jetbrains-gateway/main.test.ts b/jetbrains-gateway/main.test.ts index b327e41..0a5b3bc 100644 --- a/jetbrains-gateway/main.test.ts +++ b/jetbrains-gateway/main.test.ts @@ -14,6 +14,26 @@ describe("jetbrains-gateway", async () => { folder: "/home/foo", }); + it("should create a link with the default values", async () => { + const state = await runTerraformApply(import.meta.dir, { + // These are all required. + agent_id: "foo", + agent_name: "foo", + folder: "/home/coder", + }); + expect(state.outputs.url.value).toBe( + "jetbrains-gateway://connect#type=coder&workspace=default&owner=default&agent=foo&folder=/home/coder&url=https://mydeployment.coder.com&token=$SESSION_TOKEN&ide_product_code=IU&ide_build_number=241.14494.240&ide_download_link=https://download.jetbrains.com/idea/ideaIU-2024.1.tar.gz", + ); + + const coder_app = state.resources.find( + (res) => res.type === "coder_app" && res.name === "gateway", + ); + + expect(coder_app).not.toBeNull(); + expect(coder_app?.instances.length).toBe(1); + expect(coder_app?.instances[0].attributes.order).toBeNull(); + }); + it("default to first ide", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", diff --git a/jetbrains-gateway/main.tf b/jetbrains-gateway/main.tf index c96098c..d4474b2 100644 --- a/jetbrains-gateway/main.tf +++ b/jetbrains-gateway/main.tf @@ -243,6 +243,7 @@ data "coder_parameter" "jetbrains_ide" { } data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} resource "coder_app" "gateway" { agent_id = var.agent_id @@ -254,6 +255,8 @@ resource "coder_app" "gateway" { url = join("", [ "jetbrains-gateway://connect#type=coder&workspace=", data.coder_workspace.me.name, + "&owner=", + data.coder_workspace_owner.me.name, "&agent=", var.agent_name, "&folder=", From 50a946df0fb63d679dddf129a687004d29f07200 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Tue, 15 Oct 2024 06:48:15 -0700 Subject: [PATCH 18/26] chore: explicitly setup terraform (#319) --- .github/workflows/ci.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4d0c709..c5d9c73 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -17,6 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: coder/coder/.github/actions/setup-tf@main - uses: oven-sh/setup-bun@v2 with: bun-version: latest @@ -29,6 +30,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 # Needed to get tags + - uses: coder/coder/.github/actions/setup-tf@main - uses: oven-sh/setup-bun@v2 with: bun-version: latest From 4dcab99cb05bbb2c5d2826a729140b3243b30f7d Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Tue, 15 Oct 2024 10:53:36 -0700 Subject: [PATCH 19/26] fix(vscode-web): remove exit if extension installation fails (#318) --- vscode-web/README.md | 8 ++++---- vscode-web/run.sh | 26 ++++++++++++-------------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/vscode-web/README.md b/vscode-web/README.md index ba395d0..821d518 100644 --- a/vscode-web/README.md +++ b/vscode-web/README.md @@ -14,7 +14,7 @@ Automatically install [Visual Studio Code Server](https://code.visualstudio.com/ ```tf module "vscode-web" { source = "registry.coder.com/modules/vscode-web/coder" - version = "1.0.14" + version = "1.0.20" agent_id = coder_agent.example.id accept_license = true } @@ -29,7 +29,7 @@ module "vscode-web" { ```tf module "vscode-web" { source = "registry.coder.com/modules/vscode-web/coder" - version = "1.0.14" + version = "1.0.20" agent_id = coder_agent.example.id install_prefix = "/home/coder/.vscode-web" folder = "/home/coder" @@ -42,7 +42,7 @@ module "vscode-web" { ```tf module "vscode-web" { source = "registry.coder.com/modules/vscode-web/coder" - version = "1.0.14" + version = "1.0.20" agent_id = coder_agent.example.id extensions = ["github.copilot", "ms-python.python", "ms-toolsai.jupyter"] accept_license = true @@ -56,7 +56,7 @@ Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarte ```tf module "vscode-web" { source = "registry.coder.com/modules/vscode-web/coder" - version = "1.0.14" + version = "1.0.20" agent_id = coder_agent.example.id extensions = ["dracula-theme.theme-dracula"] settings = { diff --git a/vscode-web/run.sh b/vscode-web/run.sh index ce8782f..ecfad68 100755 --- a/vscode-web/run.sh +++ b/vscode-web/run.sh @@ -72,27 +72,25 @@ for extension in "$${EXTENSIONLIST[@]}"; do output=$($VSCODE_WEB "$EXTENSION_ARG" --install-extension "$extension" --force) if [ $? -ne 0 ]; then echo "Failed to install extension: $extension: $output" - exit 1 fi done if [ "${AUTO_INSTALL_EXTENSIONS}" = true ]; then if ! command -v jq > /dev/null; then echo "jq is required to install extensions from a workspace file." - exit 0 - fi - - WORKSPACE_DIR="$HOME" - if [ -n "${FOLDER}" ]; then - WORKSPACE_DIR="${FOLDER}" - fi + else + WORKSPACE_DIR="$HOME" + if [ -n "${FOLDER}" ]; then + WORKSPACE_DIR="${FOLDER}" + fi - if [ -f "$WORKSPACE_DIR/.vscode/extensions.json" ]; then - printf "🧩 Installing extensions from %s/.vscode/extensions.json...\n" "$WORKSPACE_DIR" - extensions=$(jq -r '.recommendations[]' "$WORKSPACE_DIR"/.vscode/extensions.json) - for extension in $extensions; do - $VSCODE_WEB "$EXTENSION_ARG" --install-extension "$extension" --force - done + if [ -f "$WORKSPACE_DIR/.vscode/extensions.json" ]; then + printf "🧩 Installing extensions from %s/.vscode/extensions.json...\n" "$WORKSPACE_DIR" + extensions=$(jq -r '.recommendations[]' "$WORKSPACE_DIR"/.vscode/extensions.json) + for extension in $extensions; do + $VSCODE_WEB "$EXTENSION_ARG" --install-extension "$extension" --force + done + fi fi fi From acd5edffe7f78ecedf613315f83b097ee79a9a45 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Tue, 15 Oct 2024 14:04:28 -0700 Subject: [PATCH 20/26] fix(vault-jwt): fix vault CLI installation (#311) --- vault-jwt/README.md | 8 ++++---- vault-jwt/run.sh | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/vault-jwt/README.md b/vault-jwt/README.md index 67aa7e5..939bed2 100644 --- a/vault-jwt/README.md +++ b/vault-jwt/README.md @@ -15,7 +15,7 @@ This module lets you authenticate with [Hashicorp Vault](https://www.vaultprojec ```tf module "vault" { source = "registry.coder.com/modules/vault-jwt/coder" - version = "1.0.19" + version = "1.0.20" agent_id = coder_agent.example.id vault_addr = "https://vault.example.com" vault_jwt_role = "coder" # The Vault role to use for authentication @@ -41,7 +41,7 @@ curl -H "X-Vault-Token: ${VAULT_TOKEN}" -X GET "${VAULT_ADDR}/v1/coder/secrets/d ```tf module "vault" { source = "registry.coder.com/modules/vault-jwt/coder" - version = "1.0.19" + version = "1.0.20" agent_id = coder_agent.example.id vault_addr = "https://vault.example.com" vault_jwt_auth_path = "oidc" @@ -56,7 +56,7 @@ data "coder_workspace_owner" "me" {} module "vault" { source = "registry.coder.com/modules/vault-jwt/coder" - version = "1.0.19" + version = "1.0.20" agent_id = coder_agent.example.id vault_addr = "https://vault.example.com" vault_jwt_role = data.coder_workspace_owner.me.groups[0] @@ -68,7 +68,7 @@ module "vault" { ```tf module "vault" { source = "registry.coder.com/modules/vault-jwt/coder" - version = "1.0.19" + version = "1.0.20" agent_id = coder_agent.example.id vault_addr = "https://vault.example.com" vault_jwt_role = "coder" # The Vault role to use for authentication diff --git a/vault-jwt/run.sh b/vault-jwt/run.sh index 662b378..ef45884 100644 --- a/vault-jwt/run.sh +++ b/vault-jwt/run.sh @@ -51,7 +51,7 @@ install() { printf "Failed to determine the latest Vault version.\n" return 1 fi - VAULT_CLI_VERSION=$${VAULT_CLI_VERSION} + VAULT_CLI_VERSION=$${LATEST_VERSION} fi # Check if the vault CLI is installed and has the correct version From 48c81c9ff4a6d46e0f067387bfbe3c34b843f16e Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Wed, 16 Oct 2024 19:03:00 -0700 Subject: [PATCH 21/26] kasm VNC (#250) Co-authored-by: Michael Smith --- kasmvnc/README.md | 23 +++++++ kasmvnc/main.test.ts | 37 ++++++++++ kasmvnc/main.tf | 63 +++++++++++++++++ kasmvnc/run.sh | 161 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 284 insertions(+) create mode 100644 kasmvnc/README.md create mode 100644 kasmvnc/main.test.ts create mode 100644 kasmvnc/main.tf create mode 100644 kasmvnc/run.sh diff --git a/kasmvnc/README.md b/kasmvnc/README.md new file mode 100644 index 0000000..845cbc2 --- /dev/null +++ b/kasmvnc/README.md @@ -0,0 +1,23 @@ +--- +display_name: KasmVNC +description: A modern open source VNC server +icon: ../.icons/kasmvnc.svg +maintainer_github: coder +verified: true +tags: [helper, vnc, desktop] +--- + +# KasmVNC + +Automatically install [KasmVNC](https://kasmweb.com/kasmvnc) in a workspace, and create an app to access it via the dashboard. + +```tf +module "kasmvnc" { + source = "registry.coder.com/modules/kasmvnc/coder" + version = "1.0.21" + agent_id = coder_agent.example.id + desktop_environment = "xfce" +} +``` + +> **Note:** This module only works on workspaces with a pre-installed desktop environment. As an example base image you can use `codercom/enterprise-desktop` image. diff --git a/kasmvnc/main.test.ts b/kasmvnc/main.test.ts new file mode 100644 index 0000000..0116d05 --- /dev/null +++ b/kasmvnc/main.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from "bun:test"; +import { + runTerraformApply, + runTerraformInit, + testRequiredVariables, +} from "../test"; + +const allowedDesktopEnvs = ["xfce", "kde", "gnome", "lxde", "lxqt"] as const; +type AllowedDesktopEnv = (typeof allowedDesktopEnvs)[number]; + +type TestVariables = Readonly<{ + agent_id: string; + desktop_environment: AllowedDesktopEnv; + port?: string; + kasm_version?: string; +}>; + +describe("Kasm VNC", async () => { + await runTerraformInit(import.meta.dir); + testRequiredVariables(import.meta.dir, { + agent_id: "foo", + desktop_environment: "gnome", + }); + + it("Successfully installs for all expected Kasm desktop versions", async () => { + for (const v of allowedDesktopEnvs) { + const applyWithEnv = () => { + runTerraformApply(import.meta.dir, { + agent_id: "foo", + desktop_environment: v, + }); + }; + + expect(applyWithEnv).not.toThrow(); + } + }); +}); diff --git a/kasmvnc/main.tf b/kasmvnc/main.tf new file mode 100644 index 0000000..3a730ff --- /dev/null +++ b/kasmvnc/main.tf @@ -0,0 +1,63 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + coder = { + source = "coder/coder" + version = ">= 0.12" + } + } +} + +variable "agent_id" { + type = string + description = "The ID of a Coder agent." +} + +variable "port" { + type = number + description = "The port to run KasmVNC on." + default = 6800 +} + +variable "kasm_version" { + type = string + description = "Version of KasmVNC to install." + default = "1.3.2" +} + +variable "desktop_environment" { + type = string + description = "Specifies the desktop environment of the workspace. This should be pre-installed on the workspace." + validation { + condition = contains(["xfce", "kde", "gnome", "lxde", "lxqt"], var.desktop_environment) + error_message = "Invalid desktop environment. Please specify a valid desktop environment." + } +} + +resource "coder_script" "kasm_vnc" { + agent_id = var.agent_id + display_name = "KasmVNC" + icon = "/icon/kasmvnc.svg" + script = templatefile("${path.module}/run.sh", { + PORT : var.port, + DESKTOP_ENVIRONMENT : var.desktop_environment, + VERSION : var.kasm_version + }) + run_on_start = true +} + +resource "coder_app" "kasm_vnc" { + agent_id = var.agent_id + slug = "kasm-vnc" + display_name = "kasmVNC" + url = "http://localhost:${var.port}" + icon = "/icon/kasmvnc.svg" + subdomain = true + share = "owner" + healthcheck { + url = "http://localhost:${var.port}/app" + interval = 5 + threshold = 5 + } +} diff --git a/kasmvnc/run.sh b/kasmvnc/run.sh new file mode 100644 index 0000000..0390ea1 --- /dev/null +++ b/kasmvnc/run.sh @@ -0,0 +1,161 @@ +#!/usr/bin/env bash + +#!/bin/bash + +# Function to check if vncserver is already installed +check_installed() { + if command -v vncserver &> /dev/null; then + echo "vncserver is already installed." + return 0 # Don't exit, just indicate it's installed + else + return 1 # Indicates not installed + fi +} + +# Function to install kasmvncserver for debian-based distros +install_deb() { + local url=$1 + wget $url -O /tmp/kasmvncserver.deb + sudo apt-get install --yes --no-install-recommends --no-install-suggests /tmp/kasmvncserver.deb + sudo adduser $USER ssl-cert + rm /tmp/kasmvncserver.deb +} + +# Function to install kasmvncserver for Oracle 8 +install_rpm_oracle8() { + local url=$1 + wget $url -O /tmp/kasmvncserver.rpm + sudo dnf config-manager --set-enabled ol8_codeready_builder + sudo dnf install oracle-epel-release-el8 -y + sudo dnf localinstall /tmp/kasmvncserver.rpm -y + sudo usermod -aG kasmvnc-cert $USER + rm /tmp/kasmvncserver.rpm +} + +# Function to install kasmvncserver for CentOS 7 +install_rpm_centos7() { + local url=$1 + wget $url -O /tmp/kasmvncserver.rpm + sudo yum install epel-release -y + sudo yum install /tmp/kasmvncserver.rpm -y + sudo usermod -aG kasmvnc-cert $USER + rm /tmp/kasmvncserver.rpm +} + +# Function to install kasmvncserver for rpm-based distros +install_rpm() { + local url=$1 + wget $url -O /tmp/kasmvncserver.rpm + sudo rpm -i /tmp/kasmvncserver.rpm + rm /tmp/kasmvncserver.rpm +} + +# Function to install kasmvncserver for Alpine Linux +install_alpine() { + local url=$1 + wget $url -O /tmp/kasmvncserver.tgz + tar -xzf /tmp/kasmvncserver.tgz -C /usr/local/bin/ + rm /tmp/kasmvncserver.tgz +} + +# Detect system information +distro=$(grep "^ID=" /etc/os-release | awk -F= '{print $2}') +version=$(grep "^VERSION_ID=" /etc/os-release | awk -F= '{print $2}' | tr -d '"') +arch=$(uname -m) + +echo "Detected Distribution: $distro" +echo "Detected Version: $version" +echo "Detected Architecture: $arch" + +# Map arch to package arch +if [[ "$arch" == "x86_64" ]]; then + if [[ "$distro" == "ubuntu" || "$distro" == "debian" || "$distro" == "kali" ]]; then + arch="amd64" + else + arch="x86_64" + fi +elif [[ "$arch" == "aarch64" || "$arch" == "arm64" ]]; then + if [[ "$distro" == "ubuntu" || "$distro" == "debian" || "$distro" == "kali" ]]; then + arch="arm64" + else + arch="aarch64" + fi +else + echo "Unsupported architecture: $arch" + exit 1 +fi + +# Check if vncserver is installed, and install if not +if ! check_installed; then + case $distro in + ubuntu | debian | kali) + case $version in + "20.04") + install_deb "https://github.com/kasmtech/KasmVNC/releases/download/v${VERSION}/kasmvncserver_focal_${VERSION}_$${arch}.deb" + ;; + "22.04") + install_deb "https://github.com/kasmtech/KasmVNC/releases/download/v${VERSION}/kasmvncserver_jammy_${VERSION}_$${arch}.deb" + ;; + "24.04") + install_deb "https://github.com/kasmtech/KasmVNC/releases/download/v${VERSION}/kasmvncserver_noble_${VERSION}_$${arch}.deb" + ;; + *) + echo "Unsupported Ubuntu/Debian/Kali version: $${version}" + exit 1 + ;; + esac + ;; + oracle) + if [[ "$version" == "8" ]]; then + install_rpm_oracle8 "https://github.com/kasmtech/KasmVNC/releases/download/v${VERSION}/kasmvncserver_oracle_8_${VERSION}_$${arch}.rpm" + else + echo "Unsupported Oracle version: $${version}" + exit 1 + fi + ;; + centos) + if [[ "$version" == "7" ]]; then + install_rpm_centos7 "https://github.com/kasmtech/KasmVNC/releases/download/v${VERSION}/kasmvncserver_centos_core_${VERSION}_$${arch}.rpm" + else + install_rpm "https://github.com/kasmtech/KasmVNC/releases/download/v${VERSION}/kasmvncserver_centos_core_${VERSION}_$${arch}.rpm" + fi + ;; + alpine) + if [[ "$version" == "3.17" || "$version" == "3.18" || "$version" == "3.19" || "$version" == "3.20" ]]; then + install_alpine "https://github.com/kasmtech/KasmVNC/releases/download/v${VERSION}/kasmvnc.alpine_$${version}_$${arch}.tgz" + else + echo "Unsupported Alpine version: $${version}" + exit 1 + fi + ;; + fedora | opensuse) + install_rpm "https://github.com/kasmtech/KasmVNC/releases/download/v${VERSION}/kasmvncserver_$${distro}_$${version}_${VERSION}_$${arch}.rpm" + ;; + *) + echo "Unsupported distribution: $${distro}" + exit 1 + ;; + esac +else + echo "vncserver already installed. Skipping installation." +fi + +# Coder port-forwarding from dashboard only supports HTTP +sudo bash -c "cat > /etc/kasmvnc/kasmvnc.yaml < /tmp/kasmvncserver.log 2>&1 & From 9752bf89a6dfb9cbd762a42be57d76d5cf6f63f1 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Thu, 17 Oct 2024 07:17:38 -0700 Subject: [PATCH 22/26] chore(kasmvnc): refactor download logic to support multiple tools (#323) --- kasmvnc/run.sh | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/kasmvnc/run.sh b/kasmvnc/run.sh index 0390ea1..47fe571 100644 --- a/kasmvnc/run.sh +++ b/kasmvnc/run.sh @@ -12,10 +12,26 @@ check_installed() { fi } +# Function to download a file using wget, curl, or busybox as a fallback +download_file() { + local url=$1 + local output=$2 + if command -v wget &> /dev/null; then + wget $url -O $output + elif command -v curl &> /dev/null; then + curl -L $url -o $output + elif command -v busybox &> /dev/null; then + busybox wget -O $output $url + else + echo "Neither wget, curl, nor busybox is installed. Please install one of them to proceed." + exit 1 + fi +} + # Function to install kasmvncserver for debian-based distros install_deb() { local url=$1 - wget $url -O /tmp/kasmvncserver.deb + download_file $url /tmp/kasmvncserver.deb sudo apt-get install --yes --no-install-recommends --no-install-suggests /tmp/kasmvncserver.deb sudo adduser $USER ssl-cert rm /tmp/kasmvncserver.deb @@ -24,7 +40,7 @@ install_deb() { # Function to install kasmvncserver for Oracle 8 install_rpm_oracle8() { local url=$1 - wget $url -O /tmp/kasmvncserver.rpm + download_file $url /tmp/kasmvncserver.rpm sudo dnf config-manager --set-enabled ol8_codeready_builder sudo dnf install oracle-epel-release-el8 -y sudo dnf localinstall /tmp/kasmvncserver.rpm -y @@ -35,7 +51,7 @@ install_rpm_oracle8() { # Function to install kasmvncserver for CentOS 7 install_rpm_centos7() { local url=$1 - wget $url -O /tmp/kasmvncserver.rpm + download_file $url /tmp/kasmvncserver.rpm sudo yum install epel-release -y sudo yum install /tmp/kasmvncserver.rpm -y sudo usermod -aG kasmvnc-cert $USER @@ -45,7 +61,7 @@ install_rpm_centos7() { # Function to install kasmvncserver for rpm-based distros install_rpm() { local url=$1 - wget $url -O /tmp/kasmvncserver.rpm + download_file $url /tmp/kasmvncserver.rpm sudo rpm -i /tmp/kasmvncserver.rpm rm /tmp/kasmvncserver.rpm } @@ -53,7 +69,7 @@ install_rpm() { # Function to install kasmvncserver for Alpine Linux install_alpine() { local url=$1 - wget $url -O /tmp/kasmvncserver.tgz + download_file $url /tmp/kasmvncserver.tgz tar -xzf /tmp/kasmvncserver.tgz -C /usr/local/bin/ rm /tmp/kasmvncserver.tgz } From 8e0dfcd5347ed7a6a0532df1cf91f3f0c55473e3 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Thu, 17 Oct 2024 07:25:03 -0700 Subject: [PATCH 23/26] feat(jetbrains-gateway): add slug variable (#322) --- jetbrains-gateway/README.md | 8 ++++---- jetbrains-gateway/main.tf | 8 +++++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/jetbrains-gateway/README.md b/jetbrains-gateway/README.md index 99bc5bd..0745fa7 100644 --- a/jetbrains-gateway/README.md +++ b/jetbrains-gateway/README.md @@ -14,7 +14,7 @@ This module adds a JetBrains Gateway Button to open any workspace with a single ```tf module "jetbrains_gateway" { source = "registry.coder.com/modules/jetbrains-gateway/coder" - version = "1.0.20" + version = "1.0.21" agent_id = coder_agent.example.id agent_name = "example" folder = "/home/coder/example" @@ -32,7 +32,7 @@ module "jetbrains_gateway" { ```tf module "jetbrains_gateway" { source = "registry.coder.com/modules/jetbrains-gateway/coder" - version = "1.0.20" + version = "1.0.21" agent_id = coder_agent.example.id agent_name = "example" folder = "/home/coder/example" @@ -46,7 +46,7 @@ module "jetbrains_gateway" { ```tf module "jetbrains_gateway" { source = "registry.coder.com/modules/jetbrains-gateway/coder" - version = "1.0.20" + version = "1.0.21" agent_id = coder_agent.example.id agent_name = "example" folder = "/home/coder/example" @@ -61,7 +61,7 @@ module "jetbrains_gateway" { ```tf module "jetbrains_gateway" { source = "registry.coder.com/modules/jetbrains-gateway/coder" - version = "1.0.20" + version = "1.0.21" agent_id = coder_agent.example.id agent_name = "example" folder = "/home/coder/example" diff --git a/jetbrains-gateway/main.tf b/jetbrains-gateway/main.tf index d4474b2..2bc00d3 100644 --- a/jetbrains-gateway/main.tf +++ b/jetbrains-gateway/main.tf @@ -18,6 +18,12 @@ variable "agent_id" { description = "The ID of a Coder agent." } +variable "slug" { + type = string + description = "The slug for the coder_app. Allows resuing the module with the same template." + default = "gateway" +} + variable "agent_name" { type = string description = "Agent name." @@ -247,7 +253,7 @@ data "coder_workspace_owner" "me" {} resource "coder_app" "gateway" { agent_id = var.agent_id - slug = "gateway" + slug = var.slug display_name = local.display_name icon = local.icon external = true From 20d97a25dd832256c1deee2b66c141ae02d79520 Mon Sep 17 00:00:00 2001 From: Yves ANDOLFATTO <145037627+yandolfat@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:21:36 +0200 Subject: [PATCH 24/26] fix(filebrowser): support custom base_url in case of custom db path (#320) Co-authored-by: Muhammad Atif Ali Co-authored-by: Muhammad Atif Ali --- filebrowser/README.md | 6 +++--- filebrowser/run.sh | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/filebrowser/README.md b/filebrowser/README.md index 8665d82..1b53ffa 100644 --- a/filebrowser/README.md +++ b/filebrowser/README.md @@ -14,7 +14,7 @@ A file browser for your workspace. ```tf module "filebrowser" { source = "registry.coder.com/modules/filebrowser/coder" - version = "1.0.19" + version = "1.0.22" agent_id = coder_agent.example.id } ``` @@ -28,7 +28,7 @@ module "filebrowser" { ```tf module "filebrowser" { source = "registry.coder.com/modules/filebrowser/coder" - version = "1.0.19" + version = "1.0.22" agent_id = coder_agent.example.id folder = "/home/coder/project" } @@ -39,7 +39,7 @@ module "filebrowser" { ```tf module "filebrowser" { source = "registry.coder.com/modules/filebrowser/coder" - version = "1.0.19" + version = "1.0.22" agent_id = coder_agent.example.id database_path = ".config/filebrowser.db" } diff --git a/filebrowser/run.sh b/filebrowser/run.sh index 22f13ed..8a31d4d 100644 --- a/filebrowser/run.sh +++ b/filebrowser/run.sh @@ -18,7 +18,7 @@ if [ "${DB_PATH}" != "filebrowser.db" ]; then fi # set baseurl to be able to run if sudomain=false; if subdomain=true the SERVER_BASE_PATH value will be "" -filebrowser config set --baseurl "${SERVER_BASE_PATH}" > ${LOG_PATH} 2>&1 +filebrowser config set --baseurl "${SERVER_BASE_PATH}"$${DB_FLAG} > ${LOG_PATH} 2>&1 printf "📂 Serving $${ROOT_DIR} at http://localhost:${PORT} \n\n" From 7992d9d2652bc9714a22e93fdf35f61672d4c366 Mon Sep 17 00:00:00 2001 From: djarbz <30350993+djarbz@users.noreply.github.com> Date: Sun, 20 Oct 2024 22:04:59 -0500 Subject: [PATCH 25/26] fix(kasmVNC): fix debian installation and improve logging (#326) --- kasmvnc/README.md | 2 +- kasmvnc/run.sh | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/kasmvnc/README.md b/kasmvnc/README.md index 845cbc2..3b7fe50 100644 --- a/kasmvnc/README.md +++ b/kasmvnc/README.md @@ -14,7 +14,7 @@ Automatically install [KasmVNC](https://kasmweb.com/kasmvnc) in a workspace, and ```tf module "kasmvnc" { source = "registry.coder.com/modules/kasmvnc/coder" - version = "1.0.21" + version = "1.0.22" agent_id = coder_agent.example.id desktop_environment = "xfce" } diff --git a/kasmvnc/run.sh b/kasmvnc/run.sh index 47fe571..b831537 100644 --- a/kasmvnc/run.sh +++ b/kasmvnc/run.sh @@ -19,7 +19,7 @@ download_file() { if command -v wget &> /dev/null; then wget $url -O $output elif command -v curl &> /dev/null; then - curl -L $url -o $output + curl -fsSL $url -o $output elif command -v busybox &> /dev/null; then busybox wget -O $output $url else @@ -32,7 +32,8 @@ download_file() { install_deb() { local url=$1 download_file $url /tmp/kasmvncserver.deb - sudo apt-get install --yes --no-install-recommends --no-install-suggests /tmp/kasmvncserver.deb + sudo apt-get update + DEBIAN_FRONTEND=noninteractive sudo apt-get install --yes -qq --no-install-recommends --no-install-suggests /tmp/kasmvncserver.deb sudo adduser $USER ssl-cert rm /tmp/kasmvncserver.deb } @@ -103,6 +104,7 @@ fi # Check if vncserver is installed, and install if not if ! check_installed; then + echo "Installing KASM version: ${VERSION}" case $distro in ubuntu | debian | kali) case $version in From 1b147ae90dcb1e861751a808a68f68c449c128b6 Mon Sep 17 00:00:00 2001 From: framctr <35109437+framctr@users.noreply.github.com> Date: Mon, 21 Oct 2024 09:06:10 +0200 Subject: [PATCH 26/26] feat(jupyterlab): add support for `subdomain=false` (#316) Co-authored-by: Muhammad Atif Ali Co-authored-by: Asher --- jupyterlab/README.md | 2 +- jupyterlab/main.tf | 16 +++++++++++++--- jupyterlab/run.sh | 20 +++++++++++++++----- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/jupyterlab/README.md b/jupyterlab/README.md index ed73b56..52d5a50 100644 --- a/jupyterlab/README.md +++ b/jupyterlab/README.md @@ -16,7 +16,7 @@ A module that adds JupyterLab in your Coder template. ```tf module "jupyterlab" { source = "registry.coder.com/modules/jupyterlab/coder" - version = "1.0.19" + version = "1.0.22" agent_id = coder_agent.example.id } ``` diff --git a/jupyterlab/main.tf b/jupyterlab/main.tf index d7928f0..d66edb1 100644 --- a/jupyterlab/main.tf +++ b/jupyterlab/main.tf @@ -9,6 +9,9 @@ terraform { } } +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 @@ -36,6 +39,12 @@ variable "share" { } } +variable "subdomain" { + type = bool + description = "Determines whether JupyterLab 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)." @@ -49,17 +58,18 @@ resource "coder_script" "jupyterlab" { 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/jupyterlab" }) run_on_start = true } resource "coder_app" "jupyterlab" { agent_id = var.agent_id - slug = "jupyterlab" + slug = "jupyterlab" # sync with the usage in URL display_name = "JupyterLab" - url = "http://localhost:${var.port}" + url = var.subdomain ? "http://localhost:${var.port}" : "http://localhost:${var.port}/@${data.coder_workspace_owner.me.name}/${data.coder_workspace.me.name}/apps/jupyterlab" icon = "/icon/jupyter.svg" - subdomain = true + subdomain = var.subdomain share = var.share order = var.order } diff --git a/jupyterlab/run.sh b/jupyterlab/run.sh index 0245b06..aff21b7 100755 --- a/jupyterlab/run.sh +++ b/jupyterlab/run.sh @@ -1,5 +1,9 @@ #!/usr/bin/env sh +if [ -n "${BASE_URL}" ]; then + BASE_URL_FLAG="--ServerApp.base_url=${BASE_URL}" +fi + BOLD='\033[0;1m' printf "$${BOLD}Installing jupyterlab!\n" @@ -15,11 +19,17 @@ if ! command -v jupyterlab > /dev/null 2>&1; then fi # install jupyterlab pipx install -q jupyterlab - echo "🥳 jupyterlab has been installed\n\n" + printf "%s\n\n" "🥳 jupyterlab has been installed" else - echo "🥳 jupyterlab is already installed\n\n" + printf "%s\n\n" "🥳 jupyterlab is already installed" fi -echo "👷 Starting jupyterlab in background..." -echo "check logs at ${LOG_PATH}" -$HOME/.local/bin/jupyter-lab --ServerApp.ip='0.0.0.0' --ServerApp.port=${PORT} --no-browser --ServerApp.token='' --ServerApp.password='' > ${LOG_PATH} 2>&1 & +printf "👷 Starting jupyterlab in background..." +printf "check logs at ${LOG_PATH}" +$HOME/.local/bin/jupyter-lab --no-browser \ + "$BASE_URL_FLAG" \ + --ServerApp.ip='*' \ + --ServerApp.port="${PORT}" \ + --ServerApp.token='' \ + --ServerApp.password='' \ + > "${LOG_PATH}" 2>&1 &