diff --git a/go.mod b/go.mod index d05a28a..6d869ee 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ replace tailscale.com => github.com/coder/tailscale v1.1.1-0.20240702054557-aa55 require ( github.com/GoogleContainerTools/kaniko v1.9.2 - github.com/coder/envbuilder v1.0.0-rc.0.0.20240830145058-fb7e689f39ed + github.com/coder/envbuilder v1.0.0-rc.0.0.20240910082823-b7781d802f88 github.com/coder/serpent v0.7.0 github.com/docker/docker v26.1.5+incompatible github.com/gliderlabs/ssh v0.3.7 @@ -295,7 +295,7 @@ require ( go4.org/netipx v0.0.0-20230728180743-ad4cb58a6516 // indirect golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect - golang.org/x/mod v0.19.0 // indirect + golang.org/x/mod v0.21.0 // indirect golang.org/x/net v0.26.0 // indirect golang.org/x/oauth2 v0.20.0 // indirect golang.org/x/sync v0.8.0 // indirect diff --git a/go.sum b/go.sum index 3678f90..744e4f0 100644 --- a/go.sum +++ b/go.sum @@ -186,8 +186,8 @@ github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoC github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/coder/coder/v2 v2.10.1-0.20240704130443-c2d44d16a352 h1:L/EjCuZxs5tOcqqCaASj/nu65TRYEFcTt8qRQfHZXX0= github.com/coder/coder/v2 v2.10.1-0.20240704130443-c2d44d16a352/go.mod h1:P1KoQSgnKEAG6Mnd3YlGzAophty+yKA9VV48LpfNRvo= -github.com/coder/envbuilder v1.0.0-rc.0.0.20240830145058-fb7e689f39ed h1:sDEjs9qB2uJ7O85vGmzMja99IZuLvesxElOUFyy22UY= -github.com/coder/envbuilder v1.0.0-rc.0.0.20240830145058-fb7e689f39ed/go.mod h1:LWImvtIWaX3eiAI3zyU46WE/PrE099nCut1zJultSk0= +github.com/coder/envbuilder v1.0.0-rc.0.0.20240910082823-b7781d802f88 h1:eXOILD2tWepnV1r7XZalBX0yC4NJMnpf6OP1nF8O2Ak= +github.com/coder/envbuilder v1.0.0-rc.0.0.20240910082823-b7781d802f88/go.mod h1:krXpDmUsORgNNdvBe6tnwWCGGDLhabom1UUqAZq9+v0= github.com/coder/kaniko v0.0.0-20240830141327-f307586e3dca h1:PrcSWrllqipTrtet50a3VyAJEQmjziIZyhpy0bsC6o0= github.com/coder/kaniko v0.0.0-20240830141327-f307586e3dca/go.mod h1:XoTDIhNF0Ll4tLmRYdOn31udU9w5zFrY2PME/crSRCA= github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 h1:3A0ES21Ke+FxEM8CXx9n47SZOKOpgSE1bbJzlE4qPVs= @@ -895,8 +895,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/internal/imgutil/imgutil.go b/internal/imgutil/imgutil.go index 5c2d96a..5c04441 100644 --- a/internal/imgutil/imgutil.go +++ b/internal/imgutil/imgutil.go @@ -7,8 +7,9 @@ import ( "io" "os" "path/filepath" + "strings" - "github.com/coder/envbuilder/constants" + eboptions "github.com/coder/envbuilder/options" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" @@ -34,7 +35,9 @@ func GetRemoteImage(imgRef string) (v1.Image, error) { // ExtractEnvbuilderFromImage reads the image located at imgRef and extracts // MagicBinaryLocation to destPath. func ExtractEnvbuilderFromImage(ctx context.Context, imgRef, destPath string) error { - needle := filepath.Clean(constants.MagicBinaryLocation)[1:] // skip leading '/' + var o eboptions.Options + o.SetDefaults() + needle := strings.TrimPrefix(o.BinaryPath, "/") img, err := GetRemoteImage(imgRef) if err != nil { return fmt.Errorf("check remote image: %w", err) diff --git a/internal/provider/cached_image_resource.go b/internal/provider/cached_image_resource.go index 27adc98..9378094 100644 --- a/internal/provider/cached_image_resource.go +++ b/internal/provider/cached_image_resource.go @@ -10,7 +10,6 @@ import ( kconfig "github.com/GoogleContainerTools/kaniko/pkg/config" "github.com/coder/envbuilder" - "github.com/coder/envbuilder/constants" eboptions "github.com/coder/envbuilder/options" "github.com/coder/terraform-provider-envbuilder/internal/imgutil" "github.com/coder/terraform-provider-envbuilder/internal/tfutil" @@ -455,7 +454,7 @@ func runCacheProbe(ctx context.Context, builderImage string, opts eboptions.Opti }() oldKanikoDir := kconfig.KanikoDir - tmpKanikoDir := filepath.Join(tmpDir, constants.MagicDir) + tmpKanikoDir := filepath.Join(tmpDir, ".envbuilder") // Normally you would set the KANIKO_DIR environment variable, but we are importing kaniko directly. kconfig.KanikoDir = tmpKanikoDir tflog.Info(ctx, "set kaniko dir to "+tmpKanikoDir) @@ -467,6 +466,8 @@ func runCacheProbe(ctx context.Context, builderImage string, opts eboptions.Opti if err := os.MkdirAll(tmpKanikoDir, 0o755); err != nil { return nil, fmt.Errorf("failed to create kaniko dir: %w", err) } + // Use the temporary directory as our 'magic dir'. + opts.MagicDirBase = tmpKanikoDir // In order to correctly reproduce the final layer of the cached image, we // need the envbuilder binary used to originally build the image! @@ -496,13 +497,6 @@ func runCacheProbe(ctx context.Context, builderImage string, opts eboptions.Opti tflog.Debug(ctx, "workspace_folder not specified, using temp dir", map[string]any{"workspace_folder": opts.WorkspaceFolder}) } - // We need a place to clone the repo. - repoDir := filepath.Join(tmpDir, "repo") - if err := os.MkdirAll(repoDir, 0o755); err != nil { - return nil, fmt.Errorf("failed to create repo dir: %w", err) - } - opts.RemoteRepoDir = repoDir - // The below options are not relevant and are set to their zero value // explicitly. // They must be set by extra_env to be used in the final builder image. diff --git a/internal/provider/cached_image_resource_test.go b/internal/provider/cached_image_resource_test.go index 00b50ff..6b6c832 100644 --- a/internal/provider/cached_image_resource_test.go +++ b/internal/provider/cached_image_resource_test.go @@ -46,6 +46,7 @@ func TestAccCachedImageResource(t *testing.T) { "CODER_AGENT_TOKEN", "some-token", "CODER_AGENT_URL", "https://coder.example.com", "ENVBUILDER_CACHE_REPO", deps.CacheRepo, + "ENVBUILDER_DOCKER_CONFIG_BASE64", deps.DockerConfigBase64, "ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH", deps.Repo.Key, "ENVBUILDER_GIT_URL", deps.Repo.URL, "ENVBUILDER_REMOTE_REPO_BUILD_MODE", "true", @@ -78,6 +79,7 @@ RUN date > /date.txt`, "CODER_AGENT_TOKEN", "some-token", "CODER_AGENT_URL", "https://coder.example.com", "ENVBUILDER_CACHE_REPO", deps.CacheRepo, + "ENVBUILDER_DOCKER_CONFIG_BASE64", deps.DockerConfigBase64, "ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH", deps.Repo.Key, "ENVBUILDER_GIT_URL", deps.Repo.URL, "ENVBUILDER_REMOTE_REPO_BUILD_MODE", "true", @@ -88,9 +90,8 @@ RUN date > /date.txt`, }, }, { - // This test case ensures that parameters passed via extra_env are - // handled correctly. - name: "extra_env", + // This test case ensures that overriding the devcontainer directory works. + name: "different_dir", files: map[string]string{ "path/to/.devcontainer/devcontainer.json": `{"build": { "dockerfile": "Dockerfile" }}`, "path/to/.devcontainer/Dockerfile": `FROM localhost:5000/test-ubuntu:latest @@ -115,6 +116,7 @@ RUN date > /date.txt`, "ENVBUILDER_DEVCONTAINER_DIR", "path/to/.devcontainer", "ENVBUILDER_DEVCONTAINER_JSON_PATH", "path/to/.devcontainer/devcontainer.json", "ENVBUILDER_DOCKERFILE_PATH", "path/to/.devcontainer/Dockerfile", + "ENVBUILDER_DOCKER_CONFIG_BASE64", deps.DockerConfigBase64, "ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH", deps.Repo.Key, "ENVBUILDER_GIT_URL", deps.Repo.URL, "ENVBUILDER_REMOTE_REPO_BUILD_MODE", "true", @@ -149,6 +151,7 @@ RUN date > /date.txt`, "CODER_AGENT_URL", "https://coder.example.com", "ENVBUILDER_CACHE_REPO", deps.CacheRepo, "ENVBUILDER_DOCKERFILE_PATH", "Dockerfile", + "ENVBUILDER_DOCKER_CONFIG_BASE64", deps.DockerConfigBase64, "ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH", deps.Repo.Key, "ENVBUILDER_GIT_URL", deps.Repo.URL, "ENVBUILDER_REMOTE_REPO_BUILD_MODE", "true", diff --git a/internal/provider/provider_internal_test.go b/internal/provider/provider_internal_test.go index a9be0ae..aedfca2 100644 --- a/internal/provider/provider_internal_test.go +++ b/internal/provider/provider_internal_test.go @@ -272,7 +272,6 @@ func Test_computeEnvFromOptions(t *testing.T) { PostStartScriptPath: "string", PushImage: true, RemoteRepoBuildMode: true, - RemoteRepoDir: "string", SetupScript: "string", SkipRebuild: true, SSLCertBase64: "string", @@ -314,7 +313,6 @@ func Test_computeEnvFromOptions(t *testing.T) { "ENVBUILDER_POST_START_SCRIPT_PATH": "string", "ENVBUILDER_PUSH_IMAGE": "true", "ENVBUILDER_REMOTE_REPO_BUILD_MODE": "true", - "ENVBUILDER_REMOTE_REPO_DIR": "string", "ENVBUILDER_SETUP_SCRIPT": "string", "ENVBUILDER_SKIP_REBUILD": "true", "ENVBUILDER_SSL_CERT_BASE64": "string", diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index 26dc7d4..ef58b73 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -3,6 +3,7 @@ package provider import ( "bufio" "context" + "encoding/base64" "fmt" "io" "os" @@ -35,10 +36,11 @@ var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServe // testDependencies contain information about stuff the test depends on. type testDependencies struct { - BuilderImage string - CacheRepo string - ExtraEnv map[string]string - Repo testGitRepoSSH + BuilderImage string + CacheRepo string + DockerConfigBase64 string + ExtraEnv map[string]string + Repo testGitRepoSSH } // Config generates a valid Terraform config file from the dependencies. @@ -47,8 +49,9 @@ func (d *testDependencies) Config(t testing.TB) string { tpl := `provider envbuilder {} resource "envbuilder_cached_image" "test" { - builder_image = {{ quote .BuilderImage }} + builder_image = {{ quote .BuilderImage }} cache_repo = {{ quote .CacheRepo }} + docker_config_base64 = {{ quote .DockerConfigBase64 }} git_url = {{ quote .Repo.URL }} extra_env = { "ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH": {{ quote .Repo.Key }} @@ -78,19 +81,29 @@ func setup(ctx context.Context, t testing.TB, extraEnv, files map[string]string) envbuilderVersion := getEnvOrDefault("ENVBUILDER_VERSION", "latest") envbuilderImageRef := envbuilderImage + ":" + envbuilderVersion - // TODO: envbuilder creates /.envbuilder/bin/envbuilder owned by root:root which we are unable to clean up. - // This causes tests to fail. + testUsername := "testuser" + testPassword := "testpassword" + testAuthBase64 := base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", testUsername, testPassword))) regDir := t.TempDir() - reg := registrytest.New(t, regDir) + reg := registrytest.New(t, regDir, registrytest.BasicAuthMW(t, testUsername, testPassword)) repoDir := setupGitRepo(t, files) gitRepo := serveGitRepoSSH(ctx, t, repoDir) + dockerConfigJSON := fmt.Sprintf(`{ + "auths": { + "%s": { + "auth": "%s", + } + } + }`, reg, testAuthBase64) + dockerConfigJSONBase64 := base64.StdEncoding.EncodeToString([]byte(dockerConfigJSON)) return testDependencies{ - BuilderImage: envbuilderImageRef, - CacheRepo: reg + "/test", - ExtraEnv: extraEnv, - Repo: gitRepo, + BuilderImage: envbuilderImageRef, + CacheRepo: reg + "/test", + ExtraEnv: extraEnv, + Repo: gitRepo, + DockerConfigBase64: dockerConfigJSONBase64, } } @@ -115,6 +128,7 @@ func seedCache(ctx context.Context, t testing.TB, deps testDependencies) { "ENVBUILDER_VERBOSE": "true", "ENVBUILDER_GIT_URL": deps.Repo.URL, "ENVBUILDER_GIT_SSH_PRIVATE_KEY_PATH": "/id_ed25519", + "ENVBUILDER_DOCKER_CONFIG_BASE64": deps.DockerConfigBase64, } for k, v := range deps.ExtraEnv { diff --git a/testutil/registrytest/registrytest.go b/testutil/registrytest/registrytest.go index e18043a..be8d40d 100644 --- a/testutil/registrytest/registrytest.go +++ b/testutil/registrytest/registrytest.go @@ -2,6 +2,7 @@ package registrytest import ( "fmt" + "net/http" "net/http/httptest" "net/url" "testing" @@ -13,12 +14,31 @@ import ( // New starts a new Docker registry listening on localhost. // It will automatically shut down when the test finishes. // It will store data in dir. -func New(t testing.TB, dir string) string { +func New(t testing.TB, dir string, mws ...func(http.Handler) http.Handler) string { t.Helper() regHandler := registry.New(registry.WithBlobHandler(registry.NewDiskBlobHandler(dir))) + for _, mw := range mws { + regHandler = mw(regHandler) + } regSrv := httptest.NewServer(regHandler) t.Cleanup(func() { regSrv.Close() }) regSrvURL, err := url.Parse(regSrv.URL) require.NoError(t, err) return fmt.Sprintf("localhost:%s", regSrvURL.Port()) } + +func BasicAuthMW(t testing.TB, username, password string) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if username != "" || password != "" { + authUser, authPass, ok := r.BasicAuth() + if !ok || username != authUser || password != authPass { + t.Logf("basic auth failed: got user %q, pass %q", authUser, authPass) + w.WriteHeader(http.StatusUnauthorized) + return + } + } + next.ServeHTTP(w, r) + }) + } +}