Skip to content

Commit b18cd0e

Browse files
authored
fix: do not error if failed to push image (#390)
Relates to #385 This commit adds an option ENVBUILDER_EXIT_ON_PUSH_FAILURE that controls the previous behaviour of exiting with an error if ENVBUILDER_PUSH_IMAGE was set to a truthy value and we failed to push the image. - Before, Envbuilder would always exit with an error on a failed push. - Now, Envbuilder will only exit with an error if ENVBUILDER_EXIT_ON_PUSH_FAILURE is set to a truthy value. - Otherwise, Envbuilder will continue building the image and executing ENVBUILDER_INIT_SCRIPT even if push fails.
1 parent c16ae9f commit b18cd0e

File tree

5 files changed

+71
-6
lines changed

5 files changed

+71
-6
lines changed

docs/env-variables.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
| `--docker-config-base64` | `ENVBUILDER_DOCKER_CONFIG_BASE64` | | The base64 encoded Docker config file that will be used to pull images from private container registries. When this is set, Docker configuration set via the DOCKER_CONFIG environment variable is ignored. |
1919
| `--fallback-image` | `ENVBUILDER_FALLBACK_IMAGE` | | Specifies an alternative image to use when neither an image is declared in the devcontainer.json file nor a Dockerfile is present. If there's a build failure (from a faulty Dockerfile) or a misconfiguration, this image will be the substitute. Set ExitOnBuildFailure to true to halt the container if the build faces an issue. |
2020
| `--exit-on-build-failure` | `ENVBUILDER_EXIT_ON_BUILD_FAILURE` | | Terminates the container upon a build failure. This is handy when preferring the FALLBACK_IMAGE in cases where no devcontainer.json or image is provided. However, it ensures that the container stops if the build process encounters an error. |
21+
| `--exit-on-push-failure` | `ENVBUILDER_EXIT_ON_PUSH_FAILURE` | | ExitOnPushFailure terminates the container upon a push failure. This is useful if failure to push the built image should abort execution and result in an error. |
2122
| `--force-safe` | `ENVBUILDER_FORCE_SAFE` | | Ignores any filesystem safety checks. This could cause serious harm to your system! This is used in cases where bypass is needed to unblock customers. |
2223
| `--insecure` | `ENVBUILDER_INSECURE` | | Bypass TLS verification when cloning and pulling from container registries. |
2324
| `--ignore-paths` | `ENVBUILDER_IGNORE_PATHS` | | The comma separated list of paths to ignore when building the workspace. |

envbuilder.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -583,10 +583,13 @@ func run(ctx context.Context, opts options.Options, execArgs *execArgsInfo) erro
583583
endStage("🏗️ Built image!")
584584
if opts.PushImage {
585585
endStage = startStage("🏗️ Pushing image...")
586-
if err := executor.DoPush(image, kOpts); err != nil {
586+
if err := executor.DoPush(image, kOpts); err == nil {
587+
endStage("🏗️ Pushed image!")
588+
} else if !opts.ExitOnPushFailure {
589+
endStage("⚠️️ Failed to push image!")
590+
} else {
587591
return nil, xerrors.Errorf("do push: %w", err)
588592
}
589-
endStage("🏗️ Pushed image!")
590593
}
591594

592595
return image, err

integration/integration_test.go

+48-4
Original file line numberDiff line numberDiff line change
@@ -1949,9 +1949,10 @@ RUN date --utc > /root/date.txt`, testImageAlpine),
19491949
_, err = remote.Image(ref, remoteAuthOpt)
19501950
require.ErrorContains(t, err, "NAME_UNKNOWN", "expected image to not be present before build + push")
19511951

1952-
// When: we run envbuilder with PUSH_IMAGE set
1952+
// When: we run envbuilder with PUSH_IMAGE and EXIT_ON_PUSH_FAILURE set
19531953
_, err = runEnvbuilder(t, runOpts{env: append(opts,
19541954
envbuilderEnv("PUSH_IMAGE", "1"),
1955+
envbuilderEnv("EXIT_ON_PUSH_FAILURE", "1"),
19551956
)})
19561957
// Then: it should fail with an Unauthorized error
19571958
require.ErrorContains(t, err, "401 Unauthorized", "expected unauthorized error using no auth when cache repo requires it")
@@ -2144,7 +2145,7 @@ RUN date --utc > /root/date.txt`, testImageAlpine),
21442145
require.ErrorContains(t, err, "--cache-repo must be set when using --push-image")
21452146
})
21462147

2147-
t.Run("PushErr", func(t *testing.T) {
2148+
t.Run("PushErr/ExitOnPushFail", func(t *testing.T) {
21482149
t.Parallel()
21492150

21502151
srv := gittest.CreateGitServer(t, gittest.Options{
@@ -2174,12 +2175,50 @@ RUN date --utc > /root/date.txt`, testImageAlpine),
21742175
envbuilderEnv("GIT_URL", srv.URL),
21752176
envbuilderEnv("CACHE_REPO", notRegURL),
21762177
envbuilderEnv("PUSH_IMAGE", "1"),
2178+
envbuilderEnv("EXIT_ON_PUSH_FAILURE", "1"),
21772179
}})
21782180

21792181
// Then: envbuilder should fail with a descriptive error
21802182
require.ErrorContains(t, err, "failed to push to destination")
21812183
})
21822184

2185+
t.Run("PushErr/NoExitOnPushFail", func(t *testing.T) {
2186+
t.Parallel()
2187+
2188+
srv := gittest.CreateGitServer(t, gittest.Options{
2189+
Files: map[string]string{
2190+
".devcontainer/Dockerfile": fmt.Sprintf(`FROM %s
2191+
USER root
2192+
ARG WORKDIR=/
2193+
WORKDIR $WORKDIR
2194+
ENV FOO=bar
2195+
RUN echo $FOO > /root/foo.txt
2196+
RUN date --utc > /root/date.txt`, testImageAlpine),
2197+
".devcontainer/devcontainer.json": `{
2198+
"name": "Test",
2199+
"build": {
2200+
"dockerfile": "Dockerfile"
2201+
},
2202+
}`,
2203+
},
2204+
})
2205+
2206+
// Given: registry is not set up (in this case, not a registry)
2207+
notRegSrv := httptest.NewServer(http.NotFoundHandler())
2208+
notRegURL := strings.TrimPrefix(notRegSrv.URL, "http://") + "/test"
2209+
2210+
// When: we run envbuilder with PUSH_IMAGE set
2211+
_, err := runEnvbuilder(t, runOpts{env: []string{
2212+
envbuilderEnv("GIT_URL", srv.URL),
2213+
envbuilderEnv("CACHE_REPO", notRegURL),
2214+
envbuilderEnv("PUSH_IMAGE", "1"),
2215+
envbuilderEnv("EXIT_ON_PUSH_FAILURE", "0"),
2216+
}})
2217+
2218+
// Then: envbuilder should not fail
2219+
require.NoError(t, err)
2220+
})
2221+
21832222
t.Run("CacheAndPushDevcontainerFeatures", func(t *testing.T) {
21842223
t.Parallel()
21852224

@@ -2421,8 +2460,13 @@ func pushImage(t *testing.T, ref name.Reference, remoteOpt remote.Option, env ..
24212460
if remoteOpt != nil {
24222461
remoteOpts = append(remoteOpts, remoteOpt)
24232462
}
2424-
2425-
_, err := runEnvbuilder(t, runOpts{env: append(env, envbuilderEnv("PUSH_IMAGE", "1"))})
2463+
opts := runOpts{
2464+
env: append(env,
2465+
envbuilderEnv("PUSH_IMAGE", "1"),
2466+
envbuilderEnv("EXIT_ON_PUSH_FAILURE", "1"),
2467+
),
2468+
}
2469+
_, err := runEnvbuilder(t, opts)
24262470
require.NoError(t, err, "envbuilder push image failed")
24272471

24282472
img, err := remote.Image(ref, remoteOpts...)

options/options.go

+12
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ type Options struct {
7878
// devcontainer.json or image is provided. However, it ensures that the
7979
// container stops if the build process encounters an error.
8080
ExitOnBuildFailure bool
81+
// ExitOnPushFailure terminates the container upon a push failure. This is
82+
// useful if failure to push the built image should abort execution
83+
// and result in an error.
84+
ExitOnPushFailure bool
8185
// ForceSafe ignores any filesystem safety checks. This could cause serious
8286
// harm to your system! This is used in cases where bypass is needed to
8387
// unblock customers.
@@ -304,6 +308,14 @@ func (o *Options) CLI() serpent.OptionSet {
304308
"no devcontainer.json or image is provided. However, it ensures " +
305309
"that the container stops if the build process encounters an error.",
306310
},
311+
{
312+
Flag: "exit-on-push-failure",
313+
Env: WithEnvPrefix("EXIT_ON_PUSH_FAILURE"),
314+
Value: serpent.BoolOf(&o.ExitOnPushFailure),
315+
Description: "ExitOnPushFailure terminates the container upon a push failure. " +
316+
"This is useful if failure to push the built image should abort execution " +
317+
"and result in an error.",
318+
},
307319
{
308320
Flag: "force-safe",
309321
Env: WithEnvPrefix("FORCE_SAFE"),

options/testdata/options.golden

+5
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ OPTIONS:
6666
image is provided. However, it ensures that the container stops if the
6767
build process encounters an error.
6868

69+
--exit-on-push-failure bool, $ENVBUILDER_EXIT_ON_PUSH_FAILURE
70+
ExitOnPushFailure terminates the container upon a push failure. This
71+
is useful if failure to push the built image should abort execution
72+
and result in an error.
73+
6974
--export-env-file string, $ENVBUILDER_EXPORT_ENV_FILE
7075
Optional file path to a .env file where envbuilder will dump
7176
environment variables from devcontainer.json and the built container

0 commit comments

Comments
 (0)