Skip to content

Commit f59bd37

Browse files
authored
chore: add Makefile and local registry cache (#138)
Adds a Makefile and updates README with local dev instructions Adds a local registry cache to avoid hitting Docker Hub rate-limits Updates existing tests to reference locally cached images
1 parent a3b3b15 commit f59bd37

File tree

6 files changed

+100
-31
lines changed

6 files changed

+100
-31
lines changed

.github/workflows/ci.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,4 @@ jobs:
4848
go-version: "~1.21"
4949

5050
- name: Test
51-
run: go test ./...
51+
run: make test

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
scripts/envbuilder-*
2+
.registry-cache

Makefile

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
GOARCH := $(shell go env GOARCH)
2+
PWD=$(shell pwd)
3+
4+
develop:
5+
./scripts/develop.sh
6+
7+
build: scripts/envbuilder-$(GOARCH)
8+
./scripts/build.sh
9+
10+
.PHONY: test
11+
test: test-registry test-images
12+
go test -count=1 ./...
13+
14+
# Starts a local Docker registry on port 5000 with a local disk cache.
15+
.PHONY: test-registry
16+
test-registry: .registry-cache
17+
if ! curl -fsSL http://localhost:5000/v2/_catalog > /dev/null 2>&1; then \
18+
docker rm -f envbuilder-registry && \
19+
docker run -d -p 5000:5000 --name envbuilder-registry --volume $(PWD)/.registry-cache:/var/lib/registry registry:2; \
20+
fi
21+
22+
# Pulls images referenced in integration tests and pushes them to the local cache.
23+
.PHONY: test-images
24+
test-images: .registry-cache .registry-cache/docker/registry/v2/repositories/envbuilder-test-alpine .registry-cache/docker/registry/v2/repositories/envbuilder-test-ubuntu .registry-cache/docker/registry/v2/repositories/envbuilder-test-codercom-code-server
25+
26+
.registry-cache:
27+
mkdir -p .registry-cache && chmod -R ag+w .registry-cache
28+
29+
.registry-cache/docker/registry/v2/repositories/envbuilder-test-alpine:
30+
docker pull alpine:latest
31+
docker tag alpine:latest localhost:5000/envbuilder-test-alpine:latest
32+
docker push localhost:5000/envbuilder-test-alpine:latest
33+
34+
.registry-cache/docker/registry/v2/repositories/envbuilder-test-ubuntu:
35+
docker pull ubuntu:latest
36+
docker tag ubuntu:latest localhost:5000/envbuilder-test-ubuntu:latest
37+
docker push localhost:5000/envbuilder-test-ubuntu:latest
38+
39+
.registry-cache/docker/registry/v2/repositories/envbuilder-test-codercom-code-server:
40+
docker pull codercom/code-server:latest
41+
docker tag codercom/code-server:latest localhost:5000/envbuilder-test-codercom-code-server:latest
42+
docker push localhost:5000/envbuilder-test-codercom-code-server:latest

README.md

+20
Original file line numberDiff line numberDiff line change
@@ -224,3 +224,23 @@ docker run -it --rm \
224224
- [`SSL_CERT_FILE`](https://go.dev/src/crypto/x509/root_unix.go#L19): Specifies the path to an SSL certificate.
225225
- [`SSL_CERT_DIR`](https://go.dev/src/crypto/x509/root_unix.go#L25): Identifies which directory to check for SSL certificate files.
226226
- `SSL_CERT_BASE64`: Specifies a base64-encoded SSL certificate that will be added to the global certificate pool on start.
227+
228+
229+
# Local Development
230+
231+
Building `envbuilder` currently **requires** a Linux system.
232+
233+
On MacOS or Windows systems, we recommend either using a VM or the provided `.devcontainer` for development.
234+
235+
**Additional Requirements:**
236+
237+
- `go 1.21`
238+
- `make`
239+
- Docker daemon (for running tests)
240+
241+
**Makefile targets:**
242+
243+
- `build`: builds and tags `envbuilder:latest` for your current architecture.
244+
- `develop`: runs `envbuilder:latest` against a sample Git repository.
245+
- `test`: run tests.
246+
- `test-registry`: stands up a local registry for caching images used in tests.

devcontainer/devcontainer_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func TestCompileWithFeatures(t *testing.T) {
7878
"context": ".",
7979
},
8080
// Comments here!
81-
"image": "codercom/code-server:latest",
81+
"image": "localhost:5000/envbuilder-test-codercom-code-server:latest",
8282
"features": {
8383
"` + featureOne + `": {},
8484
"` + featureTwo + `": "potato"
@@ -96,7 +96,7 @@ func TestCompileWithFeatures(t *testing.T) {
9696
featureTwoMD5 := md5.Sum([]byte(featureTwo))
9797
featureTwoDir := fmt.Sprintf("/.envbuilder/features/two-%x", featureTwoMD5[:4])
9898

99-
require.Equal(t, `FROM codercom/code-server:latest
99+
require.Equal(t, `FROM localhost:5000/envbuilder-test-codercom-code-server:latest
100100
101101
USER root
102102
# Rust tomato - Example description!
@@ -116,7 +116,7 @@ func TestCompileDevContainer(t *testing.T) {
116116
t.Parallel()
117117
fs := memfs.New()
118118
dc := &devcontainer.Spec{
119-
Image: "codercom/code-server:latest",
119+
Image: "localhost:5000/envbuilder-test-ubuntu:latest",
120120
}
121121
params, err := dc.Compile(fs, "", magicDir, "", "", false)
122122
require.NoError(t, err)
@@ -141,7 +141,7 @@ func TestCompileDevContainer(t *testing.T) {
141141
require.NoError(t, err)
142142
file, err := fs.OpenFile(filepath.Join(dcDir, "Dockerfile"), os.O_CREATE|os.O_WRONLY, 0644)
143143
require.NoError(t, err)
144-
_, err = io.WriteString(file, "FROM ubuntu")
144+
_, err = io.WriteString(file, "FROM localhost:5000/envbuilder-test-ubuntu:latest")
145145
require.NoError(t, err)
146146
_ = file.Close()
147147
params, err := dc.Compile(fs, dcDir, magicDir, "", "/var/workspace", false)

integration/integration_test.go

+32-26
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,15 @@ import (
4343

4444
const (
4545
testContainerLabel = "envbox-integration-test"
46+
testImageAlpine = "localhost:5000/envbuilder-test-alpine:latest"
47+
testImageUbuntu = "localhost:5000/envbuilder-test-ubuntu:latest"
4648
)
4749

4850
func TestFailsGitAuth(t *testing.T) {
4951
t.Parallel()
5052
url := createGitServer(t, gitServerOptions{
5153
files: map[string]string{
52-
"Dockerfile": "FROM alpine:latest",
54+
"Dockerfile": "FROM " + testImageAlpine,
5355
},
5456
username: "kyle",
5557
password: "testing",
@@ -64,7 +66,7 @@ func TestSucceedsGitAuth(t *testing.T) {
6466
t.Parallel()
6567
url := createGitServer(t, gitServerOptions{
6668
files: map[string]string{
67-
"Dockerfile": "FROM alpine:latest",
69+
"Dockerfile": "FROM " + testImageAlpine,
6870
},
6971
username: "kyle",
7072
password: "testing",
@@ -142,7 +144,7 @@ func TestBuildFromDevcontainerWithFeatures(t *testing.T) {
142144
}
143145
}
144146
}`,
145-
".devcontainer/Dockerfile": "FROM ubuntu",
147+
".devcontainer/Dockerfile": "FROM " + testImageUbuntu,
146148
".devcontainer/feature3/devcontainer-feature.json": string(feature3Spec),
147149
".devcontainer/feature3/install.sh": "echo $GRAPE > /test3output",
148150
},
@@ -166,7 +168,7 @@ func TestBuildFromDockerfile(t *testing.T) {
166168
// Ensures that a Git repository with a Dockerfile is cloned and built.
167169
url := createGitServer(t, gitServerOptions{
168170
files: map[string]string{
169-
"Dockerfile": "FROM alpine:latest",
171+
"Dockerfile": "FROM " + testImageAlpine,
170172
},
171173
})
172174
ctr, err := runEnvbuilder(t, options{env: []string{
@@ -183,7 +185,7 @@ func TestBuildPrintBuildOutput(t *testing.T) {
183185
// Ensures that a Git repository with a Dockerfile is cloned and built.
184186
url := createGitServer(t, gitServerOptions{
185187
files: map[string]string{
186-
"Dockerfile": "FROM alpine:latest\nRUN echo hello",
188+
"Dockerfile": "FROM " + testImageAlpine + "\nRUN echo hello",
187189
},
188190
})
189191
ctr, err := runEnvbuilder(t, options{env: []string{
@@ -211,7 +213,7 @@ func TestBuildIgnoreVarRunSecrets(t *testing.T) {
211213
// Ensures that a Git repository with a Dockerfile is cloned and built.
212214
url := createGitServer(t, gitServerOptions{
213215
files: map[string]string{
214-
"Dockerfile": "FROM alpine:latest",
216+
"Dockerfile": "FROM " + testImageAlpine,
215217
},
216218
})
217219
dir := t.TempDir()
@@ -234,7 +236,7 @@ func TestBuildWithSetupScript(t *testing.T) {
234236
// Ensures that a Git repository with a Dockerfile is cloned and built.
235237
url := createGitServer(t, gitServerOptions{
236238
files: map[string]string{
237-
"Dockerfile": "FROM alpine:latest",
239+
"Dockerfile": "FROM " + testImageAlpine,
238240
},
239241
})
240242
ctr, err := runEnvbuilder(t, options{env: []string{
@@ -260,7 +262,7 @@ func TestBuildFromDevcontainerInCustomPath(t *testing.T) {
260262
"dockerfile": "Dockerfile"
261263
},
262264
}`,
263-
".devcontainer/custom/Dockerfile": "FROM ubuntu",
265+
".devcontainer/custom/Dockerfile": "FROM " + testImageUbuntu,
264266
},
265267
})
266268
ctr, err := runEnvbuilder(t, options{env: []string{
@@ -285,7 +287,7 @@ func TestBuildFromDevcontainerInSubfolder(t *testing.T) {
285287
"dockerfile": "Dockerfile"
286288
},
287289
}`,
288-
".devcontainer/subfolder/Dockerfile": "FROM ubuntu",
290+
".devcontainer/subfolder/Dockerfile": "FROM " + testImageUbuntu,
289291
},
290292
})
291293
ctr, err := runEnvbuilder(t, options{env: []string{
@@ -308,7 +310,7 @@ func TestBuildFromDevcontainerInRoot(t *testing.T) {
308310
"dockerfile": "Dockerfile"
309311
},
310312
}`,
311-
"Dockerfile": "FROM ubuntu",
313+
"Dockerfile": "FROM " + testImageUbuntu,
312314
},
313315
})
314316
ctr, err := runEnvbuilder(t, options{env: []string{
@@ -323,7 +325,7 @@ func TestBuildFromDevcontainerInRoot(t *testing.T) {
323325
func TestBuildCustomCertificates(t *testing.T) {
324326
srv := httptest.NewTLSServer(createGitHandler(t, gitServerOptions{
325327
files: map[string]string{
326-
"Dockerfile": "FROM alpine:latest",
328+
"Dockerfile": "FROM " + testImageAlpine,
327329
},
328330
}))
329331
ctr, err := runEnvbuilder(t, options{env: []string{
@@ -344,7 +346,7 @@ func TestBuildStopStartCached(t *testing.T) {
344346
// Ensures that a Git repository with a Dockerfile is cloned and built.
345347
url := createGitServer(t, gitServerOptions{
346348
files: map[string]string{
347-
"Dockerfile": "FROM alpine:latest",
349+
"Dockerfile": "FROM " + testImageAlpine,
348350
},
349351
})
350352
ctr, err := runEnvbuilder(t, options{env: []string{
@@ -407,7 +409,7 @@ func TestBuildFailsFallback(t *testing.T) {
407409
// Ensures that a Git repository with a Dockerfile is cloned and built.
408410
url := createGitServer(t, gitServerOptions{
409411
files: map[string]string{
410-
"Dockerfile": `FROM alpine
412+
"Dockerfile": `FROM ` + testImageAlpine + `
411413
RUN exit 1`,
412414
},
413415
})
@@ -439,7 +441,7 @@ RUN exit 1`,
439441
})
440442
ctr, err := runEnvbuilder(t, options{env: []string{
441443
"GIT_URL=" + url,
442-
"FALLBACK_IMAGE=alpine:latest",
444+
"FALLBACK_IMAGE=" + testImageAlpine,
443445
}})
444446
require.NoError(t, err)
445447

@@ -458,7 +460,7 @@ func TestExitBuildOnFailure(t *testing.T) {
458460
_, err := runEnvbuilder(t, options{env: []string{
459461
"GIT_URL=" + url,
460462
"DOCKERFILE_PATH=Dockerfile",
461-
"FALLBACK_IMAGE=alpine",
463+
"FALLBACK_IMAGE=" + testImageAlpine,
462464
// Ensures that the fallback doesn't work when an image is specified.
463465
"EXIT_ON_BUILD_FAILURE=true",
464466
}})
@@ -486,7 +488,7 @@ func TestContainerEnv(t *testing.T) {
486488
"REMOTE_BAR": "${FROM_CONTAINER_ENV}"
487489
}
488490
}`,
489-
".devcontainer/Dockerfile": "FROM alpine:latest\nENV FROM_DOCKERFILE=foo",
491+
".devcontainer/Dockerfile": "FROM " + testImageAlpine + "\nENV FROM_DOCKERFILE=foo",
490492
},
491493
})
492494
ctr, err := runEnvbuilder(t, options{env: []string{
@@ -523,7 +525,7 @@ func TestLifecycleScripts(t *testing.T) {
523525
"parallel2": ["sh", "-c", "echo parallel2 > /tmp/parallel2"]
524526
}
525527
}`,
526-
".devcontainer/Dockerfile": "FROM alpine:latest\nUSER nobody",
528+
".devcontainer/Dockerfile": "FROM " + testImageAlpine + "\nUSER nobody",
527529
},
528530
})
529531
ctr, err := runEnvbuilder(t, options{env: []string{
@@ -559,7 +561,7 @@ func TestPostStartScript(t *testing.T) {
559561
".devcontainer/init.sh": `#!/bin/sh
560562
/tmp/post-start.sh
561563
sleep infinity`,
562-
".devcontainer/Dockerfile": `FROM alpine:latest
564+
".devcontainer/Dockerfile": `FROM ` + testImageAlpine + `
563565
COPY init.sh /bin
564566
RUN chmod +x /bin/init.sh
565567
USER nobody`,
@@ -586,7 +588,9 @@ func TestPrivateRegistry(t *testing.T) {
586588
t.Parallel()
587589
t.Run("NoAuth", func(t *testing.T) {
588590
t.Parallel()
589-
image := setupPassthroughRegistry(t, "library/alpine", &registryAuth{
591+
// Even if something goes wrong with auth,
592+
// the pull will fail as "scratch" is a reserved name.
593+
image := setupPassthroughRegistry(t, "scratch", &registryAuth{
590594
Username: "user",
591595
Password: "test",
592596
})
@@ -605,7 +609,7 @@ func TestPrivateRegistry(t *testing.T) {
605609
})
606610
t.Run("Auth", func(t *testing.T) {
607611
t.Parallel()
608-
image := setupPassthroughRegistry(t, "library/alpine", &registryAuth{
612+
image := setupPassthroughRegistry(t, "envbuilder-test-alpine:latest", &registryAuth{
609613
Username: "user",
610614
Password: "test",
611615
})
@@ -635,7 +639,9 @@ func TestPrivateRegistry(t *testing.T) {
635639
})
636640
t.Run("InvalidAuth", func(t *testing.T) {
637641
t.Parallel()
638-
image := setupPassthroughRegistry(t, "library/alpine", &registryAuth{
642+
// Even if something goes wrong with auth,
643+
// the pull will fail as "scratch" is a reserved name.
644+
image := setupPassthroughRegistry(t, "scratch", &registryAuth{
639645
Username: "user",
640646
Password: "banana",
641647
})
@@ -672,22 +678,22 @@ type registryAuth struct {
672678

673679
func setupPassthroughRegistry(t *testing.T, image string, auth *registryAuth) string {
674680
t.Helper()
675-
dockerURL, err := url.Parse("https://registry-1.docker.io")
681+
dockerURL, err := url.Parse("http://localhost:5000")
676682
require.NoError(t, err)
677683
proxy := httputil.NewSingleHostReverseProxy(dockerURL)
678684

679685
// The Docker registry uses short-lived JWTs to authenticate
680686
// anonymously to pull images. To test our MITM auth, we need to
681687
// generate a JWT for the proxy to use.
682-
registry, err := name.NewRegistry("registry-1.docker.io")
688+
registry, err := name.NewRegistry("localhost:5000")
683689
require.NoError(t, err)
684690
proxy.Transport, err = transport.NewWithContext(context.Background(), registry, authn.Anonymous, http.DefaultTransport, []string{})
685691
require.NoError(t, err)
686692

687693
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
688-
r.Host = "registry-1.docker.io"
689-
r.URL.Host = "registry-1.docker.io"
690-
r.URL.Scheme = "https"
694+
r.Host = "localhost:5000"
695+
r.URL.Host = "localhost:5000"
696+
r.URL.Scheme = "http"
691697

692698
if auth != nil {
693699
user, pass, ok := r.BasicAuth()

0 commit comments

Comments
 (0)