diff --git a/.icons/aider.svg b/.icons/aider.svg index 44e064ff..bb54e1fb 100644 --- a/.icons/aider.svg +++ b/.icons/aider.svg @@ -1,3 +1 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/.icons/airflow.svg b/.icons/airflow.svg index 06b18bee..29a32e08 100644 --- a/.icons/airflow.svg +++ b/.icons/airflow.svg @@ -1,18 +1 @@ - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/.icons/akamai.svg b/.icons/akamai.svg index 4af3fe08..d5b778bc 100644 --- a/.icons/akamai.svg +++ b/.icons/akamai.svg @@ -1,4 +1 @@ - - Akamai - - +Akamai \ No newline at end of file diff --git a/.icons/amazon-q.svg b/.icons/amazon-q.svg index 4a9b3262..1c217bdc 100644 --- a/.icons/amazon-q.svg +++ b/.icons/amazon-q.svg @@ -1,13 +1 @@ - - - - - - - - - - - - - + \ No newline at end of file diff --git a/.icons/antigravity.svg b/.icons/antigravity.svg index a046b6b6..54eec2c4 100644 --- a/.icons/antigravity.svg +++ b/.icons/antigravity.svg @@ -1,2 +1 @@ - -Google_Antigravity-logo - brandlogos.net \ No newline at end of file +Google_Antigravity-logo - brandlogos.net \ No newline at end of file diff --git a/.icons/auggie.svg b/.icons/auggie.svg index 590bd5aa..180e1dee 100644 --- a/.icons/auggie.svg +++ b/.icons/auggie.svg @@ -1,8 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/.icons/auto-dev-server.svg b/.icons/auto-dev-server.svg index f043b56d..aae4a1a5 100644 --- a/.icons/auto-dev-server.svg +++ b/.icons/auto-dev-server.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/.icons/aws.svg b/.icons/aws.svg index 3244c974..828a8223 100644 --- a/.icons/aws.svg +++ b/.icons/aws.svg @@ -1,13 +1 @@ - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/.icons/azure.svg b/.icons/azure.svg index 645ac663..5e96a718 100644 --- a/.icons/azure.svg +++ b/.icons/azure.svg @@ -1,23 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/.icons/box-emoji.svg b/.icons/box-emoji.svg index a2595599..dc8ed58c 100644 --- a/.icons/box-emoji.svg +++ b/.icons/box-emoji.svg @@ -1,27 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/.icons/claude.svg b/.icons/claude.svg index 998fb0d5..22dcf464 100644 --- a/.icons/claude.svg +++ b/.icons/claude.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/.icons/cloud-devops.svg b/.icons/cloud-devops.svg index a3b6e82a..659dcc52 100644 --- a/.icons/cloud-devops.svg +++ b/.icons/cloud-devops.svg @@ -1,29 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/.icons/cmux.svg b/.icons/cmux.svg index 95b56bb0..012bff21 100644 --- a/.icons/cmux.svg +++ b/.icons/cmux.svg @@ -1,47 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/.icons/coder.svg b/.icons/coder.svg index 60d7eff6..5db19df6 100644 --- a/.icons/coder.svg +++ b/.icons/coder.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/.icons/copyparty.svg b/.icons/copyparty.svg index 2c4f0d04..bb8cf89e 100644 --- a/.icons/copyparty.svg +++ b/.icons/copyparty.svg @@ -1,210 +1 @@ - - - copyparty_logo - - - - - - - - - - - - image/svg+xml - - copyparty_logo - github.com/9001/copyparty - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +copyparty_logo \ No newline at end of file diff --git a/.icons/desktop.svg b/.icons/desktop.svg index 77d231ce..2dee883a 100644 --- a/.icons/desktop.svg +++ b/.icons/desktop.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/.icons/devcontainers.svg b/.icons/devcontainers.svg index fb0443bd..72c2fc32 100644 --- a/.icons/devcontainers.svg +++ b/.icons/devcontainers.svg @@ -1,2 +1 @@ - -file_type_devcontainer \ No newline at end of file +file_type_devcontainer \ No newline at end of file diff --git a/.icons/digital-ocean.svg b/.icons/digital-ocean.svg index 6f10b237..7a3e8462 100644 --- a/.icons/digital-ocean.svg +++ b/.icons/digital-ocean.svg @@ -1,10 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/.icons/docker.svg b/.icons/docker.svg index 78e549ef..2a174a89 100644 --- a/.icons/docker.svg +++ b/.icons/docker.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/.icons/dotfiles.svg b/.icons/dotfiles.svg index c57ef859..bc582549 100644 --- a/.icons/dotfiles.svg +++ b/.icons/dotfiles.svg @@ -1,10 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/.icons/electric-plug-emoji.svg b/.icons/electric-plug-emoji.svg index 15743822..d8b03f26 100644 --- a/.icons/electric-plug-emoji.svg +++ b/.icons/electric-plug-emoji.svg @@ -1 +1 @@ -🔌 \ No newline at end of file +🔌 \ No newline at end of file diff --git a/.icons/exoscale.svg b/.icons/exoscale.svg index c56a6154..dc4f8462 100644 --- a/.icons/exoscale.svg +++ b/.icons/exoscale.svg @@ -1 +1 @@ -Artboard 1 \ No newline at end of file +Artboard 1 \ No newline at end of file diff --git a/.icons/filebrowser.svg b/.icons/filebrowser.svg index 5e78eccf..42bcc8db 100644 --- a/.icons/filebrowser.svg +++ b/.icons/filebrowser.svg @@ -1,147 +1 @@ - -image/svg+xml - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/.icons/fleet.svg b/.icons/fleet.svg index ba910eb9..494c2406 100644 --- a/.icons/fleet.svg +++ b/.icons/fleet.svg @@ -1,60 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/.icons/folder.svg b/.icons/folder.svg index b718dea5..1759bfc4 100644 --- a/.icons/folder.svg +++ b/.icons/folder.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/.icons/gateway.svg b/.icons/gateway.svg index b68e9490..238b4dda 100644 --- a/.icons/gateway.svg +++ b/.icons/gateway.svg @@ -1,64 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/.icons/gemini.svg b/.icons/gemini.svg index f1cf3575..33a088c1 100644 --- a/.icons/gemini.svg +++ b/.icons/gemini.svg @@ -1 +1 @@ -Gemini \ No newline at end of file +Gemini \ No newline at end of file diff --git a/.icons/git.svg b/.icons/git.svg index ceef1163..831d3100 100644 --- a/.icons/git.svg +++ b/.icons/git.svg @@ -1,3 +1 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/.icons/goose.svg b/.icons/goose.svg index cbbe8419..e73ebc35 100644 --- a/.icons/goose.svg +++ b/.icons/goose.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/.icons/hetzner.svg b/.icons/hetzner.svg index 74bb87c1..24ac242e 100644 --- a/.icons/hetzner.svg +++ b/.icons/hetzner.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/.icons/jetbrains.svg b/.icons/jetbrains.svg index b281f962..8a0d73e9 100644 --- a/.icons/jetbrains.svg +++ b/.icons/jetbrains.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/.icons/jfrog.svg b/.icons/jfrog.svg index e137700c..ad0050a3 100644 --- a/.icons/jfrog.svg +++ b/.icons/jfrog.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/.icons/jupyter.svg b/.icons/jupyter.svg index 38350dfe..9beddb14 100644 --- a/.icons/jupyter.svg +++ b/.icons/jupyter.svg @@ -1,14 +1 @@ - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/.icons/kasmvnc.svg b/.icons/kasmvnc.svg index 958f2832..33d25197 100644 --- a/.icons/kasmvnc.svg +++ b/.icons/kasmvnc.svg @@ -1,7 +1 @@ - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/.icons/kiro.svg b/.icons/kiro.svg index e132ead1..823400d2 100644 --- a/.icons/kiro.svg +++ b/.icons/kiro.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/.icons/kubernetes.svg b/.icons/kubernetes.svg index 42bb9229..7f07a7f5 100644 --- a/.icons/kubernetes.svg +++ b/.icons/kubernetes.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/.icons/lxc.svg b/.icons/lxc.svg index 0e8e118f..13cd5c59 100644 --- a/.icons/lxc.svg +++ b/.icons/lxc.svg @@ -1,21 +1 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/.icons/mux.svg b/.icons/mux.svg index a6ce26f1..3ee3276d 100644 --- a/.icons/mux.svg +++ b/.icons/mux.svg @@ -1,5 +1 @@ - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/.icons/nextflow.svg b/.icons/nextflow.svg index bcc10553..e9a38072 100644 --- a/.icons/nextflow.svg +++ b/.icons/nextflow.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/.icons/nexus-repository.svg b/.icons/nexus-repository.svg index ca135cd5..45a2bf60 100644 --- a/.icons/nexus-repository.svg +++ b/.icons/nexus-repository.svg @@ -1,5 +1 @@ - - - - \ No newline at end of file + \ No newline at end of file diff --git a/.icons/nomad.svg b/.icons/nomad.svg index b4dc91c7..ff7d00d0 100644 --- a/.icons/nomad.svg +++ b/.icons/nomad.svg @@ -1,2 +1 @@ - - + \ No newline at end of file diff --git a/.icons/openai.svg b/.icons/openai.svg index ba36fc2a..ffa802de 100644 --- a/.icons/openai.svg +++ b/.icons/openai.svg @@ -1,15 +1 @@ - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/.icons/opencode.svg b/.icons/opencode.svg index b79c7332..790fbc49 100644 --- a/.icons/opencode.svg +++ b/.icons/opencode.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/.icons/openwebui.svg b/.icons/openwebui.svg index 06f67645..b4ac79b0 100644 --- a/.icons/openwebui.svg +++ b/.icons/openwebui.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/.icons/perplexica.svg b/.icons/perplexica.svg index 0b3a85b5..e1e0226f 100644 --- a/.icons/perplexica.svg +++ b/.icons/perplexica.svg @@ -1,8 +1 @@ - - - - - + \ No newline at end of file diff --git a/.icons/personalize.svg b/.icons/personalize.svg index 76bc6780..3e402bc1 100644 --- a/.icons/personalize.svg +++ b/.icons/personalize.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/.icons/pgadmin.svg b/.icons/pgadmin.svg index 9fa5c4d5..f75dee65 100644 --- a/.icons/pgadmin.svg +++ b/.icons/pgadmin.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/.icons/positron.svg b/.icons/positron.svg index 590372e4..6d1282f5 100644 --- a/.icons/positron.svg +++ b/.icons/positron.svg @@ -1,12 +1 @@ - - - - - - - - - - - - + \ No newline at end of file diff --git a/.icons/proxmox.svg b/.icons/proxmox.svg index c18256e2..5758f8c5 100755 --- a/.icons/proxmox.svg +++ b/.icons/proxmox.svg @@ -1,137 +1 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/.icons/rdp.svg b/.icons/rdp.svg index a6722326..1e83ccdf 100644 --- a/.icons/rdp.svg +++ b/.icons/rdp.svg @@ -1,35 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/.icons/rustdesk.svg b/.icons/rustdesk.svg index 6c801233..fdc4e04c 100644 --- a/.icons/rustdesk.svg +++ b/.icons/rustdesk.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/.icons/scaleway.svg b/.icons/scaleway.svg index ebe1ddf2..488c90c1 100644 --- a/.icons/scaleway.svg +++ b/.icons/scaleway.svg @@ -1,2 +1 @@ - -Scaleway icon \ No newline at end of file +Scaleway icon \ No newline at end of file diff --git a/.icons/slack.svg b/.icons/slack.svg index fb55f724..1f9731dd 100644 --- a/.icons/slack.svg +++ b/.icons/slack.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/.icons/sourcegraph-amp.svg b/.icons/sourcegraph-amp.svg index 83777bd2..5466803a 100644 --- a/.icons/sourcegraph-amp.svg +++ b/.icons/sourcegraph-amp.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/.icons/tasks.svg b/.icons/tasks.svg index 67088c42..0ab7ea7f 100644 --- a/.icons/tasks.svg +++ b/.icons/tasks.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/.icons/vault.svg b/.icons/vault.svg index c90525cd..d9c882a1 100644 --- a/.icons/vault.svg +++ b/.icons/vault.svg @@ -1,2 +1 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/.icons/vsphere.svg b/.icons/vsphere.svg new file mode 100644 index 00000000..dd94f13d --- /dev/null +++ b/.icons/vsphere.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/.icons/windsurf.svg b/.icons/windsurf.svg index 2e4e4e49..f8164466 100644 --- a/.icons/windsurf.svg +++ b/.icons/windsurf.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/.icons/zed.svg b/.icons/zed.svg index 06b5c183..d5910ecc 100644 --- a/.icons/zed.svg +++ b/.icons/zed.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/cmd/.icons/docker.svg b/cmd/.icons/docker.svg new file mode 100644 index 00000000..2a174a89 --- /dev/null +++ b/cmd/.icons/docker.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cmd/.icons/goose.svg b/cmd/.icons/goose.svg new file mode 100644 index 00000000..e73ebc35 --- /dev/null +++ b/cmd/.icons/goose.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cmd/readmevalidation/codermodules_test.go b/cmd/readmevalidation/codermodules_test.go index 194a861e..e9e88bfc 100644 --- a/cmd/readmevalidation/codermodules_test.go +++ b/cmd/readmevalidation/codermodules_test.go @@ -1,22 +1,117 @@ package main import ( - _ "embed" + "os" + "path/filepath" "testing" ) -//go:embed testSamples/sampleReadmeBody.md -var testBody string +type readmeTestCase struct { + filePath string + shouldPass bool +} -func TestValidateCoderResourceReadmeBody(t *testing.T) { +func loadTestCases(t *testing.T, dir string, shouldPass bool) []readmeTestCase { + t.Helper() + files, err := os.ReadDir(dir) + if err != nil { + t.Fatalf("Failed to read directory %s: %v", dir, err) + } + + var testCases []readmeTestCase + for _, file := range files { + testCases = append(testCases, readmeTestCase{ + filePath: filepath.Join(dir, file.Name()), + shouldPass: shouldPass, + }) + } + return testCases +} + +func TestValidateModuleReadmes(t *testing.T) { t.Parallel() - t.Run("Parses a valid README body with zero issues", func(t *testing.T) { - t.Parallel() + testCases := append( + loadTestCases(t, "testSamples/modules/pass", true), + loadTestCases(t, "testSamples/modules/fail", false)..., + ) - errs := validateCoderModuleReadmeBody(testBody) - for _, e := range errs { - t.Error(e) - } - }) + for _, tc := range testCases { + t.Run(tc.filePath, func(t *testing.T) { + t.Parallel() + + content, err := os.ReadFile(tc.filePath) + if err != nil { + t.Fatalf("Failed to read file: %v", err) + } + + rm := readme{ + filePath: tc.filePath, + rawText: string(content), + } + + resource, errs := parseCoderResourceReadme("modules", rm) + if len(errs) != 0 { + if tc.shouldPass { + for _, e := range errs { + t.Errorf("Unexpected parsing error: %v", e) + } + } + return + } + + validationErrs := validateCoderModuleReadme(resource) + if tc.shouldPass && len(validationErrs) != 0 { + for _, e := range validationErrs { + t.Errorf("Unexpected validation error: %v", e) + } + } else if !tc.shouldPass && len(validationErrs) == 0 { + t.Error("Expected validation errors but got none") + } + }) + } +} + +func TestValidateTemplateReadmes(t *testing.T) { + t.Parallel() + + testCases := append( + loadTestCases(t, "testSamples/templates/pass", true), + loadTestCases(t, "testSamples/templates/fail", false)..., + ) + + for _, tc := range testCases { + t.Run(tc.filePath, func(t *testing.T) { + t.Parallel() + + content, err := os.ReadFile(tc.filePath) + if err != nil { + t.Fatalf("Failed to read file: %v", err) + } + + rm := readme{ + filePath: tc.filePath, + rawText: string(content), + } + + resource, errs := parseCoderResourceReadme("templates", rm) + if len(errs) != 0 { + if tc.shouldPass { + for _, e := range errs { + t.Errorf("Unexpected parsing error: %v", e) + } + } + return + } + + validationErrs := validateCoderModuleReadme(resource) + if tc.shouldPass && len(validationErrs) != 0 { + for _, e := range validationErrs { + t.Errorf("Unexpected validation error: %v", e) + } + } else if !tc.shouldPass && len(validationErrs) == 0 { + t.Error("Expected validation errors but got none") + } + }) + } } diff --git a/cmd/readmevalidation/coderresources.go b/cmd/readmevalidation/coderresources.go index 818d73c8..ee723265 100644 --- a/cmd/readmevalidation/coderresources.go +++ b/cmd/readmevalidation/coderresources.go @@ -82,33 +82,43 @@ func validateCoderResourceDescription(description string) error { return nil } -func isPermittedRelativeURL(checkURL string) bool { - // Would normally be skittish about having relative paths like this, but it should be safe because we have - // guarantees about the structure of the repo, and where this logic will run. - return strings.HasPrefix(checkURL, "./") || strings.HasPrefix(checkURL, "/") || strings.HasPrefix(checkURL, "../../../../.icons") +func isPermittedRelativeURL(checkURL string, readmeFilePath string) error { + // Icon URLs must reference the top-level .icons directory + expectedPrefix := "../../../../.icons/" + if !strings.HasPrefix(checkURL, expectedPrefix) { + return xerrors.Errorf("icon URL %q must reference the top-level .icons directory using %q", checkURL, expectedPrefix) + } + + // Resolve the path relative to the README file and check if it exists + readmeDir := path.Dir(readmeFilePath) + resolvedPath := path.Join(readmeDir, checkURL) + + if _, err := os.Stat(resolvedPath); err != nil { + if os.IsNotExist(err) { + return xerrors.Errorf("icon file does not exist at resolved path %q (referenced as %q)", resolvedPath, checkURL) + } + return xerrors.Errorf("error checking icon file at %q: %v", resolvedPath, err) + } + + return nil } -func validateCoderResourceIconURL(iconURL string) []error { +func validateCoderResourceIconURL(iconURL string, filePath string) []error { if iconURL == "" { return []error{xerrors.New("icon URL cannot be empty")} } var errs []error - // If the URL does not have a relative path. - if !strings.HasPrefix(iconURL, ".") && !strings.HasPrefix(iconURL, "/") { - if _, err := url.ParseRequestURI(iconURL); err != nil { - errs = append(errs, xerrors.New("absolute icon URL is not correctly formatted")) - } - if strings.Contains(iconURL, "?") { - errs = append(errs, xerrors.New("icon URLs cannot contain query parameters")) - } + // Reject absolute HTTP/HTTPS URLs - all icons must be local to the repository + if strings.HasPrefix(iconURL, "http://") || strings.HasPrefix(iconURL, "https://") { + errs = append(errs, xerrors.Errorf("icon URL must reference the top-level .icons directory, not an absolute URL %q", iconURL)) return errs } - // If the URL has a relative path. - if !isPermittedRelativeURL(iconURL) { - errs = append(errs, xerrors.Errorf("relative icon URL %q must either be scoped to that module's directory, or the top-level /.icons directory (this can usually be done by starting the path with \"../../../.icons\")", iconURL)) + // Validate that the icon references ../../../../.icons/ and exists + if err := isPermittedRelativeURL(iconURL, filePath); err != nil { + errs = append(errs, err) } return errs @@ -153,7 +163,7 @@ func validateCoderResourceFrontmatter(resourceType string, filePath string, fm c errs = append(errs, addFilePathToError(filePath, err)) } - for _, err := range validateCoderResourceIconURL(fm.IconURL) { + for _, err := range validateCoderResourceIconURL(fm.IconURL, filePath) { errs = append(errs, addFilePathToError(filePath, err)) } for _, err := range validateSupportedOperatingSystems(fm.OperatingSystems) { diff --git a/cmd/readmevalidation/testSamples/modules/fail/absoluteIconPath.md b/cmd/readmevalidation/testSamples/modules/fail/absoluteIconPath.md new file mode 100644 index 00000000..d68264b0 --- /dev/null +++ b/cmd/readmevalidation/testSamples/modules/fail/absoluteIconPath.md @@ -0,0 +1,22 @@ +--- +display_name: "Goose" +description: "Run the Goose agent in your workspace to generate code and perform tasks" +icon: "https://github.com/coder/registry/pull/599.svg" +verified: false +tags: ["ai", "agent"] +--- + +# Goose + +Run the [Goose](https://block.github.io/goose/) agent in your workspace to generate code and perform tasks. + +```tf +module "goose" { + source = "registry.coder.com/coder/goose/coder" + version = "1.0.31" + agent_id = coder_agent.main.id + folder = "/home/coder" + install_goose = true + goose_version = "v1.0.16" +} +``` diff --git a/cmd/readmevalidation/testSamples/modules/fail/wrongPathFormat.md b/cmd/readmevalidation/testSamples/modules/fail/wrongPathFormat.md new file mode 100644 index 00000000..e9c671c4 --- /dev/null +++ b/cmd/readmevalidation/testSamples/modules/fail/wrongPathFormat.md @@ -0,0 +1,19 @@ +--- +display_name: "Wrong Path" +description: "Test module with wrong icon path format" +icon: "../../../../.icons/invalid.svg" +verified: false +tags: ["test"] +--- + +# Wrong Path + +This should fail validation. + +```tf +module "test" { + source = "registry.coder.com/coder/test/coder" + version = "1.0.0" + agent_id = coder_agent.main.id +} +``` diff --git a/cmd/readmevalidation/testSamples/modules/pass/sampleModuleReadme.md b/cmd/readmevalidation/testSamples/modules/pass/sampleModuleReadme.md new file mode 100644 index 00000000..b0db1f26 --- /dev/null +++ b/cmd/readmevalidation/testSamples/modules/pass/sampleModuleReadme.md @@ -0,0 +1,56 @@ +--- +display_name: "Docker Container" +description: "Develop in a container on a Docker host" +icon: "../../../../.icons/docker.svg" +verified: true +tags: ["docker", "container"] +supported_os: ["linux", "macos"] +--- + +# Docker Container + +Develop in a Docker container on a remote Docker host. + +```tf +terraform { + required_providers { + coder = { + source = "coder/coder" + version = "~> 1.0" + } + docker = { + source = "kreuzwerker/docker" + version = "~> 3.0" + } + } +} + +provider "docker" {} + +provider "coder" {} + +data "coder_workspace" "me" {} + +resource "coder_agent" "main" { + os = "linux" + arch = "amd64" +} + +resource "docker_container" "workspace" { + image = "codercom/enterprise-base:ubuntu" + name = "coder-${data.coder_workspace.me.owner}-${data.coder_workspace.me.name}" + + env = ["CODER_AGENT_TOKEN=${coder_agent.main.token}"] +} +``` + +## Getting Started + +This template creates a Docker container on your Docker host. You'll need: + +- A Docker host accessible from your Coder deployment +- The Docker provider configured with appropriate credentials + +## Customization + +You can customize the container image, resources, and configuration to match your needs. diff --git a/cmd/readmevalidation/testSamples/sampleReadmeBody.md b/cmd/readmevalidation/testSamples/sampleReadmeBody.md deleted file mode 100644 index b96662af..00000000 --- a/cmd/readmevalidation/testSamples/sampleReadmeBody.md +++ /dev/null @@ -1,121 +0,0 @@ -# Goose - -Run the [Goose](https://block.github.io/goose/) agent in your workspace to generate code and perform tasks. - -```tf -module "goose" { - source = "registry.coder.com/coder/goose/coder" - version = "1.0.31" - agent_id = coder_agent.main.id - folder = "/home/coder" - install_goose = true - goose_version = "v1.0.16" -} -``` - -## Prerequisites - -- `screen` must be installed in your workspace to run Goose in the background -- You must add the [Coder Login](https://registry.coder.com/modules/coder-login) module to your template - -The `codercom/oss-dogfood:latest` container image can be used for testing on container-based workspaces. - -## Examples - -Your workspace must have `screen` installed to use this. - -### Run in the background and report tasks (Experimental) - -> This functionality is in early access as of Coder v2.21 and is still evolving. -> For now, we recommend testing it in a demo or staging environment, -> rather than deploying to production -> -> Learn more in [the Coder documentation](https://coder.com/docs/tutorials/ai-agents) -> -> Join our [Discord channel](https://discord.gg/coder) or -> [contact us](https://coder.com/contact) to get help or share feedback. - -```tf -module "coder-login" { - count = data.coder_workspace.me.start_count - source = "registry.coder.com/coder/coder-login/coder" - version = "1.0.15" - agent_id = coder_agent.main.id -} - -variable "anthropic_api_key" { - type = string - description = "The Anthropic API key" - sensitive = true -} - -data "coder_parameter" "ai_prompt" { - type = "string" - name = "AI Prompt" - default = "" - description = "Write a prompt for Goose" - mutable = true -} - -# Set the prompt and system prompt for Goose via environment variables -resource "coder_agent" "main" { - # ... - env = { - GOOSE_SYSTEM_PROMPT = <<-EOT - You are a helpful assistant that can help write code. - - Run all long running tasks (e.g. npm run dev) in the background and not in the foreground. - - Periodically check in on background tasks. - - Notify Coder of the status of the task before and after your steps. - EOT - GOOSE_TASK_PROMPT = data.coder_parameter.ai_prompt.value - - # An API key is required for experiment_auto_configure - # See https://block.github.io/goose/docs/getting-started/providers - ANTHROPIC_API_KEY = var.anthropic_api_key # or use a coder_parameter - } -} - -module "goose" { - count = data.coder_workspace.me.start_count - source = "registry.coder.com/coder/goose/coder" - version = "1.0.31" - agent_id = coder_agent.main.id - folder = "/home/coder" - install_goose = true - goose_version = "v1.0.16" - - # Enable experimental features - experiment_report_tasks = true - - # Run Goose in the background - experiment_use_screen = true - - # Avoid configuring Goose manually - experiment_auto_configure = true - - # Required for experiment_auto_configure - experiment_goose_provider = "anthropic" - experiment_goose_model = "claude-3-5-sonnet-latest" -} -``` - -## Run standalone - -Run Goose as a standalone app in your workspace. This will install Goose and run it directly without using screen or any task reporting to the Coder UI. - -```tf -module "goose" { - source = "registry.coder.com/coder/goose/coder" - version = "1.0.31" - agent_id = coder_agent.main.id - folder = "/home/coder" - install_goose = true - goose_version = "v1.0.16" - - # Icon is not available in Coder v2.20 and below, so we'll use a custom icon URL - icon = "https://raw.githubusercontent.com/block/goose/refs/heads/main/ui/desktop/src/images/icon.svg" -} -``` diff --git a/cmd/readmevalidation/testSamples/templates/fail/absoluteIconPath.md b/cmd/readmevalidation/testSamples/templates/fail/absoluteIconPath.md new file mode 100644 index 00000000..879a2a83 --- /dev/null +++ b/cmd/readmevalidation/testSamples/templates/fail/absoluteIconPath.md @@ -0,0 +1,27 @@ +--- +display_name: "Docker Container" +description: "Develop in a container on a Docker host" +icon: "https://github.com/coder/registry/pull/599.jpeg" +verified: true +tags: ["docker", "container"] +supported_os: ["linux", "macos"] +--- + +# Docker Container + +Develop in a Docker container on a remote Docker host. + +```tf +terraform { + required_providers { + coder = { + source = "coder/coder" + version = "~> 1.0" + } + docker = { + source = "kreuzwerker/docker" + version = "~> 3.0" + } + } +} +``` diff --git a/cmd/readmevalidation/testSamples/templates/fail/wrongPathFormat.md b/cmd/readmevalidation/testSamples/templates/fail/wrongPathFormat.md new file mode 100644 index 00000000..7078cfb8 --- /dev/null +++ b/cmd/readmevalidation/testSamples/templates/fail/wrongPathFormat.md @@ -0,0 +1,20 @@ +--- +display_name: "Docker Container" +description: "Develop in a container on a Docker host" +icon: "../../../../.icons/invalid.svg" +verified: true +tags: ["docker", "container"] +supported_os: ["linux", "macos"] +--- + +# Wrong Path + +This should fail validation. + +```tf +module "test" { + source = "registry.coder.com/coder/test/coder" + version = "1.0.0" + agent_id = coder_agent.main.id +} +``` diff --git a/cmd/readmevalidation/testSamples/templates/pass/sampleTemplateReadme.md b/cmd/readmevalidation/testSamples/templates/pass/sampleTemplateReadme.md new file mode 100644 index 00000000..b0db1f26 --- /dev/null +++ b/cmd/readmevalidation/testSamples/templates/pass/sampleTemplateReadme.md @@ -0,0 +1,56 @@ +--- +display_name: "Docker Container" +description: "Develop in a container on a Docker host" +icon: "../../../../.icons/docker.svg" +verified: true +tags: ["docker", "container"] +supported_os: ["linux", "macos"] +--- + +# Docker Container + +Develop in a Docker container on a remote Docker host. + +```tf +terraform { + required_providers { + coder = { + source = "coder/coder" + version = "~> 1.0" + } + docker = { + source = "kreuzwerker/docker" + version = "~> 3.0" + } + } +} + +provider "docker" {} + +provider "coder" {} + +data "coder_workspace" "me" {} + +resource "coder_agent" "main" { + os = "linux" + arch = "amd64" +} + +resource "docker_container" "workspace" { + image = "codercom/enterprise-base:ubuntu" + name = "coder-${data.coder_workspace.me.owner}-${data.coder_workspace.me.name}" + + env = ["CODER_AGENT_TOKEN=${coder_agent.main.token}"] +} +``` + +## Getting Started + +This template creates a Docker container on your Docker host. You'll need: + +- A Docker host accessible from your Coder deployment +- The Docker provider configured with appropriate credentials + +## Customization + +You can customize the container image, resources, and configuration to match your needs. diff --git a/registry/anis/.images/avatar.png b/registry/anis/.images/avatar.png new file mode 100644 index 00000000..3fb22a10 Binary files /dev/null and b/registry/anis/.images/avatar.png differ diff --git a/registry/anis/README.md b/registry/anis/README.md new file mode 100644 index 00000000..20d5e708 --- /dev/null +++ b/registry/anis/README.md @@ -0,0 +1,12 @@ +--- +display_name: "Anis Khalfallah" +bio: "DevOps Engineer" +github: "aniskhalfallah" +avatar: "./.images/avatar.png" +linkedin: "https://www.linkedin.com/in/khalfallah-anis/" +status: "community" +--- + +# Anis KHALFALLAH + +DevOps Engineer diff --git a/registry/anis/templates/vmware-linux/README.md b/registry/anis/templates/vmware-linux/README.md new file mode 100644 index 00000000..e56957fc --- /dev/null +++ b/registry/anis/templates/vmware-linux/README.md @@ -0,0 +1,81 @@ +--- +display_name: VMware vSphere VM (Linux) +description: Provision VMware vSphere virtual machines as Coder workspaces +icon: ../../../../.icons/vsphere.svg +verified: false +tags: [vm, linux, vmware, vsphere] +--- + +# Summary + +Provision VMware vSphere virtual machines as [Coder workspaces](https://coder.com/docs/workspaces) using this Terraform template. + +## Prerequisites + +To deploy Coder workspaces on VMware vSphere, you'll need the following: + +### vSphere Resources + +Before deploying, ensure your vSphere environment has: + +- A **vSphere Datacenter** already created +- A **Compute Cluster** within that datacenter +- A **Datastore** with sufficient storage capacity +- A **Network** (port group) accessible by VMs +- A **VM Template** with Ubuntu and cloud-init configured + +### VM Template Requirements + +Your VM template must have + +- **cloud-init** installed and configured for VMware datasource + +### vSphere Authentication + +You'll need the following credentials: + +- **vSphere Server** (hostname or IP) +- **Username** +- **Password** +- **Datacenter Name** +- **Cluster Name** +- **Datastore Name** +- **Network Name** +- **VM Template Name** + +[VMware Provider Documentation](https://registry.terraform.io/providers/hashicorp/vsphere/latest/docs) + +--- + +## Example `.tfvars` File + +```hcl +vsphere_server = "vcenter.example.com" +vsphere_username = "administrator@vsphere.local" +vsphere_password = "YourSecurePassword123!" +vsphere_datacenter = "DC01" +cluster_name = "Cluster01" +vsphere_datastore = "datastore1" +vsphere_network = "VM Network" +vm_template = "ubuntu-22.04-cloud-init-template" +``` + +--- + +## Architecture + +This template creates: + +- A **vSphere Virtual Machine** per workspace +- **Dynamic resource allocation** (CPU, memory configurable by users) +- **Two disks**: root disk (from template) and separate home volume +- **Coder agent** installed via cloud-init +- **code-server** for browser-based VS Code access + +## Workspace Parameters + +Users can customize their workspace with: + +- **VCPUs**: 1, 2, 4, or 8 virtual CPUs +- **Memory**: 1, 2, 4, 8, 16, or 32 GB RAM +- **Home Volume Size**: 10-1024 GB (default: 20 GB) diff --git a/registry/anis/templates/vmware-linux/cloud-init/cloud-config.yaml.tftpl b/registry/anis/templates/vmware-linux/cloud-init/cloud-config.yaml.tftpl new file mode 100644 index 00000000..5faeab3d --- /dev/null +++ b/registry/anis/templates/vmware-linux/cloud-init/cloud-config.yaml.tftpl @@ -0,0 +1,56 @@ +#cloud-config +hostname: ${hostname} +users: + - name: ${username} + sudo: ["ALL=(ALL) NOPASSWD:ALL"] + groups: sudo + shell: /bin/bash +packages: + - git + - curl + - wget + - unzip +disk_setup: + /dev/sdb: + table_type: "gpt" + layout: true + overwrite: false +fs_setup: + - label: ${home_volume_label} + filesystem: ext4 + device: /dev/sdb + partition: auto +mounts: + - ["/dev/sdb", "/home/${username}", "ext4", "defaults", "0", "2"] +write_files: + - path: /opt/coder/init + permissions: "0755" + encoding: b64 + content: ${init_script} + - path: /etc/systemd/system/coder-agent.service + permissions: "0644" + content: | + [Unit] + Description=Coder Agent + After=network-online.target + Wants=network-online.target + + [Service] + User=${username} + ExecStart=/opt/coder/init + Environment=CODER_AGENT_TOKEN=${coder_agent_token} + Restart=always + RestartSec=10 + TimeoutStopSec=90 + KillMode=process + + OOMScoreAdjust=-1000 + SyslogIdentifier=coder-agent + + [Install] + WantedBy=multi-user.target +runcmd: + - mkdir -p /home/${username} + - chown ${username}:${username} /home/${username} + - systemctl enable coder-agent + - systemctl start coder-agent diff --git a/registry/anis/templates/vmware-linux/main.tf b/registry/anis/templates/vmware-linux/main.tf new file mode 100644 index 00000000..13385493 --- /dev/null +++ b/registry/anis/templates/vmware-linux/main.tf @@ -0,0 +1,245 @@ +terraform { + required_providers { + coder = { + source = "coder/coder" + } + vsphere = { + source = "vmware/vsphere" + } + } +} + + +provider "vsphere" { + user = var.vsphere_username + password = var.vsphere_password + vsphere_server = var.vsphere_server + + allow_unverified_ssl = var.unverified_ssl +} + +variable "vsphere_username" { + type = string + default = "" +} +variable "vsphere_password" { + type = string + default = "" + sensitive = true +} +variable "vsphere_server" { + type = string + default = "" +} +variable "datacenter_name" { + type = string + default = "" +} +variable "cluster_name" { + type = string + default = "" +} +variable "datastore_name" { + type = string + default = "" + sensitive = true +} +variable "network_name" { + type = string + default = "" +} +variable "vm_template" { + type = string + default = "" +} +variable "unverified_ssl" { + type = bool + default = true +} + +locals { + vm_name = "coder-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}" + root_disk_label = substr("${local.vm_name}-root", 0, 32) + home_volume_label = substr("${local.vm_name}-home", 0, 32) +} + +data "coder_parameter" "instance_vcpus" { + name = "instance_vcpus" + display_name = "vCPUs" + description = "Number of vCPUs " + type = "number" + default = 1 + mutable = true + option { + name = "1 vCPU" + value = 1 + } + option { + name = "2 vCPUs" + value = 2 + } + option { + name = "4 vCPUs" + value = 4 + } + option { + name = "8 vCPUs" + value = 8 + } +} + +data "coder_parameter" "instance_memory" { + name = "instance_memory" + display_name = "Memory (GB)" + description = "Amount of RAM" + type = "number" + default = 2048 + mutable = true + option { + name = "1 GB" + value = 1024 + } + option { + name = "2 GB" + value = 2048 + } + option { + name = "4 GB" + value = 4096 + } + option { + name = "8 GB" + value = 8192 + } + option { + name = "16 GB" + value = 16384 + } + option { + name = "32 GB" + value = 32768 + } +} + +data "coder_parameter" "home_volume_size" { + name = "home_volume_size" + display_name = "Home Volume Size (GB)" + description = "How large would you like your home volume to be (in GB)?" + type = "number" + default = 20 + mutable = true + + validation { + min = 10 + max = 1024 + monotonic = "increasing" + } +} + +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +resource "coder_agent" "main" { + os = "linux" + arch = "amd64" + + metadata { + key = "cpu" + display_name = "CPU Usage" + interval = 5 + timeout = 5 + script = "coder stat cpu" + } + metadata { + key = "memory" + display_name = "Memory Usage" + interval = 5 + timeout = 5 + script = "coder stat mem" + } + metadata { + key = "home" + display_name = "Home Usage" + interval = 600 # every 10 minutes + timeout = 30 # df can take a while on large filesystems + script = "coder stat disk --path /home/${lower(data.coder_workspace_owner.me.name)}" + } +} + +data "vsphere_datacenter" "dc" { + name = var.datacenter_name +} + +data "vsphere_datastore" "datastore" { + name = var.datastore_name + datacenter_id = data.vsphere_datacenter.dc.id +} + +data "vsphere_compute_cluster" "cluster" { + name = var.cluster_name + datacenter_id = data.vsphere_datacenter.dc.id +} + +data "vsphere_network" "network" { + name = var.network_name + datacenter_id = data.vsphere_datacenter.dc.id +} + +data "vsphere_virtual_machine" "template" { + name = var.vm_template + datacenter_id = data.vsphere_datacenter.dc.id +} + +locals { + cloud_init_config = templatefile("cloud-init/cloud-config.yaml.tftpl", { + hostname = local.vm_name + username = lower(data.coder_workspace_owner.me.name) + home_volume_label = local.home_volume_label + init_script = base64encode(coder_agent.main.init_script) + coder_agent_token = coder_agent.main.token + }) +} + +resource "vsphere_virtual_machine" "workspace" { + name = local.vm_name + firmware = data.vsphere_virtual_machine.template.firmware + resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id + datastore_id = data.vsphere_datastore.datastore.id + + num_cpus = data.coder_parameter.instance_vcpus.value + memory = data.coder_parameter.instance_memory.value + guest_id = data.vsphere_virtual_machine.template.guest_id + + scsi_type = data.vsphere_virtual_machine.template.scsi_type + + network_interface { + network_id = data.vsphere_network.network.id + adapter_type = data.vsphere_virtual_machine.template.network_interface_types[0] + } + + disk { + label = "disk0" + size = data.vsphere_virtual_machine.template.disks.0.size + } + disk { + label = local.home_volume_label + size = data.coder_parameter.home_volume_size.value + unit_number = 1 + } + extra_config = { + "guestinfo.userdata" = base64encode(local.cloud_init_config) + "guestinfo.userdata.encoding" = "base64" + } + clone { + template_uuid = data.vsphere_virtual_machine.template.id + } +} + +module "code-server" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/code-server/coder" + version = "~> 1.0" + + agent_id = coder_agent.main.id + order = 1 +} \ No newline at end of file diff --git a/registry/coder-labs/.images/avatar.svg b/registry/coder-labs/.images/avatar.svg index 8040fb06..676ccbf4 100644 --- a/registry/coder-labs/.images/avatar.svg +++ b/registry/coder-labs/.images/avatar.svg @@ -1,5 +1 @@ - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/registry/coder/.images/avatar.svg b/registry/coder/.images/avatar.svg index 60d7eff6..5db19df6 100644 --- a/registry/coder/.images/avatar.svg +++ b/registry/coder/.images/avatar.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/registry/coder/.images/aws-devcontainer-architecture.svg b/registry/coder/.images/aws-devcontainer-architecture.svg index be66c3f1..c9673a37 100644 --- a/registry/coder/.images/aws-devcontainer-architecture.svg +++ b/registry/coder/.images/aws-devcontainer-architecture.svg @@ -1,8 +1 @@ -AWSAWSHostingHostingVirtual MachineVirtual MachineLinux HardwareLinux HardwareCoder WorkspaceCoder WorkspaceDevcontainerDevcontainerenvbuilder created filesystemenvbuilder created filesystemA Clone of your repoA Clone of your repoSource codeSource codeLanguagesLanguagesPython. Go, etcPython. Go, etcToolingToolingExtensions, linting, formatting, etcExtensions, linting, formatting, etcCPUsCPUsDisk StorageDisk StorageCode EditorCode EditorVS Code DesktopVS Code DesktopLocal InstallationLocal InstallationVS Code DesktopVS Code DesktopLocal InstallationLocal Installationcode-servercode-serverA web IDEA web IDEJetBrains GatewayJetBrains GatewayLocal InstallationLocal InstallationCommand LineCommand LineSSH via Coder CLISSH via Coder CLI \ No newline at end of file +AWSAWSHostingHostingVirtual MachineVirtual MachineLinux HardwareLinux HardwareCoder WorkspaceCoder WorkspaceDevcontainerDevcontainerenvbuilder created filesystemenvbuilder created filesystemA Clone of your repoA Clone of your repoSource codeSource codeLanguagesLanguagesPython. Go, etcPython. Go, etcToolingToolingExtensions, linting, formatting, etcExtensions, linting, formatting, etcCPUsCPUsDisk StorageDisk StorageCode EditorCode EditorVS Code DesktopVS Code DesktopLocal InstallationLocal InstallationVS Code DesktopVS Code DesktopLocal InstallationLocal Installationcode-servercode-serverA web IDEA web IDEJetBrains GatewayJetBrains GatewayLocal InstallationLocal InstallationCommand LineCommand LineSSH via Coder CLISSH via Coder CLI \ No newline at end of file diff --git a/registry/coder/.images/gcp-devcontainer-architecture.svg b/registry/coder/.images/gcp-devcontainer-architecture.svg index c3dfd645..a53d48a7 100644 --- a/registry/coder/.images/gcp-devcontainer-architecture.svg +++ b/registry/coder/.images/gcp-devcontainer-architecture.svg @@ -1,8 +1 @@ -GCPGCPHostingHostingVirtual MachineVirtual MachineLinux HardwareLinux HardwareCoder WorkspaceCoder WorkspaceDevcontainerDevcontainerenvbuilder created filesystemenvbuilder created filesystemA Clone of your repoA Clone of your repoSource codeSource codeLanguagesLanguagesPython. Go, etcPython. Go, etcToolingToolingExtensions, linting, formatting, etcExtensions, linting, formatting, etcCPUsCPUsDisk StorageDisk StorageCode EditorCode EditorVS Code DesktopVS Code DesktopLocal InstallationLocal InstallationVS Code DesktopVS Code DesktopLocal InstallationLocal Installationcode-servercode-serverA web IDEA web IDEJetBrains GatewayJetBrains GatewayLocal InstallationLocal InstallationCommand LineCommand LineSSH via Coder CLISSH via Coder CLI \ No newline at end of file +GCPGCPHostingHostingVirtual MachineVirtual MachineLinux HardwareLinux HardwareCoder WorkspaceCoder WorkspaceDevcontainerDevcontainerenvbuilder created filesystemenvbuilder created filesystemA Clone of your repoA Clone of your repoSource codeSource codeLanguagesLanguagesPython. Go, etcPython. Go, etcToolingToolingExtensions, linting, formatting, etcExtensions, linting, formatting, etcCPUsCPUsDisk StorageDisk StorageCode EditorCode EditorVS Code DesktopVS Code DesktopLocal InstallationLocal InstallationVS Code DesktopVS Code DesktopLocal InstallationLocal Installationcode-servercode-serverA web IDEA web IDEJetBrains GatewayJetBrains GatewayLocal InstallationLocal InstallationCommand LineCommand LineSSH via Coder CLISSH via Coder CLI \ No newline at end of file diff --git a/registry/coder/modules/claude-code/README.md b/registry/coder/modules/claude-code/README.md index 9a9c062f..c0004504 100644 --- a/registry/coder/modules/claude-code/README.md +++ b/registry/coder/modules/claude-code/README.md @@ -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 = "4.2.9" + version = "4.3.0" agent_id = coder_agent.main.id workdir = "/home/coder/project" claude_api_key = "xxxx-xxxxx-xxxx" @@ -44,16 +44,12 @@ This example shows how to configure the Claude Code module to run the agent behi ```tf module "claude-code" { - source = "dev.registry.coder.com/coder/claude-code/coder" - version = "4.2.9" - agent_id = coder_agent.main.id - workdir = "/home/coder/project" - enable_boundary = true - boundary_version = "main" - boundary_log_dir = "/tmp/boundary_logs" - boundary_log_level = "WARN" - boundary_additional_allowed_urls = ["GET *google.com"] - boundary_proxy_port = "8087" + source = "dev.registry.coder.com/coder/claude-code/coder" + version = "4.3.0" + agent_id = coder_agent.main.id + workdir = "/home/coder/project" + enable_boundary = true + boundary_version = "v0.5.1" } ``` @@ -72,7 +68,7 @@ data "coder_parameter" "ai_prompt" { module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "4.2.9" + version = "4.3.0" agent_id = coder_agent.main.id workdir = "/home/coder/project" @@ -108,7 +104,7 @@ Run and configure Claude Code as a standalone CLI in your workspace. ```tf module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "4.2.9" + version = "4.3.0" agent_id = coder_agent.main.id workdir = "/home/coder/project" install_claude_code = true @@ -130,7 +126,7 @@ variable "claude_code_oauth_token" { module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "4.2.9" + version = "4.3.0" agent_id = coder_agent.main.id workdir = "/home/coder/project" claude_code_oauth_token = var.claude_code_oauth_token @@ -203,7 +199,7 @@ resource "coder_env" "bedrock_api_key" { module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "4.2.9" + version = "4.3.0" agent_id = coder_agent.main.id workdir = "/home/coder/project" model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0" @@ -260,7 +256,7 @@ resource "coder_env" "google_application_credentials" { module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "4.2.9" + version = "4.3.0" agent_id = coder_agent.main.id workdir = "/home/coder/project" model = "claude-sonnet-4@20250514" diff --git a/registry/coder/modules/claude-code/main.tf b/registry/coder/modules/claude-code/main.tf index bd160ded..62f24c36 100644 --- a/registry/coder/modules/claude-code/main.tf +++ b/registry/coder/modules/claude-code/main.tf @@ -210,42 +210,6 @@ variable "boundary_version" { default = "main" } -variable "boundary_log_dir" { - type = string - description = "Directory for boundary logs" - default = "/tmp/boundary_logs" -} - -variable "boundary_log_level" { - type = string - description = "Log level for boundary process" - default = "WARN" -} - -variable "boundary_additional_allowed_urls" { - type = list(string) - description = "Additional URLs to allow through boundary (in addition to default allowed URLs)" - default = [] -} - -variable "boundary_proxy_port" { - type = string - description = "Port for HTTP Proxy used by Boundary" - default = "8087" -} - -variable "enable_boundary_pprof" { - type = bool - description = "Whether to enable coder boundary pprof server" - default = false -} - -variable "boundary_pprof_port" { - type = string - description = "Port for pprof server used by Boundary" - default = "6067" -} - variable "compile_boundary_from_source" { type = bool description = "Whether to compile boundary from source instead of using the official install script" @@ -374,12 +338,6 @@ module "agentapi" { ARG_REPORT_TASKS='${var.report_tasks}' \ ARG_ENABLE_BOUNDARY='${var.enable_boundary}' \ ARG_BOUNDARY_VERSION='${var.boundary_version}' \ - ARG_BOUNDARY_LOG_DIR='${var.boundary_log_dir}' \ - ARG_BOUNDARY_LOG_LEVEL='${var.boundary_log_level}' \ - ARG_BOUNDARY_ADDITIONAL_ALLOWED_URLS='${join("|", var.boundary_additional_allowed_urls)}' \ - ARG_BOUNDARY_PROXY_PORT='${var.boundary_proxy_port}' \ - ARG_ENABLE_BOUNDARY_PPROF='${var.enable_boundary_pprof}' \ - ARG_BOUNDARY_PPROF_PORT='${var.boundary_pprof_port}' \ ARG_COMPILE_FROM_SOURCE='${var.compile_boundary_from_source}' \ ARG_CODER_HOST='${local.coder_host}' \ /tmp/start.sh diff --git a/registry/coder/modules/claude-code/main.tftest.hcl b/registry/coder/modules/claude-code/main.tftest.hcl index adfca6d2..dd9e66a6 100644 --- a/registry/coder/modules/claude-code/main.tftest.hcl +++ b/registry/coder/modules/claude-code/main.tftest.hcl @@ -192,10 +192,9 @@ run "test_claude_code_with_boundary" { command = plan variables { - agent_id = "test-agent-boundary" - workdir = "/home/coder/boundary-test" - enable_boundary = true - boundary_log_dir = "/tmp/test-boundary-logs" + agent_id = "test-agent-boundary" + workdir = "/home/coder/boundary-test" + enable_boundary = true } assert { @@ -203,11 +202,6 @@ run "test_claude_code_with_boundary" { error_message = "Boundary should be enabled" } - assert { - condition = var.boundary_log_dir == "/tmp/test-boundary-logs" - error_message = "Boundary log dir should be set correctly" - } - assert { condition = local.coder_host != "" error_message = "Coder host should be extracted from access URL" diff --git a/registry/coder/modules/claude-code/scripts/start.sh b/registry/coder/modules/claude-code/scripts/start.sh index 061dce67..e14b26a6 100644 --- a/registry/coder/modules/claude-code/scripts/start.sh +++ b/registry/coder/modules/claude-code/scripts/start.sh @@ -16,11 +16,6 @@ ARG_AI_PROMPT=$(echo -n "${ARG_AI_PROMPT:-}" | base64 -d) ARG_REPORT_TASKS=${ARG_REPORT_TASKS:-true} ARG_ENABLE_BOUNDARY=${ARG_ENABLE_BOUNDARY:-false} ARG_BOUNDARY_VERSION=${ARG_BOUNDARY_VERSION:-"main"} -ARG_BOUNDARY_LOG_DIR=${ARG_BOUNDARY_LOG_DIR:-"/tmp/boundary_logs"} -ARG_BOUNDARY_LOG_LEVEL=${ARG_BOUNDARY_LOG_LEVEL:-"WARN"} -ARG_BOUNDARY_PROXY_PORT=${ARG_BOUNDARY_PROXY_PORT:-"8087"} -ARG_ENABLE_BOUNDARY_PPROF=${ARG_ENABLE_BOUNDARY_PPROF:-false} -ARG_BOUNDARY_PPROF_PORT=${ARG_BOUNDARY_PPROF_PORT:-"6067"} ARG_COMPILE_FROM_SOURCE=${ARG_COMPILE_FROM_SOURCE:-false} ARG_CODER_HOST=${ARG_CODER_HOST:-} @@ -36,9 +31,6 @@ printf "ARG_WORKDIR: %s\n" "$ARG_WORKDIR" printf "ARG_REPORT_TASKS: %s\n" "$ARG_REPORT_TASKS" printf "ARG_ENABLE_BOUNDARY: %s\n" "$ARG_ENABLE_BOUNDARY" printf "ARG_BOUNDARY_VERSION: %s\n" "$ARG_BOUNDARY_VERSION" -printf "ARG_BOUNDARY_LOG_DIR: %s\n" "$ARG_BOUNDARY_LOG_DIR" -printf "ARG_BOUNDARY_LOG_LEVEL: %s\n" "$ARG_BOUNDARY_LOG_LEVEL" -printf "ARG_BOUNDARY_PROXY_PORT: %s\n" "$ARG_BOUNDARY_PROXY_PORT" printf "ARG_COMPILE_FROM_SOURCE: %s\n" "$ARG_COMPILE_FROM_SOURCE" printf "ARG_CODER_HOST: %s\n" "$ARG_CODER_HOST" @@ -229,35 +221,11 @@ function start_agentapi() { if [ "${ARG_ENABLE_BOUNDARY:-false}" = "true" ]; then install_boundary - mkdir -p "$ARG_BOUNDARY_LOG_DIR" printf "Starting with coder boundary enabled\n" - # Build boundary args with conditional --unprivileged flag - BOUNDARY_ARGS=(--log-dir "$ARG_BOUNDARY_LOG_DIR") # Add default allowed URLs BOUNDARY_ARGS+=(--allow "domain=anthropic.com" --allow "domain=registry.npmjs.org" --allow "domain=sentry.io" --allow "domain=claude.ai" --allow "domain=$ARG_CODER_HOST") - # Add any additional allowed URLs from the variable - if [ -n "$ARG_BOUNDARY_ADDITIONAL_ALLOWED_URLS" ]; then - IFS='|' read -ra ADDITIONAL_URLS <<< "$ARG_BOUNDARY_ADDITIONAL_ALLOWED_URLS" - for url in "${ADDITIONAL_URLS[@]}"; do - # Quote the URL to preserve spaces within the allow rule - BOUNDARY_ARGS+=(--allow "$url") - done - fi - - # Set HTTP Proxy port used by Boundary - BOUNDARY_ARGS+=(--proxy-port "$ARG_BOUNDARY_PROXY_PORT") - - # Set log level for boundary - BOUNDARY_ARGS+=(--log-level "$ARG_BOUNDARY_LOG_LEVEL") - - if [ "${ARG_ENABLE_BOUNDARY_PPROF:-false}" = "true" ]; then - # Enable boundary pprof server on specified port - BOUNDARY_ARGS+=(--pprof) - BOUNDARY_ARGS+=(--pprof-port "$ARG_BOUNDARY_PPROF_PORT") - fi - agentapi server --type claude --term-width 67 --term-height 1190 -- \ boundary-run "${BOUNDARY_ARGS[@]}" -- \ claude "${ARGS[@]}"