diff --git a/go.mod b/go.mod index 66e2fb6..ddfc2ad 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.20240807151028-6e5bfa5faa29 + github.com/coder/envbuilder v1.0.0-rc.5.0.20240815111948-e6283db826e8 github.com/docker/docker v26.1.4+incompatible github.com/gliderlabs/ssh v0.3.7 github.com/go-git/go-billy/v5 v5.5.0 @@ -248,7 +248,7 @@ require ( github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/skeema/knownhosts v1.2.2 // indirect + github.com/skeema/knownhosts v1.3.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect diff --git a/go.sum b/go.sum index 4215a54..76dc58f 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.20240807151028-6e5bfa5faa29 h1:PhJBofIrh6NGuTQ93nW7/KWcYX6Ju0PGoE/BbNvf87o= -github.com/coder/envbuilder v1.0.0-rc.0.0.20240807151028-6e5bfa5faa29/go.mod h1:lm33s3+chqnl7lB4avNFfDH5gXDYSunYD7/y4bJ/LMA= +github.com/coder/envbuilder v1.0.0-rc.5.0.20240815111948-e6283db826e8 h1:tfmaVV7qFpODoBliTcDQyrB09FIjCQRUtjefr7zFEXY= +github.com/coder/envbuilder v1.0.0-rc.5.0.20240815111948-e6283db826e8/go.mod h1:HFqLE6BNJhR/fLknKWon5Eqhsr5FmuEJO1OJ/RKF2BA= github.com/coder/kaniko v0.0.0-20240807142221-ffc5e60fca41 h1:1Ye7AcLnuT5IDv6il7Fxo+aqpzlWfedkpraCCwx8Lyo= github.com/coder/kaniko v0.0.0-20240807142221-ffc5e60fca41/go.mod h1:YMK7BlxerzLlMwihGxNWUaFoN9LXCij4P+w/8/fNlcM= github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 h1:3A0ES21Ke+FxEM8CXx9n47SZOKOpgSE1bbJzlE4qPVs= @@ -718,8 +718,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= -github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= +github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= diff --git a/internal/provider/cached_image_resource_test.go b/internal/provider/cached_image_resource_test.go index 4653f8b..5eeddfa 100644 --- a/internal/provider/cached_image_resource_test.go +++ b/internal/provider/cached_image_resource_test.go @@ -10,106 +10,129 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) +// testEnvValue is a multi-line environment variable value that we use in +// tests to ensure that we can handle multi-line values correctly. +var testEnvValue = `bar +baz` + func TestAccCachedImageResource(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) defer cancel() - files := map[string]string{ - ".devcontainer/devcontainer.json": `{"build": { "dockerfile": "Dockerfile" }}`, - ".devcontainer/Dockerfile": `FROM localhost:5000/test-ubuntu:latest - RUN date > /date.txt`, - } - deps := setup(ctx, t, files) - deps.ExtraEnv["FOO"] = `bar -baz` // THIS IS A LOAD-BEARING NEWLINE. DO NOT REMOVE. - resource.Test(t, resource.TestCase{ - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - // Initial state: cache has not been seeded. - { - Config: deps.Config(t), - PlanOnly: true, - ExpectNonEmptyPlan: true, - }, - // Should detect that no cached image is present and plan to create the resource. - { - Config: deps.Config(t), - Check: resource.ComposeAggregateTestCheckFunc( - // Computed values MUST be present. - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "id", uuid.Nil.String()), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "exists", "false"), - resource.TestCheckResourceAttrSet("envbuilder_cached_image.test", "env.0"), - // Cached image should be set to the builder image. - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "image", deps.BuilderImage), - // Inputs should still be present. - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "cache_repo", deps.CacheRepo), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "extra_env.FOO", "bar\nbaz"), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "git_url", deps.Repo.URL), - // Should be empty - resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_username"), - resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_password"), - resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "cache_ttl_days"), - ), - ExpectNonEmptyPlan: true, // TODO: check the plan. - }, - // Re-running plan should have the same effect. - { - Config: deps.Config(t), - Check: resource.ComposeAggregateTestCheckFunc( - // Computed values MUST be present. - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "id", uuid.Nil.String()), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "exists", "false"), - resource.TestCheckResourceAttrSet("envbuilder_cached_image.test", "env.0"), - // Cached image should be set to the builder image. - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "image", deps.BuilderImage), - // Inputs should still be present. - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "cache_repo", deps.CacheRepo), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "extra_env.FOO", "bar\nbaz"), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "git_url", deps.Repo.URL), - // Should be empty - resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_username"), - resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_password"), - resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "cache_ttl_days"), - ), - ExpectNonEmptyPlan: true, // TODO: check the plan. - }, - // Now, seed the cache and re-run. We should now successfully create the cached image resource. - { - PreConfig: func() { - seedCache(ctx, t, deps) - }, - Config: deps.Config(t), - Check: resource.ComposeAggregateTestCheckFunc( - // Inputs should still be present. - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "cache_repo", deps.CacheRepo), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "extra_env.FOO", "bar\nbaz"), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "git_url", deps.Repo.URL), - // Should be empty - resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_username"), - resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_password"), - resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "cache_ttl_days"), - // Computed - resource.TestCheckResourceAttrWith("envbuilder_cached_image.test", "id", quotedPrefix("sha256:")), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "exists", "true"), - resource.TestCheckResourceAttrSet("envbuilder_cached_image.test", "image"), - resource.TestCheckResourceAttrWith("envbuilder_cached_image.test", "image", quotedPrefix(deps.CacheRepo)), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "env.0", "FOO=bar\nbaz"), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "env.1", fmt.Sprintf("ENVBUILDER_CACHE_REPO=%s", deps.CacheRepo)), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "env.2", fmt.Sprintf("ENVBUILDER_GIT_URL=%s", deps.Repo.URL)), - resource.TestCheckResourceAttr("envbuilder_cached_image.test", "env.3", "ENVBUILDER_REMOTE_REPO_BUILD_MODE=true"), - resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "env.4"), - ), + for _, tc := range []struct { + name string + files map[string]string + }{ + { + name: "devcontainer only", + files: map[string]string{ + ".devcontainer/devcontainer.json": `{"image": "localhost:5000/test-ubuntu:latest"}`, }, - // Should produce an empty plan after apply - { - Config: deps.Config(t), - PlanOnly: true, - }, - // Ensure idempotence in this state! - { - Config: deps.Config(t), - PlanOnly: true, + }, + { + name: "devcontainer and Dockerfile", + files: map[string]string{ + ".devcontainer/devcontainer.json": `{"build": { "dockerfile": "Dockerfile" }}`, + ".devcontainer/Dockerfile": `FROM localhost:5000/test-ubuntu:latest +RUN date > /date.txt`, }, }, - }) + } { + t.Run(tc.name, func(t *testing.T) { + //nolint: paralleltest + deps := setup(ctx, t, tc.files) + deps.ExtraEnv["FOO"] = testEnvValue + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Initial state: cache has not been seeded. + { + Config: deps.Config(t), + PlanOnly: true, + ExpectNonEmptyPlan: true, + }, + // Should detect that no cached image is present and plan to create the resource. + { + Config: deps.Config(t), + Check: resource.ComposeAggregateTestCheckFunc( + // Computed values MUST be present. + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "id", uuid.Nil.String()), + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "exists", "false"), + resource.TestCheckResourceAttrSet("envbuilder_cached_image.test", "env.0"), + // Cached image should be set to the builder image. + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "image", deps.BuilderImage), + // Inputs should still be present. + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "cache_repo", deps.CacheRepo), + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "extra_env.FOO", "bar\nbaz"), + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "git_url", deps.Repo.URL), + // Should be empty + resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_username"), + resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_password"), + resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "cache_ttl_days"), + ), + ExpectNonEmptyPlan: true, // TODO: check the plan. + }, + // Re-running plan should have the same effect. + { + Config: deps.Config(t), + Check: resource.ComposeAggregateTestCheckFunc( + // Computed values MUST be present. + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "id", uuid.Nil.String()), + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "exists", "false"), + resource.TestCheckResourceAttrSet("envbuilder_cached_image.test", "env.0"), + // Cached image should be set to the builder image. + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "image", deps.BuilderImage), + // Inputs should still be present. + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "cache_repo", deps.CacheRepo), + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "extra_env.FOO", "bar\nbaz"), + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "git_url", deps.Repo.URL), + // Should be empty + resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_username"), + resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_password"), + resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "cache_ttl_days"), + ), + ExpectNonEmptyPlan: true, // TODO: check the plan. + }, + // Now, seed the cache and re-run. We should now successfully create the cached image resource. + { + PreConfig: func() { + seedCache(ctx, t, deps) + }, + Config: deps.Config(t), + Check: resource.ComposeAggregateTestCheckFunc( + // Inputs should still be present. + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "cache_repo", deps.CacheRepo), + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "extra_env.FOO", "bar\nbaz"), + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "git_url", deps.Repo.URL), + // Should be empty + resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_username"), + resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "git_password"), + resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "cache_ttl_days"), + // Computed + resource.TestCheckResourceAttrWith("envbuilder_cached_image.test", "id", quotedPrefix("sha256:")), + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "exists", "true"), + resource.TestCheckResourceAttrSet("envbuilder_cached_image.test", "image"), + resource.TestCheckResourceAttrWith("envbuilder_cached_image.test", "image", quotedPrefix(deps.CacheRepo)), + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "env.0", "FOO=bar\nbaz"), + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "env.1", fmt.Sprintf("ENVBUILDER_CACHE_REPO=%s", deps.CacheRepo)), + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "env.2", fmt.Sprintf("ENVBUILDER_GIT_URL=%s", deps.Repo.URL)), + resource.TestCheckResourceAttr("envbuilder_cached_image.test", "env.3", "ENVBUILDER_REMOTE_REPO_BUILD_MODE=true"), + resource.TestCheckNoResourceAttr("envbuilder_cached_image.test", "env.4"), + ), + }, + // Should produce an empty plan after apply + { + Config: deps.Config(t), + PlanOnly: true, + }, + // Ensure idempotence in this state! + { + Config: deps.Config(t), + PlanOnly: true, + }, + }, + }) + }) + } }