-
Notifications
You must be signed in to change notification settings - Fork 9
/
course-definition.yml
299 lines (242 loc) · 12.7 KB
/
course-definition.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
slug: "docker"
name: "Build your own Docker"
short_name: "Docker"
release_status: "deprecated"
# This is shown on the course overview page. Markdown supported, recommended length ~40 words.
#
# Recommended format:
#
# > ABC is <whatever>. In this challenge, you'll build your own ABC that's capable of D, E, F and G.
# >
# > Along the way, we'll learn about X, Y, Z and more.
#
# Example:
#
# > Redis is an in-memory data structure store often used as a database, cache, message broken and streaming engine. In this challenge
# > you'll build your own Redis server that is capable of serving basic commands, reading RDB files and more.
# >
# > Along the way, you'll learn about TCP servers, the Redis Protocol and more.
description_md: |-
Docker is a tool used to build & run applications in containers. In this challenge, you'll build
your own Docker implementation that can pull an image from Docker Hub and execute commands in it.
Along the way, you'll learn about chroot, kernel namespaces, the Docker registry API and much more.
# Keep this under 70 characters
short_description_md: |-
Learn about kernel namespaces, chroot, the registry API and more
completion_percentage: 30
languages:
- slug: "c"
- slug: "go"
- slug: "nim"
- slug: "php"
- slug: "python"
release_status: "beta"
- slug: "ruby"
release_status: "beta"
- slug: "rust"
- slug: "swift"
release_status: "alpha"
alpha_tester_usernames: ["Terky"]
marketing:
difficulty: medium
sample_extension_idea_title: "Build from Dockerfile"
sample_extension_idea_description: "A Docker implementation that can build images from a Dockerfile"
testimonials:
- author_name: "Raghav Dua"
author_description: "SRE, Coinbase"
author_avatar: "https://codecrafters.io/images/external/testimonials/raghav-dua.jpeg"
link: "https://github.com/duaraghav8"
text: |-
I spent a full day on your Docker building course and ended up building the whole thing myself. As a SRE (and
mostly a user of docker), digging into the internals blew me away.
- author_name: "Beyang Liu"
author_description: "CTO at SourceGraph"
author_avatar: "https://codecrafters.io/images/external/testimonials/beyang-liu.jpeg"
link: "https://twitter.com/beyang"
text: |-
CodeCrafters has you build your own version of things like Git and Docker from scratch. A cool way to build a stronger mental model of how those tools work.
stages:
- slug: "je9"
name: "Execute a program"
difficulty: very_easy
description_md: |-
Your task is to implement a very basic version
of [`docker run`](https://docs.docker.com/engine/reference/run/)</a>. It will
be executed similar to `docker run`:
```
mydocker run alpine:latest /usr/local/bin/docker-explorer echo hey
```
[docker-explorer](https://github.com/codecrafters-io/docker-explorer) is a custom test program that exposes
commands like `echo` and `ls`.
For now, don't worry about pulling the `alpine:latest` image. We will just
execute a local program for this stage and print its output. You'll work on
pulling images from Docker Hub in stage 6.
marketing_md: |-
In this stage, you'll execute a program using `fork` + `exec`.
tester_source_code_url: "https://github.com/codecrafters-io/docker-tester/blob/18245703a5beed8ee0a7e1cbb7204a7ee3b3b5d1/internal/stage_basic_exec.go#L9"
- slug: "kf3"
name: "Wireup stdout & stderr"
difficulty: easy
description_md: |-
You'll now pipe the program's stdout and stderr to the
parent process.
Like the last stage, the tester will run your program like this:
```
mydocker run alpine:latest /usr/local/bin/docker-explorer echo hey
```
To test this behaviour locally, you could use the `echo` + `echo_stderr`
commands that `docker-explorer` exposes. Run `docker-explorer --help` to
view usage.
If you've got any logs or print statements in your code, make sure to remove
them. The tester can't differentiate between debug logs and the actual
output!
Note: The **README** in your repository contains setup
information for this stage and beyond (takes < 5 min).
marketing_md: |-
In this stage, you'll relay the child program's stdout & stderr to the
parent process.
tester_source_code_url: "https://github.com/codecrafters-io/docker-tester/blob/18245703a5beed8ee0a7e1cbb7204a7ee3b3b5d1/internal/stage_stdio.go#L9"
- slug: "cn8"
name: "Handle exit codes"
difficulty: easy
description_md: |-
In this stage, you'll need to relay the program's exit code to the parent
process.
If the program you're executing exits with exit code 1, your program
should exit with exit code 1 too.
To test this behaviour locally, you could use the `exit` command that
`docker-explorer` exposes. Run `docker-explorer --help` to view usage.
Just like the previous stage, the tester will run your program like this:
```
mydocker run alpine:latest /usr/local/bin/docker-explorer exit 1
```
marketing_md: |-
In this stage, you'll wait for the child program's exit code and exit with
it.
tester_source_code_url: "https://github.com/codecrafters-io/docker-tester/blob/18245703a5beed8ee0a7e1cbb7204a7ee3b3b5d1/internal/stage_exit_code.go#L9"
- slug: "if6"
name: "Filesystem isolation"
difficulty: medium
description_md: |-
In the previous stage, we executed a program that existed locally on our
machine. This program had write access to the whole filesystem, which
means that it could do **dangerous** things!
In this stage, you'll use [chroot](https://en.wikipedia.org/wiki/Chroot)
to ensure that the program you execute doesn't have access to any files on
the host machine. Create an empty temporary directory and `chroot` into it
when executing the command. You'll need to copy the binary being executed
too.
{{#lang_is_rust}}
At the time of writing this, the implementation of chroot in Rust's standard library
([std::os::unix::fs::chroot](https://doc.rust-lang.org/std/os/unix/fs/fn.chroot.html)) is still a
nightly-only experimental API. We've included [libc](https://crates.io/crates/libc) as a dependency
instead.
{{/lang_is_rust}}
{{#lang_is_nim}}
Since Nim's [posix module](https://nim-lang.org/docs/posix.html) doesn't
have `chroot` defined, you'll need to implement this yourself! For
examples on how to do this, view the source for other syscalls like
[chdir](https://nim-lang.org/docs/posix.html#chdir%2Ccstring).
{{/lang_is_nim}}
{{#lang_is_go}}
When executing your program within the chroot directory, you might run into an error that says
`open /dev/null: no such file or directory`. This is because [Cmd.Run()](https://golang.org/pkg/os/exec/#Cmd.Run)
and its siblings expect `/dev/null` to be present. You can work around this by either creating an empty
`/dev/null` file inside the chroot directory, or by ensuring that `Cmd.Stdout`, `Cmd.Stderr` and `Cmd.Stdin` are not `nil`.
More details about this [here](https://rohitpaulk.com/articles/cmd-run-dev-null).
{{/lang_is_go}}
{{#lang_is_rust}}
When executing your program within the chroot directory, you might run into an error that says
`no such file or directory` even if the binary exists within the chroot. This is because
[Command::output()](https://doc.rust-lang.org/std/process/struct.Command.html#method.output)
expects `/dev/null` to be present. You can work around this by creating an empty
`/dev/null` file inside the chroot directory. This cryptic error effects Go programs too, more details
[here](https://rohitpaulk.com/articles/cmd-run-dev-null).
{{/lang_is_rust}}
Just like the previous stage, the tester will run your program like this:
```
mydocker run alpine:latest /usr/local/bin/docker-explorer ls /some_dir
```
marketing_md: |-
In this stage, you'll restrict a program's access to the host filesystem
by using [chroot](https://en.wikipedia.org/wiki/Chroot).
tester_source_code_url: "https://github.com/codecrafters-io/docker-tester/blob/18245703a5beed8ee0a7e1cbb7204a7ee3b3b5d1/internal/stage_fs_isolation.go#L8"
- slug: "lu7"
name: "Process isolation"
difficulty: medium
description_md: |-
In the previous stage, we guarded against malicious activity by
restricting an executable's access to the filesystem.
There's another resource that needs to be guarded: the process tree. The
process you're executing is currently capable of viewing all other
processes running on the host system, and sending signals to them.
In this stage, you'll use [PID
namespaces](http://man7.org/linux/man-pages/man7/pid_namespaces.7.html) to
ensure that the program you execute has its own isolated process tree.
The process being executed must see itself as PID 1.
{{#lang_is_php}}
You'll need to use the `pcntl_unshare` function for this, which was
[added in PHP 7.4](https://www.php.net/manual/en/migration74.new-functions.php), and isn't properly documented
yet (as of 22 Jan 2021). Here's the [pull request](https://github.com/php/php-src/pull/3760) where it was added.
{{/lang_is_php}}
Just like the previous stage, the tester will run your program like this:
```
mydocker run alpine:latest /usr/local/bin/docker-explorer mypid
```
marketing_md: |-
In this stage, you'll restrict a program's access to the host's process
tree by using [PID
namespaces](http://man7.org/linux/man-pages/man7/pid_namespaces.7.html).
tester_source_code_url: "https://github.com/codecrafters-io/docker-tester/blob/18245703a5beed8ee0a7e1cbb7204a7ee3b3b5d1/internal/stage_process_isolation.go#L5"
- slug: "hs1"
name: "Fetch an image from the Docker Registry"
should_skip_previous_stages_for_test_run: true
difficulty: hard
description_md: |-
Your docker implementation can now execute a program with a fair degree of
isolation - it can't modify files or interact with processes running on
the host.
In this stage, you'll use [the Docker registry
API](https://docs.docker.com/registry/spec/api/) to fetch the contents of
a public image on [Docker Hub](https://hub.docker.com/) and then execute a
command within it.
You'll need to:
- Do a small [authentication dance](https://docs.docker.com/registry/spec/auth/token/)
- Fetch the [image manifest](https://docs.docker.com/registry/spec/api/#pulling-an-image-manifest)
- [Pull layers](https://docs.docker.com/registry/spec/api/#pulling-a-layer) of an image and extract them to the chroot directory
The base URL for Docker Hub's public registry is `registry.hub.docker.com`.
The tester will run your program like this:
```
mydocker run alpine:latest /bin/echo hey
```
The image used will be an [official
image](https://docs.docker.com/docker-hub/official_images/) from Docker
Hub. For example: [`alpine:latest`](https://hub.docker.com/_/alpine),
[`alpine:latest`](https://hub.docker.com/_/alpine),
[`busybox:latest`](https://hub.docker.com/_/busybox). When interacting with the
Registry API, you'll need to prepend `library/` to the image names.
{{#lang_is_rust}}
Since Rust doesn't have an archive extraction utility in its stdlib, you
might want to shell out and use `tar`.
You can use the [reqwest](https://crates.io/crates/reqwest) crate to make
HTTP requests, we've included it in the `Cargo.toml` file. We've also included
[serde_json](https://crates.io/crates/serde_json) to help with parsing JSON.
{{/lang_is_rust}}
{{#lang_is_go}}
Since Go doesn't have an archive extraction utility in its stdlib, you
might want to shell out and use `tar`.
{{/lang_is_go}}
{{#lang_is_nim}}
Since Nim doesn't have an archive extraction utility in its stdlib, you
might want to shell out and use `tar`.
{{/lang_is_nim}}
{{#lang_is_c}}
Since C doesn't have an archive extraction utility in its stdlib, you
might want to shell out and use `tar`.
You can assume that `libcurl` is available in the build environment.
{{/lang_is_c}}
marketing_md: |-
In this stage, you'll fetch an image from Docker Hub and execute a command
in it. You'll need to use [the Docker Registry
API](https://docs.docker.com/registry/spec/api/) for this.
tester_source_code_url: "https://github.com/codecrafters-io/docker-tester/blob/18245703a5beed8ee0a7e1cbb7204a7ee3b3b5d1/internal/stage_fetch_from_registry.go#L8"