Skip to content

Commit 56be370

Browse files
committed
fix: allow setting MagicDir in Options
1 parent fb7e689 commit 56be370

File tree

4 files changed

+208
-89
lines changed

4 files changed

+208
-89
lines changed

constants/constants.go

Lines changed: 44 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -15,34 +15,10 @@ const (
1515
// nothing going on... it's empty!
1616
EmptyWorkspaceDir = WorkspacesDir + "/empty"
1717

18-
// MagicDir is where all envbuilder related files are stored.
19-
// This is a special directory that must not be modified
20-
// by the user or images.
21-
MagicDir = "/.envbuilder"
22-
)
23-
24-
var (
25-
ErrNoFallbackImage = errors.New("no fallback image has been specified")
26-
27-
// MagicFile is a file that is created in the workspace
28-
// when envbuilder has already been run. This is used
29-
// to skip building when a container is restarting.
30-
// e.g. docker stop -> docker start
31-
MagicFile = filepath.Join(MagicDir, "built")
32-
33-
// MagicFile is the location of the build context when
34-
// using remote build mode.
35-
MagicRemoteRepoDir = filepath.Join(MagicDir, "repo")
36-
37-
// MagicBinaryLocation is the expected location of the envbuilder binary
38-
// inside a builder image.
39-
MagicBinaryLocation = filepath.Join(MagicDir, "bin", "envbuilder")
40-
41-
// MagicImage is a file that is created in the image when
42-
// envbuilder has already been run. This is used to skip
43-
// the destructive initial build step when 'resuming' envbuilder
44-
// from a previously built image.
45-
MagicImage = filepath.Join(MagicDir, "image")
18+
// defaultMagicDir is the default working location for envbuilder.
19+
// This is a special directory that must not be modified by the user
20+
// or images. This is intentionally unexported.
21+
defaultMagicDir = "/.envbuilder"
4622

4723
// MagicTempDir is a directory inside the build context inside which
4824
// we place files referenced by MagicDirectives.
@@ -51,14 +27,46 @@ var (
5127
// MagicDirectives are directives automatically appended to Dockerfiles
5228
// when pushing the image. These directives allow the built image to be
5329
// 're-used'.
54-
MagicDirectives = fmt.Sprintf(`
55-
COPY --chmod=0755 %[1]s %[2]s
56-
COPY --chmod=0644 %[3]s %[4]s
30+
MagicDirectives = `
31+
COPY --chmod=0755 .envbuilder.tmp/envbuilder /.envbuilder/bin/envbuilder
32+
COPY --chmod=0644 .envbuilder.tmp/image /.envbuilder/image
5733
USER root
5834
WORKDIR /
59-
ENTRYPOINT [%[2]q]
60-
`,
61-
".envbuilder.tmp/envbuilder", MagicBinaryLocation,
62-
".envbuilder.tmp/image", MagicImage,
63-
)
35+
ENTRYPOINT ["/.envbuilder/bin/envbuilder"]
36+
`
6437
)
38+
39+
// ErrNoFallbackImage is returned when no fallback image has been specified.
40+
var ErrNoFallbackImage = errors.New("no fallback image has been specified")
41+
42+
// MagicDir is a working directory for envbuilder. We use this to
43+
// store files that are used when building images.
44+
type MagicDir string
45+
46+
// String returns the string representation of the MagicDir.
47+
func (m MagicDir) String() string {
48+
if m == "" {
49+
// Instead of the zero value, use defaultMagicDir.
50+
return defaultMagicDir
51+
}
52+
return filepath.Join("/", string(m))
53+
}
54+
55+
// MagicDir implements fmt.Stringer.
56+
var _ fmt.Stringer = MagicDir("")
57+
58+
// MagicFile is a file that is created in the workspace
59+
// when envbuilder has already been run. This is used
60+
// to skip building when a container is restarting.
61+
// e.g. docker stop -> docker start
62+
func (m MagicDir) Built() string {
63+
return filepath.Join(m.String(), "built")
64+
}
65+
66+
// MagicImage is a file that is created in the image when
67+
// envbuilder has already been run. This is used to skip
68+
// the destructive initial build step when 'resuming' envbuilder
69+
// from a previously built image.
70+
func (m MagicDir) Image() string {
71+
return filepath.Join(m.String(), "image")
72+
}

envbuilder.go

Lines changed: 48 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func Run(ctx context.Context, opts options.Options) error {
9292

9393
opts.Logger(log.LevelInfo, "%s %s - Build development environments from repositories in a container", newColor(color.Bold).Sprintf("envbuilder"), buildinfo.Version())
9494

95-
cleanupDockerConfigJSON, err := initDockerConfigJSON(opts.DockerConfigBase64)
95+
cleanupDockerConfigJSON, err := initDockerConfigJSON(opts.Logger, opts.MagicDir, opts.DockerConfigBase64)
9696
if err != nil {
9797
return err
9898
}
@@ -168,7 +168,7 @@ func Run(ctx context.Context, opts options.Options) error {
168168
}
169169

170170
defaultBuildParams := func() (*devcontainer.Compiled, error) {
171-
dockerfile := filepath.Join(constants.MagicDir, "Dockerfile")
171+
dockerfile := filepath.Join(opts.MagicDir.String(), "Dockerfile")
172172
file, err := opts.Filesystem.OpenFile(dockerfile, os.O_CREATE|os.O_WRONLY, 0o644)
173173
if err != nil {
174174
return nil, err
@@ -190,7 +190,7 @@ func Run(ctx context.Context, opts options.Options) error {
190190
return &devcontainer.Compiled{
191191
DockerfilePath: dockerfile,
192192
DockerfileContent: content,
193-
BuildContext: constants.MagicDir,
193+
BuildContext: opts.MagicDir.String(),
194194
}, nil
195195
}
196196

@@ -232,7 +232,7 @@ func Run(ctx context.Context, opts options.Options) error {
232232
opts.Logger(log.LevelInfo, "No Dockerfile or image specified; falling back to the default image...")
233233
fallbackDockerfile = defaultParams.DockerfilePath
234234
}
235-
buildParams, err = devContainer.Compile(opts.Filesystem, devcontainerDir, constants.MagicDir, fallbackDockerfile, opts.WorkspaceFolder, false, os.LookupEnv)
235+
buildParams, err = devContainer.Compile(opts.Filesystem, devcontainerDir, opts.MagicDir.String(), fallbackDockerfile, opts.WorkspaceFolder, false, os.LookupEnv)
236236
if err != nil {
237237
return fmt.Errorf("compile devcontainer.json: %w", err)
238238
}
@@ -304,7 +304,7 @@ func Run(ctx context.Context, opts options.Options) error {
304304
// So we add them to the default ignore list. See:
305305
// https://github.com/GoogleContainerTools/kaniko/blob/63be4990ca5a60bdf06ddc4d10aa4eca0c0bc714/cmd/executor/cmd/root.go#L136
306306
ignorePaths := append([]string{
307-
constants.MagicDir,
307+
opts.MagicDir.String(),
308308
opts.WorkspaceFolder,
309309
// See: https://github.com/coder/envbuilder/issues/37
310310
"/etc/resolv.conf",
@@ -332,31 +332,26 @@ func Run(ctx context.Context, opts options.Options) error {
332332
if err := util.AddAllowedPathToDefaultIgnoreList(opts.BinaryPath); err != nil {
333333
return fmt.Errorf("add envbuilder binary to ignore list: %w", err)
334334
}
335-
if err := util.AddAllowedPathToDefaultIgnoreList(constants.MagicImage); err != nil {
335+
if err := util.AddAllowedPathToDefaultIgnoreList(opts.MagicDir.Image()); err != nil {
336336
return fmt.Errorf("add magic image file to ignore list: %w", err)
337337
}
338-
magicTempDir := filepath.Join(buildParams.BuildContext, constants.MagicTempDir)
339-
if err := opts.Filesystem.MkdirAll(magicTempDir, 0o755); err != nil {
338+
magicTempDir := constants.MagicDir(filepath.Join(buildParams.BuildContext, constants.MagicTempDir))
339+
if err := opts.Filesystem.MkdirAll(magicTempDir.String(), 0o755); err != nil {
340340
return fmt.Errorf("create magic temp dir in build context: %w", err)
341341
}
342342
// Add the magic directives that embed the binary into the built image.
343343
buildParams.DockerfileContent += constants.MagicDirectives
344344
// Copy the envbuilder binary into the build context.
345345
// External callers will need to specify the path to the desired envbuilder binary.
346-
envbuilderBinDest := filepath.Join(
347-
magicTempDir,
348-
filepath.Base(constants.MagicBinaryLocation),
349-
)
346+
envbuilderBinDest := filepath.Join(magicTempDir.String(), "envbuilder")
350347
// Also touch the magic file that signifies the image has been built!
351-
magicImageDest := filepath.Join(
352-
magicTempDir,
353-
filepath.Base(constants.MagicImage),
354-
)
348+
// magicImageDest := filepath.Join(magicTempDir, "image")
349+
magicImageDest := magicTempDir.Image()
355350
// Clean up after build!
356351
var cleanupOnce sync.Once
357352
cleanupBuildContext = func() {
358353
cleanupOnce.Do(func() {
359-
for _, path := range []string{magicImageDest, envbuilderBinDest, magicTempDir} {
354+
for _, path := range []string{magicImageDest, envbuilderBinDest, magicTempDir.String()} {
360355
if err := opts.Filesystem.Remove(path); err != nil {
361356
opts.Logger(log.LevelWarn, "failed to clean up magic temp dir from build context: %w", err)
362357
}
@@ -370,15 +365,14 @@ func Run(ctx context.Context, opts options.Options) error {
370365
return fmt.Errorf("copy envbuilder binary to build context: %w", err)
371366
}
372367

373-
opts.Logger(log.LevelDebug, "touching magic image file at %q in build context %q", magicImageDest, buildParams.BuildContext)
368+
opts.Logger(log.LevelDebug, "touching magic image file at %q in build context %q", magicImageDest, magicTempDir)
374369
if err := touchFile(opts.Filesystem, magicImageDest, 0o755); err != nil {
375370
return fmt.Errorf("touch magic image file in build context: %w", err)
376371
}
377-
378372
}
379373

380374
// temp move of all ro mounts
381-
tempRemountDest := filepath.Join("/", constants.MagicDir, "mnt")
375+
tempRemountDest := filepath.Join(opts.MagicDir.String(), "mnt")
382376
// ignorePrefixes is a superset of ignorePaths that we pass to kaniko's
383377
// IgnoreList.
384378
ignorePrefixes := append([]string{"/dev", "/proc", "/sys"}, ignorePaths...)
@@ -399,8 +393,8 @@ func Run(ctx context.Context, opts options.Options) error {
399393
defer closeStderr()
400394
build := func() (v1.Image, error) {
401395
defer cleanupBuildContext()
402-
_, alreadyBuiltErr := opts.Filesystem.Stat(constants.MagicFile)
403-
_, isImageErr := opts.Filesystem.Stat(constants.MagicImage)
396+
_, alreadyBuiltErr := opts.Filesystem.Stat(opts.MagicDir.Built())
397+
_, isImageErr := opts.Filesystem.Stat(opts.MagicDir.Image())
404398
if (alreadyBuiltErr == nil && opts.SkipRebuild) || isImageErr == nil {
405399
endStage := startStage("🏗️ Skipping build because of cache...")
406400
imageRef, err := devcontainer.ImageFromDockerfile(buildParams.DockerfileContent)
@@ -545,7 +539,7 @@ func Run(ctx context.Context, opts options.Options) error {
545539

546540
// Create the magic file to indicate that this build
547541
// has already been ran before!
548-
file, err := opts.Filesystem.Create(constants.MagicFile)
542+
file, err := opts.Filesystem.Create(opts.MagicDir.Built())
549543
if err != nil {
550544
return fmt.Errorf("create magic file: %w", err)
551545
}
@@ -752,7 +746,7 @@ func Run(ctx context.Context, opts options.Options) error {
752746
opts.Logger(log.LevelInfo, "=== Running the setup command %q as the root user...", opts.SetupScript)
753747

754748
envKey := "ENVBUILDER_ENV"
755-
envFile := filepath.Join("/", constants.MagicDir, "environ")
749+
envFile := filepath.Join(opts.MagicDir.String(), "environ")
756750
file, err := os.Create(envFile)
757751
if err != nil {
758752
return fmt.Errorf("create environ file: %w", err)
@@ -876,7 +870,7 @@ func RunCacheProbe(ctx context.Context, opts options.Options) (v1.Image, error)
876870

877871
opts.Logger(log.LevelInfo, "%s %s - Build development environments from repositories in a container", newColor(color.Bold).Sprintf("envbuilder"), buildinfo.Version())
878872

879-
cleanupDockerConfigJSON, err := initDockerConfigJSON(opts.DockerConfigBase64)
873+
cleanupDockerConfigJSON, err := initDockerConfigJSON(opts.Logger, opts.MagicDir, opts.DockerConfigBase64)
880874
if err != nil {
881875
return nil, err
882876
}
@@ -1080,7 +1074,7 @@ func RunCacheProbe(ctx context.Context, opts options.Options) (v1.Image, error)
10801074
// So we add them to the default ignore list. See:
10811075
// https://github.com/GoogleContainerTools/kaniko/blob/63be4990ca5a60bdf06ddc4d10aa4eca0c0bc714/cmd/executor/cmd/root.go#L136
10821076
ignorePaths := append([]string{
1083-
constants.MagicDir,
1077+
opts.MagicDir.String(),
10841078
opts.WorkspaceFolder,
10851079
// See: https://github.com/coder/envbuilder/issues/37
10861080
"/etc/resolv.conf",
@@ -1103,29 +1097,25 @@ func RunCacheProbe(ctx context.Context, opts options.Options) (v1.Image, error)
11031097
// build via executor.RunCacheProbe we need to have the *exact* copy of the
11041098
// envbuilder binary available used to build the image and we also need to
11051099
// add the magic directives to the Dockerfile content.
1100+
// MAGICDIR
11061101
buildParams.DockerfileContent += constants.MagicDirectives
11071102
magicTempDir := filepath.Join(buildParams.BuildContext, constants.MagicTempDir)
11081103
if err := opts.Filesystem.MkdirAll(magicTempDir, 0o755); err != nil {
11091104
return nil, fmt.Errorf("create magic temp dir in build context: %w", err)
11101105
}
1111-
envbuilderBinDest := filepath.Join(
1112-
magicTempDir,
1113-
filepath.Base(constants.MagicBinaryLocation),
1114-
)
1106+
envbuilderBinDest := filepath.Join(magicTempDir, "envbuilder")
11151107

11161108
// Copy the envbuilder binary into the build context.
1117-
opts.Logger(log.LevelDebug, "copying envbuilder binary at %q to build context %q", opts.BinaryPath, buildParams.BuildContext)
1109+
opts.Logger(log.LevelDebug, "copying envbuilder binary at %q to build context %q", opts.BinaryPath, envbuilderBinDest)
11181110
if err := copyFile(opts.Filesystem, opts.BinaryPath, envbuilderBinDest, 0o755); err != nil {
11191111
return nil, xerrors.Errorf("copy envbuilder binary to build context: %w", err)
11201112
}
11211113

1122-
// Also touch the magic file that signifies the image has been built!
1123-
magicImageDest := filepath.Join(
1124-
magicTempDir,
1125-
filepath.Base(constants.MagicImage),
1126-
)
1114+
// Also touch the magic file that signifies the image has been built!A
1115+
magicImageDest := filepath.Join(magicTempDir, "image")
1116+
opts.Logger(log.LevelDebug, "touching magic image file at %q in build context %q", magicImageDest, magicTempDir)
11271117
if err := touchFile(opts.Filesystem, magicImageDest, 0o755); err != nil {
1128-
return nil, fmt.Errorf("touch magic image file in build context: %w", err)
1118+
return nil, fmt.Errorf("touch magic image file at %q: %w", magicImageDest, err)
11291119
}
11301120
defer func() {
11311121
// Clean up after we're done!
@@ -1417,21 +1407,24 @@ func findDevcontainerJSON(workspaceFolder string, options options.Options) (stri
14171407
// maybeDeleteFilesystem wraps util.DeleteFilesystem with a guard to hopefully stop
14181408
// folks from unwittingly deleting their entire root directory.
14191409
func maybeDeleteFilesystem(logger log.Func, force bool) error {
1410+
// We always expect the magic directory to be set to the default, signifying that
1411+
// the user is running envbuilder in a container.
1412+
// If this is set to anything else we should bail out to prevent accidental data loss.
1413+
defaultMagicDir := constants.MagicDir("")
14201414
kanikoDir, ok := os.LookupEnv("KANIKO_DIR")
1421-
if !ok || strings.TrimSpace(kanikoDir) != constants.MagicDir {
1422-
if force {
1423-
bailoutSecs := 10
1424-
logger(log.LevelWarn, "WARNING! BYPASSING SAFETY CHECK! THIS WILL DELETE YOUR ROOT FILESYSTEM!")
1425-
logger(log.LevelWarn, "You have %d seconds to bail out!", bailoutSecs)
1426-
for i := bailoutSecs; i > 0; i-- {
1427-
logger(log.LevelWarn, "%d...", i)
1428-
<-time.After(time.Second)
1429-
}
1430-
} else {
1431-
logger(log.LevelError, "KANIKO_DIR is not set to %s. Bailing!\n", constants.MagicDir)
1415+
if !ok || strings.TrimSpace(kanikoDir) != defaultMagicDir.String() {
1416+
if !force {
1417+
logger(log.LevelError, "KANIKO_DIR is not set to %s. Bailing!\n", defaultMagicDir.String())
14321418
logger(log.LevelError, "To bypass this check, set FORCE_SAFE=true.")
14331419
return errors.New("safety check failed")
14341420
}
1421+
bailoutSecs := 10
1422+
logger(log.LevelWarn, "WARNING! BYPASSING SAFETY CHECK! THIS WILL DELETE YOUR ROOT FILESYSTEM!")
1423+
logger(log.LevelWarn, "You have %d seconds to bail out!", bailoutSecs)
1424+
for i := bailoutSecs; i > 0; i-- {
1425+
logger(log.LevelWarn, "%d...", i)
1426+
<-time.After(time.Second)
1427+
}
14351428
}
14361429

14371430
return util.DeleteFilesystem()
@@ -1469,13 +1462,13 @@ func touchFile(fs billy.Filesystem, dst string, mode fs.FileMode) error {
14691462
return f.Close()
14701463
}
14711464

1472-
func initDockerConfigJSON(dockerConfigBase64 string) (func() error, error) {
1465+
func initDockerConfigJSON(logf log.Func, magicDir constants.MagicDir, dockerConfigBase64 string) (func() error, error) {
14731466
var cleanupOnce sync.Once
14741467
noop := func() error { return nil }
14751468
if dockerConfigBase64 == "" {
14761469
return noop, nil
14771470
}
1478-
cfgPath := filepath.Join(constants.MagicDir, "config.json")
1471+
cfgPath := filepath.Join(magicDir.String(), "config.json")
14791472
decoded, err := base64.StdEncoding.DecodeString(dockerConfigBase64)
14801473
if err != nil {
14811474
return noop, fmt.Errorf("decode docker config: %w", err)
@@ -1489,10 +1482,14 @@ func initDockerConfigJSON(dockerConfigBase64 string) (func() error, error) {
14891482
if err != nil {
14901483
return noop, fmt.Errorf("parse docker config: %w", err)
14911484
}
1485+
for k := range configFile.AuthConfigs {
1486+
logf(log.LevelInfo, "Docker config contains auth for registry %q", k)
1487+
}
14921488
err = os.WriteFile(cfgPath, decoded, 0o644)
14931489
if err != nil {
14941490
return noop, fmt.Errorf("write docker config: %w", err)
14951491
}
1492+
logf(log.LevelInfo, "Wrote Docker config JSON to %s", cfgPath)
14961493
cleanup := func() error {
14971494
var cleanupErr error
14981495
cleanupOnce.Do(func() {
@@ -1501,7 +1498,7 @@ func initDockerConfigJSON(dockerConfigBase64 string) (func() error, error) {
15011498
if !errors.Is(err, fs.ErrNotExist) {
15021499
cleanupErr = fmt.Errorf("remove docker config: %w", cleanupErr)
15031500
}
1504-
_, _ = fmt.Fprintf(os.Stderr, "failed to remove the Docker config secret file: %s\n", cleanupErr)
1501+
logf(log.LevelError, "failed to remove the Docker config secret file: %s", cleanupErr)
15051502
}
15061503
})
15071504
return cleanupErr

0 commit comments

Comments
 (0)