diff --git a/internal/provider/resource_ko_build.go b/internal/provider/resource_ko_build.go index 55eb144..12b8022 100644 --- a/internal/provider/resource_ko_build.go +++ b/internal/provider/resource_ko_build.go @@ -355,31 +355,32 @@ func resourceKoBuildCreate(ctx context.Context, d *schema.ResourceData, meta int return nil } +const zeroRef = "example.com/zero@sha256:0000000000000000000000000000000000000000000000000000000000000000" + func resourceKoBuildRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { po, err := NewProviderOpts(meta) if err != nil { return diag.Errorf("configuring provider: %v", err) } + var diags diag.Diagnostics _, ref, err := doBuild(ctx, fromData(d, po)) if err != nil { - // Check for conditions that might indicate that the underlying module has been deleted. - // This is not an exhaustive list, but is a best effort check to see if the build failed because a deletion. - // See https://www.hashicorp.com/blog/writing-custom-terraform-providers#implementing-read for more details. - if errors.Is(err, os.ErrNotExist) { - d.SetId("") - return nil - } - return diag.Errorf("[id=%s] read doBuild: %v", d.Id(), err) + ref = zeroRef + diags = append(diags, diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Image build failed to read -- create may fail.", + Detail: fmt.Sprintf("failed to read image: %v", err), + }) } _ = d.Set("image_ref", ref) - if ref != d.Id() { + if ref != d.Id() || ref == zeroRef { d.SetId("") // triggers create on next apply. } else { d.SetId(ref) } - return nil + return diags } func resourceKoBuildDelete(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics { diff --git a/internal/provider/resource_ko_build_test.go b/internal/provider/resource_ko_build_test.go index 281550b..35280e9 100644 --- a/internal/provider/resource_ko_build_test.go +++ b/internal/provider/resource_ko_build_test.go @@ -117,11 +117,11 @@ func TestAccResourceKoBuild(t *testing.T) { ProviderFactories: providerFactories, Steps: []resource.TestStep{{ Config: ` - resource "ko_build" "foo" { - importpath = "github.com/ko-build/terraform-provider-ko/cmd/test-cgo" - env = ["CGO_ENABLED=1"] - } - `, + resource "ko_build" "foo" { + importpath = "github.com/ko-build/terraform-provider-ko/cmd/test-cgo" + env = ["CGO_ENABLED=1"] + } + `, Check: resource.ComposeTestCheckFunc( resource.TestMatchResourceAttr("ko_build.foo", "image_ref", regexp.MustCompile("^"+url+"/github.com/ko-build/terraform-provider-ko/cmd/test-cgo@sha256:")), @@ -172,6 +172,27 @@ func TestAccResourceKoBuild(t *testing.T) { }}, }) }) + + t.Run("build fails during plan", func(t *testing.T) { + res := `resource "ko_build" "foo" { importpath = "github.com/ko-build/terraform-provider-ko/cmd/not-found" }` + + resource.Test(t, resource.TestCase{ + ProviderFactories: providerFactories, + Steps: []resource.TestStep{{ + // A failed build during plan should still show a diff, which will fail at create time. + // This enables cases where an importpath changes, where the previous state is now invalid + // and the ko build will fail; this should not block the create though, which should + // succeed and update the state. + PlanOnly: true, + ExpectNonEmptyPlan: true, + Config: res, + }, { + // The same failed build during create should fail with an error. + Config: res, + ExpectError: regexp.MustCompile(".*create doBuild.*no required module provides package.*"), + }}, + }) + }) } func TestAccResourceKoBuild_ImageRepo(t *testing.T) {