diff --git a/cmd/wfctl/infra_apply.go b/cmd/wfctl/infra_apply.go index 460339e9..b0ea009d 100644 --- a/cmd/wfctl/infra_apply.go +++ b/cmd/wfctl/infra_apply.go @@ -843,7 +843,16 @@ func isIaCNotFound(err error) bool { return true } var platformNotFound *platform.ResourceNotFoundError - return errors.As(err, &platformNotFound) + if errors.As(err, &platformNotFound) { + return true + } + // gRPC fallback: typed adapter loses sentinel identity across the + // wire. The message survives as a wrapped string. Match on the + // literal ErrResourceNotFound.Error() value so adoption can still + // detect "not present yet, fall back to create" against remote + // plugin drivers (workflow-plugin-digitalocean v2+ database + // adoption is the original repro path). + return strings.Contains(err.Error(), interfaces.ErrResourceNotFound.Error()) } func resourceStateFromLiveOutput(spec interfaces.ResourceSpec, providerType string, live *interfaces.ResourceOutput) (interfaces.ResourceState, error) { diff --git a/cmd/wfctl/infra_apply_isiacnotfound_test.go b/cmd/wfctl/infra_apply_isiacnotfound_test.go new file mode 100644 index 00000000..ccbebbef --- /dev/null +++ b/cmd/wfctl/infra_apply_isiacnotfound_test.go @@ -0,0 +1,41 @@ +package main + +import ( + "errors" + "fmt" + "testing" + + "github.com/GoCodeAlone/workflow/interfaces" +) + +// TestIsIaCNotFound_TypedSentinel confirms native wrapping still works. +func TestIsIaCNotFound_TypedSentinel(t *testing.T) { + err := fmt.Errorf("database %q: %w", "multisite-pg", interfaces.ErrResourceNotFound) + if !isIaCNotFound(err) { + t.Error("expected typed sentinel to be detected") + } +} + +// TestIsIaCNotFound_GRPCStringFallback is the regression test for the +// gocodealone-multisite deploy: the typed gRPC adapter strips sentinel +// identity, leaving only the wrapped error string. Adoption is meant +// to be "look up, fall back to create" — without this fallback the +// remote plugin's not-found returns made apply-prereq fail every run. +func TestIsIaCNotFound_GRPCStringFallback(t *testing.T) { + grpcErr := errors.New(`rpc error: code = Unknown desc = database "multisite-pg": iac: resource not found`) + if !isIaCNotFound(grpcErr) { + t.Error("expected gRPC-flattened not-found to be detected via string match") + } +} + +func TestIsIaCNotFound_NilSafe(t *testing.T) { + if isIaCNotFound(nil) { + t.Error("nil err must not be reported as not-found") + } +} + +func TestIsIaCNotFound_OtherErrorsIgnored(t *testing.T) { + if isIaCNotFound(errors.New("permission denied")) { + t.Error("non-matching error must not be reported as not-found") + } +}