Skip to content

Commit

Permalink
Display dynamic provider stacktrace on upstream provider panic
Browse files Browse the repository at this point in the history
We ensure that panic messages are added to the returned error so that they are displayed
to the user.

Fixes pulumi/pulumi-terraform-provider#22.
  • Loading branch information
iwahbe committed Aug 28, 2024
1 parent 2f83381 commit 7326560
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 0 deletions.
25 changes: 25 additions & 0 deletions dynamic/internal/shim/run/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ import (
tfaddr "github.com/opentofu/registry-address"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

"github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge"
)
Expand Down Expand Up @@ -192,6 +195,25 @@ func getProviderServer(
return runProvider(ctx, p)
}

func includePanic(
ctx context.Context, method string,
req, reply any,
cc *grpc.ClientConn, invoker grpc.UnaryInvoker,
opts ...grpc.CallOption,
) error {
err := invoker(ctx, method, req, reply, cc, opts...)
if status.Code(err) != codes.Unavailable {
return nil
}

panics := logging.PluginPanics()
if len(panics) == 0 {
return err
}

return fmt.Errorf("%w:\n%s", err, strings.Join(panics, "\n"))
}

// runProvider produces a provider factory that runs up the executable
// file in the given cache package and uses go-plugin to implement
// providers.Interface against it.
Expand All @@ -213,6 +235,9 @@ func runProvider(ctx context.Context, meta *providercache.CachedProvider) (Provi
VersionedPlugins: tfplugin.VersionedPlugins,
SyncStdout: logging.PluginOutputMonitor(fmt.Sprintf("%s:stdout", meta.Provider)),
SyncStderr: logging.PluginOutputMonitor(fmt.Sprintf("%s:stderr", meta.Provider)),
GRPCDialOptions: []grpc.DialOption{
grpc.WithUnaryInterceptor(includePanic),
},
}

client := plugin.NewClient(config)
Expand Down
30 changes: 30 additions & 0 deletions dynamic/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,21 @@ func TestMain(m *testing.M) {
os.Exit(exitVal)
}

func TestStacktraceDisplayed(t *testing.T) {
t.Parallel()
skipWindows(t)

ctx := context.Background()
grpc := pfProviderTestServer(ctx, t)

_, err := grpc.Create(ctx, &pulumirpc.CreateRequest{
Urn: string(resource.NewURN(
"test", "test", "", "pfprovider:index/panic:Panic", "panic",
)),
})
assert.ErrorContains(t, err, "PANIC MESSAGE HERE")
}

func TestPrimitiveTypes(t *testing.T) {
t.Parallel()
skipWindows(t)
Expand Down Expand Up @@ -371,6 +386,7 @@ var pfProviderPath = func() func(t *testing.T) string {
}
}()

// grpcTestServer returns an unparameterized in-memory gRPC server.
func grpcTestServer(ctx context.Context, t *testing.T) pulumirpc.ResourceProviderServer {
defaultInfo, metadata, close := initialSetup()
t.Cleanup(func() { assert.NoError(t, close()) })
Expand All @@ -379,6 +395,20 @@ func grpcTestServer(ctx context.Context, t *testing.T) pulumirpc.ResourceProvide
return s
}

// pfProviderTestServer returns an in-memory gRPC server already parameterized by the
// pfprovider test Terraform provider.
func pfProviderTestServer(ctx context.Context, t *testing.T) pulumirpc.ResourceProviderServer {
grpc := grpcTestServer(ctx, t)
t.Run("parameterize", assertGRPCCall(grpc.Parameterize, &pulumirpc.ParameterizeRequest{
Parameters: &pulumirpc.ParameterizeRequest_Args{
Args: &pulumirpc.ParameterizeRequest_ParametersArgs{
Args: []string{pfProviderPath(t)},
},
},
}, noParallel))
return grpc
}

func skipWindows(t *testing.T) {
t.Helper()
if runtime.GOOS != "windows" {
Expand Down
79 changes: 79 additions & 0 deletions dynamic/test/pfprovider/panic_resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package main

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
)

// Ensure provider defined types fully satisfy framework interfaces.
var _ resource.Resource = &panicResource{}
var _ resource.ResourceWithImportState = &panicResource{}

func NewPanicResource() resource.Resource { return &panicResource{} }

// panicResource defines the resource implementation.
type panicResource struct{}

func (r *panicResource) Metadata(
ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse,
) {
resp.TypeName = req.ProviderTypeName + "_panic"
}

func (r *panicResource) Schema(
ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse,
) {
resp.Schema = schema.Schema{
// This description is used by the documentation generator and the language server.
MarkdownDescription: "Example resource",

Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
MarkdownDescription: "Example identifier",
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
},
}
}

func (r *panicResource) Configure(
ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse,
) {
}

func (r *panicResource) Create(
ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse,
) {
panic("PANIC MESSAGE HERE")
}

func (r *panicResource) Read(
ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse,
) {
panic("PANIC MESSAGE HERE")
}

func (r *panicResource) Update(
ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse,
) {
panic("PANIC MESSAGE HERE")
}

func (r *panicResource) Delete(
ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse,
) {
panic("PANIC MESSAGE HERE")
}

func (r *panicResource) ImportState(
ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse,
) {
panic("PANIC MESSAGE HERE")
}
1 change: 1 addition & 0 deletions dynamic/test/pfprovider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ func (p *PFProvider) Configure(
func (p *PFProvider) Resources(ctx context.Context) []func() resource.Resource {
return []func() resource.Resource{
NewExampleResource,
NewPanicResource,
}
}

Expand Down
4 changes: 4 additions & 0 deletions dynamic/testdata/TestStacktraceDisplayed/parameterize.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "pfprovider",
"version": "0.0.0"
}

0 comments on commit 7326560

Please sign in to comment.