Skip to content

Commit

Permalink
Merge #10933
Browse files Browse the repository at this point in the history
10933: Allow component resources to inherit `providers` from component resources r=iwahbe a=iwahbe

<!--- 
Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation.
-->

# Description

<!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. -->

Fixes #10640 

## Checklist

<!--- Please provide details if the checkbox below is to be left unchecked. -->
- [x] I have added tests that prove my fix is effective or that my feature works
<!--- 
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the Pulumi Service,
then the service should honor older versions of the CLI where this change would not exist.
You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Service API version
  <!-- `@Pulumi` employees: If yes, you must submit corresponding changes in the service repo. -->


Co-authored-by: Ian Wahbe <ian@wahbe.com>
  • Loading branch information
bors[bot] and iwahbe committed Oct 20, 2022
2 parents c6e7f5b + e387ba8 commit a5cac1d
Show file tree
Hide file tree
Showing 15 changed files with 960 additions and 58 deletions.
@@ -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:
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...)
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

0 comments on commit a5cac1d

Please sign in to comment.