From add5f6e348587a023faf2dbd77caf7ae88b87a55 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Thu, 30 Jul 2026 17:53:56 +0000 Subject: [PATCH 1/2] fix(git-clone): skip clone script when url is empty The module treats the repository URL as optional, but an empty value still created a coder_script that exited 1 with "No repository specified!". With start_blocks_login set, that failed the workspace startup and marked it unhealthy. Gate the coder_script on a resolved, non-blank URL so nothing is created when there is nothing to clone, and report empty folder_name and repo_dir instead of paths derived from a missing URL. Closes #63 --- registry/coder/modules/git-clone/README.md | 24 +++--- .../modules/git-clone/git-clone.tftest.hcl | 79 +++++++++++++++++++ registry/coder/modules/git-clone/main.test.ts | 38 +++++++++ registry/coder/modules/git-clone/main.tf | 13 +-- 4 files changed, 137 insertions(+), 17 deletions(-) create mode 100644 registry/coder/modules/git-clone/git-clone.tftest.hcl diff --git a/registry/coder/modules/git-clone/README.md b/registry/coder/modules/git-clone/README.md index e0c3c8e04..b234da1f1 100644 --- a/registry/coder/modules/git-clone/README.md +++ b/registry/coder/modules/git-clone/README.md @@ -14,7 +14,7 @@ This module allows you to automatically clone a repository by URL and skip if it module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "2.0.2" + version = "2.0.3" agent_id = coder_agent.example.id url = "https://github.com/coder/coder" } @@ -28,7 +28,7 @@ module "git-clone" { module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "2.0.2" + version = "2.0.3" agent_id = coder_agent.example.id url = "https://github.com/coder/coder" base_dir = "~/projects/coder" @@ -43,7 +43,7 @@ To use with [Git Authentication](https://coder.com/docs/v2/latest/admin/git-prov module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "2.0.2" + version = "2.0.3" agent_id = coder_agent.example.id url = "https://github.com/coder/coder" } @@ -70,7 +70,7 @@ data "coder_parameter" "git_repo" { module "git_clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "2.0.2" + version = "2.0.3" agent_id = coder_agent.example.id url = data.coder_parameter.git_repo.value } @@ -105,7 +105,7 @@ Configuring `git-clone` for a self-hosted GitHub Enterprise Server running at `g module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "2.0.2" + version = "2.0.3" agent_id = coder_agent.example.id url = "https://github.example.com/coder/coder/tree/feat/example" git_providers = { @@ -125,7 +125,7 @@ To GitLab clone with a specific branch like `feat/example` module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "2.0.2" + version = "2.0.3" agent_id = coder_agent.example.id url = "https://gitlab.com/coder/coder/-/tree/feat/example" } @@ -137,7 +137,7 @@ Configuring `git-clone` for a self-hosted GitLab running at `gitlab.example.com` module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "2.0.2" + version = "2.0.3" agent_id = coder_agent.example.id url = "https://gitlab.example.com/coder/coder/-/tree/feat/example" git_providers = { @@ -159,7 +159,7 @@ For example, to clone the `feat/example` branch: module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "2.0.2" + version = "2.0.3" agent_id = coder_agent.example.id url = "https://github.com/coder/coder" branch_name = "feat/example" @@ -177,7 +177,7 @@ For example, this will clone into the `~/projects/coder/coder-dev` folder: module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "2.0.2" + version = "2.0.3" agent_id = coder_agent.example.id url = "https://github.com/coder/coder" folder_name = "coder-dev" @@ -202,7 +202,7 @@ fetches, or partial clones. module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "2.0.2" + version = "2.0.3" agent_id = coder_agent.example.id url = "https://github.com/coder/coder" extra_args = [ @@ -223,7 +223,7 @@ This is useful for preparing the environment or validating prerequisites before module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "2.0.2" + version = "2.0.3" agent_id = coder_agent.example.id url = "https://github.com/coder/coder" pre_clone_script = <<-EOT @@ -246,7 +246,7 @@ This is useful for running initialization tasks like installing dependencies or module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "2.0.2" + version = "2.0.3" agent_id = coder_agent.example.id url = "https://github.com/coder/coder" post_clone_script = <<-EOT diff --git a/registry/coder/modules/git-clone/git-clone.tftest.hcl b/registry/coder/modules/git-clone/git-clone.tftest.hcl new file mode 100644 index 000000000..7943bd6be --- /dev/null +++ b/registry/coder/modules/git-clone/git-clone.tftest.hcl @@ -0,0 +1,79 @@ +# Tests for git-clone module + +run "creates_clone_script_for_repository_url" { + command = plan + + variables { + agent_id = "test-agent-id" + url = "https://github.com/coder/coder" + } + + assert { + condition = length(coder_script.git_clone) == 1 + error_message = "A clone script should be created when a repository URL is provided" + } + + assert { + condition = coder_script.git_clone[0].agent_id == "test-agent-id" + error_message = "Clone script agent ID should match the input variable" + } +} + +run "skips_clone_script_for_empty_url" { + command = plan + + variables { + agent_id = "test-agent-id" + url = "" + } + + assert { + condition = length(coder_script.git_clone) == 0 + error_message = "No clone script should be created when the repository URL is empty" + } + + assert { + condition = output.folder_name == "" && output.repo_dir == "" + error_message = "folder_name and repo_dir should be empty when there is no repository and no folder name" + } +} + +run "reports_explicit_folder_name_without_url" { + command = plan + + variables { + agent_id = "test-agent-id" + url = "" + base_dir = "/tmp" + folder_name = "project" + } + + assert { + condition = length(coder_script.git_clone) == 0 + error_message = "No clone script should be created when the repository URL is empty" + } + + assert { + condition = output.folder_name == "project" && output.repo_dir == "/tmp/project" + error_message = "An explicit folder name should still be reported in the outputs" + } +} + +run "skips_clone_script_for_whitespace_url" { + command = plan + + variables { + agent_id = "test-agent-id" + url = " " + } + + assert { + condition = length(coder_script.git_clone) == 0 + error_message = "No clone script should be created when the repository URL is only whitespace" + } + + assert { + condition = output.folder_name == "" && output.repo_dir == "" + error_message = "folder_name and repo_dir should be empty when there is no repository and no folder name" + } +} diff --git a/registry/coder/modules/git-clone/main.test.ts b/registry/coder/modules/git-clone/main.test.ts index b809d5ac4..8e670bd0d 100644 --- a/registry/coder/modules/git-clone/main.test.ts +++ b/registry/coder/modules/git-clone/main.test.ts @@ -51,6 +51,44 @@ describe("git-clone", async () => { url: "foo", }); + it("does not create a script when url is empty", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + url: "", + }); + expect( + state.resources.filter((resource) => resource.type === "coder_script"), + ).toHaveLength(0); + expect(state.outputs.folder_name.value).toEqual(""); + expect(state.outputs.repo_dir.value).toEqual(""); + }); + + it("does not create a script when url is only whitespace", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + url: " ", + }); + expect( + state.resources.filter((resource) => resource.type === "coder_script"), + ).toHaveLength(0); + expect(state.outputs.folder_name.value).toEqual(""); + expect(state.outputs.repo_dir.value).toEqual(""); + }); + + it("reports an explicit folder_name when url is empty", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + url: "", + base_dir: "/tmp", + folder_name: "project", + }); + expect( + state.resources.filter((resource) => resource.type === "coder_script"), + ).toHaveLength(0); + expect(state.outputs.folder_name.value).toEqual("project"); + expect(state.outputs.repo_dir.value).toEqual("/tmp/project"); + }); + it("fails without git", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", diff --git a/registry/coder/modules/git-clone/main.tf b/registry/coder/modules/git-clone/main.tf index b4e6a4f8c..92acdfc15 100644 --- a/registry/coder/modules/git-clone/main.tf +++ b/registry/coder/modules/git-clone/main.tf @@ -10,7 +10,7 @@ terraform { } variable "url" { - description = "The URL of the Git repository." + description = "The URL of the Git repository. When empty, the clone is skipped." type = string } @@ -87,10 +87,12 @@ locals { clone_url = var.branch_name == "" && local.tree_path != "" ? replace(local.url, "/${local.tree_path}.*/", "") : local.url # Extract the branch name from the URL branch_name = var.branch_name == "" && local.tree_path != "" ? replace(replace(local.url, local.clone_url, ""), "/.*${local.tree_path}/", "") : var.branch_name + # Skip the clone entirely when no repository URL is provided + clone_enabled = trimspace(local.clone_url) != "" # Extract the folder name from the URL - folder_name = var.folder_name == "" ? replace(basename(local.clone_url), ".git", "") : var.folder_name + folder_name = var.folder_name != "" ? var.folder_name : (local.clone_enabled ? replace(basename(local.clone_url), ".git", "") : "") # Construct the path to clone the repository - clone_path = var.base_dir != "" ? join("/", [var.base_dir, local.folder_name]) : join("/", ["~", local.folder_name]) + clone_path = local.folder_name == "" ? "" : (var.base_dir != "" ? join("/", [var.base_dir, local.folder_name]) : join("/", ["~", local.folder_name])) # Construct the web URL web_url = startswith(local.clone_url, "git@") ? replace(replace(local.clone_url, ":", "/"), "git@", "https://") : local.clone_url # Encode the post_clone_script for passing to the shell script @@ -125,7 +127,7 @@ locals { output "repo_dir" { value = local.clone_path - description = "Full path of cloned repo directory" + description = "Full path of cloned repo directory. Empty when there is no folder to clone into." } output "git_provider" { @@ -135,7 +137,7 @@ output "git_provider" { output "folder_name" { value = local.folder_name - description = "The name of the folder that will be created" + description = "The name of the folder that will be created. Empty when no repository is cloned and no folder name is provided." } output "clone_url" { @@ -154,6 +156,7 @@ output "branch_name" { } resource "coder_script" "git_clone" { + count = local.clone_enabled ? 1 : 0 agent_id = var.agent_id script = <<-EOT #!/bin/bash From f69d3c2927c90ebeb5d06fc1c4e1b11b455969e4 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Fri, 31 Jul 2026 15:59:11 +0000 Subject: [PATCH 2/2] refactor(git-clone): drop unreachable empty url guard from run.sh The coder_script is only created when the resolved URL is non-blank, so REPO_URL can never be empty by the time the script runs. --- registry/coder/modules/git-clone/run.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/registry/coder/modules/git-clone/run.sh b/registry/coder/modules/git-clone/run.sh index 5aea7c7d1..dc1343915 100644 --- a/registry/coder/modules/git-clone/run.sh +++ b/registry/coder/modules/git-clone/run.sh @@ -14,12 +14,6 @@ SCRIPTS_DIR="${SCRIPTS_DIR}" PRE_CLONE_LOG_PATH="${PRE_CLONE_LOG_PATH}" POST_CLONE_LOG_PATH="${POST_CLONE_LOG_PATH}" -# Check if the variable is empty... -if [ -z "$REPO_URL" ]; then - echo "No repository specified!" - exit 1 -fi - # Check if the variable is empty... if [ -z "$CLONE_PATH" ]; then echo "No clone path specified!"