Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow component resources to inherit providers from component resources #10933

Merged
merged 1 commit into from Oct 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,4 @@
changes:
- type: fix
scope: cli/engine
description: Component Resources inherit thier parents providers map
61 changes: 41 additions & 20 deletions pkg/resource/deploy/source_eval.go
Expand Up @@ -471,17 +471,18 @@ func (d *defaultProviders) getDefaultProviderRef(req providers.ProviderRequest)
// resmon implements the pulumirpc.ResourceMonitor interface and acts as the gateway between a language runtime's
// evaluation of a program and the internal resource planning and deployment logic.
type resmon struct {
diagostics diag.Sink // logger for user-facing messages
providers ProviderSource // the provider source itself.
defaultProviders *defaultProviders // the default provider manager.
constructInfo plugin.ConstructInfo // information for construct and call calls.
regChan chan *registerResourceEvent // the channel to send resource registrations to.
regOutChan chan *registerResourceOutputsEvent // the channel to send resource output registrations to.
regReadChan chan *readResourceEvent // the channel to send resource reads to.
cancel chan bool // a channel that can cancel the server.
done chan error // a channel that resolves when the server completes.
disableResourceReferences bool // true if resource references are disabled.
disableOutputValues bool // true if output values are disabled.
diagostics diag.Sink // logger for user-facing messages
providers ProviderSource // the provider source itself.
componentResourceProviders map[resource.URN]map[string]string // which providers component resources used
defaultProviders *defaultProviders // the default provider manager.
constructInfo plugin.ConstructInfo // information for construct and call calls.
regChan chan *registerResourceEvent // the channel to send resource registrations to.
regOutChan chan *registerResourceOutputsEvent // the channel to send resource output registrations to.
regReadChan chan *readResourceEvent // the channel to send resource reads to.
cancel chan bool // a channel that can cancel the server.
done chan error // a channel that resolves when the server completes.
disableResourceReferences bool // true if resource references are disabled.
disableOutputValues bool // true if output values are disabled.
}

var _ SourceResourceMonitor = (*resmon)(nil)
Expand All @@ -506,15 +507,16 @@ func newResourceMonitor(src *evalSource, provs ProviderSource, regChan chan *reg

// New up an engine RPC server.
resmon := &resmon{
diagostics: src.plugctx.Diag,
providers: provs,
defaultProviders: d,
regChan: regChan,
regOutChan: regOutChan,
regReadChan: regReadChan,
cancel: cancel,
disableResourceReferences: opts.DisableResourceReferences,
disableOutputValues: opts.DisableOutputValues,
diagostics: src.plugctx.Diag,
providers: provs,
defaultProviders: d,
componentResourceProviders: map[resource.URN]map[string]string{},
regChan: regChan,
regOutChan: regOutChan,
regReadChan: regReadChan,
cancel: cancel,
disableResourceReferences: opts.DisableResourceReferences,
disableOutputValues: opts.DisableOutputValues,
}

// Fire up a gRPC server and start listening for incomings.
Expand Down Expand Up @@ -959,6 +961,20 @@ func (rm *resmon) RegisterResource(ctx context.Context,
t = tokens.Type(req.GetType())
}

// We handle updating the providers map to include the providers field of the parent if
// both the current resource and its parent is a component resource.
if parentsProviders, parentIsComponent := rm.componentResourceProviders[parent]; !custom &&
parent != "" && parentIsComponent {
for k, v := range parentsProviders {
if req.Providers == nil {
req.Providers = map[string]string{}
}
if _, ok := req.Providers[k]; !ok {
req.Providers[k] = v
}
}
}

label := fmt.Sprintf("ResourceMonitor.RegisterResource(%s,%s)", t, name)

var providerRef providers.Reference
Expand Down Expand Up @@ -1116,6 +1132,7 @@ func (rm *resmon) RegisterResource(ctx context.Context,

// If this is a remote component, fetch its provider and issue the construct call. Otherwise, register the resource.
var result *RegisterResult

var outputDeps map[string]*pulumirpc.RegisterResourceResponse_PropertyDependencies
if remote {
provider, ok := rm.providers.GetProvider(providerRef)
Expand Down Expand Up @@ -1177,6 +1194,10 @@ func (rm *resmon) RegisterResource(ctx context.Context,
}
}

if !custom && result != nil && result.State != nil && result.State.URN != "" {
rm.componentResourceProviders[result.State.URN] = req.GetProviders()
}

// Filter out partially-known values if the requestor does not support them.
outputs := result.State.Outputs

Expand Down
13 changes: 7 additions & 6 deletions sdk/go/pulumi/context.go
Expand Up @@ -632,7 +632,7 @@ func (ctx *Context) ReadResource(
}

// Prepare the inputs for an impending operation.
inputs, err = ctx.prepareResourceInputs(resource, props, t, options, res, false)
inputs, err = ctx.prepareResourceInputs(resource, props, t, options, res, false /* remote */, true /* custom */)
if err != nil {
return
}
Expand Down Expand Up @@ -817,7 +817,7 @@ func (ctx *Context) registerResource(
}()

// Prepare the inputs for an impending operation.
inputs, err = ctx.prepareResourceInputs(resource, props, t, options, resState, remote)
inputs, err = ctx.prepareResourceInputs(resource, props, t, options, resState, remote, custom)
if err != nil {
return
}
Expand Down Expand Up @@ -1251,11 +1251,11 @@ type resourceInputs struct {

// prepareResourceInputs prepares the inputs for a resource operation, shared between read and register.
func (ctx *Context) prepareResourceInputs(res Resource, props Input, t string, opts *resourceOptions,
state *resourceState, remote bool) (*resourceInputs, error) {
state *resourceState, remote, custom bool) (*resourceInputs, error) {

// Get the parent and dependency URNs from the options, in addition to the protection bit. If there wasn't an
// explicit parent, and a root stack resource exists, we will automatically parent to that.
resOpts, err := ctx.getOpts(res, t, state.provider, opts, remote)
resOpts, err := ctx.getOpts(res, t, state.provider, opts, remote, custom)
if err != nil {
return nil, fmt.Errorf("resolving options: %w", err)
}
Expand Down Expand Up @@ -1363,7 +1363,8 @@ type resourceOpts struct {

// getOpts returns a set of resource options from an array of them. This includes the parent URN, any dependency URNs,
// a boolean indicating whether the resource is to be protected, and the URN and ID of the resource's provider, if any.
func (ctx *Context) getOpts(res Resource, t string, provider ProviderResource, opts *resourceOptions, remote bool,
func (ctx *Context) getOpts(
res Resource, t string, provider ProviderResource, opts *resourceOptions, remote, custom bool,
) (resourceOpts, error) {

var importID ID
Expand Down Expand Up @@ -1409,7 +1410,7 @@ func (ctx *Context) getOpts(res Resource, t string, provider ProviderResource, o
}

var providerRefs map[string]string
if remote {
if remote || !custom {
if opts.Providers != nil {
providerRefs = make(map[string]string, len(opts.Providers))
for name, provider := range opts.Providers {
Expand Down
8 changes: 4 additions & 4 deletions sdk/go/pulumi/rpc_test.go
Expand Up @@ -873,25 +873,25 @@ func TestDependsOnComponent(t *testing.T) {
ctx, err := NewContext(context.Background(), RunInfo{})
assert.Nil(t, err)

registerResource := func(name string, res Resource, options ...ResourceOption) (Resource, []string) {
registerResource := func(name string, res Resource, custom bool, options ...ResourceOption) (Resource, []string) {
opts := merge(options...)
state := ctx.makeResourceState("", "", res, nil, nil, "", "", nil, nil)
state.resolve(ctx, nil, nil, name, "", &structpb.Struct{}, nil)

inputs, err := ctx.prepareResourceInputs(res, Map{}, "", opts, state, false)
inputs, err := ctx.prepareResourceInputs(res, Map{}, "", opts, state, false, custom)
require.NoError(t, err)

return res, inputs.deps
}

newResource := func(name string, options ...ResourceOption) (Resource, []string) {
var res testResource
return registerResource(name, &res, options...)
return registerResource(name, &res, true, options...)
}

newComponent := func(name string, options ...ResourceOption) (Resource, []string) {
var res simpleComponentResource
return registerResource(name, &res, options...)
return registerResource(name, &res, false, options...)
}

resA, _ := newResource("resA", nil)
Expand Down
2 changes: 1 addition & 1 deletion sdk/nodejs/runtime/resource.ts
Expand Up @@ -529,7 +529,7 @@ async function prepareResource(label: string, res: Resource, parent: Resource |
}

const providerRefs: Map<string, string> = new Map<string, string>();
if (remote) {
if (remote || !custom) {
const componentOpts = <ComponentResourceOptions>opts;
expandProviders(componentOpts);
// the <ProviderResource[]> casts are safe because expandProviders
Expand Down
2 changes: 1 addition & 1 deletion sdk/python/lib/pulumi/runtime/resource.py
Expand Up @@ -154,7 +154,7 @@ async def prepare_resource(
# For remote resources, merge any provider opts into a single dict, and then create a new dict with all of the
# resolved provider refs.
provider_refs: Dict[str, Optional[str]] = {}
if remote and opts is not None:
if (remote or not custom) and opts is not None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just "not custom" now isn't it? Resources are either LocalComponent, RemoteComponent, or Custom (remote=true, custom=true should be inexpressible). Also is a similar change not needed in the dotnet code?

Copy link
Member Author

@iwahbe iwahbe Oct 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find the equivalent logic. It is just a branch on custom:

if (custom)
{
var provider = customOpts?.Provider;
if (provider == null)
{
if (options.Parent != null)
{
// If no provider was given, but we have a parent, then inherit the
// provider from our parent.
options.Provider = options.Parent.GetProvider(type);
}
}
else
{
// If a provider was specified, add it to the providers map under this type's package so that
// any children of this resource inherit its provider.
var typeComponents = type.Split(":");
if (typeComponents.Length == 3)
{
var pkg = typeComponents[0];
this._providers = this._providers.SetItem(pkg, provider);
}
}
}
else
{
// Note: we checked above that at most one of options.provider or options.providers
// is set.
// If options.provider is set, treat that as if we were given a array of provider
// with that single value in it. Otherwise, take the array of providers, convert it
// to a map and combine with any providers we've already set from our parent.
var providerList = options.Provider != null
? new List<ProviderResource> { options.Provider }
: componentOpts?.Providers;
this._providers = this._providers.AddRange(ConvertToProvidersMap(providerList));
}

.NET should work as is.

providers = convert_providers(opts.provider, opts.providers)
for name, provider in providers.items():
# If we were given providers, wait for them to resolve and construct provider references from them.
Expand Down
6 changes: 6 additions & 0 deletions tests/integration/component_setup.sh
Expand Up @@ -44,6 +44,12 @@ setup_go() (
if [ -d "testcomponent-go" ]; then
cd testcomponent-go
go build -o "pulumi-resource-testcomponent$(go env GOEXE)"
cd ..
fi
if [ -d "testcomponent2-go" ]; then
cd testcomponent2-go
go build -o "pulumi-resource-secondtestcomponent$(go env GOEXE)"
cd ..
fi
)

Expand Down
56 changes: 33 additions & 23 deletions tests/integration/construct_component/testcomponent-go/main.go
Expand Up @@ -41,7 +41,7 @@ func NewResource(ctx *pulumi.Context, name string, echo pulumi.Input,
opts ...pulumi.ResourceOption) (*Resource, error) {
args := &ResourceArgs{Echo: echo}
var resource Resource
err := ctx.RegisterResource("testcomponent:index:Resource", name, args, &resource, opts...)
err := ctx.RegisterResource(providerName+":index:Resource", name, args, &resource, opts...)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see providerName declared in here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its declared on line 104.

const providerName = "testcomponent"

if err != nil {
return nil, err
}
Expand Down Expand Up @@ -76,7 +76,7 @@ func NewComponent(ctx *pulumi.Context, name string, args *ComponentArgs,
secret := conf.RequireSecret(secretKey)

component := &Component{}
err := ctx.RegisterComponentResource("testcomponent:index:Component", name, component, opts...)
err := ctx.RegisterComponentResource(providerName+":index:Component", name, component, opts...)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -115,28 +115,35 @@ func main() {
}
}

type testcomponentProvider struct {
type Provider struct {
host *provider.HostClient
name string
version string

expectResourceArg bool
}

func makeProvider(host *provider.HostClient, name, version string) (pulumirpc.ResourceProviderServer, error) {
return &testcomponentProvider{
return &Provider{
host: host,
name: name,
version: version,
}, nil
}

func (p *testcomponentProvider) Create(ctx context.Context,
func (p *Provider) Create(ctx context.Context,
req *pulumirpc.CreateRequest) (*pulumirpc.CreateResponse, error) {
urn := resource.URN(req.GetUrn())
typ := urn.Type()
if typ != "testcomponent:index:Resource" {
if typ != providerName+":index:Resource" {
return nil, fmt.Errorf("Unknown resource type '%s'", typ)
}

if s, ok := req.GetProperties().Fields["echo"].AsInterface().(string); ok &&
s == "checkExpected" && !p.expectResourceArg {
return nil, fmt.Errorf("did not receive configured provider")
}

id := currentID
currentID++

Expand All @@ -145,12 +152,12 @@ func (p *testcomponentProvider) Create(ctx context.Context,
}, nil
}

func (p *testcomponentProvider) Construct(ctx context.Context,
func (p *Provider) Construct(ctx context.Context,
req *pulumirpc.ConstructRequest) (*pulumirpc.ConstructResponse, error) {
return pulumiprovider.Construct(ctx, req, p.host.EngineConn(), func(ctx *pulumi.Context, typ, name string,
inputs pulumiprovider.ConstructInputs, options pulumi.ResourceOption) (*pulumiprovider.ConstructResult, error) {

if typ != "testcomponent:index:Component" {
if typ != providerName+":index:Component" {
return nil, fmt.Errorf("unknown resource type %s", typ)
}

Expand All @@ -168,82 +175,85 @@ func (p *testcomponentProvider) Construct(ctx context.Context,
})
}

func (p *testcomponentProvider) CheckConfig(ctx context.Context,
func (p *Provider) CheckConfig(ctx context.Context,
req *pulumirpc.CheckRequest) (*pulumirpc.CheckResponse, error) {
return &pulumirpc.CheckResponse{Inputs: req.GetNews()}, nil
}

func (p *testcomponentProvider) DiffConfig(ctx context.Context,
func (p *Provider) DiffConfig(ctx context.Context,
req *pulumirpc.DiffRequest) (*pulumirpc.DiffResponse, error) {
return &pulumirpc.DiffResponse{}, nil
}

func (p *testcomponentProvider) Configure(ctx context.Context,
func (p *Provider) Configure(ctx context.Context,
req *pulumirpc.ConfigureRequest) (*pulumirpc.ConfigureResponse, error) {
if _, ok := req.GetArgs().Fields["expectResourceArg"]; ok {
p.expectResourceArg = true
}
return &pulumirpc.ConfigureResponse{
AcceptSecrets: true,
SupportsPreview: true,
AcceptResources: true,
}, nil
}

func (p *testcomponentProvider) Invoke(ctx context.Context,
func (p *Provider) Invoke(ctx context.Context,
req *pulumirpc.InvokeRequest) (*pulumirpc.InvokeResponse, error) {
return nil, fmt.Errorf("Unknown Invoke token '%s'", req.GetTok())
}

func (p *testcomponentProvider) StreamInvoke(req *pulumirpc.InvokeRequest,
func (p *Provider) StreamInvoke(req *pulumirpc.InvokeRequest,
server pulumirpc.ResourceProvider_StreamInvokeServer) error {
return fmt.Errorf("Unknown StreamInvoke token '%s'", req.GetTok())
}

func (p *testcomponentProvider) Call(ctx context.Context,
func (p *Provider) Call(ctx context.Context,
req *pulumirpc.CallRequest) (*pulumirpc.CallResponse, error) {
return nil, fmt.Errorf("Unknown Call token '%s'", req.GetTok())
}

func (p *testcomponentProvider) Check(ctx context.Context,
func (p *Provider) Check(ctx context.Context,
req *pulumirpc.CheckRequest) (*pulumirpc.CheckResponse, error) {
return &pulumirpc.CheckResponse{Inputs: req.News, Failures: nil}, nil
}

func (p *testcomponentProvider) Diff(ctx context.Context, req *pulumirpc.DiffRequest) (*pulumirpc.DiffResponse, error) {
func (p *Provider) Diff(ctx context.Context, req *pulumirpc.DiffRequest) (*pulumirpc.DiffResponse, error) {
return &pulumirpc.DiffResponse{}, nil
}

func (p *testcomponentProvider) Read(ctx context.Context, req *pulumirpc.ReadRequest) (*pulumirpc.ReadResponse, error) {
func (p *Provider) Read(ctx context.Context, req *pulumirpc.ReadRequest) (*pulumirpc.ReadResponse, error) {
return &pulumirpc.ReadResponse{
Id: req.GetId(),
Properties: req.GetProperties(),
}, nil
}

func (p *testcomponentProvider) Update(ctx context.Context,
func (p *Provider) Update(ctx context.Context,
req *pulumirpc.UpdateRequest) (*pulumirpc.UpdateResponse, error) {
return &pulumirpc.UpdateResponse{
Properties: req.GetNews(),
}, nil
}

func (p *testcomponentProvider) Delete(ctx context.Context, req *pulumirpc.DeleteRequest) (*pbempty.Empty, error) {
func (p *Provider) Delete(ctx context.Context, req *pulumirpc.DeleteRequest) (*pbempty.Empty, error) {
return &pbempty.Empty{}, nil
}

func (p *testcomponentProvider) GetPluginInfo(context.Context, *pbempty.Empty) (*pulumirpc.PluginInfo, error) {
func (p *Provider) GetPluginInfo(context.Context, *pbempty.Empty) (*pulumirpc.PluginInfo, error) {
return &pulumirpc.PluginInfo{
Version: p.version,
}, nil
}

func (p *testcomponentProvider) Attach(ctx context.Context, req *pulumirpc.PluginAttach) (*pbempty.Empty, error) {
func (p *Provider) Attach(ctx context.Context, req *pulumirpc.PluginAttach) (*pbempty.Empty, error) {
return &pbempty.Empty{}, nil
}

func (p *testcomponentProvider) GetSchema(ctx context.Context,
func (p *Provider) GetSchema(ctx context.Context,
req *pulumirpc.GetSchemaRequest) (*pulumirpc.GetSchemaResponse, error) {
return &pulumirpc.GetSchemaResponse{}, nil
}

func (p *testcomponentProvider) Cancel(context.Context, *pbempty.Empty) (*pbempty.Empty, error) {
func (p *Provider) Cancel(context.Context, *pbempty.Empty) (*pbempty.Empty, error) {
return &pbempty.Empty{}, nil
}
@@ -0,0 +1,2 @@
pulumi-resource-secondtestcomponent
pulumi-resource-secondtestcomponent.exe