Skip to content
Open
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
53 changes: 43 additions & 10 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 = "1.2.2"
version = "1.3.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 = "1.2.2"
version = "1.3.0"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
base_dir = "~/projects/coder"
Expand All @@ -43,11 +43,12 @@ 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 = "1.2.2"
version = "1.3.0"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
}


data "coder_external_auth" "github" {
id = "github"
}
Expand All @@ -69,11 +70,12 @@ data "coder_parameter" "git_repo" {
module "git_clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.2.2"
version = "1.3.0"
agent_id = coder_agent.example.id
url = data.coder_parameter.git_repo.value
}


# Create a code-server instance for the cloned repository
module "code-server" {
count = data.coder_workspace.me.start_count
Expand Down Expand Up @@ -103,13 +105,14 @@ 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 = "1.2.2"
version = "1.3.0"
agent_id = coder_agent.example.id
url = "https://github.example.com/coder/coder/tree/feat/example"
git_providers = {
"https://github.example.com/" = {
provider = "github"
}

}
}
```
Expand All @@ -122,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 = "1.2.2"
version = "1.3.0"
agent_id = coder_agent.example.id
url = "https://gitlab.com/coder/coder/-/tree/feat/example"
}
Expand All @@ -134,13 +137,14 @@ 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 = "1.2.2"
version = "1.3.0"
agent_id = coder_agent.example.id
url = "https://gitlab.example.com/coder/coder/-/tree/feat/example"
git_providers = {
"https://gitlab.example.com/" = {
provider = "gitlab"
}

}
}
```
Expand All @@ -155,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 = "1.2.2"
version = "1.3.0"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
branch_name = "feat/example"
Expand All @@ -173,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 = "1.2.2"
version = "1.3.0"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
folder_name = "coder-dev"
Expand All @@ -199,6 +203,35 @@ module "git-clone" {
}
```

## Custom clone arguments

Pass additional arguments to the git clone command using the `clone_args` variable.
This is useful for scenarios like using a local mirror with `--reference` to speed up cloning.

```tf
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.0"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
clone_args = "--reference /mnt/git-mirrors/coder.git"
}
```

You can also combine multiple arguments:

```tf
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.0"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
clone_args = "--reference /mnt/git-mirrors/coder.git --dissociate"
}
```

## Post-clone script

Run a custom script after cloning the repository by setting the `post_clone_script` variable.
Expand All @@ -208,7 +241,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 = "1.2.2"
version = "1.3.0"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
post_clone_script = <<-EOT
Expand Down
141 changes: 118 additions & 23 deletions registry/coder/modules/git-clone/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import {
testRequiredVariables,
} from "~test";

// Use bitnami/git which has both bash and git pre-installed
const gitImage = "bitnami/git";

describe("git-clone", async () => {
await runTerraformInit(import.meta.dir);

Expand All @@ -19,7 +22,12 @@ describe("git-clone", async () => {
agent_id: "foo",
url: "some-url",
});
const output = await executeScriptInContainer(state, "alpine");
// Use debian:stable-slim which has bash but no git
const output = await executeScriptInContainer(
state,
"debian:stable-slim",
"bash",
);
expect(output.exitCode).toBe(1);
expect(output.stdout).toEqual(["Git is not installed!"]);
});
Expand All @@ -29,11 +37,10 @@ describe("git-clone", async () => {
agent_id: "foo",
url: "fake-url",
});
const output = await executeScriptInContainer(state, "alpine/git");
expect(output.stdout).toEqual([
"Creating directory ~/fake-url...",
"Cloning fake-url to ~/fake-url...",
]);
const output = await executeScriptInContainer(state, gitImage, "bash");
expect(output.stdout[0]).toContain("Creating directory");
expect(output.stdout[0]).toContain("fake-url");
expect(output.stdout[1]).toContain("Cloning fake-url to");
expect(output.stderr.join(" ")).toContain("fatal");
expect(output.stderr.join(" ")).toContain("fake-url");
});
Expand Down Expand Up @@ -204,25 +211,29 @@ describe("git-clone", async () => {
agent_id: "foo",
url: "https://github.com/michaelbrewer/repo-tests.log/tree/feat/branch",
});
const output = await executeScriptInContainer(state, "alpine/git");
const output = await executeScriptInContainer(state, gitImage, "bash");
expect(output.exitCode).toBe(0);
expect(output.stdout).toEqual([
"Creating directory ~/repo-tests.log...",
"Cloning https://github.com/michaelbrewer/repo-tests.log to ~/repo-tests.log on branch feat/branch...",
]);
expect(output.stdout[0]).toContain("Creating directory");
expect(output.stdout[0]).toContain("repo-tests.log");
expect(output.stdout[1]).toContain(
"Cloning https://github.com/michaelbrewer/repo-tests.log",
);
expect(output.stdout[1]).toContain("on branch feat/branch");
});

it("runs with gitlab clone with switch to feat/branch", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
url: "https://gitlab.com/mike.brew/repo-tests.log/-/tree/feat/branch",
});
const output = await executeScriptInContainer(state, "alpine/git");
const output = await executeScriptInContainer(state, gitImage, "bash");
expect(output.exitCode).toBe(0);
expect(output.stdout).toEqual([
"Creating directory ~/repo-tests.log...",
"Cloning https://gitlab.com/mike.brew/repo-tests.log to ~/repo-tests.log on branch feat/branch...",
]);
expect(output.stdout[0]).toContain("Creating directory");
expect(output.stdout[0]).toContain("repo-tests.log");
expect(output.stdout[1]).toContain(
"Cloning https://gitlab.com/mike.brew/repo-tests.log",
);
expect(output.stdout[1]).toContain("on branch feat/branch");
});

it("runs with github clone with branch_name set to feat/branch", async () => {
Expand All @@ -238,12 +249,14 @@ describe("git-clone", async () => {
expect(state.outputs.web_url.value).toEqual(url);
expect(state.outputs.branch_name.value).toEqual(branch_name);

const output = await executeScriptInContainer(state, "alpine/git");
const output = await executeScriptInContainer(state, gitImage, "bash");
expect(output.exitCode).toBe(0);
expect(output.stdout).toEqual([
"Creating directory ~/repo-tests.log...",
"Cloning https://github.com/michaelbrewer/repo-tests.log to ~/repo-tests.log on branch feat/branch...",
]);
expect(output.stdout[0]).toContain("Creating directory");
expect(output.stdout[0]).toContain("repo-tests.log");
expect(output.stdout[1]).toContain(
"Cloning https://github.com/michaelbrewer/repo-tests.log",
);
expect(output.stdout[1]).toContain("on branch feat/branch");
});

it("runs post-clone script", async () => {
Expand All @@ -254,11 +267,93 @@ describe("git-clone", async () => {
});
const output = await executeScriptInContainer(
state,
"alpine/git",
"sh",
gitImage,
"bash",
"mkdir -p ~/fake-url && echo 'existing' > ~/fake-url/file.txt",
);
expect(output.stdout).toContain("Running post-clone script...");
expect(output.stdout).toContain("Post-clone script executed");
});

it("runs with clone_args", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
url: "https://github.com/michaelbrewer/repo-tests.log",
clone_args: "--single-branch",
});
const output = await executeScriptInContainer(state, gitImage, "bash");
expect(output.exitCode).toBe(0);
expect(output.stdout[0]).toContain("Creating directory");
expect(output.stdout[0]).toContain("repo-tests.log");
expect(output.stdout[1]).toContain(
"Cloning https://github.com/michaelbrewer/repo-tests.log",
);
});

it("runs with depth", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
url: "https://github.com/michaelbrewer/repo-tests.log",
depth: "1",
});
const output = await executeScriptInContainer(state, gitImage, "bash");
expect(output.exitCode).toBe(0);
expect(output.stdout[0]).toContain("Creating directory");
expect(output.stdout[0]).toContain("repo-tests.log");
expect(output.stdout[1]).toContain(
"Cloning https://github.com/michaelbrewer/repo-tests.log",
);
});

it("runs with depth and branch_name", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
url: "https://github.com/michaelbrewer/repo-tests.log",
branch_name: "feat/branch",
depth: "1",
});
const output = await executeScriptInContainer(state, gitImage, "bash");
expect(output.exitCode).toBe(0);
expect(output.stdout[0]).toContain("Creating directory");
expect(output.stdout[0]).toContain("repo-tests.log");
expect(output.stdout[1]).toContain(
"Cloning https://github.com/michaelbrewer/repo-tests.log",
);
expect(output.stdout[1]).toContain("on branch feat/branch");
});

it("runs with clone_args and branch_name", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
url: "https://github.com/michaelbrewer/repo-tests.log",
branch_name: "feat/branch",
clone_args: "--single-branch",
});
const output = await executeScriptInContainer(state, gitImage, "bash");
expect(output.exitCode).toBe(0);
expect(output.stdout[0]).toContain("Creating directory");
expect(output.stdout[0]).toContain("repo-tests.log");
expect(output.stdout[1]).toContain(
"Cloning https://github.com/michaelbrewer/repo-tests.log",
);
expect(output.stdout[1]).toContain("on branch feat/branch");
});

it("runs with depth, branch_name, and clone_args", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
url: "https://github.com/michaelbrewer/repo-tests.log",
branch_name: "feat/branch",
depth: "1",
clone_args: "--single-branch",
});
const output = await executeScriptInContainer(state, gitImage, "bash");
expect(output.exitCode).toBe(0);
expect(output.stdout[0]).toContain("Creating directory");
expect(output.stdout[0]).toContain("repo-tests.log");
expect(output.stdout[1]).toContain(
"Cloning https://github.com/michaelbrewer/repo-tests.log",
);
expect(output.stdout[1]).toContain("on branch feat/branch");
});
});
7 changes: 7 additions & 0 deletions registry/coder/modules/git-clone/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ variable "post_clone_script" {
default = null
}

variable "clone_args" {
description = "Additional arguments to pass to the git clone command (e.g., '--reference /path/to/mirror' for using a local mirror)."
type = string
default = ""
}

locals {
# Remove query parameters and fragments from the URL
url = replace(replace(var.url, "/\\?.*/", ""), "/#.*/", "")
Expand Down Expand Up @@ -129,6 +135,7 @@ resource "coder_script" "git_clone" {
BRANCH_NAME : local.branch_name,
DEPTH = var.depth,
POST_CLONE_SCRIPT : local.encoded_post_clone_script,
CLONE_ARGS : var.clone_args,
})
display_name = "Git Clone"
icon = "/icon/git.svg"
Expand Down
Loading
Loading