Compare commits

...

47 Commits

Author SHA1 Message Date
Muhammad Atif Ali
5b2f3bd599 chore: fix formatting for jfrog-oauth README (#119) 2024-01-11 00:17:20 +03:00
Muhammad Atif Ali
357bd41252 feat(jfrog): add JFrog vscode extension, CLI completion and docker support (#115) 2024-01-05 17:41:04 +03:00
Muhammad Atif Ali
382933aece chore(jfrog-oauth): update JFrog OAuth module README (#114) 2023-12-25 20:54:40 +03:00
Muhammad Atif Ali
f8faea1855 feat(vault-github): use coder_env to set VAULT_ADDR in workspace (#112) 2023-12-16 19:00:50 +03:00
Muhammad Atif Ali
1e3bd2b04b Add formatting check for shell scripts (#106) 2023-12-16 17:39:08 +03:00
Muhammad Atif Ali
1e7f91231c fix(jetbrains-gateway): fix tests (#111) 2023-12-08 04:33:53 +03:00
Muhammad Atif Ali
c6b1990225 fix(jetbrains-gateway): fix tests 2023-12-08 04:20:05 +03:00
Muhammad Atif Ali
3878e66700 fix: use agent_name in jetbrains-gateway (#110) 2023-12-08 04:10:19 +03:00
Muhammad Atif Ali
d48b68d374 chore: update screenshot for vault-github module (#109) 2023-12-08 03:12:22 +03:00
Muhammad Atif Ali
a954af73c5 chore(jetbrans-gateway): update JetBrains IDEs and remove community editions
Another try at #96. I cannot reproduce the error on deployment and dev.coder.com.

It also removes Community editions, `Rider`, and `DataGrip` as they are not supported for Remote Development.
2023-12-08 02:34:14 +03:00
Muhammad Atif Ali
90853d78d4 Add files via upload 2023-12-08 01:49:33 +03:00
Asher
9fc5eb9d29 Add missing URL to VS Code desktop module
Without this the plugin will only work if the user has happened to log
in before and that URL was previously saved.
2023-11-29 11:58:28 -09:00
Muhammad Atif Ali
6366097eea Update Coder external-auth link in README.md (#104) 2023-11-29 13:53:00 +03:00
Muhammad Atif Ali
c1800b7a85 Add Hashicorp Vault Integration (GitHub) (#105)
Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
2023-11-29 13:23:48 +03:00
Muhammad Atif Ali
98bb94c5f0 feat: add JFrog access token output to module output (#101) 2023-11-15 18:15:00 +03:00
Muhammad Atif Ali
8e3f48ce5c fix(jfrog-token)!: add attributes to fine control the token behaviour (#100) 2023-11-15 16:03:43 +03:00
Muhammad Atif Ali
73ef0dc7d0 Add JFrog (OAuth) integration module (#97) 2023-11-15 00:12:57 +03:00
Spike Curtis
4e7f1e0ffd Revert "chore(jetbrans-gateway): update JetBrains IDEs (#96)" (#98)
This reverts commit b0187c69c1.
2023-11-14 10:05:15 +03:00
Muhammad Atif Ali
b0187c69c1 chore(jetbrans-gateway): update JetBrains IDEs (#96) 2023-11-14 01:18:35 +03:00
phorcys420
4dc9eae9c9 feat: add git-commit-signing module (#94)
* feat: add git-commit-signing module

* feat(git-commit-signing): check for git and jq

* fix(git-commit-signing): only use icon once

* fix(git-commit-signing): fix typo in README

Co-authored-by: Muhammad Atif Ali <matifali@live.com>

* bun fmt

* chore: clarify readme SSH key paragraph

* fix: add `curl` as dependency

* feat: download keys to ~/.ssh/git-commit-signing

* feat: add conflict disclaimer

---------

Co-authored-by: Muhammad Atif Ali <matifali@live.com>
Co-authored-by: Atif Ali <atif@coder.com>
2023-11-03 20:42:45 +03:00
Muhammad Atif Ali
e2f4fcba4a fix(git-clone): update the required provider version (#95) 2023-10-26 12:39:12 +03:00
Eric Paulsen
08162f5894 Merge pull request #91 from coder/share-var
feat: share variable
2023-10-16 11:30:29 -04:00
Muhammad Atif Ali
4f78c20201 fmt 2023-10-16 17:59:18 +03:00
Muhammad Atif Ali
52e4f3fb6f bun fmt 2023-10-16 17:54:24 +03:00
masterwendu
e090e79d4f Add Exoscale instance type Module (#88) 2023-10-16 17:37:36 +03:00
masterwendu
24bf54d1bb Add Exoscale zone Module (#87) 2023-10-16 17:36:58 +03:00
Eric Paulsen
11d7787cb0 feat: share variable 2023-10-15 21:58:03 -04:00
Stephen Kirby
b6ec1d85a7 Merge pull request #90 from coder/code-server-extensions
code-server: fix multiple-extension installation
2023-10-13 14:49:33 -05:00
Stephen Kirby
eaf6fae789 added readme block 2023-10-13 19:43:18 +00:00
Stephen Kirby
93965edc97 applied array changes 2023-10-13 19:39:30 +00:00
Stephen Kirby
b04683ca4c arr printing 2023-10-13 19:38:20 +00:00
Stephen Kirby
7b71f610e5 arr printing 2023-10-13 19:37:40 +00:00
Stephen Kirby
7d4723336e arr printing 2023-10-13 19:36:13 +00:00
Stephen Kirby
8918d8aef5 arr printing 2023-10-13 19:35:26 +00:00
Stephen Kirby
1745c534ed complete reversion 2023-10-13 19:32:24 +00:00
Stephen Kirby
f5b7df46f2 removed shell 2023-10-13 19:30:25 +00:00
Stephen Kirby
1a71239436 invalid character debug 2023-10-13 19:29:36 +00:00
Stephen Kirby
c47cf97fd9 naming fix 2023-10-13 19:28:01 +00:00
Stephen Kirby
54fc306a95 naming fix 2023-10-13 19:26:55 +00:00
Stephen Kirby
71d7ba80a5 naming fix 2023-10-13 19:26:29 +00:00
Stephen Kirby
7f4da980d1 naming fix 2023-10-13 19:25:45 +00:00
Stephen Kirby
7fa87d3074 naming fix 2023-10-13 19:25:14 +00:00
Stephen Kirby
03a4cc01be array IFS 2023-10-13 19:20:52 +00:00
Stephen Kirby
8fac468bb4 printing arr 2023-10-13 19:18:46 +00:00
Stephen Kirby
c85bf5db4f printing arr 2023-10-13 19:13:52 +00:00
Stephen Kirby
972b5b8282 debugging arr 2023-10-13 19:01:31 +00:00
Stephen Kirby
151cb234b0 testing array reading 2023-10-13 18:58:26 +00:00
59 changed files with 1996 additions and 328 deletions

View File

@@ -20,6 +20,8 @@ jobs:
- uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Setup
run: bun install
- run: bun test
pretty:
runs-on: ubuntu-latest
@@ -28,7 +30,9 @@ jobs:
- uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Setup
run: bun install
- name: Format
run: bun fmt:ci
- name: Lint
run: bun install && bun lint
run: bun lint

1
.icons/exoscale.svg Normal file
View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 68.03 68.03"><defs><style>.cls-1{fill:#da291c;}</style></defs><title>Artboard 1</title><polygon class="cls-1" points="34.02 13.31 11.27 52.72 14.52 52.72 34.02 18.94 34.02 24.57 17.77 52.72 21.02 52.72 34.02 30.2 34.02 35.83 24.27 52.72 27.52 52.72 34.02 41.46 34.02 47.09 30.77 52.72 34.02 52.72 34.02 52.72 56.77 52.72 34.02 13.31"/></svg>

After

Width:  |  Height:  |  Size: 427 B

2
.icons/vault.svg Normal file
View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="none"><path fill="#FFD814" d="M0 0l7.971 15.516L16 0H0zm6.732 6.16h-1.27V4.89h1.27v1.27zm0-1.906h-1.27V2.985h1.27v1.269zm1.904 3.81h-1.27v-1.27h1.27v1.27zm0-1.905h-1.27V4.89h1.27v1.27zm0-1.905h-1.27V2.985h1.27v1.269zm1.894 1.905H9.26V4.89h1.27v1.27zM9.26 4.254V2.985h1.27v1.269H9.26z"/></svg>

After

Width:  |  Height:  |  Size: 506 B

BIN
.images/exoscale-custom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
.images/exoscale-zones.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
.images/jfrog-oauth.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
.images/vault-login.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

View File

@@ -1,7 +1,15 @@
#!/usr/bin/env bash
#!/usr/bin/env sh
# Convert templated variables to shell variables
# shellcheck disable=SC2269
LOG_PATH=${LOG_PATH}
# shellcheck disable=SC2034
BOLD='\033[0;1m'
# shellcheck disable=SC2059
printf "$${BOLD}Installing MODULE_NAME ...\n\n"
# Add code here
# Use varibles from the templatefile function in main.tf
# e.g. LOG_PATH, PORT, etc.
@@ -13,6 +21,6 @@ printf "👷 Starting MODULE_NAME in background...\n\n"
# 1. Use & to run it in background
# 2. redirct stdout and stderr to log files
./app >${LOG_PATH} 2>&1 &
./app > "$${LOG_PATH}" 2>&1 &
printf "check logs at ${LOG_PATH} \n\n"
printf "check logs at %s\n\n" "$${LOG_PATH}"

BIN
bun.lockb

Binary file not shown.

View File

@@ -63,6 +63,18 @@ module "settings" {
}
```
### Install multiple extensions
Just run code-server in the background, don't fetch it from GitHub:
```hcl
module "settings" {
source = "https://registry.coder.com/modules/code-server"
agent_id = coder_agent.example.id
extensions = [ "dracula-theme.theme-dracula", "ms-azuretools.vscode-docker" ]
}
```
### Offline Mode
Just run code-server in the background, don't fetch it from GitHub:

View File

@@ -62,6 +62,15 @@ variable "install_version" {
default = ""
}
variable "share" {
type = string
default = "owner"
validation {
condition = var.share == "owner" || var.share == "authenticated" || var.share == "public"
error_message = "Incorrect value. Please set either 'owner', 'authenticated', or 'public'."
}
}
resource "coder_script" "code-server" {
agent_id = var.agent_id
display_name = "code-server"
@@ -85,7 +94,7 @@ resource "coder_app" "code-server" {
url = "http://localhost:${var.port}/${var.folder != "" ? "?folder=${urlencode(var.folder)}" : ""}"
icon = "/icon/code.svg"
subdomain = false
share = "owner"
share = var.share
healthcheck {
url = "http://localhost:${var.port}/healthz"

View File

@@ -25,7 +25,8 @@ printf "🥳 code-server has been installed in ${INSTALL_PREFIX}\n\n"
CODE_SERVER="${INSTALL_PREFIX}/bin/code-server"
# Install each extension...
for extension in "$${EXTENSIONS[@]}"; do
IFS=',' read -r -a EXTENSIONLIST <<< "$${EXTENSIONS}"
for extension in "$${EXTENSIONLIST[@]}"; do
if [ -z "$extension" ]; then
continue
fi
@@ -41,9 +42,9 @@ done
if [ ! -f ~/.local/share/code-server/Machine/settings.json ]; then
echo "⚙️ Creating settings file..."
mkdir -p ~/.local/share/code-server/Machine
echo "${SETTINGS}" >~/.local/share/code-server/Machine/settings.json
echo "${SETTINGS}" > ~/.local/share/code-server/Machine/settings.json
fi
echo "👷 Running code-server in the background..."
echo "Check logs at ${LOG_PATH}!"
$CODE_SERVER --auth none --port ${PORT} >${LOG_PATH} 2>&1 &
$CODE_SERVER --auth none --port ${PORT} > ${LOG_PATH} 2>&1 &

View File

@@ -7,8 +7,9 @@ BOLD='\033[0;1m'
printf "$${BOLD}Logging into Coder...\n\n$${RESET}"
if ! coder list >/dev/null 2>&1; then
set +x; coder login --token="${CODER_USER_TOKEN}" --url="${CODER_DEPLOYMENT_URL}"
if ! coder list > /dev/null 2>&1; then
set +x
coder login --token="${CODER_USER_TOKEN}" --url="${CODER_DEPLOYMENT_URL}"
else
echo "You are already authenticated with coder."
fi

View File

@@ -0,0 +1,109 @@
---
display_name: exoscale-instance-type
description: A parameter with human readable exoscale instance names
icon: ../.icons/exoscale.svg
maintainer_github: WhizUs
verified: false
tags: [helper, parameter, instances, exoscale]
---
# exoscale-instance-type
A parameter with all Exoscale instance types. This allows developers to select
their desired virtuell machine for the workspace.
Customize the preselected parameter value:
```hcl
module "exoscale-instance-type" {
source = "https://registry.coder.com/modules/exoscale-instance-type"
default = "standard.medium"
}
resource "exoscale_compute_instance" "instance" {
type = module.exoscale-instance-type.value
...
}
resource "coder_metadata" "workspace_info" {
item {
key = "instance type"
value = module.exoscale-instance-type.name
}
}
```
![Exoscale instance types](../.images/exoscale-instance-types.png)
## Examples
### Customize type
Change the display name a type using the corresponding maps:
```hcl
module "exoscale-instance-type" {
source = "https://registry.coder.com/modules/exoscale-instance-type"
default = "standard.medium"
custom_names = {
"standard.medium": "Mittlere Instanz" # German translation
}
custom_descriptions = {
"standard.medium": "4 GB Arbeitsspeicher, 2 Kerne, 10 - 400 GB Festplatte" # German translation
}
}
resource "exoscale_compute_instance" "instance" {
type = module.exoscale-instance-type.value
...
}
resource "coder_metadata" "workspace_info" {
item {
key = "instance type"
value = module.exoscale-instance-type.name
}
}
```
![Exoscale instance types Custom](../.images/exoscale-instance-custom.png)
### Use category and exlude type
Show only gpu1 types
```hcl
module "exoscale-instance-type" {
source = "https://registry.coder.com/modules/exoscale-instance-type"
default = "gpu.large"
type_category = ["gpu"]
exclude = [
"gpu2.small",
"gpu2.medium",
"gpu2.large",
"gpu2.huge",
"gpu3.small",
"gpu3.medium",
"gpu3.large",
"gpu3.huge"
]
}
resource "exoscale_compute_instance" "instance" {
type = module.exoscale-instance-type.value
...
}
resource "coder_metadata" "workspace_info" {
item {
key = "instance type"
value = module.exoscale-instance-type.name
}
}
```
![Exoscale instance types category and exclude](../.images/exoscale-instance-exclude.png)
## Related templates
A related exoscale template will be provided soon.

View File

@@ -0,0 +1,34 @@
import { describe, expect, it } from "bun:test";
import {
runTerraformApply,
runTerraformInit,
testRequiredVariables,
} from "../test";
describe("exoscale-instance-type", async () => {
await runTerraformInit(import.meta.dir);
testRequiredVariables(import.meta.dir, {});
it("default output", async () => {
const state = await runTerraformApply(import.meta.dir, {});
expect(state.outputs.value.value).toBe("");
});
it("customized default", async () => {
const state = await runTerraformApply(import.meta.dir, {
default: "gpu3.huge",
type_category: `["gpu", "cpu"]`,
});
expect(state.outputs.value.value).toBe("gpu3.huge");
});
it("fails because of wrong categroy definition", async () => {
expect(async () => {
await runTerraformApply(import.meta.dir, {
default: "gpu3.huge",
// type_category: ["standard"] is standard
});
}).toThrow('default value "gpu3.huge" must be defined as one of options');
});
});

View File

@@ -0,0 +1,279 @@
terraform {
required_version = ">= 1.0"
required_providers {
coder = {
source = "coder/coder"
version = ">= 0.12"
}
}
}
variable "display_name" {
default = "Exoscale instance type"
description = "The display name of the parameter."
type = string
}
variable "description" {
default = "Select the exoscale instance type to use for the workspace. Check out the pricing page for more information: https://www.exoscale.com/pricing"
description = "The description of the parameter."
type = string
}
variable "default" {
default = ""
description = "The default instance type to use if no type is specified. One of [\"standard.micro\", \"standard.tiny\", \"standard.small\", \"standard.medium\", \"standard.large\", \"standard.extra\", \"standard.huge\", \"standard.mega\", \"standard.titan\", \"standard.jumbo\", \"standard.colossus\", \"cpu.extra\", \"cpu.huge\", \"cpu.mega\", \"cpu.titan\", \"memory.extra\", \"memory.huge\", \"memory.mega\", \"memory.titan\", \"storage.extra\", \"storage.huge\", \"storage.mega\", \"storage.titan\", \"storage.jumbo\", \"gpu.small\", \"gpu.medium\", \"gpu.large\", \"gpu.huge\", \"gpu2.small\", \"gpu2.medium\", \"gpu2.large\", \"gpu2.huge\", \"gpu3.small\", \"gpu3.medium\", \"gpu3.large\", \"gpu3.huge\"]"
type = string
}
variable "mutable" {
default = false
description = "Whether the parameter can be changed after creation."
type = bool
}
variable "custom_names" {
default = {}
description = "A map of custom display names for instance type IDs."
type = map(string)
}
variable "custom_descriptions" {
default = {}
description = "A map of custom descriptions for instance type IDs."
type = map(string)
}
variable "type_category" {
default = ["standard"]
description = "A list of instance type categories the user is allowed to choose. One of [\"standard\", \"cpu\", \"memory\", \"storage\", \"gpu\"]"
type = list(string)
}
variable "exclude" {
default = []
description = "A list of instance type IDs to exclude. One of [\"standard.micro\", \"standard.tiny\", \"standard.small\", \"standard.medium\", \"standard.large\", \"standard.extra\", \"standard.huge\", \"standard.mega\", \"standard.titan\", \"standard.jumbo\", \"standard.colossus\", \"cpu.extra\", \"cpu.huge\", \"cpu.mega\", \"cpu.titan\", \"memory.extra\", \"memory.huge\", \"memory.mega\", \"memory.titan\", \"storage.extra\", \"storage.huge\", \"storage.mega\", \"storage.titan\", \"storage.jumbo\", \"gpu.small\", \"gpu.medium\", \"gpu.large\", \"gpu.huge\", \"gpu2.small\", \"gpu2.medium\", \"gpu2.large\", \"gpu2.huge\", \"gpu3.small\", \"gpu3.medium\", \"gpu3.large\", \"gpu3.huge\"]"
type = list(string)
}
locals {
# https://www.exoscale.com/pricing/
standard_instances = [
{
value = "standard.micro",
name = "Standard Micro",
description = "512 MB RAM, 1 Core, 10 - 200 GB Disk"
},
{
value = "standard.tiny",
name = "Standard Tiny",
description = "1 GB RAM, 1 Core, 10 - 400 GB Disk"
},
{
value = "standard.small",
name = "Standard Small",
description = "2 GB RAM, 2 Cores, 10 - 400 GB Disk"
},
{
value = "standard.medium",
name = "Standard Medium",
description = "4 GB RAM, 2 Cores, 10 - 400 GB Disk"
},
{
value = "standard.large",
name = "Standard Large",
description = "8 GB RAM, 4 Cores, 10 - 400 GB Disk"
},
{
value = "standard.extra",
name = "Standard Extra",
description = "rge",
description = "16 GB RAM, 4 Cores, 10 - 800 GB Disk"
},
{
value = "standard.huge",
name = "Standard Huge",
description = "32 GB RAM, 8 Cores, 10 - 800 GB Disk"
},
{
value = "standard.mega",
name = "Standard Mega",
description = "64 GB RAM, 12 Cores, 10 - 800 GB Disk"
},
{
value = "standard.titan",
name = "Standard Titan",
description = "128 GB RAM, 16 Cores, 10 - 1.6 TB Disk"
},
{
value = "standard.jumbo",
name = "Standard Jumbo",
description = "256 GB RAM, 24 Cores, 10 - 1.6 TB Disk"
},
{
value = "standard.colossus",
name = "Standard Colossus",
description = "320 GB RAM, 40 Cores, 10 - 1.6 TB Disk"
}
]
cpu_instances = [
{
value = "cpu.extra",
name = "CPU Extra-Large",
description = "16 GB RAM, 8 Cores, 10 - 800 GB Disk"
},
{
value = "cpu.huge",
name = "CPU Huge",
description = "32 GB RAM, 16 Cores, 10 - 800 GB Disk"
},
{
value = "cpu.mega",
name = "CPU Mega",
description = "64 GB RAM, 32 Cores, 10 - 800 GB Disk"
},
{
value = "cpu.titan",
name = "CPU Titan",
description = "128 GB RAM, 40 Cores, 0.1 - 1.6 TB Disk"
}
]
memory_instances = [
{
value = "memory.extra",
name = "Memory Extra-Large",
description = "16 GB RAM, 2 Cores, 10 - 800 GB Disk"
},
{
value = "memory.huge",
name = "Memory Huge",
description = "32 GB RAM, 4 Cores, 10 - 800 GB Disk"
},
{
value = "memory.mega",
name = "Memory Mega",
description = "64 GB RAM, 8 Cores, 10 - 800 GB Disk"
},
{
value = "memory.titan",
name = "Memory Titan",
description = "128 GB RAM, 12 Cores, 0.1 - 1.6 TB Disk"
}
]
storage_instances = [
{
value = "storage.extra",
name = "Storage Extra-Large",
description = "16 GB RAM, 4 Cores, 1 - 2 TB Disk"
},
{
value = "storage.huge",
name = "Storage Huge",
description = "32 GB RAM, 8 Cores, 2 - 3 TB Disk"
},
{
value = "storage.mega",
name = "Storage Mega",
description = "64 GB RAM, 12 Cores, 3 - 5 TB Disk"
},
{
value = "storage.titan",
name = "Storage Titan",
description = "128 GB RAM, 16 Cores, 5 - 10 TB Disk"
},
{
value = "storage.jumbo",
name = "Storage Jumbo",
description = "225 GB RAM, 24 Cores, 10 - 15 TB Disk"
}
]
gpu_instances = [
{
value = "gpu.small",
name = "GPU1 Small",
description = "56 GB RAM, 12 Cores, 1 GPU, 100 - 800 GB Disk"
},
{
value = "gpu.medium",
name = "GPU1 Medium",
description = "90 GB RAM, 16 Cores, 2 GPU, 0.1 - 1.2 TB Disk"
},
{
value = "gpu.large",
name = "GPU1 Large",
description = "120 GB RAM, 24 Cores, 3 GPU, 0.1 - 1.6 TB Disk"
},
{
value = "gpu.huge",
name = "GPU1 Huge",
description = "225 GB RAM, 48 Cores, 4 GPU, 0.1 - 1.6 TB Disk"
},
{
value = "gpu2.small",
name = "GPU2 Small",
description = "56 GB RAM, 12 Cores, 1 GPU, 100 - 800 GB Disk"
},
{
value = "gpu2.medium",
name = "GPU2 Medium",
description = "90 GB RAM, 16 Cores, 2 GPU, 0.1 - 1.2 TB Disk"
},
{
value = "gpu2.large",
name = "GPU2 Large",
description = "120 GB RAM, 24 Cores, 3 GPU, 0.1 - 1.6 TB Disk"
},
{
value = "gpu2.huge",
name = "GPU2 Huge",
description = "225 GB RAM, 48 Cores, 4 GPU, 0.1 - 1.6 TB Disk"
},
{
value = "gpu3.small",
name = "GPU3 Small",
description = "56 GB RAM, 12 Cores, 1 GPU, 100 - 800 GB Disk"
},
{
value = "gpu3.medium",
name = "GPU3 Medium",
description = "120 GB RAM, 24 Cores, 2 GPU, 0.1 - 1.2 TB Disk"
},
{
value = "gpu3.large",
name = "GPU3 Large",
description = "224 GB RAM, 48 Cores, 4 GPU, 0.1 - 1.6 TB Disk"
},
{
value = "gpu3.huge",
name = "GPU3 Huge",
description = "448 GB RAM, 96 Cores, 8 GPU, 0.1 - 1.6 TB Disk"
}
]
}
data "coder_parameter" "instance_type" {
name = "exoscale_instance_type"
display_name = var.display_name
description = var.description
default = var.default == "" ? null : var.default
mutable = var.mutable
dynamic "option" {
for_each = [for k, v in concat(
contains(var.type_category, "standard") ? local.standard_instances : [],
contains(var.type_category, "cpu") ? local.cpu_instances : [],
contains(var.type_category, "memory") ? local.memory_instances : [],
contains(var.type_category, "storage") ? local.storage_instances : [],
contains(var.type_category, "gpu") ? local.gpu_instances : []
) : v if !(contains(var.exclude, v.value))]
content {
name = try(var.custom_names[option.value.value], option.value.name)
description = try(var.custom_descriptions[option.value.value], option.value.description)
value = option.value.value
}
}
}
output "value" {
value = data.coder_parameter.instance_type.value
}

93
exoscale-zone/README.md Normal file
View File

@@ -0,0 +1,93 @@
---
display_name: exoscale-zone
description: A parameter with human zone names and icons
icon: ../.icons/exoscale.svg
maintainer_github: WhizUs
verified: false
tags: [helper, parameter, zones, regions, exoscale]
---
# exoscale-zone
A parameter with all Exoscale zones. This allows developers to select
the zone closest to them.
Customize the preselected parameter value:
```hcl
module "exoscale-zone" {
source = "https://registry.coder.com/modules/exoscale-zone"
default = "ch-dk-2"
}
data "exoscale_compute_template" "my_template" {
zone = module.exoscale-zone.value
name = "Linux Ubuntu 22.04 LTS 64-bit"
}
resource "exoscale_compute_instance" "instance" {
zone = module.exoscale-zone.value
....
}
```
![Exoscale Zones](../.images/exoscale-zones.png)
## Examples
### Customize zones
Change the display name and icon for a zone using the corresponding maps:
```hcl
module "exoscale-zone" {
source = "https://registry.coder.com/modules/exoscale-zone"
default = "at-vie-1"
custom_names = {
"at-vie-1": "Home Vienna"
}
custom_icons = {
"at-vie-1": "/emojis/1f3e0.png"
}
}
data "exoscale_compute_template" "my_template" {
zone = module.exoscale-zone.value
name = "Linux Ubuntu 22.04 LTS 64-bit"
}
resource "exoscale_compute_instance" "instance" {
zone = module.exoscale-zone.value
....
}
```
![Exoscale Custom](../.images/exoscale-custom.png)
### Exclude regions
Hide the Switzerland zones Geneva and Zurich
```hcl
module "exoscale-zone" {
source = "https://registry.coder.com/modules/exoscale-zone"
exclude = [ "ch-gva-2", "ch-dk-2" ]
}
data "exoscale_compute_template" "my_template" {
zone = module.exoscale-zone.value
name = "Linux Ubuntu 22.04 LTS 64-bit"
}
resource "exoscale_compute_instance" "instance" {
zone = module.exoscale-zone.value
....
}
```
![Exoscale Exclude](../.images/exoscale-exclude.png)
## Related templates
An exoscale sample template will be delivered soon.

View File

@@ -0,0 +1,25 @@
import { describe, expect, it } from "bun:test";
import {
executeScriptInContainer,
runTerraformApply,
runTerraformInit,
testRequiredVariables,
} from "../test";
describe("exoscale-zone", async () => {
await runTerraformInit(import.meta.dir);
testRequiredVariables(import.meta.dir, {});
it("default output", async () => {
const state = await runTerraformApply(import.meta.dir, {});
expect(state.outputs.value.value).toBe("");
});
it("customized default", async () => {
const state = await runTerraformApply(import.meta.dir, {
default: "at-vie-1",
});
expect(state.outputs.value.value).toBe("at-vie-1");
});
});

110
exoscale-zone/main.tf Normal file
View File

@@ -0,0 +1,110 @@
terraform {
required_version = ">= 1.0"
required_providers {
coder = {
source = "coder/coder"
version = ">= 0.12"
}
}
}
variable "display_name" {
default = "Exoscale Region"
description = "The display name of the parameter."
type = string
}
variable "description" {
default = "The region to deploy workspace infrastructure."
description = "The description of the parameter."
type = string
}
variable "default" {
default = ""
description = "The default region to use if no region is specified."
type = string
}
variable "mutable" {
default = false
description = "Whether the parameter can be changed after creation."
type = bool
}
variable "custom_names" {
default = {}
description = "A map of custom display names for region IDs."
type = map(string)
}
variable "custom_icons" {
default = {}
description = "A map of custom icons for region IDs."
type = map(string)
}
variable "exclude" {
default = []
description = "A list of region IDs to exclude."
type = list(string)
}
locals {
# This is a static list because the zones don't change _that_
# frequently and including the `exoscale_zones` data source requires
# the provider, which requires a zone.
# https://www.exoscale.com/datacenters/
zones = {
"de-fra-1" = {
name = "Frankfurt - Germany"
icon = "/emojis/1f1e9-1f1ea.png"
}
"at-vie-1" = {
name = "Vienna 1 - Austria"
icon = "/emojis/1f1e6-1f1f9.png"
}
"at-vie-2" = {
name = "Vienna 2 - Austria"
icon = "/emojis/1f1e6-1f1f9.png"
}
"ch-gva-2" = {
name = "Geneva - Switzerland"
icon = "/emojis/1f1e8-1f1ed.png"
}
"ch-dk-2" = {
name = "Zurich - Switzerland"
icon = "/emojis/1f1e8-1f1ed.png"
}
"bg-sof-1" = {
name = "Sofia - Bulgaria"
icon = "/emojis/1f1e7-1f1ec.png"
}
"de-muc-1" = {
name = "Munich - Germany"
icon = "/emojis/1f1e9-1f1ea.png"
}
}
}
data "coder_parameter" "zone" {
name = "exoscale_zone"
display_name = var.display_name
description = var.description
default = var.default == "" ? null : var.default
mutable = var.mutable
dynamic "option" {
for_each = { for k, v in local.zones : k => v if !(contains(var.exclude, k)) }
content {
name = try(var.custom_names[option.key], option.value.name)
icon = try(var.custom_icons[option.key], option.value.icon)
value = option.key
}
}
}
output "value" {
value = data.coder_parameter.zone.value
}

View File

@@ -43,6 +43,15 @@ variable "folder" {
default = "~"
}
variable "share" {
type = string
default = "owner"
validation {
condition = var.share == "owner" || var.share == "authenticated" || var.share == "public"
error_message = "Incorrect value. Please set either 'owner', 'authenticated', or 'public'."
}
}
resource "coder_script" "filebrowser" {
agent_id = var.agent_id
display_name = "File Browser"
@@ -64,5 +73,5 @@ resource "coder_app" "filebrowser" {
url = "http://localhost:${var.port}"
icon = "https://raw.githubusercontent.com/filebrowser/logo/master/icon_raw.svg"
subdomain = true
share = "owner"
share = var.share
}

View File

@@ -21,6 +21,6 @@ printf "📂 Serving $${ROOT_DIR} at http://localhost:${PORT} \n\n"
printf "Running 'filebrowser --noauth --root $ROOT_DIR --port ${PORT}$${DB_FLAG}' \n\n"
filebrowser --noauth --root $ROOT_DIR --port ${PORT}$${DB_FLAG} >${LOG_PATH} 2>&1 &
filebrowser --noauth --root $ROOT_DIR --port ${PORT}$${DB_FLAG} > ${LOG_PATH} 2>&1 &
printf "📝 Logs at ${LOG_PATH} \n\n"

View File

@@ -4,7 +4,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
version = ">= 0.11"
version = ">= 0.12"
}
}
}

View File

@@ -18,7 +18,7 @@ if [ -z "$CLONE_PATH" ]; then
fi
# Check if `git` is installed...
if ! command -v git >/dev/null; then
if ! command -v git > /dev/null; then
echo "Git is not installed!"
exit 1
fi

View File

@@ -0,0 +1,24 @@
---
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
tags: [helper, git]
---
# git-commit-signing
This module downloads your SSH key from Coder and uses it to sign commits with Git.
It requires `curl` and `jq` to be installed inside your workspace.
Please observe that using the SSH key that's part of your Coder account for commit signing, means that in the event of a breach of your Coder account, or a malicious admin, someone could perform commit signing pretending to be you.
This module has a chance of conflicting with the user's dotfiles / the personalize module if one of those has configuration directives that overwrite this module's / each other's git configuration.
```hcl
module "git-commit-signing" {
source = "https://registry.coder.com/modules/git-commit-signing"
agent_id = coder_agent.example.id
}
```

View File

@@ -0,0 +1,25 @@
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."
}
resource "coder_script" "git-commit-signing" {
display_name = "Git commit signing"
icon = "https://raw.githubusercontent.com/coder/modules/main/.icons/git.svg"
script = file("${path.module}/run.sh")
run_on_start = true
agent_id = var.agent_id
}

41
git-commit-signing/run.sh Executable file
View File

@@ -0,0 +1,41 @@
#!/usr/bin/env sh
if ! command -v git > /dev/null; then
echo "git is not installed"
exit 1
fi
if ! command -v curl > /dev/null; then
echo "curl is not installed"
exit 1
fi
if ! command -v jq > /dev/null; then
echo "jq is not installed"
exit 1
fi
mkdir -p ~/.ssh/git-commit-signing
echo "Downloading SSH key"
ssh_key=$(curl --request GET \
--url "${CODER_AGENT_URL}api/v2/workspaceagents/me/gitsshkey" \
--header "Coder-Session-Token: ${CODER_AGENT_TOKEN}")
jq --raw-output ".public_key" > ~/.ssh/git-commit-signing/coder.pub << EOF
$ssh_key
EOF
jq --raw-output ".private_key" > ~/.ssh/git-commit-signing/coder << EOF
$ssh_key
EOF
chmod -R 400 ~/.ssh/git-commit-signing/coder
chmod -R 400 ~/.ssh/git-commit-signing/coder.pub
echo "Configuring git to use the SSH key"
git config --global gpg.format ssh
git config --global commit.gpgsign true
git config --global user.signingkey ~/.ssh/git-commit-signing/coder

View File

@@ -4,7 +4,7 @@ BOLD='\033[0;1m'
printf "$${BOLD}Checking git-config!\n"
# Check if git is installed
command -v git >/dev/null 2>&1 || {
command -v git > /dev/null 2>&1 || {
echo "Git is not installed!"
exit 1
}

View File

@@ -15,9 +15,9 @@ This module adds a JetBrains Gateway Button to open any workspace with a single
module "jetbrains_gateway" {
source = "https://registry.coder.com/modules/jetbrains-gateway"
agent_id = coder_agent.example.id
agent_name = "example"
folder = "/home/coder/example"
jetbrains_ides = ["GO", "WS", "IU", "IC", "PY", "PC", "PS", "CL", "RM", "DB", "RD"]
jetbrains_ides = ["GO", "WS", "IU", "PY", "PS", "CL", "RM"]
default = "PY"
}
```
@@ -31,7 +31,6 @@ module "jetbrains_gateway" {
module "jetbrains_gateway" {
source = "https://registry.coder.com/modules/jetbrains-gateway"
agent_id = coder_agent.example.id
agent_name = "example"
folder = "/home/coder/example"
jetbrains_ides = ["GO", "WS"]
default = "GO"
@@ -45,11 +44,7 @@ This module and JetBrains Gateway support the following JetBrains IDEs:
- GoLand (`GO`)
- WebStorm (`WS`)
- IntelliJ IDEA Ultimate (`IU`)
- IntelliJ IDEA Community (`IC`)
- PyCharm Professional (`PY`)
- PyCharm Community (`PC`)
- PhpStorm (`PS`)
- CLion (`CL`)
- RubyMine (`RM`)
- DataGrip (`DB`)
- Rider (`RD`)

View File

@@ -2,7 +2,6 @@ import { it, expect, describe } from "bun:test";
import {
runTerraformInit,
testRequiredVariables,
executeScriptInContainer,
runTerraformApply,
} from "../test";
@@ -11,20 +10,19 @@ describe("jetbrains-gateway", async () => {
await testRequiredVariables(import.meta.dir, {
agent_id: "foo",
agent_name: "bar",
agent_name: "foo",
folder: "/baz/",
jetbrains_ides: '["IU", "IC", "PY"]',
});
it("default to first ide", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
agent_name: "bar",
agent_name: "foo",
folder: "/baz/",
jetbrains_ides: '["IU", "IC", "PY"]',
jetbrains_ides: '["IU", "GO", "PY"]',
});
expect(state.outputs.jetbrains_ides.value).toBe(
'["IU","232.9921.47","https://download.jetbrains.com/idea/ideaIU-2023.2.2.tar.gz"]',
'["IU","232.10203.10","https://download.jetbrains.com/idea/ideaIU-2023.2.4.tar.gz"]',
);
});
});

View File

@@ -16,7 +16,7 @@ variable "agent_id" {
variable "agent_name" {
type = string
description = "The name of a Coder agent."
description = "Agent name."
}
variable "folder" {
@@ -33,13 +33,14 @@ variable "default" {
variable "jetbrains_ides" {
type = list(string)
description = "The list of IDE product codes."
default = ["IU", "PS", "WS", "PY", "CL", "GO", "RM"]
validation {
condition = (
alltrue([
for code in var.jetbrains_ides : contains(["IU", "IC", "PS", "WS", "PY", "PC", "CL", "GO", "DB", "RD", "RM"], code)
for code in var.jetbrains_ides : contains(["IU", "PS", "WS", "PY", "CL", "GO", "RM"], code)
])
)
error_message = "The jetbrains_ides must be a list of valid product codes. Valid product codes are: IU, IC, PS, WS, PY, PC, CL, GO, DB, RD, RM."
error_message = "The jetbrains_ides must be a list of valid product codes. Valid product codes are IU, PS, WS, PY, CL, GO, RM."
}
# check if the list is empty
validation {
@@ -58,57 +59,37 @@ locals {
"GO" = {
icon = "/icon/goland.svg",
name = "GoLand",
value = jsonencode(["GO", "232.9921.53", "https://download.jetbrains.com/go/goland-2023.2.2.tar.gz"])
value = jsonencode(["GO", "232.10203.20", "https://download.jetbrains.com/go/goland-2023.2.4.tar.gz"])
},
"WS" = {
icon = "/icon/webstorm.svg",
name = "WebStorm",
value = jsonencode(["WS", "232.9921.42", "https://download.jetbrains.com/webstorm/WebStorm-2023.2.2.tar.gz"])
value = jsonencode(["WS", "232.10203.14", "https://download.jetbrains.com/webstorm/WebStorm-2023.2.4.tar.gz"])
},
"IU" = {
icon = "/icon/intellij.svg",
name = "IntelliJ IDEA Ultimate",
value = jsonencode(["IU", "232.9921.47", "https://download.jetbrains.com/idea/ideaIU-2023.2.2.tar.gz"])
},
"IC" = {
icon = "/icon/intellij.svg",
name = "IntelliJ IDEA Community",
value = jsonencode(["IC", "232.9921.47", "https://download.jetbrains.com/idea/ideaIC-2023.2.2.tar.gz"])
value = jsonencode(["IU", "232.10203.10", "https://download.jetbrains.com/idea/ideaIU-2023.2.4.tar.gz"])
},
"PY" = {
icon = "/icon/pycharm.svg",
name = "PyCharm Professional",
value = jsonencode(["PY", "232.9559.58", "https://download.jetbrains.com/python/pycharm-professional-2023.2.1.tar.gz"])
value = jsonencode(["PY", "232.10203.26", "https://download.jetbrains.com/python/pycharm-professional-2023.2.4.tar.gz"])
},
"PC" = {
icon = "/icon/pycharm.svg",
name = "PyCharm Community",
value = jsonencode(["PC", "232.9559.58", "https://download.jetbrains.com/python/pycharm-community-2023.2.1.tar.gz"])
},
"RD" = {
icon = "/icon/rider.svg",
name = "Rider",
value = jsonencode(["RD", "232.9559.61", "https://download.jetbrains.com/rider/JetBrains.Rider-2023.2.1.tar.gz"])
}
"CL" = {
icon = "/icon/clion.svg",
name = "CLion",
value = jsonencode(["CL", "232.9921.42", "https://download.jetbrains.com/cpp/CLion-2023.2.2.tar.gz"])
},
"DB" = {
icon = "/icon/datagrip.svg",
name = "DataGrip",
value = jsonencode(["DB", "232.9559.28", "https://download.jetbrains.com/datagrip/datagrip-2023.2.1.tar.gz"])
},
"PS" = {
icon = "/icon/phpstorm.svg",
name = "PhpStorm",
value = jsonencode(["PS", "232.9559.64", "https://download.jetbrains.com/webide/PhpStorm-2023.2.1.tar.gz"])
value = jsonencode(["PS", "232.10072.32", "https://download.jetbrains.com/webide/PhpStorm-2023.2.3.tar.gz"])
},
"RM" = {
icon = "/icon/rubymine.svg",
name = "RubyMine",
value = jsonencode(["RM", "232.9921.48", "https://download.jetbrains.com/ruby/RubyMine-2023.2.2.tar.gz"])
value = jsonencode(["RM", "232.10203.15", "https://download.jetbrains.com/ruby/RubyMine-2023.2.4.tar.gz"])
}
}
}

142
jfrog-oauth/README.md Normal file
View File

@@ -0,0 +1,142 @@
---
display_name: JFrog (OAuth)
description: Install the JF CLI and authenticate with Artifactory using OAuth.
icon: ../.icons/jfrog.svg
maintainer_github: coder
partner_github: jfrog
verified: true
tags: [integration, jfrog]
---
# JFrog
Install the JF CLI and authenticate package managers with Artifactory using OAuth configured via the Coder `external-auth` feature.
<p align="center">
<img src='../.images/jfrog-oauth.png' alt="JFrog OAuth" width='600'>
</p>
```hcl
module "jfrog" {
source = "https://registry.coder.com/modules/jfrog-oauth"
agent_id = coder_agent.example.id
jfrog_url = "https://jfrog.example.com"
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"
}
}
```
> Note
> This module does not install `npm`, `go`, `pip`, etc but only configure them. You need to handle the installation of these tools yourself.
## Prerequisites
Coder [`external-auth`](https://coder.com/docs/v2/latest/admin/external-auth) configured with Artifactory. This requires a [custom integration](https://jfrog.com/help/r/jfrog-installation-setup-documentation/enable-new-integrations) in Artifactory with **Callback URL** set to `https://<your-coder-url>/external-auth/jfrog/callback`.
To set this up,
1. Modify your `values.yaml` for JFrog Artifactory to add,
```yaml
artifactory:
enabled: true
frontend:
extraEnvironmentVariables:
- name: JF_FRONTEND_FEATURETOGGLER_ACCESSINTEGRATION
value: "true"
access:
accessConfig:
integrations-enabled: true
integration-templates:
- id: "1"
name: "CODER"
redirect-uri: "https://CODER_URL/external-auth/jfrog/callback"
scope: "applied-permissions/user"
```
> Note
> Replace `CODER_URL` with your Coder deployment URL, e.g., <coder.example.com>
2. Add a new [external authetication](https://coder.com/docs/v2/latest/admin/external-auth) to Coder by setting these env variables,
```env
# JFrog Artifactory External Auth
CODER_EXTERNAL_AUTH_1_ID="jfrog"
CODER_EXTERNAL_AUTH_1_TYPE="jfrog"
CODER_EXTERNAL_AUTH_1_CLIENT_ID="YYYYYYYYYYYYYYY"
CODER_EXTERNAL_AUTH_1_CLIENT_SECRET="XXXXXXXXXXXXXXXXXXX"
CODER_EXTERNAL_AUTH_1_DISPLAY_NAME="JFrog Artifactory"
CODER_EXTERNAL_AUTH_1_DISPLAY_ICON="/icon/jfrog.svg"
CODER_EXTERNAL_AUTH_1_AUTH_URL="https://JFROG_URL/ui/authorization"
CODER_EXTERNAL_AUTH_1_TOKEN_URL="https://JFROG_URL/access/api/v1/integrations/YYYYYYYYYYYYYYY/token"
CODER_EXTERNAL_AUTH_1_SCOPES="applied-permissions/user"
```
> Note
> Replace `JFROG_URL` with your JFrog Artifactory base URL, e.g., <artifactory.example.com>
## Examples
Configure the Python pip package manager to fetch packages from Artifactory while mapping the Coder email to the Artifactory username.
```hcl
module "jfrog" {
source = "https://registry.coder.com/modules/jfrog-oauth"
agent_id = coder_agent.example.id
jfrog_url = "https://jfrog.example.com"
auth_method = "oauth"
username_field = "email"
package_managers = {
"pypi": "pypi"
}
}
```
You should now be able to install packages from Artifactory using both the `jf pip` and `pip` command.
```shell
jf pip install requests
```
```shell
pip install requests
```
### Configure code-server with JFrog extension
The [JFrog extension](https://open-vsx.org/extension/JFrog/jfrog-vscode-extension) for VS Code allows you to interact with Artifactory from within the IDE.
```hcl
module "jfrog" {
source = "https://registry.coder.com/modules/jfrog-oauth"
agent_id = coder_agent.example.id
jfrog_url = "https://jfrog.example.com"
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"
}
}
```
### Using the access token in other terraform resources
JFrog Access token is also available as a terraform output. You can use it in other terraform resources. For example, you can use it to configure an [Artifactory docker registry](https://jfrog.com/help/r/jfrog-artifactory-documentation/docker-registry) with the [docker terraform provider](https://registry.terraform.io/providers/kreuzwerker/docker/latest/docs).
```hcl
provider "docker" {
...
registry_auth {
address = "https://YYYY.jfrog.io/artifactory/api/docker/REPO-KEY"
username = module.jfrog.username
password = module.jfrog.access_token
}
}
```

19
jfrog-oauth/main.test.ts Normal file
View File

@@ -0,0 +1,19 @@
import { serve } from "bun";
import { describe } from "bun:test";
import {
createJSONResponse,
runTerraformInit,
testRequiredVariables,
} from "../test";
describe("jfrog-oauth", async () => {
await runTerraformInit(import.meta.dir);
testRequiredVariables(import.meta.dir, {
agent_id: "some-agent-id",
jfrog_url: "http://localhost:8081",
package_managers: "{}",
});
});
//TODO add more tests

131
jfrog-oauth/main.tf Normal file
View File

@@ -0,0 +1,131 @@
terraform {
required_version = ">= 1.0"
required_providers {
coder = {
source = "coder/coder"
version = ">= 0.12.4"
}
}
}
variable "jfrog_url" {
type = string
description = "JFrog instance URL. e.g. https://myartifactory.jfrog.io"
# ensue the URL is HTTPS or HTTP
validation {
condition = can(regex("^(https|http)://", var.jfrog_url))
error_message = "jfrog_url must be a valid URL starting with either 'https://' or 'http://'"
}
}
variable "username_field" {
type = string
description = "The field to use for the artifactory username. i.e. Coder username or email."
default = "username"
validation {
condition = can(regex("^(email|username)$", var.username_field))
error_message = "username_field must be either 'email' or 'username'"
}
}
variable "external_auth_id" {
type = string
description = "JFrog external auth ID. Default: 'jfrog'"
default = "jfrog"
}
variable "agent_id" {
type = string
description = "The ID of a Coder agent."
}
variable "configure_code_server" {
type = bool
description = "Set to true to configure code-server to use JFrog."
default = false
}
variable "package_managers" {
type = map(string)
description = <<EOF
A map of package manager names to their respective artifactory repositories.
For example:
{
"npm": "YOUR_NPM_REPO_KEY",
"go": "YOUR_GO_REPO_KEY",
"pypi": "YOUR_PYPI_REPO_KEY",
"docker": "YOUR_DOCKER_REPO_KEY"
}
EOF
}
locals {
# The username field to use for artifactory
username = var.username_field == "email" ? data.coder_workspace.me.owner_email : data.coder_workspace.me.owner
jfrog_host = replace(var.jfrog_url, "https://", "")
}
data "coder_workspace" "me" {}
data "coder_external_auth" "jfrog" {
id = var.external_auth_id
}
resource "coder_script" "jfrog" {
agent_id = var.agent_id
display_name = "jfrog"
icon = "/icon/jfrog.svg"
script = templatefile("${path.module}/run.sh", {
JFROG_URL : var.jfrog_url,
JFROG_HOST : local.jfrog_host,
ARTIFACTORY_USERNAME : local.username,
ARTIFACTORY_EMAIL : data.coder_workspace.me.owner_email,
ARTIFACTORY_ACCESS_TOKEN : data.coder_external_auth.jfrog.access_token,
CONFIGURE_CODE_SERVER : var.configure_code_server,
REPOSITORY_NPM : lookup(var.package_managers, "npm", ""),
REPOSITORY_GO : lookup(var.package_managers, "go", ""),
REPOSITORY_PYPI : lookup(var.package_managers, "pypi", ""),
REPOSITORY_DOCKER : lookup(var.package_managers, "docker", ""),
})
run_on_start = true
}
resource "coder_env" "jfrog_ide_url" {
count = var.configure_code_server ? 1 : 0
agent_id = var.agent_id
name = "JFROG_IDE_URL"
value = var.jfrog_url
}
resource "coder_env" "jfrog_ide_access_token" {
count = var.configure_code_server ? 1 : 0
agent_id = var.agent_id
name = "JFROG_IDE_ACCESS_TOKEN"
value = data.coder_external_auth.jfrog.access_token
}
resource "coder_env" "jfrog_ide_store_connection" {
count = var.configure_code_server ? 1 : 0
agent_id = var.agent_id
name = "JFROG_IDE_STORE_CONNECTION"
value = true
}
resource "coder_env" "goproxy" {
count = lookup(var.package_managers, "go", "") == "" ? 0 : 1
agent_id = var.agent_id
name = "GOPROXY"
value = "https://${local.username}:${data.coder_external_auth.jfrog.access_token}@${local.jfrog_host}/artifactory/api/go/${lookup(var.package_managers, "go", "")}"
}
output "access_token" {
description = "value of the JFrog access token"
value = data.coder_external_auth.jfrog.access_token
sensitive = true
}
output "username" {
description = "value of the JFrog username"
value = local.username
}

122
jfrog-oauth/run.sh Normal file
View File

@@ -0,0 +1,122 @@
#!/usr/bin/env bash
BOLD='\033[0;1m'
# check if JFrog CLI is already installed
if command -v jf > /dev/null 2>&1; then
echo "✅ JFrog CLI is already installed, skipping installation."
else
echo "📦 Installing JFrog CLI..."
curl -fL https://install-cli.jfrog.io | sudo sh
sudo chmod 755 /usr/local/bin/jf
fi
# 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 0
# Set the configured server as the default.
jf c use 0
# 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."
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}
EOF
echo "//${JFROG_HOST}/artifactory/api/npm/${REPOSITORY_NPM}/:_authToken=${ARTIFACTORY_ACCESS_TOKEN}" >> ~/.npmrc
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."
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
EOF
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."
else
echo "🐹 Configuring go..."
jf goc --global --repo-resolve "${REPOSITORY_GO}"
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."
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
else
echo "🤔 no docker is installed, skipping docker configuration."
fi
fi
# Install the JFrog vscode extension for code-server.
if [ "${CONFIGURE_CODE_SERVER}" == "true" ]; then
while ! [ -x /tmp/code-server/bin/code-server ]; do
counter=0
if [ $counter -eq 60 ]; then
echo "Timed out waiting for /tmp/code-server/bin/code-server to be installed."
exit 1
fi
echo "Waiting for /tmp/code-server/bin/code-server to be installed..."
sleep 1
((counter++))
done
echo "📦 Installing JFrog extension..."
/tmp/code-server/bin/code-server --install-extension jfrog.jfrog-vscode-extension
echo "🥳 JFrog extension installed!"
else
echo "🤔 Skipping JFrog extension installation. Set configure_code_server to true to install the JFrog extension."
fi
# Configure the JFrog CLI completion
echo "📦 Configuring JFrog CLI completion..."
# Get the user's shell
SHELLNAME=$(grep "^$USER" /etc/passwd | awk -F':' '{print $7}' | awk -F'/' '{print $NF}')
# Generate the completion script
jf completion $SHELLNAME --install
# 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
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
echo "autoload -Uz compinit" >> ~/.zshrc
echo "compinit" >> ~/.zshrc
echo 'source "$HOME/.jfrog/jfrog_zsh_completion"' >> ~/.zshrc
echo "# END: jf CLI shell completion" >> ~/.zshrc
else
echo "🥳 ~/.zshrc already contains jf CLI shell completion configuration, skipping."
fi
else
echo "🤔 ~/.bashrc or ~/.zshrc does not exist, skipping jf CLI shell completion configuration."
fi

108
jfrog-token/README.md Normal file
View File

@@ -0,0 +1,108 @@
---
display_name: JFrog (Token)
description: Install the JF CLI and authenticate with Artifactory using Artifactory terraform provider.
icon: ../.icons/jfrog.svg
maintainer_github: coder
partner_github: jfrog
verified: true
tags: [integration, jfrog]
---
# JFrog
Install the JF CLI and authenticate package managers with Artifactory using Artifactory terraform provider.
```hcl
module "jfrog" {
source = "https://registry.coder.com/modules/jfrog-token"
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"
}
}
```
Get a JFrog access token from your Artifactory instance. The token must be an [admin token](https://registry.terraform.io/providers/jfrog/artifactory/latest/docs#access-token). It is recommended to store the token in a secret terraform variable.
```hcl
variable "artifactory_access_token" {
type = string
sensitive = true
}
```
> Note
> This module does not install `npm`, `go`, `pip`, etc but only configure them. You need to handle the installation of these tools yourself.
![JFrog](../.images/jfrog.png)
## Examples
### Configure npm, go, and pypi to use Artifactory local repositories
```hcl
module "jfrog" {
source = "https://registry.coder.com/modules/jfrog-token"
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"
}
}
```
You should now be able to install packages from Artifactory using both the `jf npm`, `jf go`, `jf pip` and `npm`, `go`, `pip` commands.
```shell
jf npm install prettier
jf go get github.com/golang/example/hello
jf pip install requests
```
```shell
npm install prettier
go get github.com/golang/example/hello
pip install requests
```
### Configure code-server with JFrog extension
The [JFrog extension](https://open-vsx.org/extension/JFrog/jfrog-vscode-extension) for VS Code allows you to interact with Artifactory from within the IDE.
```hcl
module "jfrog" {
source = "https://registry.coder.com/modules/jfrog-token"
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"
}
}
```
### Using the access token in other terraform resources
JFrog Access token is also available as a terraform output. You can use it in other terraform resources. For example, you can use it to configure an [Artifactory docker registry](https://jfrog.com/help/r/jfrog-artifactory-documentation/docker-registry) with the [docker terraform provider](https://registry.terraform.io/providers/kreuzwerker/docker/latest/docs).
```hcl
provider "docker" {
...
registry_auth {
address = "https://YYYY.jfrog.io/artifactory/api/docker/REPO-KEY"
username = module.jfrog.username
password = module.jfrog.access_token
}
}
```

View File

@@ -6,7 +6,7 @@ import {
testRequiredVariables,
} from "../test";
describe("jfrog", async () => {
describe("jfrog-token", async () => {
await runTerraformInit(import.meta.dir);
// Run a fake JFrog server so the provider can initialize
@@ -25,7 +25,7 @@ describe("jfrog", async () => {
return createJSONResponse({
token_id: "xxx",
access_token: "xxx",
scope: "any",
scopes: "any",
});
return createJSONResponse({});
},

164
jfrog-token/main.tf Normal file
View File

@@ -0,0 +1,164 @@
terraform {
required_version = ">= 1.0"
required_providers {
coder = {
source = "coder/coder"
version = ">= 0.12.4"
}
artifactory = {
source = "registry.terraform.io/jfrog/artifactory"
version = "~> 10.0.2"
}
}
}
variable "jfrog_url" {
type = string
description = "JFrog instance URL. e.g. https://myartifactory.jfrog.io"
# ensue the URL is HTTPS or HTTP
validation {
condition = can(regex("^(https|http)://", var.jfrog_url))
error_message = "jfrog_url must be a valid URL starting with either 'https://' or 'http://'"
}
}
variable "artifactory_access_token" {
type = string
description = "The admin-level access token to use for JFrog."
}
variable "check_license" {
type = bool
description = "Toggle for pre-flight checking of Artifactory license. Default to `true`."
default = true
}
variable "refreshable" {
type = bool
description = "Is this token refreshable? Default is `false`."
default = false
}
variable "expires_in" {
type = number
description = "The amount of time, in seconds, it would take for the token to expire."
default = null
}
variable "username_field" {
type = string
description = "The field to use for the artifactory username. Default `username`."
default = "username"
validation {
condition = can(regex("^(email|username)$", var.username_field))
error_message = "username_field must be either 'email' or 'username'"
}
}
variable "agent_id" {
type = string
description = "The ID of a Coder agent."
}
variable "configure_code_server" {
type = bool
description = "Set to true to configure code-server to use JFrog."
default = false
}
variable "package_managers" {
type = map(string)
description = <<EOF
A map of package manager names to their respective artifactory repositories.
For example:
{
"npm": "YOUR_NPM_REPO_KEY",
"go": "YOUR_GO_REPO_KEY",
"pypi": "YOUR_PYPI_REPO_KEY",
"docker": "YOUR_DOCKER_REPO_KEY"
}
EOF
}
locals {
# The username field to use for artifactory
username = var.username_field == "email" ? data.coder_workspace.me.owner_email : data.coder_workspace.me.owner
jfrog_host = replace(var.jfrog_url, "https://", "")
}
# Configure the Artifactory provider
provider "artifactory" {
url = join("/", [var.jfrog_url, "artifactory"])
access_token = var.artifactory_access_token
check_license = var.check_license
}
resource "artifactory_scoped_token" "me" {
# This is hacky, but on terraform plan the data source gives empty strings,
# which fails validation.
username = length(local.username) > 0 ? local.username : "dummy"
scopes = ["applied-permissions/user"]
refreshable = var.refreshable
expires_in = var.expires_in
}
data "coder_workspace" "me" {}
resource "coder_script" "jfrog" {
agent_id = var.agent_id
display_name = "jfrog"
icon = "/icon/jfrog.svg"
script = templatefile("${path.module}/run.sh", {
JFROG_URL : var.jfrog_url,
JFROG_HOST : local.jfrog_host,
ARTIFACTORY_USERNAME : local.username,
ARTIFACTORY_EMAIL : data.coder_workspace.me.owner_email,
ARTIFACTORY_ACCESS_TOKEN : artifactory_scoped_token.me.access_token,
CONFIGURE_CODE_SERVER : var.configure_code_server,
REPOSITORY_NPM : lookup(var.package_managers, "npm", ""),
REPOSITORY_GO : lookup(var.package_managers, "go", ""),
REPOSITORY_PYPI : lookup(var.package_managers, "pypi", ""),
REPOSITORY_DOCKER : lookup(var.package_managers, "docker", ""),
})
run_on_start = true
}
resource "coder_env" "jfrog_ide_url" {
count = var.configure_code_server ? 1 : 0
agent_id = var.agent_id
name = "JFROG_IDE_URL"
value = var.jfrog_url
}
resource "coder_env" "jfrog_ide_access_token" {
count = var.configure_code_server ? 1 : 0
agent_id = var.agent_id
name = "JFROG_IDE_ACCESS_TOKEN"
value = artifactory_scoped_token.me.access_token
}
resource "coder_env" "jfrog_ide_store_connection" {
count = var.configure_code_server ? 1 : 0
agent_id = var.agent_id
name = "JFROG_IDE_STORE_CONNECTION"
value = true
}
resource "coder_env" "goproxy" {
count = lookup(var.package_managers, "go", "") == "" ? 0 : 1
agent_id = var.agent_id
name = "GOPROXY"
value = "https://${local.username}:${artifactory_scoped_token.me.access_token}@${local.jfrog_host}/artifactory/api/go/${lookup(var.package_managers, "go", "")}"
}
output "access_token" {
description = "value of the JFrog access token"
value = artifactory_scoped_token.me.access_token
sensitive = true
}
output "username" {
description = "value of the JFrog username"
value = local.username
}

122
jfrog-token/run.sh Normal file
View File

@@ -0,0 +1,122 @@
#!/usr/bin/env bash
BOLD='\033[0;1m'
# check if JFrog CLI is already installed
if command -v jf > /dev/null 2>&1; then
echo "✅ JFrog CLI is already installed, skipping installation."
else
echo "📦 Installing JFrog CLI..."
curl -fL https://install-cli.jfrog.io | sudo sh
sudo chmod 755 /usr/local/bin/jf
fi
# 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 0
# Set the configured server as the default.
jf c use 0
# 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."
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}
EOF
echo "//${JFROG_HOST}/artifactory/api/npm/${REPOSITORY_NPM}/:_authToken=${ARTIFACTORY_ACCESS_TOKEN}" >> ~/.npmrc
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."
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
EOF
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."
else
echo "🐹 Configuring go..."
jf goc --global --repo-resolve "${REPOSITORY_GO}"
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."
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
else
echo "🤔 no docker is installed, skipping docker configuration."
fi
fi
# Install the JFrog vscode extension for code-server.
if [ "${CONFIGURE_CODE_SERVER}" == "true" ]; then
while ! [ -x /tmp/code-server/bin/code-server ]; do
counter=0
if [ $counter -eq 60 ]; then
echo "Timed out waiting for /tmp/code-server/bin/code-server to be installed."
exit 1
fi
echo "Waiting for /tmp/code-server/bin/code-server to be installed..."
sleep 1
((counter++))
done
echo "📦 Installing JFrog extension..."
/tmp/code-server/bin/code-server --install-extension jfrog.jfrog-vscode-extension
echo "🥳 JFrog extension installed!"
else
echo "🤔 Skipping JFrog extension installation. Set configure_code_server to true to install the JFrog extension."
fi
# Configure the JFrog CLI completion
echo "📦 Configuring JFrog CLI completion..."
# Get the user's shell
SHELLNAME=$(grep "^$USER" /etc/passwd | awk -F':' '{print $7}' | awk -F'/' '{print $NF}')
# Generate the completion script
jf completion $SHELLNAME --install
# 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
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
echo "autoload -Uz compinit" >> ~/.zshrc
echo "compinit" >> ~/.zshrc
echo 'source "$HOME/.jfrog/jfrog_zsh_completion"' >> ~/.zshrc
echo "# END: jf CLI shell completion" >> ~/.zshrc
else
echo "🥳 ~/.zshrc already contains jf CLI shell completion configuration, skipping."
fi
else
echo "🤔 ~/.bashrc or ~/.zshrc does not exist, skipping jf CLI shell completion configuration."
fi

View File

@@ -1,56 +0,0 @@
---
display_name: JFrog
description: Install the JF CLI and authenticate with Artifactory
icon: ../.icons/jfrog.svg
maintainer_github: coder
partner_github: jfrog
verified: true
tags: [integration]
---
# JFrog
Install the JF CLI and authenticate package managers with Artifactory.
```hcl
module "jfrog" {
source = "https://registry.coder.com/modules/jfrog"
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-remote",
"go": "go-remote",
"pypi": "pypi-remote"
}
}
```
Get a JFrog access token from your Artifactory instance. The token must have admin permissions. It is recommended to store the token in a secret terraform variable.
```hcl
variable "artifactory_access_token" {
type = string
sensitive = true
}
```
![JFrog](../.images/jfrog.png)
## Examples
### Configure npm, go, and pypi to use Artifactory local repositories
```hcl
module "jfrog" {
source = "https://registry.coder.com/modules/jfrog"
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"
}
}
```

View File

@@ -1,71 +0,0 @@
terraform {
required_version = ">= 1.0"
required_providers {
coder = {
source = "coder/coder"
version = ">= 0.12"
}
artifactory = {
source = "registry.terraform.io/jfrog/artifactory"
version = "~> 8.4.0"
}
}
}
variable "jfrog_url" {
type = string
description = "JFrog instance URL. e.g. https://YYY.jfrog.io"
}
variable "artifactory_access_token" {
type = string
description = "The admin-level access token to use for JFrog."
}
# Configure the Artifactory provider
provider "artifactory" {
url = join("/", [var.jfrog_url, "artifactory"])
access_token = var.artifactory_access_token
}
resource "artifactory_scoped_token" "me" {
# This is hacky, but on terraform plan the data source gives empty strings,
# which fails validation.
username = length(data.coder_workspace.me.owner_email) > 0 ? data.coder_workspace.me.owner_email : "plan"
}
variable "agent_id" {
type = string
description = "The ID of a Coder agent."
}
variable "package_managers" {
type = map(string)
description = <<EOF
A map of package manager names to their respective artifactory repositories.
For example:
{
"npm": "npm-local",
"go": "go-local",
"pypi": "pypi-local"
}
EOF
}
data "coder_workspace" "me" {}
resource "coder_script" "jfrog" {
agent_id = var.agent_id
display_name = "jfrog"
icon = "/icon/jfrog.svg"
script = templatefile("${path.module}/run.sh", {
JFROG_URL : var.jfrog_url,
JFROG_HOST : replace(var.jfrog_url, "https://", ""),
ARTIFACTORY_USERNAME : data.coder_workspace.me.owner_email,
ARTIFACTORY_ACCESS_TOKEN : artifactory_scoped_token.me.access_token,
REPOSITORY_NPM : lookup(var.package_managers, "npm", ""),
REPOSITORY_GO : lookup(var.package_managers, "go", ""),
REPOSITORY_PYPI : lookup(var.package_managers, "pypi", ""),
})
run_on_start = true
}

View File

@@ -1,49 +0,0 @@
#!/usr/bin/env sh
BOLD='\033[0;1m'
printf "$${BOLD}Installing JFrog CLI..."
# Install the JFrog CLI.
curl -fL https://install-cli.jfrog.io | sudo sh
sudo chmod 755 /usr/local/bin/jf
# The jf CLI checks $CI when determining whether to use interactive
# flows.
export CI=true
# Authenticate with the JFrog CLI.
jf c rm 0 || true
echo "${ARTIFACTORY_ACCESS_TOKEN}" | jf c add --access-token-stdin --url "${JFROG_URL}" 0
# Configure the `npm` CLI to use the Artifactory "npm" repository.
if [ -z "${REPOSITORY_NPM}" ]; then
echo "🤔 REPOSITORY_NPM is not set, skipping npm configuration."
else
echo "📦 Configuring npm..."
jf npmc --global --repo-resolve "${JFROG_URL}/artifactory/api/npm/${REPOSITORY_NPM}"
cat <<EOF >~/.npmrc
email = ${ARTIFACTORY_USERNAME}
registry = ${JFROG_URL}/artifactory/api/npm/${REPOSITORY_NPM}
EOF
jf rt curl /api/npm/auth >>~/.npmrc
fi
# Configure the `pip` to use the Artifactory "python" repository.
if [ -z "${REPOSITORY_PYPI}" ]; then
echo "🤔 REPOSITORY_PYPI is not set, skipping pip configuration."
else
echo "🐍 Configuring pip..."
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
EOF
fi
# Set GOPROXY to use the Artifactory "go" repository.
if [ -z "${REPOSITORY_GO}" ]; then
echo "🤔 REPOSITORY_GO is not set, skipping go configuration."
else
echo "🐹 Configuring go..."
export GOPROXY="https://${ARTIFACTORY_USERNAME}:${ARTIFACTORY_ACCESS_TOKEN}@${JFROG_HOST}/artifactory/api/go/${REPOSITORY_GO}"
fi
echo "🥳 Configuration complete!"

View File

@@ -27,6 +27,15 @@ variable "port" {
default = 19999
}
variable "share" {
type = string
default = "owner"
validation {
condition = var.share == "owner" || var.share == "authenticated" || var.share == "public"
error_message = "Incorrect value. Please set either 'owner', 'authenticated', or 'public'."
}
}
resource "coder_script" "jupyter-notebook" {
agent_id = var.agent_id
display_name = "jupyter-notebook"
@@ -45,5 +54,5 @@ resource "coder_app" "jupyter-notebook" {
url = "http://localhost:${var.port}"
icon = "/icon/jupyter.svg"
subdomain = true
share = "owner"
share = var.share
}

View File

@@ -5,10 +5,10 @@ BOLD='\033[0;1m'
printf "$${BOLD}Installing jupyter-notebook!\n"
# check if jupyter-notebook is installed
if ! command -v jupyter-notebook >/dev/null 2>&1; then
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
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"
exit 1
@@ -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 &

View File

@@ -27,6 +27,15 @@ variable "port" {
default = 19999
}
variable "share" {
type = string
default = "owner"
validation {
condition = var.share == "owner" || var.share == "authenticated" || var.share == "public"
error_message = "Incorrect value. Please set either 'owner', 'authenticated', or 'public'."
}
}
resource "coder_script" "jupyterlab" {
agent_id = var.agent_id
display_name = "jupyterlab"
@@ -45,5 +54,5 @@ resource "coder_app" "jupyterlab" {
url = "http://localhost:${var.port}"
icon = "/icon/jupyter.svg"
subdomain = true
share = "owner"
share = var.share
}

View File

@@ -5,10 +5,10 @@ BOLD='\033[0;1m'
printf "$${BOLD}Installing jupyterlab!\n"
# check if jupyterlab is installed
if ! command -v jupyterlab >/dev/null 2>&1; then
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
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"
exit 1
@@ -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 &

View File

@@ -2,16 +2,17 @@
"name": "modules",
"scripts": {
"test": "bun test",
"fmt": "bun x prettier -w **/*.ts **/*.md *.md && terraform fmt **/*.tf",
"fmt:ci": "bun x prettier --check **/*.ts **/*.md *.md && terraform fmt -check **/*.tf",
"fmt": "bun x prettier --plugin prettier-plugin-sh -w **/*.sh .sample/run.sh new.sh **/*.ts **/*.md *.md && terraform fmt **/*.tf .sample/main.tf",
"fmt:ci": "bun x prettier --plugin prettier-plugin-sh --check **/*.sh .sample/run.sh new.sh **/*.ts **/*.md *.md && terraform fmt -check **/*.tf .sample/main.tf",
"lint": "bun run lint.ts"
},
"devDependencies": {
"bun-types": "^1.0.3",
"bun-types": "^1.0.18",
"gray-matter": "^4.0.3",
"marked": "^9.0.3"
"marked": "^11.1.0",
"prettier-plugin-sh": "^0.13.1"
},
"peerDependencies": {
"typescript": "^5.0.0"
"typescript": "^5.3.3"
}
}

View File

@@ -1,14 +1,15 @@
#!/usr/bin/env sh
PROVIDER_ID=${PROVIDER_ID}
SLACK_MESSAGE=$(cat << "EOF"
SLACK_MESSAGE=$(
cat << "EOF"
${SLACK_MESSAGE}
EOF
)
SLACK_URL=$${SLACK_URL:-https://slack.com}
usage() {
cat <<EOF
cat << EOF
slackme — Send a Slack notification when a command finishes
Usage: slackme <command>
@@ -74,7 +75,7 @@ START=$(date +%s%N)
# Run all arguments as a command
$@
END=$(date +%s%N)
DURATION_MS=$${DURATION_MS:-$(( (END - START) / 1000000 ))}
DURATION_MS=$${DURATION_MS:-$(((END - START) / 1000000))}
PRETTY_DURATION=$(pretty_duration $DURATION_MS)
set -e

74
vault-github/README.md Normal file
View File

@@ -0,0 +1,74 @@
---
display_name: Hashicorp Vault Integration (GitHub)
description: Authenticates with Vault using GitHub
icon: ../.icons/vault.svg
maintainer_github: coder
verified: true
tags: [helper, integration, vault, github]
---
# Hashicorp Vault Integration (GitHub)
This module lets you authenticate with [Hashicorp Vault](https://www.vaultproject.io/) in your Coder workspaces using [external auth](https://coder.com/docs/v2/latest/admin/external-auth) for GitHub.
```hcl
module "vault" {
source = "https://registry.coder.com/modules/vault-github"
agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com"
}
```
Then you can use the Vault CLI in your workspaces to fetch secrets from Vault:
```shell
vault kv get -mount=secret my-secret
```
or using the Vault API:
```shell
curl -H "X-Vault-Token: ${VAULT_TOKEN}" -X GET "${VAULT_ADDR}/v1/secret/data/my-secret"
```
![Vault login](../.images/vault-login.png)
## Configuration
To configure the Vault module, you must set up a Vault GitHub auth method. See the [Vault documentation](https://www.vaultproject.io/docs/auth/github) for more information.
## Examples
### Configure Vault integration with a different Coder GitHub external auth ID (i.e., not the default `github`)
```hcl
module "vault" {
source = "https://registry.coder.com/modules/vault"
agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com"
coder_github_auth_id = "my-github-auth-id"
}
```
### Configure Vault integration with a different Coder GitHub external auth ID and a different Vault GitHub auth path
```hcl
module "vault" {
source = "https://registry.coder.com/modules/vault"
agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com"
coder_github_auth_id = "my-github-auth-id"
vault_github_auth_path = "my-github-auth-path"
}
```
### Configure Vault integration and install a specific version of the Vault CLI
```hcl
module "vault" {
source = "https://registry.coder.com/modules/vault"
agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com"
vault_cli_version = "1.15.0"
}
```

69
vault-github/main.tf Normal file
View File

@@ -0,0 +1,69 @@
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 "coder_github_auth_id" {
type = string
description = "The ID of the GitHub external auth."
default = "github"
}
variable "vault_github_auth_path" {
type = string
description = "The path to the GitHub auth method."
default = "github"
}
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"
}
}
data "coder_workspace" "me" {}
resource "coder_script" "vault" {
agent_id = var.agent_id
display_name = "Vault (GitHub)"
icon = "/icon/vault.svg"
script = templatefile("${path.module}/run.sh", {
VAULT_ADDR : var.vault_addr,
AUTH_PATH : var.vault_github_auth_path,
GITHUB_EXTERNAL_AUTH_ID : data.coder_external_auth.github.id,
INSTALL_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_external_auth" "github" {
id = var.coder_github_auth_id
}

101
vault-github/run.sh Normal file
View File

@@ -0,0 +1,101 @@
#!/usr/bin/env sh
# Convert all templated variables to shell variables
INSTALL_VERSION=${INSTALL_VERSION}
VAULT_ADDR=${VAULT_ADDR}
GITHUB_EXTERNAL_AUTH_ID=${GITHUB_EXTERNAL_AUTH_ID}
AUTH_PATH=${AUTH_PATH}
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() {
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
}
# Fetch the latest version of Vault if INSTALL_VERSION is 'latest'
if [ "$${INSTALL_VERSION}" = "latest" ]; then
LATEST_VERSION=$(curl -s https://releases.hashicorp.com/vault/ | grep -oP 'vault/\K[0-9]+\.[0-9]+\.[0-9]+' | 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"
exit 1
fi
VERSION=$${LATEST_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}" = "$${INSTALL_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}" "$${VERSION}"
fi
fetch vault.zip "https://releases.hashicorp.com/vault/$${VERSION}/vault_$${VERSION}_linux_amd64.zip"
if [ $? -ne 0 ]; then
printf "Failed to download Vault.\n"
exit 1
fi
unzip vault.zip
if [ $? -ne 0 ]; then
printf "Failed to unzip Vault.\n"
exit 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
mv vault ~/.local/bin/vault
if [ ! -f ~/.local/bin/vault ]; then
printf "Failed to move Vault to local bin.\n"
exit 1
fi
printf "Please add ~/.local/bin to your PATH to use vault CLI.\n"
fi
fi
# Authenticate with Vault
printf "🔑 Authenticating with Vault ...\n\n"
GITHUB_TOKEN=$(coder external-auth access-token "$${GITHUB_EXTERNAL_AUTH_ID}")
if [ $? -ne 0 ]; then
printf "Authentication with Vault failed. Please check your credentials.\n"
exit 1
fi
export VAULT_ADDR="$${VAULT_ADDR}"
# Login to vault using the GitHub token
printf "🔑 Logging in to Vault ...\n\n"
vault login -no-print -method=github -path=/$${AUTH_PATH} token="$${GITHUB_TOKEN}"
printf "🥳 Vault authentication complete!\n\n"
printf "You can now use Vault CLI to access secrets.\n"

View File

@@ -35,6 +35,8 @@ resource "coder_app" "vscode" {
data.coder_workspace.me.name,
"&folder=",
var.folder,
"&url=",
data.coder_workspace.me.access_url,
"&token=$SESSION_TOKEN",
]) : join("", [
"vscode://coder.coder-remote/open?owner=",

View File

@@ -26,6 +26,15 @@ variable "folder" {
default = ""
}
variable "share" {
type = string
default = "owner"
validation {
condition = var.share == "owner" || var.share == "authenticated" || var.share == "public"
error_message = "Incorrect value. Please set either 'owner', 'authenticated', or 'public'."
}
}
variable "log_path" {
type = string
description = "The path to log."
@@ -67,7 +76,7 @@ resource "coder_app" "vscode-web" {
url = var.folder == "" ? "http://localhost:${var.port}" : "http://localhost:${var.port}?folder=${var.folder}"
icon = "/icon/code.svg"
subdomain = true
share = "owner"
share = var.share
healthcheck {
url = "http://localhost:${var.port}/healthz"

View File

@@ -18,4 +18,4 @@ printf "🥳 vscode-cli has been installed.\n\n"
echo "👷 Running ${INSTALL_DIR}/bin/code serve-web --port ${PORT} --without-connection-token --accept-server-license-terms in the background..."
echo "Check logs at ${LOG_PATH}!"
${INSTALL_DIR}/code serve-web --port ${PORT} --without-connection-token --accept-server-license-terms >${LOG_PATH} 2>&1 &
${INSTALL_DIR}/code serve-web --port ${PORT} --without-connection-token --accept-server-license-terms > ${LOG_PATH} 2>&1 &