Skip to content

Commit d4b4ebd

Browse files
defelmnqmafredrimatifali
authored
feat(devcontainers-cli): add devcontainers-cli module (#425)
Related to [this issue](coder/coder#16432) Create a new module to install [@devcontainers/cli](https://github.com/devcontainers/cli) using npm. --------- Co-authored-by: Mathias Fredriksson <[email protected]> Co-authored-by: M Atif Ali <[email protected]>
1 parent cccee31 commit d4b4ebd

File tree

5 files changed

+214
-0
lines changed

5 files changed

+214
-0
lines changed

.icons/devcontainers.svg

+2
Loading

devcontainers-cli/README.md

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
display_name: devcontainers-cli
3+
description: devcontainers-cli module provides an easy way to install @devcontainers/cli into a workspace
4+
icon: ../.icons/devcontainers.svg
5+
verified: true
6+
maintainer_github: coder
7+
tags: [devcontainers]
8+
---
9+
10+
# devcontainers-cli
11+
12+
The devcontainers-cli module provides an easy way to install [`@devcontainers/cli`](https://github.com/devcontainers/cli) into a workspace. It can be used within any workspace as it runs only if
13+
@devcontainers/cli is not installed yet.
14+
`npm` is required and should be pre-installed in order for the module to work.
15+
16+
```tf
17+
module "devcontainers-cli" {
18+
source = "registry.coder.com/modules/devcontainers-cli/coder"
19+
version = "1.0.0"
20+
agent_id = coder_agent.example.id
21+
}
22+
```

devcontainers-cli/main.test.ts

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { describe, expect, it } from "bun:test";
2+
import {
3+
execContainer,
4+
executeScriptInContainer,
5+
findResourceInstance,
6+
runContainer,
7+
runTerraformApply,
8+
runTerraformInit,
9+
testRequiredVariables,
10+
type TerraformState,
11+
} from "../test";
12+
13+
const executeScriptInContainerWithPackageManager = async (
14+
state: TerraformState,
15+
image: string,
16+
packageManager: string,
17+
shell = "sh",
18+
): Promise<{
19+
exitCode: number;
20+
stdout: string[];
21+
stderr: string[];
22+
}> => {
23+
const instance = findResourceInstance(state, "coder_script");
24+
const id = await runContainer(image);
25+
26+
// Install the specified package manager
27+
if (packageManager === "npm") {
28+
await execContainer(id, [shell, "-c", "apk add nodejs npm"]);
29+
} else if (packageManager === "pnpm") {
30+
await execContainer(id, [
31+
shell,
32+
"-c",
33+
"apk add nodejs npm && npm install -g pnpm",
34+
]);
35+
} else if (packageManager === "yarn") {
36+
await execContainer(id, [
37+
shell,
38+
"-c",
39+
"apk add nodejs npm && npm install -g yarn",
40+
]);
41+
}
42+
43+
const resp = await execContainer(id, [shell, "-c", instance.script]);
44+
const stdout = resp.stdout.trim().split("\n");
45+
const stderr = resp.stderr.trim().split("\n");
46+
return {
47+
exitCode: resp.exitCode,
48+
stdout,
49+
stderr,
50+
};
51+
};
52+
53+
describe("devcontainers-cli", async () => {
54+
await runTerraformInit(import.meta.dir);
55+
56+
testRequiredVariables(import.meta.dir, {
57+
agent_id: "some-agent-id",
58+
});
59+
60+
it("misses all package managers", async () => {
61+
const state = await runTerraformApply(import.meta.dir, {
62+
agent_id: "some-agent-id",
63+
});
64+
const output = await executeScriptInContainer(state, "docker:dind");
65+
expect(output.exitCode).toBe(1);
66+
expect(output.stderr).toEqual([
67+
"ERROR: No supported package manager (npm, pnpm, yarn) is installed. Please install one first.",
68+
]);
69+
}, 15000);
70+
71+
it("installs devcontainers-cli with npm", async () => {
72+
const state = await runTerraformApply(import.meta.dir, {
73+
agent_id: "some-agent-id",
74+
});
75+
76+
const output = await executeScriptInContainerWithPackageManager(
77+
state,
78+
"docker:dind",
79+
"npm",
80+
);
81+
expect(output.exitCode).toBe(0);
82+
83+
expect(output.stdout[0]).toEqual(
84+
"Installing @devcontainers/cli using npm...",
85+
);
86+
expect(output.stdout[output.stdout.length - 1]).toEqual(
87+
"🥳 @devcontainers/cli has been installed into /usr/local/bin/devcontainer!",
88+
);
89+
});
90+
91+
it("installs devcontainers-cli with yarn", async () => {
92+
const state = await runTerraformApply(import.meta.dir, {
93+
agent_id: "some-agent-id",
94+
});
95+
96+
const output = await executeScriptInContainerWithPackageManager(
97+
state,
98+
"docker:dind",
99+
"yarn",
100+
);
101+
expect(output.exitCode).toBe(0);
102+
103+
expect(output.stdout[0]).toEqual(
104+
"Installing @devcontainers/cli using yarn...",
105+
);
106+
expect(output.stdout[output.stdout.length - 1]).toEqual(
107+
"🥳 @devcontainers/cli has been installed into /usr/local/bin/devcontainer!",
108+
);
109+
});
110+
111+
it("displays warning if docker is not installed", async () => {
112+
const state = await runTerraformApply(import.meta.dir, {
113+
agent_id: "some-agent-id",
114+
});
115+
116+
const output = await executeScriptInContainerWithPackageManager(
117+
state,
118+
"alpine",
119+
"npm",
120+
);
121+
expect(output.exitCode).toBe(0);
122+
123+
expect(output.stdout[0]).toEqual(
124+
"WARNING: Docker was not found but is required to use @devcontainers/cli, please make sure it is available.",
125+
);
126+
expect(output.stdout[output.stdout.length - 1]).toEqual(
127+
"🥳 @devcontainers/cli has been installed into /usr/local/bin/devcontainer!",
128+
);
129+
});
130+
});

devcontainers-cli/main.tf

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
terraform {
2+
required_version = ">= 1.0"
3+
4+
required_providers {
5+
coder = {
6+
source = "coder/coder"
7+
version = ">= 0.17"
8+
}
9+
}
10+
}
11+
12+
variable "agent_id" {
13+
type = string
14+
description = "The ID of a Coder agent."
15+
}
16+
17+
resource "coder_script" "devcontainers-cli" {
18+
agent_id = var.agent_id
19+
display_name = "devcontainers-cli"
20+
icon = "/icon/devcontainers.svg"
21+
script = templatefile("${path.module}/run.sh", {})
22+
run_on_start = true
23+
}

devcontainers-cli/run.sh

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/usr/bin/env sh
2+
3+
# If @devcontainers/cli is already installed, we can skip
4+
if command -v devcontainer > /dev/null 2>&1; then
5+
echo "🥳 @devcontainers/cli is already installed into $(which devcontainer)!"
6+
exit 0
7+
fi
8+
9+
# Check if docker is installed
10+
if ! command -v docker > /dev/null 2>&1; then
11+
echo "WARNING: Docker was not found but is required to use @devcontainers/cli, please make sure it is available."
12+
fi
13+
14+
# Determine the package manager to use: npm, pnpm, or yarn
15+
if command -v pnpm > /dev/null 2>&1; then
16+
PACKAGE_MANAGER="pnpm"
17+
elif command -v yarn > /dev/null 2>&1; then
18+
PACKAGE_MANAGER="yarn"
19+
elif command -v npm > /dev/null 2>&1; then
20+
PACKAGE_MANAGER="npm"
21+
else
22+
echo "ERROR: No supported package manager (npm, pnpm, yarn) is installed. Please install one first." 1>&2
23+
exit 1
24+
fi
25+
26+
echo "Installing @devcontainers/cli using $PACKAGE_MANAGER..."
27+
28+
# Install @devcontainers/cli using the selected package manager
29+
if [ "$PACKAGE_MANAGER" = "npm" ] || [ "$PACKAGE_MANAGER" = "pnpm" ]; then
30+
$PACKAGE_MANAGER install -g @devcontainers/cli \
31+
&& echo "🥳 @devcontainers/cli has been installed into $(which devcontainer)!"
32+
elif [ "$PACKAGE_MANAGER" = "yarn" ]; then
33+
$PACKAGE_MANAGER global add @devcontainers/cli \
34+
&& echo "🥳 @devcontainers/cli has been installed into $(which devcontainer)!"
35+
fi
36+
37+
exit 0

0 commit comments

Comments
 (0)