Refactor Mock newResource and call to accept property bag rather than individual args (#6672)

This commit is contained in:
Paul Stack 2021-04-08 12:47:08 +01:00 committed by stack72
parent fef3157b18
commit e955a6b06a
23 changed files with 444 additions and 221 deletions

View file

@ -19,13 +19,12 @@ import (
"encoding/json"
"fmt"
"github.com/pulumi/pulumi/pkg/v3/backend"
"github.com/pulumi/pulumi/pkg/v3/backend/display"
"github.com/pulumi/pulumi/pkg/v3/resource/stack"
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/config"
"github.com/spf13/cobra"
"github.com/pulumi/pulumi/pkg/v3/backend/display"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
"github.com/spf13/cobra"
)
func newStackChangeSecretsProviderCmd() *cobra.Command {

View file

@ -18,10 +18,6 @@ import (
"encoding/json"
"fmt"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/result"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
"github.com/pkg/errors"
"github.com/pulumi/pulumi/pkg/v3/backend/display"
"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
@ -31,6 +27,8 @@ import (
"github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/result"
"github.com/spf13/cobra"
survey "gopkg.in/AlecAivazis/survey.v1"
surveycore "gopkg.in/AlecAivazis/survey.v1/core"

View file

@ -143,18 +143,12 @@ func TestGeneratePackage(t *testing.T) {
type mocks int
func (mocks) NewResource(
typeToken string,
name string,
inputs resource.PropertyMap,
provider string,
id string,
) (string, resource.PropertyMap, error) {
return name + "_id", inputs, nil
func (mocks) NewResource(args pulumi.MockResourceArgs) (string, resource.PropertyMap, error) {
return args.Name + "_id", args.Inputs, nil
}
func (mocks) Call(token string, args resource.PropertyMap, provider string) (resource.PropertyMap, error) {
return args, nil
func (mocks) Call(args pulumi.MockCallArgs) (resource.PropertyMap, error) {
return args.Args, nil
}
func TestEnumUsage(t *testing.T) {

View file

@ -22,14 +22,13 @@ import (
"sort"
"strings"
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
"github.com/hashicorp/hcl/v2"
"github.com/pulumi/pulumi/pkg/v3/codegen"
"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2"
"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model"
"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model/format"
"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/syntax"
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
"github.com/zclconf/go-cty/cty"
)

View file

@ -12,23 +12,23 @@ namespace Pulumi.Tests.Mocks
{
class MyMocks : IMocks
{
public Task<object> CallAsync(string token, ImmutableDictionary<string, object> args, string? provider)
public Task<object> CallAsync(MockCallArgs args)
{
return Task.FromResult<object>(args);
}
public Task<(string? id, object state)> NewResourceAsync(string type, string name, ImmutableDictionary<string, object> inputs, string? provider, string? id)
public Task<(string? id, object state)> NewResourceAsync(MockResourceArgs args)
{
switch (type)
switch (args.Type)
{
case "aws:ec2/instance:Instance":
return Task.FromResult<(string?, object)>(("i-1234567890abcdef0", new Dictionary<string, object> {
{ "publicIp", "203.0.113.12" },
}));
case "pkg:index:MyCustom":
return Task.FromResult<(string?, object)>((name + "_id", inputs));
return Task.FromResult<(string?, object)>((args.Name + "_id", args.Inputs));
default:
throw new Exception($"Unknown resource {type}");
throw new Exception($"Unknown resource {args.Type}");
}
}
}

View file

@ -66,14 +66,14 @@ namespace Pulumi.Tests.Serialization
this.isPreview = isPreview;
}
public Task<object> CallAsync(string token, ImmutableDictionary<string, object> args, string? provider)
public Task<object> CallAsync(MockCallArgs args)
{
throw new Exception($"Unknown function {token}");
throw new Exception($"Unknown function {args.Token}");
}
public Task<(string? id, object state)> NewResourceAsync(string type, string name, ImmutableDictionary<string, object> inputs, string? provider, string? id)
public Task<(string? id, object state)> NewResourceAsync(MockResourceArgs args)
{
switch (type)
switch (args.Type)
{
case "test:index:resource":
case "test:missing:resource":
@ -82,7 +82,7 @@ namespace Pulumi.Tests.Serialization
case "test:missing:component":
return Task.FromResult<(string?, object)>((null, new Dictionary<string, object> {}));
default:
throw new Exception($"Unknown resource {type}");
throw new Exception($"Unknown resource {args.Type}");
}
}
}

View file

@ -204,9 +204,29 @@ Pulumi.StackReferenceArgs.Name.set -> void
Pulumi.StackReferenceArgs.StackReferenceArgs() -> void
Pulumi.StringAsset
Pulumi.StringAsset.StringAsset(string text) -> void
Pulumi.Testing.MockResourceArgs
Pulumi.Testing.MockResourceArgs.MockResourceArgs() -> void
Pulumi.Testing.MockResourceArgs.Type.get -> string
Pulumi.Testing.MockResourceArgs.Type.set -> void
Pulumi.Testing.MockResourceArgs.Name.get -> string
Pulumi.Testing.MockResourceArgs.Name.set -> void
Pulumi.Testing.MockResourceArgs.Provider.get -> string
Pulumi.Testing.MockResourceArgs.Provider.set -> void
Pulumi.Testing.MockResourceArgs.Id.get -> string
Pulumi.Testing.MockResourceArgs.Id.set -> void
Pulumi.Testing.MockResourceArgs.Inputs.get -> System.Collections.Immutable.ImmutableDictionary<string, object>
Pulumi.Testing.MockResourceArgs.Inputs.set -> void
Pulumi.Testing.MockCallArgs
Pulumi.Testing.MockCallArgs.MockCallArgs() -> void
Pulumi.Testing.MockCallArgs.Token.get -> string
Pulumi.Testing.MockCallArgs.Token.set -> void
Pulumi.Testing.MockCallArgs.Provider.get -> string
Pulumi.Testing.MockCallArgs.Provider.set -> void
Pulumi.Testing.MockCallArgs.Args.get -> System.Collections.Immutable.ImmutableDictionary<string, object>
Pulumi.Testing.MockCallArgs.Args.set -> void
Pulumi.Testing.IMocks
Pulumi.Testing.IMocks.CallAsync(string token, System.Collections.Immutable.ImmutableDictionary<string, object> args, string provider) -> System.Threading.Tasks.Task<object>
Pulumi.Testing.IMocks.NewResourceAsync(string type, string name, System.Collections.Immutable.ImmutableDictionary<string, object> inputs, string provider, string id) -> System.Threading.Tasks.Task<(string id, object state)>
Pulumi.Testing.IMocks.CallAsync(Pulumi.Testing.MockCallArgs args) -> System.Threading.Tasks.Task<object>
Pulumi.Testing.IMocks.NewResourceAsync(Pulumi.Testing.MockResourceArgs args) -> System.Threading.Tasks.Task<(string id, object state)>
Pulumi.Testing.TestOptions
Pulumi.Testing.TestOptions.TestOptions() -> void
Pulumi.Testing.TestOptions.ProjectName.get -> string

View file

@ -1,5 +1,6 @@
// Copyright 2016-2020, Pulumi Corporation
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
@ -13,24 +14,69 @@ namespace Pulumi.Testing
/// <summary>
/// Invoked when a new resource is created by the program.
/// </summary>
/// <param name="type">Resource type name.</param>
/// <param name="name">Resource name.</param>
/// <param name="inputs">Dictionary of resource input properties.</param>
/// <param name="provider">Provider.</param>
/// <param name="id">Resource identifier.</param>
/// <param name="args">MockResourceArgs</param>
/// <returns>A tuple of a resource identifier and resource state. State can be either a POCO
/// or a dictionary bag. The returned ID may be null for component resources.</returns>
Task<(string? id, object state)> NewResourceAsync(string type, string name,
ImmutableDictionary<string, object> inputs, string? provider, string? id);
Task<(string? id, object state)> NewResourceAsync(MockResourceArgs args);
/// <summary>
/// Invoked when the program needs to call a provider to load data (e.g., to retrieve an existing
/// resource).
/// </summary>
/// <param name="token">Function token.</param>
/// <param name="args">Dictionary of input arguments.</param>
/// <param name="provider">Provider.</param>
/// <param name="args">MockCallArgs</param>
/// <returns>Invocation result, can be either a POCO or a dictionary bag.</returns>
Task<object> CallAsync(string token, ImmutableDictionary<string, object> args, string? provider);
Task<object> CallAsync(MockCallArgs args);
}
/// <summary>
/// MockResourceArgs for use in NewResourceAsync
/// </summary>
public class MockResourceArgs
{
/// <summary>
/// Resource type name.
/// </summary>
public string? Type { get; set; }
/// <summary>
/// Resource Name.
/// </summary>
public string? Name { get; set; }
/// <summary>
/// Dictionary of resource input properties.
/// </summary>
public ImmutableDictionary<string, object> Inputs { get; set; } = null!;
/// <summary>
/// Provider.
/// </summary>
public string? Provider { get; set; }
/// <summary>
/// Resource identifier.
/// </summary>
public string? Id { get; set; }
}
/// <summary>
/// MockCallArgs for use in CallAsync
/// </summary>
public class MockCallArgs
{
/// <summary>
/// Resource identifier.
/// </summary>
public string? Token { get; set; }
/// <summary>
/// Dictionary of input arguments.
/// </summary>
public ImmutableDictionary<string, object> Args { get; set; } = null!;
/// <summary>
/// Provider.
/// </summary>
public string? Provider { get; set; }
}
}

View file

@ -47,16 +47,27 @@ namespace Pulumi.Testing
}
return new InvokeResponse { Return = await SerializeAsync(registeredResource).ConfigureAwait(false) };
}
var result = await _mocks.CallAsync(request.Tok, args, request.Provider)
var result = await _mocks.CallAsync(new MockCallArgs
{
Token = request.Tok,
Args = args,
Provider = request.Provider,
})
.ConfigureAwait(false);
return new InvokeResponse { Return = await SerializeAsync(result).ConfigureAwait(false) };
}
public async Task<ReadResourceResponse> ReadResourceAsync(Resource resource, ReadResourceRequest request)
{
var (id, state) = await _mocks.NewResourceAsync(request.Type, request.Name,
ToDictionary(request.Properties), request.Provider, request.Id).ConfigureAwait(false);
var (id, state) = await _mocks.NewResourceAsync(new MockResourceArgs
{
Type = request.Type,
Name = request.Name,
Inputs = ToDictionary(request.Properties),
Provider = request.Provider,
Id = request.Id,
}).ConfigureAwait(false);
var urn = NewUrn(request.Parent, request.Type, request.Name);
var serializedState = await SerializeToDictionary(state).ConfigureAwait(false);
@ -101,8 +112,14 @@ namespace Pulumi.Testing
};
}
var (id, state) = await _mocks.NewResourceAsync(request.Type, request.Name, ToDictionary(request.Object),
request.Provider, request.ImportId).ConfigureAwait(false);
var (id, state) = await _mocks.NewResourceAsync(new MockResourceArgs
{
Type = request.Type,
Name = request.Name,
Inputs = ToDictionary(request.Object),
Provider = request.Provider,
Id = request.ImportId,
}).ConfigureAwait(false);
var urn = NewUrn(request.Parent, request.Type, request.Name);
var serializedState = await SerializeToDictionary(state).ConfigureAwait(false);

View file

@ -219,8 +219,6 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzr
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/pulumi/pulumi/sdk/v2 v2.22.0 h1:O42/vaUiXIyDgIcChQ6RvFEshCGzuVJy53uJr9QZI8o=
github.com/pulumi/pulumi/sdk/v2 v2.22.0/go.mod h1:fCFhRV6NmidWetmgDPA76efL+s0JqLlS54JJIwfOt+o=
github.com/pulumi/pulumi/sdk/v3 v3.0.0-20210322210933-10a6a2caf014 h1:WUlOHsRhzO08oUCEjZhWS0VHssiIjCNio89VlAvD9ao=
github.com/pulumi/pulumi/sdk/v3 v3.0.0-20210322210933-10a6a2caf014/go.mod h1:GBHyQ7awNQSRmiKp/p8kIKrGrMOZeA/k2czoM/GOqds=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af h1:gu+uRPtBe88sKxUCEXRoeCvVG90TJmwhiqRpvdhQFng=

View file

@ -16,9 +16,8 @@ import (
)
type MockResourceMonitor interface {
Call(token string, args resource.PropertyMap, provider string) (resource.PropertyMap, error)
NewResource(typeToken, name string, inputs resource.PropertyMap,
provider, id string) (string, resource.PropertyMap, error)
Call(args MockCallArgs) (resource.PropertyMap, error)
NewResource(args MockResourceArgs) (string, resource.PropertyMap, error)
}
func WithMocks(project, stack string, mocks MockResourceMonitor) RunOption {
@ -27,6 +26,33 @@ func WithMocks(project, stack string, mocks MockResourceMonitor) RunOption {
}
}
// MockResourceArgs is used to construct call Mock
type MockCallArgs struct {
// Token indicates which function is being called. This token is of the form "package:module:function".
Token string
// Args are the arguments provided to the function call.
Args resource.PropertyMap
// Provider is the identifier of the provider instance being used to make the call.
Provider string
}
// MockResourceArgs is a used to construct a newResource Mock
type MockResourceArgs struct {
// TypeToken is the token that indicates which resource type is being constructed. This token
// is of the form "package:module:type".
TypeToken string
// Name is the logical name of the resource instance.
Name string
// Inputs are the inputs for the resource.
Inputs resource.PropertyMap
// Provider is the identifier of the provider instance being used to manage this resource.
Provider string
// ID is the physical identifier of an existing resource to read or import.
ID string
// Custom specifies whether or not the resource is Custom (i.e. managed by a resource provider).
Custom bool
}
type mockMonitor struct {
project string
stack string
@ -81,8 +107,11 @@ func (m *mockMonitor) Invoke(ctx context.Context, in *pulumirpc.InvokeRequest,
Return: result,
}, nil
}
resultV, err := m.mocks.Call(in.GetTok(), args, in.GetProvider())
resultV, err := m.mocks.Call(MockCallArgs{
Token: in.GetTok(),
Args: args,
Provider: in.GetProvider(),
})
if err != nil {
return nil, err
}
@ -117,7 +146,14 @@ func (m *mockMonitor) ReadResource(ctx context.Context, in *pulumirpc.ReadResour
return nil, err
}
id, state, err := m.mocks.NewResource(in.GetType(), in.GetName(), stateIn, in.GetProvider(), in.GetId())
id, state, err := m.mocks.NewResource(MockResourceArgs{
TypeToken: in.GetType(),
Name: in.GetName(),
Inputs: stateIn,
Provider: in.GetProvider(),
ID: in.GetId(),
Custom: false,
})
if err != nil {
return nil, err
}
@ -161,7 +197,14 @@ func (m *mockMonitor) RegisterResource(ctx context.Context, in *pulumirpc.Regist
return nil, err
}
id, state, err := m.mocks.NewResource(in.GetType(), in.GetName(), inputs, in.GetProvider(), in.GetImportId())
id, state, err := m.mocks.NewResource(MockResourceArgs{
TypeToken: in.GetType(),
Name: in.GetName(),
Inputs: inputs,
Provider: in.GetProvider(),
ID: in.GetImportId(),
Custom: in.GetCustom(),
})
if err != nil {
return nil, err
}

View file

@ -12,25 +12,23 @@ import (
)
type testMonitor struct {
CallF func(tok string, args resource.PropertyMap, provider string) (resource.PropertyMap, error)
NewResourceF func(typeToken, name string, inputs resource.PropertyMap,
provider, id string) (string, resource.PropertyMap, error)
CallF func(args MockCallArgs) (resource.PropertyMap, error)
NewResourceF func(args MockResourceArgs) (string, resource.PropertyMap, error)
}
func (m *testMonitor) Call(tok string, args resource.PropertyMap, provider string) (resource.PropertyMap, error) {
func (m *testMonitor) Call(args MockCallArgs) (resource.PropertyMap, error) {
if m.CallF == nil {
return resource.PropertyMap{}, nil
}
return m.CallF(tok, args, provider)
return m.CallF(args)
}
func (m *testMonitor) NewResource(typeToken, name string, inputs resource.PropertyMap,
provider, id string) (string, resource.PropertyMap, error) {
func (m *testMonitor) NewResource(args MockResourceArgs) (string, resource.PropertyMap, error) {
if m.NewResourceF == nil {
return name, resource.PropertyMap{}, nil
return args.Name, resource.PropertyMap{}, nil
}
return m.NewResourceF(typeToken, name, inputs, provider, id)
return m.NewResourceF(args)
}
type testResource2 struct {
@ -75,19 +73,18 @@ type invokeResult struct {
func TestRegisterResource(t *testing.T) {
mocks := &testMonitor{
NewResourceF: func(typeToken, name string, inputs resource.PropertyMap,
provider, id string) (string, resource.PropertyMap, error) {
NewResourceF: func(args MockResourceArgs) (string, resource.PropertyMap, error) {
assert.Equal(t, "test:resource:type", typeToken)
assert.Equal(t, "resA", name)
assert.True(t, inputs.DeepEquals(resource.NewPropertyMapFromMap(map[string]interface{}{
assert.Equal(t, "test:resource:type", args.TypeToken)
assert.Equal(t, "resA", args.Name)
assert.True(t, args.Inputs.DeepEquals(resource.NewPropertyMapFromMap(map[string]interface{}{
"foo": "oof",
"bar": "rab",
"baz": "zab",
"bang": "gnab",
})))
assert.Equal(t, "", provider)
assert.Equal(t, "", id)
assert.Equal(t, "", args.Provider)
assert.Equal(t, "", args.ID)
return "someID", resource.PropertyMap{"foo": resource.NewStringProperty("qux")}, nil
},
@ -163,18 +160,17 @@ func TestRegisterResource(t *testing.T) {
func TestReadResource(t *testing.T) {
mocks := &testMonitor{
NewResourceF: func(typeToken, name string, state resource.PropertyMap,
provider, id string) (string, resource.PropertyMap, error) {
NewResourceF: func(args MockResourceArgs) (string, resource.PropertyMap, error) {
assert.Equal(t, "test:resource:type", typeToken)
assert.Equal(t, "resA", name)
assert.True(t, state.DeepEquals(resource.NewPropertyMapFromMap(map[string]interface{}{
assert.Equal(t, "test:resource:type", args.TypeToken)
assert.Equal(t, "resA", args.Name)
assert.True(t, args.Inputs.DeepEquals(resource.NewPropertyMapFromMap(map[string]interface{}{
"foo": "oof",
})))
assert.Equal(t, "", provider)
assert.Equal(t, "someID", id)
assert.Equal(t, "", args.Provider)
assert.Equal(t, "someID", args.ID)
return id, resource.PropertyMap{"foo": resource.NewStringProperty("qux")}, nil
return args.ID, resource.PropertyMap{"foo": resource.NewStringProperty("qux")}, nil
},
}
@ -228,9 +224,9 @@ func TestReadResource(t *testing.T) {
func TestInvoke(t *testing.T) {
mocks := &testMonitor{
CallF: func(token string, args resource.PropertyMap, provider string) (resource.PropertyMap, error) {
assert.Equal(t, "test:index:func", token)
assert.True(t, args.DeepEquals(resource.NewPropertyMapFromMap(map[string]interface{}{
CallF: func(args MockCallArgs) (resource.PropertyMap, error) {
assert.Equal(t, "test:index:func", args.Token)
assert.True(t, args.Args.DeepEquals(resource.NewPropertyMapFromMap(map[string]interface{}{
"bang": "gnab",
"bar": "rab",
})))
@ -357,16 +353,15 @@ func TestRegisterResourceWithResourceReferences(t *testing.T) {
RegisterResourceModule("pkg", "index", module(0))
mocks := &testMonitor{
NewResourceF: func(typeToken, name string, inputs resource.PropertyMap,
provider, id string) (string, resource.PropertyMap, error) {
NewResourceF: func(args MockResourceArgs) (string, resource.PropertyMap, error) {
switch typeToken {
switch args.TypeToken {
case "pkg:index:Instance":
return "i-1234567890abcdef0", resource.PropertyMap{}, nil
case "pkg:index:MyCustom":
return name + "_id", inputs, nil
return args.Name + "_id", args.Inputs, nil
default:
return "", nil, errors.Errorf("unknown resource %s", typeToken)
return "", nil, errors.Errorf("unknown resource %s", args.TypeToken)
}
},
}

View file

@ -17,16 +17,15 @@ func TestStackReference(t *testing.T) {
},
}
mocks := &testMonitor{
NewResourceF: func(typeToken, name string, inputs resource.PropertyMap,
provider, id string) (string, resource.PropertyMap, error) {
assert.Equal(t, "pulumi:pulumi:StackReference", typeToken)
assert.Equal(t, resName, name)
assert.True(t, inputs.DeepEquals(resource.NewPropertyMapFromMap(map[string]interface{}{
NewResourceF: func(args MockResourceArgs) (string, resource.PropertyMap, error) {
assert.Equal(t, "pulumi:pulumi:StackReference", args.TypeToken)
assert.Equal(t, resName, args.Name)
assert.True(t, args.Inputs.DeepEquals(resource.NewPropertyMapFromMap(map[string]interface{}{
"name": "stack",
})))
assert.Equal(t, "", provider)
assert.Equal(t, inputs["name"].StringValue(), id)
return inputs["name"].StringValue(), resource.NewPropertyMapFromMap(map[string]interface{}{
assert.Equal(t, "", args.Provider)
assert.Equal(t, args.Inputs["name"].StringValue(), args.ID)
return args.Inputs["name"].StringValue(), resource.NewPropertyMapFromMap(map[string]interface{}{
"name": "stack",
"outputs": outputs,
}), nil

View file

@ -21,7 +21,7 @@ export {
export { CodePathOptions, computeCodePaths } from "./closure/codePaths";
export { leakedPromises } from "./debuggable";
export { Mocks, setMocks } from "./mocks";
export { Mocks, setMocks, MockResourceArgs, MockCallArgs } from "./mocks";
export * from "./config";
export * from "./invoke";

View file

@ -19,6 +19,61 @@ const provproto = require("../proto/provider_pb.js");
const resproto = require("../proto/resource_pb.js");
const structproto = require("google-protobuf/google/protobuf/struct_pb.js");
/**
* MockResourceArgs is a used to construct a newResource Mock
*/
export interface MockResourceArgs {
/**
* The token that indicates which resource type is being constructed. This token is of the form "package:module:type".
*/
type: string;
/**
* The logical name of the resource instance.
*/
name: string;
/**
* The inputs for the resource.
*/
inputs: any;
/**
* If provided, the identifier of the provider instance being used to manage this resource.
*/
provider?: string;
/**
* Specifies whether or not the resource is Custom (i.e. managed by a resource provider).
*/
custom?: boolean;
/**
* If provided, the physical identifier of an existing resource to read or import.
*/
id?: string;
}
/**
* MockResourceArgs is used to construct call Mock
*/
export interface MockCallArgs {
/**
* The token that indicates which function is being called. This token is of the form "package:module:function".
*/
token: string;
/**
* The arguments provided to the function call.
*/
inputs: any;
/**
* If provided, the identifier of the provider instance being used to make the call.
*/
provider?: string;
}
/**
* Mocks is an abstract class that allows subclasses to replace operations normally implemented by the Pulumi engine with
* their own implementations. This can be used during testing to ensure that calls to provider functions and resource constructors
@ -28,24 +83,17 @@ export interface Mocks {
/**
* Mocks provider-implemented function calls (e.g. aws.get_availability_zones).
*
* @param token: The token that indicates which function is being called. This token is of the form "package:module:function".
* @param args: The arguments provided to the function call.
* @param provider: If provided, the identifier of the provider instance being used to make the call.
* @param args: MockCallArgs
*/
call(token: string, args: any, provider?: string): Record<string, any>;
call(args: MockCallArgs): Record<string, any>;
/**
* Mocks resource construction calls. This function should return the physical identifier and the output properties
* for the resource being constructed.
*
* @param type: The token that indicates which resource type is being constructed. This token is of the form "package:module:type".
* @param name: The logical name of the resource instance.
* @param inputs: The inputs for the resource.
* @param provider: If provided, the identifier of the provider instance being used to manage this resource.
* @param id: If provided, the physical identifier of an existing resource to read or import.
* @param custom: Specifies whether or not the resource is Custom (i.e. managed by a resource provider). This is always set, but marked optional for backwards compatibility.
* @param args: MockResourceArgs
*/
newResource(type: string, name: string, inputs: any, provider?: string, id?: string, custom?: boolean): { id: string | undefined, state: Record<string, any> };
newResource(args: MockResourceArgs): { id: string | undefined, state: Record<string, any> };
}
export class MockMonitor {
@ -79,7 +127,11 @@ export class MockMonitor {
return;
}
const result = this.mocks.call(tok, inputs, req.getProvider());
const result = this.mocks.call({
token: tok,
inputs: inputs,
provider: req.getProvider(),
});
const response = new provproto.InvokeResponse();
response.setReturn(structproto.Struct.fromJavaScript(await serializeProperties("", result)));
callback(null, response);
@ -90,13 +142,14 @@ export class MockMonitor {
public async readResource(req: any, callback: (err: any, innterResponse: any) => void) {
try {
const result = this.mocks.newResource(
req.getType(),
req.getName(),
deserializeProperties(req.getProperties()),
req.getProvider(),
req.getId(),
req.getCustom());
const result = this.mocks.newResource({
type: req.getType(),
name: req.getName(),
inputs: deserializeProperties(req.getProperties()),
provider: req.getProvider(),
custom: req.getCustom(),
id: req.getId(),
});
const urn = this.newUrn(req.getParent(), req.getType(), req.getName());
const serializedState = await serializeProperties("", result.state);
@ -114,13 +167,14 @@ export class MockMonitor {
public async registerResource(req: any, callback: (err: any, innerResponse: any) => void) {
try {
const result = this.mocks.newResource(
req.getType(),
req.getName(),
deserializeProperties(req.getObject()),
req.getProvider(),
req.getImportid(),
req.getCustom());
const result = this.mocks.newResource({
type: req.getType(),
name: req.getName(),
inputs: deserializeProperties(req.getObject()),
provider: req.getProvider(),
custom: req.getCustom(),
id: req.getImportid(),
});
const urn = this.newUrn(req.getParent(), req.getType(), req.getName());
const serializedState = await serializeProperties("", result.state);

View file

@ -52,12 +52,12 @@ class TestResourceModule implements runtime.ResourceModule {
}
class TestMocks implements runtime.Mocks {
call(token: string, args: any, provider?: string): Record<string, any> {
throw new Error(`unknown function ${token}`);
call(args: runtime.MockCallArgs): Record<string, any> {
throw new Error(`unknown function ${args.token}`);
}
newResource(type: string, name: string, inputs: any, provider?: string, id?: string): { id: string | undefined, state: Record<string, any> } {
switch (type) {
newResource(args: runtime.MockResourceArgs): { id: string | undefined, state: Record<string, any> } {
switch (args.type) {
case "test:index:component":
return {id: undefined, state: {}};
case "test:index:custom":
@ -69,7 +69,7 @@ class TestMocks implements runtime.Mocks {
case "error":
throw new Error("this is an intentional error");
default:
throw new Error(`unknown resource type ${type}`);
throw new Error(`unknown resource type ${args.type}`);
}
}
}

View file

@ -15,20 +15,21 @@
import * as assert from "assert";
import * as pulumi from "../index";
import { CustomResourceOptions } from "../resource";
import { MockCallArgs, MockResourceArgs } from "../runtime";
pulumi.runtime.setMocks({
call: (token: string, args: any, provider?: string) => {
switch (token) {
call: (args: MockCallArgs) => {
switch (args.token) {
case "test:index:MyFunction":
return { out_value: 59 };
default:
return {};
}
},
newResource: (type: string, name: string, inputs: any, provider?: string, id?: string, custom?: boolean): {id: string, state: any} => {
switch (type) {
newResource: (args: MockResourceArgs): {id: string, state: any} => {
switch (args.type) {
case "aws:ec2/instance:Instance":
assert.strictEqual(custom, true);
assert.strictEqual(args.custom, true);
const state = {
arn: "arn:aws:ec2:us-west-2:123456789012:instance/i-1234567890abcdef0",
instanceState: "running",
@ -37,12 +38,12 @@ pulumi.runtime.setMocks({
publicDns: "ec2-203-0-113-12.compute-1.amazonaws.com",
publicIP: "203.0.113.12",
};
return { id: "i-1234567890abcdef0", state: { ...inputs, ...state } };
return { id: "i-1234567890abcdef0", state: { ...args.inputs, ...state } };
case "pkg:index:MyCustom":
assert.strictEqual(custom, true);
return { id: name + "_id", state: inputs };
assert.strictEqual(args.custom, true);
return { id: args.name + "_id", state: args.inputs };
default:
assert.strictEqual(custom, false);
assert.strictEqual(args.custom, false);
return { id: "", state: {} };
}
},

View file

@ -28,6 +28,8 @@ from .mocks import (
Mocks,
set_mocks,
test,
MockResourceArgs,
MockCallArgs,
)
from .settings import (
@ -70,6 +72,8 @@ __all__ = [
"Mocks",
"set_mocks",
"test",
"MockCallArgs",
"MockResourceArgs",
# settings
"Settings",

View file

@ -38,6 +38,52 @@ def test(fn):
return wrapper
class MockResourceArgs:
"""
MockResourceArgs is used to construct a newResource Mock
"""
typ: str
name: str
inputs: dict
provider: str
resource_id: str
custom: bool
def __init__(self, typ: str, name: str, inputs: dict, provider: str, resource_id: str, custom: bool) -> None:
"""
:param str typ: The token that indicates which resource type is being constructed. This token is of the form "package:module:type".
:param str name: The logical name of the resource instance.
:param dict inputs: The inputs for the resource.
:param str provider: The identifier of the provider instance being used to manage this resource.
:param str resource_id: The physical identifier of an existing resource to read or import.
:param bool custom: Specifies whether or not the resource is Custom (i.e. managed by a resource provider).
"""
self.typ = typ
self.name = name
self.inputs = inputs
self.provider = provider
self.resource_id = resource_id
self.custom = custom
class MockCallArgs:
"""
MockCallArgs is used to construct a call Mock
"""
token: str
args: dict
provider: str
def __init__(self, token: str, args: dict, provider: str) -> None:
"""
:param str token: The token that indicates which function is being called. This token is of the form "package:module:function".
:param dict args: The arguments provided to the function call.
:param str provider: The identifier of the provider instance being used to make the call
"""
self.token = token
self.args = args
self.provider = provider
class Mocks(ABC):
"""
Mocks is an abstract class that allows subclasses to replace operations normally implemented by the Pulumi engine with
@ -45,27 +91,21 @@ class Mocks(ABC):
return predictable values.
"""
@abstractmethod
def call(self, token: str, args: dict, provider: Optional[str]) -> Tuple[dict, Optional[List[Tuple[str,str]]]]:
def call(self, args: MockCallArgs) -> Tuple[dict, Optional[List[Tuple[str,str]]]]:
"""
call mocks provider-implemented function calls (e.g. aws.get_availability_zones).
:param str token: The token that indicates which function is being called. This token is of the form "package:module:function".
:param dict args: The arguments provided to the function call.
:param Optional[str] provider: If provided, the identifier of the provider instance being used to make the call.
:param MockCallArgs args.
"""
return {}, None
@abstractmethod
def new_resource(self, type_: str, name: str, inputs: dict, provider: Optional[str], id_: Optional[str]) -> Tuple[Optional[str], dict]:
def new_resource(self, args: MockResourceArgs) -> Tuple[Optional[str], dict]:
"""
new_resource mocks resource construction calls. This function should return the physical identifier and the output properties
for the resource being constructed.
:param str type_: The token that indicates which resource type is being constructed. This token is of the form "package:module:type".
:param str name: The logical name of the resource instance.
:param dict inputs: The inputs for the resource.
:param Optional[str] provider: If provided, the identifier of the provider instance being used to manage this resource.
:param Optional[str] id_: If provided, the physical identifier of an existing resource to read or import.
:param MockResourceArgs args.
"""
return "", {}
@ -105,7 +145,8 @@ class MockMonitor:
fields = {"failures": None, "return": ret_proto}
return provider_pb2.InvokeResponse(**fields)
tup = self.mocks.call(request.tok, args, request.provider)
call_args = MockCallArgs(token=request.tok, args=args, provider=request.provider)
tup = self.mocks.call(call_args)
if isinstance(tup, dict):
(ret, failures) = (tup, None)
else:
@ -122,7 +163,13 @@ class MockMonitor:
state = rpc.deserialize_properties(request.properties)
id_, state = self.mocks.new_resource(request.type, request.name, state, request.provider, request.id)
resource_args = MockResourceArgs(typ=request.type,
name=request.name,
inputs=state,
provider=request.provider,
resource_id=request.id,
custom=request.custom or False)
id_, state = self.mocks.new_resource(resource_args)
props_proto = _sync_await(rpc.serialize_properties(state, {}))
@ -143,7 +190,13 @@ class MockMonitor:
inputs = rpc.deserialize_properties(request.object)
id_, state = self.mocks.new_resource(request.type, request.name, inputs, request.provider, request.importId)
resource_args = MockResourceArgs(typ=request.type,
name=request.name,
inputs=inputs,
provider=request.provider,
resource_id=request.importId,
custom=request.custom or False)
id_, state = self.mocks.new_resource(resource_args)
obj_proto = _sync_await(rpc.serialize_properties(state, {}))

View file

@ -18,7 +18,7 @@ from typing import Any, Dict, List, Mapping, Optional, Sequence, cast
from google.protobuf import struct_pb2
from pulumi.resource import ComponentResource, CustomResource, ResourceOptions
from pulumi.runtime import Mocks, ResourceModule, rpc, rpc_manager, known_types, set_mocks, settings
from pulumi.runtime import Mocks, MockCallArgs, MockResourceArgs, ResourceModule, rpc, rpc_manager, known_types, set_mocks, settings
from pulumi import Input, Output, UNKNOWN, input_type
from pulumi.asset import (
FileAsset,
@ -66,16 +66,16 @@ class MyResourceModule(ResourceModule):
class MyMocks(Mocks):
def call(self, token, args, provider):
raise Exception(f"unknown function {token}")
def call(self, args: MockCallArgs):
raise Exception(f"unknown function {args.token}")
def new_resource(self, typ, name, inputs, provider, id):
if typ == "test:index:resource":
def new_resource(self, args: MockResourceArgs):
if args.typ == "test:index:resource":
return [None if settings.is_dry_run() else "id", {}]
elif typ == "test:index:component":
elif args.typ == "test:index:component":
return [None, {}]
else:
raise Exception(f"unknown resource type {typ}")
raise Exception(f"unknown resource type {args.typ}")
@pulumi.output_type

View file

@ -27,26 +27,26 @@ class GrpcError(grpc.RpcError):
return self._details
class MyMocks(pulumi.runtime.Mocks):
def call(self, token, args, provider):
if token == 'test:index:MyFunction':
def call(self, args: pulumi.runtime.MockCallArgs):
if args.token == 'test:index:MyFunction':
return {
'out_value': 59,
}
elif token == 'test:index:FailFunction':
elif args.token == 'test:index:FailFunction':
return ({}, [('none', 'this function fails!')])
elif token == 'test:index:ThrowFunction':
elif args.token == 'test:index:ThrowFunction':
raise GrpcError(42, 'this function throws!')
else:
return {}
def new_resource(self, type_, name, inputs, provider, id_):
if type_ == 'aws:ec2/securityGroup:SecurityGroup':
def new_resource(self, args: pulumi.runtime.MockResourceArgs):
if args.typ == 'aws:ec2/securityGroup:SecurityGroup':
state = {
'arn': 'arn:aws:ec2:us-west-2:123456789012:security-group/sg-12345678',
'name': inputs['name'] if 'name' in inputs else name + '-sg',
'name': args.inputs['name'] if 'name' in args.inputs else args.name + '-sg',
}
return ['sg-12345678', dict(inputs, **state)]
elif type_ == 'aws:ec2/instance:Instance':
return ['sg-12345678', dict(args.inputs, **state)]
elif args.typ == 'aws:ec2/instance:Instance':
state = {
'arn': 'arn:aws:ec2:us-west-2:123456789012:instance/i-1234567890abcdef0',
'instanceState': 'running',
@ -55,9 +55,9 @@ class MyMocks(pulumi.runtime.Mocks):
'public_dns': 'ec2-203-0-113-12.compute-1.amazonaws.com',
'public_ip': '203.0.113.12',
}
return ['i-1234567890abcdef0', dict(inputs, **state)]
elif type_ == 'pkg:index:MyCustom':
return [name + '_id', inputs]
return ['i-1234567890abcdef0', dict(args.inputs, **state)]
elif args.typ == 'pkg:index:MyCustom':
return [args.name + '_id', args.inputs]
else:
return ['', {}]

View file

@ -3,34 +3,37 @@
import pulumi
from pulumi_example import Foo, FooArgs, Provider
class MyMocks(pulumi.runtime.Mocks):
resources = {}
def call(self, token, args, provider):
def call(self, args: pulumi.runtime.MockCallArgs):
return {}
def new_resource(self, type_, name, inputs, provider, id_):
self.resources[name] = inputs
if name == "f1":
assert inputs == {"first": 1, "second": "second", "third": "third"}
elif name == "f2":
assert inputs == {"args": "args", "first": 2, "second": "s", "third": "t"}
elif name == "f3":
assert len(inputs) == 0
assert provider.endswith("f3provider_id")
elif name == "f4":
assert inputs == {"args": "hi"}
elif name == "f5":
assert inputs == {"first": 100, "second": "200", "third": "300"}
assert provider.endswith("f5provider_id")
elif name == "a1":
assert inputs == {"first": 10, "second": "asecond", "third": "athird"}
elif name == "a2":
assert inputs == {"first": 42, "second": "2nd", "third": "3rd"}
elif name == "a3":
assert inputs == {"args": "someargs", "first": 50, "second": "2", "third": "3"}
elif name == "a4":
assert inputs == {"first": 11, "second": "12", "third": "13"}
assert provider.endswith("a4provider_id")
return [name + '_id', inputs]
def new_resource(self, args: pulumi.runtime.MockResourceArgs):
if args.name == "f1":
assert args.inputs == {"first": 1, "second": "second", "third": "third"}
elif args.name == "f2":
assert args.inputs == {"args": "args", "first": 2, "second": "s", "third": "t"}
elif args.name == "f3":
assert len(args.inputs) == 0
assert args.provider.endswith("f3provider_id")
elif args.name == "f4":
assert args.inputs == {"args": "hi"}
elif args.name == "f5":
assert args.inputs == {"first": 100, "second": "200", "third": "300"}
assert args.provider.endswith("f5provider_id")
elif args.name == "a1":
assert args.inputs == {"first": 10, "second": "asecond", "third": "athird"}
elif args.name == "a2":
assert args.inputs == {"first": 42, "second": "2nd", "third": "3rd"}
elif args.name == "a3":
assert args.inputs == {"args": "someargs", "first": 50, "second": "2", "third": "3"}
elif args.name == "a4":
assert args.inputs == {"first": 11, "second": "12", "third": "13"}
assert args.provider.endswith("a4provider_id")
return [args.name + '_id', args.inputs]
pulumi.runtime.set_mocks(MyMocks())

View file

@ -502,35 +502,35 @@ class NewBehaviorDict(pulumi.CustomResource):
class MyMocks(pulumi.runtime.Mocks):
def call(self, token, args, provider):
def call(self, args: pulumi.runtime.MockCallArgs):
return {}
def new_resource(self, type_, name, inputs, provider, id_):
if name == "o1":
assert inputs == {"serviceName": "hello"}
elif name == "o2":
assert inputs == {"serviceName": "hi", "nestedValue": {"fooBar": "foo bar", "someValue": 42}}
elif name == "o3":
assert inputs == {"serviceName": "bye", "nestedValue": {"fooBar": "bar foo", "someValue": 24}}
elif name == "o4":
def new_resource(self, args: pulumi.runtime.MockResourceArgs):
if args.name == "o1":
assert args.inputs == {"serviceName": "hello"}
elif args.name == "o2":
assert args.inputs == {"serviceName": "hi", "nestedValue": {"fooBar": "foo bar", "someValue": 42}}
elif args.name == "o3":
assert args.inputs == {"serviceName": "bye", "nestedValue": {"fooBar": "bar foo", "someValue": 24}}
elif args.name == "o4":
# The "service_name" key in the user-defined tags dict is translated to "serviceName"
# due to it being in the case tables.
assert inputs == {"serviceName": "sn", "tags": {"serviceName": "my-service"}}
elif name == "o5":
assert inputs == {"serviceName": "o5sn", "listOfNestedValues": [{"fooBar": "f", "someValue": 1}]}
elif name == "o6":
assert inputs == {"serviceName": "o6sn", "listOfNestedValues": [{"fooBar": "b", "someValue": 2}]}
elif name == "n1":
assert inputs == {"serviceName": "hi new", "nestedValue": {"fooBar": "noo nar", "someValue": 100}}
elif name == "n2":
assert inputs == {"serviceName": "hello new", "nestedValue": {"fooBar": "2", "someValue": 3}}
elif name == "n3":
assert args.inputs == {"serviceName": "sn", "tags": {"serviceName": "my-service"}}
elif args.name == "o5":
assert args.inputs == {"serviceName": "o5sn", "listOfNestedValues": [{"fooBar": "f", "someValue": 1}]}
elif args.name == "o6":
assert args.inputs == {"serviceName": "o6sn", "listOfNestedValues": [{"fooBar": "b", "someValue": 2}]}
elif args.name == "n1":
assert args.inputs == {"serviceName": "hi new", "nestedValue": {"fooBar": "noo nar", "someValue": 100}}
elif args.name == "n2":
assert args.inputs == {"serviceName": "hello new", "nestedValue": {"fooBar": "2", "someValue": 3}}
elif args.name == "n3":
# service_name correctly isn't translated, because the tags dict is a user-defined dict.
assert inputs == {"serviceName": "sn", "tags": {"service_name": "a-service"}}
elif name == "n4":
assert inputs == {"serviceName": "a", "items": {"foo": "bar"}, "keys": ["foo"], "values": ["bar"], "get": "bar"}
elif name == "d1":
assert inputs == {"serviceName": "b", "items": {"hello": "world"}, "keys": ["hello"], "values": ["world"], "get": "world"}
return [name + '_id', {**inputs, "somethingElse": "hehe"}]
assert args.inputs == {"serviceName": "sn", "tags": {"service_name": "a-service"}}
elif args.name == "n4":
assert args.inputs == {"serviceName": "a", "items": {"foo": "bar"}, "keys": ["foo"], "values": ["bar"], "get": "bar"}
elif args.name == "d1":
assert args.inputs == {"serviceName": "b", "items": {"hello": "world"}, "keys": ["hello"], "values": ["world"], "get": "world"}
return [args.name + '_id', {**args.inputs, "somethingElse": "hehe"}]
pulumi.runtime.set_mocks(MyMocks())