Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 27 additions & 21 deletions registry/coder/modules/git-clone/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.5"
version = "2.1.0"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
}
Expand All @@ -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.5"
version = "2.1.0"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
base_dir = "~/projects/coder"
Expand All @@ -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.5"
version = "2.1.0"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
}
Expand All @@ -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.5"
version = "2.1.0"
agent_id = coder_agent.example.id
url = data.coder_parameter.git_repo.value
}
Expand Down Expand Up @@ -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.5"
version = "2.1.0"
agent_id = coder_agent.example.id
url = "https://github.example.com/coder/coder/tree/feat/example"
git_providers = {
Expand All @@ -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.5"
version = "2.1.0"
agent_id = coder_agent.example.id
url = "https://gitlab.com/coder/coder/-/tree/feat/example"
}
Expand All @@ -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.5"
version = "2.1.0"
agent_id = coder_agent.example.id
url = "https://gitlab.example.com/coder/coder/-/tree/feat/example"
git_providers = {
Expand All @@ -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.5"
version = "2.1.0"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
branch_name = "feat/example"
Expand All @@ -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.5"
version = "2.1.0"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
folder_name = "coder-dev"
Expand All @@ -188,32 +188,38 @@ module "git-clone" {
## Extra `git clone` arguments

> [!NOTE]
> **Upgrading from v1.x?** The `depth` variable was removed in v2.0.0. Use `extra_args = ["--depth=1"]` instead.
> **Upgrading from v1.x?** The `depth` variable was removed in v2.0.0. Use `additional_args = ["--depth", "1"]` instead.
> `additional_args` rejects `-b` and `--branch`; set `branch_name` instead.
> Do not pass `-b` or `--branch` in `extra_args` when `branch_name` is
> already set (or extracted from the URL). Git silently accepts the last
> `-b` flag, so the two values would conflict.

Pass any additional flags through `extra_args` (one element per argument).
This lets you enable anything `git clone` supports without the module having
to expose it explicitly, for example a shallow clone, submodules, parallel
fetches, or partial clones.
Pass any additional arguments through `additional_args`, a list with one
element per argument. This lets you enable anything `git clone` supports
without the module having to expose it explicitly, for example a shallow
clone, submodules, parallel fetches, or partial clones. Each element is passed
exactly as written and in order, so a flag can repeat and a value can contain
spaces.

```tf
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "2.0.5"
version = "2.1.0"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
extra_args = [
"--depth=1",
additional_args = [
"--depth", "1",
"--recurse-submodules",
"--jobs=8",
"--filter=blob:none",
"--jobs", "8",
"--filter", "blob:none",
]
}
```

`extra_args` is deprecated in favor of `additional_args`, which takes the same
list. It still works and is appended after `additional_args`.

## Pre-clone script

Run a custom script before cloning the repository by setting the `pre_clone_script` variable.
Expand All @@ -223,7 +229,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.5"
version = "2.1.0"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
pre_clone_script = <<-EOT
Expand All @@ -246,7 +252,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.5"
version = "2.1.0"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
post_clone_script = <<-EOT
Expand Down
41 changes: 41 additions & 0 deletions registry/coder/modules/git-clone/git-clone.tftest.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,44 @@ run "skips_clone_script_for_whitespace_url" {
error_message = "folder_name and repo_dir should be empty when there is no repository and no folder name"
}
}

run "deprecated_extra_args_still_applied" {
command = plan

variables {
agent_id = "foo"
url = "https://github.com/coder/coder"
additional_args = ["--depth", "1", "-c", "http.extraHeader=X-A: 1", "-c", "http.extraHeader=X-B: 2"]
extra_args = ["--jobs=8"]
}

# additional_args keeps its order and repeated flags, then extra_args follows.
assert {
condition = local.encoded_extra_args == base64encode("--depth\n1\n-c\nhttp.extraHeader=X-A: 1\n-c\nhttp.extraHeader=X-B: 2\n--jobs=8")
error_message = "extra_args must still be passed, after additional_args"
}
}

run "additional_args_rejects_newline" {
command = plan

variables {
agent_id = "foo"
url = "https://github.com/coder/coder"
additional_args = ["--depth\n1"]
}

expect_failures = [var.additional_args]
}

run "additional_args_rejects_branch" {
command = plan

variables {
agent_id = "foo"
url = "https://github.com/coder/coder"
additional_args = ["--branch=main"]
}

expect_failures = [var.additional_args]
}
39 changes: 38 additions & 1 deletion registry/coder/modules/git-clone/main.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it } from "bun:test";
import { describe, expect, it, setDefaultTimeout } from "bun:test";
import {
execContainer,
findResourceInstance,
Expand All @@ -10,6 +10,9 @@ import {
type TerraformState,
} from "~test";

// The first test to use an image pulls it, which can outlast bun's 5s default.
setDefaultTimeout(60 * 1000);

const executeScriptInContainer = async (
state: TerraformState,
image: string,
Expand Down Expand Up @@ -417,6 +420,40 @@ describe("git-clone", async () => {
);
});

it("passes additional_args to git clone before extra_args", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
url: "fake-url",
additional_args: JSON.stringify([
"--recurse-submodules",
"-c",
"user.name=Coder User",
"-c",
"core.sshCommand=ssh -i /tmp/key",
]),
extra_args: JSON.stringify(["--jobs=8"]),
});
const output = await executeScriptInContainer(
state,
"alpine/git",
installFakeGit,
);
expect(output.exitCode).toBe(0);
expect(output.stdout.join("\n")).toContain(
[
"argv:clone",
"argv:--recurse-submodules",
"argv:-c",
"argv:user.name=Coder User",
"argv:-c",
"argv:core.sshCommand=ssh -i /tmp/key",
"argv:--jobs=8",
"argv:fake-url",
"argv:/root/fake-url",
].join("\n"),
);
});

it("passes extra_args alongside branch_name in the correct order", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
Expand Down
22 changes: 20 additions & 2 deletions registry/coder/modules/git-clone/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,26 @@ variable "folder_name" {
default = ""
}

variable "additional_args" {
description = "Additional arguments to pass to `git clone`, one list element per argument, in order (e.g. `[\"--depth\", \"1\", \"--recurse-submodules\", \"-c\", \"http.extraHeader=X-A: 1\", \"-c\", \"http.extraHeader=X-B: 2\"]`). Each element is passed as written, so a flag can repeat and a value may contain spaces."
type = list(string)
default = []

validation {
condition = alltrue([for a in var.additional_args : a != "" && !can(regex("\n", a))])
error_message = "additional_args elements must be non-empty and must not contain newlines."
}

# The module already sets the branch from branch_name or the URL. Keep this
# list in sync with the `git clone` flags in run.sh.
validation {
condition = !anytrue([for a in var.additional_args : contains(["-b", "--branch"], split("=", a)[0])])
error_message = "additional_args must not set -b or --branch; use branch_name instead."
}
}

variable "extra_args" {
description = "Extra arguments to pass to `git clone`, one element per argument (e.g. `[\"--recurse-submodules\", \"--jobs=8\", \"--filter=blob:none\"]`)."
description = "Deprecated: use `additional_args` instead, which takes the same list. Still honored and appended after `additional_args`."
type = list(string)
default = []
}
Expand Down Expand Up @@ -99,7 +117,7 @@ locals {
encoded_post_clone_script = var.post_clone_script != null ? base64encode(var.post_clone_script) : ""
# Encode the pre_clone_script for passing to the shell script
encoded_pre_clone_script = var.pre_clone_script != null ? base64encode(var.pre_clone_script) : ""
encoded_extra_args = base64encode(join("\n", var.extra_args))
encoded_extra_args = base64encode(join("\n", concat(var.additional_args, var.extra_args)))

# Module directory paths (matches coder-utils convention)
# Use folder_name so two git-clone instances in the same template get
Expand Down
2 changes: 2 additions & 0 deletions registry/coder/modules/git-clone/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ if [ -z "$(ls -A "$CLONE_PATH")" ]; then
git clone $${extra_args[@]+"$${extra_args[@]}"} "$REPO_URL" "$CLONE_PATH"
else
echo "Cloning $REPO_URL to $CLONE_PATH on branch $BRANCH_NAME..."
# Keep -b in sync with the reserved-flag list in additional_args'
# validation in main.tf, so additional_args cannot set it a second time.
git clone $${extra_args[@]+"$${extra_args[@]}"} -b "$BRANCH_NAME" "$REPO_URL" "$CLONE_PATH"
fi
else
Expand Down
Loading