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 }