Compare commits

...

4 Commits

Author SHA1 Message Date
DevelopmentCats ab2a61e8d4 chore: remove test from cloud-dev template 2026-01-05 15:35:26 -06:00
DevelopmentCats 9702f036a4 refactor(github-upload-public-key): enhance test structure and cleanup handling 2026-01-05 12:58:34 -06:00
35C4n0r c819ca7f83 fix(nboyers/templates/cloud-dev): fix broken template icon (#633)
## Description

<!-- Briefly describe what this PR does and why -->

## Type of Change

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

## Template Information

**Path:** `registry/nboyers/templates/cloud-devops`

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->
2026-01-05 13:47:22 +05:30
Sebastian Mengwall accf5a34ab fix(modules/anomaly/tmux): fix config handling in run scripts (#629)
## Description

Fix custom tmux config handling. Two bugs:

1. `TMUX_CONFIG="${TMUX_CONFIG}"` - Terraform substitutes config inline,
bash interprets `set -g` etc as shell commands
2. `printf "$TMUX_CONFIG"` - `%` in `bind %` treated as format specifier


## Type of Change

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

## Module Information

**Path:** `registry/anomaly/modules/tmux`  
**New version:** 1.0.4  
**Breaking change:** [x] No

## Testing & Validation

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

## Related Issues

None
2026-01-04 03:12:20 +00:00
7 changed files with 132 additions and 163 deletions
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 35 KiB

+3 -3
View File
@@ -15,7 +15,7 @@ up a default or custom tmux configuration with session save/restore capabilities
```tf
module "tmux" {
source = "registry.coder.com/anomaly/tmux/coder"
version = "1.0.3"
version = "1.0.4"
agent_id = coder_agent.example.id
}
```
@@ -39,7 +39,7 @@ module "tmux" {
```tf
module "tmux" {
source = "registry.coder.com/anomaly/tmux/coder"
version = "1.0.3"
version = "1.0.4"
agent_id = coder_agent.example.id
tmux_config = "" # Optional: custom tmux.conf content
save_interval = 1 # Optional: save interval in minutes
@@ -78,7 +78,7 @@ This module can provision multiple tmux sessions, each as a separate app in the
```tf
module "tmux" {
source = "registry.coder.com/anomaly/tmux/coder"
version = "1.0.3"
version = "1.0.4"
agent_id = var.agent_id
sessions = ["default", "dev", "anomaly"]
tmux_config = <<-EOT
+1 -1
View File
@@ -55,7 +55,7 @@ resource "coder_script" "tmux" {
display_name = "tmux"
icon = "/icon/terminal.svg"
script = templatefile("${path.module}/scripts/run.sh", {
TMUX_CONFIG = var.tmux_config
TMUX_CONFIG = base64encode(var.tmux_config)
SAVE_INTERVAL = var.save_interval
})
run_on_start = true
+2 -2
View File
@@ -4,7 +4,7 @@ BOLD='\033[0;1m'
# Convert templated variables to shell variables
SAVE_INTERVAL="${SAVE_INTERVAL}"
TMUX_CONFIG="${TMUX_CONFIG}"
TMUX_CONFIG=$(echo -n "${TMUX_CONFIG}" | base64 -d)
# Function to install tmux
install_tmux() {
@@ -73,7 +73,7 @@ setup_tmux_config() {
mkdir -p "$config_dir"
if [ -n "$TMUX_CONFIG" ]; then
printf "$TMUX_CONFIG" > "$config_file"
printf "%s" "$TMUX_CONFIG" > "$config_file"
printf "$${BOLD}Custom tmux configuration applied at {$config_file} \n\n"
else
cat > "$config_file" << EOF
@@ -1,9 +1,17 @@
import { type Server, serve } from "bun";
import { describe, expect, it } from "bun:test";
import { serve } from "bun";
import {
afterEach,
beforeAll,
describe,
expect,
it,
setDefaultTimeout,
} from "bun:test";
import {
createJSONResponse,
execContainer,
findResourceInstance,
removeContainer,
runContainer,
runTerraformApply,
runTerraformInit,
@@ -11,77 +19,48 @@ import {
writeCoder,
} from "~test";
describe("github-upload-public-key", async () => {
await runTerraformInit(import.meta.dir);
testRequiredVariables(import.meta.dir, {
agent_id: "foo",
});
it("creates new key if one does not exist", async () => {
const { instance, id, server } = await setupContainer();
await writeCoder(id, "echo foo");
const url = server.url.toString().slice(0, -1);
const exec = await execContainer(id, [
"env",
`CODER_ACCESS_URL=${url}`,
`GITHUB_API_URL=${url}`,
"CODER_OWNER_SESSION_TOKEN=foo",
"CODER_EXTERNAL_AUTH_ID=github",
"bash",
"-c",
instance.script,
]);
expect(exec.stdout).toContain(
"Your Coder public key has been added to GitHub!",
);
expect(exec.exitCode).toBe(0);
// we need to increase timeout to pull the container
}, 15000);
it("does nothing if one already exists", async () => {
const { instance, id, server } = await setupContainer();
// use keyword to make server return a existing key
await writeCoder(id, "echo findkey");
const url = server.url.toString().slice(0, -1);
const exec = await execContainer(id, [
"env",
`CODER_ACCESS_URL=${url}`,
`GITHUB_API_URL=${url}`,
"CODER_OWNER_SESSION_TOKEN=foo",
"CODER_EXTERNAL_AUTH_ID=github",
"bash",
"-c",
instance.script,
]);
expect(exec.stdout).toContain(
"Your Coder public key is already on GitHub!",
);
expect(exec.exitCode).toBe(0);
});
let cleanupFunctions: (() => Promise<void>)[] = [];
const registerCleanup = (cleanup: () => Promise<void>) => {
cleanupFunctions.push(cleanup);
};
afterEach(async () => {
const cleanupFnsCopy = cleanupFunctions.slice().reverse();
cleanupFunctions = [];
for (const cleanup of cleanupFnsCopy) {
try {
await cleanup();
} catch (error) {
console.error("Error during cleanup:", error);
}
}
});
const setupContainer = async (
image = "lorello/alpine-bash",
vars: Record<string, string> = {},
) => {
const server = await setupServer();
const server = setupServer();
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
...vars,
});
const instance = findResourceInstance(state, "coder_script");
const id = await runContainer(image);
registerCleanup(async () => {
server.stop();
});
registerCleanup(async () => {
await removeContainer(id);
});
return { id, instance, server };
};
const setupServer = async (): Promise<Server> => {
let url: URL;
const fakeSlackHost = serve({
const setupServer = () => {
const fakeGithubHost = serve({
fetch: (req) => {
url = new URL(req.url);
const url = new URL(req.url);
if (url.pathname === "/api/v2/users/me/gitsshkey") {
return createJSONResponse({
public_key: "exists",
@@ -128,5 +107,60 @@ const setupServer = async (): Promise<Server> => {
port: 0,
});
return fakeSlackHost;
return fakeGithubHost;
};
setDefaultTimeout(30 * 1000);
describe("github-upload-public-key", () => {
beforeAll(async () => {
await runTerraformInit(import.meta.dir);
});
testRequiredVariables(import.meta.dir, {
agent_id: "foo",
});
it("creates new key if one does not exist", async () => {
const { instance, id, server } = await setupContainer();
await writeCoder(id, "echo foo");
const url = server.url.toString().slice(0, -1);
const exec = await execContainer(id, [
"env",
`CODER_ACCESS_URL=${url}`,
`GITHUB_API_URL=${url}`,
"CODER_OWNER_SESSION_TOKEN=foo",
"CODER_EXTERNAL_AUTH_ID=github",
"bash",
"-c",
instance.script,
]);
expect(exec.stdout).toContain(
"Your Coder public key has been added to GitHub!",
);
expect(exec.exitCode).toBe(0);
});
it("does nothing if one already exists", async () => {
const { instance, id, server } = await setupContainer();
// use keyword to make server return a existing key
await writeCoder(id, "echo findkey");
const url = server.url.toString().slice(0, -1);
const exec = await execContainer(id, [
"env",
`CODER_ACCESS_URL=${url}`,
`GITHUB_API_URL=${url}`,
"CODER_OWNER_SESSION_TOKEN=foo",
"CODER_EXTERNAL_AUTH_ID=github",
"bash",
"-c",
instance.script,
]);
expect(exec.stdout).toContain(
"Your Coder public key is already on GitHub!",
);
expect(exec.exitCode).toBe(0);
});
});
+5 -12
View File
@@ -1,16 +1,9 @@
---
display_name: "Cloud DevOps Workspace"
description: "A multi-cloud DevOps workspace that runs on Amazon EKS and provides authenticated access to AWS, Azure, and GCP."
icon: "https://raw.githubusercontent.com/coder/coder-icons/main/icons/cloud-devops.svg"
tags:
- devops
- kubernetes
- aws
- eks
- multi-cloud
- terraform
- cdk
- pulumi
display_name: Cloud DevOps Workspace
description: A multi-cloud DevOps workspace that runs on Amazon EKS and provides authenticated access to AWS, Azure, and GCP.
icon: ../../../../.icons/cloud-devops.svg
verified: false
tags: [devops, kubernetes, aws, eks, multi-cloud, terraform, cdk, pulumi]
---
# Cloud DevOps Workspace
@@ -1,87 +0,0 @@
# Run 'terraform test' from this template directory (where main.tf lives)
# --- Mock cloud providers so no external calls happen ---
mock_provider "aws" {}
mock_provider "kubernetes" {}
# Provide fake values for data sources your template reads
override_data {
target = data.aws_eks_cluster.eks
values = {
name = "unit-test-eks"
endpoint = "https://example.eks.local"
certificate_authority = [{
data = base64encode("dummy-ca")
}]
}
}
override_data {
target = data.aws_eks_cluster_auth.eks
values = {
token = "dummy-token"
}
}
# ---------------------------
# 1) Validate configuration
# ---------------------------
run "validate" {
command = validate
}
# ---------------------------
# 2) Plan with representative inputs
# ---------------------------
run "plan_with_defaults" {
command = plan
variables {
host_cluster_name = "unit-test-eks"
# IaC/tooling toggles
iac_tool = "terraform"
enable_aws = true
enable_azure = false
enable_gcp = false
# Dev creds (empty OK for unit test)
aws_access_key_id = ""
aws_secret_access_key = ""
azure_client_id = ""
azure_tenant_id = ""
azure_client_secret = ""
gcp_service_account = ""
}
# Simple sanity assertions (adjust resource addresses to your template)
assert {
condition = can(resource.kubernetes_namespace.workspace)
error_message = "kubernetes_namespace.workspace was not created in plan."
}
assert {
condition = can(resource.coder_agent.main)
error_message = "coder_agent.main was not planned."
}
}
# ---------------------------
# 3) Plan with CDK selected
# ---------------------------
run "plan_with_cdk" {
command = plan
variables {
host_cluster_name = "unit-test-eks"
iac_tool = "cdk"
enable_aws = true
enable_azure = false
enable_gcp = false
}
# Ensure the env reflects choice (string map lookup)
assert {
condition = contains(keys(resource.coder_agent.main.env), "IAC_TOOL")
error_message = "IAC_TOOL env not present on coder_agent.main."
}
}