Compare commits

...

69 Commits

Author SHA1 Message Date
Muhammad Atif Ali
d10ce91a64 fix: fix fetching rc versions of vault cli (#156)
Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
2024-02-14 17:04:56 +03:00
Muhammad Atif Ali
534491613f Update module versions to v1.0.3 (#159) 2024-02-14 16:37:01 +03:00
Muhammad Atif Ali
ac64af6f02 Update Hashicorp vault modules (#140)
Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
2024-02-14 01:43:33 +03:00
Muhammad Atif Ali
b299f98161 ci: automate version bumps in module README.md files (#139)
Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
2024-02-13 15:23:44 +03:00
Muhammad Atif Ali
7e897a51e6 chore(vault-github): Add partner github and tests (#142) 2024-02-13 12:18:23 +03:00
Muhammad Atif Ali
ac54966f5e feat!(git-config): use full name for git configuration (#141) 2024-02-12 17:18:13 +03:00
Andrew Svoboda
aef9b3b116 Add build numbers and versions to jetbrains gateway module (#150) 2024-02-12 17:16:31 +03:00
Phorcys
a5c4d00a01 fix(git-commit-signing): fix SSH key permissions (#152) 2024-02-10 00:20:05 +03:00
Muhammad Atif Ali
3227a47044 fix(jetbrains-gateway): fix readme to include agent_name (#151) 2024-02-09 21:19:29 +03:00
Florian Gareis
cf1807dd5c Allow custom display name and slug for VS Code Web (#146) 2024-02-09 21:18:20 +03:00
Florian Gareis
4c993d342d Fix code-server docu (#147) 2024-02-09 21:17:32 +03:00
Muhammad Atif Ali
5a7e3f6ca4 Add Hashicorp Vault Secrets Integration module (#144) 2024-02-09 21:16:41 +03:00
Muhammad Atif Ali
acab6437bc chore: bump version to 1.0.2 and add script to update them automatically. (#128)
Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
2024-01-30 17:51:31 +03:00
Muhammad Atif Ali
f16d7ca3f5 docs(jfrog-oauth): fix documentation link 2024-01-30 12:41:09 +03:00
Mathias Fredriksson
a9a58bff32 chore: lint for tf/hcl blocks (#135)
Co-authored-by: Muhammad Atif Ali <atif@coder.com>
2024-01-28 00:48:49 +03:00
Muhammad Atif Ali
6b842004e6 ci: check for typos (#131) 2024-01-27 16:50:47 +03:00
Mathias Fredriksson
376c0cae31 chore: add prettier terraform formatting in markdown files (#134) 2024-01-27 15:02:40 +02:00
Muhammad Atif Ali
7d31865c94 feat!(git-clone): change path input to base_dir and return repo_dir as output (#132) 2024-01-26 16:13:03 +03:00
Muhammad Atif Ali
d3fc2d2212 docs(jfrog-oauth): improve docs (#129)
* docs(jfrog-oauth): improve docs

Adds additional step and screenshot to show creating an OAuth app in JFrog platform

* Update README.md

* Add files via upload

* fmt

* move JFrog Artifactory integration setup instructions

* Update JFrog token documentation
2024-01-26 09:20:21 +03:00
Conor Bèhard Roberts
38a2d86376 feat: enable basename of url to be added to custom path (git-clone) (#124) 2024-01-23 13:55:30 +03:00
Muhammad Atif Ali
b968a2aa22 feat: add version to module docs (#122) 2024-01-23 13:50:55 +03:00
Muhammad Atif Ali
5b3edd9bbd fix(code-server): write settings to User (#123) 2024-01-18 23:22:57 +03:00
Muhammad Atif Ali
5b2f3bd599 chore: fix formatting for jfrog-oauth README (#119) 2024-01-11 00:17:20 +03:00
Muhammad Atif Ali
357bd41252 feat(jfrog): add JFrog vscode extension, CLI completion and docker support (#115) 2024-01-05 17:41:04 +03:00
Muhammad Atif Ali
382933aece chore(jfrog-oauth): update JFrog OAuth module README (#114) 2023-12-25 20:54:40 +03:00
Muhammad Atif Ali
f8faea1855 feat(vault-github): use coder_env to set VAULT_ADDR in workspace (#112) 2023-12-16 19:00:50 +03:00
Muhammad Atif Ali
1e3bd2b04b Add formatting check for shell scripts (#106) 2023-12-16 17:39:08 +03:00
Muhammad Atif Ali
1e7f91231c fix(jetbrains-gateway): fix tests (#111) 2023-12-08 04:33:53 +03:00
Muhammad Atif Ali
c6b1990225 fix(jetbrains-gateway): fix tests 2023-12-08 04:20:05 +03:00
Muhammad Atif Ali
3878e66700 fix: use agent_name in jetbrains-gateway (#110) 2023-12-08 04:10:19 +03:00
Muhammad Atif Ali
d48b68d374 chore: update screenshot for vault-github module (#109) 2023-12-08 03:12:22 +03:00
Muhammad Atif Ali
a954af73c5 chore(jetbrans-gateway): update JetBrains IDEs and remove community editions
Another try at #96. I cannot reproduce the error on deployment and dev.coder.com.

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

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

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

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

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

* bun fmt

* chore: clarify readme SSH key paragraph

* fix: add `curl` as dependency

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

* feat: add conflict disclaimer

---------

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

View File

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

41
.github/workflows/update-readme.yaml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: Update README on Tag
on:
workflow_dispatch:
push:
tags:
- 'v*'
jobs:
update-readme:
permissions:
contents: read
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'
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'

1
.icons/exoscale.svg Normal file
View File

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

After

Width:  |  Height:  |  Size: 427 B

2
.icons/vault.svg Normal file
View File

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

After

Width:  |  Height:  |  Size: 506 B

BIN
.images/exoscale-custom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
.images/exoscale-zones.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
.images/jfrog-oauth.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
.images/vault-login.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

View File

@@ -11,9 +11,10 @@ tags: [helper]
<!-- Describes what this module does -->
```hcl
```tf
module "MODULE_NAME" {
source = "https://registry.coder.com/modules/MODULE_NAME"
source = "registry.coder.com/modules/MODULE_NAME/coder"
version = "1.0.2"
}
```
@@ -25,13 +26,14 @@ module "MODULE_NAME" {
Install the Dracula theme from [OpenVSX](https://open-vsx.org/):
```hcl
```tf
module "MODULE_NAME" {
source = "https://registry.coder.com/modules/MODULE_NAME"
agent_id = coder_agent.example.id
extensions = [
"dracula-theme.theme-dracula"
]
source = "registry.coder.com/modules/MODULE_NAME/coder"
version = "1.0.2"
agent_id = coder_agent.example.id
extensions = [
"dracula-theme.theme-dracula"
]
}
```
@@ -41,14 +43,15 @@ 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:
```hcl
```tf
module "MODULE_NAME" {
source = "https://registry.coder.com/modules/MODULE_NAME"
agent_id = coder_agent.example.id
extensions = [ "dracula-theme.theme-dracula" ]
settings = {
"workbench.colorTheme" = "Dracula"
}
source = "registry.coder.com/modules/MODULE_NAME/coder"
version = "1.0.2"
agent_id = coder_agent.example.id
extensions = [ "dracula-theme.theme-dracula" ]
settings = {
"workbench.colorTheme" = "Dracula"
}
}
```
@@ -56,10 +59,11 @@ module "MODULE_NAME" {
Run code-server in the background, don't fetch it from GitHub:
```hcl
```tf
module "MODULE_NAME" {
source = "https://registry.coder.com/modules/MODULE_NAME"
agent_id = coder_agent.example.id
offline = true
source = "registry.coder.com/modules/MODULE_NAME/coder"
version = "1.0.2"
agent_id = coder_agent.example.id
offline = true
}
```

View File

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

View File

@@ -3,7 +3,7 @@
To create a new module, clone this repository and run:
```shell
./new.sh MOUDLE_NAME
./new.sh MODULE_NAME
```
## Testing a Module
@@ -19,8 +19,10 @@ $ bun test -t '<module>'
You can test a module locally by updating the source as follows
```hcl
source = "git::https://github.com/<USERNAME>/<REPO>.git//<MODULE-NAME>?ref=<BRANCH-NAME>"
```tf
module "example" {
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.

View File

@@ -14,10 +14,11 @@ Modules extend Templates to create reusable components for your development envi
e.g.
```hcl
```tf
module "code-server" {
source = "https://registry.coder.com/modules/code-server"
agent_id = coder_agent.main.id
source = "registry.coder.com/modules/code-server/coder"
version = "1.0.2"
agent_id = coder_agent.main.id
}
```

View File

@@ -14,14 +14,15 @@ the region closest to them.
Customize the preselected parameter value:
```hcl
```tf
module "aws-region" {
source = "https://registry.coder.com/modules/aws-region"
default = "us-east-1"
source = "registry.coder.com/modules/aws-region/coder"
version = "1.0.2"
default = "us-east-1"
}
provider "aws" {
region = module.aws_region.value
region = module.aws_region.value
}
```
@@ -33,20 +34,23 @@ provider "aws" {
Change the display name and icon for a region using the corresponding maps:
```hcl
```tf
module "aws-region" {
source = "https://registry.coder.com/modules/aws-region"
default = "ap-south-1"
custom_names = {
"ap-south-1": "Awesome Mumbai!"
}
custom_icons = {
"ap-south-1": "/emojis/1f33a.png"
}
source = "registry.coder.com/modules/aws-region/coder"
version = "1.0.2"
default = "ap-south-1"
custom_names = {
"ap-south-1" : "Awesome Mumbai!"
}
custom_icons = {
"ap-south-1" : "/emojis/1f33a.png"
}
}
provider "aws" {
region = module.aws_region.value
region = module.aws_region.value
}
```
@@ -56,14 +60,15 @@ provider "aws" {
Hide the Asia Pacific regions Seoul and Osaka:
```hcl
```tf
module "aws-region" {
source = "https://registry.coder.com/modules/aws-region"
exclude = [ "ap-northeast-2", "ap-northeast-3" ]
source = "registry.coder.com/modules/aws-region/coder"
version = "1.0.2"
exclude = ["ap-northeast-2", "ap-northeast-3"]
}
provider "aws" {
region = module.aws_region.value
region = module.aws_region.value
}
```

View File

@@ -11,14 +11,15 @@ tags: [helper, parameter, azure, regions]
This module adds a parameter with all Azure regions, allowing developers to select the region closest to them.
```hcl
```tf
module "azure_region" {
source = "https://registry.coder.com/modules/azure-region"
default = "eastus"
source = "registry.coder.com/modules/azure-region/coder"
version = "1.0.2"
default = "eastus"
}
resource "azurem_resource_group" "example" {
location = module.azure_region.value
location = module.azure_region.value
}
```
@@ -30,19 +31,20 @@ resource "azurem_resource_group" "example" {
Change the display name and icon for a region using the corresponding maps:
```hcl
```tf
module "azure-region" {
source = "https://registry.coder.com/modules/azure-region"
custom_names = {
"australia": "Go Australia!"
}
custom_icons = {
"australia": "/icons/smiley.svg"
}
source = "registry.coder.com/modules/azure-region/coder"
version = "1.0.2"
custom_names = {
"australia" : "Go Australia!"
}
custom_icons = {
"australia" : "/icons/smiley.svg"
}
}
resource "azurerm_resource_group" "example" {
location = module.azure_region.value
location = module.azure_region.value
}
```
@@ -52,19 +54,20 @@ resource "azurerm_resource_group" "example" {
Hide all regions in Australia except australiacentral:
```hcl
```tf
module "azure-region" {
source = "https://registry.coder.com/modules/azure-region"
exclude = [
"australia",
"australiacentral2",
"australiaeast",
"australiasoutheast"
]
source = "registry.coder.com/modules/azure-region/coder"
version = "1.0.2"
exclude = [
"australia",
"australiacentral2",
"australiaeast",
"australiasoutheast"
]
}
resource "azurerm_resource_group" "example" {
location = module.azure_region.value
location = module.azure_region.value
}
```

BIN
bun.lockb

Binary file not shown.

View File

@@ -11,10 +11,11 @@ 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.
```hcl
```tf
module "code-server" {
source = "https://registry.coder.com/modules/code-server"
agent_id = coder_agent.example.id
source = "registry.coder.com/modules/code-server/coder"
version = "1.0.2"
agent_id = coder_agent.example.id
}
```
@@ -24,9 +25,10 @@ module "code-server" {
### Pin Versions
```hcl
```tf
module "code-server" {
source = "https://registry.coder.com/modules/code-server"
source = "registry.coder.com/modules/code-server/coder"
version = "1.0.2"
agent_id = coder_agent.example.id
install_version = "4.8.3"
}
@@ -36,13 +38,14 @@ module "code-server" {
Install the Dracula theme from [OpenVSX](https://open-vsx.org/):
```hcl
```tf
module "code-server" {
source = "https://registry.coder.com/modules/code-server"
agent_id = coder_agent.example.id
extensions = [
"dracula-theme.theme-dracula"
]
source = "registry.coder.com/modules/code-server/coder"
version = "1.0.2"
agent_id = coder_agent.example.id
extensions = [
"dracula-theme.theme-dracula"
]
}
```
@@ -52,14 +55,28 @@ 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:
```hcl
module "settings" {
source = "https://registry.coder.com/modules/code-server"
agent_id = coder_agent.example.id
extensions = [ "dracula-theme.theme-dracula" ]
settings = {
"workbench.colorTheme" = "Dracula"
}
```tf
module "code-server" {
source = "registry.coder.com/modules/code-server/coder"
version = "1.0.2"
agent_id = coder_agent.example.id
extensions = ["dracula-theme.theme-dracula"]
settings = {
"workbench.colorTheme" = "Dracula"
}
}
```
### Install multiple extensions
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.2"
agent_id = coder_agent.example.id
extensions = ["dracula-theme.theme-dracula", "ms-azuretools.vscode-docker"]
}
```
@@ -67,10 +84,11 @@ module "settings" {
Just run code-server in the background, don't fetch it from GitHub:
```hcl
module "settings" {
source = "https://registry.coder.com/modules/code-server"
agent_id = coder_agent.example.id
offline = true
```tf
module "code-server" {
source = "registry.coder.com/modules/code-server/coder"
version = "1.0.2"
agent_id = coder_agent.example.id
offline = true
}
```

View File

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

View File

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

View File

@@ -11,10 +11,11 @@ tags: [helper]
Automatically logs the user into Coder when creating their workspace.
```hcl
```tf
module "coder-login" {
source = "https://registry.coder.com/modules/coder-login"
agent_id = coder_agent.example.id
source = "registry.coder.com/modules/coder-login/coder"
version = "1.0.2"
agent_id = coder_agent.example.id
}
```

View File

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

View File

@@ -11,9 +11,10 @@ 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.
```hcl
```tf
module "dotfiles" {
source = "https://registry.coder.com/modules/dotfiles"
source = "registry.coder.com/modules/dotfiles/coder"
version = "1.0.2"
agent_id = coder_agent.example.id
}
```

View File

@@ -0,0 +1,114 @@
---
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.2"
default = "standard.medium"
}
resource "exoscale_compute_instance" "instance" {
type = module.exoscale-instance-type.value
# ...
}
resource "coder_metadata" "workspace_info" {
item {
key = "instance type"
value = module.exoscale-instance-type.name
}
}
```
![Exoscale instance types](../.images/exoscale-instance-types.png)
## Examples
### Customize type
Change the display name a type using the corresponding maps:
```tf
module "exoscale-instance-type" {
source = "registry.coder.com/modules/exoscale-instance-type/coder"
version = "1.0.2"
default = "standard.medium"
custom_names = {
"standard.medium" : "Mittlere Instanz" # German translation
}
custom_descriptions = {
"standard.medium" : "4 GB Arbeitsspeicher, 2 Kerne, 10 - 400 GB Festplatte" # German translation
}
}
resource "exoscale_compute_instance" "instance" {
type = module.exoscale-instance-type.value
# ...
}
resource "coder_metadata" "workspace_info" {
item {
key = "instance type"
value = module.exoscale-instance-type.name
}
}
```
![Exoscale instance types Custom](../.images/exoscale-instance-custom.png)
### Use category and exclude type
Show only gpu1 types
```tf
module "exoscale-instance-type" {
source = "registry.coder.com/modules/exoscale-instance-type/coder"
version = "1.0.2"
default = "gpu.large"
type_category = ["gpu"]
exclude = [
"gpu2.small",
"gpu2.medium",
"gpu2.large",
"gpu2.huge",
"gpu3.small",
"gpu3.medium",
"gpu3.large",
"gpu3.huge"
]
}
resource "exoscale_compute_instance" "instance" {
type = module.exoscale-instance-type.value
# ...
}
resource "coder_metadata" "workspace_info" {
item {
key = "instance type"
value = module.exoscale-instance-type.name
}
}
```
![Exoscale instance types category and exclude](../.images/exoscale-instance-exclude.png)
## Related templates
A related exoscale template will be provided soon.

View File

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

View File

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

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

@@ -0,0 +1,98 @@
---
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.2"
default = "ch-dk-2"
}
data "exoscale_compute_template" "my_template" {
zone = module.exoscale-zone.value
name = "Linux Ubuntu 22.04 LTS 64-bit"
}
resource "exoscale_compute_instance" "instance" {
zone = module.exoscale-zone.value
# ...
}
```
![Exoscale Zones](../.images/exoscale-zones.png)
## Examples
### Customize zones
Change the display name and icon for a zone using the corresponding maps:
```tf
module "exoscale-zone" {
source = "registry.coder.com/modules/exoscale-zone/coder"
version = "1.0.2"
default = "at-vie-1"
custom_names = {
"at-vie-1" : "Home Vienna"
}
custom_icons = {
"at-vie-1" : "/emojis/1f3e0.png"
}
}
data "exoscale_compute_template" "my_template" {
zone = module.exoscale-zone.value
name = "Linux Ubuntu 22.04 LTS 64-bit"
}
resource "exoscale_compute_instance" "instance" {
zone = module.exoscale-zone.value
# ...
}
```
![Exoscale Custom](../.images/exoscale-custom.png)
### Exclude regions
Hide the Switzerland zones Geneva and Zurich
```tf
module "exoscale-zone" {
source = "registry.coder.com/modules/exoscale-zone/coder"
version = "1.0.2"
exclude = ["ch-gva-2", "ch-dk-2"]
}
data "exoscale_compute_template" "my_template" {
zone = module.exoscale-zone.value
name = "Linux Ubuntu 22.04 LTS 64-bit"
}
resource "exoscale_compute_instance" "instance" {
zone = module.exoscale-zone.value
# ...
}
```
![Exoscale Exclude](../.images/exoscale-exclude.png)
## Related templates
An exoscale sample template will be delivered soon.

View File

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

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

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

View File

@@ -11,10 +11,11 @@ tags: [helper, filebrowser]
A file browser for your workspace.
```hcl
```tf
module "filebrowser" {
source = "https://registry.coder.com/modules/filebrowser"
agent_id = coder_agent.example.id
source = "registry.coder.com/modules/filebrowser/coder"
version = "1.0.3"
agent_id = coder_agent.example.id
}
```
@@ -24,20 +25,22 @@ module "filebrowser" {
### Serve a specific directory
```hcl
```tf
module "filebrowser" {
source = "https://registry.coder.com/modules/filebrowser"
agent_id = coder_agent.example.id
folder = "/home/coder/project"
source = "registry.coder.com/modules/filebrowser/coder"
version = "1.0.3"
agent_id = coder_agent.example.id
folder = "/home/coder/project"
}
```
### Specify location of `filebrowser.db`
```hcl
```tf
module "filebrowser" {
source = "https://registry.coder.com/modules/filebrowser"
agent_id = coder_agent.example.id
database_path = ".config/filebrowser.db"
source = "registry.coder.com/modules/filebrowser/coder"
version = "1.0.3"
agent_id = coder_agent.example.id
database_path = ".config/filebrowser.db"
}
```

View File

@@ -33,7 +33,7 @@ describe("filebrowser", async () => {
expect(output.stdout).toEqual([
"\u001b[0;1mInstalling filebrowser ",
"",
"🥳 Installation comlete! ",
"🥳 Installation complete! ",
"",
"👷 Starting filebrowser in background... ",
"",
@@ -55,7 +55,7 @@ describe("filebrowser", async () => {
expect(output.stdout).toEqual([
"\u001b[0;1mInstalling filebrowser ",
"",
"🥳 Installation comlete! ",
"🥳 Installation complete! ",
"",
"👷 Starting filebrowser in background... ",
"",
@@ -77,7 +77,7 @@ describe("filebrowser", async () => {
expect(output.stdout).toEqual([
"\u001B[0;1mInstalling filebrowser ",
"",
"🥳 Installation comlete! ",
"🥳 Installation complete! ",
"",
"👷 Starting filebrowser in background... ",
"",

View File

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

View File

@@ -5,7 +5,7 @@ printf "$${BOLD}Installing filebrowser \n\n"
curl -fsSL https://raw.githubusercontent.com/filebrowser/get/master/get.sh | bash
printf "🥳 Installation comlete! \n\n"
printf "🥳 Installation complete! \n\n"
printf "👷 Starting filebrowser in background... \n\n"
@@ -21,6 +21,6 @@ printf "📂 Serving $${ROOT_DIR} at http://localhost:${PORT} \n\n"
printf "Running 'filebrowser --noauth --root $ROOT_DIR --port ${PORT}$${DB_FLAG}' \n\n"
filebrowser --noauth --root $ROOT_DIR --port ${PORT}$${DB_FLAG} >${LOG_PATH} 2>&1 &
filebrowser --noauth --root $ROOT_DIR --port ${PORT}$${DB_FLAG} > ${LOG_PATH} 2>&1 &
printf "📝 Logs at ${LOG_PATH} \n\n"

View File

@@ -13,10 +13,11 @@ 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.
```hcl
```tf
module "fly-region" {
source = "https://registry.coder.com/modules/fly-region"
default = "atl"
source = "registry.coder.com/modules/fly-region/coder"
version = "1.0.2"
default = "atl"
}
```
@@ -28,11 +29,12 @@ module "fly-region" {
The regions argument can be used to display only the desired regions in the Coder parameter.
```hcl
```tf
module "fly-region" {
source = "https://registry.coder.com/modules/fly-region"
default = "ams"
regions = ["ams", "arn", "atl"]
source = "registry.coder.com/modules/fly-region/coder"
version = "1.0.2"
default = "ams"
regions = ["ams", "arn", "atl"]
}
```
@@ -42,16 +44,19 @@ module "fly-region" {
Set custom icons and names with their respective maps.
```hcl
```tf
module "fly-region" {
source = "https://registry.coder.com/modules/fly-region"
default = "ams"
custom_icons = {
"ams" = "/emojis/1f90e.png"
}
custom_names = {
"ams" = "We love the Netherlands!"
}
source = "registry.coder.com/modules/fly-region/coder"
version = "1.0.2"
default = "ams"
custom_icons = {
"ams" = "/emojis/1f90e.png"
}
custom_names = {
"ams" = "We love the Netherlands!"
}
}
```

View File

@@ -11,14 +11,15 @@ tags: [gcp, regions, parameter, helper]
This module adds Google Cloud Platform regions to your Coder template.
```hcl
```tf
module "gcp_region" {
source = "https://registry.coder.com/modules/gcp-region"
regions = ["us", "europe"]
source = "registry.coder.com/modules/gcp-region/coder"
version = "1.0.2"
regions = ["us", "europe"]
}
resource "google_compute_instance" "example" {
zone = module.gcp_region.value
zone = module.gcp_region.value
}
```
@@ -30,44 +31,47 @@ 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`.
```hcl
```tf
module "gcp_region" {
source = "https://registry.coder.com/modules/gcp-region"
default = ["us-west1-a"]
regions = ["us-west1"]
gpu_only = false
source = "registry.coder.com/modules/gcp-region/coder"
version = "1.0.2"
default = ["us-west1-a"]
regions = ["us-west1"]
gpu_only = false
}
resource "google_compute_instance" "example" {
zone = module.gcp_region.value
zone = module.gcp_region.value
}
```
### Add all zones in the Europe West region
```hcl
```tf
module "gcp_region" {
source = "https://registry.coder.com/modules/gcp-region"
regions = ["europe-west"]
single_zone_per_region = false
source = "registry.coder.com/modules/gcp-region/coder"
version = "1.0.2"
regions = ["europe-west"]
single_zone_per_region = false
}
resource "google_compute_instance" "example" {
zone = module.gcp_region.value
zone = module.gcp_region.value
}
```
### Add a single zone from each region in US and Europe that laos has GPUs
### Add a single zone from each region in US and Europe that has GPUs
```hcl
```tf
module "gcp_region" {
source = "https://registry.coder.com/modules/gcp-region"
regions = ["us", "europe"]
gpu_only = true
single_zone_per_region = true
source = "registry.coder.com/modules/gcp-region/coder"
version = "1.0.2"
regions = ["us", "europe"]
gpu_only = true
single_zone_per_region = true
}
resource "google_compute_instance" "example" {
zone = module.gcp_region.value
zone = module.gcp_region.value
}
```

View File

@@ -9,21 +9,14 @@ tags: [git, helper]
# Git Clone
This module allows you to automatically clone a repository by URL and skip if it exists in the path provided.
This module allows you to automatically clone a repository by URL and skip if it exists in the base directory provided.
```hcl
```tf
module "git-clone" {
source = "https://registry.coder.com/modules/git-clone"
agent_id = coder_agent.example.id
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"
source = "registry.coder.com/modules/git-clone/coder"
version = "1.0.2"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
}
```
@@ -31,11 +24,29 @@ data "coder_git_auth" "github" {
### Custom Path
```hcl
```tf
module "git-clone" {
source = "https://registry.coder.com/modules/git-clone"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
path = "~/projects/coder/coder"
source = "registry.coder.com/modules/git-clone/coder"
version = "1.0.2"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
base_dir = "~/projects/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.2"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
}
data "coder_git_auth" "github" {
id = "github"
}
```

View File

@@ -4,7 +4,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
version = ">= 0.11"
version = ">= 0.12"
}
}
}
@@ -14,9 +14,9 @@ variable "url" {
type = string
}
variable "path" {
variable "base_dir" {
default = ""
description = "The path to clone the repository. Defaults to \"$HOME/<basename of url>\"."
description = "The base directory to clone the repository. Defaults to \"$HOME\"."
type = string
}
@@ -25,10 +25,19 @@ variable "agent_id" {
type = string
}
locals {
clone_path = var.base_dir != "" ? join("/", [var.base_dir, replace(basename(var.url), ".git", "")]) : join("/", ["~", replace(basename(var.url), ".git", "")])
}
output "repo_dir" {
value = local.clone_path
description = "Full path of cloned repo directory"
}
resource "coder_script" "git_clone" {
agent_id = var.agent_id
script = templatefile("${path.module}/run.sh", {
CLONE_PATH : var.path != "" ? var.path : join("/", ["~", basename(var.url)]),
CLONE_PATH = local.clone_path
REPO_URL : var.url,
})
display_name = "Git Clone"

View File

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

View File

@@ -0,0 +1,25 @@
---
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.3"
agent_id = coder_agent.example.id
}
```

View File

@@ -0,0 +1,25 @@
terraform {
required_version = ">= 1.0"
required_providers {
coder = {
source = "coder/coder"
version = ">= 0.12"
}
}
}
variable "agent_id" {
type = string
description = "The ID of a Coder agent."
}
resource "coder_script" "git-commit-signing" {
display_name = "Git commit signing"
icon = "https://raw.githubusercontent.com/coder/modules/main/.icons/git.svg"
script = file("${path.module}/run.sh")
run_on_start = true
agent_id = var.agent_id
}

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

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

View File

@@ -11,9 +11,10 @@ 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.
```hcl
```tf
module "git-config" {
source = "https://registry.coder.com/modules/git-config"
source = "registry.coder.com/modules/git-config/coder"
version = "1.0.3"
agent_id = coder_agent.example.id
}
```
@@ -24,10 +25,11 @@ TODO: Add screenshot
### Allow users to override both username and email
```hcl
```tf
module "git-config" {
source = "https://registry.coder.com/modules/git-config"
agent_id = coder_agent.example.id
source = "registry.coder.com/modules/git-config/coder"
version = "1.0.3"
agent_id = coder_agent.example.id
allow_email_change = true
}
```
@@ -36,13 +38,12 @@ TODO: Add screenshot
## Disallowing users from overriding both username and email
```hcl
```tf
module "git-config" {
source = "https://registry.coder.com/modules/git-config"
agent_id = coder_agent.example.id
source = "registry.coder.com/modules/git-config/coder"
version = "1.0.3"
agent_id = coder_agent.example.id
allow_username_change = false
allow_email_change = false
allow_email_change = false
}
```
TODO: Add screenshot

View File

@@ -1,43 +0,0 @@
import { describe, expect, it } from "bun:test";
import {
executeScriptInContainer,
runTerraformApply,
runTerraformInit,
testRequiredVariables,
} from "../test";
describe("git-config", async () => {
await runTerraformInit(import.meta.dir);
testRequiredVariables(import.meta.dir, {
agent_id: "foo",
});
it("fails without git", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
});
const output = await executeScriptInContainer(state, "alpine");
expect(output.exitCode).toBe(1);
expect(output.stdout).toEqual([
"\u001B[0;1mChecking git-config!",
"Git is not installed!",
]);
});
it("runs with git", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
});
const output = await executeScriptInContainer(state, "alpine/git");
expect(output.exitCode).toBe(0);
expect(output.stdout).toEqual([
"\u001B[0;1mChecking git-config!",
"git-config: No user.email found, setting to ",
"git-config: No user.name found, setting to default",
"",
"\u001B[0;1mgit-config: using email: ",
"\u001B[0;1mgit-config: using username: default",
]);
});
});

View File

@@ -4,7 +4,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
version = ">= 0.12"
version = ">= 0.13"
}
}
}
@@ -34,7 +34,7 @@ data "coder_parameter" "user_email" {
name = "user_email"
type = "string"
default = ""
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"
mutable = true
}
@@ -44,18 +44,31 @@ data "coder_parameter" "username" {
name = "username"
type = "string"
default = ""
description = "Git user.name to be used for commits. Leave empty to default to Coder username."
display_name = "Git config user.name"
description = "Git user.name to be used for commits. Leave empty to default to Coder user's Full Name."
display_name = "Full Name for Git config"
mutable = true
}
resource "coder_script" "git_config" {
resource "coder_env" "git_author_name" {
agent_id = var.agent_id
script = templatefile("${path.module}/run.sh", {
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, "")
})
display_name = "Git Config"
icon = "/icon/git.svg"
run_on_start = true
name = "GIT_AUTHOR_NAME"
value = coalesce(try(data.coder_parameter.username[0].value, ""), data.coder_workspace.me.owner_name, data.coder_workspace.me.owner)
}
resource "coder_env" "git_commmiter_name" {
agent_id = var.agent_id
name = "GIT_COMMITTER_NAME"
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)
}
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)
}

View File

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

View File

@@ -0,0 +1,68 @@
---
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.3"
agent_id = coder_agent.example.id
app_name = "demo-app"
}
```
## Configuration
To configure the HCP Vault Secrets module, you must 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. 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.3"
agent_id = coder_agent.example.id
app_name = "demo-app"
}
```
## 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.3"
agent_id = coder_agent.example.id
app_name = "demo-app"
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.3"
agent_id = coder_agent.example.id
app_name = "demo-app"
client_id = "HCP_CLIENT_ID"
client_secret = "HCP_CLIENT_SECRET"
}
```

67
hcp-vault-secrets/main.tf Normal file
View File

@@ -0,0 +1,67 @@
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
}
provider "coder" {}
variable "agent_id" {
type = string
description = "The ID of a Coder agent."
}
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]
}

View File

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

View File

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

View File

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

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

@@ -0,0 +1,103 @@
---
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.
![JFrog OAuth](../.images/jfrog-oauth.png)
```tf
module "jfrog" {
source = "registry.coder.com/modules/jfrog-oauth/coder"
version = "1.0.2"
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.2"
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.2"
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.

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

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

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

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

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

@@ -0,0 +1,122 @@
#!/usr/bin/env bash
BOLD='\033[0;1m'
# check if JFrog CLI is already installed
if command -v jf > /dev/null 2>&1; then
echo "✅ JFrog CLI is already installed, skipping installation."
else
echo "📦 Installing JFrog CLI..."
curl -fL https://install-cli.jfrog.io | sudo sh
sudo chmod 755 /usr/local/bin/jf
fi
# The jf CLI checks $CI when determining whether to use interactive
# flows.
export CI=true
# Authenticate JFrog CLI with Artifactory.
echo "${ARTIFACTORY_ACCESS_TOKEN}" | jf c add --access-token-stdin --url "${JFROG_URL}" --overwrite 0
# Set the configured server as the default.
jf c use 0
# Configure npm to use the Artifactory "npm" repository.
if [ -z "${REPOSITORY_NPM}" ]; then
echo "🤔 no npm repository is set, skipping npm configuration."
echo "You can configure an npm repository by providing the a key for 'npm' in the 'package_managers' input."
else
echo "📦 Configuring npm..."
jf npmc --global --repo-resolve "${REPOSITORY_NPM}"
cat << EOF > ~/.npmrc
email=${ARTIFACTORY_EMAIL}
registry=${JFROG_URL}/artifactory/api/npm/${REPOSITORY_NPM}
EOF
echo "//${JFROG_HOST}/artifactory/api/npm/${REPOSITORY_NPM}/:_authToken=${ARTIFACTORY_ACCESS_TOKEN}" >> ~/.npmrc
fi
# Configure the `pip` to use the Artifactory "python" repository.
if [ -z "${REPOSITORY_PYPI}" ]; then
echo "🤔 no pypi repository is set, skipping pip configuration."
echo "You can configure a pypi repository by providing the a key for 'pypi' in the 'package_managers' input."
else
echo "📦 Configuring pip..."
jf pipc --global --repo-resolve "${REPOSITORY_PYPI}"
mkdir -p ~/.pip
cat << EOF > ~/.pip/pip.conf
[global]
index-url = https://${ARTIFACTORY_USERNAME}:${ARTIFACTORY_ACCESS_TOKEN}@${JFROG_HOST}/artifactory/api/pypi/${REPOSITORY_PYPI}/simple
EOF
fi
# Configure Artifactory "go" repository.
if [ -z "${REPOSITORY_GO}" ]; then
echo "🤔 no go repository is set, skipping go configuration."
echo "You can configure a go repository by providing the a key for 'go' in the 'package_managers' input."
else
echo "🐹 Configuring go..."
jf goc --global --repo-resolve "${REPOSITORY_GO}"
fi
echo "🥳 Configuration complete!"
# Configure the JFrog CLI to use the Artifactory "docker" repository.
if [ -z "${REPOSITORY_DOCKER}" ]; then
echo "🤔 no docker repository is set, skipping docker configuration."
echo "You can configure a docker repository by providing the a key for 'docker' in the 'package_managers' input."
else
if command -v docker > /dev/null 2>&1; then
echo "🔑 Configuring 🐳 docker credentials..."
mkdir -p ~/.docker
echo -n "${ARTIFACTORY_ACCESS_TOKEN}" | docker login ${JFROG_HOST} --username ${ARTIFACTORY_USERNAME} --password-stdin
else
echo "🤔 no docker is installed, skipping docker configuration."
fi
fi
# Install the JFrog vscode extension for code-server.
if [ "${CONFIGURE_CODE_SERVER}" == "true" ]; then
while ! [ -x /tmp/code-server/bin/code-server ]; do
counter=0
if [ $counter -eq 60 ]; then
echo "Timed out waiting for /tmp/code-server/bin/code-server to be installed."
exit 1
fi
echo "Waiting for /tmp/code-server/bin/code-server to be installed..."
sleep 1
((counter++))
done
echo "📦 Installing JFrog extension..."
/tmp/code-server/bin/code-server --install-extension jfrog.jfrog-vscode-extension
echo "🥳 JFrog extension installed!"
else
echo "🤔 Skipping JFrog extension installation. Set configure_code_server to true to install the JFrog extension."
fi
# Configure the JFrog CLI completion
echo "📦 Configuring JFrog CLI completion..."
# Get the user's shell
SHELLNAME=$(grep "^$USER" /etc/passwd | awk -F':' '{print $7}' | awk -F'/' '{print $NF}')
# Generate the completion script
jf completion $SHELLNAME --install
# Add the completion script to the user's shell profile
if [ "$SHELLNAME" == "bash" ] && [ -f ~/.bashrc ]; then
if ! grep -q "# jf CLI shell completion" ~/.bashrc; then
echo "" >> ~/.bashrc
echo "# BEGIN: jf CLI shell completion (added by coder module jfrog-oauth)" >> ~/.bashrc
echo 'source "$HOME/.jfrog/jfrog_bash_completion"' >> ~/.bashrc
echo "# END: jf CLI shell completion" >> ~/.bashrc
else
echo "🥳 ~/.bashrc already contains jf CLI shell completion configuration, skipping."
fi
elif [ "$SHELLNAME" == "zsh" ] && [ -f ~/.zshrc ]; then
if ! grep -q "# jf CLI shell completion" ~/.zshrc; then
echo "" >> ~/.zshrc
echo "# BEGIN: jf CLI shell completion (added by coder module jfrog-oauth)" >> ~/.zshrc
echo "autoload -Uz compinit" >> ~/.zshrc
echo "compinit" >> ~/.zshrc
echo 'source "$HOME/.jfrog/jfrog_zsh_completion"' >> ~/.zshrc
echo "# END: jf CLI shell completion" >> ~/.zshrc
else
echo "🥳 ~/.zshrc already contains jf CLI shell completion configuration, skipping."
fi
else
echo "🤔 ~/.bashrc or ~/.zshrc does not exist, skipping jf CLI shell completion configuration."
fi

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

@@ -0,0 +1,106 @@
---
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.2"
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.
![JFrog](../.images/jfrog.png)
## 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.2"
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.2"
agent_id = coder_agent.example.id
jfrog_url = "https://XXXX.jfrog.io"
artifactory_access_token = var.artifactory_access_token
configure_code_server = true # Add JFrog extension configuration for code-server
package_managers = {
"npm" : "npm",
"go" : "go",
"pypi" : "pypi"
}
}
```
### Using the access token in other terraform resources
JFrog Access token is also available as a terraform output. You can use it in other terraform resources. For example, you can use it to configure an [Artifactory docker registry](https://jfrog.com/help/r/jfrog-artifactory-documentation/docker-registry) with the [docker terraform provider](https://registry.terraform.io/providers/kreuzwerker/docker/latest/docs).
```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.

View File

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

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

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

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

@@ -0,0 +1,122 @@
#!/usr/bin/env bash
BOLD='\033[0;1m'
# check if JFrog CLI is already installed
if command -v jf > /dev/null 2>&1; then
echo "✅ JFrog CLI is already installed, skipping installation."
else
echo "📦 Installing JFrog CLI..."
curl -fL https://install-cli.jfrog.io | sudo sh
sudo chmod 755 /usr/local/bin/jf
fi
# The jf CLI checks $CI when determining whether to use interactive
# flows.
export CI=true
# Authenticate JFrog CLI with Artifactory.
echo "${ARTIFACTORY_ACCESS_TOKEN}" | jf c add --access-token-stdin --url "${JFROG_URL}" --overwrite 0
# Set the configured server as the default.
jf c use 0
# Configure npm to use the Artifactory "npm" repository.
if [ -z "${REPOSITORY_NPM}" ]; then
echo "🤔 no npm repository is set, skipping npm configuration."
echo "You can configure an npm repository by providing the a key for 'npm' in the 'package_managers' input."
else
echo "📦 Configuring npm..."
jf npmc --global --repo-resolve "${REPOSITORY_NPM}"
cat << EOF > ~/.npmrc
email=${ARTIFACTORY_EMAIL}
registry=${JFROG_URL}/artifactory/api/npm/${REPOSITORY_NPM}
EOF
echo "//${JFROG_HOST}/artifactory/api/npm/${REPOSITORY_NPM}/:_authToken=${ARTIFACTORY_ACCESS_TOKEN}" >> ~/.npmrc
fi
# Configure the `pip` to use the Artifactory "python" repository.
if [ -z "${REPOSITORY_PYPI}" ]; then
echo "🤔 no pypi repository is set, skipping pip configuration."
echo "You can configure a pypi repository by providing the a key for 'pypi' in the 'package_managers' input."
else
echo "🐍 Configuring pip..."
jf pipc --global --repo-resolve "${REPOSITORY_PYPI}"
mkdir -p ~/.pip
cat << EOF > ~/.pip/pip.conf
[global]
index-url = https://${ARTIFACTORY_USERNAME}:${ARTIFACTORY_ACCESS_TOKEN}@${JFROG_HOST}/artifactory/api/pypi/${REPOSITORY_PYPI}/simple
EOF
fi
# Configure Artifactory "go" repository.
if [ -z "${REPOSITORY_GO}" ]; then
echo "🤔 no go repository is set, skipping go configuration."
echo "You can configure a go repository by providing the a key for 'go' in the 'package_managers' input."
else
echo "🐹 Configuring go..."
jf goc --global --repo-resolve "${REPOSITORY_GO}"
fi
echo "🥳 Configuration complete!"
# Configure the JFrog CLI to use the Artifactory "docker" repository.
if [ -z "${REPOSITORY_DOCKER}" ]; then
echo "🤔 no docker repository is set, skipping docker configuration."
echo "You can configure a docker repository by providing the a key for 'docker' in the 'package_managers' input."
else
if command -v docker > /dev/null 2>&1; then
echo "🔑 Configuring 🐳 docker credentials..."
mkdir -p ~/.docker
echo -n "${ARTIFACTORY_ACCESS_TOKEN}" | docker login ${JFROG_HOST} --username ${ARTIFACTORY_USERNAME} --password-stdin
else
echo "🤔 no docker is installed, skipping docker configuration."
fi
fi
# Install the JFrog vscode extension for code-server.
if [ "${CONFIGURE_CODE_SERVER}" == "true" ]; then
while ! [ -x /tmp/code-server/bin/code-server ]; do
counter=0
if [ $counter -eq 60 ]; then
echo "Timed out waiting for /tmp/code-server/bin/code-server to be installed."
exit 1
fi
echo "Waiting for /tmp/code-server/bin/code-server to be installed..."
sleep 1
((counter++))
done
echo "📦 Installing JFrog extension..."
/tmp/code-server/bin/code-server --install-extension jfrog.jfrog-vscode-extension
echo "🥳 JFrog extension installed!"
else
echo "🤔 Skipping JFrog extension installation. Set configure_code_server to true to install the JFrog extension."
fi
# Configure the JFrog CLI completion
echo "📦 Configuring JFrog CLI completion..."
# Get the user's shell
SHELLNAME=$(grep "^$USER" /etc/passwd | awk -F':' '{print $7}' | awk -F'/' '{print $NF}')
# Generate the completion script
jf completion $SHELLNAME --install
# Add the completion script to the user's shell profile
if [ "$SHELLNAME" == "bash" ] && [ -f ~/.bashrc ]; then
if ! grep -q "# jf CLI shell completion" ~/.bashrc; then
echo "" >> ~/.bashrc
echo "# BEGIN: jf CLI shell completion (added by coder module jfrog-token)" >> ~/.bashrc
echo 'source "$HOME/.jfrog/jfrog_bash_completion"' >> ~/.bashrc
echo "# END: jf CLI shell completion" >> ~/.bashrc
else
echo "🥳 ~/.bashrc already contains jf CLI shell completion configuration, skipping."
fi
elif [ "$SHELLNAME" == "zsh" ] && [ -f ~/.zshrc ]; then
if ! grep -q "# jf CLI shell completion" ~/.zshrc; then
echo "" >> ~/.zshrc
echo "# BEGIN: jf CLI shell completion (added by coder module jfrog-token)" >> ~/.zshrc
echo "autoload -Uz compinit" >> ~/.zshrc
echo "compinit" >> ~/.zshrc
echo 'source "$HOME/.jfrog/jfrog_zsh_completion"' >> ~/.zshrc
echo "# END: jf CLI shell completion" >> ~/.zshrc
else
echo "🥳 ~/.zshrc already contains jf CLI shell completion configuration, skipping."
fi
else
echo "🤔 ~/.bashrc or ~/.zshrc does not exist, skipping jf CLI shell completion configuration."
fi

View File

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

View File

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

View File

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

View File

@@ -13,9 +13,10 @@ A module that adds Jupyter Notebook in your Coder template.
![Jupyter Notebook](../.images/jupyter-notebook.png)
```hcl
```tf
module "jupyter-notebook" {
source = "https://registry.coder.com/modules/jupyter-notebook"
agent_id = coder_agent.example.id
source = "registry.coder.com/modules/jupyter-notebook/coder"
version = "1.0.2"
agent_id = coder_agent.example.id
}
```

View File

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

View File

@@ -5,21 +5,21 @@ BOLD='\033[0;1m'
printf "$${BOLD}Installing jupyter-notebook!\n"
# check if jupyter-notebook is installed
if ! command -v jupyter-notebook >/dev/null 2>&1; then
# install jupyter-notebook
# check if python3 pip is installed
if ! command -v pip3 >/dev/null 2>&1; then
echo "pip3 is not installed"
echo "Please install pip3 in your Dockerfile/VM image before running this script"
exit 1
fi
# install jupyter-notebook
pip3 install --upgrade --no-cache-dir --no-warn-script-location jupyter
echo "🥳 jupyter-notebook has been installed\n\n"
if ! command -v jupyter-notebook > /dev/null 2>&1; then
# install jupyter-notebook
# check if python3 pip is installed
if ! command -v pip3 > /dev/null 2>&1; then
echo "pip3 is not installed"
echo "Please install pip3 in your Dockerfile/VM image before running this script"
exit 1
fi
# install jupyter-notebook
pip3 install --upgrade --no-cache-dir --no-warn-script-location jupyter
echo "🥳 jupyter-notebook has been installed\n\n"
else
echo "🥳 jupyter-notebook is already installed\n\n"
echo "🥳 jupyter-notebook is already installed\n\n"
fi
echo "👷 Starting jupyter-notebook in background..."
echo "check logs at ${LOG_PATH}"
$HOME/.local/bin/jupyter notebook --NotebookApp.ip='0.0.0.0' --ServerApp.port=${PORT} --no-browser --ServerApp.token='' --ServerApp.password='' >${LOG_PATH} 2>&1 &
$HOME/.local/bin/jupyter notebook --NotebookApp.ip='0.0.0.0' --ServerApp.port=${PORT} --no-browser --ServerApp.token='' --ServerApp.password='' > ${LOG_PATH} 2>&1 &

View File

@@ -13,9 +13,10 @@ A module that adds JupyterLab in your Coder template.
![JupyterLab](../.images/jupyterlab.png)
```hcl
```tf
module "jupyterlab" {
source = "https://registry.coder.com/modules/jupyterlab"
agent_id = coder_agent.example.id
source = "registry.coder.com/modules/jupyterlab/coder"
version = "1.0.2"
agent_id = coder_agent.example.id
}
```

View File

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

View File

@@ -5,21 +5,21 @@ BOLD='\033[0;1m'
printf "$${BOLD}Installing jupyterlab!\n"
# check if jupyterlab is installed
if ! command -v jupyterlab >/dev/null 2>&1; then
# install jupyterlab
# check if python3 pip is installed
if ! command -v pip3 >/dev/null 2>&1; then
echo "pip3 is not installed"
echo "Please install pip3 in your Dockerfile/VM image before running this script"
exit 1
fi
# install jupyterlab
pip3 install --upgrade --no-cache-dir --no-warn-script-location jupyterlab
echo "🥳 jupyterlab has been installed\n\n"
if ! command -v jupyterlab > /dev/null 2>&1; then
# install jupyterlab
# check if python3 pip is installed
if ! command -v pip3 > /dev/null 2>&1; then
echo "pip3 is not installed"
echo "Please install pip3 in your Dockerfile/VM image before running this script"
exit 1
fi
# install jupyterlab
pip3 install --upgrade --no-cache-dir --no-warn-script-location jupyterlab
echo "🥳 jupyterlab has been installed\n\n"
else
echo "🥳 jupyterlab is already installed\n\n"
echo "🥳 jupyterlab is already installed\n\n"
fi
echo "👷 Starting jupyterlab in background..."
echo "check logs at ${LOG_PATH}"
$HOME/.local/bin/jupyter lab --ServerApp.ip='0.0.0.0' --ServerApp.port=${PORT} --no-browser --ServerApp.token='' --ServerApp.password='' >${LOG_PATH} 2>&1 &
$HOME/.local/bin/jupyter lab --ServerApp.ip='0.0.0.0' --ServerApp.port=${PORT} --no-browser --ServerApp.token='' --ServerApp.password='' > ${LOG_PATH} 2>&1 &

49
lint.ts
View File

@@ -13,9 +13,39 @@ let badExit = false;
// error reports an error to the console and sets badExit to true
// so that the process will exit with a non-zero exit code.
const error = (...data: any[]) => {
console.error(...data);
badExit = true;
}
console.error(...data);
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.
// Exits with 0 if all is good!
@@ -62,6 +92,7 @@ for (const dir of dirs) {
let h1 = false;
let code = false;
let paragraph = false;
let version = true;
for (const token of tokens) {
if (token.type === "heading" && token.depth === 1) {
@@ -77,6 +108,10 @@ for (const dir of dirs) {
}
if (token.type === "code") {
code = true;
if (token.lang === "tf" && !token.text.includes("version")) {
version = false;
error(dir.name, "missing version in tf code block");
}
continue;
}
}
@@ -89,6 +124,14 @@ for (const dir of dirs) {
if (!code) {
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) {

26
new.sh
View File

@@ -1,21 +1,21 @@
#!/usr/bin/env bash
# This scripts creates a new sample moduledir with requried files
# This scripts creates a new sample moduledir with required files
# Run it like : ./new.sh my-module
MODULE_NAME=$1
# Check if module name is provided
if [ -z "$MODULE_NAME" ]; then
echo "Usage: ./new.sh <module_name>"
exit 1
echo "Usage: ./new.sh <module_name>"
exit 1
fi
# Create module directory and exit if it alredy exists
# Create module directory and exit if it already exists
if [ -d "$MODULE_NAME" ]; then
echo "Module with name $MODULE_NAME already exists"
echo "Please choose a different name"
exit 1
echo "Module with name $MODULE_NAME already exists"
echo "Please choose a different name"
exit 1
fi
mkdir -p "${MODULE_NAME}"
@@ -27,13 +27,13 @@ cd "${MODULE_NAME}"
# Detect OS
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
sed -i '' "s/MODULE_NAME/${MODULE_NAME}/g" main.tf
sed -i '' "s/MODULE_NAME/${MODULE_NAME}/g" README.md
# macOS
sed -i '' "s/MODULE_NAME/${MODULE_NAME}/g" main.tf
sed -i '' "s/MODULE_NAME/${MODULE_NAME}/g" README.md
else
# Linux
sed -i "s/MODULE_NAME/${MODULE_NAME}/g" main.tf
sed -i "s/MODULE_NAME/${MODULE_NAME}/g" README.md
# Linux
sed -i "s/MODULE_NAME/${MODULE_NAME}/g" main.tf
sed -i "s/MODULE_NAME/${MODULE_NAME}/g" README.md
fi
# Make run.sh executable

View File

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

View File

@@ -11,9 +11,10 @@ tags: [helper]
Run a script on workspace start that allows developers to run custom commands to personalize their workspace.
```hcl
```tf
module "personalize" {
source = "https://registry.coder.com/modules/personalize"
source = "registry.coder.com/modules/personalize/coder"
version = "1.0.2"
agent_id = coder_agent.example.id
}
```

View File

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

View File

@@ -12,7 +12,7 @@ tags: [helper]
Add the `slackme` command to your workspace that DMs you on Slack when your command finishes running.
```bash
$ slackme npm run long-build
slackme npm run long-build
```
## Setup
@@ -54,10 +54,11 @@ $ 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`:
```hcl
```tf
module "slackme" {
source = "https://registry.coder.com/modules/slackme"
agent_id = coder_agent.example.id
source = "registry.coder.com/modules/slackme/coder"
version = "1.0.2"
agent_id = coder_agent.example.id
auth_provider_id = "slack"
}
```
@@ -69,12 +70,13 @@ $ slackme npm run long-build
- `$COMMAND` is replaced with the command the user executed.
- `$DURATION` is replaced with a human-readable duration the command took to execute.
```hcl
```tf
module "slackme" {
source = "https://registry.coder.com/modules/slackme"
agent_id = coder_agent.example.id
source = "registry.coder.com/modules/slackme/coder"
version = "1.0.2"
agent_id = coder_agent.example.id
auth_provider_id = "slack"
slack_message = <<EOF
slack_message = <<EOF
👋 Hey there from Coder! $COMMAND took $DURATION to execute!
EOF
}

View File

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

29
update-version.sh Executable file
View File

@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# This script updates the version number in the README.md files of all modules
# to the latest tag in the repository. It is intended to be run from the root
# of the repository or by using the `bun update-version` command.
set -euo pipefail
current_tag=$(git describe --tags --abbrev=0)
previous_tag=$(git describe --tags --abbrev=0 $current_tag^)
mapfile -t changed_dirs < <(git diff --name-only "$previous_tag"..."$current_tag" -- ':!**/README.md' ':!**/*.test.ts' | xargs dirname | grep -v '^\.' | sort -u)
LATEST_TAG=$(git describe --abbrev=0 --tags | sed 's/^v//') || exit $?
for dir in "${changed_dirs[@]}"; do
if [[ -f "$dir/README.md" ]]; then
echo "Bumping version in $dir/README.md"
file="$dir/README.md"
tmpfile=$(mktemp /tmp/tempfile.XXXXXX)
awk -v tag="$LATEST_TAG" '{
if ($1 == "version" && $2 == "=") {
sub(/"[^"]*"/, "\"" tag "\"")
print
} else {
print
}
}' "$file" > "$tmpfile" && mv "$tmpfile" "$file"
fi
done

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

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

11
vault-github/main.test.ts Normal file
View File

@@ -0,0 +1,11 @@
import { describe } from "bun:test";
import { runTerraformInit, testRequiredVariables } from "../test";
describe("vault-token", async () => {
await runTerraformInit(import.meta.dir);
testRequiredVariables(import.meta.dir, {
agent_id: "foo",
vault_addr: "foo",
});
});

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

@@ -0,0 +1,68 @@
terraform {
required_version = ">= 1.0"
required_providers {
coder = {
source = "coder/coder"
version = ">= 0.12.4"
}
}
}
# Add required variables for your modules and remove any unneeded variables
variable "agent_id" {
type = string
description = "The ID of a Coder agent."
}
variable "vault_addr" {
type = string
description = "The address of the Vault server."
}
variable "coder_github_auth_id" {
type = string
description = "The ID of the GitHub external auth."
default = "github"
}
variable "vault_github_auth_path" {
type = string
description = "The path to the GitHub auth method."
default = "github"
}
variable "vault_cli_version" {
type = string
description = "The version of Vault to install."
default = "latest"
validation {
condition = can(regex("^(latest|[0-9]+\\.[0-9]+\\.[0-9]+)$", var.vault_cli_version))
error_message = "Vault version must be in the format 0.0.0 or latest"
}
}
data "coder_workspace" "me" {}
resource "coder_script" "vault" {
agent_id = var.agent_id
display_name = "Vault (GitHub)"
icon = "/icon/vault.svg"
script = templatefile("${path.module}/run.sh", {
AUTH_PATH : var.vault_github_auth_path,
GITHUB_EXTERNAL_AUTH_ID : data.coder_external_auth.github.id,
INSTALL_VERSION : var.vault_cli_version,
})
run_on_start = true
start_blocks_login = true
}
resource "coder_env" "vault_addr" {
agent_id = var.agent_id
name = "VAULT_ADDR"
value = var.vault_addr
}
data "coder_external_auth" "github" {
id = var.coder_github_auth_id
}

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

@@ -0,0 +1,109 @@
#!/usr/bin/env bash
# Convert all templated variables to shell variables
INSTALL_VERSION=${INSTALL_VERSION}
GITHUB_EXTERNAL_AUTH_ID=${GITHUB_EXTERNAL_AUTH_ID}
AUTH_PATH=${AUTH_PATH}
fetch() {
dest="$1"
url="$2"
if command -v curl > /dev/null 2>&1; then
curl -sSL --fail "$${url}" -o "$${dest}"
elif command -v wget > /dev/null 2>&1; then
wget -O "$${dest}" "$${url}"
elif command -v busybox > /dev/null 2>&1; then
busybox wget -O "$${dest}" "$${url}"
else
printf "curl, wget, or busybox is not installed. Please install curl or wget in your image.\n"
exit 1
fi
}
unzip_safe() {
if command -v unzip > /dev/null 2>&1; then
command unzip "$@"
elif command -v busybox > /dev/null 2>&1; then
busybox unzip "$@"
else
printf "unzip or busybox is not installed. Please install unzip in your image.\n"
exit 1
fi
}
install() {
# Fetch the latest version of Vault if INSTALL_VERSION is 'latest'
if [ "$${INSTALL_VERSION}" = "latest" ]; then
LATEST_VERSION=$(curl -s https://releases.hashicorp.com/vault/ | grep -v '-rc' | grep -oP 'vault/\K[0-9]+\.[0-9]+\.[0-9]+' | sort -V | tail -n 1)
printf "Latest version of Vault is %s.\n\n" "$${LATEST_VERSION}"
if [ -z "$${LATEST_VERSION}" ]; then
printf "Failed to determine the latest Vault version.\n"
return 1
fi
INSTALL_VERSION=$${LATEST_VERSION}
fi
# Check if the vault CLI is installed and has the correct version
installation_needed=1
if command -v vault > /dev/null 2>&1; then
CURRENT_VERSION=$(vault version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
if [ "$${CURRENT_VERSION}" = "$${INSTALL_VERSION}" ]; then
printf "Vault version %s is already installed and up-to-date.\n\n" "$${CURRENT_VERSION}"
installation_needed=0
fi
fi
if [ $${installation_needed} -eq 1 ]; then
# Download and install Vault
if [ -z "$${CURRENT_VERSION}" ]; then
printf "Installing Vault CLI ...\n\n"
else
printf "Upgrading Vault CLI from version %s to %s ...\n\n" "$${CURRENT_VERSION}" "${INSTALL_VERSION}"
fi
fetch vault.zip "https://releases.hashicorp.com/vault/$${INSTALL_VERSION}/vault_$${INSTALL_VERSION}_linux_amd64.zip"
if [ $? -ne 0 ]; then
printf "Failed to download Vault.\n"
return 1
fi
if ! unzip_safe vault.zip; then
printf "Failed to unzip Vault.\n"
return 1
fi
rm vault.zip
if sudo mv vault /usr/local/bin/vault 2> /dev/null; then
printf "Vault installed successfully!\n\n"
else
mkdir -p ~/.local/bin
if ! mv vault ~/.local/bin/vault; then
printf "Failed to move Vault to local bin.\n"
return 1
fi
printf "Please add ~/.local/bin to your PATH to use vault CLI.\n"
fi
fi
return 0
}
TMP=$(mktemp -d)
if ! (
cd "$TMP"
install
); then
echo "Failed to install Vault CLI."
exit 1
fi
rm -rf "$TMP"
# Authenticate with Vault
printf "🔑 Authenticating with Vault ...\n\n"
GITHUB_TOKEN=$(coder external-auth access-token "$${GITHUB_EXTERNAL_AUTH_ID}")
if [ $? -ne 0 ]; then
printf "Authentication with Vault failed. Please check your credentials.\n"
exit 1
fi
# Login to vault using the GitHub token
printf "🔑 Logging in to Vault ...\n\n"
vault login -no-print -method=github -path=/$${AUTH_PATH} token="$${GITHUB_TOKEN}"
printf "🥳 Vault authentication complete!\n\n"
printf "You can now use Vault CLI to access secrets.\n"

83
vault-token/README.md Normal file
View File

@@ -0,0 +1,83 @@
---
display_name: Hashicorp Vault Integration (Token)
description: Authenticates with Vault using Token
icon: ../.icons/vault.svg
maintainer_github: coder
partner_github: hashicorp
verified: true
tags: [helper, integration, vault, token]
---
# Hashicorp Vault Integration (Token)
This module lets you authenticate with [Hashicorp Vault](https://www.vaultproject.io/) in your Coder workspaces using a [Vault token](https://developer.hashicorp.com/vault/docs/auth/token).
```tf
variable "vault_token" {
type = string
description = "The Vault token to use for authentication."
sensitive = true
}
module "vault" {
source = "registry.coder.com/modules/vault-token/coder"
version = "1.0.3"
agent_id = coder_agent.example.id
vault_token = var.token
vault_addr = "https://vault.example.com"
}
```
Then you can use the Vault CLI in your workspaces to fetch secrets from Vault:
```shell
vault kv get -namespace=coder -mount=secrets coder
```
or using the Vault API:
```shell
curl -H "X-Vault-Token: ${VAULT_TOKEN}" -X GET "${VAULT_ADDR}/v1/coder/secrets/data/coder"
```
## Configuration
To configure the Vault module, you must create a Vault token with the the required permissions and configure the module with the token and Vault address.
1. Create a vault policy with read access to the secret mount you need your developers to access.
```shell
vault policy write read-coder-secrets - <<EOF
path "coder/data/*" {
capabilities = ["read"]
}
path "coder/metadata/*" {
capabilities = ["read"]
}
EOF
```
2. Create a token using this policy.
```shell
vault token create -policy="read-coder-secrets"
```
3. Copy the generated token and use in your template.
## Examples
### Configure Vault integration and install a specific version of the Vault CLI
```tf
variable "vault_token" {
type = string
description = "The Vault token to use for authentication."
sensitive = true
}
module "vault" {
source = "registry.coder.com/modules/vault-token/coder"
version = "1.0.3"
agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com"
vault_token = var.token
vault_cli_version = "1.15.0"
}
```

12
vault-token/main.test.ts Normal file
View File

@@ -0,0 +1,12 @@
import { describe } from "bun:test";
import { runTerraformInit, testRequiredVariables } from "../test";
describe("vault-token", async () => {
await runTerraformInit(import.meta.dir);
testRequiredVariables(import.meta.dir, {
agent_id: "foo",
vault_addr: "foo",
vault_token: "foo",
});
});

62
vault-token/main.tf Normal file
View File

@@ -0,0 +1,62 @@
terraform {
required_version = ">= 1.0"
required_providers {
coder = {
source = "coder/coder"
version = ">= 0.12.4"
}
}
}
# Add required variables for your modules and remove any unneeded variables
variable "agent_id" {
type = string
description = "The ID of a Coder agent."
}
variable "vault_addr" {
type = string
description = "The address of the Vault server."
}
variable "vault_token" {
type = string
description = "The Vault token to use for authentication."
sensitive = true
}
variable "vault_cli_version" {
type = string
description = "The version of Vault to install."
default = "latest"
validation {
condition = can(regex("^(latest|[0-9]+\\.[0-9]+\\.[0-9]+)$", var.vault_cli_version))
error_message = "Vault version must be in the format 0.0.0 or latest"
}
}
data "coder_workspace" "me" {}
resource "coder_script" "vault" {
agent_id = var.agent_id
display_name = "Vault (Token)"
icon = "/icon/vault.svg"
script = templatefile("${path.module}/run.sh", {
INSTALL_VERSION : var.vault_cli_version,
})
run_on_start = true
start_blocks_login = true
}
resource "coder_env" "vault_addr" {
agent_id = var.agent_id
name = "VAULT_ADDR"
value = var.vault_addr
}
resource "coder_env" "vault_token" {
agent_id = var.agent_id
name = "VAULT_TOKEN"
value = var.vault_token
}

93
vault-token/run.sh Normal file
View File

@@ -0,0 +1,93 @@
#!/usr/bin/env bash
# Convert all templated variables to shell variables
INSTALL_VERSION=${INSTALL_VERSION}
fetch() {
dest="$1"
url="$2"
if command -v curl > /dev/null 2>&1; then
curl -sSL --fail "$${url}" -o "$${dest}"
elif command -v wget > /dev/null 2>&1; then
wget -O "$${dest}" "$${url}"
elif command -v busybox > /dev/null 2>&1; then
busybox wget -O "$${dest}" "$${url}"
else
printf "curl, wget, or busybox is not installed. Please install curl or wget in your image.\n"
return 1
fi
}
unzip_safe() {
if command -v unzip > /dev/null 2>&1; then
command unzip "$@"
elif command -v busybox > /dev/null 2>&1; then
busybox unzip "$@"
else
printf "unzip or busybox is not installed. Please install unzip in your image.\n"
return 1
fi
}
install() {
# Fetch the latest version of Vault if INSTALL_VERSION is 'latest'
if [ "$${INSTALL_VERSION}" = "latest" ]; then
LATEST_VERSION=$(curl -s https://releases.hashicorp.com/vault/ | grep -v '-rc' | grep -oP 'vault/\K[0-9]+\.[0-9]+\.[0-9]+' | sort -V | tail -n 1)
printf "Latest version of Vault is %s.\n\n" "$${LATEST_VERSION}"
if [ -z "$${LATEST_VERSION}" ]; then
printf "Failed to determine the latest Vault version.\n"
return 1
fi
INSTALL_VERSION=$${LATEST_VERSION}
fi
# Check if the vault CLI is installed and has the correct version
installation_needed=1
if command -v vault > /dev/null 2>&1; then
CURRENT_VERSION=$(vault version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
if [ "$${CURRENT_VERSION}" = "$${INSTALL_VERSION}" ]; then
printf "Vault version %s is already installed and up-to-date.\n\n" "$${CURRENT_VERSION}"
installation_needed=0
fi
fi
if [ $${installation_needed} -eq 1 ]; then
# Download and install Vault
if [ -z "$${CURRENT_VERSION}" ]; then
printf "Installing Vault CLI ...\n\n"
else
printf "Upgrading Vault CLI from version %s to %s ...\n\n" "$${CURRENT_VERSION}" "${INSTALL_VERSION}"
fi
fetch vault.zip "https://releases.hashicorp.com/vault/$${INSTALL_VERSION}/vault_$${INSTALL_VERSION}_linux_amd64.zip"
if [ $? -ne 0 ]; then
printf "Failed to download Vault.\n"
return 1
fi
if ! unzip_safe vault.zip; then
printf "Failed to unzip Vault.\n"
return 1
fi
rm vault.zip
if sudo mv vault /usr/local/bin/vault 2> /dev/null; then
printf "Vault installed successfully!\n\n"
else
mkdir -p ~/.local/bin
if ! mv vault ~/.local/bin/vault; then
printf "Failed to move Vault to local bin.\n"
return 1
fi
printf "Please add ~/.local/bin to your PATH to use vault CLI.\n"
fi
fi
return 0
}
TMP=$(mktemp -d)
if ! (
cd "$TMP"
install
); then
echo "Failed to install Vault CLI."
exit 1
fi
rm -rf "$TMP"

View File

@@ -13,9 +13,10 @@ Add a button to open any workspace with a single click.
Uses the [Coder Remote VS Code Extension](https://github.com/coder/vscode-coder).
```hcl
```tf
module "vscode" {
source = "https://registry.coder.com/modules/vscode-desktop"
source = "registry.coder.com/modules/vscode-desktop/coder"
version = "1.0.2"
agent_id = coder_agent.example.id
}
```
@@ -24,10 +25,11 @@ module "vscode" {
### Open in a specific directory
```hcl
```tf
module "vscode" {
source = "https://registry.coder.com/modules/vscode-desktop"
source = "registry.coder.com/modules/vscode-desktop/coder"
version = "1.0.2"
agent_id = coder_agent.example.id
folder = "/home/coder/project"
folder = "/home/coder/project"
}
```

View File

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

View File

@@ -11,9 +11,10 @@ tags: [helper, ide, vscode, web]
Automatically install [Visual Studio Code Server](https://code.visualstudio.com/docs/remote/vscode-server) in a workspace using the [VS Code CLI](https://code.visualstudio.com/docs/editor/command-line) and create an app to access it via the dashboard.
```hcl
```tf
module "vscode-web" {
source = "https://registry.coder.com/modules/vscode-web"
source = "registry.coder.com/modules/vscode-web/coder"
version = "1.0.3"
agent_id = coder_agent.example.id
accept_license = true
}
@@ -25,12 +26,13 @@ module "vscode-web" {
### Install VS Code Web to a custom folder
```hcl
```tf
module "vscode-web" {
source = "https://registry.coder.com/modules/vscode-web"
agent_id = coder_agent.example.id
install_dir = "/home/coder/.vscode-web"
folder = "/home/coder"
accept_license = true
source = "registry.coder.com/modules/vscode-web/coder"
version = "1.0.3"
agent_id = coder_agent.example.id
install_dir = "/home/coder/.vscode-web"
folder = "/home/coder"
accept_license = true
}
```

View File

@@ -20,12 +20,33 @@ variable "port" {
default = 13338
}
variable "display_name" {
type = string
description = "The display name for the VS Code Web application."
default = "VS Code Web"
}
variable "slug" {
type = string
description = "The slug for the VS Code Web application."
default = "vscode-web"
}
variable "folder" {
type = string
description = "The folder to open in vscode-web."
default = ""
}
variable "share" {
type = string
default = "owner"
validation {
condition = var.share == "owner" || var.share == "authenticated" || var.share == "public"
error_message = "Incorrect value. Please set either 'owner', 'authenticated', or 'public'."
}
}
variable "log_path" {
type = string
description = "The path to log."
@@ -62,12 +83,12 @@ resource "coder_script" "vscode-web" {
resource "coder_app" "vscode-web" {
agent_id = var.agent_id
slug = "vscode-web"
display_name = "VS Code Web"
slug = var.slug
display_name = var.display_name
url = var.folder == "" ? "http://localhost:${var.port}" : "http://localhost:${var.port}?folder=${var.folder}"
icon = "/icon/code.svg"
subdomain = true
share = "owner"
share = var.share
healthcheck {
url = "http://localhost:${var.port}/healthz"

View File

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