Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6617d81

Browse files
authored
feat: allow for configurable ignore paths (#33)
1 parent 0968214 commit 6617d81

File tree

4 files changed

+107
-41
lines changed

4 files changed

+107
-41
lines changed

cmd/envbuilder/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func main() {
3030
SilenceUsage: true,
3131
SilenceErrors: true,
3232
RunE: func(cmd *cobra.Command, args []string) error {
33-
options := envbuilder.OptionsFromEnv(os.Getenv)
33+
options := envbuilder.OptionsFromEnv(os.LookupEnv)
3434

3535
var sendLogs func(ctx context.Context, log ...agentsdk.StartupLog) error
3636
agentURL := os.Getenv("CODER_AGENT_URL")

envbuilder.go

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,17 @@ type Options struct {
137137
// and pulling from container registries.
138138
Insecure bool `env:"INSECURE"`
139139

140+
// IgnorePaths is a comma separated list of paths
141+
// to ignore when building the workspace.
142+
IgnorePaths []string `env:"IGNORE_PATHS"`
143+
144+
// SkipRebuild skips building if the MagicFile exists.
145+
// This is used to skip building when a container is
146+
// restarting. e.g. docker stop -> docker start
147+
// This value can always be set to true - even if the
148+
// container is being started for the first time.
149+
SkipRebuild bool `env:"SKIP_REBUILD"`
150+
140151
// GitURL is the URL of the Git repository to clone.
141152
// This is optional!
142153
GitURL string `env:"GIT_URL"`
@@ -184,6 +195,12 @@ func Run(ctx context.Context, options Options) error {
184195
if options.InitCommand == "" {
185196
options.InitCommand = "/bin/sh"
186197
}
198+
if options.IgnorePaths == nil {
199+
// Kubernetes frequently stores secrets in /var/run/secrets, and
200+
// other applications might as well. This seems to be a sensible
201+
// default, but if that changes, it's simple to adjust.
202+
options.IgnorePaths = []string{"/var/run"}
203+
}
187204
// Default to the shell!
188205
initArgs := []string{"-c", options.InitScript}
189206
if options.InitArgs != "" {
@@ -464,17 +481,22 @@ func Run(ctx context.Context, options Options) error {
464481
// IgnorePaths in the Kaniko options doesn't properly ignore paths.
465482
// So we add them to the default ignore list. See:
466483
// https://github.com/GoogleContainerTools/kaniko/blob/63be4990ca5a60bdf06ddc4d10aa4eca0c0bc714/cmd/executor/cmd/root.go#L136
467-
ignorePaths := []string{MagicDir, options.LayerCacheDir, options.WorkspaceFolder}
484+
ignorePaths := append([]string{
485+
MagicDir,
486+
options.LayerCacheDir,
487+
options.WorkspaceFolder,
488+
}, options.IgnorePaths...)
489+
468490
for _, ignorePath := range ignorePaths {
469491
util.AddToDefaultIgnoreList(util.IgnoreListEntry{
470492
Path: ignorePath,
471-
PrefixMatchOnly: true,
493+
PrefixMatchOnly: false,
472494
})
473495
}
474496

475497
build := func() (v1.Image, error) {
476498
_, err := options.Filesystem.Stat(MagicFile)
477-
if err == nil {
499+
if err == nil && options.SkipRebuild {
478500
endStage := startStage("🏗️ Skipping build because of cache...")
479501
imageRef, err := devcontainer.ImageFromDockerfile(buildParams.DockerfileContent)
480502
if err != nil {
@@ -557,6 +579,10 @@ func Run(ctx context.Context, options Options) error {
557579
case strings.Contains(err.Error(), "authentication required"):
558580
fallback = true
559581
fallbackErr = err
582+
// This occurs from Docker Hub when the image cannot be found!
583+
case strings.Contains(err.Error(), "manifest unknown"):
584+
fallback = true
585+
fallbackErr = err
560586
case strings.Contains(err.Error(), "unexpected status code 401 Unauthorized"):
561587
logf(codersdk.LogLevelError, "Unable to pull the provided image. Ensure your registry credentials are correct!")
562588
}
@@ -851,7 +877,7 @@ func findUser(nameOrID string) (*user.User, error) {
851877
}
852878

853879
// OptionsFromEnv returns a set of options from environment variables.
854-
func OptionsFromEnv(getEnv func(string) string) Options {
880+
func OptionsFromEnv(getEnv func(string) (string, bool)) Options {
855881
options := Options{}
856882

857883
val := reflect.ValueOf(&options).Elem()
@@ -866,10 +892,18 @@ func OptionsFromEnv(getEnv func(string) string) Options {
866892
}
867893
switch fieldTyp.Type.Kind() {
868894
case reflect.String:
869-
field.SetString(getEnv(env))
895+
v, _ := getEnv(env)
896+
field.SetString(v)
870897
case reflect.Bool:
871-
v, _ := strconv.ParseBool(getEnv(env))
898+
e, _ := getEnv(env)
899+
v, _ := strconv.ParseBool(e)
872900
field.SetBool(v)
901+
case reflect.Slice:
902+
v, ok := getEnv(env)
903+
if !ok {
904+
continue
905+
}
906+
field.Set(reflect.ValueOf(strings.Split(v, ",")))
873907
}
874908
}
875909

envbuilder_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ func TestSystemOptions(t *testing.T) {
3030
"GIT_URL": "https://github.com/coder/coder",
3131
"WORKSPACE_FOLDER": "/workspaces/coder",
3232
}
33-
env := envbuilder.OptionsFromEnv(func(s string) string {
34-
return opts[s]
33+
env := envbuilder.OptionsFromEnv(func(s string) (string, bool) {
34+
return opts[s], true
3535
})
3636
require.Equal(t, "echo hello", env.InitScript)
3737
require.Equal(t, "kylecarbs/testing", env.CacheRepo)

integration/integration_test.go

Lines changed: 64 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ import (
1414
"net/http/httptest"
1515
"net/http/httputil"
1616
"net/url"
17+
"os"
1718
"os/exec"
19+
"path/filepath"
1820
"strings"
1921
"testing"
2022
"time"
@@ -52,9 +54,9 @@ func TestFailsGitAuth(t *testing.T) {
5254
username: "kyle",
5355
password: "testing",
5456
})
55-
_, err := runEnvbuilder(t, []string{
57+
_, err := runEnvbuilder(t, options{env: []string{
5658
"GIT_URL=" + url,
57-
})
59+
}})
5860
require.ErrorContains(t, err, "authentication required")
5961
}
6062

@@ -67,12 +69,12 @@ func TestSucceedsGitAuth(t *testing.T) {
6769
username: "kyle",
6870
password: "testing",
6971
})
70-
_, err := runEnvbuilder(t, []string{
72+
_, err := runEnvbuilder(t, options{env: []string{
7173
"GIT_URL=" + url,
7274
"DOCKERFILE_PATH=Dockerfile",
7375
"GIT_USERNAME=kyle",
7476
"GIT_PASSWORD=testing",
75-
})
77+
}})
7678
require.NoError(t, err)
7779
}
7880

@@ -111,9 +113,9 @@ func TestBuildFromDevcontainerWithFeatures(t *testing.T) {
111113
".devcontainer/Dockerfile": "FROM ubuntu",
112114
},
113115
})
114-
ctr, err := runEnvbuilder(t, []string{
116+
ctr, err := runEnvbuilder(t, options{env: []string{
115117
"GIT_URL=" + url,
116-
})
118+
}})
117119
require.NoError(t, err)
118120

119121
output := execContainer(t, ctr, "cat /test")
@@ -127,9 +129,32 @@ func TestBuildFromDockerfile(t *testing.T) {
127129
"Dockerfile": "FROM alpine:latest",
128130
},
129131
})
130-
ctr, err := runEnvbuilder(t, []string{
132+
ctr, err := runEnvbuilder(t, options{env: []string{
131133
"GIT_URL=" + url,
132134
"DOCKERFILE_PATH=Dockerfile",
135+
}})
136+
require.NoError(t, err)
137+
138+
output := execContainer(t, ctr, "echo hello")
139+
require.Equal(t, "hello", strings.TrimSpace(output))
140+
}
141+
142+
func TestBuildIgnoreVarRunSecrets(t *testing.T) {
143+
// Ensures that a Git repository with a Dockerfile is cloned and built.
144+
url := createGitServer(t, gitServerOptions{
145+
files: map[string]string{
146+
"Dockerfile": "FROM alpine:latest",
147+
},
148+
})
149+
dir := t.TempDir()
150+
err := os.WriteFile(filepath.Join(dir, "secret"), []byte("test"), 0644)
151+
require.NoError(t, err)
152+
ctr, err := runEnvbuilder(t, options{
153+
env: []string{
154+
"GIT_URL=" + url,
155+
"DOCKERFILE_PATH=Dockerfile",
156+
},
157+
binds: []string{fmt.Sprintf("%s:/var/run/secrets", dir)},
133158
})
134159
require.NoError(t, err)
135160

@@ -144,11 +169,11 @@ func TestBuildWithSetupScript(t *testing.T) {
144169
"Dockerfile": "FROM alpine:latest",
145170
},
146171
})
147-
ctr, err := runEnvbuilder(t, []string{
172+
ctr, err := runEnvbuilder(t, options{env: []string{
148173
"GIT_URL=" + url,
149174
"DOCKERFILE_PATH=Dockerfile",
150175
"SETUP_SCRIPT=echo \"INIT_ARGS=-c 'echo hi > /wow && sleep infinity'\" >> $ENVBUILDER_ENV",
151-
})
176+
}})
152177
require.NoError(t, err)
153178

154179
output := execContainer(t, ctr, "cat /wow")
@@ -161,14 +186,14 @@ func TestBuildCustomCertificates(t *testing.T) {
161186
"Dockerfile": "FROM alpine:latest",
162187
},
163188
}))
164-
ctr, err := runEnvbuilder(t, []string{
189+
ctr, err := runEnvbuilder(t, options{env: []string{
165190
"GIT_URL=" + srv.URL,
166191
"DOCKERFILE_PATH=Dockerfile",
167192
"SSL_CERT_BASE64=" + base64.StdEncoding.EncodeToString(pem.EncodeToMemory(&pem.Block{
168193
Type: "CERTIFICATE",
169194
Bytes: srv.TLS.Certificates[0].Certificate[0],
170195
})),
171-
})
196+
}})
172197
require.NoError(t, err)
173198

174199
output := execContainer(t, ctr, "echo hello")
@@ -182,10 +207,11 @@ func TestBuildStopStartCached(t *testing.T) {
182207
"Dockerfile": "FROM alpine:latest",
183208
},
184209
})
185-
ctr, err := runEnvbuilder(t, []string{
210+
ctr, err := runEnvbuilder(t, options{env: []string{
186211
"GIT_URL=" + url,
187212
"DOCKERFILE_PATH=Dockerfile",
188-
})
213+
"SKIP_REBUILD=true",
214+
}})
189215
require.NoError(t, err)
190216

191217
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
@@ -212,9 +238,9 @@ func TestCloneFailsFallback(t *testing.T) {
212238
t.Parallel()
213239
t.Run("BadRepo", func(t *testing.T) {
214240
t.Parallel()
215-
_, err := runEnvbuilder(t, []string{
241+
_, err := runEnvbuilder(t, options{env: []string{
216242
"GIT_URL=bad-value",
217-
})
243+
}})
218244
require.ErrorContains(t, err, envbuilder.ErrNoFallbackImage.Error())
219245
})
220246
}
@@ -229,10 +255,10 @@ func TestBuildFailsFallback(t *testing.T) {
229255
"Dockerfile": "bad syntax",
230256
},
231257
})
232-
_, err := runEnvbuilder(t, []string{
258+
_, err := runEnvbuilder(t, options{env: []string{
233259
"GIT_URL=" + url,
234260
"DOCKERFILE_PATH=Dockerfile",
235-
})
261+
}})
236262
require.ErrorContains(t, err, envbuilder.ErrNoFallbackImage.Error())
237263
require.ErrorContains(t, err, "dockerfile parse error")
238264
})
@@ -245,10 +271,10 @@ func TestBuildFailsFallback(t *testing.T) {
245271
RUN exit 1`,
246272
},
247273
})
248-
_, err := runEnvbuilder(t, []string{
274+
_, err := runEnvbuilder(t, options{env: []string{
249275
"GIT_URL=" + url,
250276
"DOCKERFILE_PATH=Dockerfile",
251-
})
277+
}})
252278
require.ErrorContains(t, err, envbuilder.ErrNoFallbackImage.Error())
253279
})
254280
t.Run("BadDevcontainer", func(t *testing.T) {
@@ -259,9 +285,9 @@ RUN exit 1`,
259285
".devcontainer/devcontainer.json": "not json",
260286
},
261287
})
262-
_, err := runEnvbuilder(t, []string{
288+
_, err := runEnvbuilder(t, options{env: []string{
263289
"GIT_URL=" + url,
264-
})
290+
}})
265291
require.ErrorContains(t, err, envbuilder.ErrNoFallbackImage.Error())
266292
})
267293
t.Run("NoImageOrDockerfile", func(t *testing.T) {
@@ -271,10 +297,10 @@ RUN exit 1`,
271297
".devcontainer/devcontainer.json": "{}",
272298
},
273299
})
274-
ctr, err := runEnvbuilder(t, []string{
300+
ctr, err := runEnvbuilder(t, options{env: []string{
275301
"GIT_URL=" + url,
276302
"FALLBACK_IMAGE=alpine:latest",
277-
})
303+
}})
278304
require.NoError(t, err)
279305

280306
output := execContainer(t, ctr, "echo hello")
@@ -297,10 +323,10 @@ func TestPrivateRegistry(t *testing.T) {
297323
"Dockerfile": "FROM " + image,
298324
},
299325
})
300-
_, err := runEnvbuilder(t, []string{
326+
_, err := runEnvbuilder(t, options{env: []string{
301327
"GIT_URL=" + url,
302328
"DOCKERFILE_PATH=Dockerfile",
303-
})
329+
}})
304330
require.ErrorContains(t, err, "Unauthorized")
305331
})
306332
t.Run("Auth", func(t *testing.T) {
@@ -326,11 +352,11 @@ func TestPrivateRegistry(t *testing.T) {
326352
})
327353
require.NoError(t, err)
328354

329-
_, err = runEnvbuilder(t, []string{
355+
_, err = runEnvbuilder(t, options{env: []string{
330356
"GIT_URL=" + url,
331357
"DOCKERFILE_PATH=Dockerfile",
332358
"DOCKER_CONFIG_BASE64=" + base64.StdEncoding.EncodeToString(config),
333-
})
359+
}})
334360
require.NoError(t, err)
335361
})
336362
t.Run("InvalidAuth", func(t *testing.T) {
@@ -356,11 +382,11 @@ func TestPrivateRegistry(t *testing.T) {
356382
})
357383
require.NoError(t, err)
358384

359-
_, err = runEnvbuilder(t, []string{
385+
_, err = runEnvbuilder(t, options{env: []string{
360386
"GIT_URL=" + url,
361387
"DOCKERFILE_PATH=Dockerfile",
362388
"DOCKER_CONFIG_BASE64=" + base64.StdEncoding.EncodeToString(config),
363-
})
389+
}})
364390
require.ErrorContains(t, err, "Unauthorized")
365391
})
366392
}
@@ -409,7 +435,7 @@ func setupPassthroughRegistry(t *testing.T, image string, auth *registryAuth) st
409435
}
410436

411437
func TestNoMethodFails(t *testing.T) {
412-
_, err := runEnvbuilder(t, []string{})
438+
_, err := runEnvbuilder(t, options{env: []string{}})
413439
require.ErrorContains(t, err, envbuilder.ErrNoFallbackImage.Error())
414440
}
415441

@@ -509,9 +535,14 @@ func cleanOldEnvbuilders() {
509535
}
510536
}
511537

538+
type options struct {
539+
binds []string
540+
env []string
541+
}
542+
512543
// runEnvbuilder starts the envbuilder container with the given environment
513544
// variables and returns the container ID.
514-
func runEnvbuilder(t *testing.T, env []string) (string, error) {
545+
func runEnvbuilder(t *testing.T, options options) (string, error) {
515546
t.Helper()
516547
ctx := context.Background()
517548
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
@@ -521,12 +552,13 @@ func runEnvbuilder(t *testing.T, env []string) (string, error) {
521552
})
522553
ctr, err := cli.ContainerCreate(ctx, &container.Config{
523554
Image: "envbuilder:latest",
524-
Env: env,
555+
Env: options.env,
525556
Labels: map[string]string{
526557
testContainerLabel: "true",
527558
},
528559
}, &container.HostConfig{
529560
NetworkMode: container.NetworkMode("host"),
561+
Binds: options.binds,
530562
}, nil, nil, "")
531563
require.NoError(t, err)
532564
t.Cleanup(func() {

0 commit comments

Comments
 (0)