mirror of
https://github.com/coder/registry.git
synced 2026-06-03 13:08:14 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 507b73a07e | |||
| 814f765313 | |||
| 92a154f54a | |||
| 7aa7dea5ad | |||
| 59b0472125 | |||
| 673caf2e95 | |||
| ab5ff4b4be | |||
| f5a68b500b | |||
| a5edad7f17 | |||
| fb657b875d | |||
| 016d4dc523 | |||
| c8d99cfba3 | |||
| 74c8698566 | |||
| 03333991a4 | |||
| 2b0dba4ed1 | |||
| 57c900b2c9 | |||
| 0ccee61192 | |||
| 494dc4b8a1 | |||
| 3b135ad4a4 | |||
| 258591833f | |||
| 3efc22c589 | |||
| 8ba4c323c2 |
@@ -14,6 +14,7 @@ on:
|
||||
paths:
|
||||
- ".github/workflows/deploy-registry.yaml"
|
||||
- "registry/**/templates/**"
|
||||
- "registry/**/README.md"
|
||||
- ".icons/**"
|
||||
|
||||
jobs:
|
||||
@@ -29,7 +30,7 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Authenticate with Google Cloud
|
||||
uses: google-github-actions/auth@140bb5113ffb6b65a7e9b937a81fa96cf5064462
|
||||
uses: google-github-actions/auth@b7593ed2efd1c1617e1b0254da33b86225adb2a5
|
||||
with:
|
||||
workload_identity_provider: projects/309789351055/locations/global/workloadIdentityPools/github-actions/providers/github
|
||||
service_account: registry-v2-github@coder-registry-1.iam.gserviceaccount.com
|
||||
|
||||
+2
-2
@@ -89,7 +89,7 @@ Create `registry/[your-username]/README.md`:
|
||||
---
|
||||
display_name: "Your Name"
|
||||
bio: "Brief description of who you are and what you do"
|
||||
avatar_url: "./.images/avatar.png"
|
||||
avatar: "./.images/avatar.png"
|
||||
github: "your-username"
|
||||
linkedin: "https://www.linkedin.com/in/your-username" # Optional
|
||||
website: "https://yourwebsite.com" # Optional
|
||||
@@ -102,7 +102,7 @@ status: "community"
|
||||
Brief description of who you are and what you do.
|
||||
```
|
||||
|
||||
> **Note**: The `avatar_url` must point to `./.images/avatar.png` or `./.images/avatar.svg`.
|
||||
> **Note**: The `avatar` must point to `./.images/avatar.png` or `./.images/avatar.svg`.
|
||||
|
||||
### 2. Generate Module Files
|
||||
|
||||
|
||||
+51
-5
@@ -42,12 +42,58 @@ go build ./cmd/readmevalidation && ./readmevalidation
|
||||
|
||||
## Making a Release
|
||||
|
||||
### Create Release Tags
|
||||
### Automated Tag and Release Process
|
||||
|
||||
After merging a PR:
|
||||
After merging a PR, use the automated script to create and push release tags:
|
||||
|
||||
1. Get the new version from the PR (shown as `old → new`)
|
||||
2. Checkout the merge commit and create the tag:
|
||||
**Prerequisites:**
|
||||
|
||||
- Ensure all module versions are updated in their respective README files (the script uses this as the source of truth)
|
||||
- Make sure you have the necessary permissions to push tags to the repository
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. **Checkout the merge commit:**
|
||||
|
||||
```bash
|
||||
git checkout MERGE_COMMIT_ID
|
||||
```
|
||||
|
||||
2. **Run the tag release script:**
|
||||
|
||||
```bash
|
||||
./scripts/tag_release.sh
|
||||
```
|
||||
|
||||
3. **Review and confirm:**
|
||||
- The script will automatically scan all modules in the registry
|
||||
- It will detect which modules need version bumps by comparing README versions to existing tags
|
||||
- A summary will be displayed showing which modules need tagging
|
||||
- Confirm the list is correct when prompted
|
||||
|
||||
4. **Automatic tagging:**
|
||||
- After confirmation, the script will automatically create all necessary release tags
|
||||
- Tags will be pushed to the remote repository
|
||||
- The script operates on the current checked-out commit
|
||||
|
||||
**Example output:**
|
||||
|
||||
```text
|
||||
🔍 Scanning all modules for missing release tags...
|
||||
|
||||
📦 coder/code-server: v4.1.2 (needs tag)
|
||||
✅ coder/dotfiles: v1.0.5 (already tagged)
|
||||
|
||||
## Tags to be created:
|
||||
- `release/coder/code-server/v4.1.2`
|
||||
|
||||
❓ Do you want to proceed with creating and pushing these release tags?
|
||||
Continue? [y/N]: y
|
||||
```
|
||||
|
||||
### Manual Process (Fallback)
|
||||
|
||||
If the automated script fails, you can manually tag and release modules:
|
||||
|
||||
```bash
|
||||
# Checkout the merge commit
|
||||
@@ -81,7 +127,7 @@ tags: ["tag1", "tag2"]
|
||||
```yaml
|
||||
display_name: "Your Name"
|
||||
bio: "Brief description of who you are and what you do"
|
||||
avatar_url: "./.images/avatar.png"
|
||||
avatar: "./.images/avatar.png"
|
||||
github: "username"
|
||||
linkedin: "https://www.linkedin.com/in/username" # Optional
|
||||
website: "https://yourwebsite.com" # Optional
|
||||
|
||||
@@ -336,12 +336,12 @@ func validateAllCoderResourceFilesOfType(resourceType string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info(context.Background(), "rocessing README files", "num_files", len(allReadmeFiles))
|
||||
logger.Info(context.Background(), "processing README files", "num_files", len(allReadmeFiles))
|
||||
resources, err := parseCoderResourceReadmeFiles(resourceType, allReadmeFiles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Info(context.Background(), "rocessed README files as valid Coder resources", "num_files", len(resources), "type", resourceType)
|
||||
logger.Info(context.Background(), "processed README files as valid Coder resources", "num_files", len(resources), "type", resourceType)
|
||||
|
||||
if err := validateCoderResourceRelativeURLs(resources); err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
display_name: "Jay Kumar"
|
||||
bio: "I'm a Software Engineer :)"
|
||||
avatar_url: "./.images/avatar.png"
|
||||
avatar: "./.images/avatar.jpeg"
|
||||
github: "35C4n0r"
|
||||
linkedin: "https://www.linkedin.com/in/jaykum4r"
|
||||
support_email: "work.jaykumar@gmail.com"
|
||||
|
||||
@@ -118,7 +118,6 @@ data "coder_workspace_preset" "default" {
|
||||
EOT
|
||||
"preview_port" = "4200"
|
||||
"container_image" = "codercom/example-universal:ubuntu"
|
||||
"jetbrains_ide" = "PY"
|
||||
}
|
||||
|
||||
# Pre-builds is a Coder Premium
|
||||
|
||||
@@ -16,7 +16,7 @@ The AgentAPI module is a building block for modules that need to run an AgentAPI
|
||||
```tf
|
||||
module "agentapi" {
|
||||
source = "registry.coder.com/coder/agentapi/coder"
|
||||
version = "1.0.2"
|
||||
version = "1.1.1"
|
||||
|
||||
agent_id = var.agent_id
|
||||
web_app_slug = local.app_slug
|
||||
|
||||
@@ -148,4 +148,105 @@ describe("agentapi", async () => {
|
||||
]);
|
||||
expect(respAgentAPI.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test("no-subdomain-base-path", async () => {
|
||||
const { id } = await setup({
|
||||
moduleVariables: {
|
||||
agentapi_subdomain: "false",
|
||||
},
|
||||
});
|
||||
|
||||
const respModuleScript = await execModuleScript(id);
|
||||
expect(respModuleScript.exitCode).toBe(0);
|
||||
|
||||
await expectAgentAPIStarted(id);
|
||||
const agentApiStartLog = await readFileContainer(
|
||||
id,
|
||||
"/home/coder/test-agentapi-start.log",
|
||||
);
|
||||
expect(agentApiStartLog).toContain("Using AGENTAPI_CHAT_BASE_PATH: /@default/default.foo/apps/agentapi-web/chat");
|
||||
});
|
||||
|
||||
test("validate-agentapi-version", async () => {
|
||||
const cases = [
|
||||
{
|
||||
moduleVariables: {
|
||||
agentapi_version: "v0.3.2",
|
||||
},
|
||||
shouldThrow: "",
|
||||
},
|
||||
{
|
||||
moduleVariables: {
|
||||
agentapi_version: "v0.3.3",
|
||||
},
|
||||
shouldThrow: "",
|
||||
},
|
||||
{
|
||||
moduleVariables: {
|
||||
agentapi_version: "v0.0.1",
|
||||
agentapi_subdomain: "false",
|
||||
},
|
||||
shouldThrow: "Running with subdomain = false is only supported by agentapi >= v0.3.3.",
|
||||
},
|
||||
{
|
||||
moduleVariables: {
|
||||
agentapi_version: "v0.3.2",
|
||||
agentapi_subdomain: "false",
|
||||
},
|
||||
shouldThrow: "Running with subdomain = false is only supported by agentapi >= v0.3.3.",
|
||||
},
|
||||
{
|
||||
moduleVariables: {
|
||||
agentapi_version: "v0.3.3",
|
||||
agentapi_subdomain: "false",
|
||||
},
|
||||
shouldThrow: "",
|
||||
},
|
||||
{
|
||||
moduleVariables: {
|
||||
agentapi_version: "v0.3.999",
|
||||
agentapi_subdomain: "false",
|
||||
},
|
||||
shouldThrow: "",
|
||||
},
|
||||
{
|
||||
moduleVariables: {
|
||||
agentapi_version: "v0.999.999",
|
||||
agentapi_subdomain: "false",
|
||||
},
|
||||
},
|
||||
{
|
||||
moduleVariables: {
|
||||
agentapi_version: "v999.999.999",
|
||||
agentapi_subdomain: "false",
|
||||
},
|
||||
},
|
||||
{
|
||||
moduleVariables: {
|
||||
agentapi_version: "arbitrary-string-bypasses-validation",
|
||||
},
|
||||
shouldThrow: "",
|
||||
}
|
||||
];
|
||||
for (const { moduleVariables, shouldThrow } of cases) {
|
||||
if (shouldThrow) {
|
||||
expect(setup({ moduleVariables: moduleVariables as Record<string, string> })).rejects.toThrow(shouldThrow);
|
||||
} else {
|
||||
expect(setup({ moduleVariables: moduleVariables as Record<string, string> })).resolves.toBeDefined();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("agentapi-allowed-hosts", async () => {
|
||||
// verify that the agentapi binary has access to the AGENTAPI_ALLOWED_HOSTS environment variable
|
||||
// set in main.sh
|
||||
const { id } = await setup();
|
||||
await execModuleScript(id);
|
||||
await expectAgentAPIStarted(id);
|
||||
const agentApiStartLog = await readFileContainer(
|
||||
id,
|
||||
"/home/coder/agentapi-mock.log",
|
||||
);
|
||||
expect(agentApiStartLog).toContain("AGENTAPI_ALLOWED_HOSTS: *");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -117,7 +117,7 @@ variable "install_agentapi" {
|
||||
variable "agentapi_version" {
|
||||
type = string
|
||||
description = "The version of AgentAPI to install."
|
||||
default = "v0.2.3"
|
||||
default = "v0.3.3"
|
||||
}
|
||||
|
||||
variable "agentapi_port" {
|
||||
@@ -126,6 +126,31 @@ variable "agentapi_port" {
|
||||
default = 3284
|
||||
}
|
||||
|
||||
locals {
|
||||
# agentapi_subdomain_false_min_version_expr matches a semantic version >= v0.3.3.
|
||||
# Initial support was added in v0.3.1 but configuration via environment variable
|
||||
# was added in v0.3.3.
|
||||
# This is unfortunately a regex because there is no builtin way to compare semantic versions in Terraform.
|
||||
# See: https://regex101.com/r/oHPyRa/1
|
||||
agentapi_subdomain_false_min_version_expr = "^v(0\\.(3\\.[3-9]|3.[1-9]\\d+|[4-9]\\.\\d+|[1-9]\\d+\\.\\d+)|[1-9]\\d*\\.\\d+\\.\\d+)$"
|
||||
}
|
||||
|
||||
variable "agentapi_subdomain" {
|
||||
type = bool
|
||||
description = "Whether to use a subdomain for AgentAPI."
|
||||
default = true
|
||||
validation {
|
||||
condition = var.agentapi_subdomain || (
|
||||
# If version doesn't look like a valid semantic version, just allow it.
|
||||
# Note that boolean operators do not short-circuit in Terraform.
|
||||
can(regex("^v\\d+\\.\\d+\\.\\d+$", var.agentapi_version)) ?
|
||||
can(regex(local.agentapi_subdomain_false_min_version_expr, var.agentapi_version)) :
|
||||
true
|
||||
)
|
||||
error_message = "Running with subdomain = false is only supported by agentapi >= v0.3.3."
|
||||
}
|
||||
}
|
||||
|
||||
variable "module_dir_name" {
|
||||
type = string
|
||||
description = "Name of the subdirectory in the home directory for module files."
|
||||
@@ -140,7 +165,14 @@ locals {
|
||||
encoded_post_install_script = var.post_install_script != null ? base64encode(var.post_install_script) : ""
|
||||
agentapi_start_script_b64 = base64encode(var.start_script)
|
||||
agentapi_wait_for_start_script_b64 = base64encode(file("${path.module}/scripts/agentapi-wait-for-start.sh"))
|
||||
main_script = file("${path.module}/scripts/main.sh")
|
||||
// Chat base path is only set if not using a subdomain.
|
||||
// NOTE:
|
||||
// - Initial support for --chat-base-path was added in v0.3.1 but configuration
|
||||
// via environment variable AGENTAPI_CHAT_BASE_PATH was added in v0.3.3.
|
||||
// - As CODER_WORKSPACE_AGENT_NAME is a recent addition we use agent ID
|
||||
// for backward compatibility.
|
||||
agentapi_chat_base_path = var.agentapi_subdomain ? "" : "/@${data.coder_workspace_owner.me.name}/${data.coder_workspace.me.name}.${var.agent_id}/apps/${var.web_app_slug}/chat"
|
||||
main_script = file("${path.module}/scripts/main.sh")
|
||||
}
|
||||
|
||||
resource "coder_script" "agentapi" {
|
||||
@@ -165,6 +197,7 @@ resource "coder_script" "agentapi" {
|
||||
ARG_WAIT_FOR_START_SCRIPT="$(echo -n '${local.agentapi_wait_for_start_script_b64}' | base64 -d)" \
|
||||
ARG_POST_INSTALL_SCRIPT="$(echo -n '${local.encoded_post_install_script}' | base64 -d)" \
|
||||
ARG_AGENTAPI_PORT='${var.agentapi_port}' \
|
||||
ARG_AGENTAPI_CHAT_BASE_PATH='${local.agentapi_chat_base_path}' \
|
||||
/tmp/main.sh
|
||||
EOT
|
||||
run_on_start = true
|
||||
@@ -178,7 +211,7 @@ resource "coder_app" "agentapi_web" {
|
||||
icon = var.web_app_icon
|
||||
order = var.web_app_order
|
||||
group = var.web_app_group
|
||||
subdomain = true
|
||||
subdomain = var.agentapi_subdomain
|
||||
healthcheck {
|
||||
url = "http://localhost:${var.agentapi_port}/status"
|
||||
interval = 3
|
||||
|
||||
@@ -13,6 +13,7 @@ START_SCRIPT="$ARG_START_SCRIPT"
|
||||
WAIT_FOR_START_SCRIPT="$ARG_WAIT_FOR_START_SCRIPT"
|
||||
POST_INSTALL_SCRIPT="$ARG_POST_INSTALL_SCRIPT"
|
||||
AGENTAPI_PORT="$ARG_AGENTAPI_PORT"
|
||||
AGENTAPI_CHAT_BASE_PATH="${ARG_AGENTAPI_CHAT_BASE_PATH:-}"
|
||||
set +o nounset
|
||||
|
||||
command_exists() {
|
||||
@@ -92,5 +93,9 @@ export LANG=en_US.UTF-8
|
||||
export LC_ALL=en_US.UTF-8
|
||||
|
||||
cd "${WORKDIR}"
|
||||
|
||||
export AGENTAPI_CHAT_BASE_PATH="${AGENTAPI_CHAT_BASE_PATH:-}"
|
||||
# Disable host header check since AgentAPI is proxied by Coder (which does its own validation)
|
||||
export AGENTAPI_ALLOWED_HOSTS="*"
|
||||
nohup "$module_path/scripts/agentapi-start.sh" true "${AGENTAPI_PORT}" &>"$module_path/agentapi-start.log" &
|
||||
"$module_path/scripts/agentapi-wait-for-start.sh" "${AGENTAPI_PORT}"
|
||||
|
||||
@@ -24,7 +24,16 @@ export const setupContainer = async ({
|
||||
});
|
||||
const coderScript = findResourceInstance(state, "coder_script");
|
||||
const id = await runContainer(image ?? "codercom/enterprise-node:latest");
|
||||
return { id, coderScript, cleanup: () => removeContainer(id) };
|
||||
return {
|
||||
id, coderScript, cleanup: async () => {
|
||||
if (process.env["DEBUG"] === "true" || process.env["DEBUG"] === "1" || process.env["DEBUG"] === "yes") {
|
||||
console.log(`Not removing container ${id} in debug mode`);
|
||||
console.log(`Run "docker rm -f ${id}" to remove it manually.`);
|
||||
} else {
|
||||
await removeContainer(id);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const loadTestFile = async (
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const http = require("http");
|
||||
const fs = require("fs");
|
||||
const args = process.argv.slice(2);
|
||||
const portIdx = args.findIndex((arg) => arg === "--port") + 1;
|
||||
const port = portIdx ? args[portIdx] : 3284;
|
||||
|
||||
console.log(`starting server on port ${port}`);
|
||||
fs.writeFileSync("/home/coder/agentapi-mock.log", `AGENTAPI_ALLOWED_HOSTS: ${process.env.AGENTAPI_ALLOWED_HOSTS}`);
|
||||
|
||||
http
|
||||
.createServer(function (_request, response) {
|
||||
|
||||
@@ -11,6 +11,12 @@ log_file_path="$module_path/agentapi.log"
|
||||
echo "using prompt: $use_prompt" >>/home/coder/test-agentapi-start.log
|
||||
echo "using port: $port" >>/home/coder/test-agentapi-start.log
|
||||
|
||||
AGENTAPI_CHAT_BASE_PATH="${AGENTAPI_CHAT_BASE_PATH:-}"
|
||||
if [ -n "$AGENTAPI_CHAT_BASE_PATH" ]; then
|
||||
echo "Using AGENTAPI_CHAT_BASE_PATH: $AGENTAPI_CHAT_BASE_PATH" >>/home/coder/test-agentapi-start.log
|
||||
export AGENTAPI_CHAT_BASE_PATH
|
||||
fi
|
||||
|
||||
agentapi server --port "$port" --term-width 67 --term-height 1190 -- \
|
||||
bash -c aiagent \
|
||||
>"$log_file_path" 2>&1
|
||||
|
||||
@@ -125,24 +125,7 @@ variable "ai_prompt" {
|
||||
locals {
|
||||
encoded_pre_install_script = var.experiment_pre_install_script != null ? base64encode(var.experiment_pre_install_script) : ""
|
||||
encoded_post_install_script = var.experiment_post_install_script != null ? base64encode(var.experiment_post_install_script) : ""
|
||||
# We need to use allowed tools to limit the context Amazon Q receives.
|
||||
# Amazon Q can't handle big contexts, and the `create_template_version` tool
|
||||
# has a description that's too long.
|
||||
mcp_json = <<EOT
|
||||
{
|
||||
"mcpServers": {
|
||||
"coder": {
|
||||
"command": "coder",
|
||||
"args": ["exp", "mcp", "server", "--allowed-tools", "coder_report_task"],
|
||||
"env": {
|
||||
"CODER_MCP_APP_STATUS_SLUG": "amazon-q"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOT
|
||||
encoded_mcp_json = base64encode(local.mcp_json)
|
||||
full_prompt = <<-EOT
|
||||
full_prompt = <<-EOT
|
||||
${var.system_prompt}
|
||||
|
||||
Your first task is:
|
||||
@@ -211,6 +194,12 @@ resource "coder_script" "amazon_q" {
|
||||
cd "$PREV_DIR"
|
||||
echo "Extracted auth tarball"
|
||||
|
||||
if [ "${var.experiment_report_tasks}" = "true" ]; then
|
||||
echo "Configuring Amazon Q to report tasks via Coder MCP..."
|
||||
q mcp add --name coder --command "coder" --args "exp,mcp,server,--allowed-tools,coder_report_task" --env "CODER_MCP_APP_STATUS_SLUG=amazon-q" --scope global --force
|
||||
echo "Added Coder MCP server to Amazon Q configuration"
|
||||
fi
|
||||
|
||||
if [ -n "${local.encoded_post_install_script}" ]; then
|
||||
echo "Running post-install script..."
|
||||
echo "${local.encoded_post_install_script}" | base64 -d > /tmp/post_install.sh
|
||||
@@ -218,13 +207,6 @@ resource "coder_script" "amazon_q" {
|
||||
/tmp/post_install.sh
|
||||
fi
|
||||
|
||||
if [ "${var.experiment_report_tasks}" = "true" ]; then
|
||||
echo "Configuring Amazon Q to report tasks via Coder MCP..."
|
||||
mkdir -p ~/.aws/amazonq
|
||||
echo "${local.encoded_mcp_json}" | base64 -d > ~/.aws/amazonq/mcp.json
|
||||
echo "Created the ~/.aws/amazonq/mcp.json configuration file"
|
||||
fi
|
||||
|
||||
if [ "${var.experiment_use_tmux}" = "true" ] && [ "${var.experiment_use_screen}" = "true" ]; then
|
||||
echo "Error: Both experiment_use_tmux and experiment_use_screen cannot be true simultaneously."
|
||||
echo "Please set only one of them to true."
|
||||
|
||||
@@ -13,7 +13,7 @@ Run the [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude
|
||||
```tf
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "2.0.4"
|
||||
version = "2.0.7"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder"
|
||||
install_claude_code = true
|
||||
@@ -84,7 +84,7 @@ resource "coder_agent" "main" {
|
||||
module "claude-code" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "2.0.4"
|
||||
version = "2.0.7"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder"
|
||||
install_claude_code = true
|
||||
@@ -102,7 +102,7 @@ Run Claude Code as a standalone app in your workspace. This will install Claude
|
||||
```tf
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "2.0.4"
|
||||
version = "2.0.7"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder"
|
||||
install_claude_code = true
|
||||
|
||||
@@ -10,6 +10,7 @@ import path from "path";
|
||||
import {
|
||||
execContainer,
|
||||
findResourceInstance,
|
||||
readFileContainer,
|
||||
removeContainer,
|
||||
runContainer,
|
||||
runTerraformApply,
|
||||
@@ -319,4 +320,21 @@ describe("claude-code", async () => {
|
||||
agentApiUrl: "http://localhost:3284",
|
||||
});
|
||||
});
|
||||
|
||||
// verify that the agentapi binary has access to the AGENTAPI_ALLOWED_HOSTS environment variable
|
||||
// set in main.tf
|
||||
test("agentapi-allowed-hosts", async () => {
|
||||
const { id } = await setup();
|
||||
|
||||
const respModuleScript = await execModuleScript(id);
|
||||
expect(respModuleScript.exitCode).toBe(0);
|
||||
|
||||
await expectAgentAPIStarted(id);
|
||||
|
||||
const agentApiStartLog = await readFileContainer(
|
||||
id,
|
||||
"/home/coder/agentapi-mock.log",
|
||||
);
|
||||
expect(agentApiStartLog).toContain("AGENTAPI_ALLOWED_HOSTS: *");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -241,6 +241,10 @@ resource "coder_script" "claude_code" {
|
||||
export LC_ALL=en_US.UTF-8
|
||||
|
||||
cd "${local.workdir}"
|
||||
|
||||
# Disable host header check since AgentAPI is proxied by Coder (which does its own validation)
|
||||
export AGENTAPI_ALLOWED_HOSTS="*"
|
||||
|
||||
nohup "$module_path/scripts/agentapi-start.sh" use_prompt &> "$module_path/agentapi-start.log" &
|
||||
"$module_path/scripts/agentapi-wait-for-start.sh"
|
||||
EOT
|
||||
|
||||
@@ -20,6 +20,8 @@ if (
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
fs.writeFileSync("/home/coder/agentapi-mock.log", `AGENTAPI_ALLOWED_HOSTS: ${process.env.AGENTAPI_ALLOWED_HOSTS}`);
|
||||
|
||||
console.log(`starting server on port ${port}`);
|
||||
|
||||
http
|
||||
|
||||
@@ -15,7 +15,7 @@ The devcontainers-cli module provides an easy way to install [`@devcontainers/cl
|
||||
```tf
|
||||
module "devcontainers-cli" {
|
||||
source = "registry.coder.com/coder/devcontainers-cli/coder"
|
||||
version = "1.0.31"
|
||||
version = "1.0.32"
|
||||
agent_id = coder_agent.example.id
|
||||
}
|
||||
```
|
||||
|
||||
@@ -45,6 +45,8 @@ const executeScriptInContainerWithPackageManager = async (
|
||||
|
||||
console.log(path);
|
||||
|
||||
await execContainer(id, [shell, "-c", "mkdir -p /tmp/coder-script-data"]);
|
||||
|
||||
const resp = await execContainer(
|
||||
id,
|
||||
[shell, "-c", instance.script],
|
||||
@@ -52,6 +54,8 @@ const executeScriptInContainerWithPackageManager = async (
|
||||
"--env",
|
||||
"CODER_SCRIPT_BIN_DIR=/tmp/coder-script-data/bin",
|
||||
"--env",
|
||||
"CODER_SCRIPT_DATA_DIR=/tmp/coder-script-data",
|
||||
"--env",
|
||||
`PATH=${path}:/tmp/coder-script-data/bin`,
|
||||
],
|
||||
);
|
||||
|
||||
Regular → Executable
+7
-1
@@ -1,5 +1,11 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# We want to cd into `$CODER_SCRIPT_DATA_DIR` as the current directory
|
||||
# might contain a `package.json` with `packageManager` set to something
|
||||
# other than the detected package manager. When this happens, it can
|
||||
# cause the installation to fail.
|
||||
cd "$CODER_SCRIPT_DATA_DIR"
|
||||
|
||||
# If @devcontainers/cli is already installed, we can skip
|
||||
if command -v devcontainer >/dev/null 2>&1; then
|
||||
echo "🥳 @devcontainers/cli is already installed into $(which devcontainer)!"
|
||||
@@ -34,7 +40,7 @@ install() {
|
||||
# so that the devcontainer command is available
|
||||
if [ -z "$PNPM_HOME" ]; then
|
||||
PNPM_HOME="$CODER_SCRIPT_BIN_DIR"
|
||||
export M_HOME
|
||||
export PNPM_HOME
|
||||
fi
|
||||
pnpm add -g @devcontainers/cli
|
||||
elif [ "$PACKAGE_MANAGER" = "yarn" ]; then
|
||||
|
||||
@@ -13,7 +13,7 @@ Run the [Goose](https://block.github.io/goose/) agent in your workspace to gener
|
||||
```tf
|
||||
module "goose" {
|
||||
source = "registry.coder.com/coder/goose/coder"
|
||||
version = "2.0.1"
|
||||
version = "2.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder"
|
||||
install_goose = true
|
||||
@@ -79,7 +79,7 @@ resource "coder_agent" "main" {
|
||||
module "goose" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/goose/coder"
|
||||
version = "2.0.1"
|
||||
version = "2.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder"
|
||||
install_goose = true
|
||||
|
||||
@@ -251,4 +251,21 @@ describe("goose", async () => {
|
||||
expect(prompt.exitCode).not.toBe(0);
|
||||
expect(prompt.stderr).toContain("No such file or directory");
|
||||
});
|
||||
|
||||
test("subdomain-false", async () => {
|
||||
const { id } = await setup({
|
||||
agentapiMockScript: await loadTestFile(
|
||||
import.meta.dir,
|
||||
"agentapi-mock-print-args.js",
|
||||
),
|
||||
moduleVariables: {
|
||||
subdomain: "false",
|
||||
},
|
||||
});
|
||||
|
||||
await execModuleScript(id);
|
||||
|
||||
const agentapiMockOutput = await readFileContainer(id, agentapiStartLog);
|
||||
expect(agentapiMockOutput).toContain("AGENTAPI_CHAT_BASE_PATH=/@default/default.foo/apps/goose/chat");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -63,7 +63,13 @@ variable "install_agentapi" {
|
||||
variable "agentapi_version" {
|
||||
type = string
|
||||
description = "The version of AgentAPI to install."
|
||||
default = "v0.2.3"
|
||||
default = "v0.3.3"
|
||||
}
|
||||
|
||||
variable "subdomain" {
|
||||
type = bool
|
||||
description = "Whether to use a subdomain for AgentAPI."
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "goose_provider" {
|
||||
@@ -133,7 +139,7 @@ EOT
|
||||
|
||||
module "agentapi" {
|
||||
source = "registry.coder.com/coder/agentapi/coder"
|
||||
version = "1.0.0"
|
||||
version = "1.1.0"
|
||||
|
||||
agent_id = var.agent_id
|
||||
web_app_slug = local.app_slug
|
||||
@@ -146,6 +152,7 @@ module "agentapi" {
|
||||
module_dir_name = local.module_dir_name
|
||||
install_agentapi = var.install_agentapi
|
||||
agentapi_version = var.agentapi_version
|
||||
agentapi_subdomain = var.subdomain
|
||||
pre_install_script = var.pre_install_script
|
||||
post_install_script = var.post_install_script
|
||||
start_script = local.start_script
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
const http = require("http");
|
||||
const args = process.argv.slice(2);
|
||||
console.log(args);
|
||||
console.log(`AGENTAPI_CHAT_BASE_PATH=${process.env["AGENTAPI_CHAT_BASE_PATH"]}`);
|
||||
const port = 3284;
|
||||
|
||||
console.log(`starting server on port ${port}`);
|
||||
|
||||
@@ -14,7 +14,7 @@ This module adds JetBrains IDE buttons to launch IDEs directly from the dashboar
|
||||
module "jetbrains" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains/coder"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/project"
|
||||
}
|
||||
@@ -39,7 +39,7 @@ When `default` contains IDE codes, those IDEs are created directly without user
|
||||
module "jetbrains" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains/coder"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/project"
|
||||
default = ["PY", "IU"] # Pre-configure GoLand and IntelliJ IDEA
|
||||
@@ -52,7 +52,7 @@ module "jetbrains" {
|
||||
module "jetbrains" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains/coder"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/project"
|
||||
# Show parameter with limited options
|
||||
@@ -66,7 +66,7 @@ module "jetbrains" {
|
||||
module "jetbrains" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains/coder"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/project"
|
||||
default = ["IU", "PY"]
|
||||
@@ -81,7 +81,7 @@ module "jetbrains" {
|
||||
module "jetbrains" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains/coder"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/workspace/project"
|
||||
|
||||
@@ -107,7 +107,7 @@ module "jetbrains" {
|
||||
module "jetbrains_pycharm" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains/coder"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/workspace/project"
|
||||
|
||||
|
||||
@@ -202,6 +202,7 @@ data "coder_parameter" "jetbrains_ides" {
|
||||
count = length(var.default) == 0 ? 1 : 0
|
||||
type = "list(string)"
|
||||
name = "jetbrains_ides"
|
||||
description = "Select which JetBrains IDEs to configure for use in this workspace."
|
||||
display_name = "JetBrains IDEs"
|
||||
icon = "/icon/jetbrains-toolbox.svg"
|
||||
mutable = true
|
||||
@@ -230,6 +231,7 @@ resource "coder_app" "jetbrains" {
|
||||
icon = local.options_metadata[each.key].icon
|
||||
external = true
|
||||
order = var.coder_app_order
|
||||
group = var.group
|
||||
url = join("", [
|
||||
"jetbrains://gateway/coder?&workspace=", # requires 2.6.3+ version of Toolbox
|
||||
data.coder_workspace.me.name,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
display_name: "Eric Paulsen"
|
||||
bio: "Field CTO, EMEA @ Coder"
|
||||
avatar_url: "./.images/avatar.png"
|
||||
avatar: "./.images/avatar.png"
|
||||
github: "ericpaulsen"
|
||||
linkedin: "https://www.linkedin.com/in/ericpaulsen17" # Optional
|
||||
website: "https://ericpaulsen.io" # Optional
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
@@ -0,0 +1,32 @@
|
||||
---
|
||||
display_name: "Mark Milligan"
|
||||
bio: "VP of Revenue at https://nuon.co. Former VP of Sales at Coder. Love building startup revenue teams and tinkering with technology."
|
||||
avatar: "./.images/avatar.png"
|
||||
github: "sharkymark"
|
||||
linkedin: "https://www.linkedin.com/in/marktmilligan" # Optional
|
||||
website: "https://markmilligan.io" # Optional
|
||||
support_email: "mtm20176@gmail.com" # Optional
|
||||
status: "community"
|
||||
---
|
||||
|
||||
# Mark Milligan
|
||||
|
||||
Former VP of Sales at Coder for 4 years, and now VP of Revenue at Nuon. I love building startup revenue teams and tinkering with technology.
|
||||
|
||||
## About Me
|
||||
|
||||
Visit my [website](https://markmilligan.io) to learn more about my work and interests.
|
||||
|
||||
## Links
|
||||
|
||||
[My presentation about Great White Sharks](https://docs.google.com/presentation/d/13I3Af7l-ZSVCh-ovEvOKIM30ABIvNKhkRC3CnYZN450/edit?slide=id.p#slide=id.p) - given twice in 2020 and 2021 to the Coder team.
|
||||
|
||||
[NOAA Radar](https://radar.weather.gov/)
|
||||
|
||||
[Flight Radar](https://www.flightradar24.com/airport/aus)
|
||||
|
||||
### Webcams
|
||||
|
||||
[Austin - facing south](https://cctv.austinmobility.io/image/51.jpg)
|
||||
|
||||
[Austin - facing north](https://cctv.austinmobility.io/image/52.jpg)
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
display_name: "Claude Code AI Agent Template"
|
||||
description: The goal is to try the experimental ai agent integration with Claude CodeAI agent
|
||||
icon: "../../../../.icons/claude.svg"
|
||||
verified: false
|
||||
tags: ["ai", "docker", "container", "claude", "agent", "tasks"]
|
||||
---
|
||||
|
||||
# ai agent template for a workspace in a container on a Docker host
|
||||
|
||||
### Docker image
|
||||
|
||||
1. Based on Coder-managed image `codercom/example-universal:ubuntu`
|
||||
|
||||
[Image on DockerHub](https://hub.docker.com/r/codercom/example-universal)
|
||||
|
||||
### Apps included
|
||||
|
||||
1. A web-based terminal
|
||||
1. code-server Web IDE
|
||||
1. A [sample app](https://github.com/gothinkster/realworld) to test the environment
|
||||
1. [Claude Code AI agent](https://www.anthropic.com/claude-code) to assist with development tasks
|
||||
|
||||
### Resources
|
||||
|
||||
[Coder docs on AI agents and tasks](https://coder.com/docs/ai-coder/tasks)
|
||||
|
||||
[main.tf for Coder example](https://github.com/coder/registry/blob/main/registry/coder-labs/templates/tasks-docker/main.tf)
|
||||
|
||||
[Claude Code Coder Terraform module](https://registry.coder.com/modules/coder/claude-code)
|
||||
|
||||
[Docker Terraform provider](https://registry.terraform.io/providers/kreuzwerker/docker/latest/docs)
|
||||
|
||||
[Coder Terraform provider](https://registry.terraform.io/providers/coder/coder/latest/docs)
|
||||
@@ -0,0 +1,363 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
}
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "docker" {
|
||||
host = var.socket
|
||||
}
|
||||
|
||||
provider "coder" {
|
||||
}
|
||||
|
||||
data "coder_workspace" "me" {
|
||||
}
|
||||
|
||||
data "coder_workspace_owner" "me" {
|
||||
}
|
||||
|
||||
data "coder_provisioner" "me" {
|
||||
}
|
||||
|
||||
variable "socket" {
|
||||
type = string
|
||||
description = <<-EOF
|
||||
The Unix socket that the Docker daemon listens on and how containers
|
||||
communicate with the Docker daemon.
|
||||
|
||||
Either Unix or TCP
|
||||
e.g., unix:///var/run/docker.sock
|
||||
|
||||
EOF
|
||||
default = "unix:///var/run/docker.sock"
|
||||
}
|
||||
|
||||
variable "anthropic_api_key" {
|
||||
type = string
|
||||
description = "Generate one at: https://console.anthropic.com/settings/keys"
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
resource "coder_env" "anthropic_api_key" {
|
||||
agent_id = coder_agent.dev.id
|
||||
name = "CODER_MCP_CLAUDE_API_KEY"
|
||||
value = var.anthropic_api_key
|
||||
}
|
||||
|
||||
# The Claude Code module does the automatic task reporting
|
||||
# Other agent modules: https://registry.coder.com/modules?search=agent
|
||||
# Or use a custom agent:
|
||||
module "claude-code" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "2.0.0"
|
||||
agent_id = coder_agent.dev.id
|
||||
folder = "/home/coder/projects"
|
||||
install_claude_code = true
|
||||
claude_code_version = "latest"
|
||||
order = 999
|
||||
|
||||
experiment_post_install_script = data.coder_parameter.setup_script.value
|
||||
|
||||
# This enables Coder Tasks
|
||||
experiment_report_tasks = true
|
||||
}
|
||||
|
||||
# We are using presets to set the prompts, image, and set up instructions
|
||||
# See https://coder.com/docs/admin/templates/extending-templates/parameters#workspace-presets
|
||||
data "coder_workspace_preset" "default" {
|
||||
name = "Real World App: Angular + Django"
|
||||
default = true
|
||||
parameters = {
|
||||
"system_prompt" = <<-EOT
|
||||
-- Framing --
|
||||
You are a helpful assistant that can help with code. You are running inside a Coder Workspace and provide status updates to the user via Coder MCP. Stay on track, feel free to debug, but when the original plan fails, do not choose a different route/architecture without checking the user first.
|
||||
|
||||
-- Tool Selection --
|
||||
- playwright: previewing your changes after you made them
|
||||
to confirm it worked as expected
|
||||
- desktop-commander - use only for commands that keep running
|
||||
(servers, dev watchers, GUI apps).
|
||||
- Built-in tools - use for everything else:
|
||||
(file operations, git commands, builds & installs, one-off shell commands)
|
||||
|
||||
Remember this decision rule:
|
||||
- Stays running? → desktop-commander
|
||||
- Finishes immediately? → built-in tools
|
||||
|
||||
-- Context --
|
||||
There is an existing app and tmux dev server running on port 8000. Be sure to read it's CLAUDE.md (./realworld-django-rest-framework-angular/CLAUDE.md) to learn more about it.
|
||||
|
||||
Since this app is for demo purposes and the user is previewing the homepage and subsequent pages, aim to make the first visual change/prototype very quickly so the user can preview it, then focus on backend or logic which can be a more involved, long-running architecture plan.
|
||||
|
||||
EOT
|
||||
|
||||
"setup_script" = <<-EOT
|
||||
# Set up projects dir
|
||||
mkdir -p /home/coder/projects
|
||||
cd $HOME/projects
|
||||
|
||||
# Packages: Install additional packages
|
||||
sudo apt-get update && sudo apt-get install -y tmux
|
||||
if ! command -v google-chrome >/dev/null 2>&1; then
|
||||
yes | npx playwright install chrome
|
||||
fi
|
||||
|
||||
# MCP: Install and configure MCP Servers
|
||||
npm install -g @wonderwhy-er/desktop-commander
|
||||
claude mcp add playwright npx -- @playwright/mcp@latest --headless --isolated --no-sandbox
|
||||
claude mcp add desktop-commander desktop-commander
|
||||
|
||||
# Repo: Clone and pull changes from the git repository
|
||||
if [ ! -d "realworld-django-rest-framework-angular" ]; then
|
||||
git clone https://github.com/coder-contrib/realworld-django-rest-framework-angular.git
|
||||
else
|
||||
cd realworld-django-rest-framework-angular
|
||||
git fetch
|
||||
# Check for uncommitted changes
|
||||
if git diff-index --quiet HEAD -- && \
|
||||
[ -z "$(git status --porcelain --untracked-files=no)" ] && \
|
||||
[ -z "$(git log --branches --not --remotes)" ]; then
|
||||
echo "Repo is clean. Pulling latest changes..."
|
||||
git pull
|
||||
else
|
||||
echo "Repo has uncommitted or unpushed changes. Skipping pull."
|
||||
fi
|
||||
|
||||
cd ..
|
||||
fi
|
||||
|
||||
# Initialize: Start the development server
|
||||
cd realworld-django-rest-framework-angular && ./start-dev.sh
|
||||
EOT
|
||||
"preview_port" = "4200"
|
||||
"container_image" = "codercom/example-universal:ubuntu"
|
||||
}
|
||||
}
|
||||
|
||||
# Advanced parameters (these are all set via preset)
|
||||
data "coder_parameter" "system_prompt" {
|
||||
name = "system_prompt"
|
||||
display_name = "System Prompt"
|
||||
type = "string"
|
||||
form_type = "textarea"
|
||||
description = "System prompt for the agent with generalized instructions"
|
||||
mutable = false
|
||||
}
|
||||
data "coder_parameter" "ai_prompt" {
|
||||
type = "string"
|
||||
name = "AI Prompt"
|
||||
default = ""
|
||||
description = "Write a prompt for Claude Code"
|
||||
mutable = true
|
||||
}
|
||||
data "coder_parameter" "setup_script" {
|
||||
name = "setup_script"
|
||||
display_name = "Setup Script"
|
||||
type = "string"
|
||||
form_type = "textarea"
|
||||
description = "Script to run before running the agent"
|
||||
mutable = false
|
||||
}
|
||||
data "coder_parameter" "container_image" {
|
||||
name = "container_image"
|
||||
display_name = "Container Image"
|
||||
type = "string"
|
||||
default = "codercom/example-universal:ubuntu"
|
||||
mutable = false
|
||||
}
|
||||
data "coder_parameter" "preview_port" {
|
||||
name = "preview_port"
|
||||
display_name = "Preview Port"
|
||||
description = "The port the web app is running to preview in Tasks"
|
||||
type = "number"
|
||||
default = "3000"
|
||||
mutable = false
|
||||
}
|
||||
|
||||
# Other variables for Claude Code
|
||||
resource "coder_env" "claude_task_prompt" {
|
||||
agent_id = coder_agent.dev.id
|
||||
name = "CODER_MCP_CLAUDE_TASK_PROMPT"
|
||||
value = data.coder_parameter.ai_prompt.value
|
||||
}
|
||||
resource "coder_env" "app_status_slug" {
|
||||
agent_id = coder_agent.dev.id
|
||||
name = "CODER_MCP_APP_STATUS_SLUG"
|
||||
value = "claude-code"
|
||||
}
|
||||
resource "coder_env" "claude_system_prompt" {
|
||||
agent_id = coder_agent.dev.id
|
||||
name = "CODER_MCP_CLAUDE_SYSTEM_PROMPT"
|
||||
value = data.coder_parameter.system_prompt.value
|
||||
}
|
||||
|
||||
module "coder-login" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/coder-login/coder"
|
||||
agent_id = coder_agent.dev.id
|
||||
}
|
||||
|
||||
module "dotfiles" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/dotfiles/coder"
|
||||
agent_id = coder_agent.dev.id
|
||||
}
|
||||
|
||||
module "code-server" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/code-server/coder"
|
||||
agent_id = coder_agent.dev.id
|
||||
folder = "/home/coder/projects"
|
||||
}
|
||||
|
||||
module "git-config" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/modules/git-config/coder"
|
||||
agent_id = coder_agent.dev.id
|
||||
}
|
||||
|
||||
resource "coder_agent" "dev" {
|
||||
arch = data.coder_provisioner.me.arch
|
||||
os = "linux"
|
||||
|
||||
# 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 resources, you can use the `coder stat` command.
|
||||
# If you need more control, you can write your own script.
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
metadata {
|
||||
display_name = "CPU Usage (Host)"
|
||||
key = "4_cpu_usage_host"
|
||||
script = "coder stat cpu --host"
|
||||
interval = 10
|
||||
timeout = 1
|
||||
}
|
||||
|
||||
metadata {
|
||||
display_name = "Memory Usage (Host)"
|
||||
key = "5_mem_usage_host"
|
||||
script = "coder stat mem --host"
|
||||
interval = 10
|
||||
timeout = 1
|
||||
}
|
||||
|
||||
display_apps {
|
||||
vscode = true
|
||||
vscode_insiders = false
|
||||
ssh_helper = false
|
||||
port_forwarding_helper = true
|
||||
web_terminal = true
|
||||
}
|
||||
|
||||
startup_script_behavior = "non-blocking"
|
||||
connection_timeout = 300
|
||||
|
||||
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}"
|
||||
}
|
||||
|
||||
startup_script = <<EOT
|
||||
#!/bin/sh
|
||||
|
||||
EOT
|
||||
|
||||
}
|
||||
|
||||
resource "coder_app" "preview" {
|
||||
agent_id = coder_agent.dev.id
|
||||
slug = "preview"
|
||||
display_name = "Preview your app"
|
||||
icon = "${data.coder_workspace.me.access_url}/emojis/1f50e.png"
|
||||
url = "http://localhost:${data.coder_parameter.preview_port.value}"
|
||||
share = "authenticated"
|
||||
subdomain = true
|
||||
open_in = "tab"
|
||||
order = 0
|
||||
healthcheck {
|
||||
url = "http://localhost:${data.coder_parameter.preview_port.value}/"
|
||||
interval = 5
|
||||
threshold = 15
|
||||
}
|
||||
}
|
||||
|
||||
resource "docker_container" "workspace" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
image = data.coder_parameter.container_image.value
|
||||
# Uses lower() to avoid Docker restriction on container names.
|
||||
name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
|
||||
hostname = lower(data.coder_workspace.me.name)
|
||||
dns = ["1.1.1.1"]
|
||||
|
||||
# Use the docker gateway if the access URL is 127.0.0.1
|
||||
#entrypoint = ["sh", "-c", replace(coder_agent.dev.init_script, "127.0.0.1", "host.docker.internal")]
|
||||
|
||||
# Use the docker gateway if the access URL is 127.0.0.1
|
||||
command = [
|
||||
"sh", "-c",
|
||||
<<EOT
|
||||
trap '[ $? -ne 0 ] && echo === Agent script exited with non-zero code. Sleeping infinitely to preserve logs... && sleep infinity' EXIT
|
||||
${replace(coder_agent.dev.init_script, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal")}
|
||||
EOT
|
||||
]
|
||||
|
||||
|
||||
env = ["CODER_AGENT_TOKEN=${coder_agent.dev.token}"]
|
||||
volumes {
|
||||
container_path = "/home/coder/"
|
||||
volume_name = docker_volume.coder_volume.name
|
||||
read_only = false
|
||||
}
|
||||
host {
|
||||
host = "host.docker.internal"
|
||||
ip = "host-gateway"
|
||||
}
|
||||
}
|
||||
|
||||
resource "docker_volume" "coder_volume" {
|
||||
name = "coder-${data.coder_workspace_owner.me.name}-${data.coder_workspace.me.name}"
|
||||
}
|
||||
|
||||
resource "coder_metadata" "workspace_info" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
resource_id = docker_container.workspace[0].id
|
||||
item {
|
||||
key = "image"
|
||||
value = data.coder_parameter.container_image.value
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -247,8 +247,8 @@ export const runTerraformApply = async <TVars extends TerraformVariables>(
|
||||
"-compact-warnings",
|
||||
"-input=false",
|
||||
"-auto-approve",
|
||||
"-state",
|
||||
"-no-color",
|
||||
"-state",
|
||||
stateFile,
|
||||
],
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user