-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This allows PreviewProviderUpgrade to avoid side-effects from invokes which cause failures when the replay environment differs from the recording environment such as local vs CI. - Allow wrapping a provider factory and intercept invokes to replay them from a log file. - Expose `GetUpgradeCacheDir` so users don't have to hard code the whole long path themselves. Closes #43 Fixes #31 ### Example usage ```go // Construct a provider factory as usual providerFactory := providers.ResourceProviderFactory(providerServer) // Locate where the gRPC logs were recorded cacheDir := providertest.GetUpgradeCacheDir(filepath.Base(dir), "5.60.0") // Intercept invoke calls and replay responses from the log file. factoryWithReplay := providerFactory.ReplayInvokes(filepath.Join(cacheDir, "grpc.json"), true) ```
- Loading branch information
1 parent
be10035
commit cecfbc7
Showing
5 changed files
with
142 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package providers | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"reflect" | ||
|
||
"github.com/pulumi/providertest/grpclog" | ||
pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go" | ||
) | ||
|
||
// ReplayInvokes wraps a provider factory, intercepting all invokes and replaying them from a gRPC log. | ||
// Example: | ||
// providerFactory := providers.ResourceProviderFactory(providerServer) | ||
// cacheDir := providertest.GetUpgradeCacheDir(filepath.Base(dir), "5.60.0") | ||
// factoryWithReplay := providerFactory.ReplayInvokes(filepath.Join(cacheDir, "grpc.json"), true) | ||
func (pf ProviderFactory) ReplayInvokes(grpcLogPath string, allowLiveFallback bool) ProviderFactory { | ||
interceptors := ProviderInterceptors{ | ||
Invoke: func(ctx context.Context, in *pulumirpc.InvokeRequest, client pulumirpc.ResourceProviderClient) (*pulumirpc.InvokeResponse, error) { | ||
log, err := grpclog.LoadLog(grpcLogPath) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to load gRPC log: %w", err) | ||
} | ||
invokes, err := log.Invokes() | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to get invokes from log: %w", err) | ||
} | ||
requestedToken := in.GetTok() | ||
// Avoid using range due to invokes containing sync locks. | ||
for i := 0; i < len(invokes); i++ { | ||
if invokes[i].Request.Tok == requestedToken { | ||
if reflect.DeepEqual(in.Args.AsMap(), invokes[i].Request.Args.AsMap()) { | ||
return &invokes[i].Response, nil | ||
} | ||
} | ||
} | ||
if allowLiveFallback { | ||
return client.Invoke(ctx, in) | ||
} else { | ||
return nil, fmt.Errorf("failed to find invoke %s in gRPC log", requestedToken) | ||
} | ||
}, | ||
} | ||
return func(ctx context.Context, pt PulumiTest) (Port, error) { | ||
port, err := pf(ctx, pt) | ||
if err != nil { | ||
return -1, err | ||
} | ||
interceptResourceProviderServer, err := NewProviderInterceptProxy(ctx, port, interceptors) | ||
if err != nil { | ||
return -1, err | ||
} | ||
return startResourceProviderServer(ctx, pt, func(pt PulumiTest) (pulumirpc.ResourceProviderServer, error) { | ||
return interceptResourceProviderServer, nil | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
name: yaml_command_invoke | ||
description: This is used to test invoke replay. | ||
runtime: yaml | ||
variables: | ||
# Create a variable which always changes on every run | ||
randomShellString1: | ||
fn::invoke: | ||
function: command:local:run | ||
arguments: | ||
command: cat /dev/urandom | LC_ALL=C tr -dc 'a-zA-Z0-9' | head -c 10 | ||
# Create a second to check the args are deeply matched | ||
# We therefore have a slightly different command so the command text isn't identical | ||
randomShellString2: | ||
fn::invoke: | ||
function: command:local:run | ||
arguments: | ||
command: cat /dev/urandom | LC_ALL=C tr -dc 'a-zA-Z0-9' | head -c 20 | ||
resources: | ||
randomEchoResource1: | ||
type: command:local:Command | ||
properties: | ||
create: echo "${randomShellString1}" | ||
# Force this resource to be updated if the invoke changes which is easy to assert on in the preview result. | ||
triggers: | ||
- ${randomShellString1} | ||
randomEchoResource2: | ||
type: command:local:Command | ||
properties: | ||
create: echo "${randomShellString2}" | ||
# Force this resource to be updated if the invoke changes which is easy to assert on in the preview result. | ||
triggers: | ||
- ${randomShellString2} |