Compare commits
2 Commits
v1.0.13
...
slackme/ex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd3d42d54e | ||
|
|
66f1c3b97c |
8
.github/workflows/ci.yaml
vendored
@@ -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,11 +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: typos-action
|
|
||||||
uses: crate-ci/typos@v1.17.2
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: bun lint
|
run: bun install && bun lint
|
||||||
|
|||||||
42
.github/workflows/update-readme.yaml
vendored
@@ -1,42 +0,0 @@
|
|||||||
name: Update README on Tag
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update-readme:
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Get the latest tag
|
|
||||||
id: get-latest-tag
|
|
||||||
run: echo "TAG=$(git describe --tags --abbrev=0 | sed 's/^v//')" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Run update script
|
|
||||||
run: ./update-version.sh
|
|
||||||
|
|
||||||
- name: Create Pull Request
|
|
||||||
id: create-pr
|
|
||||||
uses: peter-evans/create-pull-request@v5
|
|
||||||
with:
|
|
||||||
commit-message: 'chore: bump version to ${{ env.TAG }} in README.md files'
|
|
||||||
title: 'chore: bump version to ${{ env.TAG }} in README.md files'
|
|
||||||
body: 'This is an auto-generated PR to update README.md files of all modules with the new tag ${{ env.TAG }}'
|
|
||||||
branch: 'update-readme-branch'
|
|
||||||
base: 'main'
|
|
||||||
env:
|
|
||||||
TAG: ${{ steps.get-latest-tag.outputs.TAG }}
|
|
||||||
|
|
||||||
- name: Auto-approve
|
|
||||||
uses: hmarr/auto-approve-action@v4
|
|
||||||
if: github.ref == 'refs/heads/update-readme-branch'
|
|
||||||
|
Before Width: | Height: | Size: 15 KiB |
@@ -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 |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg width="2270" height="2500" viewBox="0 0 256 282" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet"><g fill="#8CC84B"><path d="M116.504 3.58c6.962-3.985 16.03-4.003 22.986 0 34.995 19.774 70.001 39.517 104.99 59.303 6.581 3.707 10.983 11.031 10.916 18.614v118.968c.049 7.897-4.788 15.396-11.731 19.019-34.88 19.665-69.742 39.354-104.616 59.019-7.106 4.063-16.356 3.75-23.24-.646-10.457-6.062-20.932-12.094-31.39-18.15-2.137-1.274-4.546-2.288-6.055-4.36 1.334-1.798 3.719-2.022 5.657-2.807 4.365-1.388 8.374-3.616 12.384-5.778 1.014-.694 2.252-.428 3.224.193 8.942 5.127 17.805 10.403 26.777 15.481 1.914 1.105 3.852-.362 5.488-1.274 34.228-19.345 68.498-38.617 102.72-57.968 1.268-.61 1.969-1.956 1.866-3.345.024-39.245.006-78.497.012-117.742.145-1.576-.767-3.025-2.192-3.67-34.759-19.575-69.5-39.18-104.253-58.76a3.621 3.621 0 0 0-4.094-.006C91.2 39.257 56.465 58.88 21.712 78.454c-1.42.646-2.373 2.071-2.204 3.653.006 39.245 0 78.497 0 117.748a3.329 3.329 0 0 0 1.89 3.303c9.274 5.259 18.56 10.481 27.84 15.722 5.228 2.814 11.647 4.486 17.407 2.33 5.083-1.823 8.646-7.01 8.549-12.407.048-39.016-.024-78.038.036-117.048-.127-1.732 1.516-3.163 3.2-3 4.456-.03 8.918-.06 13.374.012 1.86-.042 3.14 1.823 2.91 3.568-.018 39.263.048 78.527-.03 117.79.012 10.464-4.287 21.85-13.966 26.97-11.924 6.177-26.662 4.867-38.442-1.056-10.198-5.09-19.93-11.097-29.947-16.55C5.368 215.886.555 208.357.604 200.466V81.497c-.073-7.74 4.504-15.197 11.29-18.85C46.768 42.966 81.636 23.27 116.504 3.58z"/><path d="M146.928 85.99c15.21-.979 31.493-.58 45.18 6.913 10.597 5.742 16.472 17.793 16.659 29.566-.296 1.588-1.956 2.464-3.472 2.355-4.413-.006-8.827.06-13.24-.03-1.872.072-2.96-1.654-3.195-3.309-1.268-5.633-4.34-11.212-9.642-13.929-8.139-4.075-17.576-3.87-26.451-3.785-6.479.344-13.446.905-18.935 4.715-4.214 2.886-5.494 8.712-3.99 13.404 1.418 3.369 5.307 4.456 8.489 5.458 18.33 4.794 37.754 4.317 55.734 10.626 7.444 2.572 14.726 7.572 17.274 15.366 3.333 10.446 1.872 22.932-5.56 31.318-6.027 6.901-14.805 10.657-23.56 12.697-11.647 2.597-23.734 2.663-35.562 1.51-11.122-1.268-22.696-4.19-31.282-11.768-7.342-6.375-10.928-16.308-10.572-25.895.085-1.619 1.697-2.748 3.248-2.615 4.444-.036 8.888-.048 13.332.006 1.775-.127 3.091 1.407 3.182 3.08.82 5.367 2.837 11 7.517 14.182 9.032 5.827 20.365 5.428 30.707 5.591 8.568-.38 18.186-.495 25.178-6.158 3.689-3.23 4.782-8.634 3.785-13.283-1.08-3.925-5.186-5.754-8.712-6.95-18.095-5.724-37.736-3.647-55.656-10.12-7.275-2.571-14.31-7.432-17.105-14.906-3.9-10.578-2.113-23.662 6.098-31.765 8.006-8.06 19.563-11.164 30.551-12.275z"/></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.5 KiB |
@@ -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 |
|
Before Width: | Height: | Size: 603 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 174 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 205 KiB |
@@ -11,10 +11,9 @@ tags: [helper]
|
|||||||
|
|
||||||
<!-- Describes what this module does -->
|
<!-- Describes what this module does -->
|
||||||
|
|
||||||
```tf
|
```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.2"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -26,10 +25,9 @@ module "MODULE_NAME" {
|
|||||||
|
|
||||||
Install the Dracula theme from [OpenVSX](https://open-vsx.org/):
|
Install the Dracula theme from [OpenVSX](https://open-vsx.org/):
|
||||||
|
|
||||||
```tf
|
```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.2"
|
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
extensions = [
|
extensions = [
|
||||||
"dracula-theme.theme-dracula"
|
"dracula-theme.theme-dracula"
|
||||||
@@ -43,10 +41,9 @@ Enter the `<author>.<name>` into the extensions array and code-server will autom
|
|||||||
|
|
||||||
Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarted/settings#_settingsjson) file:
|
Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarted/settings#_settingsjson) file:
|
||||||
|
|
||||||
```tf
|
```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.2"
|
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
extensions = [ "dracula-theme.theme-dracula" ]
|
extensions = [ "dracula-theme.theme-dracula" ]
|
||||||
settings = {
|
settings = {
|
||||||
@@ -59,10 +56,9 @@ module "MODULE_NAME" {
|
|||||||
|
|
||||||
Run code-server in the background, don't fetch it from GitHub:
|
Run code-server in the background, don't fetch it from GitHub:
|
||||||
|
|
||||||
```tf
|
```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.2"
|
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
offline = true
|
offline = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
coder = {
|
coder = {
|
||||||
source = "coder/coder"
|
source = "coder/coder"
|
||||||
version = ">= 0.17"
|
version = ">= 0.12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,12 +50,6 @@ variable "mutable" {
|
|||||||
description = "Whether the parameter is mutable."
|
description = "Whether the parameter is mutable."
|
||||||
default = true
|
default = true
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "order" {
|
|
||||||
type = number
|
|
||||||
description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
|
|
||||||
default = null
|
|
||||||
}
|
|
||||||
# Add other variables here
|
# Add other variables here
|
||||||
|
|
||||||
|
|
||||||
@@ -75,10 +69,9 @@ resource "coder_app" "MODULE_NAME" {
|
|||||||
slug = "MODULE_NAME"
|
slug = "MODULE_NAME"
|
||||||
display_name = "MODULE_NAME"
|
display_name = "MODULE_NAME"
|
||||||
url = "http://localhost:${var.port}"
|
url = "http://localhost:${var.port}"
|
||||||
icon = local.icon_url
|
icon = loocal.icon_url
|
||||||
subdomain = false
|
subdomain = false
|
||||||
share = "owner"
|
share = "owner"
|
||||||
order = var.order
|
|
||||||
|
|
||||||
# Remove if the app does not have a healthcheck endpoint
|
# Remove if the app does not have a healthcheck endpoint
|
||||||
healthcheck {
|
healthcheck {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
To create a new module, clone this repository and run:
|
To create a new module, clone this repository and run:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
./new.sh MODULE_NAME
|
./new.sh MOUDLE_NAME
|
||||||
```
|
```
|
||||||
|
|
||||||
## Testing a Module
|
## Testing a Module
|
||||||
@@ -19,10 +19,8 @@ $ 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
|
||||||
|
|
||||||
```tf
|
```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.
|
||||||
|
|||||||
@@ -14,10 +14,9 @@ Modules extend Templates to create reusable components for your development envi
|
|||||||
|
|
||||||
e.g.
|
e.g.
|
||||||
|
|
||||||
```tf
|
```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.2"
|
|
||||||
agent_id = coder_agent.main.id
|
agent_id = coder_agent.main.id
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
---
|
|
||||||
display_name: airflow
|
|
||||||
description: A module that adds Apache Airflow in your Coder template
|
|
||||||
icon: ../.icons/airflow.svg
|
|
||||||
maintainer_github: coder
|
|
||||||
partner_github: nataindata
|
|
||||||
verified: true
|
|
||||||
tags: [airflow, idea, web, helper]
|
|
||||||
---
|
|
||||||
|
|
||||||
# airflow
|
|
||||||
|
|
||||||
A module that adds Apache Airflow in your Coder template.
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "airflow" {
|
|
||||||
source = "registry.coder.com/modules/apache-airflow/coder"
|
|
||||||
version = "1.0.2"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
### Example 1
|
|
||||||
|
|
||||||
Install the Dracula theme from [OpenVSX](https://open-vsx.org/):
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "airflow" {
|
|
||||||
source = "registry.coder.com/modules/apache-airflow/coder"
|
|
||||||
version = "1.0.2"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
extensions = [
|
|
||||||
"dracula-theme.theme-dracula"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Enter the `<author>.<name>` into the extensions array and code-server will automatically install on start.
|
|
||||||
|
|
||||||
### Example 2
|
|
||||||
|
|
||||||
Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarted/settings#_settingsjson) file:
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "airflow" {
|
|
||||||
source = "registry.coder.com/modules/apache-airflow/coder"
|
|
||||||
version = "1.0.2"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
extensions = ["dracula-theme.theme-dracula"]
|
|
||||||
settings = {
|
|
||||||
"workbench.colorTheme" = "Dracula"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example 3
|
|
||||||
|
|
||||||
Run code-server in the background, don't fetch it from GitHub:
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "airflow" {
|
|
||||||
source = "registry.coder.com/modules/apache-airflow/coder"
|
|
||||||
version = "1.0.2"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
offline = true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
terraform {
|
|
||||||
required_version = ">= 1.0"
|
|
||||||
|
|
||||||
required_providers {
|
|
||||||
coder = {
|
|
||||||
source = "coder/coder"
|
|
||||||
version = ">= 0.17"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add required variables for your modules and remove any unneeded variables
|
|
||||||
variable "agent_id" {
|
|
||||||
type = string
|
|
||||||
description = "The ID of a Coder agent."
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "log_path" {
|
|
||||||
type = string
|
|
||||||
description = "The path to log airflow to."
|
|
||||||
default = "/tmp/airflow.log"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "port" {
|
|
||||||
type = number
|
|
||||||
description = "The port to run airflow on."
|
|
||||||
default = 8080
|
|
||||||
}
|
|
||||||
|
|
||||||
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 "order" {
|
|
||||||
type = number
|
|
||||||
description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
|
|
||||||
default = null
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "coder_script" "airflow" {
|
|
||||||
agent_id = var.agent_id
|
|
||||||
display_name = "airflow"
|
|
||||||
icon = "/icon/apache-guacamole.svg"
|
|
||||||
script = templatefile("${path.module}/run.sh", {
|
|
||||||
LOG_PATH : var.log_path,
|
|
||||||
PORT : var.port
|
|
||||||
})
|
|
||||||
run_on_start = true
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "coder_app" "airflow" {
|
|
||||||
agent_id = var.agent_id
|
|
||||||
slug = "airflow"
|
|
||||||
display_name = "airflow"
|
|
||||||
url = "http://localhost:${var.port}"
|
|
||||||
icon = "/icon/apache-guacamole.svg"
|
|
||||||
subdomain = true
|
|
||||||
share = var.share
|
|
||||||
order = var.order
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
|
|
||||||
BOLD='\033[0;1m'
|
|
||||||
|
|
||||||
PATH=$PATH:~/.local/bin
|
|
||||||
pip install --upgrade apache-airflow
|
|
||||||
|
|
||||||
filename=~/airflow/airflow.db
|
|
||||||
if ! [ -f $filename ] || ! [ -s $filename ]; then
|
|
||||||
airflow db init
|
|
||||||
fi
|
|
||||||
|
|
||||||
export AIRFLOW__CORE__LOAD_EXAMPLES=false
|
|
||||||
|
|
||||||
airflow webserver > ${LOG_PATH} 2>&1 &
|
|
||||||
|
|
||||||
airflow scheduler >> /tmp/airflow_scheduler.log 2>&1 &
|
|
||||||
|
|
||||||
airflow users create -u admin -p admin -r Admin -e admin@admin.com -f Coder -l User
|
|
||||||
@@ -14,10 +14,9 @@ the region closest to them.
|
|||||||
|
|
||||||
Customize the preselected parameter value:
|
Customize the preselected parameter value:
|
||||||
|
|
||||||
```tf
|
```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.12"
|
|
||||||
default = "us-east-1"
|
default = "us-east-1"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,16 +33,13 @@ provider "aws" {
|
|||||||
|
|
||||||
Change the display name and icon for a region using the corresponding maps:
|
Change the display name and icon for a region using the corresponding maps:
|
||||||
|
|
||||||
```tf
|
```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.12"
|
|
||||||
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"
|
||||||
}
|
}
|
||||||
@@ -60,10 +56,9 @@ provider "aws" {
|
|||||||
|
|
||||||
Hide the Asia Pacific regions Seoul and Osaka:
|
Hide the Asia Pacific regions Seoul and Osaka:
|
||||||
|
|
||||||
```tf
|
```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.12"
|
|
||||||
exclude = [ "ap-northeast-2", "ap-northeast-3" ]
|
exclude = [ "ap-northeast-2", "ap-northeast-3" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,13 +22,4 @@ describe("aws-region", async () => {
|
|||||||
});
|
});
|
||||||
expect(state.outputs.value.value).toBe("us-west-2");
|
expect(state.outputs.value.value).toBe("us-west-2");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("set custom order for coder_parameter", async () => {
|
|
||||||
const order = 99;
|
|
||||||
const state = await runTerraformApply(import.meta.dir, {
|
|
||||||
coder_parameter_order: order.toString(),
|
|
||||||
});
|
|
||||||
expect(state.resources).toHaveLength(1);
|
|
||||||
expect(state.resources[0].instances[0].attributes.order).toBe(order);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -51,25 +51,11 @@ variable "exclude" {
|
|||||||
type = list(string)
|
type = list(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "coder_parameter_order" {
|
|
||||||
type = number
|
|
||||||
description = "The order determines the position of a template parameter in the UI/CLI presentation. The lowest order is shown first and parameters with equal order are sorted by name (ascending order)."
|
|
||||||
default = null
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
locals {
|
||||||
# This is a static list because the regions don't change _that_
|
# This is a static list because the regions don't change _that_
|
||||||
# frequently and including the `aws_regions` data source requires
|
# frequently and including the `aws_regions` data source requires
|
||||||
# the provider, which requires a region.
|
# the provider, which requires a region.
|
||||||
regions = {
|
regions = {
|
||||||
"af-south-1" = {
|
|
||||||
name = "Africa (Cape Town)"
|
|
||||||
icon = "/emojis/1f1ff-1f1e6.png"
|
|
||||||
}
|
|
||||||
"ap-east-1" = {
|
|
||||||
name = "Asia Pacific (Hong Kong)"
|
|
||||||
icon = "/emojis/1f1ed-1f1f0.png"
|
|
||||||
}
|
|
||||||
"ap-northeast-1" = {
|
"ap-northeast-1" = {
|
||||||
name = "Asia Pacific (Tokyo)"
|
name = "Asia Pacific (Tokyo)"
|
||||||
icon = "/emojis/1f1ef-1f1f5.png"
|
icon = "/emojis/1f1ef-1f1f5.png"
|
||||||
@@ -86,10 +72,6 @@ locals {
|
|||||||
name = "Asia Pacific (Mumbai)"
|
name = "Asia Pacific (Mumbai)"
|
||||||
icon = "/emojis/1f1ee-1f1f3.png"
|
icon = "/emojis/1f1ee-1f1f3.png"
|
||||||
}
|
}
|
||||||
"ap-south-2" = {
|
|
||||||
name = "Asia Pacific (Hyderabad)"
|
|
||||||
icon = "/emojis/1f1ee-1f1f3.png"
|
|
||||||
}
|
|
||||||
"ap-southeast-1" = {
|
"ap-southeast-1" = {
|
||||||
name = "Asia Pacific (Singapore)"
|
name = "Asia Pacific (Singapore)"
|
||||||
icon = "/emojis/1f1f8-1f1ec.png"
|
icon = "/emojis/1f1f8-1f1ec.png"
|
||||||
@@ -98,42 +80,18 @@ locals {
|
|||||||
name = "Asia Pacific (Sydney)"
|
name = "Asia Pacific (Sydney)"
|
||||||
icon = "/emojis/1f1e6-1f1fa.png"
|
icon = "/emojis/1f1e6-1f1fa.png"
|
||||||
}
|
}
|
||||||
"ap-southeast-3" = {
|
|
||||||
name = "Asia Pacific (Jakarta)"
|
|
||||||
icon = "/emojis/1f1ee-1f1e9.png"
|
|
||||||
}
|
|
||||||
"ap-southeast-4" = {
|
|
||||||
name = "Asia Pacific (Melbourne)"
|
|
||||||
icon = "/emojis/1f1e6-1f1fa.png"
|
|
||||||
}
|
|
||||||
"ca-central-1" = {
|
"ca-central-1" = {
|
||||||
name = "Canada (Central)"
|
name = "Canada (Central)"
|
||||||
icon = "/emojis/1f1e8-1f1e6.png"
|
icon = "/emojis/1f1e8-1f1e6.png"
|
||||||
}
|
}
|
||||||
"ca-west-1" = {
|
|
||||||
name = "Canada West (Calgary)"
|
|
||||||
icon = "/emojis/1f1e8-1f1e6.png"
|
|
||||||
}
|
|
||||||
"eu-central-1" = {
|
"eu-central-1" = {
|
||||||
name = "EU (Frankfurt)"
|
name = "EU (Frankfurt)"
|
||||||
icon = "/emojis/1f1ea-1f1fa.png"
|
icon = "/emojis/1f1ea-1f1fa.png"
|
||||||
}
|
}
|
||||||
"eu-central-2" = {
|
|
||||||
name = "Europe (Zurich)"
|
|
||||||
icon = "/emojis/1f1ea-1f1fa.png"
|
|
||||||
}
|
|
||||||
"eu-north-1" = {
|
"eu-north-1" = {
|
||||||
name = "EU (Stockholm)"
|
name = "EU (Stockholm)"
|
||||||
icon = "/emojis/1f1ea-1f1fa.png"
|
icon = "/emojis/1f1ea-1f1fa.png"
|
||||||
}
|
}
|
||||||
"eu-south-1" = {
|
|
||||||
name = "Europe (Milan)"
|
|
||||||
icon = "/emojis/1f1ea-1f1fa.png"
|
|
||||||
}
|
|
||||||
"eu-south-2" = {
|
|
||||||
name = "Europe (Spain)"
|
|
||||||
icon = "/emojis/1f1ea-1f1fa.png"
|
|
||||||
}
|
|
||||||
"eu-west-1" = {
|
"eu-west-1" = {
|
||||||
name = "EU (Ireland)"
|
name = "EU (Ireland)"
|
||||||
icon = "/emojis/1f1ea-1f1fa.png"
|
icon = "/emojis/1f1ea-1f1fa.png"
|
||||||
@@ -146,14 +104,6 @@ locals {
|
|||||||
name = "EU (Paris)"
|
name = "EU (Paris)"
|
||||||
icon = "/emojis/1f1ea-1f1fa.png"
|
icon = "/emojis/1f1ea-1f1fa.png"
|
||||||
}
|
}
|
||||||
"il-central-1" = {
|
|
||||||
name = "Israel (Tel Aviv)"
|
|
||||||
icon = "/emojis/1f1ee-1f1f1.png"
|
|
||||||
}
|
|
||||||
"me-south-1" = {
|
|
||||||
name = "Middle East (Bahrain)"
|
|
||||||
icon = "/emojis/1f1e7-1f1ed.png"
|
|
||||||
}
|
|
||||||
"sa-east-1" = {
|
"sa-east-1" = {
|
||||||
name = "South America (São Paulo)"
|
name = "South America (São Paulo)"
|
||||||
icon = "/emojis/1f1e7-1f1f7.png"
|
icon = "/emojis/1f1e7-1f1f7.png"
|
||||||
@@ -182,7 +132,6 @@ data "coder_parameter" "region" {
|
|||||||
display_name = var.display_name
|
display_name = var.display_name
|
||||||
description = var.description
|
description = var.description
|
||||||
default = var.default == "" ? null : var.default
|
default = var.default == "" ? null : var.default
|
||||||
order = var.coder_parameter_order
|
|
||||||
mutable = var.mutable
|
mutable = var.mutable
|
||||||
dynamic "option" {
|
dynamic "option" {
|
||||||
for_each = { for k, v in local.regions : k => v if !(contains(var.exclude, k)) }
|
for_each = { for k, v in local.regions : k => v if !(contains(var.exclude, k)) }
|
||||||
|
|||||||
@@ -11,10 +11,9 @@ tags: [helper, parameter, azure, regions]
|
|||||||
|
|
||||||
This module adds a parameter with all Azure regions, allowing developers to select the region closest to them.
|
This module adds a parameter with all Azure regions, allowing developers to select the region closest to them.
|
||||||
|
|
||||||
```tf
|
```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.12"
|
|
||||||
default = "eastus"
|
default = "eastus"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,10 +30,9 @@ resource "azurem_resource_group" "example" {
|
|||||||
|
|
||||||
Change the display name and icon for a region using the corresponding maps:
|
Change the display name and icon for a region using the corresponding maps:
|
||||||
|
|
||||||
```tf
|
```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.12"
|
|
||||||
custom_names = {
|
custom_names = {
|
||||||
"australia": "Go Australia!"
|
"australia": "Go Australia!"
|
||||||
}
|
}
|
||||||
@@ -54,10 +52,9 @@ resource "azurerm_resource_group" "example" {
|
|||||||
|
|
||||||
Hide all regions in Australia except australiacentral:
|
Hide all regions in Australia except australiacentral:
|
||||||
|
|
||||||
```tf
|
```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.12"
|
|
||||||
exclude = [
|
exclude = [
|
||||||
"australia",
|
"australia",
|
||||||
"australiacentral2",
|
"australiacentral2",
|
||||||
|
|||||||
@@ -22,13 +22,4 @@ describe("azure-region", async () => {
|
|||||||
});
|
});
|
||||||
expect(state.outputs.value.value).toBe("westus");
|
expect(state.outputs.value.value).toBe("westus");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("set custom order for coder_parameter", async () => {
|
|
||||||
const order = 99;
|
|
||||||
const state = await runTerraformApply(import.meta.dir, {
|
|
||||||
coder_parameter_order: order.toString(),
|
|
||||||
});
|
|
||||||
expect(state.resources).toHaveLength(1);
|
|
||||||
expect(state.resources[0].instances[0].attributes.order).toBe(order);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -50,12 +50,6 @@ variable "exclude" {
|
|||||||
type = list(string)
|
type = list(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "coder_parameter_order" {
|
|
||||||
type = number
|
|
||||||
description = "The order determines the position of a template parameter in the UI/CLI presentation. The lowest order is shown first and parameters with equal order are sorted by name (ascending order)."
|
|
||||||
default = null
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
locals {
|
||||||
# Note: Options are limited to 64 regions, some redundant regions have been removed.
|
# Note: Options are limited to 64 regions, some redundant regions have been removed.
|
||||||
all_regions = {
|
all_regions = {
|
||||||
@@ -315,7 +309,6 @@ data "coder_parameter" "region" {
|
|||||||
display_name = var.display_name
|
display_name = var.display_name
|
||||||
description = var.description
|
description = var.description
|
||||||
default = var.default == "" ? null : var.default
|
default = var.default == "" ? null : var.default
|
||||||
order = var.coder_parameter_order
|
|
||||||
mutable = var.mutable
|
mutable = var.mutable
|
||||||
icon = "/icon/azure.png"
|
icon = "/icon/azure.png"
|
||||||
dynamic "option" {
|
dynamic "option" {
|
||||||
|
|||||||
@@ -11,10 +11,9 @@ tags: [helper, ide, web]
|
|||||||
|
|
||||||
Automatically install [code-server](https://github.com/coder/code-server) in a workspace, create an app to access it via the dashboard, install extensions, and pre-configure editor settings.
|
Automatically install [code-server](https://github.com/coder/code-server) in a workspace, create an app to access it via the dashboard, install extensions, and pre-configure editor settings.
|
||||||
|
|
||||||
```tf
|
```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.12"
|
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -25,10 +24,9 @@ module "code-server" {
|
|||||||
|
|
||||||
### Pin Versions
|
### Pin Versions
|
||||||
|
|
||||||
```tf
|
```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.12"
|
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
install_version = "4.8.3"
|
install_version = "4.8.3"
|
||||||
}
|
}
|
||||||
@@ -38,10 +36,9 @@ module "code-server" {
|
|||||||
|
|
||||||
Install the Dracula theme from [OpenVSX](https://open-vsx.org/):
|
Install the Dracula theme from [OpenVSX](https://open-vsx.org/):
|
||||||
|
|
||||||
```tf
|
```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.12"
|
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
extensions = [
|
extensions = [
|
||||||
"dracula-theme.theme-dracula"
|
"dracula-theme.theme-dracula"
|
||||||
@@ -55,10 +52,9 @@ Enter the `<author>.<name>` into the extensions array and code-server will autom
|
|||||||
|
|
||||||
Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarted/settings#_settingsjson) file:
|
Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarted/settings#_settingsjson) file:
|
||||||
|
|
||||||
```tf
|
```hcl
|
||||||
module "code-server" {
|
module "settings" {
|
||||||
source = "registry.coder.com/modules/code-server/coder"
|
source = "https://registry.coder.com/modules/code-server"
|
||||||
version = "1.0.12"
|
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
extensions = [ "dracula-theme.theme-dracula" ]
|
extensions = [ "dracula-theme.theme-dracula" ]
|
||||||
settings = {
|
settings = {
|
||||||
@@ -67,41 +63,13 @@ module "code-server" {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Install multiple extensions
|
### Offline Mode
|
||||||
|
|
||||||
Just run code-server in the background, don't fetch it from GitHub:
|
Just run code-server in the background, don't fetch it from GitHub:
|
||||||
|
|
||||||
```tf
|
```hcl
|
||||||
module "code-server" {
|
module "settings" {
|
||||||
source = "registry.coder.com/modules/code-server/coder"
|
source = "https://registry.coder.com/modules/code-server"
|
||||||
version = "1.0.12"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
extensions = ["dracula-theme.theme-dracula", "ms-azuretools.vscode-docker"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Offline and Use Cached Modes
|
|
||||||
|
|
||||||
By default the module looks for code-server at `/tmp/code-server` but this can be changed with `install_prefix`.
|
|
||||||
|
|
||||||
Run an existing copy of code-server if found, otherwise download from GitHub:
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "code-server" {
|
|
||||||
source = "registry.coder.com/modules/code-server/coder"
|
|
||||||
version = "1.0.12"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
use_cached = true
|
|
||||||
extensions = ["dracula-theme.theme-dracula", "ms-azuretools.vscode-docker"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Just run code-server in the background, don't fetch it from GitHub:
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "code-server" {
|
|
||||||
source = "registry.coder.com/modules/code-server/coder"
|
|
||||||
version = "1.0.12"
|
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
offline = true
|
offline = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
import { describe, expect, it } from "bun:test";
|
import { describe, expect, it } from "bun:test";
|
||||||
import {
|
import { runTerraformInit, testRequiredVariables } from "../test";
|
||||||
runTerraformApply,
|
|
||||||
runTerraformInit,
|
|
||||||
testRequiredVariables,
|
|
||||||
} from "../test";
|
|
||||||
|
|
||||||
describe("code-server", async () => {
|
describe("code-server", async () => {
|
||||||
await runTerraformInit(import.meta.dir);
|
await runTerraformInit(import.meta.dir);
|
||||||
@@ -12,27 +8,5 @@ describe("code-server", async () => {
|
|||||||
agent_id: "foo",
|
agent_id: "foo",
|
||||||
});
|
});
|
||||||
|
|
||||||
it("use_cached and offline can not be used together", () => {
|
|
||||||
const t = async () => {
|
|
||||||
await runTerraformApply(import.meta.dir, {
|
|
||||||
agent_id: "foo",
|
|
||||||
use_cached: "true",
|
|
||||||
offline: "true",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
expect(t).toThrow("Offline and Use Cached can not be used together");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("offline and extensions can not be used together", () => {
|
|
||||||
const t = async () => {
|
|
||||||
await runTerraformApply(import.meta.dir, {
|
|
||||||
agent_id: "foo",
|
|
||||||
offline: "true",
|
|
||||||
extensions: '["1", "2"]',
|
|
||||||
});
|
|
||||||
};
|
|
||||||
expect(t).toThrow("Offline mode does not allow extensions to be installed");
|
|
||||||
});
|
|
||||||
|
|
||||||
// More tests depend on shebang refactors
|
// More tests depend on shebang refactors
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
coder = {
|
coder = {
|
||||||
source = "coder/coder"
|
source = "coder/coder"
|
||||||
version = ">= 0.17"
|
version = ">= 0.12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,12 +32,6 @@ variable "display_name" {
|
|||||||
default = "code-server"
|
default = "code-server"
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "slug" {
|
|
||||||
type = string
|
|
||||||
description = "The slug for the code-server application."
|
|
||||||
default = "code-server"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "settings" {
|
variable "settings" {
|
||||||
type = map(string)
|
type = map(string)
|
||||||
description = "A map of settings to apply to code-server."
|
description = "A map of settings to apply to code-server."
|
||||||
@@ -68,39 +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'."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "order" {
|
|
||||||
type = number
|
|
||||||
description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
|
|
||||||
default = null
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "offline" {
|
|
||||||
type = bool
|
|
||||||
description = "Just run code-server in the background, don't fetch it from GitHub"
|
|
||||||
default = false
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "use_cached" {
|
|
||||||
type = bool
|
|
||||||
description = "Uses cached copy code-server in the background, otherwise fetched it from GitHub"
|
|
||||||
default = false
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "extensions_dir" {
|
|
||||||
type = string
|
|
||||||
description = "Override the directory to store extensions in."
|
|
||||||
default = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
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"
|
||||||
@@ -108,40 +69,23 @@ resource "coder_script" "code-server" {
|
|||||||
script = templatefile("${path.module}/run.sh", {
|
script = templatefile("${path.module}/run.sh", {
|
||||||
VERSION : var.install_version,
|
VERSION : var.install_version,
|
||||||
EXTENSIONS : join(",", var.extensions),
|
EXTENSIONS : join(",", var.extensions),
|
||||||
APP_NAME : var.display_name,
|
|
||||||
PORT : var.port,
|
PORT : var.port,
|
||||||
LOG_PATH : var.log_path,
|
LOG_PATH : var.log_path,
|
||||||
INSTALL_PREFIX : var.install_prefix,
|
INSTALL_PREFIX : var.install_prefix,
|
||||||
// This is necessary otherwise the quotes are stripped!
|
// This is necessary otherwise the quotes are stripped!
|
||||||
SETTINGS : replace(jsonencode(var.settings), "\"", "\\\""),
|
SETTINGS : replace(jsonencode(var.settings), "\"", "\\\""),
|
||||||
OFFLINE : var.offline,
|
|
||||||
USE_CACHED : var.use_cached,
|
|
||||||
EXTENSIONS_DIR : var.extensions_dir,
|
|
||||||
})
|
})
|
||||||
run_on_start = true
|
run_on_start = true
|
||||||
|
|
||||||
lifecycle {
|
|
||||||
precondition {
|
|
||||||
condition = !var.offline || length(var.extensions) == 0
|
|
||||||
error_message = "Offline mode does not allow extensions to be installed"
|
|
||||||
}
|
|
||||||
|
|
||||||
precondition {
|
|
||||||
condition = !var.offline || !var.use_cached
|
|
||||||
error_message = "Offline and Use Cached can not be used together"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "coder_app" "code-server" {
|
resource "coder_app" "code-server" {
|
||||||
agent_id = var.agent_id
|
agent_id = var.agent_id
|
||||||
slug = var.slug
|
slug = "code-server"
|
||||||
display_name = var.display_name
|
display_name = var.display_name
|
||||||
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"
|
||||||
order = var.order
|
|
||||||
|
|
||||||
healthcheck {
|
healthcheck {
|
||||||
url = "http://localhost:${var.port}/healthz"
|
url = "http://localhost:${var.port}/healthz"
|
||||||
|
|||||||
@@ -4,40 +4,6 @@ EXTENSIONS=("${EXTENSIONS}")
|
|||||||
BOLD='\033[0;1m'
|
BOLD='\033[0;1m'
|
||||||
CODE='\033[36;40;1m'
|
CODE='\033[36;40;1m'
|
||||||
RESET='\033[0m'
|
RESET='\033[0m'
|
||||||
CODE_SERVER="${INSTALL_PREFIX}/bin/code-server"
|
|
||||||
|
|
||||||
# Set extension directory
|
|
||||||
EXTENSION_ARG=""
|
|
||||||
if [ -n "${EXTENSIONS_DIR}" ]; then
|
|
||||||
EXTENSION_ARG="--extensions-dir=${EXTENSIONS_DIR}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
function run_code_server() {
|
|
||||||
echo "👷 Running code-server in the background..."
|
|
||||||
echo "Check logs at ${LOG_PATH}!"
|
|
||||||
$CODE_SERVER "$EXTENSION_ARG" --auth none --port "${PORT}" --app-name "${APP_NAME}" > "${LOG_PATH}" 2>&1 &
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if the settings file exists...
|
|
||||||
if [ ! -f ~/.local/share/code-server/User/settings.json ]; then
|
|
||||||
echo "⚙️ Creating settings file..."
|
|
||||||
mkdir -p ~/.local/share/code-server/User
|
|
||||||
echo "${SETTINGS}" > ~/.local/share/code-server/User/settings.json
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if code-server is already installed for offline or cached mode
|
|
||||||
if [ -f "$CODE_SERVER" ]; then
|
|
||||||
if [ "${OFFLINE}" = true ] || [ "${USE_CACHED}" = true ]; then
|
|
||||||
echo "🥳 Found a copy of code-server"
|
|
||||||
run_code_server
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
# Offline mode always expects a copy of code-server to be present
|
|
||||||
if [ "${OFFLINE}" = true ]; then
|
|
||||||
echo "Failed to find a copy of code-server"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf "$${BOLD}Installing code-server!\n"
|
printf "$${BOLD}Installing code-server!\n"
|
||||||
|
|
||||||
@@ -56,18 +22,28 @@ if [ $? -ne 0 ]; then
|
|||||||
fi
|
fi
|
||||||
printf "🥳 code-server has been installed in ${INSTALL_PREFIX}\n\n"
|
printf "🥳 code-server has been installed in ${INSTALL_PREFIX}\n\n"
|
||||||
|
|
||||||
|
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
|
||||||
printf "🧩 Installing extension $${CODE}$extension$${RESET}...\n"
|
printf "🧩 Installing extension $${CODE}$extension$${RESET}...\n"
|
||||||
output=$($CODE_SERVER "$EXTENSION_ARG" --install-extension "$extension")
|
output=$($CODE_SERVER --install-extension "$extension")
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "Failed to install extension: $extension: $output"
|
echo "Failed to install extension: $extension: $output"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
run_code_server
|
# Check if the settings file exists...
|
||||||
|
if [ ! -f ~/.local/share/code-server/Machine/settings.json ]; then
|
||||||
|
echo "⚙️ Creating settings file..."
|
||||||
|
mkdir -p ~/.local/share/code-server/Machine
|
||||||
|
echo "${SETTINGS}" >~/.local/share/code-server/Machine/settings.json
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "👷 Running code-server in the background..."
|
||||||
|
echo "Check logs at ${LOG_PATH}!"
|
||||||
|
$CODE_SERVER --auth none --port ${PORT} >${LOG_PATH} 2>&1 &
|
||||||
|
|||||||
@@ -11,10 +11,9 @@ tags: [helper]
|
|||||||
|
|
||||||
Automatically logs the user into Coder when creating their workspace.
|
Automatically logs the user into Coder when creating their workspace.
|
||||||
|
|
||||||
```tf
|
```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.2"
|
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ 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
|
||||||
|
|||||||
@@ -11,23 +11,9 @@ tags: [helper]
|
|||||||
|
|
||||||
Allow developers to optionally bring their own [dotfiles repository](https://dotfiles.github.io)! Under the hood, this module uses the [coder dotfiles](https://coder.com/docs/v2/latest/dotfiles) command.
|
Allow developers to optionally bring their own [dotfiles repository](https://dotfiles.github.io)! Under the hood, this module uses the [coder dotfiles](https://coder.com/docs/v2/latest/dotfiles) command.
|
||||||
|
|
||||||
```tf
|
```hcl
|
||||||
module "dotfiles" {
|
module "dotfiles" {
|
||||||
source = "registry.coder.com/modules/dotfiles/coder"
|
source = "https://registry.coder.com/modules/dotfiles"
|
||||||
version = "1.0.12"
|
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Setting a default dotfiles repository
|
|
||||||
|
|
||||||
You can set a default dotfiles repository for all users by setting the `default_dotfiles_uri` variable:
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "dotfiles" {
|
|
||||||
source = "registry.coder.com/modules/dotfiles/coder"
|
|
||||||
version = "1.0.12"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
default_dotfiles_uri = "https://github.com/coder/dotfiles"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -18,23 +18,4 @@ describe("dotfiles", async () => {
|
|||||||
});
|
});
|
||||||
expect(state.outputs.dotfiles_uri.value).toBe("");
|
expect(state.outputs.dotfiles_uri.value).toBe("");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("set a default dotfiles_uri", async () => {
|
|
||||||
const default_dotfiles_uri = "foo";
|
|
||||||
const state = await runTerraformApply(import.meta.dir, {
|
|
||||||
agent_id: "foo",
|
|
||||||
default_dotfiles_uri,
|
|
||||||
});
|
|
||||||
expect(state.outputs.dotfiles_uri.value).toBe(default_dotfiles_uri);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("set custom order for coder_parameter", async () => {
|
|
||||||
const order = 99;
|
|
||||||
const state = await runTerraformApply(import.meta.dir, {
|
|
||||||
agent_id: "foo",
|
|
||||||
coder_parameter_order: order.toString(),
|
|
||||||
});
|
|
||||||
expect(state.resources).toHaveLength(2);
|
|
||||||
expect(state.resources[0].instances[0].attributes.order).toBe(order);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,24 +14,11 @@ variable "agent_id" {
|
|||||||
description = "The ID of a Coder agent."
|
description = "The ID of a Coder agent."
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "default_dotfiles_uri" {
|
|
||||||
type = string
|
|
||||||
description = "The default dotfiles URI if the workspace user does not provide one."
|
|
||||||
default = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "coder_parameter_order" {
|
|
||||||
type = number
|
|
||||||
description = "The order determines the position of a template parameter in the UI/CLI presentation. The lowest order is shown first and parameters with equal order are sorted by name (ascending order)."
|
|
||||||
default = null
|
|
||||||
}
|
|
||||||
|
|
||||||
data "coder_parameter" "dotfiles_uri" {
|
data "coder_parameter" "dotfiles_uri" {
|
||||||
type = "string"
|
type = "string"
|
||||||
name = "dotfiles_uri"
|
name = "dotfiles_uri"
|
||||||
display_name = "Dotfiles URL (optional)"
|
display_name = "Dotfiles URL (optional)"
|
||||||
order = var.coder_parameter_order
|
default = ""
|
||||||
default = var.default_dotfiles_uri
|
|
||||||
description = "Enter a URL for a [dotfiles repository](https://dotfiles.github.io) to personalize your workspace"
|
description = "Enter a URL for a [dotfiles repository](https://dotfiles.github.io) to personalize your workspace"
|
||||||
mutable = true
|
mutable = true
|
||||||
icon = "/icon/dotfiles.svg"
|
icon = "/icon/dotfiles.svg"
|
||||||
|
|||||||
@@ -1,114 +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 virtual machine for the workspace.
|
|
||||||
|
|
||||||
Customize the preselected parameter value:
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "exoscale-instance-type" {
|
|
||||||
source = "registry.coder.com/modules/exoscale-instance-type/coder"
|
|
||||||
version = "1.0.12"
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
### Customize type
|
|
||||||
|
|
||||||
Change the display name a type using the corresponding maps:
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "exoscale-instance-type" {
|
|
||||||
source = "registry.coder.com/modules/exoscale-instance-type/coder"
|
|
||||||
version = "1.0.12"
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Use category and exclude type
|
|
||||||
|
|
||||||
Show only gpu1 types
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "exoscale-instance-type" {
|
|
||||||
source = "registry.coder.com/modules/exoscale-instance-type/coder"
|
|
||||||
version = "1.0.12"
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Related templates
|
|
||||||
|
|
||||||
A related exoscale template will be provided soon.
|
|
||||||
@@ -1,43 +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');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("set custom order for coder_parameter", async () => {
|
|
||||||
const order = 99;
|
|
||||||
const state = await runTerraformApply(import.meta.dir, {
|
|
||||||
coder_parameter_order: order.toString(),
|
|
||||||
});
|
|
||||||
expect(state.resources).toHaveLength(1);
|
|
||||||
expect(state.resources[0].instances[0].attributes.order).toBe(order);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,286 +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)
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "coder_parameter_order" {
|
|
||||||
type = number
|
|
||||||
description = "The order determines the position of a template parameter in the UI/CLI presentation. The lowest order is shown first and parameters with equal order are sorted by name (ascending order)."
|
|
||||||
default = null
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
order = var.coder_parameter_order
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,98 +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:
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "exoscale-zone" {
|
|
||||||
source = "registry.coder.com/modules/exoscale-zone/coder"
|
|
||||||
version = "1.0.12"
|
|
||||||
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
|
|
||||||
# ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
### Customize zones
|
|
||||||
|
|
||||||
Change the display name and icon for a zone using the corresponding maps:
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "exoscale-zone" {
|
|
||||||
source = "registry.coder.com/modules/exoscale-zone/coder"
|
|
||||||
version = "1.0.12"
|
|
||||||
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
|
|
||||||
# ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Exclude regions
|
|
||||||
|
|
||||||
Hide the Switzerland zones Geneva and Zurich
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "exoscale-zone" {
|
|
||||||
source = "registry.coder.com/modules/exoscale-zone/coder"
|
|
||||||
version = "1.0.12"
|
|
||||||
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
|
|
||||||
# ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Related templates
|
|
||||||
|
|
||||||
An exoscale sample template will be delivered soon.
|
|
||||||
@@ -1,34 +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");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("set custom order for coder_parameter", async () => {
|
|
||||||
const order = 99;
|
|
||||||
const state = await runTerraformApply(import.meta.dir, {
|
|
||||||
coder_parameter_order: order.toString(),
|
|
||||||
});
|
|
||||||
expect(state.resources).toHaveLength(1);
|
|
||||||
expect(state.resources[0].instances[0].attributes.order).toBe(order);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,116 +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)
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "coder_parameter_order" {
|
|
||||||
type = number
|
|
||||||
description = "The order determines the position of a template parameter in the UI/CLI presentation. The lowest order is shown first and parameters with equal order are sorted by name (ascending order)."
|
|
||||||
default = null
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
order = var.coder_parameter_order
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -11,10 +11,9 @@ tags: [helper, filebrowser]
|
|||||||
|
|
||||||
A file browser for your workspace.
|
A file browser for your workspace.
|
||||||
|
|
||||||
```tf
|
```hcl
|
||||||
module "filebrowser" {
|
module "filebrowser" {
|
||||||
source = "registry.coder.com/modules/filebrowser/coder"
|
source = "https://registry.coder.com/modules/filebrowser"
|
||||||
version = "1.0.8"
|
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -25,10 +24,9 @@ module "filebrowser" {
|
|||||||
|
|
||||||
### Serve a specific directory
|
### Serve a specific directory
|
||||||
|
|
||||||
```tf
|
```hcl
|
||||||
module "filebrowser" {
|
module "filebrowser" {
|
||||||
source = "registry.coder.com/modules/filebrowser/coder"
|
source = "https://registry.coder.com/modules/filebrowser"
|
||||||
version = "1.0.8"
|
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
folder = "/home/coder/project"
|
folder = "/home/coder/project"
|
||||||
}
|
}
|
||||||
@@ -36,10 +34,9 @@ module "filebrowser" {
|
|||||||
|
|
||||||
### Specify location of `filebrowser.db`
|
### Specify location of `filebrowser.db`
|
||||||
|
|
||||||
```tf
|
```hcl
|
||||||
module "filebrowser" {
|
module "filebrowser" {
|
||||||
source = "registry.coder.com/modules/filebrowser/coder"
|
source = "https://registry.coder.com/modules/filebrowser"
|
||||||
version = "1.0.8"
|
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
database_path = ".config/filebrowser.db"
|
database_path = ".config/filebrowser.db"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ describe("filebrowser", async () => {
|
|||||||
expect(output.stdout).toEqual([
|
expect(output.stdout).toEqual([
|
||||||
"\u001b[0;1mInstalling filebrowser ",
|
"\u001b[0;1mInstalling filebrowser ",
|
||||||
"",
|
"",
|
||||||
"🥳 Installation complete! ",
|
"🥳 Installation comlete! ",
|
||||||
"",
|
"",
|
||||||
"👷 Starting filebrowser in background... ",
|
"👷 Starting filebrowser in background... ",
|
||||||
"",
|
"",
|
||||||
@@ -55,7 +55,7 @@ describe("filebrowser", async () => {
|
|||||||
expect(output.stdout).toEqual([
|
expect(output.stdout).toEqual([
|
||||||
"\u001b[0;1mInstalling filebrowser ",
|
"\u001b[0;1mInstalling filebrowser ",
|
||||||
"",
|
"",
|
||||||
"🥳 Installation complete! ",
|
"🥳 Installation comlete! ",
|
||||||
"",
|
"",
|
||||||
"👷 Starting filebrowser in background... ",
|
"👷 Starting filebrowser in background... ",
|
||||||
"",
|
"",
|
||||||
@@ -77,7 +77,7 @@ describe("filebrowser", async () => {
|
|||||||
expect(output.stdout).toEqual([
|
expect(output.stdout).toEqual([
|
||||||
"\u001B[0;1mInstalling filebrowser ",
|
"\u001B[0;1mInstalling filebrowser ",
|
||||||
"",
|
"",
|
||||||
"🥳 Installation complete! ",
|
"🥳 Installation comlete! ",
|
||||||
"",
|
"",
|
||||||
"👷 Starting filebrowser in background... ",
|
"👷 Starting filebrowser in background... ",
|
||||||
"",
|
"",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
coder = {
|
coder = {
|
||||||
source = "coder/coder"
|
source = "coder/coder"
|
||||||
version = ">= 0.17"
|
version = ">= 0.12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,21 +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'."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "order" {
|
|
||||||
type = number
|
|
||||||
description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
|
|
||||||
default = null
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "coder_script" "filebrowser" {
|
resource "coder_script" "filebrowser" {
|
||||||
agent_id = var.agent_id
|
agent_id = var.agent_id
|
||||||
display_name = "File Browser"
|
display_name = "File Browser"
|
||||||
@@ -79,6 +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"
|
||||||
order = var.order
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ printf "$${BOLD}Installing filebrowser \n\n"
|
|||||||
|
|
||||||
curl -fsSL https://raw.githubusercontent.com/filebrowser/get/master/get.sh | bash
|
curl -fsSL https://raw.githubusercontent.com/filebrowser/get/master/get.sh | bash
|
||||||
|
|
||||||
printf "🥳 Installation complete! \n\n"
|
printf "🥳 Installation comlete! \n\n"
|
||||||
|
|
||||||
printf "👷 Starting filebrowser in background... \n\n"
|
printf "👷 Starting filebrowser in background... \n\n"
|
||||||
|
|
||||||
|
|||||||
@@ -13,10 +13,9 @@ This module adds Fly.io regions to your Coder template. Regions can be whitelist
|
|||||||
|
|
||||||
We can use the simplest format here, only adding a default selection as the `atl` region.
|
We can use the simplest format here, only adding a default selection as the `atl` region.
|
||||||
|
|
||||||
```tf
|
```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.2"
|
|
||||||
default = "atl"
|
default = "atl"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -29,10 +28,9 @@ module "fly-region" {
|
|||||||
|
|
||||||
The regions argument can be used to display only the desired regions in the Coder parameter.
|
The regions argument can be used to display only the desired regions in the Coder parameter.
|
||||||
|
|
||||||
```tf
|
```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.2"
|
|
||||||
default = "ams"
|
default = "ams"
|
||||||
regions = ["ams", "arn", "atl"]
|
regions = ["ams", "arn", "atl"]
|
||||||
}
|
}
|
||||||
@@ -44,16 +42,13 @@ module "fly-region" {
|
|||||||
|
|
||||||
Set custom icons and names with their respective maps.
|
Set custom icons and names with their respective maps.
|
||||||
|
|
||||||
```tf
|
```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.2"
|
|
||||||
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!"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,9 @@ tags: [gcp, regions, parameter, helper]
|
|||||||
|
|
||||||
This module adds Google Cloud Platform regions to your Coder template.
|
This module adds Google Cloud Platform regions to your Coder template.
|
||||||
|
|
||||||
```tf
|
```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.12"
|
|
||||||
regions = ["us", "europe"]
|
regions = ["us", "europe"]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,10 +30,9 @@ resource "google_compute_instance" "example" {
|
|||||||
|
|
||||||
Note: setting `gpu_only = true` and using a default region without GPU support, the default will be set to `null`.
|
Note: setting `gpu_only = true` and using a default region without GPU support, the default will be set to `null`.
|
||||||
|
|
||||||
```tf
|
```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.12"
|
|
||||||
default = ["us-west1-a"]
|
default = ["us-west1-a"]
|
||||||
regions = ["us-west1"]
|
regions = ["us-west1"]
|
||||||
gpu_only = false
|
gpu_only = false
|
||||||
@@ -47,10 +45,9 @@ resource "google_compute_instance" "example" {
|
|||||||
|
|
||||||
### Add all zones in the Europe West region
|
### Add all zones in the Europe West region
|
||||||
|
|
||||||
```tf
|
```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.12"
|
|
||||||
regions = ["europe-west"]
|
regions = ["europe-west"]
|
||||||
single_zone_per_region = false
|
single_zone_per_region = false
|
||||||
}
|
}
|
||||||
@@ -60,12 +57,11 @@ resource "google_compute_instance" "example" {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Add a single zone from each region in US and Europe that has GPUs
|
### Add a single zone from each region in US and Europe that laos has GPUs
|
||||||
|
|
||||||
```tf
|
```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.12"
|
|
||||||
regions = ["us", "europe"]
|
regions = ["us", "europe"]
|
||||||
gpu_only = true
|
gpu_only = true
|
||||||
single_zone_per_region = true
|
single_zone_per_region = true
|
||||||
|
|||||||
@@ -40,13 +40,4 @@ describe("gcp-region", async () => {
|
|||||||
});
|
});
|
||||||
expect(state.outputs.value.value).toBe("us-west2-b");
|
expect(state.outputs.value.value).toBe("us-west2-b");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("set custom order for coder_parameter", async () => {
|
|
||||||
const order = 99;
|
|
||||||
const state = await runTerraformApply(import.meta.dir, {
|
|
||||||
coder_parameter_order: order.toString(),
|
|
||||||
});
|
|
||||||
expect(state.resources).toHaveLength(1);
|
|
||||||
expect(state.resources[0].instances[0].attributes.order).toBe(order);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -63,12 +63,6 @@ variable "single_zone_per_region" {
|
|||||||
type = bool
|
type = bool
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "coder_parameter_order" {
|
|
||||||
type = number
|
|
||||||
description = "The order determines the position of a template parameter in the UI/CLI presentation. The lowest order is shown first and parameters with equal order are sorted by name (ascending order)."
|
|
||||||
default = null
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
locals {
|
||||||
zones = {
|
zones = {
|
||||||
# US Central
|
# US Central
|
||||||
@@ -721,7 +715,6 @@ data "coder_parameter" "region" {
|
|||||||
icon = "/icon/gcp.png"
|
icon = "/icon/gcp.png"
|
||||||
mutable = var.mutable
|
mutable = var.mutable
|
||||||
default = var.default != null && var.default != "" && (!var.gpu_only || try(local.zones[var.default].gpu, false)) ? var.default : null
|
default = var.default != null && var.default != "" && (!var.gpu_only || try(local.zones[var.default].gpu, false)) ? var.default : null
|
||||||
order = var.coder_parameter_order
|
|
||||||
dynamic "option" {
|
dynamic "option" {
|
||||||
for_each = {
|
for_each = {
|
||||||
for k, v in local.zones : k => v
|
for k, v in local.zones : k => v
|
||||||
|
|||||||
@@ -9,147 +9,33 @@ tags: [git, helper]
|
|||||||
|
|
||||||
# Git Clone
|
# Git Clone
|
||||||
|
|
||||||
This module allows you to automatically clone a repository by URL and skip if it exists in the base directory provided.
|
This module allows you to automatically clone a repository by URL and skip if it exists in the path provided.
|
||||||
|
|
||||||
```tf
|
```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.12"
|
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
url = "https://github.com/coder/coder"
|
url = "https://github.com/coder/coder"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To use with [Git Authentication](https://coder.com/docs/v2/latest/admin/git-providers), add the provider by ID to your template:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
data "coder_git_auth" "github" {
|
||||||
|
id = "github"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### Custom Path
|
### Custom Path
|
||||||
|
|
||||||
```tf
|
```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.12"
|
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
url = "https://github.com/coder/coder"
|
url = "https://github.com/coder/coder"
|
||||||
base_dir = "~/projects/coder"
|
path = "~/projects/coder/coder"
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Git Authentication
|
|
||||||
|
|
||||||
To use with [Git Authentication](https://coder.com/docs/v2/latest/admin/git-providers), add the provider by ID to your template:
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "git-clone" {
|
|
||||||
source = "registry.coder.com/modules/git-clone/coder"
|
|
||||||
version = "1.0.12"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
url = "https://github.com/coder/coder"
|
|
||||||
}
|
|
||||||
|
|
||||||
data "coder_git_auth" "github" {
|
|
||||||
id = "github"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## GitHub clone with branch name
|
|
||||||
|
|
||||||
To GitHub clone with a specific branch like `feat/example`
|
|
||||||
|
|
||||||
```tf
|
|
||||||
# Prompt the user for the git repo URL
|
|
||||||
data "coder_parameter" "git_repo" {
|
|
||||||
name = "git_repo"
|
|
||||||
display_name = "Git repository"
|
|
||||||
default = "https://github.com/coder/coder/tree/feat/example"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Clone the repository for branch `feat/example`
|
|
||||||
module "git_clone" {
|
|
||||||
source = "registry.coder.com/modules/git-clone/coder"
|
|
||||||
version = "1.0.12"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
url = data.coder_parameter.git_repo.value
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create a code-server instance for the cloned repository
|
|
||||||
module "code-server" {
|
|
||||||
source = "registry.coder.com/modules/code-server/coder"
|
|
||||||
version = "1.0.12"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
order = 1
|
|
||||||
folder = "/home/${local.username}/${module.git_clone.folder_name}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create a Coder app for the website
|
|
||||||
resource "coder_app" "website" {
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
order = 2
|
|
||||||
slug = "website"
|
|
||||||
external = true
|
|
||||||
display_name = module.git_clone.folder_name
|
|
||||||
url = module.git_clone.web_url
|
|
||||||
icon = module.git_clone.git_provider != "" ? "/icon/${module.git_clone.git_provider}.svg" : "/icon/git.svg"
|
|
||||||
count = module.git_clone.web_url != "" ? 1 : 0
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Configuring `git-clone` for a self-hosted GitHub Enterprise Server running at `github.example.com`
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "git-clone" {
|
|
||||||
source = "registry.coder.com/modules/git-clone/coder"
|
|
||||||
version = "1.0.12"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
url = "https://github.example.com/coder/coder/tree/feat/example"
|
|
||||||
git_providers = {
|
|
||||||
"https://github.example.com/" = {
|
|
||||||
provider = "github"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## GitLab clone with branch name
|
|
||||||
|
|
||||||
To GitLab clone with a specific branch like `feat/example`
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "git-clone" {
|
|
||||||
source = "registry.coder.com/modules/git-clone/coder"
|
|
||||||
version = "1.0.12"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
url = "https://gitlab.com/coder/coder/-/tree/feat/example"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Configuring `git-clone` for a self-hosted GitLab running at `gitlab.example.com`
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "git-clone" {
|
|
||||||
source = "registry.coder.com/modules/git-clone/coder"
|
|
||||||
version = "1.0.12"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
url = "https://gitlab.example.com/coder/coder/-/tree/feat/example"
|
|
||||||
git_providers = {
|
|
||||||
"https://gitlab.example.com/" = {
|
|
||||||
provider = "gitlab"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Git clone with branch_name set
|
|
||||||
|
|
||||||
Alternatively, you can set the `branch_name` attribute to clone a specific branch.
|
|
||||||
|
|
||||||
For example, to clone the `feat/example` branch:
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "git-clone" {
|
|
||||||
source = "registry.coder.com/modules/git-clone/coder"
|
|
||||||
version = "1.0.12"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
url = "https://github.com/coder/coder"
|
|
||||||
branch_name = "feat/example"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -36,196 +36,4 @@ describe("git-clone", async () => {
|
|||||||
"Cloning fake-url to ~/fake-url...",
|
"Cloning fake-url to ~/fake-url...",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("repo_dir should match repo name for https", async () => {
|
|
||||||
const url = "https://github.com/coder/coder.git";
|
|
||||||
const state = await runTerraformApply(import.meta.dir, {
|
|
||||||
agent_id: "foo",
|
|
||||||
base_dir: "/tmp",
|
|
||||||
url,
|
|
||||||
});
|
|
||||||
expect(state.outputs.repo_dir.value).toEqual("/tmp/coder");
|
|
||||||
expect(state.outputs.folder_name.value).toEqual("coder");
|
|
||||||
expect(state.outputs.clone_url.value).toEqual(url);
|
|
||||||
expect(state.outputs.web_url.value).toEqual(url);
|
|
||||||
expect(state.outputs.branch_name.value).toEqual("");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("repo_dir should match repo name for https without .git", async () => {
|
|
||||||
const url = "https://github.com/coder/coder";
|
|
||||||
const state = await runTerraformApply(import.meta.dir, {
|
|
||||||
agent_id: "foo",
|
|
||||||
base_dir: "/tmp",
|
|
||||||
url,
|
|
||||||
});
|
|
||||||
expect(state.outputs.repo_dir.value).toEqual("/tmp/coder");
|
|
||||||
expect(state.outputs.clone_url.value).toEqual(url);
|
|
||||||
expect(state.outputs.web_url.value).toEqual(url);
|
|
||||||
expect(state.outputs.branch_name.value).toEqual("");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("repo_dir should match repo name for ssh", async () => {
|
|
||||||
const url = "git@github.com:coder/coder.git";
|
|
||||||
const state = await runTerraformApply(import.meta.dir, {
|
|
||||||
agent_id: "foo",
|
|
||||||
base_dir: "/tmp",
|
|
||||||
url,
|
|
||||||
});
|
|
||||||
expect(state.outputs.repo_dir.value).toEqual("/tmp/coder");
|
|
||||||
expect(state.outputs.git_provider.value).toEqual("");
|
|
||||||
expect(state.outputs.clone_url.value).toEqual(url);
|
|
||||||
const https_url = "https://github.com/coder/coder.git";
|
|
||||||
expect(state.outputs.web_url.value).toEqual(https_url);
|
|
||||||
expect(state.outputs.branch_name.value).toEqual("");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("branch_name should not include query string", async () => {
|
|
||||||
const state = await runTerraformApply(import.meta.dir, {
|
|
||||||
agent_id: "foo",
|
|
||||||
url: "https://gitlab.com/mike.brew/repo-tests.log/-/tree/feat/branch?ref_type=heads",
|
|
||||||
});
|
|
||||||
expect(state.outputs.repo_dir.value).toEqual("~/repo-tests.log");
|
|
||||||
expect(state.outputs.folder_name.value).toEqual("repo-tests.log");
|
|
||||||
const https_url = "https://gitlab.com/mike.brew/repo-tests.log";
|
|
||||||
expect(state.outputs.clone_url.value).toEqual(https_url);
|
|
||||||
expect(state.outputs.web_url.value).toEqual(https_url);
|
|
||||||
expect(state.outputs.branch_name.value).toEqual("feat/branch");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("branch_name should not include fragments", async () => {
|
|
||||||
const state = await runTerraformApply(import.meta.dir, {
|
|
||||||
agent_id: "foo",
|
|
||||||
base_dir: "/tmp",
|
|
||||||
url: "https://gitlab.com/mike.brew/repo-tests.log/-/tree/feat/branch#name",
|
|
||||||
});
|
|
||||||
expect(state.outputs.repo_dir.value).toEqual("/tmp/repo-tests.log");
|
|
||||||
const https_url = "https://gitlab.com/mike.brew/repo-tests.log";
|
|
||||||
expect(state.outputs.clone_url.value).toEqual(https_url);
|
|
||||||
expect(state.outputs.web_url.value).toEqual(https_url);
|
|
||||||
expect(state.outputs.branch_name.value).toEqual("feat/branch");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("gitlab url with branch should match", async () => {
|
|
||||||
const state = await runTerraformApply(import.meta.dir, {
|
|
||||||
agent_id: "foo",
|
|
||||||
base_dir: "/tmp",
|
|
||||||
url: "https://gitlab.com/mike.brew/repo-tests.log/-/tree/feat/branch",
|
|
||||||
});
|
|
||||||
expect(state.outputs.repo_dir.value).toEqual("/tmp/repo-tests.log");
|
|
||||||
expect(state.outputs.git_provider.value).toEqual("gitlab");
|
|
||||||
const https_url = "https://gitlab.com/mike.brew/repo-tests.log";
|
|
||||||
expect(state.outputs.clone_url.value).toEqual(https_url);
|
|
||||||
expect(state.outputs.web_url.value).toEqual(https_url);
|
|
||||||
expect(state.outputs.branch_name.value).toEqual("feat/branch");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("github url with branch should match", async () => {
|
|
||||||
const state = await runTerraformApply(import.meta.dir, {
|
|
||||||
agent_id: "foo",
|
|
||||||
base_dir: "/tmp",
|
|
||||||
url: "https://github.com/michaelbrewer/repo-tests.log/tree/feat/branch",
|
|
||||||
});
|
|
||||||
expect(state.outputs.repo_dir.value).toEqual("/tmp/repo-tests.log");
|
|
||||||
expect(state.outputs.git_provider.value).toEqual("github");
|
|
||||||
const https_url = "https://github.com/michaelbrewer/repo-tests.log";
|
|
||||||
expect(state.outputs.clone_url.value).toEqual(https_url);
|
|
||||||
expect(state.outputs.web_url.value).toEqual(https_url);
|
|
||||||
expect(state.outputs.branch_name.value).toEqual("feat/branch");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("self-host git url with branch should match", async () => {
|
|
||||||
const state = await runTerraformApply(import.meta.dir, {
|
|
||||||
agent_id: "foo",
|
|
||||||
base_dir: "/tmp",
|
|
||||||
url: "https://git.example.com/example/project/-/tree/feat/example",
|
|
||||||
git_providers: `
|
|
||||||
{
|
|
||||||
"https://git.example.com/" = {
|
|
||||||
provider = "gitlab"
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
});
|
|
||||||
expect(state.outputs.repo_dir.value).toEqual("/tmp/project");
|
|
||||||
expect(state.outputs.git_provider.value).toEqual("gitlab");
|
|
||||||
const https_url = "https://git.example.com/example/project";
|
|
||||||
expect(state.outputs.clone_url.value).toEqual(https_url);
|
|
||||||
expect(state.outputs.web_url.value).toEqual(https_url);
|
|
||||||
expect(state.outputs.branch_name.value).toEqual("feat/example");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("handle unsupported git provider configuration", async () => {
|
|
||||||
const t = async () => {
|
|
||||||
await runTerraformApply(import.meta.dir, {
|
|
||||||
agent_id: "foo",
|
|
||||||
url: "foo",
|
|
||||||
git_providers: `
|
|
||||||
{
|
|
||||||
"https://git.example.com/" = {
|
|
||||||
provider = "bitbucket"
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
expect(t).toThrow('Allowed values for provider are "github" or "gitlab".');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("handle unknown git provider url", async () => {
|
|
||||||
const url = "https://git.unknown.com/coder/coder";
|
|
||||||
const state = await runTerraformApply(import.meta.dir, {
|
|
||||||
agent_id: "foo",
|
|
||||||
base_dir: "/tmp",
|
|
||||||
url,
|
|
||||||
});
|
|
||||||
expect(state.outputs.repo_dir.value).toEqual("/tmp/coder");
|
|
||||||
expect(state.outputs.clone_url.value).toEqual(url);
|
|
||||||
expect(state.outputs.web_url.value).toEqual(url);
|
|
||||||
expect(state.outputs.branch_name.value).toEqual("");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("runs with github clone with switch to feat/branch", async () => {
|
|
||||||
const state = await runTerraformApply(import.meta.dir, {
|
|
||||||
agent_id: "foo",
|
|
||||||
url: "https://github.com/michaelbrewer/repo-tests.log/tree/feat/branch",
|
|
||||||
});
|
|
||||||
const output = await executeScriptInContainer(state, "alpine/git");
|
|
||||||
expect(output.exitCode).toBe(0);
|
|
||||||
expect(output.stdout).toEqual([
|
|
||||||
"Creating directory ~/repo-tests.log...",
|
|
||||||
"Cloning https://github.com/michaelbrewer/repo-tests.log to ~/repo-tests.log on branch feat/branch...",
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("runs with gitlab clone with switch to feat/branch", async () => {
|
|
||||||
const state = await runTerraformApply(import.meta.dir, {
|
|
||||||
agent_id: "foo",
|
|
||||||
url: "https://gitlab.com/mike.brew/repo-tests.log/-/tree/feat/branch",
|
|
||||||
});
|
|
||||||
const output = await executeScriptInContainer(state, "alpine/git");
|
|
||||||
expect(output.exitCode).toBe(0);
|
|
||||||
expect(output.stdout).toEqual([
|
|
||||||
"Creating directory ~/repo-tests.log...",
|
|
||||||
"Cloning https://gitlab.com/mike.brew/repo-tests.log to ~/repo-tests.log on branch feat/branch...",
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("runs with github clone with branch_name set to feat/branch", async () => {
|
|
||||||
const url = "https://github.com/michaelbrewer/repo-tests.log";
|
|
||||||
const branch_name = "feat/branch";
|
|
||||||
const state = await runTerraformApply(import.meta.dir, {
|
|
||||||
agent_id: "foo",
|
|
||||||
url,
|
|
||||||
branch_name,
|
|
||||||
});
|
|
||||||
expect(state.outputs.repo_dir.value).toEqual("~/repo-tests.log");
|
|
||||||
expect(state.outputs.clone_url.value).toEqual(url);
|
|
||||||
expect(state.outputs.web_url.value).toEqual(url);
|
|
||||||
expect(state.outputs.branch_name.value).toEqual(branch_name);
|
|
||||||
|
|
||||||
const output = await executeScriptInContainer(state, "alpine/git");
|
|
||||||
expect(output.exitCode).toBe(0);
|
|
||||||
expect(output.stdout).toEqual([
|
|
||||||
"Creating directory ~/repo-tests.log...",
|
|
||||||
"Cloning https://github.com/michaelbrewer/repo-tests.log to ~/repo-tests.log on branch feat/branch...",
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
coder = {
|
coder = {
|
||||||
source = "coder/coder"
|
source = "coder/coder"
|
||||||
version = ">= 0.12"
|
version = ">= 0.11"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,9 +14,9 @@ variable "url" {
|
|||||||
type = string
|
type = string
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "base_dir" {
|
variable "path" {
|
||||||
default = ""
|
default = ""
|
||||||
description = "The base directory to clone the repository. Defaults to \"$HOME\"."
|
description = "The path to clone the repository. Defaults to \"$HOME/<basename of url>\"."
|
||||||
type = string
|
type = string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,88 +25,11 @@ variable "agent_id" {
|
|||||||
type = string
|
type = string
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "git_providers" {
|
|
||||||
type = map(object({
|
|
||||||
provider = string
|
|
||||||
}))
|
|
||||||
description = "A mapping of URLs to their git provider."
|
|
||||||
default = {
|
|
||||||
"https://github.com/" = {
|
|
||||||
provider = "github"
|
|
||||||
},
|
|
||||||
"https://gitlab.com/" = {
|
|
||||||
provider = "gitlab"
|
|
||||||
},
|
|
||||||
}
|
|
||||||
validation {
|
|
||||||
error_message = "Allowed values for provider are \"github\" or \"gitlab\"."
|
|
||||||
condition = alltrue([for provider in var.git_providers : contains(["github", "gitlab"], provider.provider)])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "branch_name" {
|
|
||||||
description = "The branch name to clone. If not provided, the default branch will be cloned."
|
|
||||||
type = string
|
|
||||||
default = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
# Remove query parameters and fragments from the URL
|
|
||||||
url = replace(replace(var.url, "/\\?.*/", ""), "/#.*/", "")
|
|
||||||
|
|
||||||
# Find the git provider based on the URL and determine the tree path
|
|
||||||
provider_key = try(one([for key in keys(var.git_providers) : key if startswith(local.url, key)]), null)
|
|
||||||
provider = try(lookup(var.git_providers, local.provider_key).provider, "")
|
|
||||||
tree_path = local.provider == "gitlab" ? "/-/tree/" : local.provider == "github" ? "/tree/" : ""
|
|
||||||
|
|
||||||
# Remove tree and branch name from the URL
|
|
||||||
clone_url = var.branch_name == "" && local.tree_path != "" ? replace(local.url, "/${local.tree_path}.*/", "") : local.url
|
|
||||||
# Extract the branch name from the URL
|
|
||||||
branch_name = var.branch_name == "" && local.tree_path != "" ? replace(replace(local.url, local.clone_url, ""), "/.*${local.tree_path}/", "") : var.branch_name
|
|
||||||
# Extract the folder name from the URL
|
|
||||||
folder_name = replace(basename(local.clone_url), ".git", "")
|
|
||||||
# Construct the path to clone the repository
|
|
||||||
clone_path = var.base_dir != "" ? join("/", [var.base_dir, local.folder_name]) : join("/", ["~", local.folder_name])
|
|
||||||
# Construct the web URL
|
|
||||||
web_url = startswith(local.clone_url, "git@") ? replace(replace(local.clone_url, ":", "/"), "git@", "https://") : local.clone_url
|
|
||||||
}
|
|
||||||
|
|
||||||
output "repo_dir" {
|
|
||||||
value = local.clone_path
|
|
||||||
description = "Full path of cloned repo directory"
|
|
||||||
}
|
|
||||||
|
|
||||||
output "git_provider" {
|
|
||||||
value = local.provider
|
|
||||||
description = "The git provider of the repository"
|
|
||||||
}
|
|
||||||
|
|
||||||
output "folder_name" {
|
|
||||||
value = local.folder_name
|
|
||||||
description = "The name of the folder that will be created"
|
|
||||||
}
|
|
||||||
|
|
||||||
output "clone_url" {
|
|
||||||
value = local.clone_url
|
|
||||||
description = "The exact Git repository URL that will be cloned"
|
|
||||||
}
|
|
||||||
|
|
||||||
output "web_url" {
|
|
||||||
value = local.web_url
|
|
||||||
description = "Git https repository URL (may be invalid for unsupported providers)"
|
|
||||||
}
|
|
||||||
|
|
||||||
output "branch_name" {
|
|
||||||
value = local.branch_name
|
|
||||||
description = "Git branch name (may be empty)"
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = local.clone_path,
|
CLONE_PATH : var.path != "" ? var.path : join("/", ["~", basename(var.url)]),
|
||||||
REPO_URL : local.clone_url,
|
REPO_URL : var.url,
|
||||||
BRANCH_NAME : local.branch_name,
|
|
||||||
})
|
})
|
||||||
display_name = "Git Clone"
|
display_name = "Git Clone"
|
||||||
icon = "/icon/git.svg"
|
icon = "/icon/git.svg"
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
REPO_URL="${REPO_URL}"
|
REPO_URL="${REPO_URL}"
|
||||||
CLONE_PATH="${CLONE_PATH}"
|
CLONE_PATH="${CLONE_PATH}"
|
||||||
BRANCH_NAME="${BRANCH_NAME}"
|
|
||||||
# Expand home if it's specified!
|
# Expand home if it's specified!
|
||||||
CLONE_PATH="$${CLONE_PATH/#\~/$${HOME}}"
|
CLONE_PATH="$${CLONE_PATH/#\~/$${HOME}}"
|
||||||
|
|
||||||
@@ -34,13 +33,8 @@ fi
|
|||||||
# Check if the directory is empty
|
# Check if the directory is empty
|
||||||
# and if it is, clone the repo, otherwise skip cloning
|
# and if it is, clone the repo, otherwise skip cloning
|
||||||
if [ -z "$(ls -A "$CLONE_PATH")" ]; then
|
if [ -z "$(ls -A "$CLONE_PATH")" ]; then
|
||||||
if [ -z "$BRANCH_NAME" ]; then
|
|
||||||
echo "Cloning $REPO_URL to $CLONE_PATH..."
|
echo "Cloning $REPO_URL to $CLONE_PATH..."
|
||||||
git clone "$REPO_URL" "$CLONE_PATH"
|
git clone "$REPO_URL" "$CLONE_PATH"
|
||||||
else
|
|
||||||
echo "Cloning $REPO_URL to $CLONE_PATH on branch $BRANCH_NAME..."
|
|
||||||
git clone "$REPO_URL" -b "$BRANCH_NAME" "$CLONE_PATH"
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
echo "$CLONE_PATH already exists and isn't empty, skipping clone!"
|
echo "$CLONE_PATH already exists and isn't empty, skipping clone!"
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -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.
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "git-commit-signing" {
|
|
||||||
source = "registry.coder.com/modules/git-commit-signing/coder"
|
|
||||||
version = "1.0.11"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -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 = "/icon/git.svg"
|
|
||||||
|
|
||||||
script = file("${path.module}/run.sh")
|
|
||||||
run_on_start = true
|
|
||||||
|
|
||||||
agent_id = var.agent_id
|
|
||||||
}
|
|
||||||
@@ -1,42 +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}" \
|
|
||||||
--silent --show-error)
|
|
||||||
|
|
||||||
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 600 ~/.ssh/git-commit-signing/coder
|
|
||||||
chmod -R 644 ~/.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
|
|
||||||
@@ -11,10 +11,9 @@ tags: [helper, git]
|
|||||||
|
|
||||||
Runs a script that updates git credentials in the workspace to match the user's Coder credentials, optionally allowing to the developer to override the defaults.
|
Runs a script that updates git credentials in the workspace to match the user's Coder credentials, optionally allowing to the developer to override the defaults.
|
||||||
|
|
||||||
```tf
|
```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.12"
|
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -25,10 +24,9 @@ TODO: Add screenshot
|
|||||||
|
|
||||||
### Allow users to override both username and email
|
### Allow users to override both username and email
|
||||||
|
|
||||||
```tf
|
```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.12"
|
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
allow_email_change = true
|
allow_email_change = true
|
||||||
}
|
}
|
||||||
@@ -38,12 +36,13 @@ TODO: Add screenshot
|
|||||||
|
|
||||||
## Disallowing users from overriding both username and email
|
## Disallowing users from overriding both username and email
|
||||||
|
|
||||||
```tf
|
```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.12"
|
|
||||||
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
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
TODO: Add screenshot
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { describe, expect, it } from "bun:test";
|
import { describe, expect, it } from "bun:test";
|
||||||
import {
|
import {
|
||||||
|
executeScriptInContainer,
|
||||||
runTerraformApply,
|
runTerraformApply,
|
||||||
runTerraformInit,
|
runTerraformInit,
|
||||||
testRequiredVariables,
|
testRequiredVariables,
|
||||||
@@ -12,88 +13,31 @@ describe("git-config", async () => {
|
|||||||
agent_id: "foo",
|
agent_id: "foo",
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can run apply allow_username_change and allow_email_change disabled", async () => {
|
it("fails without git", async () => {
|
||||||
const state = await runTerraformApply(import.meta.dir, {
|
const state = await runTerraformApply(import.meta.dir, {
|
||||||
agent_id: "foo",
|
agent_id: "foo",
|
||||||
allow_username_change: "false",
|
|
||||||
allow_email_change: "false",
|
|
||||||
});
|
});
|
||||||
|
const output = await executeScriptInContainer(state, "alpine");
|
||||||
const resources = state.resources;
|
expect(output.exitCode).toBe(1);
|
||||||
expect(resources).toHaveLength(3);
|
expect(output.stdout).toEqual([
|
||||||
expect(resources).toMatchObject([
|
"\u001B[0;1mChecking git-config!",
|
||||||
{ type: "coder_workspace", name: "me" },
|
"Git is not installed!",
|
||||||
{ type: "coder_env", name: "git_author_name" },
|
|
||||||
{ type: "coder_env", name: "git_commmiter_name" },
|
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can run apply allow_email_change enabled", async () => {
|
it("runs with git", async () => {
|
||||||
const state = await runTerraformApply(import.meta.dir, {
|
const state = await runTerraformApply(import.meta.dir, {
|
||||||
agent_id: "foo",
|
agent_id: "foo",
|
||||||
allow_email_change: "true",
|
|
||||||
});
|
});
|
||||||
|
const output = await executeScriptInContainer(state, "alpine/git");
|
||||||
const resources = state.resources;
|
expect(output.exitCode).toBe(0);
|
||||||
expect(resources).toHaveLength(5);
|
expect(output.stdout).toEqual([
|
||||||
expect(resources).toMatchObject([
|
"\u001B[0;1mChecking git-config!",
|
||||||
{ type: "coder_parameter", name: "user_email" },
|
"git-config: No user.email found, setting to ",
|
||||||
{ type: "coder_parameter", name: "username" },
|
"git-config: No user.name found, setting to default",
|
||||||
{ type: "coder_workspace", name: "me" },
|
"",
|
||||||
{ type: "coder_env", name: "git_author_name" },
|
"\u001B[0;1mgit-config: using email: ",
|
||||||
{ type: "coder_env", name: "git_commmiter_name" },
|
"\u001B[0;1mgit-config: using username: default",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can run apply allow_email_change enabled", async () => {
|
|
||||||
const state = await runTerraformApply(
|
|
||||||
import.meta.dir,
|
|
||||||
{
|
|
||||||
agent_id: "foo",
|
|
||||||
allow_username_change: "false",
|
|
||||||
allow_email_change: "false",
|
|
||||||
},
|
|
||||||
{ CODER_WORKSPACE_OWNER_EMAIL: "foo@emai.com" },
|
|
||||||
);
|
|
||||||
|
|
||||||
const resources = state.resources;
|
|
||||||
expect(resources).toHaveLength(5);
|
|
||||||
expect(resources).toMatchObject([
|
|
||||||
{ type: "coder_workspace", name: "me" },
|
|
||||||
{ type: "coder_env", name: "git_author_email" },
|
|
||||||
{ type: "coder_env", name: "git_author_name" },
|
|
||||||
{ type: "coder_env", name: "git_commmiter_email" },
|
|
||||||
{ type: "coder_env", name: "git_commmiter_name" },
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("set custom order for coder_parameter for both fields", async () => {
|
|
||||||
const order = 20;
|
|
||||||
const state = await runTerraformApply(import.meta.dir, {
|
|
||||||
agent_id: "foo",
|
|
||||||
allow_username_change: "true",
|
|
||||||
allow_email_change: "true",
|
|
||||||
coder_parameter_order: order.toString(),
|
|
||||||
});
|
|
||||||
expect(state.resources).toHaveLength(5);
|
|
||||||
// user_email order is the same as the order
|
|
||||||
expect(state.resources[0].instances[0].attributes.order).toBe(order);
|
|
||||||
// username order is incremented by 1
|
|
||||||
// @ts-ignore: Object is possibly 'null'.
|
|
||||||
expect(state.resources[1].instances[0]?.attributes.order).toBe(order + 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("set custom order for coder_parameter for just username", async () => {
|
|
||||||
const order = 30;
|
|
||||||
const state = await runTerraformApply(import.meta.dir, {
|
|
||||||
agent_id: "foo",
|
|
||||||
allow_email_change: "false",
|
|
||||||
allow_username_change: "true",
|
|
||||||
coder_parameter_order: order.toString(),
|
|
||||||
});
|
|
||||||
expect(state.resources).toHaveLength(4);
|
|
||||||
// user_email was not created
|
|
||||||
// username order is incremented by 1
|
|
||||||
expect(state.resources[0].instances[0].attributes.order).toBe(order + 1);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
coder = {
|
coder = {
|
||||||
source = "coder/coder"
|
source = "coder/coder"
|
||||||
version = ">= 0.13"
|
version = ">= 0.12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,11 +26,6 @@ variable "allow_email_change" {
|
|||||||
default = false
|
default = false
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "coder_parameter_order" {
|
|
||||||
type = number
|
|
||||||
description = "The order determines the position of a template parameter in the UI/CLI presentation. The lowest order is shown first and parameters with equal order are sorted by name (ascending order)."
|
|
||||||
default = null
|
|
||||||
}
|
|
||||||
|
|
||||||
data "coder_workspace" "me" {}
|
data "coder_workspace" "me" {}
|
||||||
|
|
||||||
@@ -39,8 +34,7 @@ data "coder_parameter" "user_email" {
|
|||||||
name = "user_email"
|
name = "user_email"
|
||||||
type = "string"
|
type = "string"
|
||||||
default = ""
|
default = ""
|
||||||
order = var.coder_parameter_order != null ? var.coder_parameter_order + 0 : null
|
description = "Git user.email to be used for commits. Leave empty to default to Coder username."
|
||||||
description = "Git user.email to be used for commits. Leave empty to default to Coder user's email."
|
|
||||||
display_name = "Git config user.email"
|
display_name = "Git config user.email"
|
||||||
mutable = true
|
mutable = true
|
||||||
}
|
}
|
||||||
@@ -50,34 +44,18 @@ data "coder_parameter" "username" {
|
|||||||
name = "username"
|
name = "username"
|
||||||
type = "string"
|
type = "string"
|
||||||
default = ""
|
default = ""
|
||||||
order = var.coder_parameter_order != null ? var.coder_parameter_order + 1 : null
|
description = "Git user.name to be used for commits. Leave empty to default to Coder username."
|
||||||
description = "Git user.name to be used for commits. Leave empty to default to Coder user's Full Name."
|
display_name = "Git config user.name"
|
||||||
display_name = "Full Name for Git config"
|
|
||||||
mutable = true
|
mutable = true
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "coder_env" "git_author_name" {
|
resource "coder_script" "git_config" {
|
||||||
agent_id = var.agent_id
|
agent_id = var.agent_id
|
||||||
name = "GIT_AUTHOR_NAME"
|
script = templatefile("${path.module}/run.sh", {
|
||||||
value = coalesce(try(data.coder_parameter.username[0].value, ""), data.coder_workspace.me.owner_name, data.coder_workspace.me.owner)
|
GIT_USERNAME = try(data.coder_parameter.username[0].value, "") == "" ? data.coder_workspace.me.owner : try(data.coder_parameter.username[0].value, "")
|
||||||
}
|
GIT_EMAIL = try(data.coder_parameter.user_email[0].value, "") == "" ? data.coder_workspace.me.owner_email : try(data.coder_parameter.user_email[0].value, "")
|
||||||
|
})
|
||||||
resource "coder_env" "git_commmiter_name" {
|
display_name = "Git Config"
|
||||||
agent_id = var.agent_id
|
icon = "/icon/git.svg"
|
||||||
name = "GIT_COMMITTER_NAME"
|
run_on_start = true
|
||||||
value = coalesce(try(data.coder_parameter.username[0].value, ""), data.coder_workspace.me.owner_name, data.coder_workspace.me.owner)
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "coder_env" "git_author_email" {
|
|
||||||
agent_id = var.agent_id
|
|
||||||
name = "GIT_AUTHOR_EMAIL"
|
|
||||||
value = coalesce(try(data.coder_parameter.user_email[0].value, ""), data.coder_workspace.me.owner_email)
|
|
||||||
count = data.coder_workspace.me.owner_email != "" ? 1 : 0
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "coder_env" "git_commmiter_email" {
|
|
||||||
agent_id = var.agent_id
|
|
||||||
name = "GIT_COMMITTER_EMAIL"
|
|
||||||
value = coalesce(try(data.coder_parameter.user_email[0].value, ""), data.coder_workspace.me.owner_email)
|
|
||||||
count = data.coder_workspace.me.owner_email != "" ? 1 : 0
|
|
||||||
}
|
}
|
||||||
|
|||||||
24
git-config/run.sh
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
BOLD='\033[0;1m'
|
||||||
|
printf "$${BOLD}Checking git-config!\n"
|
||||||
|
|
||||||
|
# Check if git is installed
|
||||||
|
command -v git >/dev/null 2>&1 || {
|
||||||
|
echo "Git is not installed!"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set git username and email if missing
|
||||||
|
if [ -z $(git config --get user.email) ]; then
|
||||||
|
printf "git-config: No user.email found, setting to ${GIT_EMAIL}\n"
|
||||||
|
git config --global user.email "${GIT_EMAIL}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z $(git config --get user.name) ]; then
|
||||||
|
printf "git-config: No user.name found, setting to ${GIT_USERNAME}\n"
|
||||||
|
git config --global user.name "${GIT_USERNAME}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "\n$${BOLD}git-config: using email: $(git config --get user.email)\n"
|
||||||
|
printf "$${BOLD}git-config: using username: $(git config --get user.name)\n\n"
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
---
|
|
||||||
display_name: "HCP Vault Secrets"
|
|
||||||
description: "Fetch secrets from HCP Vault"
|
|
||||||
icon: ../.icons/vault.svg
|
|
||||||
maintainer_github: coder
|
|
||||||
partner_github: hashicorp
|
|
||||||
verified: true
|
|
||||||
tags: [helper, integration, vault, hashicorp, hvs]
|
|
||||||
---
|
|
||||||
|
|
||||||
# HCP Vault Secrets
|
|
||||||
|
|
||||||
This module lets you fetch all or selective secrets from a [HCP Vault Secrets](https://developer.hashicorp.com/hcp/docs/vault-secrets) app into your [Coder](https://coder.com) workspaces. It makes use of the [`hcp_vault_secrets_app`](https://registry.terraform.io/providers/hashicorp/hcp/latest/docs/data-sources/vault_secrets_app) data source from the [HCP provider](https://registry.terraform.io/providers/hashicorp/hcp/latest).
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "vault" {
|
|
||||||
source = "registry.coder.com/modules/hcp-vault-secrets/coder"
|
|
||||||
version = "1.0.7"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
app_name = "demo-app"
|
|
||||||
project_id = "aaa-bbb-ccc"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
To configure the HCP Vault Secrets module, follow these steps,
|
|
||||||
|
|
||||||
1. [Create secrets in HCP Vault Secrets](https://developer.hashicorp.com/vault/tutorials/hcp-vault-secrets-get-started/hcp-vault-secrets-create-secret)
|
|
||||||
2. Create an HCP Service Principal from the HCP Vault Secrets app in the HCP console. This will give you the `HCP_CLIENT_ID` and `HCP_CLIENT_SECRET` that you need to authenticate with HCP Vault Secrets.
|
|
||||||

|
|
||||||
3. Set `HCP_CLIENT_ID` and `HCP_CLIENT_SECRET` variables on the coder provisioner (recommended) or supply them as input to the module.
|
|
||||||
4. Set the `project_id`. This is the ID of the project where the HCP Vault Secrets app is running.
|
|
||||||
|
|
||||||
> See the [HCP Vault Secrets documentation](https://developer.hashicorp.com/hcp/docs/vault-secrets) for more information.
|
|
||||||
|
|
||||||
## Fetch All Secrets
|
|
||||||
|
|
||||||
To fetch all secrets from the HCP Vault Secrets app, skip the `secrets` input.
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "vault" {
|
|
||||||
source = "registry.coder.com/modules/hcp-vault-secrets/coder"
|
|
||||||
version = "1.0.7"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
app_name = "demo-app"
|
|
||||||
project_id = "aaa-bbb-ccc"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Fetch Selective Secrets
|
|
||||||
|
|
||||||
To fetch selective secrets from the HCP Vault Secrets app, set the `secrets` input.
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "vault" {
|
|
||||||
source = "registry.coder.com/modules/hcp-vault-secrets/coder"
|
|
||||||
version = "1.0.7"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
app_name = "demo-app"
|
|
||||||
project_id = "aaa-bbb-ccc"
|
|
||||||
secrets = ["MY_SECRET_1", "MY_SECRET_2"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Set Client ID and Client Secret as Inputs
|
|
||||||
|
|
||||||
Set `client_id` and `client_secret` as module inputs.
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "vault" {
|
|
||||||
source = "registry.coder.com/modules/hcp-vault-secrets/coder"
|
|
||||||
version = "1.0.7"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
app_name = "demo-app"
|
|
||||||
project_id = "aaa-bbb-ccc"
|
|
||||||
client_id = "HCP_CLIENT_ID"
|
|
||||||
client_secret = "HCP_CLIENT_SECRET"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
terraform {
|
|
||||||
required_version = ">= 1.0"
|
|
||||||
|
|
||||||
required_providers {
|
|
||||||
coder = {
|
|
||||||
source = "coder/coder"
|
|
||||||
version = ">= 0.12.4"
|
|
||||||
}
|
|
||||||
hcp = {
|
|
||||||
source = "hashicorp/hcp"
|
|
||||||
version = ">= 0.82.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
provider "hcp" {
|
|
||||||
client_id = var.client_id
|
|
||||||
client_secret = var.client_secret
|
|
||||||
project_id = var.project_id
|
|
||||||
}
|
|
||||||
|
|
||||||
provider "coder" {}
|
|
||||||
|
|
||||||
variable "agent_id" {
|
|
||||||
type = string
|
|
||||||
description = "The ID of a Coder agent."
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "project_id" {
|
|
||||||
type = string
|
|
||||||
description = "The ID of the HCP project."
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "client_id" {
|
|
||||||
type = string
|
|
||||||
description = <<-EOF
|
|
||||||
The client ID for the HCP Vault Secrets service principal. (Optional if HCP_CLIENT_ID is set as an environment variable.)
|
|
||||||
EOF
|
|
||||||
default = null
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "client_secret" {
|
|
||||||
type = string
|
|
||||||
description = <<-EOF
|
|
||||||
The client secret for the HCP Vault Secrets service principal. (Optional if HCP_CLIENT_SECRET is set as an environment variable.)
|
|
||||||
EOF
|
|
||||||
default = null
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "app_name" {
|
|
||||||
type = string
|
|
||||||
description = "The name of the secrets app in HCP Vault Secrets"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "secrets" {
|
|
||||||
type = list(string)
|
|
||||||
description = "The names of the secrets to retrieve from HCP Vault Secrets"
|
|
||||||
default = null
|
|
||||||
}
|
|
||||||
|
|
||||||
data "hcp_vault_secrets_app" "secrets" {
|
|
||||||
app_name = var.app_name
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "coder_env" "hvs_secrets" {
|
|
||||||
# https://support.hashicorp.com/hc/en-us/articles/4538432032787-Variable-has-a-sensitive-value-and-cannot-be-used-as-for-each-arguments
|
|
||||||
for_each = var.secrets != null ? toset(var.secrets) : nonsensitive(toset(keys(data.hcp_vault_secrets_app.secrets.secrets)))
|
|
||||||
agent_id = var.agent_id
|
|
||||||
name = each.key
|
|
||||||
value = data.hcp_vault_secrets_app.secrets.secrets[each.key]
|
|
||||||
}
|
|
||||||
@@ -11,15 +11,13 @@ tags: [ide, jetbrains, helper, parameter]
|
|||||||
|
|
||||||
This module adds a JetBrains Gateway Button to open any workspace with a single click.
|
This module adds a JetBrains Gateway Button to open any workspace with a single click.
|
||||||
|
|
||||||
```tf
|
```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.12"
|
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
agent_name = "example"
|
agent_name = "example"
|
||||||
folder = "/home/coder/example"
|
folder = "/home/coder/example"
|
||||||
jetbrains_ides = ["CL", "GO", "IU", "PY", "WS"]
|
jetbrains_ides = ["GO", "WS", "IU", "IC", "PY", "PC", "PS", "CL", "RM", "DB", "RD"]
|
||||||
default = "GO"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -27,12 +25,11 @@ module "jetbrains_gateway" {
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### Add GoLand and WebStorm as options with the default set to GoLand
|
### Add GoLand and WebStorm with the default set to GoLand
|
||||||
|
|
||||||
```tf
|
```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.12"
|
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
agent_name = "example"
|
agent_name = "example"
|
||||||
folder = "/home/coder/example"
|
folder = "/home/coder/example"
|
||||||
@@ -41,37 +38,6 @@ module "jetbrains_gateway" {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Use the latest release version
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "jetbrains_gateway" {
|
|
||||||
source = "registry.coder.com/modules/jetbrains-gateway/coder"
|
|
||||||
version = "1.0.12"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
agent_name = "example"
|
|
||||||
folder = "/home/coder/example"
|
|
||||||
jetbrains_ides = ["GO", "WS"]
|
|
||||||
default = "GO"
|
|
||||||
latest = true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Use the latest EAP version
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "jetbrains_gateway" {
|
|
||||||
source = "registry.coder.com/modules/jetbrains-gateway/coder"
|
|
||||||
version = "1.0.12"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
agent_name = "example"
|
|
||||||
folder = "/home/coder/example"
|
|
||||||
jetbrains_ides = ["GO", "WS"]
|
|
||||||
default = "GO"
|
|
||||||
latest = true
|
|
||||||
channel = "eap"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Supported IDEs
|
## Supported IDEs
|
||||||
|
|
||||||
This module and JetBrains Gateway support the following JetBrains IDEs:
|
This module and JetBrains Gateway support the following JetBrains IDEs:
|
||||||
@@ -79,8 +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`)
|
- Rider (`RD`)
|
||||||
|
|||||||
@@ -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,17 +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: "/home/foo",
|
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: "/home/foo",
|
folder: "/baz/",
|
||||||
jetbrains_ides: '["IU", "GO", "PY"]',
|
jetbrains_ides: '["IU", "IC", "PY"]',
|
||||||
});
|
});
|
||||||
expect(state.outputs.identifier.value).toBe("IU");
|
expect(state.outputs.jetbrains_ides.value).toBe(
|
||||||
|
'["IU","232.9921.47","https://download.jetbrains.com/idea/ideaIU-2023.2.2.tar.gz"]',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,11 +4,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
coder = {
|
coder = {
|
||||||
source = "coder/coder"
|
source = "coder/coder"
|
||||||
version = ">= 0.17"
|
version = ">= 0.11"
|
||||||
}
|
|
||||||
http = {
|
|
||||||
source = "hashicorp/http"
|
|
||||||
version = ">= 3.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,16 +16,12 @@ 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" {
|
||||||
type = string
|
type = string
|
||||||
description = "The directory to open in the IDE. e.g. /home/coder/project"
|
description = "The directory to open in the IDE. e.g. /home/coder/project"
|
||||||
validation {
|
|
||||||
condition = can(regex("^(?:/[^/]+)+$", var.folder))
|
|
||||||
error_message = "The folder must be a full path and must not start with a ~."
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "default" {
|
variable "default" {
|
||||||
@@ -38,95 +30,16 @@ variable "default" {
|
|||||||
description = "Default IDE"
|
description = "Default IDE"
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "order" {
|
|
||||||
type = number
|
|
||||||
description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
|
|
||||||
default = null
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "coder_parameter_order" {
|
|
||||||
type = number
|
|
||||||
description = "The order determines the position of a template parameter in the UI/CLI presentation. The lowest order is shown first and parameters with equal order are sorted by name (ascending order)."
|
|
||||||
default = null
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "latest" {
|
|
||||||
type = bool
|
|
||||||
description = "Whether to fetch the latest version of the IDE."
|
|
||||||
default = false
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "channel" {
|
|
||||||
type = string
|
|
||||||
description = "JetBrains IDE release channel. Valid values are release and eap."
|
|
||||||
default = "release"
|
|
||||||
validation {
|
|
||||||
condition = can(regex("^(release|eap)$", var.channel))
|
|
||||||
error_message = "The channel must be either release or eap."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "jetbrains_ide_versions" {
|
|
||||||
type = map(object({
|
|
||||||
build_number = string
|
|
||||||
version = string
|
|
||||||
}))
|
|
||||||
description = "The set of versions for each jetbrains IDE"
|
|
||||||
default = {
|
|
||||||
"IU" = {
|
|
||||||
build_number = "241.14494.240"
|
|
||||||
version = "2024.1"
|
|
||||||
}
|
|
||||||
"PS" = {
|
|
||||||
build_number = "241.14494.237"
|
|
||||||
version = "2024.1"
|
|
||||||
}
|
|
||||||
"WS" = {
|
|
||||||
build_number = "241.14494.235"
|
|
||||||
version = "2024.1"
|
|
||||||
}
|
|
||||||
"PY" = {
|
|
||||||
build_number = "241.14494.241"
|
|
||||||
version = "2024.1"
|
|
||||||
}
|
|
||||||
"CL" = {
|
|
||||||
build_number = "241.14494.288"
|
|
||||||
version = "2024.1"
|
|
||||||
}
|
|
||||||
"GO" = {
|
|
||||||
build_number = "241.14494.238"
|
|
||||||
version = "2024.1"
|
|
||||||
}
|
|
||||||
"RM" = {
|
|
||||||
build_number = "241.14494.234"
|
|
||||||
version = "2024.1"
|
|
||||||
}
|
|
||||||
"RD" = {
|
|
||||||
build_number = "241.14494.307"
|
|
||||||
version = "2024.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
validation {
|
|
||||||
condition = (
|
|
||||||
alltrue([
|
|
||||||
for code in keys(var.jetbrains_ide_versions) : contains(["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD"], code)
|
|
||||||
])
|
|
||||||
)
|
|
||||||
error_message = "The jetbrains_ide_versions must contain a map of valid product codes. Valid product codes are ${join(",", ["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD"])}."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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", "RD"]
|
|
||||||
validation {
|
validation {
|
||||||
condition = (
|
condition = (
|
||||||
alltrue([
|
alltrue([
|
||||||
for code in var.jetbrains_ides : contains(["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD"], 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 ${join(",", ["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD"])}."
|
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 {
|
||||||
@@ -140,104 +53,81 @@ variable "jetbrains_ides" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data "http" "jetbrains_ide_versions" {
|
|
||||||
for_each = var.latest ? toset(var.jetbrains_ides) : toset([])
|
|
||||||
url = "https://data.services.jetbrains.com/products/releases?code=${each.key}&latest=true&type=${var.channel}"
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
locals {
|
||||||
jetbrains_ides = {
|
jetbrains_ides = {
|
||||||
"GO" = {
|
"GO" = {
|
||||||
icon = "/icon/goland.svg",
|
icon = "/icon/goland.svg",
|
||||||
name = "GoLand",
|
name = "GoLand",
|
||||||
identifier = "GO",
|
value = jsonencode(["GO", "232.9921.53", "https://download.jetbrains.com/go/goland-2023.2.2.tar.gz"])
|
||||||
build_number = var.jetbrains_ide_versions["GO"].build_number,
|
|
||||||
download_link = "https://download.jetbrains.com/go/goland-${var.jetbrains_ide_versions["GO"].version}.tar.gz"
|
|
||||||
version = var.jetbrains_ide_versions["GO"].version
|
|
||||||
},
|
},
|
||||||
"WS" = {
|
"WS" = {
|
||||||
icon = "/icon/webstorm.svg",
|
icon = "/icon/webstorm.svg",
|
||||||
name = "WebStorm",
|
name = "WebStorm",
|
||||||
identifier = "WS",
|
value = jsonencode(["WS", "232.9921.42", "https://download.jetbrains.com/webstorm/WebStorm-2023.2.2.tar.gz"])
|
||||||
build_number = var.jetbrains_ide_versions["WS"].build_number,
|
|
||||||
download_link = "https://download.jetbrains.com/webstorm/WebStorm-${var.jetbrains_ide_versions["WS"].version}.tar.gz"
|
|
||||||
version = var.jetbrains_ide_versions["WS"].version
|
|
||||||
},
|
},
|
||||||
"IU" = {
|
"IU" = {
|
||||||
icon = "/icon/intellij.svg",
|
icon = "/icon/intellij.svg",
|
||||||
name = "IntelliJ IDEA Ultimate",
|
name = "IntelliJ IDEA Ultimate",
|
||||||
identifier = "IU",
|
value = jsonencode(["IU", "232.9921.47", "https://download.jetbrains.com/idea/ideaIU-2023.2.2.tar.gz"])
|
||||||
build_number = var.jetbrains_ide_versions["IU"].build_number,
|
},
|
||||||
download_link = "https://download.jetbrains.com/idea/ideaIU-${var.jetbrains_ide_versions["IU"].version}.tar.gz"
|
"IC" = {
|
||||||
version = var.jetbrains_ide_versions["IU"].version
|
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",
|
||||||
identifier = "PY",
|
value = jsonencode(["PY", "232.9559.58", "https://download.jetbrains.com/python/pycharm-professional-2023.2.1.tar.gz"])
|
||||||
build_number = var.jetbrains_ide_versions["PY"].build_number,
|
|
||||||
download_link = "https://download.jetbrains.com/python/pycharm-professional-${var.jetbrains_ide_versions["PY"].version}.tar.gz"
|
|
||||||
version = var.jetbrains_ide_versions["PY"].version
|
|
||||||
},
|
},
|
||||||
|
"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",
|
||||||
identifier = "CL",
|
value = jsonencode(["CL", "232.9921.42", "https://download.jetbrains.com/cpp/CLion-2023.2.2.tar.gz"])
|
||||||
build_number = var.jetbrains_ide_versions["CL"].build_number,
|
},
|
||||||
download_link = "https://download.jetbrains.com/cpp/CLion-${var.jetbrains_ide_versions["CL"].version}.tar.gz"
|
"DB" = {
|
||||||
version = var.jetbrains_ide_versions["CL"].version
|
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",
|
||||||
identifier = "PS",
|
value = jsonencode(["PS", "232.9559.64", "https://download.jetbrains.com/webide/PhpStorm-2023.2.1.tar.gz"])
|
||||||
build_number = var.jetbrains_ide_versions["PS"].build_number,
|
|
||||||
download_link = "https://download.jetbrains.com/webide/PhpStorm-${var.jetbrains_ide_versions["PS"].version}.tar.gz"
|
|
||||||
version = var.jetbrains_ide_versions["PS"].version
|
|
||||||
},
|
},
|
||||||
"RM" = {
|
"RM" = {
|
||||||
icon = "/icon/rubymine.svg",
|
icon = "/icon/rubymine.svg",
|
||||||
name = "RubyMine",
|
name = "RubyMine",
|
||||||
identifier = "RM",
|
value = jsonencode(["RM", "232.9921.48", "https://download.jetbrains.com/ruby/RubyMine-2023.2.2.tar.gz"])
|
||||||
build_number = var.jetbrains_ide_versions["RM"].build_number,
|
|
||||||
download_link = "https://download.jetbrains.com/ruby/RubyMine-${var.jetbrains_ide_versions["RM"].version}.tar.gz"
|
|
||||||
version = var.jetbrains_ide_versions["RM"].version
|
|
||||||
}
|
|
||||||
"RD" = {
|
|
||||||
icon = "/icon/rider.svg",
|
|
||||||
name = "Rider",
|
|
||||||
identifier = "RD",
|
|
||||||
build_number = var.jetbrains_ide_versions["RD"].build_number,
|
|
||||||
download_link = "https://download.jetbrains.com/rider/JetBrains.Rider-${var.jetbrains_ide_versions["RD"].version}.tar.gz"
|
|
||||||
version = var.jetbrains_ide_versions["RD"].version
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
icon = local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].icon
|
|
||||||
json_data = var.latest ? jsondecode(data.http.jetbrains_ide_versions[data.coder_parameter.jetbrains_ide.value].response_body) : {}
|
|
||||||
key = var.latest ? keys(local.json_data)[0] : ""
|
|
||||||
display_name = local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].name
|
|
||||||
identifier = data.coder_parameter.jetbrains_ide.value
|
|
||||||
download_link = var.latest ? local.json_data[local.key][0].downloads.linux.link : local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].download_link
|
|
||||||
build_number = var.latest ? local.json_data[local.key][0].build : local.jetbrains_ides[data.coder_parameter.jetbrains_ide.value].build_number
|
|
||||||
version = var.latest ? local.json_data[local.key][0].version : var.jetbrains_ide_versions[data.coder_parameter.jetbrains_ide.value].version
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data "coder_parameter" "jetbrains_ide" {
|
data "coder_parameter" "jetbrains_ide" {
|
||||||
type = "string"
|
type = "list(string)"
|
||||||
name = "jetbrains_ide"
|
name = "jetbrains_ide"
|
||||||
display_name = "JetBrains IDE"
|
display_name = "JetBrains IDE"
|
||||||
icon = "/icon/gateway.svg"
|
icon = "/icon/gateway.svg"
|
||||||
mutable = true
|
mutable = true
|
||||||
default = var.default == "" ? var.jetbrains_ides[0] : var.default
|
# check if default is in the jet_brains_ides list and if it is not empty or null otherwise set it to null
|
||||||
order = var.coder_parameter_order
|
default = var.default != null && var.default != "" && contains(var.jetbrains_ides, var.default) ? local.jetbrains_ides[var.default].value : local.jetbrains_ides[var.jetbrains_ides[0]].value
|
||||||
|
|
||||||
dynamic "option" {
|
dynamic "option" {
|
||||||
for_each = var.jetbrains_ides
|
for_each = { for key, value in local.jetbrains_ides : key => value if contains(var.jetbrains_ides, key) }
|
||||||
content {
|
content {
|
||||||
icon = local.jetbrains_ides[option.value].icon
|
icon = option.value.icon
|
||||||
name = local.jetbrains_ides[option.value].name
|
name = option.value.name
|
||||||
value = option.value
|
value = option.value.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,11 +136,10 @@ data "coder_workspace" "me" {}
|
|||||||
|
|
||||||
resource "coder_app" "gateway" {
|
resource "coder_app" "gateway" {
|
||||||
agent_id = var.agent_id
|
agent_id = var.agent_id
|
||||||
|
display_name = data.coder_parameter.jetbrains_ide.option[index(data.coder_parameter.jetbrains_ide.option.*.value, data.coder_parameter.jetbrains_ide.value)].name
|
||||||
slug = "gateway"
|
slug = "gateway"
|
||||||
display_name = local.display_name
|
icon = data.coder_parameter.jetbrains_ide.option[index(data.coder_parameter.jetbrains_ide.option.*.value, data.coder_parameter.jetbrains_ide.value)].icon
|
||||||
icon = local.icon
|
|
||||||
external = true
|
external = true
|
||||||
order = var.order
|
|
||||||
url = join("", [
|
url = join("", [
|
||||||
"jetbrains-gateway://connect#type=coder&workspace=",
|
"jetbrains-gateway://connect#type=coder&workspace=",
|
||||||
data.coder_workspace.me.name,
|
data.coder_workspace.me.name,
|
||||||
@@ -263,38 +152,14 @@ resource "coder_app" "gateway" {
|
|||||||
"&token=",
|
"&token=",
|
||||||
"$SESSION_TOKEN",
|
"$SESSION_TOKEN",
|
||||||
"&ide_product_code=",
|
"&ide_product_code=",
|
||||||
data.coder_parameter.jetbrains_ide.value,
|
jsondecode(data.coder_parameter.jetbrains_ide.value)[0],
|
||||||
"&ide_build_number=",
|
"&ide_build_number=",
|
||||||
local.build_number,
|
jsondecode(data.coder_parameter.jetbrains_ide.value)[1],
|
||||||
"&ide_download_link=",
|
"&ide_download_link=",
|
||||||
local.download_link,
|
jsondecode(data.coder_parameter.jetbrains_ide.value)[2],
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
output "identifier" {
|
output "jetbrains_ides" {
|
||||||
value = local.identifier
|
value = data.coder_parameter.jetbrains_ide.value
|
||||||
}
|
|
||||||
|
|
||||||
output "display_name" {
|
|
||||||
value = local.display_name
|
|
||||||
}
|
|
||||||
|
|
||||||
output "icon" {
|
|
||||||
value = local.icon
|
|
||||||
}
|
|
||||||
|
|
||||||
output "download_link" {
|
|
||||||
value = local.download_link
|
|
||||||
}
|
|
||||||
|
|
||||||
output "build_number" {
|
|
||||||
value = local.build_number
|
|
||||||
}
|
|
||||||
|
|
||||||
output "version" {
|
|
||||||
value = local.version
|
|
||||||
}
|
|
||||||
|
|
||||||
output "url" {
|
|
||||||
value = coder_app.gateway.url
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,103 +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`](https://coder.com/docs/v2/latest/admin/external-auth) feature.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "jfrog" {
|
|
||||||
source = "registry.coder.com/modules/jfrog-oauth/coder"
|
|
||||||
version = "1.0.5"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
jfrog_url = "https://example.jfrog.io"
|
|
||||||
username_field = "username" # If you are using GitHub to login to both Coder and Artifactory, use username_field = "username"
|
|
||||||
|
|
||||||
package_managers = {
|
|
||||||
"npm" : "npm",
|
|
||||||
"go" : "go",
|
|
||||||
"pypi" : "pypi"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> 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
|
|
||||||
|
|
||||||
This module is usable by JFrog self-hosted (on-premises) Artifactory as it requires configuring a custom integration. This integration benefits from Coder's [external-auth](https://coder.com/docs/v2/latest/admin/external-auth) feature and allows each user to authenticate with Artifactory using an OAuth flow and issues user-scoped tokens to each user. For configuration instructions, see this [guide](https://coder.com/docs/v2/latest/guides/artifactory-integration#jfrog-oauth) on the Coder documentation.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
Configure the Python pip package manager to fetch packages from Artifactory while mapping the Coder email to the Artifactory username.
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "jfrog" {
|
|
||||||
source = "registry.coder.com/modules/jfrog-oauth/coder"
|
|
||||||
version = "1.0.5"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
jfrog_url = "https://example.jfrog.io"
|
|
||||||
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.
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "jfrog" {
|
|
||||||
source = "registry.coder.com/modules/jfrog-oauth/coder"
|
|
||||||
version = "1.0.5"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
jfrog_url = "https://example.jfrog.io"
|
|
||||||
username_field = "username" # If you are using GitHub to login to both Coder and Artifactory, use username_field = "username"
|
|
||||||
configure_code_server = true # Add JFrog extension configuration for code-server
|
|
||||||
package_managers = {
|
|
||||||
"npm" : "npm",
|
|
||||||
"go" : "go",
|
|
||||||
"pypi" : "pypi"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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).
|
|
||||||
|
|
||||||
```tf
|
|
||||||
provider "docker" {
|
|
||||||
# ...
|
|
||||||
registry_auth {
|
|
||||||
address = "https://example.jfrog.io/artifactory/api/docker/REPO-KEY"
|
|
||||||
username = module.jfrog.username
|
|
||||||
password = module.jfrog.access_token
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> Here `REPO_KEY` is the name of docker repository in Artifactory.
|
|
||||||
@@ -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
|
|
||||||
@@ -1,138 +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 "jfrog_server_id" {
|
|
||||||
type = string
|
|
||||||
description = "The server ID of the JFrog instance for JFrog CLI configuration"
|
|
||||||
default = "0"
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
JFROG_SERVER_ID : var.jfrog_server_id,
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -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 "${JFROG_SERVER_ID}"
|
|
||||||
# Set the configured server as the default.
|
|
||||||
jf c use "${JFROG_SERVER_ID}"
|
|
||||||
|
|
||||||
# Configure npm to use the Artifactory "npm" repository.
|
|
||||||
if [ -z "${REPOSITORY_NPM}" ]; then
|
|
||||||
echo "🤔 no npm repository is set, skipping npm configuration."
|
|
||||||
echo "You can configure an npm repository by providing the a key for 'npm' in the 'package_managers' input."
|
|
||||||
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
|
|
||||||
@@ -1,126 +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.
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "jfrog" {
|
|
||||||
source = "registry.coder.com/modules/jfrog-token/coder"
|
|
||||||
version = "1.0.10"
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
For detailed instructions, please see this [guide](https://coder.com/docs/v2/latest/guides/artifactory-integration#jfrog-token) on the Coder documentation.
|
|
||||||
|
|
||||||
> Note
|
|
||||||
> This module does not install `npm`, `go`, `pip`, etc but only configure them. You need to handle the installation of these tools yourself.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
### Configure npm, go, and pypi to use Artifactory local repositories
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "jfrog" {
|
|
||||||
source = "registry.coder.com/modules/jfrog-token/coder"
|
|
||||||
version = "1.0.10"
|
|
||||||
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.
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "jfrog" {
|
|
||||||
source = "registry.coder.com/modules/jfrog-token/coder"
|
|
||||||
version = "1.0.10"
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Add a custom token description
|
|
||||||
|
|
||||||
```tf
|
|
||||||
data "coder_workspace" "me" {}
|
|
||||||
|
|
||||||
module "jfrog" {
|
|
||||||
source = "registry.coder.com/modules/jfrog-token/coder"
|
|
||||||
version = "1.0.10"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
jfrog_url = "https://XXXX.jfrog.io"
|
|
||||||
artifactory_access_token = var.artifactory_access_token
|
|
||||||
token_description = "Token for Coder workspace: ${data.coder_workspace.me.owner}/${data.coder_workspace.me.name}"
|
|
||||||
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).
|
|
||||||
|
|
||||||
```tf
|
|
||||||
|
|
||||||
provider "docker" {
|
|
||||||
# ...
|
|
||||||
registry_auth {
|
|
||||||
address = "https://YYYY.jfrog.io/artifactory/api/docker/REPO-KEY"
|
|
||||||
username = module.jfrog.username
|
|
||||||
password = module.jfrog.access_token
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> Here `REPO_KEY` is the name of docker repository in Artifactory.
|
|
||||||
@@ -1,178 +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 "jfrog_server_id" {
|
|
||||||
type = string
|
|
||||||
description = "The server ID of the JFrog instance for JFrog CLI configuration"
|
|
||||||
default = "0"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "artifactory_access_token" {
|
|
||||||
type = string
|
|
||||||
description = "The admin-level access token to use for JFrog."
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "token_description" {
|
|
||||||
type = string
|
|
||||||
description = "Free text token description. Useful for filtering and managing tokens."
|
|
||||||
default = "Token for Coder workspace"
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
description = var.token_description
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
JFROG_SERVER_ID : var.jfrog_server_id,
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -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 "${JFROG_SERVER_ID}"
|
|
||||||
# Set the configured server as the default.
|
|
||||||
jf c use "${JFROG_SERVER_ID}"
|
|
||||||
|
|
||||||
# Configure npm to use the Artifactory "npm" repository.
|
|
||||||
if [ -z "${REPOSITORY_NPM}" ]; then
|
|
||||||
echo "🤔 no npm repository is set, skipping npm configuration."
|
|
||||||
echo "You can configure an npm repository by providing the a key for 'npm' in the 'package_managers' input."
|
|
||||||
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
@@ -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
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -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
@@ -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
@@ -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!"
|
||||||
@@ -13,10 +13,9 @@ A module that adds Jupyter Notebook in your Coder template.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
```tf
|
```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.8"
|
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
coder = {
|
coder = {
|
||||||
source = "coder/coder"
|
source = "coder/coder"
|
||||||
version = ">= 0.17"
|
version = ">= 0.12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,21 +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'."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "order" {
|
|
||||||
type = number
|
|
||||||
description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
|
|
||||||
default = null
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "coder_script" "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"
|
||||||
@@ -60,6 +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"
|
||||||
order = var.order
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,9 @@ A module that adds JupyterLab in your Coder template.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
```tf
|
```hcl
|
||||||
module "jupyterlab" {
|
module "jupyterlab" {
|
||||||
source = "registry.coder.com/modules/jupyterlab/coder"
|
source = "https://registry.coder.com/modules/jupyterlab"
|
||||||
version = "1.0.8"
|
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
coder = {
|
coder = {
|
||||||
source = "coder/coder"
|
source = "coder/coder"
|
||||||
version = ">= 0.17"
|
version = ">= 0.12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,21 +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'."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "order" {
|
|
||||||
type = number
|
|
||||||
description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
|
|
||||||
default = null
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "coder_script" "jupyterlab" {
|
resource "coder_script" "jupyterlab" {
|
||||||
agent_id = var.agent_id
|
agent_id = var.agent_id
|
||||||
display_name = "jupyterlab"
|
display_name = "jupyterlab"
|
||||||
@@ -60,6 +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"
|
||||||
order = var.order
|
|
||||||
}
|
}
|
||||||
|
|||||||
43
lint.ts
@@ -15,37 +15,7 @@ let badExit = false;
|
|||||||
const error = (...data: any[]) => {
|
const error = (...data: any[]) => {
|
||||||
console.error(...data);
|
console.error(...data);
|
||||||
badExit = true;
|
badExit = true;
|
||||||
};
|
|
||||||
|
|
||||||
const verifyCodeBlocks = (
|
|
||||||
tokens: marked.Token[],
|
|
||||||
res = {
|
|
||||||
codeIsTF: false,
|
|
||||||
codeIsHCL: false,
|
|
||||||
}
|
}
|
||||||
) => {
|
|
||||||
for (const token of tokens) {
|
|
||||||
// Check in-depth.
|
|
||||||
if (token.type === "list") {
|
|
||||||
verifyCodeBlocks(token.items, res);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (token.type === "list_item") {
|
|
||||||
verifyCodeBlocks(token.tokens, res);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token.type === "code") {
|
|
||||||
if (token.lang === "tf") {
|
|
||||||
res.codeIsTF = true;
|
|
||||||
}
|
|
||||||
if (token.lang === "hcl") {
|
|
||||||
res.codeIsHCL = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ensures that each README has the proper format.
|
// Ensures that each README has the proper format.
|
||||||
// Exits with 0 if all is good!
|
// Exits with 0 if all is good!
|
||||||
@@ -92,7 +62,6 @@ for (const dir of dirs) {
|
|||||||
let h1 = false;
|
let h1 = false;
|
||||||
let code = false;
|
let code = false;
|
||||||
let paragraph = false;
|
let paragraph = false;
|
||||||
let version = true;
|
|
||||||
|
|
||||||
for (const token of tokens) {
|
for (const token of tokens) {
|
||||||
if (token.type === "heading" && token.depth === 1) {
|
if (token.type === "heading" && token.depth === 1) {
|
||||||
@@ -108,10 +77,6 @@ for (const dir of dirs) {
|
|||||||
}
|
}
|
||||||
if (token.type === "code") {
|
if (token.type === "code") {
|
||||||
code = true;
|
code = true;
|
||||||
if (token.lang === "tf" && !token.text.includes("version")) {
|
|
||||||
version = false;
|
|
||||||
error(dir.name, "missing version in tf code block");
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,14 +89,6 @@ for (const dir of dirs) {
|
|||||||
if (!code) {
|
if (!code) {
|
||||||
error(dir.name, "missing example code block after paragraph");
|
error(dir.name, "missing example code block after paragraph");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { codeIsTF, codeIsHCL } = verifyCodeBlocks(tokens);
|
|
||||||
if (!codeIsTF) {
|
|
||||||
error(dir.name, "missing example tf code block");
|
|
||||||
}
|
|
||||||
if (codeIsHCL) {
|
|
||||||
error(dir.name, "hcl code block should be tf");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (badExit) {
|
if (badExit) {
|
||||||
|
|||||||
4
new.sh
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# This scripts creates a new sample moduledir with required files
|
# This scripts creates a new sample moduledir with requried files
|
||||||
# Run it like : ./new.sh my-module
|
# Run it like : ./new.sh my-module
|
||||||
|
|
||||||
MODULE_NAME=$1
|
MODULE_NAME=$1
|
||||||
@@ -11,7 +11,7 @@ if [ -z "$MODULE_NAME" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create module directory and exit if it already 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"
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
---
|
|
||||||
display_name: nodejs
|
|
||||||
description: Install Node.js via nvm
|
|
||||||
icon: ../.icons/node.svg
|
|
||||||
maintainer_github: TheZoker
|
|
||||||
verified: false
|
|
||||||
tags: [helper]
|
|
||||||
---
|
|
||||||
|
|
||||||
# nodejs
|
|
||||||
|
|
||||||
Automatically installs [Node.js](https://github.com/nodejs/node) via [nvm](https://github.com/nvm-sh/nvm). It can also install multiple versions of node and set a default version. If no options are specified, the latest version is installed.
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "nodejs" {
|
|
||||||
source = "registry.coder.com/modules/nodejs/coder"
|
|
||||||
version = "1.0.10"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Install multiple versions
|
|
||||||
|
|
||||||
This installs multiple versions of Node.js:
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "nodejs" {
|
|
||||||
source = "registry.coder.com/modules/nodejs/coder"
|
|
||||||
version = "1.0.10"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
node_versions = [
|
|
||||||
"18",
|
|
||||||
"20",
|
|
||||||
"node"
|
|
||||||
]
|
|
||||||
default_node_version = "20"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Full example
|
|
||||||
|
|
||||||
A example with all available options:
|
|
||||||
|
|
||||||
```tf
|
|
||||||
module "nodejs" {
|
|
||||||
source = "registry.coder.com/modules/nodejs/coder"
|
|
||||||
version = "1.0.10"
|
|
||||||
agent_id = coder_agent.example.id
|
|
||||||
nvm_version = "v0.39.7"
|
|
||||||
nvm_install_prefix = "/opt/nvm"
|
|
||||||
node_versions = [
|
|
||||||
"16",
|
|
||||||
"18",
|
|
||||||
"node"
|
|
||||||
]
|
|
||||||
default_node_version = "16"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { describe, expect, it } from "bun:test";
|
|
||||||
import { runTerraformInit, testRequiredVariables } from "../test";
|
|
||||||
|
|
||||||
describe("nodejs", async () => {
|
|
||||||
await runTerraformInit(import.meta.dir);
|
|
||||||
|
|
||||||
testRequiredVariables(import.meta.dir, {
|
|
||||||
agent_id: "foo",
|
|
||||||
});
|
|
||||||
|
|
||||||
// More tests depend on shebang refactors
|
|
||||||
});
|
|
||||||
@@ -1,52 +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."
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "nvm_version" {
|
|
||||||
type = string
|
|
||||||
description = "The version of nvm to install."
|
|
||||||
default = "master"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "nvm_install_prefix" {
|
|
||||||
type = string
|
|
||||||
description = "The prefix to install nvm to (relative to $HOME)."
|
|
||||||
default = ".nvm"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "node_versions" {
|
|
||||||
type = list(string)
|
|
||||||
description = "A list of Node.js versions to install."
|
|
||||||
default = ["node"]
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "default_node_version" {
|
|
||||||
type = string
|
|
||||||
description = "The default Node.js version"
|
|
||||||
default = "node"
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "coder_script" "nodejs" {
|
|
||||||
agent_id = var.agent_id
|
|
||||||
display_name = "Node.js:"
|
|
||||||
script = templatefile("${path.module}/run.sh", {
|
|
||||||
NVM_VERSION : var.nvm_version,
|
|
||||||
INSTALL_PREFIX : var.nvm_install_prefix,
|
|
||||||
NODE_VERSIONS : join(",", var.node_versions),
|
|
||||||
DEFAULT : var.default_node_version,
|
|
||||||
})
|
|
||||||
run_on_start = true
|
|
||||||
start_blocks_login = true
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
NVM_VERSION='${NVM_VERSION}'
|
|
||||||
NODE_VERSIONS='${NODE_VERSIONS}'
|
|
||||||
INSTALL_PREFIX='${INSTALL_PREFIX}'
|
|
||||||
DEFAULT='${DEFAULT}'
|
|
||||||
BOLD='\033[0;1m'
|
|
||||||
CODE='\033[36;40;1m'
|
|
||||||
RESET='\033[0m'
|
|
||||||
|
|
||||||
printf "$${BOLD}Installing nvm!$${RESET}\n"
|
|
||||||
|
|
||||||
export NVM_DIR="$HOME/$${INSTALL_PREFIX}/nvm"
|
|
||||||
mkdir -p "$NVM_DIR"
|
|
||||||
|
|
||||||
script="$(curl -sS -o- "https://raw.githubusercontent.com/nvm-sh/nvm/$${NVM_VERSION}/install.sh" 2>&1)"
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "Failed to download nvm installation script: $script"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
output="$(bash <<< "$script" 2>&1)"
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "Failed to install nvm: $output"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf "🥳 nvm has been installed\n\n"
|
|
||||||
|
|
||||||
# Set up nvm for the rest of the script.
|
|
||||||
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
|
|
||||||
|
|
||||||
# Install each node version...
|
|
||||||
IFS=',' read -r -a VERSIONLIST <<< "$${NODE_VERSIONS}"
|
|
||||||
for version in "$${VERSIONLIST[@]}"; do
|
|
||||||
if [ -z "$version" ]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
printf "🛠️ Installing node version $${CODE}$version$${RESET}...\n"
|
|
||||||
output=$(nvm install "$version" 2>&1)
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "Failed to install version: $version: $output"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Set default if provided
|
|
||||||
if [ -n "$${DEFAULT}" ]; then
|
|
||||||
printf "🛠️ Setting default node version $${CODE}$DEFAULT$${RESET}...\n"
|
|
||||||
output=$(nvm alias default $DEFAULT 2>&1)
|
|
||||||
fi
|
|
||||||
263
package-lock.json
generated
@@ -1,263 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "modules",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"name": "modules",
|
|
||||||
"devDependencies": {
|
|
||||||
"bun-types": "^1.0.18",
|
|
||||||
"gray-matter": "^4.0.3",
|
|
||||||
"marked": "^12.0.0",
|
|
||||||
"prettier": "^3.2.5",
|
|
||||||
"prettier-plugin-sh": "^0.13.1",
|
|
||||||
"prettier-plugin-terraform-formatter": "^1.2.1"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"typescript": "^5.3.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/node": {
|
|
||||||
"version": "20.11.30",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz",
|
|
||||||
"integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"undici-types": "~5.26.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/ws": {
|
|
||||||
"version": "8.5.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz",
|
|
||||||
"integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/argparse": {
|
|
||||||
"version": "1.0.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
|
||||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"sprintf-js": "~1.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/bun-types": {
|
|
||||||
"version": "1.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.1.4.tgz",
|
|
||||||
"integrity": "sha512-E1kk0FNpxpkSSlCVXEa4HfyhSUEpKtCFrybPVyz1A4TEnBGy5bqqtSYkyjKTfKScdyZTBeFrTxJLiKGOIRWgwg==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "~20.11.3",
|
|
||||||
"@types/ws": "~8.5.10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/esprima": {
|
|
||||||
"version": "4.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
|
||||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
|
||||||
"esparse": "bin/esparse.js",
|
|
||||||
"esvalidate": "bin/esvalidate.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/extend-shallow": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"is-extendable": "^0.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/gray-matter": {
|
|
||||||
"version": "4.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
|
|
||||||
"integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"js-yaml": "^3.13.1",
|
|
||||||
"kind-of": "^6.0.2",
|
|
||||||
"section-matter": "^1.0.0",
|
|
||||||
"strip-bom-string": "^1.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/is-extendable": {
|
|
||||||
"version": "0.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
|
|
||||||
"integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/js-yaml": {
|
|
||||||
"version": "3.14.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
|
||||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"argparse": "^1.0.7",
|
|
||||||
"esprima": "^4.0.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"js-yaml": "bin/js-yaml.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/kind-of": {
|
|
||||||
"version": "6.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
|
||||||
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/marked": {
|
|
||||||
"version": "12.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz",
|
|
||||||
"integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==",
|
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
|
||||||
"marked": "bin/marked.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/mvdan-sh": {
|
|
||||||
"version": "0.10.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/mvdan-sh/-/mvdan-sh-0.10.1.tgz",
|
|
||||||
"integrity": "sha512-kMbrH0EObaKmK3nVRKUIIya1dpASHIEusM13S4V1ViHFuxuNxCo+arxoa6j/dbV22YBGjl7UKJm9QQKJ2Crzhg==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/prettier": {
|
|
||||||
"version": "3.2.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
|
|
||||||
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
|
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
|
||||||
"prettier": "bin/prettier.cjs"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/prettier-plugin-sh": {
|
|
||||||
"version": "0.13.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/prettier-plugin-sh/-/prettier-plugin-sh-0.13.1.tgz",
|
|
||||||
"integrity": "sha512-ytMcl1qK4s4BOFGvsc9b0+k9dYECal7U29bL/ke08FEUsF/JLN0j6Peo0wUkFDG4y2UHLMhvpyd6Sd3zDXe/eg==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"mvdan-sh": "^0.10.1",
|
|
||||||
"sh-syntax": "^0.4.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://opencollective.com/unts"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"prettier": "^3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/prettier-plugin-terraform-formatter": {
|
|
||||||
"version": "1.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/prettier-plugin-terraform-formatter/-/prettier-plugin-terraform-formatter-1.2.1.tgz",
|
|
||||||
"integrity": "sha512-rdzV61Bs/Ecnn7uAS/vL5usTX8xUWM+nQejNLZxt3I1kJH5WSeLEmq7LYu1wCoEQF+y7Uv1xGvPRfl3lIe6+tA==",
|
|
||||||
"dev": true,
|
|
||||||
"peerDependencies": {
|
|
||||||
"prettier": ">= 1.16.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"prettier": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/section-matter": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"extend-shallow": "^2.0.1",
|
|
||||||
"kind-of": "^6.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/sh-syntax": {
|
|
||||||
"version": "0.4.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/sh-syntax/-/sh-syntax-0.4.2.tgz",
|
|
||||||
"integrity": "sha512-/l2UZ5fhGZLVZa16XQM9/Vq/hezGGbdHeVEA01uWjOL1+7Ek/gt6FquW0iKKws4a9AYPYvlz6RyVvjh3JxOteg==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.6.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://opencollective.com/unts"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/sprintf-js": {
|
|
||||||
"version": "1.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
|
||||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/strip-bom-string": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/tslib": {
|
|
||||||
"version": "2.6.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
|
||||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/typescript": {
|
|
||||||
"version": "5.4.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
|
|
||||||
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
|
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
|
||||||
"tsc": "bin/tsc",
|
|
||||||
"tsserver": "bin/tsserver"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.17"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/undici-types": {
|
|
||||||
"version": "5.26.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
|
||||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
22
package.json
@@ -2,26 +2,16 @@
|
|||||||
"name": "modules",
|
"name": "modules",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "bun test",
|
"test": "bun test",
|
||||||
"fmt": "bun x prettier -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 --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 && ./terraform_validate.sh",
|
"lint": "bun run lint.ts"
|
||||||
"update-version": "./update-version.sh"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"bun-types": "^1.0.18",
|
"bun-types": "^1.0.3",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"marked": "^12.0.0",
|
"marked": "^9.0.3"
|
||||||
"prettier": "^3.2.5",
|
|
||||||
"prettier-plugin-sh": "^0.13.1",
|
|
||||||
"prettier-plugin-terraform-formatter": "^1.2.1"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.0.0"
|
||||||
},
|
|
||||||
"prettier": {
|
|
||||||
"plugins": [
|
|
||||||
"prettier-plugin-sh",
|
|
||||||
"prettier-plugin-terraform-formatter"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,9 @@ tags: [helper]
|
|||||||
|
|
||||||
Run a script on workspace start that allows developers to run custom commands to personalize their workspace.
|
Run a script on workspace start that allows developers to run custom commands to personalize their workspace.
|
||||||
|
|
||||||
```tf
|
```hcl
|
||||||
module "personalize" {
|
module "personalize" {
|
||||||
source = "registry.coder.com/modules/personalize/coder"
|
source = "https://registry.coder.com/modules/personalize"
|
||||||
version = "1.0.2"
|
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -54,10 +54,9 @@ slackme npm run long-build
|
|||||||
|
|
||||||
3. Restart your Coder deployment. Any Template can now import the Slack Me module, and `slackme` will be available on the `$PATH`:
|
3. Restart your Coder deployment. Any Template can now import the Slack Me module, and `slackme` will be available on the `$PATH`:
|
||||||
|
|
||||||
```tf
|
```hcl
|
||||||
module "slackme" {
|
module "slackme" {
|
||||||
source = "registry.coder.com/modules/slackme/coder"
|
source = "https://registry.coder.com/modules/slackme"
|
||||||
version = "1.0.2"
|
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
auth_provider_id = "slack"
|
auth_provider_id = "slack"
|
||||||
}
|
}
|
||||||
@@ -70,10 +69,9 @@ slackme npm run long-build
|
|||||||
- `$COMMAND` is replaced with the command the user executed.
|
- `$COMMAND` is replaced with the command the user executed.
|
||||||
- `$DURATION` is replaced with a human-readable duration the command took to execute.
|
- `$DURATION` is replaced with a human-readable duration the command took to execute.
|
||||||
|
|
||||||
```tf
|
```hcl
|
||||||
module "slackme" {
|
module "slackme" {
|
||||||
source = "registry.coder.com/modules/slackme/coder"
|
source = "https://registry.coder.com/modules/slackme"
|
||||||
version = "1.0.2"
|
|
||||||
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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
#!/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
|
||||||
)
|
)
|
||||||
@@ -73,7 +72,8 @@ 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)
|
||||||
@@ -86,3 +86,5 @@ 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
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# Function to run terraform init and validate in a directory
|
|
||||||
run_terraform() {
|
|
||||||
local dir="$1"
|
|
||||||
echo "Running terraform init and validate in $dir"
|
|
||||||
pushd "$dir"
|
|
||||||
terraform init -upgrade
|
|
||||||
terraform validate
|
|
||||||
popd
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main script
|
|
||||||
main() {
|
|
||||||
# Get the directory of the script
|
|
||||||
script_dir=$(dirname "$(readlink -f "$0")")
|
|
||||||
|
|
||||||
# Get all subdirectories in the repository
|
|
||||||
subdirs=$(find "$script_dir" -mindepth 1 -maxdepth 1 -type d -not -name ".*" | sort)
|
|
||||||
|
|
||||||
for dir in $subdirs; do
|
|
||||||
run_terraform "$dir"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run the main script
|
|
||||||
main
|
|
||||||
2
test.ts
@@ -171,9 +171,9 @@ export const testRequiredVariables = (
|
|||||||
export const runTerraformApply = async (
|
export const runTerraformApply = async (
|
||||||
dir: string,
|
dir: string,
|
||||||
vars: Record<string, string>,
|
vars: Record<string, string>,
|
||||||
env: Record<string, string> = {},
|
|
||||||
): Promise<TerraformState> => {
|
): Promise<TerraformState> => {
|
||||||
const stateFile = `${dir}/${crypto.randomUUID()}.tfstate`;
|
const stateFile = `${dir}/${crypto.randomUUID()}.tfstate`;
|
||||||
|
const env = {};
|
||||||
Object.keys(vars).forEach((key) => (env[`TF_VAR_${key}`] = vars[key]));
|
Object.keys(vars).forEach((key) => (env[`TF_VAR_${key}`] = vars[key]));
|
||||||
const proc = spawn(
|
const proc = spawn(
|
||||||
[
|
[
|
||||||
|
|||||||