Compare commits

..

2 Commits

Author SHA1 Message Date
Kyle Carberry
cd3d42d54e Fmt 2023-10-11 12:46:03 -05:00
Kyle Carberry
66f1c3b97c slackme: exit with command code and quote command 2023-10-11 12:38:29 -05:00
78 changed files with 479 additions and 2199 deletions

View File

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

View File

@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 427 B

View File

@@ -1,2 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 506 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 KiB

View File

@@ -13,8 +13,7 @@ tags: [helper]
```hcl ```hcl
module "MODULE_NAME" { module "MODULE_NAME" {
source = "registry.coder.com/modules/MODULE_NAME/coder" source = "https://registry.coder.com/modules/MODULE_NAME"
version = "1.0.0"
} }
``` ```
@@ -28,12 +27,11 @@ Install the Dracula theme from [OpenVSX](https://open-vsx.org/):
```hcl ```hcl
module "MODULE_NAME" { module "MODULE_NAME" {
source = "registry.coder.com/modules/MODULE_NAME/coder" source = "https://registry.coder.com/modules/MODULE_NAME"
version = "1.0.0" agent_id = coder_agent.example.id
agent_id = coder_agent.example.id extensions = [
extensions = [ "dracula-theme.theme-dracula"
"dracula-theme.theme-dracula" ]
]
} }
``` ```
@@ -45,13 +43,12 @@ Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarte
```hcl ```hcl
module "MODULE_NAME" { module "MODULE_NAME" {
source = "registry.coder.com/modules/MODULE_NAME/coder" source = "https://registry.coder.com/modules/MODULE_NAME"
version = "1.0.0" agent_id = coder_agent.example.id
agent_id = coder_agent.example.id extensions = [ "dracula-theme.theme-dracula" ]
extensions = [ "dracula-theme.theme-dracula" ] settings = {
settings = { "workbench.colorTheme" = "Dracula"
"workbench.colorTheme" = "Dracula" }
}
} }
``` ```
@@ -61,9 +58,8 @@ Run code-server in the background, don't fetch it from GitHub:
```hcl ```hcl
module "MODULE_NAME" { module "MODULE_NAME" {
source = "registry.coder.com/modules/MODULE_NAME/coder" source = "https://registry.coder.com/modules/MODULE_NAME"
version = "1.0.0" agent_id = coder_agent.example.id
agent_id = coder_agent.example.id offline = true
offline = true
} }
``` ```

View File

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

View File

@@ -20,9 +20,7 @@ $ bun test -t '<module>'
You can test a module locally by updating the source as follows You can test a module locally by updating the source as follows
```hcl ```hcl
module "example" { source = "git::https://github.com/<USERNAME>/<REPO>.git//<MODULE-NAME>?ref=<BRANCH-NAME>"
source = "git::https://github.com/<USERNAME>/<REPO>.git//<MODULE-NAME>?ref=<BRANCH-NAME>"
}
``` ```
> **Note:** This is the responsibility of the module author to implement tests for their module. and test the module locally before submitting a PR. > **Note:** This is the responsibility of the module author to implement tests for their module. and test the module locally before submitting a PR.

View File

@@ -16,9 +16,8 @@ e.g.
```hcl ```hcl
module "code-server" { module "code-server" {
source = "registry.coder.com/modules/code-server/coder" source = "https://registry.coder.com/modules/code-server"
version = "1.0.0" agent_id = coder_agent.main.id
agent_id = coder_agent.main.id
} }
``` ```

View File

@@ -16,13 +16,12 @@ Customize the preselected parameter value:
```hcl ```hcl
module "aws-region" { module "aws-region" {
source = "registry.coder.com/modules/aws-region/coder" source = "https://registry.coder.com/modules/aws-region"
version = "1.0.0" default = "us-east-1"
default = "us-east-1"
} }
provider "aws" { provider "aws" {
region = module.aws_region.value region = module.aws_region.value
} }
``` ```
@@ -36,19 +35,18 @@ Change the display name and icon for a region using the corresponding maps:
```hcl ```hcl
module "aws-region" { module "aws-region" {
source = "registry.coder.com/modules/aws-region/coder" source = "https://registry.coder.com/modules/aws-region"
version = "1.0.0" default = "ap-south-1"
default = "ap-south-1" custom_names = {
custom_names = { "ap-south-1": "Awesome Mumbai!"
"ap-south-1": "Awesome Mumbai!" }
} custom_icons = {
custom_icons = { "ap-south-1": "/emojis/1f33a.png"
"ap-south-1": "/emojis/1f33a.png" }
}
} }
provider "aws" { provider "aws" {
region = module.aws_region.value region = module.aws_region.value
} }
``` ```
@@ -60,13 +58,12 @@ Hide the Asia Pacific regions Seoul and Osaka:
```hcl ```hcl
module "aws-region" { module "aws-region" {
source = "registry.coder.com/modules/aws-region/coder" source = "https://registry.coder.com/modules/aws-region"
version = "1.0.0" exclude = [ "ap-northeast-2", "ap-northeast-3" ]
exclude = [ "ap-northeast-2", "ap-northeast-3" ]
} }
provider "aws" { provider "aws" {
region = module.aws_region.value region = module.aws_region.value
} }
``` ```

View File

@@ -13,13 +13,12 @@ This module adds a parameter with all Azure regions, allowing developers to sele
```hcl ```hcl
module "azure_region" { module "azure_region" {
source = "registry.coder.com/modules/azure-region/coder" source = "https://registry.coder.com/modules/azure-region"
version = "1.0.0" default = "eastus"
default = "eastus"
} }
resource "azurem_resource_group" "example" { resource "azurem_resource_group" "example" {
location = module.azure_region.value location = module.azure_region.value
} }
``` ```
@@ -33,18 +32,17 @@ Change the display name and icon for a region using the corresponding maps:
```hcl ```hcl
module "azure-region" { module "azure-region" {
source = "registry.coder.com/modules/azure-region/coder" source = "https://registry.coder.com/modules/azure-region"
version = "1.0.0" custom_names = {
custom_names = { "australia": "Go Australia!"
"australia": "Go Australia!" }
} custom_icons = {
custom_icons = { "australia": "/icons/smiley.svg"
"australia": "/icons/smiley.svg" }
}
} }
resource "azurerm_resource_group" "example" { resource "azurerm_resource_group" "example" {
location = module.azure_region.value location = module.azure_region.value
} }
``` ```
@@ -56,18 +54,17 @@ Hide all regions in Australia except australiacentral:
```hcl ```hcl
module "azure-region" { module "azure-region" {
source = "registry.coder.com/modules/azure-region/coder" source = "https://registry.coder.com/modules/azure-region"
version = "1.0.0" exclude = [
exclude = [ "australia",
"australia", "australiacentral2",
"australiacentral2", "australiaeast",
"australiaeast", "australiasoutheast"
"australiasoutheast" ]
]
} }
resource "azurerm_resource_group" "example" { resource "azurerm_resource_group" "example" {
location = module.azure_region.value location = module.azure_region.value
} }
``` ```

BIN
bun.lockb

Binary file not shown.

View File

@@ -13,9 +13,8 @@ Automatically install [code-server](https://github.com/coder/code-server) in a w
```hcl ```hcl
module "code-server" { module "code-server" {
source = "registry.coder.com/modules/code-server/coder" source = "https://registry.coder.com/modules/code-server"
version = "1.0.0" agent_id = coder_agent.example.id
agent_id = coder_agent.example.id
} }
``` ```
@@ -27,8 +26,7 @@ module "code-server" {
```hcl ```hcl
module "code-server" { module "code-server" {
source = "registry.coder.com/modules/code-server/coder" source = "https://registry.coder.com/modules/code-server"
version = "1.0.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
install_version = "4.8.3" install_version = "4.8.3"
} }
@@ -40,12 +38,11 @@ Install the Dracula theme from [OpenVSX](https://open-vsx.org/):
```hcl ```hcl
module "code-server" { module "code-server" {
source = "registry.coder.com/modules/code-server/coder" source = "https://registry.coder.com/modules/code-server"
version = "1.0.0" agent_id = coder_agent.example.id
agent_id = coder_agent.example.id extensions = [
extensions = [ "dracula-theme.theme-dracula"
"dracula-theme.theme-dracula" ]
]
} }
``` ```
@@ -57,26 +54,12 @@ Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarte
```hcl ```hcl
module "settings" { module "settings" {
source = "registry.coder.com/modules/code-server/coder" source = "https://registry.coder.com/modules/code-server"
version = "1.0.0" agent_id = coder_agent.example.id
agent_id = coder_agent.example.id extensions = [ "dracula-theme.theme-dracula" ]
extensions = [ "dracula-theme.theme-dracula" ] settings = {
settings = { "workbench.colorTheme" = "Dracula"
"workbench.colorTheme" = "Dracula" }
}
}
```
### Install multiple extensions
Just run code-server in the background, don't fetch it from GitHub:
```hcl
module "settings" {
source = "registry.coder.com/modules/code-server/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
extensions = [ "dracula-theme.theme-dracula", "ms-azuretools.vscode-docker" ]
} }
``` ```
@@ -86,9 +69,8 @@ Just run code-server in the background, don't fetch it from GitHub:
```hcl ```hcl
module "settings" { module "settings" {
source = "registry.coder.com/modules/code-server/coder" source = "https://registry.coder.com/modules/code-server"
version = "1.0.0" agent_id = coder_agent.example.id
agent_id = coder_agent.example.id offline = true
offline = true
} }
``` ```

View File

@@ -62,15 +62,6 @@ variable "install_version" {
default = "" 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" { resource "coder_script" "code-server" {
agent_id = var.agent_id agent_id = var.agent_id
display_name = "code-server" display_name = "code-server"
@@ -94,7 +85,7 @@ resource "coder_app" "code-server" {
url = "http://localhost:${var.port}/${var.folder != "" ? "?folder=${urlencode(var.folder)}" : ""}" url = "http://localhost:${var.port}/${var.folder != "" ? "?folder=${urlencode(var.folder)}" : ""}"
icon = "/icon/code.svg" icon = "/icon/code.svg"
subdomain = false subdomain = false
share = var.share share = "owner"
healthcheck { healthcheck {
url = "http://localhost:${var.port}/healthz" url = "http://localhost:${var.port}/healthz"

View File

@@ -25,8 +25,7 @@ printf "🥳 code-server has been installed in ${INSTALL_PREFIX}\n\n"
CODE_SERVER="${INSTALL_PREFIX}/bin/code-server" CODE_SERVER="${INSTALL_PREFIX}/bin/code-server"
# Install each extension... # Install each extension...
IFS=',' read -r -a EXTENSIONLIST <<< "$${EXTENSIONS}" for extension in "$${EXTENSIONS[@]}"; do
for extension in "$${EXTENSIONLIST[@]}"; do
if [ -z "$extension" ]; then if [ -z "$extension" ]; then
continue continue
fi fi
@@ -39,12 +38,12 @@ for extension in "$${EXTENSIONLIST[@]}"; do
done done
# Check if the settings file exists... # Check if the settings file exists...
if [ ! -f ~/.local/share/code-server/User/settings.json ]; then if [ ! -f ~/.local/share/code-server/Machine/settings.json ]; then
echo "⚙️ Creating settings file..." echo "⚙️ Creating settings file..."
mkdir -p ~/.local/share/code-server/User mkdir -p ~/.local/share/code-server/Machine
echo "${SETTINGS}" > ~/.local/share/code-server/User/settings.json echo "${SETTINGS}" >~/.local/share/code-server/Machine/settings.json
fi fi
echo "👷 Running code-server in the background..." echo "👷 Running code-server in the background..."
echo "Check logs at ${LOG_PATH}!" 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

@@ -13,9 +13,8 @@ Automatically logs the user into Coder when creating their workspace.
```hcl ```hcl
module "coder-login" { module "coder-login" {
source = "registry.coder.com/modules/coder-login/coder" source = "https://registry.coder.com/modules/coder-login"
version = "1.0.0" agent_id = coder_agent.example.id
agent_id = coder_agent.example.id
} }
``` ```

View File

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

View File

@@ -13,8 +13,7 @@ Allow developers to optionally bring their own [dotfiles repository](https://dot
```hcl ```hcl
module "dotfiles" { module "dotfiles" {
source = "registry.coder.com/modules/dotfiles/coder" source = "https://registry.coder.com/modules/dotfiles"
version = "1.0.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
} }
``` ```

View File

@@ -1,112 +0,0 @@
---
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 = "registry.coder.com/modules/exoscale-instance-type/coder"
version = "1.0.0"
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 = "registry.coder.com/modules/exoscale-instance-type/coder"
version = "1.0.0"
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 = "registry.coder.com/modules/exoscale-instance-type/coder"
version = "1.0.0"
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

@@ -1,34 +0,0 @@
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

@@ -1,279 +0,0 @@
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
}

View File

@@ -1,96 +0,0 @@
---
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 = "registry.coder.com/modules/exoscale-zone/coder"
version = "1.0.0"
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 = "registry.coder.com/modules/exoscale-zone/coder"
version = "1.0.0"
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 = "registry.coder.com/modules/exoscale-zone/coder"
version = "1.0.0"
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

@@ -1,25 +0,0 @@
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");
});
});

View File

@@ -1,110 +0,0 @@
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

@@ -13,9 +13,8 @@ A file browser for your workspace.
```hcl ```hcl
module "filebrowser" { module "filebrowser" {
source = "registry.coder.com/modules/filebrowser/coder" source = "https://registry.coder.com/modules/filebrowser"
version = "1.0.0" agent_id = coder_agent.example.id
agent_id = coder_agent.example.id
} }
``` ```
@@ -27,10 +26,9 @@ module "filebrowser" {
```hcl ```hcl
module "filebrowser" { module "filebrowser" {
source = "registry.coder.com/modules/filebrowser/coder" source = "https://registry.coder.com/modules/filebrowser"
version = "1.0.0" agent_id = coder_agent.example.id
agent_id = coder_agent.example.id folder = "/home/coder/project"
folder = "/home/coder/project"
} }
``` ```
@@ -38,9 +36,8 @@ module "filebrowser" {
```hcl ```hcl
module "filebrowser" { module "filebrowser" {
source = "registry.coder.com/modules/filebrowser/coder" source = "https://registry.coder.com/modules/filebrowser"
version = "1.0.0" agent_id = coder_agent.example.id
agent_id = coder_agent.example.id database_path = ".config/filebrowser.db"
database_path = ".config/filebrowser.db"
} }
``` ```

View File

@@ -43,15 +43,6 @@ variable "folder" {
default = "~" 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" { resource "coder_script" "filebrowser" {
agent_id = var.agent_id agent_id = var.agent_id
display_name = "File Browser" display_name = "File Browser"
@@ -73,5 +64,5 @@ resource "coder_app" "filebrowser" {
url = "http://localhost:${var.port}" url = "http://localhost:${var.port}"
icon = "https://raw.githubusercontent.com/filebrowser/logo/master/icon_raw.svg" icon = "https://raw.githubusercontent.com/filebrowser/logo/master/icon_raw.svg"
subdomain = true subdomain = true
share = var.share share = "owner"
} }

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" 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" printf "📝 Logs at ${LOG_PATH} \n\n"

View File

@@ -15,9 +15,8 @@ We can use the simplest format here, only adding a default selection as the `atl
```hcl ```hcl
module "fly-region" { module "fly-region" {
source = "registry.coder.com/modules/fly-region/coder" source = "https://registry.coder.com/modules/fly-region"
version = "1.0.0" default = "atl"
default = "atl"
} }
``` ```
@@ -31,10 +30,9 @@ The regions argument can be used to display only the desired regions in the Code
```hcl ```hcl
module "fly-region" { module "fly-region" {
source = "registry.coder.com/modules/fly-region/coder" source = "https://registry.coder.com/modules/fly-region"
version = "1.0.0" default = "ams"
default = "ams" regions = ["ams", "arn", "atl"]
regions = ["ams", "arn", "atl"]
} }
``` ```
@@ -46,15 +44,14 @@ Set custom icons and names with their respective maps.
```hcl ```hcl
module "fly-region" { module "fly-region" {
source = "registry.coder.com/modules/fly-region/coder" source = "https://registry.coder.com/modules/fly-region"
version = "1.0.0" default = "ams"
default = "ams" custom_icons = {
custom_icons = { "ams" = "/emojis/1f90e.png"
"ams" = "/emojis/1f90e.png" }
} custom_names = {
custom_names = { "ams" = "We love the Netherlands!"
"ams" = "We love the Netherlands!" }
}
} }
``` ```

View File

@@ -13,13 +13,12 @@ This module adds Google Cloud Platform regions to your Coder template.
```hcl ```hcl
module "gcp_region" { module "gcp_region" {
source = "registry.coder.com/modules/gcp-region/coder" source = "https://registry.coder.com/modules/gcp-region"
version = "1.0.0" regions = ["us", "europe"]
regions = ["us", "europe"]
} }
resource "google_compute_instance" "example" { resource "google_compute_instance" "example" {
zone = module.gcp_region.value zone = module.gcp_region.value
} }
``` ```
@@ -33,15 +32,14 @@ Note: setting `gpu_only = true` and using a default region without GPU support,
```hcl ```hcl
module "gcp_region" { module "gcp_region" {
source = "registry.coder.com/modules/gcp-region/coder" source = "https://registry.coder.com/modules/gcp-region"
version = "1.0.0" default = ["us-west1-a"]
default = ["us-west1-a"] regions = ["us-west1"]
regions = ["us-west1"] gpu_only = false
gpu_only = false
} }
resource "google_compute_instance" "example" { resource "google_compute_instance" "example" {
zone = module.gcp_region.value zone = module.gcp_region.value
} }
``` ```
@@ -49,14 +47,13 @@ resource "google_compute_instance" "example" {
```hcl ```hcl
module "gcp_region" { module "gcp_region" {
source = "registry.coder.com/modules/gcp-region/coder" source = "https://registry.coder.com/modules/gcp-region"
version = "1.0.0" regions = ["europe-west"]
regions = ["europe-west"] single_zone_per_region = false
single_zone_per_region = false
} }
resource "google_compute_instance" "example" { resource "google_compute_instance" "example" {
zone = module.gcp_region.value zone = module.gcp_region.value
} }
``` ```
@@ -64,14 +61,13 @@ resource "google_compute_instance" "example" {
```hcl ```hcl
module "gcp_region" { module "gcp_region" {
source = "registry.coder.com/modules/gcp-region/coder" source = "https://registry.coder.com/modules/gcp-region"
version = "1.0.0" regions = ["us", "europe"]
regions = ["us", "europe"] gpu_only = true
gpu_only = true single_zone_per_region = true
single_zone_per_region = true
} }
resource "google_compute_instance" "example" { resource "google_compute_instance" "example" {
zone = module.gcp_region.value zone = module.gcp_region.value
} }
``` ```

View File

@@ -13,10 +13,9 @@ This module allows you to automatically clone a repository by URL and skip if it
```hcl ```hcl
module "git-clone" { module "git-clone" {
source = "registry.coder.com/modules/git-clone/coder" source = "https://registry.coder.com/modules/git-clone"
version = "1.0.0" agent_id = coder_agent.example.id
agent_id = coder_agent.example.id url = "https://github.com/coder/coder"
url = "https://github.com/coder/coder"
} }
``` ```
@@ -24,7 +23,7 @@ To use with [Git Authentication](https://coder.com/docs/v2/latest/admin/git-prov
```hcl ```hcl
data "coder_git_auth" "github" { data "coder_git_auth" "github" {
id = "github" id = "github"
} }
``` ```
@@ -34,10 +33,9 @@ data "coder_git_auth" "github" {
```hcl ```hcl
module "git-clone" { module "git-clone" {
source = "registry.coder.com/modules/git-clone/coder" source = "https://registry.coder.com/modules/git-clone"
version = "1.0.0" agent_id = coder_agent.example.id
agent_id = coder_agent.example.id url = "https://github.com/coder/coder"
url = "https://github.com/coder/coder" path = "~/projects/coder/coder"
path = "~/projects/coder/coder"
} }
``` ```

View File

@@ -4,7 +4,7 @@ terraform {
required_providers { required_providers {
coder = { coder = {
source = "coder/coder" source = "coder/coder"
version = ">= 0.12" version = ">= 0.11"
} }
} }
} }
@@ -28,7 +28,7 @@ variable "agent_id" {
resource "coder_script" "git_clone" { resource "coder_script" "git_clone" {
agent_id = var.agent_id agent_id = var.agent_id
script = templatefile("${path.module}/run.sh", { script = templatefile("${path.module}/run.sh", {
CLONE_PATH = var.path != "" ? join("/", [var.path, replace(basename(var.url), ".git", "")]) : join("/", ["~", replace(basename(var.url), ".git", "")]) CLONE_PATH : var.path != "" ? var.path : join("/", ["~", basename(var.url)]),
REPO_URL : var.url, REPO_URL : var.url,
}) })
display_name = "Git Clone" display_name = "Git Clone"

View File

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

View File

@@ -1,25 +0,0 @@
---
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 = "registry.coder.com/modules/git-commit-signing/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
}
```

View File

@@ -1,25 +0,0 @@
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
}

View File

@@ -1,41 +0,0 @@
#!/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

@@ -13,8 +13,7 @@ Runs a script that updates git credentials in the workspace to match the user's
```hcl ```hcl
module "git-config" { module "git-config" {
source = "registry.coder.com/modules/git-config/coder" source = "https://registry.coder.com/modules/git-config"
version = "1.0.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
} }
``` ```
@@ -27,8 +26,7 @@ TODO: Add screenshot
```hcl ```hcl
module "git-config" { module "git-config" {
source = "registry.coder.com/modules/git-config/coder" source = "https://registry.coder.com/modules/git-config"
version = "1.0.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
allow_email_change = true allow_email_change = true
} }
@@ -40,8 +38,7 @@ TODO: Add screenshot
```hcl ```hcl
module "git-config" { module "git-config" {
source = "registry.coder.com/modules/git-config/coder" source = "https://registry.coder.com/modules/git-config"
version = "1.0.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
allow_username_change = false allow_username_change = false
allow_email_change = false allow_email_change = false

View File

@@ -4,20 +4,20 @@ BOLD='\033[0;1m'
printf "$${BOLD}Checking git-config!\n" printf "$${BOLD}Checking git-config!\n"
# Check if git is installed # Check if git is installed
command -v git > /dev/null 2>&1 || { command -v git >/dev/null 2>&1 || {
echo "Git is not installed!" echo "Git is not installed!"
exit 1 exit 1
} }
# Set git username and email if missing # Set git username and email if missing
if [ -z $(git config --get user.email) ]; then if [ -z $(git config --get user.email) ]; then
printf "git-config: No user.email found, setting to ${GIT_EMAIL}\n" printf "git-config: No user.email found, setting to ${GIT_EMAIL}\n"
git config --global user.email "${GIT_EMAIL}" git config --global user.email "${GIT_EMAIL}"
fi fi
if [ -z $(git config --get user.name) ]; then if [ -z $(git config --get user.name) ]; then
printf "git-config: No user.name found, setting to ${GIT_USERNAME}\n" printf "git-config: No user.name found, setting to ${GIT_USERNAME}\n"
git config --global user.name "${GIT_USERNAME}" git config --global user.name "${GIT_USERNAME}"
fi fi
printf "\n$${BOLD}git-config: using email: $(git config --get user.email)\n" printf "\n$${BOLD}git-config: using email: $(git config --get user.email)\n"

View File

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

View File

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

View File

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

View File

@@ -1,145 +0,0 @@
---
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 = "registry.coder.com/modules/jfrog-oauth/coder"
version = "1.0.0"
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 = "registry.coder.com/modules/jfrog-oauth/coder"
version = "1.0.0"
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 = "registry.coder.com/modules/jfrog-oauth/coder"
version = "1.0.0"
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
}
}
```

View File

@@ -1,19 +0,0 @@
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

View File

@@ -1,131 +0,0 @@
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
}

View File

@@ -1,122 +0,0 @@
#!/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

View File

@@ -1,111 +0,0 @@
---
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 = "registry.coder.com/modules/jfrog-token/coder"
version = "1.0.0"
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 = "registry.coder.com/modules/jfrog-token/coder"
version = "1.0.0"
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 = "registry.coder.com/modules/jfrog-token/coder"
version = "1.0.0"
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

@@ -1,164 +0,0 @@
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
}

View File

@@ -1,122 +0,0 @@
#!/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

56
jfrog/README.md Normal file
View File

@@ -0,0 +1,56 @@
---
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

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

71
jfrog/main.tf Normal file
View File

@@ -0,0 +1,71 @@
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
}

49
jfrog/run.sh Normal file
View File

@@ -0,0 +1,49 @@
#!/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

@@ -15,8 +15,7 @@ A module that adds Jupyter Notebook in your Coder template.
```hcl ```hcl
module "jupyter-notebook" { module "jupyter-notebook" {
source = "registry.coder.com/modules/jupyter-notebook/coder" source = "https://registry.coder.com/modules/jupyter-notebook"
version = "1.0.0" agent_id = coder_agent.example.id
agent_id = coder_agent.example.id
} }
``` ```

View File

@@ -27,15 +27,6 @@ variable "port" {
default = 19999 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" { resource "coder_script" "jupyter-notebook" {
agent_id = var.agent_id agent_id = var.agent_id
display_name = "jupyter-notebook" display_name = "jupyter-notebook"
@@ -54,5 +45,5 @@ resource "coder_app" "jupyter-notebook" {
url = "http://localhost:${var.port}" url = "http://localhost:${var.port}"
icon = "/icon/jupyter.svg" icon = "/icon/jupyter.svg"
subdomain = true subdomain = true
share = var.share share = "owner"
} }

View File

@@ -5,21 +5,21 @@ BOLD='\033[0;1m'
printf "$${BOLD}Installing jupyter-notebook!\n" printf "$${BOLD}Installing jupyter-notebook!\n"
# check if jupyter-notebook is installed # 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 # install jupyter-notebook
# check if python3 pip is installed # 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 "pip3 is not installed"
echo "Please install pip3 in your Dockerfile/VM image before running this script" echo "Please install pip3 in your Dockerfile/VM image before running this script"
exit 1 exit 1
fi fi
# install jupyter-notebook # install jupyter-notebook
pip3 install --upgrade --no-cache-dir --no-warn-script-location jupyter pip3 install --upgrade --no-cache-dir --no-warn-script-location jupyter
echo "🥳 jupyter-notebook has been installed\n\n" echo "🥳 jupyter-notebook has been installed\n\n"
else else
echo "🥳 jupyter-notebook is already installed\n\n" echo "🥳 jupyter-notebook is already installed\n\n"
fi fi
echo "👷 Starting jupyter-notebook in background..." echo "👷 Starting jupyter-notebook in background..."
echo "check logs at ${LOG_PATH}" 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

@@ -15,8 +15,7 @@ A module that adds JupyterLab in your Coder template.
```hcl ```hcl
module "jupyterlab" { module "jupyterlab" {
source = "registry.coder.com/modules/jupyterlab/coder" source = "https://registry.coder.com/modules/jupyterlab"
version = "1.0.0" agent_id = coder_agent.example.id
agent_id = coder_agent.example.id
} }
``` ```

View File

@@ -27,15 +27,6 @@ variable "port" {
default = 19999 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" { resource "coder_script" "jupyterlab" {
agent_id = var.agent_id agent_id = var.agent_id
display_name = "jupyterlab" display_name = "jupyterlab"
@@ -54,5 +45,5 @@ resource "coder_app" "jupyterlab" {
url = "http://localhost:${var.port}" url = "http://localhost:${var.port}"
icon = "/icon/jupyter.svg" icon = "/icon/jupyter.svg"
subdomain = true subdomain = true
share = var.share share = "owner"
} }

View File

@@ -5,21 +5,21 @@ BOLD='\033[0;1m'
printf "$${BOLD}Installing jupyterlab!\n" printf "$${BOLD}Installing jupyterlab!\n"
# check if jupyterlab is installed # 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 # install jupyterlab
# check if python3 pip is installed # 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 "pip3 is not installed"
echo "Please install pip3 in your Dockerfile/VM image before running this script" echo "Please install pip3 in your Dockerfile/VM image before running this script"
exit 1 exit 1
fi fi
# install jupyterlab # install jupyterlab
pip3 install --upgrade --no-cache-dir --no-warn-script-location jupyterlab pip3 install --upgrade --no-cache-dir --no-warn-script-location jupyterlab
echo "🥳 jupyterlab has been installed\n\n" echo "🥳 jupyterlab has been installed\n\n"
else else
echo "🥳 jupyterlab is already installed\n\n" echo "🥳 jupyterlab is already installed\n\n"
fi fi
echo "👷 Starting jupyterlab in background..." echo "👷 Starting jupyterlab in background..."
echo "check logs at ${LOG_PATH}" 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 &

22
new.sh
View File

@@ -7,15 +7,15 @@ MODULE_NAME=$1
# Check if module name is provided # Check if module name is provided
if [ -z "$MODULE_NAME" ]; then if [ -z "$MODULE_NAME" ]; then
echo "Usage: ./new.sh <module_name>" echo "Usage: ./new.sh <module_name>"
exit 1 exit 1
fi fi
# Create module directory and exit if it alredy exists # Create module directory and exit if it alredy exists
if [ -d "$MODULE_NAME" ]; then if [ -d "$MODULE_NAME" ]; then
echo "Module with name $MODULE_NAME already exists" echo "Module with name $MODULE_NAME already exists"
echo "Please choose a different name" echo "Please choose a different name"
exit 1 exit 1
fi fi
mkdir -p "${MODULE_NAME}" mkdir -p "${MODULE_NAME}"
@@ -27,13 +27,13 @@ cd "${MODULE_NAME}"
# Detect OS # Detect OS
if [[ "$OSTYPE" == "darwin"* ]]; then if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS # macOS
sed -i '' "s/MODULE_NAME/${MODULE_NAME}/g" main.tf sed -i '' "s/MODULE_NAME/${MODULE_NAME}/g" main.tf
sed -i '' "s/MODULE_NAME/${MODULE_NAME}/g" README.md sed -i '' "s/MODULE_NAME/${MODULE_NAME}/g" README.md
else else
# Linux # Linux
sed -i "s/MODULE_NAME/${MODULE_NAME}/g" main.tf sed -i "s/MODULE_NAME/${MODULE_NAME}/g" main.tf
sed -i "s/MODULE_NAME/${MODULE_NAME}/g" README.md sed -i "s/MODULE_NAME/${MODULE_NAME}/g" README.md
fi fi
# Make run.sh executable # Make run.sh executable

View File

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

View File

@@ -13,8 +13,7 @@ Run a script on workspace start that allows developers to run custom commands to
```hcl ```hcl
module "personalize" { module "personalize" {
source = "registry.coder.com/modules/personalize/coder" source = "https://registry.coder.com/modules/personalize"
version = "1.0.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
} }
``` ```

View File

@@ -9,18 +9,18 @@ SCRIPT="$${SCRIPT/#\~/$${HOME}}"
# If the personalize script doesn't exist, educate # If the personalize script doesn't exist, educate
# the user how they can customize their environment! # the user how they can customize their environment!
if [ ! -f $SCRIPT ]; then if [ ! -f $SCRIPT ]; then
printf "$${BOLD}You don't have a personalize script!\n\n" printf "$${BOLD}You don't have a personalize script!\n\n"
printf "Run $${CODE}touch $${SCRIPT} && chmod +x $${SCRIPT}$${RESET} to create one.\n" printf "Run $${CODE}touch $${SCRIPT} && chmod +x $${SCRIPT}$${RESET} to create one.\n"
printf "It will run every time your workspace starts. Use it to install personal packages!\n\n" printf "It will run every time your workspace starts. Use it to install personal packages!\n\n"
exit 0 exit 0
fi fi
# Check if the personalize script is executable, if not, # Check if the personalize script is executable, if not,
# try to make it executable and educate the user if it fails. # try to make it executable and educate the user if it fails.
if [ ! -x $SCRIPT ]; then if [ ! -x $SCRIPT ]; then
echo "🔐 Your personalize script isn't executable!" echo "🔐 Your personalize script isn't executable!"
printf "Run $CODE\`chmod +x $SCRIPT\`$RESET to make it executable.\n" printf "Run $CODE\`chmod +x $SCRIPT\`$RESET to make it executable.\n"
exit 0 exit 0
fi fi
# Run the personalize script! # Run the personalize script!

View File

@@ -12,7 +12,7 @@ tags: [helper]
Add the `slackme` command to your workspace that DMs you on Slack when your command finishes running. Add the `slackme` command to your workspace that DMs you on Slack when your command finishes running.
```bash ```bash
slackme npm run long-build $ slackme npm run long-build
``` ```
## Setup ## Setup
@@ -56,8 +56,7 @@ slackme npm run long-build
```hcl ```hcl
module "slackme" { module "slackme" {
source = "registry.coder.com/modules/slackme/coder" source = "https://registry.coder.com/modules/slackme"
version = "1.0.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
auth_provider_id = "slack" auth_provider_id = "slack"
} }
@@ -72,8 +71,7 @@ slackme npm run long-build
```hcl ```hcl
module "slackme" { module "slackme" {
source = "registry.coder.com/modules/slackme/coder" source = "https://registry.coder.com/modules/slackme"
version = "1.0.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
auth_provider_id = "slack" auth_provider_id = "slack"
slack_message = <<EOF slack_message = <<EOF

View File

@@ -56,6 +56,15 @@ describe("slackme", async () => {
}); });
}); });
it("exits with command code", async () => {
const { instance, id } = await setupContainer();
await writeCoder(id, "echo 'some-url' && exit 1");
let exec = await execContainer(id, ["sh", "-c", instance.script]);
expect(exec.exitCode).toBe(0);
exec = await execContainer(id, ["sh", "-c", "slackme exit 1"]);
expect(exec.exitCode).toBe(1);
});
it("formats multiline message", async () => { it("formats multiline message", async () => {
await assertSlackMessage({ await assertSlackMessage({
command: "echo test", command: "echo test",

View File

@@ -1,15 +1,14 @@
#!/usr/bin/env sh #!/usr/bin/env sh
PROVIDER_ID=${PROVIDER_ID} PROVIDER_ID=${PROVIDER_ID}
SLACK_MESSAGE=$( SLACK_MESSAGE=$(cat << "EOF"
cat << "EOF"
${SLACK_MESSAGE} ${SLACK_MESSAGE}
EOF EOF
) )
SLACK_URL=$${SLACK_URL:-https://slack.com} SLACK_URL=$${SLACK_URL:-https://slack.com}
usage() { usage() {
cat << EOF cat <<EOF
slackme — Send a Slack notification when a command finishes slackme — Send a Slack notification when a command finishes
Usage: slackme <command> Usage: slackme <command>
@@ -18,45 +17,45 @@ EOF
} }
pretty_duration() { pretty_duration() {
local duration_ms=$1 local duration_ms=$1
# If the duration is less than 1 second, display in milliseconds # If the duration is less than 1 second, display in milliseconds
if [ $duration_ms -lt 1000 ]; then if [ $duration_ms -lt 1000 ]; then
echo "$${duration_ms}ms" echo "$${duration_ms}ms"
return return
fi fi
# Convert the duration to seconds # Convert the duration to seconds
local duration_sec=$((duration_ms / 1000)) local duration_sec=$((duration_ms / 1000))
local remaining_ms=$((duration_ms % 1000)) local remaining_ms=$((duration_ms % 1000))
# If the duration is less than 1 minute, display in seconds (with ms) # If the duration is less than 1 minute, display in seconds (with ms)
if [ $duration_sec -lt 60 ]; then if [ $duration_sec -lt 60 ]; then
echo "$${duration_sec}.$${remaining_ms}s" echo "$${duration_sec}.$${remaining_ms}s"
return return
fi fi
# Convert the duration to minutes # Convert the duration to minutes
local duration_min=$((duration_sec / 60)) local duration_min=$((duration_sec / 60))
local remaining_sec=$((duration_sec % 60)) local remaining_sec=$((duration_sec % 60))
# If the duration is less than 1 hour, display in minutes and seconds # If the duration is less than 1 hour, display in minutes and seconds
if [ $duration_min -lt 60 ]; then if [ $duration_min -lt 60 ]; then
echo "$${duration_min}m $${remaining_sec}.$${remaining_ms}s" echo "$${duration_min}m $${remaining_sec}.$${remaining_ms}s"
return return
fi fi
# Convert the duration to hours # Convert the duration to hours
local duration_hr=$((duration_min / 60)) local duration_hr=$((duration_min / 60))
local remaining_min=$((duration_min % 60)) local remaining_min=$((duration_min % 60))
# Display in hours, minutes, and seconds # Display in hours, minutes, and seconds
echo "$${duration_hr}hr $${remaining_min}m $${remaining_sec}.$${remaining_ms}s" echo "$${duration_hr}hr $${remaining_min}m $${remaining_sec}.$${remaining_ms}s"
} }
if [ $# -eq 0 ]; then if [ $# -eq 0 ]; then
usage usage
exit 1 exit 1
fi fi
BOT_TOKEN=$(coder external-auth access-token $PROVIDER_ID) BOT_TOKEN=$(coder external-auth access-token $PROVIDER_ID)
@@ -73,9 +72,10 @@ fi
START=$(date +%s%N) START=$(date +%s%N)
# Run all arguments as a command # Run all arguments as a command
$@ "$@"
CODE=$?
END=$(date +%s%N) END=$(date +%s%N)
DURATION_MS=$${DURATION_MS:-$(((END - START) / 1000000))} DURATION_MS=$${DURATION_MS:-$(( (END - START) / 1000000 ))}
PRETTY_DURATION=$(pretty_duration $DURATION_MS) PRETTY_DURATION=$(pretty_duration $DURATION_MS)
set -e set -e
@@ -84,5 +84,7 @@ SLACK_MESSAGE=$(echo "$SLACK_MESSAGE" | sed "s|\\$COMMAND|$COMMAND|g")
SLACK_MESSAGE=$(echo "$SLACK_MESSAGE" | sed "s|\\$DURATION|$PRETTY_DURATION|g") SLACK_MESSAGE=$(echo "$SLACK_MESSAGE" | sed "s|\\$DURATION|$PRETTY_DURATION|g")
curl --silent -o /dev/null --header "Authorization: Bearer $BOT_TOKEN" \ curl --silent -o /dev/null --header "Authorization: Bearer $BOT_TOKEN" \
-G --data-urlencode "text=$${SLACK_MESSAGE}" \ -G --data-urlencode "text=$${SLACK_MESSAGE}" \
"$SLACK_URL/api/chat.postMessage?channel=$USER_ID&pretty=1" "$SLACK_URL/api/chat.postMessage?channel=$USER_ID&pretty=1"
exit $CODE

View File

@@ -1,78 +0,0 @@
---
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 = "registry.coder.com/modules/vault-github/coder"
version = "1.0.0"
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 = "registry.coder.com/modules/vault-github/coder"
version = "1.0.0"
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 = "registry.coder.com/modules/vault-github/coder"
version = "1.0.0"
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 = "registry.coder.com/modules/vault-github/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com"
vault_cli_version = "1.15.0"
}
```

View File

@@ -1,69 +0,0 @@
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
}

View File

@@ -1,101 +0,0 @@
#!/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

@@ -15,8 +15,7 @@ Uses the [Coder Remote VS Code Extension](https://github.com/coder/vscode-coder)
```hcl ```hcl
module "vscode" { module "vscode" {
source = "registry.coder.com/modules/vscode-desktop/coder" source = "https://registry.coder.com/modules/vscode-desktop"
version = "1.0.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
} }
``` ```
@@ -27,8 +26,7 @@ module "vscode" {
```hcl ```hcl
module "vscode" { module "vscode" {
source = "registry.coder.com/modules/vscode-desktop/coder" source = "https://registry.coder.com/modules/vscode-desktop"
version = "1.0.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
folder = "/home/coder/project" folder = "/home/coder/project"
} }

View File

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

View File

@@ -13,8 +13,7 @@ Automatically install [Visual Studio Code Server](https://code.visualstudio.com/
```hcl ```hcl
module "vscode-web" { module "vscode-web" {
source = "registry.coder.com/modules/vscode-web/coder" source = "https://registry.coder.com/modules/vscode-web"
version = "1.0.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
accept_license = true accept_license = true
} }
@@ -28,8 +27,7 @@ module "vscode-web" {
```hcl ```hcl
module "vscode-web" { module "vscode-web" {
source = "registry.coder.com/modules/vscode-web/coder" source = "https://registry.coder.com/modules/vscode-web"
version = "1.0.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
install_dir = "/home/coder/.vscode-web" install_dir = "/home/coder/.vscode-web"
folder = "/home/coder" folder = "/home/coder"

View File

@@ -26,15 +26,6 @@ variable "folder" {
default = "" 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" { variable "log_path" {
type = string type = string
description = "The path to log." description = "The path to log."
@@ -76,7 +67,7 @@ resource "coder_app" "vscode-web" {
url = var.folder == "" ? "http://localhost:${var.port}" : "http://localhost:${var.port}?folder=${var.folder}" url = var.folder == "" ? "http://localhost:${var.port}" : "http://localhost:${var.port}?folder=${var.folder}"
icon = "/icon/code.svg" icon = "/icon/code.svg"
subdomain = true subdomain = true
share = var.share share = "owner"
healthcheck { healthcheck {
url = "http://localhost:${var.port}/healthz" 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 "👷 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}!" 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 &