Skip to content

Commit 17caf08

Browse files
committed
add devcontainer class to vscode package
1 parent 57d65fe commit 17caf08

File tree

8 files changed

+247
-5
lines changed

8 files changed

+247
-5
lines changed

docker/jfr/Dockerfile

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,3 @@ RUN rm /etc/s6-overlay/s6-rc.d/user/contents.d/jenkins
1515

1616
COPY /docker/jfr/run_job.sh /usr/bin/run_job
1717
COPY docker/jfr/root /
18-
19-
ENV JENKINS_HOME="/usr/share/jenkins/ref/"

docker/prod/Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ RUN apt-get update && \
1515
kmod \
1616
wget \
1717
git \
18-
curl && \
18+
curl \
19+
build-essential \
20+
python3 && \
1921
rm -rf /var/lib/apt/lists/*
2022

2123
# Install and setup docker into the container
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/* groovylint-disable, DuplicateMapLiteral, DuplicateStringLiteral, UnusedVariable */
2+
@Library('jenkins-std-lib')
3+
4+
import org.dsty.logging.LogClient
5+
import org.dsty.system.os.Path
6+
import org.dsty.system.os.programs.CliTool
7+
import org.dsty.js.node.Node
8+
import org.dsty.js.node.Npm
9+
import org.dsty.vscode.DevContainer
10+
import org.dsty.scm.Git
11+
12+
LogClient log = new LogClient()
13+
14+
node() {
15+
16+
log.info('To use vscode devcontainers we will first need Node and npm.')
17+
18+
log.info('Lets install the latest version of Node and npm.')
19+
20+
// Note that Node wont be downloaded and installed until we try to use it.
21+
Node nodeJS = new Node()
22+
23+
DevContainer devcontainer = new DevContainer(nodeJS.npm)
24+
25+
log.info('Lets clone the rust example project https://github.com/microsoft/vscode-remote-try-rust')
26+
27+
Git git = new Git(this)
28+
29+
git.checkout(
30+
changelog: false,
31+
poll: false,
32+
scm: [
33+
$class: 'GitSCM',
34+
branches: [[name: 'main']],
35+
extensions: [],
36+
userRemoteConfigs: [[url: 'https://github.com/microsoft/vscode-remote-try-rust.git']]
37+
]
38+
)
39+
40+
log.info('Now we can start the devcontainer and run a command inside it.')
41+
42+
devcontainer.withContainer {
43+
devcontainer.exec("cargo run")
44+
}
45+
46+
// The devcontainer will be stopped but not removed.
47+
48+
}

src/org/dsty/system/os/programs/ExecutableBuilder.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class ExecutableBuilder {
2727
static Executable buildExecutable(Path filePath) throws NoSuchFileException {
2828

2929
if (!filePath.exists()) {
30-
throw new NoSuchFileException(filePath.absolutize())
30+
throw new NoSuchFileException(filePath.absolutize().toString())
3131
}
3232

3333
final System os = Platform.system()
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
package org.dsty.vscode
2+
3+
import org.dsty.jenkins.Build
4+
import org.dsty.logging.LogClient
5+
import org.dsty.system.os.Path
6+
import org.dsty.system.os.programs.CliTool
7+
import org.dsty.system.os.programs.ToolBuilder
8+
import org.dsty.system.os.shell.Bash
9+
import org.dsty.system.os.shell.Result
10+
import org.dsty.system.os.shell.ExecutionException
11+
import org.dsty.js.node.Npm
12+
13+
import com.cloudbees.groovy.cps.NonCPS
14+
15+
import java.util.regex.Matcher
16+
17+
/**
18+
* Installs and runs the vscode <a href="https://github.com/devcontainers/cli"target="_blank">
19+
* <strong>devcontainer</strong></a> CLI.
20+
* <p>
21+
* The devcontainer tool can be used to create and manage devcontainers created from a
22+
* <strong>devcontainer.json</strong>.
23+
* <pre>{@code
24+
* import org.dsty.vscode.DevContainer
25+
*node() {
26+
* DevContainer devcontainer = new DevContainer()
27+
* devcontainer.withContainer {
28+
* devcontainer.exec('echo "Hello from inside DevContainer"')
29+
* &#125;
30+
*&#125;}</pre>
31+
*/
32+
class DevContainer implements Serializable {
33+
34+
/**
35+
* Logging client
36+
*/
37+
private final LogClient log
38+
39+
private CliTool devContainer
40+
41+
private Npm npm
42+
43+
private String workspace
44+
45+
private Bash bash
46+
47+
DevContainer(Npm npm) {
48+
this.npm = npm
49+
this.log = new LogClient()
50+
this.bash = new Bash()
51+
}
52+
53+
private Npm getDevContainer() {
54+
55+
if (!this.@devContainer) {
56+
57+
this.npm.installGlobal('@devcontainers/cli')
58+
59+
String prefix = this.npm.call('get prefix').stdOut.trim()
60+
Path devContainerPath = new Path(prefix).child('bin/devcontainer')
61+
62+
this.devContainer = ToolBuilder.buildTool(devContainerPath)
63+
64+
}
65+
66+
return this.@devContainer
67+
}
68+
69+
private String getWorkspace() {
70+
71+
return Path.cwd()
72+
}
73+
74+
/**
75+
* Run a command using the <strong>devcontainer</strong></a> executable.
76+
*
77+
* @param args The arguments passed directly to <strong>devcontainer</strong>.
78+
* @return The result from the command.
79+
*/
80+
Result call(String args = '') throws ExecutionException {
81+
82+
return this.run(args)
83+
}
84+
85+
/**
86+
* Calls the devcontainer CLI's <strong>up</strong> command to start the devcontainer
87+
* in <strong>devcontainer.json</strong>.
88+
* <p>
89+
* This command will not stop the container for you. If you want to have the container
90+
* stopped automaticly see {@link #withContainer()}.
91+
*
92+
* @param workspace A path to a directory from where all commands will be executed.
93+
* If a path is not specified the current directory is used.
94+
* @return The result from the command.
95+
*/
96+
Result up(Path workspace = null) {
97+
98+
if (workspace == null) {
99+
workspace = this.workspace
100+
}
101+
102+
this.run("up --workspace-folder ${workspace}")
103+
}
104+
105+
/**
106+
* Calls the devcontainer CLI's <strong>exec</strong> command to run a command inside the devcontainer.
107+
*
108+
* @param cmd The command to run inside the container.
109+
* @param workspace A path to a directory from where all commands will be executed.
110+
* If a path is not specified the current directory is used.
111+
* @return The result from the command.
112+
*/
113+
Result exec(String cmd, Path workspace = null) {
114+
115+
if (workspace == null) {
116+
workspace = this.workspace
117+
}
118+
119+
this.run("exec --workspace-folder ${workspace} ${cmd}")
120+
}
121+
122+
/**
123+
* Bring up a devcontainer, runs the user supplied code and then stops the container.
124+
* <p>
125+
* Similar to {@link #up()}, but stops the container automaticlly.
126+
*
127+
* @param workspace A path to a directory from where all commands will be executed.
128+
* If a path is not specified the current directory is used.
129+
* @param userCode The code to be ran after the devcontainer has been started.
130+
*/
131+
void withContainer(String workspace = null, Closure userCode) {
132+
133+
String output = this.up(workspace)
134+
135+
String containerId = this.parseContainerId(output)
136+
137+
log.debug("Started devcontainer ${containerId}")
138+
139+
try {
140+
userCode()
141+
} finally {
142+
this.bash.silent("docker stop ${containerId}")
143+
}
144+
145+
}
146+
147+
/**
148+
* Run a command using the <strong>devcontainer</strong> executable using the supplied arugments.
149+
*
150+
* @param args The arguments passed directly to <strong>devcontainer</strong> executable.
151+
* @return The {@link Result} from the command execution.
152+
*/
153+
private Result run(final String args) throws ExecutionException {
154+
155+
return this.devContainer.runCommand(args)
156+
157+
}
158+
159+
/**
160+
* Parse the Docker container id from the output of devcontainer up.
161+
* @param input to search for container id.
162+
* @returns the container id if found.
163+
*/
164+
@NonCPS
165+
private String parseContainerId(String input) {
166+
167+
Matcher matcher = input =~ /(?:\"containerId\")(?:\:\")(\w*)(?:\")/
168+
169+
if (!matcher.find()) {
170+
throw new Exception("Unable to find a container ID in ${input}")
171+
}
172+
173+
String containerID = matcher[0][1]
174+
175+
return containerID
176+
}
177+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/**
2+
* This package provides tools to work with vscode.
3+
*/
4+
package org.dsty.vscode

tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def run_test(job_path: str) -> str:
8282

8383
cmd = f"run_job {job_path}"
8484

85-
exitcode, raw_output = container.exec_run(cmd)
85+
exitcode, raw_output = container.exec_run(cmd, user="jenkins")
8686

8787
output = raw_output.decode("utf-8")
8888

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import pytest
2+
3+
from tests.conftest import Container
4+
5+
6+
@pytest.fixture()
7+
def job_folder():
8+
return "vscode"
9+
10+
11+
def test_devcontainer_example(container: Container, job_folder):
12+
13+
_ = container(f"{job_folder}/devcontainer_example.groovy")

0 commit comments

Comments
 (0)