Compare commits

...

8 Commits

Author SHA1 Message Date
Muhammad Atif Ali 1b14b05cb9 chore: use lowercase for namespaces 2025-08-28 22:09:58 +05:00
Jash Ambaliya 35e64f2e4a feat(pgadmin): add new module for pgAdmin (#228)
Co-authored-by: DevCats <christofer@coder.com>
Co-authored-by: Atif Ali <atif@coder.com>
2025-08-28 22:32:27 +05:30
blink-so[bot] 65edb54e88 Add template scaffolding script and enhance module script (#395)
Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com>
Co-authored-by: matifali <104543375+matifali@users.noreply.github.com>
Co-authored-by: DevCats <christofer@coder.com>
Co-authored-by: Atif Ali <atif@coder.com>
2025-08-28 20:42:05 +05:00
blink-so[bot] c270edfdab docs: add Premium license warning for externally-managed-workspace template (#397)
Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com>
Co-authored-by: M Atif Ali <U04T3LN8ASU+AtifCoder@users.noreply.github.com>
Co-authored-by: Atif Ali <atif@coder.com>
2025-08-28 11:14:57 +00:00
Kacper Sawicki f712d1c55b feat: add template for externally managed workspaces to coder-labs (#343)
## Description

Add externally-managed-workspace template for connecting Coder
workspaces to externally provisioned compute resources

## Type of Change

- [ ] New module
- [ ] Bug fix
- [x] Feature/enhancement
- [ ] Documentation
- [ ] Other

## Testing & Validation

- [x] Tests pass (`bun test`)
- [x] Code formatted (`bun run fmt`)
- [x] Changes tested locally

## Related Issues

https://github.com/coder/coder/issues/19091
2025-08-28 10:33:29 +00:00
Atif Ali bc383a32f3 chore: add AGENTS.md (#393)
## Summary
- Adds comprehensive AGENTS.md documentation for AI coding assistants
- Provides guidance on project structure, development commands, and
testing workflows
- Includes specific instructions for Terraform module development and
validation

## Test plan
- [ ] Validate document formatting and structure
- [ ] Verify all referenced commands work correctly
- [ ] Test that instructions align with existing project workflows

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: DevelopmentCats <christofer@coder.com>
2025-08-27 21:02:24 -05:00
blink-so[bot] a9b015044f Update coder-login module to use coder_env resources (#389)
This PR updates the `coder-login` module to use `coder_env` resources
instead of shell scripts for better security, maintainability, and
native Terraform integration.

## Changes
- **Replaced `coder_script` with `coder_env` resources**: Uses native
Terraform provider resources instead of shell scripts
- **Removed `run.sh` script**: Eliminated the need for external shell
scripts
- **Environment variables**: Sets `CODER_SESSION_TOKEN` and `CODER_URL`
using `coder_env` resources
- **Added comprehensive tests**: Includes Terraform tests with mocked
data validation
- **Version bump**: Updated module version from `v1.0.31` to `v1.1.0`
(minor bump)

## Benefits
- **Native Terraform approach**: Uses the provider's built-in resources
instead of external scripts
- **Better security**: Environment variables are set directly by
Terraform without shell script interpolation
- **Improved maintainability**: Cleaner, more declarative configuration
- **Proper testing**: Comprehensive test coverage with mocked data
sources
- **Correct environment variables**: Uses `CODER_SESSION_TOKEN` and
`CODER_URL` as per coder CLI documentation

## Testing
- All Terraform tests pass successfully
- Module validates correctly with `terraform validate`
- Proper formatting verified with `terraform fmt`

Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com>
Co-authored-by: Atif Ali <atif@coder.com>
Co-authored-by: Cian Johnston <public@cianjohnston.ie>
2025-08-26 21:33:00 -05:00
Michael Smith e94dfd2df6 fix: add validation for Github-Flavored Markdown Alerts (#394)
No issue to link – this was a problem we discovered while updating the
Registry website

## Description

This PR adds (very) basic validation for the GitHub Flavored Markdown
alerts that we allow contributors to add to their README files. The
errors that get generated should be correct, but the error messages
themselves aren't as helpful as they could be. I'm going to be handling
that in a separate PR, just so we can get this one in sooner.

### Changes made
- Added function for validating the core structure of all GFM alerts
- Updated existing README files that were failing the new validation
requirements

## Type of Change

- [ ] New module
- [x] Bug fix
- [ ] Feature/enhancement
- [ ] Documentation
- [ ] Other
2025-08-26 20:52:22 -05:00
29 changed files with 1032 additions and 33 deletions
+1
View File
@@ -0,0 +1 @@
../AGENTS.md
+1
View File
@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><text x="50%" y="50%" font-size="96px" text-anchor="middle" dominant-baseline="middle" font-family="Apple Color Emoji, Segoe UI Emoji, Noto Color Emoji, sans-serif">🔌</text></svg>

After

Width:  |  Height:  |  Size: 247 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="64" viewBox="0 0 25.6 25.6" width="64"><style><![CDATA[.B{stroke-linecap:round}.C{stroke-linejoin:round}.D{stroke-linejoin:miter}.E{stroke-width:.716}]]></style><g fill="none" stroke="#fff"><path d="M18.983 18.636c.163-1.357.114-1.555 1.124-1.336l.257.023c.777.035 1.793-.125 2.4-.402 1.285-.596 2.047-1.592.78-1.33-2.89.596-3.1-.383-3.1-.383 3.053-4.53 4.33-10.28 3.227-11.687-3.004-3.84-8.205-2.024-8.292-1.976l-.028.005c-.57-.12-1.2-.19-1.93-.2-1.308-.02-2.3.343-3.054.914 0 0-9.277-3.822-8.846 4.807.092 1.836 2.63 13.9 5.66 10.25C8.29 15.987 9.36 14.86 9.36 14.86c.53.353 1.167.533 1.834.468l.052-.044a2.01 2.01 0 0 0 .021.518c-.78.872-.55 1.025-2.11 1.346-1.578.325-.65.904-.046 1.056.734.184 2.432.444 3.58-1.162l-.046.183c.306.245.285 1.76.33 2.842s.116 2.093.337 2.688.48 2.13 2.53 1.7c1.713-.367 3.023-.896 3.143-5.81" fill="#000" stroke="#000" stroke-linecap="butt" stroke-width="2.149" class="D"/><path d="M23.535 15.6c-2.89.596-3.1-.383-3.1-.383 3.053-4.53 4.33-10.28 3.228-11.687-3.004-3.84-8.205-2.023-8.292-1.976l-.028.005a10.31 10.31 0 0 0-1.929-.201c-1.308-.02-2.3.343-3.054.914 0 0-9.278-3.822-8.846 4.807.092 1.836 2.63 13.9 5.66 10.25C8.29 15.987 9.36 14.86 9.36 14.86c.53.353 1.167.533 1.834.468l.052-.044a2.02 2.02 0 0 0 .021.518c-.78.872-.55 1.025-2.11 1.346-1.578.325-.65.904-.046 1.056.734.184 2.432.444 3.58-1.162l-.046.183c.306.245.52 1.593.484 2.815s-.06 2.06.18 2.716.48 2.13 2.53 1.7c1.713-.367 2.6-1.32 2.725-2.906.088-1.128.286-.962.3-1.97l.16-.478c.183-1.53.03-2.023 1.085-1.793l.257.023c.777.035 1.794-.125 2.39-.402 1.285-.596 2.047-1.592.78-1.33z" fill="#336791" stroke="none"/><g class="E"><g class="B"><path d="M12.814 16.467c-.08 2.846.02 5.712.298 6.4s.875 2.05 2.926 1.612c1.713-.367 2.337-1.078 2.607-2.647l.633-5.017M10.356 2.2S1.072-1.596 1.504 7.033c.092 1.836 2.63 13.9 5.66 10.25C8.27 15.95 9.27 14.907 9.27 14.907m6.1-13.4c-.32.1 5.164-2.005 8.282 1.978 1.1 1.407-.175 7.157-3.228 11.687" class="C"/><path d="M20.425 15.17s.2.98 3.1.382c1.267-.262.504.734-.78 1.33-1.054.49-3.418.615-3.457-.06-.1-1.745 1.244-1.215 1.147-1.652-.088-.394-.69-.78-1.086-1.744-.347-.84-4.76-7.29 1.224-6.333.22-.045-1.56-5.7-7.16-5.782S7.99 8.196 7.99 8.196" stroke-linejoin="bevel"/></g><g class="C"><path d="M11.247 15.768c-.78.872-.55 1.025-2.11 1.346-1.578.325-.65.904-.046 1.056.734.184 2.432.444 3.58-1.163.35-.49-.002-1.27-.482-1.468-.232-.096-.542-.216-.94.23z"/><path d="M11.196 15.753c-.08-.513.168-1.122.433-1.836.398-1.07 1.316-2.14.582-5.537-.547-2.53-4.22-.527-4.22-.184s.166 1.74-.06 3.365c-.297 2.122 1.35 3.916 3.246 3.733" class="B"/></g></g><g fill="#fff" class="D"><path d="M10.322 8.145c-.017.117.215.43.516.472s.558-.202.575-.32-.215-.246-.516-.288-.56.02-.575.136z" stroke-width=".239"/><path d="M19.486 7.906c.016.117-.215.43-.516.472s-.56-.202-.575-.32.215-.246.516-.288.56.02.575.136z" stroke-width=".119"/></g><path d="M20.562 7.095c.05.92-.198 1.545-.23 2.524-.046 1.422.678 3.05-.413 4.68" class="B C E"/></g></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

+168
View File
@@ -0,0 +1,168 @@
# AGENTS.md
This file provides guidance to AI coding assistants when working with code in this repository.
## Project Overview
The Coder Registry is a community-driven repository for Terraform modules and templates that extend Coder workspaces. It's organized with:
- **Modules**: Individual components and tools (IDEs, auth integrations, dev tools)
- **Templates**: Complete workspace configurations for different platforms
- **Namespaces**: Each contributor has their own namespace under `/registry/[namespace]/`
## Common Development Commands
### Formatting
```bash
bun run fmt # Format all code (Prettier + Terraform)
bun run fmt:ci # Check formatting (CI mode)
```
### Testing
```bash
# Test all modules with .tftest.hcl files
bun run test
# Test specific module (from module directory)
terraform init -upgrade
terraform test -verbose
# Validate Terraform syntax
./scripts/terraform_validate.sh
```
### Module Creation
```bash
# Generate new module scaffold
./scripts/new_module.sh namespace/module-name
```
### TypeScript Testing & Setup
The repository uses Bun for TypeScript testing with utilities:
- `test/test.ts` - Testing utilities for container management and Terraform operations
- `setup.ts` - Test cleanup (removes .tfstate files and test containers)
- Container-based testing with Docker for module validation
## Architecture & Organization
### Directory Structure
```
registry/[namespace]/
├── README.md # Contributor info with frontmatter
├── .images/ # Namespace avatar (avatar.png/svg)
├── modules/ # Individual components
│ └── [module]/ # Each module has main.tf, README.md, tests
└── templates/ # Complete workspace configs
└── [template]/ # Each template has main.tf, README.md
```
### Key Components
**Module Structure**: Each module contains:
- `main.tf` - Terraform implementation
- `README.md` - Documentation with YAML frontmatter
- `.tftest.hcl` - Terraform test files (required)
- `run.sh` - Optional startup script
**Template Structure**: Each template contains:
- `main.tf` - Complete Coder template configuration
- `README.md` - Documentation with YAML frontmatter
- Additional configs, scripts as needed
### README Frontmatter Requirements
All modules/templates require YAML frontmatter:
```yaml
---
display_name: "Module Name"
description: "Brief description"
icon: "../../../../.icons/tool.svg"
verified: false
tags: ["tag1", "tag2"]
---
```
## Testing Requirements
### Module Testing
- Every module MUST have `.tftest.hcl` test files
- Optional `main.test.ts` files for container-based testing or complex business logic validation
- Tests use Docker containers with `--network=host` flag
- Linux required for testing (Docker Desktop on macOS/Windows won't work)
- Use Colima or OrbStack on macOS instead of Docker Desktop
### Test Utilities
The `test/test.ts` file provides:
- `runTerraformApply()` - Execute Terraform with variables
- `executeScriptInContainer()` - Run coder_script resources in containers
- `testRequiredVariables()` - Validate required variables
- Container management functions
## Validation & Quality
### Automated Validation
The Go validation tool (`cmd/readmevalidation/`) checks:
- Repository structure integrity
- Contributor README files
- Module and template documentation
- Frontmatter format compliance
### Versioning
Use semantic versioning for modules:
- **Patch** (1.2.3 → 1.2.4): Bug fixes
- **Minor** (1.2.3 → 1.3.0): New features, adding inputs
- **Major** (1.2.3 → 2.0.0): Breaking changes
## Dependencies & Tools
### Required Tools
- **Terraform** - Module development and testing
- **Docker** - Container-based testing
- **Bun** - JavaScript runtime for formatting/scripts
- **Go 1.23+** - Validation tooling
### Development Dependencies
- Prettier with Terraform and shell plugins
- TypeScript for test utilities
- Various npm packages for documentation processing
## Workflow Notes
### Contributing Process
1. Create namespace (first-time contributors)
2. Generate module/template files using scripts
3. Implement functionality and tests
4. Run formatting and validation
5. Submit PR with appropriate template
### Testing Workflow
- All modules must pass `terraform test`
- Use `bun run test` for comprehensive testing
- Format code with `bun run fmt` before submission
- Manual testing recommended for templates
### Namespace Management
- Each contributor gets unique namespace
- Namespace avatar required (avatar.png/svg in .images/)
- Namespace README with contributor info and frontmatter
Symlink
+1
View File
@@ -0,0 +1 @@
AGENTS.md
+3
View File
@@ -94,6 +94,9 @@ func validateCoderModuleReadme(rm coderResourceReadme) []error {
for _, err := range validateCoderModuleReadmeBody(rm.body) {
errs = append(errs, addFilePathToError(rm.filePath, err))
}
for _, err := range validateResourceGfmAlerts(rm.body) {
errs = append(errs, addFilePathToError(rm.filePath, err))
}
if fmErrs := validateCoderResourceFrontmatter("modules", rm.filePath, rm.frontmatter); len(fmErrs) != 0 {
errs = append(errs, fmErrs...)
}
+76
View File
@@ -1,6 +1,7 @@
package main
import (
"bufio"
"errors"
"net/url"
"os"
@@ -16,11 +17,16 @@ import (
var (
supportedResourceTypes = []string{"modules", "templates"}
operatingSystems = []string{"windows", "macos", "linux"}
gfmAlertTypes = []string{"NOTE", "IMPORTANT", "CAUTION", "WARNING", "TIP"}
// TODO: This is a holdover from the validation logic used by the Coder Modules repo. It gives us some assurance, but
// realistically, we probably want to parse any Terraform code snippets, and make some deeper guarantees about how it's
// structured. Just validating whether it *can* be parsed as Terraform would be a big improvement.
terraformVersionRe = regexp.MustCompile(`^\s*\bversion\s+=`)
// Matches the format "> [!INFO]". Deliberately using a broad pattern to catch formatting issues that can mess up
// the renderer for the Registry website
gfmAlertRegex = regexp.MustCompile(`^>(\s*)\[!(\w+)\](\s*)(.*)`)
)
type coderResourceFrontmatter struct {
@@ -277,3 +283,73 @@ func aggregateCoderResourceReadmeFiles(resourceType string) ([]readme, error) {
}
return allReadmeFiles, nil
}
func validateResourceGfmAlerts(readmeBody string) []error {
trimmed := strings.TrimSpace(readmeBody)
if trimmed == "" {
return nil
}
var errs []error
var sourceLine string
isInsideGfmQuotes := false
isInsideCodeBlock := false
lineScanner := bufio.NewScanner(strings.NewReader(trimmed))
for lineScanner.Scan() {
sourceLine = lineScanner.Text()
if strings.HasPrefix(sourceLine, "```") {
isInsideCodeBlock = !isInsideCodeBlock
continue
}
if isInsideCodeBlock {
continue
}
isInsideGfmQuotes = isInsideGfmQuotes && strings.HasPrefix(sourceLine, "> ")
currentMatch := gfmAlertRegex.FindStringSubmatch(sourceLine)
if currentMatch == nil {
continue
}
// Nested GFM alerts is such a weird mistake that it's probably not really safe to keep trying to process the
// rest of the content, so this will prevent any other validations from happening for the given line
if isInsideGfmQuotes {
errs = append(errs, errors.New("registry does not support nested GFM alerts"))
continue
}
leadingWhitespace := currentMatch[1]
if len(leadingWhitespace) != 1 {
errs = append(errs, errors.New("GFM alerts must have one space between the '>' and the start of the GFM brackets"))
}
isInsideGfmQuotes = true
alertHeader := currentMatch[2]
upperHeader := strings.ToUpper(alertHeader)
if !slices.Contains(gfmAlertTypes, upperHeader) {
errs = append(errs, xerrors.Errorf("GFM alert type %q is not supported", alertHeader))
}
if alertHeader != upperHeader {
errs = append(errs, xerrors.Errorf("GFM alerts must be in all caps"))
}
trailingWhitespace := currentMatch[3]
if trailingWhitespace != "" {
errs = append(errs, xerrors.Errorf("GFM alerts must not have any trailing whitespace after the closing bracket"))
}
extraContent := currentMatch[4]
if extraContent != "" {
errs = append(errs, xerrors.Errorf("GFM alerts must not have any extra content on the same line"))
}
}
if gfmAlertRegex.Match([]byte(sourceLine)) {
errs = append(errs, xerrors.Errorf("README has an incomplete GFM alert at the end of the file"))
}
return errs
}
+3
View File
@@ -70,6 +70,9 @@ func validateCoderTemplateReadme(rm coderResourceReadme) []error {
for _, err := range validateCoderTemplateReadmeBody(rm.body) {
errs = append(errs, addFilePathToError(rm.filePath, err))
}
for _, err := range validateResourceGfmAlerts(rm.body) {
errs = append(errs, addFilePathToError(rm.filePath, err))
}
if fmErrs := validateCoderResourceFrontmatter("templates", rm.filePath, rm.frontmatter); len(fmErrs) != 0 {
errs = append(errs, fmErrs...)
}
+33
View File
@@ -0,0 +1,33 @@
---
display_name: NAMESPACE_NAME
bio: Brief description of what this namespace provides
github: your-github-username
avatar: ./.images/avatar.svg
linkedin: https://www.linkedin.com/in/your-profile
website: https://your-website.com
status: community
---
# NAMESPACE_NAME
Brief description of what this namespace provides. Include information about:
- What types of templates/modules you offer
- Your focus areas (e.g., specific cloud providers, technologies)
- Any special features or configurations
## Templates
List your available templates here:
- **template-name**: Brief description
## Modules
List your available modules here:
- **module-name**: Brief description
## Contributing
If you'd like to contribute to this namespace, please [open an issue](https://github.com/coder/registry/issues) or submit a pull request.
+58
View File
@@ -0,0 +1,58 @@
---
name: TEMPLATE_NAME
description: A brief description of what this template does
tags: [tag1, tag2, tag3]
icon: /icon/TEMPLATE_NAME.svg
---
# TEMPLATE_NAME
A brief description of what this template provides and its use case.
## Features
- Feature 1
- Feature 2
- Feature 3
## Requirements
- List any prerequisites or requirements
- Provider-specific requirements (e.g., Docker, AWS credentials)
- Minimum Coder version if applicable
## Usage
1. Step-by-step instructions on how to use this template
2. Any configuration that needs to be done
3. How to customize the template
## Variables
| Name | Description | Type | Default | Required |
| ----------- | --------------------------- | -------- | ----------------- | -------- |
| example_var | Description of the variable | `string` | `"default_value"` | no |
## Resources Created
- List of resources that will be created
- Brief description of each resource
## Customization
Explain how users can customize this template for their needs:
- How to modify the startup script
- How to add additional software
- How to configure different providers
## Troubleshooting
### Common Issues
- Issue 1 and its solution
- Issue 2 and its solution
## Contributing
Contributions are welcome! Please see the [contributing guidelines](../../CONTRIBUTING.md) for more information.
+172
View File
@@ -0,0 +1,172 @@
terraform {
required_providers {
coder = {
source = "coder/coder"
}
# Add your provider here (e.g., docker, aws, gcp, azure)
# docker = {
# source = "kreuzwerker/docker"
# }
}
}
locals {
username = data.coder_workspace_owner.me.name
}
# Add your variables here
# variable "example_var" {
# default = "default_value"
# description = "Description of the variable"
# type = string
# }
# Configure your provider here
# provider "docker" {
# host = var.docker_socket != "" ? var.docker_socket : null
# }
data "coder_provisioner" "me" {}
data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}
resource "coder_agent" "main" {
arch = data.coder_provisioner.me.arch
os = "linux"
startup_script = <<-EOT
set -e
# Prepare user home with default files on first start.
if [ ! -f ~/.init_done ]; then
cp -rT /etc/skel ~
touch ~/.init_done
fi
# Add any commands that should be executed at workspace startup here
EOT
# These environment variables allow you to make Git commits right away after creating a
# workspace. Note that they take precedence over configuration defined in ~/.gitconfig!
# You can remove this block if you'd prefer to configure Git manually or using
# dotfiles. (see docs/dotfiles.md)
env = {
GIT_AUTHOR_NAME = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name)
GIT_AUTHOR_EMAIL = "${data.coder_workspace_owner.me.email}"
GIT_COMMITTER_NAME = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name)
GIT_COMMITTER_EMAIL = "${data.coder_workspace_owner.me.email}"
}
# The following metadata blocks are optional. They are used to display
# information about your workspace in the dashboard. You can remove them
# if you don't want to display any information.
# For basic templates, you can remove the "display_apps" block.
metadata {
display_name = "CPU Usage"
key = "0_cpu_usage"
script = "coder stat cpu"
interval = 10
timeout = 1
}
metadata {
display_name = "RAM Usage"
key = "1_ram_usage"
script = "coder stat mem"
interval = 10
timeout = 1
}
metadata {
display_name = "Home Disk"
key = "3_home_disk"
script = "coder stat disk --path $${HOME}"
interval = 60
timeout = 1
}
display_apps {
vscode = true
vscode_insiders = false
ssh_helper = false
port_forwarding_helper = true
web_terminal = true
}
}
# Add your resources here (e.g., docker container, VM, etc.)
# resource "docker_image" "main" {
# name = "codercom/enterprise-base:ubuntu"
# }
# resource "docker_container" "workspace" {
# count = data.coder_workspace.me.start_count
# image = docker_image.main.image_id
# # Uses lower() to avoid Docker restriction on container names.
# name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
# # Hostname makes the shell more user friendly: coder@my-workspace:~$
# hostname = data.coder_workspace.me.name
# # Use the docker gateway if the access URL is 127.0.0.1
# entrypoint = ["sh", "-c", replace(coder_agent.main.init_script, "/localhost|127\.0\.0\.1/", "host.docker.internal")]
# env = ["CODER_AGENT_TOKEN=${coder_agent.main.token}"]
# host {
# host = "host.docker.internal"
# ip = "host-gateway"
# }
# volumes {
# container_path = "/home/${local.username}"
# volume_name = docker_volume.home_volume[0].name
# read_only = false
# }
# # Add labels in Docker to keep track of orphan resources.
# labels {
# label = "coder.owner"
# value = data.coder_workspace_owner.me.name
# }
# labels {
# label = "coder.owner_id"
# value = data.coder_workspace_owner.me.id
# }
# labels {
# label = "coder.workspace_id"
# value = data.coder_workspace.me.id
# }
# labels {
# label = "coder.workspace_name"
# value = data.coder_workspace.me.name
# }
# }
# resource "docker_volume" "home_volume" {
# count = data.coder_workspace.me.start_count
# name = "coder-${data.coder_workspace_owner.me.name}-${data.coder_workspace.me.name}-home"
# # Protect the volume from being deleted due to changes in attributes.
# lifecycle {
# ignore_changes = all
# }
# # Add labels in Docker to keep track of orphan resources.
# labels {
# label = "coder.owner"
# value = data.coder_workspace_owner.me.name
# }
# labels {
# label = "coder.owner_id"
# value = data.coder_workspace_owner.me.id
# }
# labels {
# label = "coder.workspace_id"
# value = data.coder_workspace.me.id
# }
# labels {
# label = "coder.workspace_name"
# value = data.coder_workspace.me.name
# }
# }
resource "coder_metadata" "workspace_info" {
resource_id = coder_agent.main.id
item {
key = "TEMPLATE_NAME"
value = "TEMPLATE_NAME"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

+7
View File
@@ -0,0 +1,7 @@
---
display_name: Jash
bio: Coder user and contributor.
github: AJ0070
avatar: ./.images/avatar.png
status: community
---
+23
View File
@@ -0,0 +1,23 @@
---
display_name: "pgAdmin"
description: "A web-based interface for managing PostgreSQL databases in your Coder workspace."
icon: "../../../../.icons/pgadmin.svg"
maintainer_github: "AJ0070"
verified: false
tags: ["database", "postgres", "pgadmin", "web-ide"]
---
# pgAdmin
This module adds a pgAdmin app to your Coder workspace, providing a powerful web-based interface for managing PostgreSQL databases.
It can be served on a Coder subdomain for easy access, or on `localhost` if you prefer to use port-forwarding.
```tf
module "pgadmin" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/aj0070/pgadmin/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
}
```
@@ -0,0 +1,10 @@
import { describe } from "bun:test";
import { runTerraformInit, testRequiredVariables } from "~test";
describe("pgadmin", async () => {
await runTerraformInit(import.meta.dir);
testRequiredVariables(import.meta.dir, {
agent_id: "foo",
});
});
+108
View File
@@ -0,0 +1,108 @@
terraform {
required_providers {
coder = {
source = "coder/coder"
}
}
}
variable "agent_id" {
type = string
description = "The agent to install pgAdmin on."
}
variable "port" {
type = number
description = "The port to run pgAdmin on."
default = 5050
}
variable "subdomain" {
type = bool
description = "If true, the app will be served on a subdomain."
default = true
}
variable "config" {
type = any
description = "A map of pgAdmin configuration settings."
default = {
DEFAULT_EMAIL = "admin@coder.com"
DEFAULT_PASSWORD = "coderPASSWORD"
SERVER_MODE = false
MASTER_PASSWORD_REQUIRED = false
LISTEN_ADDRESS = "127.0.0.1"
}
}
data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}
resource "coder_app" "pgadmin" {
count = data.coder_workspace.me.start_count
agent_id = var.agent_id
display_name = "pgAdmin"
slug = "pgadmin"
icon = "/icon/pgadmin.svg"
url = local.url
subdomain = var.subdomain
share = "owner"
healthcheck {
url = local.healthcheck_url
interval = 5
threshold = 6
}
}
resource "coder_script" "pgadmin" {
agent_id = var.agent_id
display_name = "Install and run pgAdmin"
icon = "/icon/pgadmin.svg"
run_on_start = true
script = templatefile("${path.module}/run.sh", {
PORT = var.port,
LOG_PATH = "/tmp/pgadmin.log",
SERVER_BASE_PATH = local.server_base_path,
CONFIG = local.config_content,
PGADMIN_DATA_DIR = local.pgadmin_data_dir,
PGADMIN_LOG_DIR = local.pgadmin_log_dir,
PGADMIN_VENV_DIR = local.pgadmin_venv_dir
})
}
locals {
server_base_path = var.subdomain ? "" : format("/@%s/%s/apps/%s", data.coder_workspace_owner.me.name, data.coder_workspace.me.name, "pgadmin")
url = "http://localhost:${var.port}${local.server_base_path}"
healthcheck_url = "http://localhost:${var.port}${local.server_base_path}/"
# pgAdmin data directories (user-local paths)
pgadmin_data_dir = "$HOME/.pgadmin"
pgadmin_log_dir = "$HOME/.pgadmin/logs"
pgadmin_venv_dir = "$HOME/.pgadmin/venv"
base_config = merge(var.config, {
LISTEN_PORT = var.port
# Override paths for user installation
DATA_DIR = local.pgadmin_data_dir
LOG_FILE = "${local.pgadmin_log_dir}/pgadmin4.log"
SQLITE_PATH = "${local.pgadmin_data_dir}/pgadmin4.db"
SESSION_DB_PATH = "${local.pgadmin_data_dir}/sessions"
STORAGE_DIR = "${local.pgadmin_data_dir}/storage"
# Disable initial setup prompts for automated deployment
SETUP_AUTH = false
})
config_with_path = var.subdomain ? local.base_config : merge(local.base_config, {
APPLICATION_ROOT = local.server_base_path
})
config_content = join("\n", [
for key, value in local.config_with_path :
format("%s = %s", key,
can(regex("^(true|false)$", tostring(value))) ? (value ? "True" : "False") :
can(tonumber(value)) ? tostring(value) :
format("'%s'", tostring(value))
)
])
}
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
set -euo pipefail
PORT=${PORT}
LOG_PATH=${LOG_PATH}
SERVER_BASE_PATH=${SERVER_BASE_PATH}
BOLD='\033[0;1m'
printf "$${BOLD}Installing pgAdmin!\n"
# Check if Python 3 is available
if ! command -v python3 > /dev/null 2>&1; then
echo "⚠️ Warning: Python 3 is not installed. Please install Python 3 before using this module."
exit 0
fi
# Setup pgAdmin directories (from Terraform configuration)
PGADMIN_DATA_DIR="${PGADMIN_DATA_DIR}"
PGADMIN_LOG_DIR="${PGADMIN_LOG_DIR}"
PGADMIN_VENV_DIR="${PGADMIN_VENV_DIR}"
printf "Setting up pgAdmin directories...\n"
mkdir -p "$PGADMIN_DATA_DIR"
mkdir -p "$PGADMIN_LOG_DIR"
# Check if pgAdmin virtual environment already exists and is working
if [ -f "$PGADMIN_VENV_DIR/bin/pgadmin4" ] && [ -f "$PGADMIN_VENV_DIR/bin/activate" ]; then
printf "🥳 pgAdmin virtual environment already exists\n\n"
else
printf "Creating Python virtual environment for pgAdmin...\n"
if ! python3 -m venv "$PGADMIN_VENV_DIR"; then
echo "⚠️ Warning: Failed to create virtual environment"
exit 0
fi
printf "Installing pgAdmin 4 in virtual environment...\n"
if ! "$PGADMIN_VENV_DIR/bin/pip" install pgadmin4; then
echo "⚠️ Warning: Failed to install pgAdmin4"
exit 0
fi
printf "🥳 pgAdmin has been installed successfully\n\n"
fi
printf "$${BOLD}Configuring pgAdmin...\n"
if [ -f "$PGADMIN_VENV_DIR/bin/pgadmin4" ]; then
# pgAdmin installs to a predictable location in the virtual environment
PYTHON_VERSION=$("$PGADMIN_VENV_DIR/bin/python" -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')")
PGADMIN_INSTALL_DIR="$PGADMIN_VENV_DIR/lib/python$PYTHON_VERSION/site-packages/pgadmin4"
# Create pgAdmin config file in the correct location (next to config.py)
cat > "$PGADMIN_INSTALL_DIR/config_local.py" << EOF
# pgAdmin configuration for Coder workspace
${CONFIG}
EOF
printf "📄 Config written to $PGADMIN_INSTALL_DIR/config_local.py\n"
printf "$${BOLD}Starting pgAdmin in background...\n"
printf "📝 Check logs at $${LOG_PATH}\n"
printf "🌐 Serving at http://localhost:${PORT}${SERVER_BASE_PATH}\n"
# Create required directories
mkdir -p "$PGADMIN_DATA_DIR/sessions"
mkdir -p "$PGADMIN_DATA_DIR/storage"
# Start pgadmin4 from the virtual environment with proper environment
cd "$PGADMIN_DATA_DIR"
PYTHONPATH="$PGADMIN_INSTALL_DIR:$${PYTHONPATH:-}" "$PGADMIN_VENV_DIR/bin/pgadmin4" > "$${LOG_PATH}" 2>&1 &
else
printf "⚠️ Warning: pgAdmin4 virtual environment not found\n"
printf "📝 Installation may have failed - check logs above\n"
fi
@@ -0,0 +1,16 @@
---
display_name: Externally Managed Workspace
description: A template to provision externally managed resources as Coder workspaces
icon: ../../../../.icons/electric-plug-emoji.svg
verified: true
tags: [external]
---
# Externally Managed Workspace Template
> [!IMPORTANT]
> External agents require a [Premium](https://coder.com/pricing) Coder license.
This template provides a minimal scaffolding for creating Coder workspaces that connect to externally provisioned compute resources.
Use this template as a starting point to build your own custom templates for scenarios where you need to connect to existing infrastructure.
@@ -0,0 +1,74 @@
terraform {
required_providers {
coder = {
source = "coder/coder"
version = ">= 2.10"
}
}
}
data "coder_parameter" "agent_config" {
name = "agent_config"
display_name = "Agent Configuration"
description = "Select the operating system and architecture combination for the agent"
type = "string"
default = "linux-amd64"
option {
name = "Linux AMD64"
value = "linux-amd64"
}
option {
name = "Linux ARM64"
value = "linux-arm64"
}
option {
name = "Linux ARMv7"
value = "linux-armv7"
}
option {
name = "Windows AMD64"
value = "windows-amd64"
}
option {
name = "Windows ARM64"
value = "windows-arm64"
}
option {
name = "macOS AMD64"
value = "darwin-amd64"
}
option {
name = "macOS ARM64 (Apple Silicon)"
value = "darwin-arm64"
}
}
data "coder_workspace" "me" {}
locals {
agent_config = split("-", data.coder_parameter.agent_config.value)
agent_os = local.agent_config[0]
agent_arch = local.agent_config[1]
}
resource "coder_agent" "main" {
arch = local.agent_arch
os = local.agent_os
}
resource "coder_external_agent" "main" {
agent_id = coder_agent.main.id
}
# Adds code-server
# See all available modules at https://registry.coder.com/modules
module "code-server" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/code-server/coder"
# This ensures that the latest non-breaking version of the module gets downloaded, you can also pin the module version to prevent breaking changes in production.
version = "~> 1.0"
agent_id = coder_agent.main.id
}
+1 -1
View File
@@ -14,7 +14,7 @@ Automatically logs the user into Coder when creating their workspace.
module "coder-login" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/coder-login/coder"
version = "1.0.31"
version = "1.1.0"
agent_id = coder_agent.example.id
}
```
+8 -9
View File
@@ -17,15 +17,14 @@ variable "agent_id" {
data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}
resource "coder_script" "coder-login" {
resource "coder_env" "coder_session_token" {
agent_id = var.agent_id
script = templatefile("${path.module}/run.sh", {
CODER_USER_TOKEN : data.coder_workspace_owner.me.session_token,
CODER_DEPLOYMENT_URL : data.coder_workspace.me.access_url
})
display_name = "Coder Login"
icon = "/icon/coder.svg"
run_on_start = true
start_blocks_login = true
name = "CODER_SESSION_TOKEN"
value = data.coder_workspace_owner.me.session_token
}
resource "coder_env" "coder_url" {
agent_id = var.agent_id
name = "CODER_URL"
value = data.coder_workspace.me.access_url
}
@@ -0,0 +1,65 @@
# Test for coder-login module
run "test_coder_login_module" {
command = plan
variables {
agent_id = "test-agent-id"
}
# Test that the coder_env resources are created with correct configuration
assert {
condition = coder_env.coder_session_token.agent_id == "test-agent-id"
error_message = "CODER_SESSION_TOKEN agent ID should match the input variable"
}
assert {
condition = coder_env.coder_session_token.name == "CODER_SESSION_TOKEN"
error_message = "Environment variable name should be 'CODER_SESSION_TOKEN'"
}
assert {
condition = coder_env.coder_url.agent_id == "test-agent-id"
error_message = "CODER_URL agent ID should match the input variable"
}
assert {
condition = coder_env.coder_url.name == "CODER_URL"
error_message = "Environment variable name should be 'CODER_URL'"
}
}
# Test with mock data sources
run "test_with_mock_data" {
command = plan
variables {
agent_id = "mock-agent"
}
# Mock the data sources for testing
override_data {
target = data.coder_workspace.me
values = {
access_url = "https://coder.example.com"
}
}
override_data {
target = data.coder_workspace_owner.me
values = {
session_token = "mock-session-token"
}
}
# Verify environment variables get the mocked values
assert {
condition = coder_env.coder_url.value == "https://coder.example.com"
error_message = "CODER_URL should match workspace access_url"
}
assert {
condition = coder_env.coder_session_token.value == "mock-session-token"
error_message = "CODER_SESSION_TOKEN should match workspace owner session_token"
}
}
-15
View File
@@ -1,15 +0,0 @@
#!/usr/bin/env sh
# Automatically authenticate the user if they are not
# logged in to another deployment
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}"
else
echo "You are already authenticated with coder."
fi
+12 -3
View File
@@ -99,19 +99,28 @@ describe("kiro", async () => {
it("writes ~/.kiro/settings/mcp.json when mcp provided", async () => {
const id = await runContainer("alpine");
try {
const mcp = JSON.stringify({ servers: { demo: { url: "http://localhost:1234" } } });
const mcp = JSON.stringify({
servers: { demo: { url: "http://localhost:1234" } },
});
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
mcp,
});
const script = findResourceInstance(state, "coder_script", "kiro_mcp").script;
const script = findResourceInstance(
state,
"coder_script",
"kiro_mcp",
).script;
const resp = await execContainer(id, ["sh", "-c", script]);
if (resp.exitCode !== 0) {
console.log(resp.stdout);
console.log(resp.stderr);
}
expect(resp.exitCode).toBe(0);
const content = await readFileContainer(id, "/root/.kiro/settings/mcp.json");
const content = await readFileContainer(
id,
"/root/.kiro/settings/mcp.json",
);
expect(content).toBe(mcp);
} finally {
await removeContainer(id);
+12 -3
View File
@@ -94,19 +94,28 @@ describe("windsurf", async () => {
it("writes ~/.codeium/windsurf/mcp_config.json when mcp provided", async () => {
const id = await runContainer("alpine");
try {
const mcp = JSON.stringify({ servers: { demo: { url: "http://localhost:1234" } } });
const mcp = JSON.stringify({
servers: { demo: { url: "http://localhost:1234" } },
});
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
mcp,
});
const script = findResourceInstance(state, "coder_script", "windsurf_mcp").script;
const script = findResourceInstance(
state,
"coder_script",
"windsurf_mcp",
).script;
const resp = await execContainer(id, ["sh", "-c", script]);
if (resp.exitCode !== 0) {
console.log(resp.stdout);
console.log(resp.stderr);
}
expect(resp.exitCode).toBe(0);
const content = await readFileContainer(id, "/root/.codeium/windsurf/mcp_config.json");
const content = await readFileContainer(
id,
"/root/.codeium/windsurf/mcp_config.json",
);
expect(content).toBe(mcp);
} finally {
await removeContainer(id);
@@ -35,7 +35,7 @@ This means, when the workspace restarts, any tools or files outside of the home
### Persistent VM
> [!IMPORTANT]
> [!IMPORTANT]
> This approach requires the [`az` CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli#install) to be present in the PATH of your Coder Provisioner.
> You will have to do this installation manually as it is not included in our official images.
@@ -35,7 +35,7 @@ This means, when the workspace restarts, any tools or files outside of the data
### Persistent VM
> [!IMPORTANT]
> [!IMPORTANT]
> This approach requires the [`az` CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli#install) to be present in the PATH of your Coder Provisioner.
> You will have to do this installation manually as it is not included in our official images.
+22
View File
@@ -29,6 +29,28 @@ if [ -d "registry/$NAMESPACE/modules/$MODULE_NAME" ]; then
echo "Please choose a different name"
exit 1
fi
# Create namespace directory if it doesn't exist
if [ ! -d "registry/$NAMESPACE" ]; then
echo "Creating namespace directory: registry/$NAMESPACE"
mkdir -p "registry/$NAMESPACE"
# Create namespace README if it doesn't exist
if [ ! -f "registry/$NAMESPACE/README.md" ]; then
echo "Creating namespace README: registry/$NAMESPACE/README.md"
cp "examples/namespace/README.md" "registry/$NAMESPACE/README.md"
# Replace NAMESPACE_NAME placeholder in the README
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
sed -i '' "s/NAMESPACE_NAME/${NAMESPACE}/g" "registry/$NAMESPACE/README.md"
else
# Linux
sed -i "s/NAMESPACE_NAME/${NAMESPACE}/g" "registry/$NAMESPACE/README.md"
fi
fi
fi
mkdir -p "registry/${NAMESPACE}/modules/${MODULE_NAME}"
# Copy required files from the example module
+79
View File
@@ -0,0 +1,79 @@
#!/usr/bin/env bash
# This script creates a new sample template directory with required files
# Run it like: ./scripts/new_template.sh my-namespace/my-template
TEMPLATE_ARG=$1
# Check if they are in the root directory
if [ ! -d "registry" ]; then
echo "Please run this script from the root directory of the repository"
echo "Usage: ./scripts/new_template.sh <namespace>/<template_name>"
exit 1
fi
# check if template name is in the format <namespace>/<template_name>
if ! [[ "$TEMPLATE_ARG" =~ ^[a-z0-9_-]+/[a-z0-9_-]+$ ]]; then
echo "Template name must be in the format <namespace>/<template_name>"
echo "Usage: ./scripts/new_template.sh <namespace>/<template_name>"
exit 1
fi
# Extract the namespace and template name
NAMESPACE=$(echo "$TEMPLATE_ARG" | cut -d'/' -f1)
TEMPLATE_NAME=$(echo "$TEMPLATE_ARG" | cut -d'/' -f2)
# Check if the template already exists
if [ -d "registry/$NAMESPACE/templates/$TEMPLATE_NAME" ]; then
echo "Template at registry/$NAMESPACE/templates/$TEMPLATE_NAME already exists"
echo "Please choose a different name"
exit 1
fi
# Create namespace directory if it doesn't exist
if [ ! -d "registry/$NAMESPACE" ]; then
echo "Creating namespace directory: registry/$NAMESPACE"
mkdir -p "registry/$NAMESPACE"
# Create namespace README if it doesn't exist
if [ ! -f "registry/$NAMESPACE/README.md" ]; then
echo "Creating namespace README: registry/$NAMESPACE/README.md"
cp "examples/namespace/README.md" "registry/$NAMESPACE/README.md"
# Replace NAMESPACE_NAME placeholder in the README
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
sed -i '' "s/NAMESPACE_NAME/${NAMESPACE}/g" "registry/$NAMESPACE/README.md"
else
# Linux
sed -i "s/NAMESPACE_NAME/${NAMESPACE}/g" "registry/$NAMESPACE/README.md"
fi
fi
fi
# Create the template directory structure
mkdir -p "registry/${NAMESPACE}/templates/${TEMPLATE_NAME}"
# Copy required files from the example template
cp -r examples/templates/* "registry/${NAMESPACE}/templates/${TEMPLATE_NAME}/"
# Change to template directory
cd "registry/${NAMESPACE}/templates/${TEMPLATE_NAME}"
# Detect OS and replace placeholders
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
sed -i '' "s/TEMPLATE_NAME/${TEMPLATE_NAME}/g" main.tf
sed -i '' "s/TEMPLATE_NAME/${TEMPLATE_NAME}/g" README.md
else
# Linux
sed -i "s/TEMPLATE_NAME/${TEMPLATE_NAME}/g" main.tf
sed -i "s/TEMPLATE_NAME/${TEMPLATE_NAME}/g" README.md
fi
echo "Template scaffolded successfully at registry/${NAMESPACE}/templates/${TEMPLATE_NAME}"
echo "Next steps:"
echo "1. Edit main.tf to add your infrastructure resources"
echo "2. Update README.md with template-specific information"
echo "3. Test your template with 'coder templates push'"
echo "4. Consider adding an icon at .icons/${TEMPLATE_NAME}.svg"