github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/resource/deploy/source_query.go (about)

     1  // Copyright 2016-2018, Pulumi Corporation.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package deploy
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"math"
    21  
    22  	pbempty "github.com/golang/protobuf/ptypes/empty"
    23  	opentracing "github.com/opentracing/opentracing-go"
    24  
    25  	"google.golang.org/grpc"
    26  
    27  	"github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers"
    28  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
    29  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource/config"
    30  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
    31  	"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
    32  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
    33  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
    34  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/result"
    35  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/rpcutil"
    36  	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
    37  	pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
    38  )
    39  
    40  // QuerySource is used to synchronously wait for a query result.
    41  type QuerySource interface {
    42  	Wait() result.Result
    43  }
    44  
    45  // NewQuerySource creates a `QuerySource` for some target runtime environment specified by
    46  // `runinfo`, and supported by language plugins provided in `plugctx`.
    47  func NewQuerySource(cancel context.Context, plugctx *plugin.Context, client BackendClient,
    48  	runinfo *EvalRunInfo, defaultProviderVersions map[tokens.Package]workspace.PluginSpec,
    49  	provs ProviderSource) (QuerySource, error) {
    50  
    51  	// Create a new builtin provider. This provider implements features such as `getStack`.
    52  	builtins := newBuiltinProvider(client, nil)
    53  
    54  	reg, err := providers.NewRegistry(plugctx.Host, nil, false, builtins)
    55  	if err != nil {
    56  		return nil, fmt.Errorf("failed to start resource monitor: %w", err)
    57  	}
    58  
    59  	// Allows queryResmon to communicate errors loading providers.
    60  	providerRegErrChan := make(chan result.Result)
    61  
    62  	// First, fire up a resource monitor that will disallow all resource operations, as well as
    63  	// service calls for things like resource ouptuts of state snapshots.
    64  	//
    65  	// NOTE: Using the queryResourceMonitor here is *VERY* important, as its job is to disallow
    66  	// resource operations in query mode!
    67  	mon, err := newQueryResourceMonitor(builtins, defaultProviderVersions, provs, reg, plugctx,
    68  		providerRegErrChan, opentracing.SpanFromContext(cancel), runinfo)
    69  	if err != nil {
    70  		return nil, fmt.Errorf("failed to start resource monitor: %w", err)
    71  	}
    72  
    73  	// Create a new iterator with appropriate channels, and gear up to go!
    74  	src := &querySource{
    75  		mon:                mon,
    76  		plugctx:            plugctx,
    77  		runinfo:            runinfo,
    78  		runLangPlugin:      runLangPlugin,
    79  		langPluginFinChan:  make(chan result.Result),
    80  		providerRegErrChan: make(chan result.Result),
    81  		cancel:             cancel,
    82  	}
    83  
    84  	// Now invoke Run in a goroutine.  All subsequent resource creation events will come in over the gRPC channel,
    85  	// and we will pump them through the channel.  If the Run call ultimately fails, we need to propagate the error.
    86  	src.forkRun()
    87  
    88  	// Finally, return the fresh iterator that the caller can use to take things from here.
    89  	return src, nil
    90  }
    91  
    92  type querySource struct {
    93  	mon                SourceResourceMonitor            // the resource monitor, per iterator.
    94  	plugctx            *plugin.Context                  // the plugin context.
    95  	runinfo            *EvalRunInfo                     // the directives to use when running the program.
    96  	runLangPlugin      func(*querySource) result.Result // runs the language plugin.
    97  	langPluginFinChan  chan result.Result               // communicates language plugin completion.
    98  	providerRegErrChan chan result.Result               // communicates errors loading providers
    99  	done               bool                             // set to true when the evaluation is done.
   100  	res                result.Result                    // result when the channel is finished.
   101  	cancel             context.Context
   102  }
   103  
   104  func (src *querySource) Close() error {
   105  	// Cancel the monitor and reclaim any associated resources.
   106  	src.done = true
   107  	return src.mon.Cancel()
   108  }
   109  
   110  func (src *querySource) Wait() result.Result {
   111  	// If we are done, quit.
   112  	if src.done {
   113  		return src.res
   114  	}
   115  
   116  	select {
   117  	case src.res = <-src.langPluginFinChan:
   118  		// Language plugin has exited. No need to call `Close`.
   119  		src.done = true
   120  		return src.res
   121  	case src.res = <-src.providerRegErrChan:
   122  		// Provider registration has failed.
   123  		src.Close()
   124  		return src.res
   125  	case <-src.cancel.Done():
   126  		src.Close()
   127  		return src.res
   128  	}
   129  }
   130  
   131  // forkRun evaluate the query program in a separate goroutine. Completion or cancellation will cause
   132  // `Wait` to stop blocking and return.
   133  func (src *querySource) forkRun() {
   134  	// Fire up the goroutine to make the RPC invocation against the language runtime.  As this executes, calls
   135  	// to queue things up in the resource channel will occur, and we will serve them concurrently.
   136  	go func() {
   137  		// Next, launch the language plugin. Communicate the error, if it exists, or nil if the
   138  		// program exited cleanly.
   139  		src.langPluginFinChan <- src.runLangPlugin(src)
   140  	}()
   141  }
   142  
   143  func runLangPlugin(src *querySource) result.Result {
   144  	rt := src.runinfo.Proj.Runtime.Name()
   145  	langhost, err := src.plugctx.Host.LanguageRuntime(rt)
   146  	if err != nil {
   147  		return result.FromError(fmt.Errorf("failed to launch language host %s: %w", rt, err))
   148  	}
   149  	contract.Assertf(langhost != nil, "expected non-nil language host %s", rt)
   150  
   151  	// Make sure to clean up before exiting.
   152  	defer contract.IgnoreClose(langhost)
   153  
   154  	// Decrypt the configuration.
   155  	var config map[config.Key]string
   156  	if src.runinfo.Target != nil {
   157  		config, err = src.runinfo.Target.Config.Decrypt(src.runinfo.Target.Decrypter)
   158  		if err != nil {
   159  			return result.FromError(err)
   160  		}
   161  	}
   162  
   163  	var name, organization string
   164  	if src.runinfo.Target != nil {
   165  		name = string(src.runinfo.Target.Name)
   166  		organization = string(src.runinfo.Target.Organization)
   167  	}
   168  
   169  	// Now run the actual program.
   170  	progerr, bail, err := langhost.Run(plugin.RunInfo{
   171  		MonitorAddress: src.mon.Address(),
   172  		Stack:          name,
   173  		Project:        string(src.runinfo.Proj.Name),
   174  		Pwd:            src.runinfo.Pwd,
   175  		Program:        src.runinfo.Program,
   176  		Args:           src.runinfo.Args,
   177  		Config:         config,
   178  		DryRun:         true,
   179  		QueryMode:      true,
   180  		Parallel:       math.MaxInt32,
   181  		Organization:   organization,
   182  	})
   183  
   184  	// Check if we were asked to Bail.  This a special random constant used for that
   185  	// purpose.
   186  	if err == nil && bail {
   187  		return result.Bail()
   188  	}
   189  
   190  	if err == nil && progerr != "" {
   191  		// If the program had an unhandled error; propagate it to the caller.
   192  		err = fmt.Errorf("an unhandled error occurred: %v", progerr)
   193  	}
   194  	return result.WrapIfNonNil(err)
   195  }
   196  
   197  // newQueryResourceMonitor creates a new resource monitor RPC server intended to be used in Pulumi's
   198  // "query mode".
   199  func newQueryResourceMonitor(
   200  	builtins *builtinProvider, defaultProviderInfo map[tokens.Package]workspace.PluginSpec,
   201  	provs ProviderSource, reg *providers.Registry, plugctx *plugin.Context,
   202  	providerRegErrChan chan<- result.Result, tracingSpan opentracing.Span, runinfo *EvalRunInfo) (*queryResmon, error) {
   203  
   204  	// Create our cancellation channel.
   205  	cancel := make(chan bool)
   206  
   207  	// Create channel for handling registrations.
   208  	providerRegChan := make(chan *registerResourceEvent)
   209  
   210  	// Create a new default provider manager.
   211  	d := &defaultProviders{
   212  		defaultProviderInfo: defaultProviderInfo,
   213  		providers:           make(map[string]providers.Reference),
   214  		config:              runinfo.Target,
   215  		requests:            make(chan defaultProviderRequest),
   216  		providerRegChan:     providerRegChan,
   217  		cancel:              cancel,
   218  	}
   219  
   220  	go func() {
   221  		for e := range providerRegChan {
   222  			urn := syntheticProviderURN(e.goal)
   223  
   224  			inputs, _, err := reg.Check(urn, resource.PropertyMap{}, e.goal.Properties, false, nil)
   225  			if err != nil {
   226  				providerRegErrChan <- result.FromError(err)
   227  				return
   228  			}
   229  			_, _, _, err = reg.Create(urn, inputs, 9999, false)
   230  			if err != nil {
   231  				providerRegErrChan <- result.FromError(err)
   232  				return
   233  			}
   234  
   235  			e.done <- &RegisterResult{State: &resource.State{
   236  				Type: e.goal.Type,
   237  				URN:  urn,
   238  			}}
   239  		}
   240  	}()
   241  
   242  	// New up an engine RPC server.
   243  	queryResmon := &queryResmon{
   244  		builtins:         builtins,
   245  		providers:        provs,
   246  		defaultProviders: d,
   247  		cancel:           cancel,
   248  		reg:              reg,
   249  	}
   250  
   251  	// Fire up a gRPC server and start listening for incomings.
   252  	handle, err := rpcutil.ServeWithOptions(rpcutil.ServeOptions{
   253  		Cancel: queryResmon.cancel,
   254  		Init: func(srv *grpc.Server) error {
   255  			pulumirpc.RegisterResourceMonitorServer(srv, queryResmon)
   256  			return nil
   257  		},
   258  		Options: rpcutil.OpenTracingServerInterceptorOptions(tracingSpan),
   259  	})
   260  	if err != nil {
   261  		return nil, err
   262  	}
   263  
   264  	monitorAddress := fmt.Sprintf("127.0.0.1:%d", handle.Port)
   265  
   266  	var config map[config.Key]string
   267  	if runinfo.Target != nil {
   268  		config, err = runinfo.Target.Config.Decrypt(runinfo.Target.Decrypter)
   269  		if err != nil {
   270  			return nil, err
   271  		}
   272  	}
   273  
   274  	var name string
   275  	if runinfo.Target != nil {
   276  		name = string(runinfo.Target.Name)
   277  	}
   278  
   279  	queryResmon.callInfo = plugin.CallInfo{
   280  		Project:        string(runinfo.Proj.Name),
   281  		Stack:          name,
   282  		Config:         config,
   283  		DryRun:         true,
   284  		Parallel:       math.MaxInt32,
   285  		MonitorAddress: monitorAddress,
   286  	}
   287  	queryResmon.addr = monitorAddress
   288  	queryResmon.done = handle.Done
   289  
   290  	go d.serve()
   291  
   292  	return queryResmon, nil
   293  }
   294  
   295  // queryResmon is a pulumirpc.ResourceMonitor that is meant to run in Pulumi's "query mode". It
   296  // performs two critical functions:
   297  //
   298  //  1. Disallows all resource operations. `queryResmon` intercepts all resource operations and
   299  //     returns an error instead of allowing them to proceed.
   300  //  2. Services requests for stack snapshots. This is primarily to allow us to allow queries across
   301  //     stack snapshots.
   302  type queryResmon struct {
   303  	builtins         *builtinProvider    // provides builtins such as `getStack`.
   304  	providers        ProviderSource      // the provider source itself.
   305  	defaultProviders *defaultProviders   // the default provider manager.
   306  	addr             string              // the address the host is listening on.
   307  	cancel           chan bool           // a channel that can cancel the server.
   308  	done             <-chan error        // a channel that resolves when the server completes.
   309  	reg              *providers.Registry // registry for resource providers.
   310  	callInfo         plugin.CallInfo     // information for call calls.
   311  }
   312  
   313  var _ SourceResourceMonitor = (*queryResmon)(nil)
   314  
   315  // Address returns the address at which the monitor's RPC server may be reached.
   316  func (rm *queryResmon) Address() string {
   317  	return rm.addr
   318  }
   319  
   320  // Cancel signals that the engine should be terminated, awaits its termination, and returns any
   321  // errors that result.
   322  func (rm *queryResmon) Cancel() error {
   323  	close(rm.cancel)
   324  	return <-rm.done
   325  }
   326  
   327  // Invoke performs an invocation of a member located in a resource provider.
   328  func (rm *queryResmon) Invoke(
   329  	ctx context.Context, req *pulumirpc.ResourceInvokeRequest) (*pulumirpc.InvokeResponse, error) {
   330  
   331  	tok := tokens.ModuleMember(req.GetTok())
   332  	label := fmt.Sprintf("QueryResourceMonitor.Invoke(%s)", tok)
   333  
   334  	providerReq, err := parseProviderRequest(tok.Package(), req.GetVersion(), req.GetPluginDownloadURL())
   335  	if err != nil {
   336  		return nil, err
   337  	}
   338  	prov, err := getProviderFromSource(rm.reg, rm.defaultProviders, providerReq, req.GetProvider(), tok)
   339  	if err != nil {
   340  		return nil, err
   341  	}
   342  
   343  	args, err := plugin.UnmarshalProperties(
   344  		req.GetArgs(), plugin.MarshalOptions{
   345  			Label:         label,
   346  			KeepUnknowns:  true,
   347  			KeepSecrets:   true,
   348  			KeepResources: true,
   349  		})
   350  	if err != nil {
   351  		return nil, fmt.Errorf("failed to unmarshal %v args: %w", tok, err)
   352  	}
   353  
   354  	// Do the invoke and then return the arguments.
   355  	logging.V(5).Infof("QueryResourceMonitor.Invoke received: tok=%v #args=%v", tok, len(args))
   356  	ret, failures, err := prov.Invoke(tok, args)
   357  	if err != nil {
   358  		return nil, fmt.Errorf("invocation of %v returned an error: %w", tok, err)
   359  	}
   360  	mret, err := plugin.MarshalProperties(ret, plugin.MarshalOptions{
   361  		Label:         label,
   362  		KeepUnknowns:  true,
   363  		KeepResources: req.GetAcceptResources(),
   364  	})
   365  	if err != nil {
   366  		return nil, fmt.Errorf("failed to marshal return: %w", err)
   367  	}
   368  
   369  	var chkfails []*pulumirpc.CheckFailure
   370  	for _, failure := range failures {
   371  		chkfails = append(chkfails, &pulumirpc.CheckFailure{
   372  			Property: string(failure.Property),
   373  			Reason:   failure.Reason,
   374  		})
   375  	}
   376  
   377  	return &pulumirpc.InvokeResponse{Return: mret, Failures: chkfails}, nil
   378  }
   379  
   380  func (rm *queryResmon) StreamInvoke(
   381  	req *pulumirpc.ResourceInvokeRequest, stream pulumirpc.ResourceMonitor_StreamInvokeServer) error {
   382  
   383  	tok := tokens.ModuleMember(req.GetTok())
   384  	label := fmt.Sprintf("QueryResourceMonitor.StreamInvoke(%s)", tok)
   385  
   386  	providerReq, err := parseProviderRequest(tok.Package(), req.GetVersion(), req.GetPluginDownloadURL())
   387  	if err != nil {
   388  		return err
   389  	}
   390  	prov, err := getProviderFromSource(rm.reg, rm.defaultProviders, providerReq, req.GetProvider(), tok)
   391  	if err != nil {
   392  		return err
   393  	}
   394  
   395  	args, err := plugin.UnmarshalProperties(
   396  		req.GetArgs(), plugin.MarshalOptions{Label: label, KeepUnknowns: true})
   397  	if err != nil {
   398  		return fmt.Errorf("failed to unmarshal %v args: %w", tok, err)
   399  	}
   400  
   401  	// Synchronously do the StreamInvoke and then return the arguments. This will block until the
   402  	// streaming operation completes!
   403  	logging.V(5).Infof("QueryResourceMonitor.StreamInvoke received: tok=%v #args=%v", tok, len(args))
   404  	failures, err := prov.StreamInvoke(tok, args, func(event resource.PropertyMap) error {
   405  		mret, err := plugin.MarshalProperties(event, plugin.MarshalOptions{Label: label,
   406  			KeepUnknowns:  true,
   407  			KeepResources: req.GetAcceptResources(),
   408  		})
   409  		if err != nil {
   410  			return fmt.Errorf("failed to marshal return: %w", err)
   411  		}
   412  
   413  		return stream.Send(&pulumirpc.InvokeResponse{Return: mret})
   414  	})
   415  	if err != nil {
   416  		return fmt.Errorf("streaming invocation of %v returned an error: %w", tok, err)
   417  	}
   418  
   419  	var chkfails []*pulumirpc.CheckFailure
   420  	for _, failure := range failures {
   421  		chkfails = append(chkfails, &pulumirpc.CheckFailure{
   422  			Property: string(failure.Property),
   423  			Reason:   failure.Reason,
   424  		})
   425  	}
   426  
   427  	if len(chkfails) > 0 {
   428  		return stream.Send(&pulumirpc.InvokeResponse{Failures: chkfails})
   429  	}
   430  	return nil
   431  }
   432  
   433  // Call dynamically executes a method in the provider associated with a component resource.
   434  func (rm *queryResmon) Call(ctx context.Context, req *pulumirpc.CallRequest) (*pulumirpc.CallResponse, error) {
   435  	tok := tokens.ModuleMember(req.GetTok())
   436  	label := fmt.Sprintf("QueryResourceMonitor.Call(%s)", tok)
   437  
   438  	providerReq, err := parseProviderRequest(tok.Package(), req.GetVersion(), req.GetPluginDownloadURL())
   439  	if err != nil {
   440  		return nil, err
   441  	}
   442  	prov, err := getProviderFromSource(rm.reg, rm.defaultProviders, providerReq, req.GetProvider(), tok)
   443  	if err != nil {
   444  		return nil, err
   445  	}
   446  
   447  	args, err := plugin.UnmarshalProperties(
   448  		req.GetArgs(), plugin.MarshalOptions{
   449  			Label:         label,
   450  			KeepUnknowns:  true,
   451  			KeepSecrets:   true,
   452  			KeepResources: true,
   453  		})
   454  	if err != nil {
   455  		return nil, fmt.Errorf("failed to unmarshal %v args: %w", tok, err)
   456  	}
   457  
   458  	argDependencies := map[resource.PropertyKey][]resource.URN{}
   459  	for name, deps := range req.GetArgDependencies() {
   460  		urns := make([]resource.URN, len(deps.Urns))
   461  		for i, urn := range deps.Urns {
   462  			urns[i] = resource.URN(urn)
   463  		}
   464  		argDependencies[resource.PropertyKey(name)] = urns
   465  	}
   466  	options := plugin.CallOptions{
   467  		ArgDependencies: argDependencies,
   468  	}
   469  
   470  	// Do the call and then return the arguments.
   471  	logging.V(5).Infof(
   472  		"QueryResourceMonitor.Call received: tok=%v #args=%v #info=%v #options=%v", tok, len(args), rm.callInfo, options)
   473  	ret, err := prov.Call(tok, args, rm.callInfo, options)
   474  	if err != nil {
   475  		return nil, fmt.Errorf("call of %v returned an error: %w", tok, err)
   476  	}
   477  	mret, err := plugin.MarshalProperties(ret.Return, plugin.MarshalOptions{
   478  		Label:         label,
   479  		KeepUnknowns:  true,
   480  		KeepSecrets:   true,
   481  		KeepResources: true,
   482  	})
   483  	if err != nil {
   484  		return nil, fmt.Errorf("failed to marshal return: %w", err)
   485  	}
   486  
   487  	returnDependencies := map[string]*pulumirpc.CallResponse_ReturnDependencies{}
   488  	for name, deps := range ret.ReturnDependencies {
   489  		urns := make([]string, len(deps))
   490  		for i, urn := range deps {
   491  			urns[i] = string(urn)
   492  		}
   493  		returnDependencies[string(name)] = &pulumirpc.CallResponse_ReturnDependencies{Urns: urns}
   494  	}
   495  
   496  	var chkfails []*pulumirpc.CheckFailure
   497  	for _, failure := range ret.Failures {
   498  		chkfails = append(chkfails, &pulumirpc.CheckFailure{
   499  			Property: string(failure.Property),
   500  			Reason:   failure.Reason,
   501  		})
   502  	}
   503  
   504  	return &pulumirpc.CallResponse{Return: mret, ReturnDependencies: returnDependencies, Failures: chkfails}, nil
   505  }
   506  
   507  // ReadResource reads the current state associated with a resource from its provider plugin.
   508  func (rm *queryResmon) ReadResource(ctx context.Context,
   509  	req *pulumirpc.ReadResourceRequest) (*pulumirpc.ReadResourceResponse, error) {
   510  
   511  	return nil, fmt.Errorf("Query mode does not support reading resources")
   512  }
   513  
   514  // RegisterResource is invoked by a language process when a new resource has been allocated.
   515  func (rm *queryResmon) RegisterResource(ctx context.Context,
   516  	req *pulumirpc.RegisterResourceRequest) (*pulumirpc.RegisterResourceResponse, error) {
   517  
   518  	return nil, fmt.Errorf("Query mode does not support creating, updating, or deleting resources")
   519  }
   520  
   521  // RegisterResourceOutputs records some new output properties for a resource that have arrived after its initial
   522  // provisioning.  These will make their way into the eventual checkpoint state file for that resource.
   523  func (rm *queryResmon) RegisterResourceOutputs(ctx context.Context,
   524  	req *pulumirpc.RegisterResourceOutputsRequest) (*pbempty.Empty, error) {
   525  
   526  	return nil, fmt.Errorf("Query mode does not support registering resource operations")
   527  }
   528  
   529  // SupportsFeature the query resmon is able to have secrets passed to it, which may be arguments to invoke calls.
   530  func (rm *queryResmon) SupportsFeature(ctx context.Context,
   531  	req *pulumirpc.SupportsFeatureRequest) (*pulumirpc.SupportsFeatureResponse, error) {
   532  
   533  	hasSupport := false
   534  	return &pulumirpc.SupportsFeatureResponse{
   535  		HasSupport: hasSupport,
   536  	}, nil
   537  }
   538  
   539  // syntheticProviderURN will create a "fake" URN for a resource provider in query mode. Query mode
   540  // has no stack, no project, and no parent, so there is otherwise no way to generate a principled
   541  // URN.
   542  func syntheticProviderURN(goal *resource.Goal) resource.URN {
   543  	return resource.NewURN(
   544  		"query-stack", "query-project", "parent-type", goal.Type, goal.Name)
   545  }