github.com/pulumi/pulumi/sdk/v3@v3.108.1/nodejs/runtime/resource.ts (about) 1 // Copyright 2016-2021, 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 import * as grpc from "@grpc/grpc-js"; 16 import * as query from "@pulumi/query"; 17 import * as log from "../log"; 18 import * as utils from "../utils"; 19 20 import { getAllResources, Input, Inputs, Output, output } from "../output"; 21 import { ResolvedResource } from "../queryable"; 22 import { 23 Alias, 24 allAliases, 25 ComponentResource, 26 ComponentResourceOptions, 27 createUrn, 28 CustomResource, 29 CustomResourceOptions, 30 expandProviders, 31 ID, 32 ProviderResource, 33 Resource, 34 ResourceOptions, 35 URN, 36 } from "../resource"; 37 import { debuggablePromise, debugPromiseLeaks } from "./debuggable"; 38 import { invoke } from "./invoke"; 39 import { getStore } from "./state"; 40 41 import { isGrpcError } from "../errors"; 42 import { 43 deserializeProperties, 44 deserializeProperty, 45 OutputResolvers, 46 resolveProperties, 47 serializeProperties, 48 serializeProperty, 49 serializeResourceProperties, 50 suppressUnhandledGrpcRejections, 51 transferProperties, 52 } from "./rpc"; 53 import { 54 excessiveDebugOutput, 55 getMonitor, 56 getStack, 57 isDryRun, 58 isLegacyApplyEnabled, 59 rpcKeepAlive, 60 serialize, 61 terminateRpcs, 62 } from "./settings"; 63 64 import * as gempty from "google-protobuf/google/protobuf/empty_pb"; 65 import * as gstruct from "google-protobuf/google/protobuf/struct_pb"; 66 import * as aliasproto from "../proto/alias_pb"; 67 import * as provproto from "../proto/provider_pb"; 68 import * as resproto from "../proto/resource_pb"; 69 import * as sourceproto from "../proto/source_pb"; 70 71 export interface SourcePosition { 72 uri: string; 73 line: number; 74 column: number; 75 } 76 77 function marshalSourcePosition(sourcePosition?: SourcePosition) { 78 if (sourcePosition === undefined) { 79 return undefined; 80 } 81 const pos = new sourceproto.SourcePosition(); 82 pos.setUri(sourcePosition.uri); 83 pos.setLine(sourcePosition.line); 84 pos.setColumn(sourcePosition.column); 85 return pos; 86 } 87 88 interface ResourceResolverOperation { 89 // A resolver for a resource's URN. 90 resolveURN: (urn: URN, err?: Error) => void; 91 // A resolver for a resource's ID (for custom resources only). 92 resolveID: ((v: ID, performApply: boolean, err?: Error) => void) | undefined; 93 // A collection of resolvers for a resource's properties. 94 resolvers: OutputResolvers; 95 // A parent URN, fully resolved, if any. 96 parentURN: URN | undefined; 97 // A provider reference, fully resolved, if any. 98 providerRef: string | undefined; 99 // A map of provider references, fully resolved, if any. 100 providerRefs: Map<string, string>; 101 // All serialized properties, fully awaited, serialized, and ready to go. 102 serializedProps: Record<string, any>; 103 // A set of URNs that this resource is directly dependent upon. These will all be URNs of 104 // custom resources, not component resources. 105 allDirectDependencyURNs: Set<URN>; 106 // Set of URNs that this resource is directly dependent upon, keyed by the property that causes 107 // the dependency. All urns in this map must exist in [allDirectDependencyURNs]. These will 108 // all be URNs of custom resources, not component resources. 109 propertyToDirectDependencyURNs: Map<string, Set<URN>>; 110 // A list of aliases applied to this resource. 111 aliases: (Alias | URN)[]; 112 // An ID to import, if any. 113 import: ID | undefined; 114 // Any important feature support from the monitor. 115 monitorSupportsStructuredAliases: boolean; 116 // If set, the providers Delete method will not be called for this resource 117 // if specified is being deleted as well. 118 deletedWithURN: URN | undefined; 119 } 120 121 /** 122 * Get an existing resource's state from the engine. 123 */ 124 export function getResource( 125 res: Resource, 126 parent: Resource | undefined, 127 props: Inputs, 128 custom: boolean, 129 urn: string, 130 ): void { 131 // Extract the resource type from the URN. 132 const urnParts = urn.split("::"); 133 const qualifiedType = urnParts[2]; 134 const urnName = urnParts[3]; 135 const type = qualifiedType.split("$").pop()!; 136 137 const label = `resource:urn=${urn}`; 138 log.debug(`Getting resource: urn=${urn}`); 139 140 const monitor = getMonitor(); 141 const resopAsync = prepareResource(label, res, parent, custom, false, props, {}); 142 143 const preallocError = new Error(); 144 debuggablePromise( 145 resopAsync.then(async (resop) => { 146 const inputs = await serializeProperties(label, { urn }); 147 148 const req = new resproto.ResourceInvokeRequest(); 149 req.setTok("pulumi:pulumi:getResource"); 150 req.setArgs(gstruct.Struct.fromJavaScript(inputs)); 151 req.setProvider(""); 152 req.setVersion(""); 153 req.setAcceptresources(!utils.disableResourceReferences); 154 155 // Now run the operation, serializing the invocation if necessary. 156 const opLabel = `monitor.getResource(${label})`; 157 runAsyncResourceOp(opLabel, async () => { 158 let resp: any = {}; 159 let err: Error | undefined; 160 try { 161 if (monitor) { 162 resp = await debuggablePromise( 163 new Promise((resolve, reject) => 164 monitor.invoke( 165 req, 166 ( 167 rpcError: grpc.ServiceError | null, 168 innerResponse: provproto.InvokeResponse | undefined, 169 ) => { 170 log.debug( 171 `getResource Invoke RPC finished: err: ${rpcError}, resp: ${innerResponse}`, 172 ); 173 if (rpcError) { 174 if ( 175 rpcError.code === grpc.status.UNAVAILABLE || 176 rpcError.code === grpc.status.CANCELLED 177 ) { 178 err = rpcError; 179 terminateRpcs(); 180 rpcError.message = "Resource monitor is terminating"; 181 (<any>preallocError).code = rpcError.code; 182 } 183 184 preallocError.message = `failed to get resource:urn=${urn}: ${rpcError.message}`; 185 reject(new Error(rpcError.details)); 186 } else { 187 resolve(innerResponse); 188 } 189 }, 190 ), 191 ), 192 opLabel, 193 ); 194 195 // If the invoke failed, raise an error 196 const failures = resp.getFailuresList(); 197 if (failures?.length) { 198 let reasons = ""; 199 for (let i = 0; i < failures.length; i++) { 200 if (reasons !== "") { 201 reasons += "; "; 202 } 203 reasons += `${failures[i].getReason()} (${failures[i].getProperty()})`; 204 } 205 throw new Error(`getResource Invoke failed: ${reasons}`); 206 } 207 208 // Otherwise, return the response. 209 const m = resp.getReturn().getFieldsMap(); 210 resp = { 211 urn: m.get("urn").toJavaScript(), 212 id: m.get("id").toJavaScript() || undefined, 213 state: m.get("state").getStructValue(), 214 }; 215 } 216 } catch (e) { 217 err = e; 218 resp = { 219 urn: "", 220 id: undefined, 221 state: undefined, 222 }; 223 } 224 225 resop.resolveURN(resp.urn, err); 226 227 // Note: 'id || undefined' is intentional. We intentionally collapse falsy values to 228 // undefined so that later parts of our system don't have to deal with values like 'null'. 229 if (resop.resolveID) { 230 const id = resp.id || undefined; 231 resop.resolveID(id, id !== undefined, err); 232 } 233 234 await resolveOutputs(res, type, urnName, props, resp.state, {}, resop.resolvers, err); 235 }); 236 }), 237 label, 238 ); 239 } 240 241 /** 242 * Reads an existing custom resource's state from the resource monitor. Note that resources read in this way 243 * will not be part of the resulting stack's state, as they are presumed to belong to another. 244 */ 245 export function readResource( 246 res: Resource, 247 parent: Resource | undefined, 248 t: string, 249 name: string, 250 props: Inputs, 251 opts: ResourceOptions, 252 sourcePosition?: SourcePosition, 253 ): void { 254 if (!opts.id) { 255 throw new Error("Cannot read resource whose options are lacking an ID value"); 256 } 257 const id: Promise<Input<ID>> = output(opts.id).promise(true); 258 259 const label = `resource:${name}[${t}]#...`; 260 log.debug(`Reading resource: t=${t}, name=${name}`); 261 262 const monitor = getMonitor(); 263 const resopAsync = prepareResource(label, res, parent, true, false, props, opts); 264 265 const preallocError = new Error(); 266 debuggablePromise( 267 resopAsync.then(async (resop) => { 268 const resolvedID = await serializeProperty(label, await id, new Set(), { keepOutputValues: false }); 269 log.debug( 270 `ReadResource RPC prepared: id=${resolvedID}, t=${t}, name=${name}` + 271 (excessiveDebugOutput ? `, obj=${JSON.stringify(resop.serializedProps)}` : ``), 272 ); 273 274 // Create a resource request and do the RPC. 275 const req = new resproto.ReadResourceRequest(); 276 req.setType(t); 277 req.setName(name); 278 req.setId(resolvedID); 279 req.setParent(resop.parentURN || ""); 280 req.setProvider(resop.providerRef || ""); 281 req.setProperties(gstruct.Struct.fromJavaScript(resop.serializedProps)); 282 req.setDependenciesList(Array.from(resop.allDirectDependencyURNs)); 283 req.setVersion(opts.version || ""); 284 req.setPlugindownloadurl(opts.pluginDownloadURL || ""); 285 req.setAcceptsecrets(true); 286 req.setAcceptresources(!utils.disableResourceReferences); 287 req.setAdditionalsecretoutputsList((<any>opts).additionalSecretOutputs || []); 288 req.setSourceposition(marshalSourcePosition(sourcePosition)); 289 290 // Now run the operation, serializing the invocation if necessary. 291 const opLabel = `monitor.readResource(${label})`; 292 runAsyncResourceOp(opLabel, async () => { 293 let resp: any = {}; 294 let err: Error | undefined; 295 try { 296 if (monitor) { 297 // If we're attached to the engine, make an RPC call and wait for it to resolve. 298 resp = await debuggablePromise( 299 new Promise((resolve, reject) => 300 monitor.readResource( 301 req, 302 ( 303 rpcError: grpc.ServiceError | null, 304 innerResponse: resproto.ReadResourceResponse | undefined, 305 ) => { 306 log.debug( 307 `ReadResource RPC finished: ${label}; err: ${rpcError}, resp: ${innerResponse}`, 308 ); 309 if (rpcError) { 310 if ( 311 rpcError.code === grpc.status.UNAVAILABLE || 312 rpcError.code === grpc.status.CANCELLED 313 ) { 314 err = rpcError; 315 terminateRpcs(); 316 rpcError.message = "Resource monitor is terminating"; 317 (<any>preallocError).code = rpcError.code; 318 } 319 320 preallocError.message = `failed to read resource #${resolvedID} '${name}' [${t}]: ${rpcError.message}`; 321 reject(preallocError); 322 } else { 323 resolve(innerResponse); 324 } 325 }, 326 ), 327 ), 328 opLabel, 329 ); 330 } else { 331 // If we aren't attached to the engine, in test mode, mock up a fake response for testing purposes. 332 const mockurn = await createUrn(req.getName(), req.getType(), req.getParent()).promise(); 333 resp = { 334 getUrn: () => mockurn, 335 getProperties: () => req.getProperties(), 336 }; 337 } 338 } catch (e) { 339 err = e; 340 resp = { 341 getUrn: () => "", 342 getProperties: () => undefined, 343 }; 344 } 345 346 // Now resolve everything: the URN, the ID (supplied as input), and the output properties. 347 resop.resolveURN(resp.getUrn(), err); 348 resop.resolveID!(resolvedID, resolvedID !== undefined, err); 349 await resolveOutputs(res, t, name, props, resp.getProperties(), {}, resop.resolvers, err); 350 }); 351 }), 352 label, 353 ); 354 } 355 356 function getParentURN(parent?: Resource | Input<string>) { 357 if (Resource.isInstance(parent)) { 358 return parent.urn; 359 } 360 return output(parent); 361 } 362 363 function mapAliasesForRequest(aliases: (URN | Alias)[] | undefined, parentURN?: URN) { 364 if (aliases === undefined) { 365 return []; 366 } 367 368 return Promise.all( 369 aliases.map(async (a) => { 370 const newAlias = new aliasproto.Alias(); 371 if (typeof a === "string") { 372 newAlias.setUrn(a); 373 } else { 374 const newAliasSpec = new aliasproto.Alias.Spec(); 375 const name = a.name === undefined ? undefined : await output(a.name).promise(); 376 const type = a.type === undefined ? undefined : await output(a.type).promise(); 377 const stack = a.stack === undefined ? undefined : await output(a.stack).promise(); 378 const project = a.project === undefined ? undefined : await output(a.project).promise(); 379 380 newAliasSpec.setName(name || ""); 381 newAliasSpec.setType(type || ""); 382 newAliasSpec.setStack(stack || ""); 383 newAliasSpec.setProject(project || ""); 384 if (a.hasOwnProperty("parent")) { 385 if (a.parent === undefined) { 386 newAliasSpec.setNoparent(true); 387 } else { 388 const aliasParentUrn = getParentURN(a.parent); 389 const urn = await aliasParentUrn.promise(); 390 if (urn !== undefined) { 391 newAliasSpec.setParenturn(urn); 392 } 393 } 394 } else if (parentURN) { 395 // If a parent isn't specified for the alias and the resource has a parent, 396 // pass along the resource's parent in the alias spec. 397 // It shouldn't be necessary to do this because the engine should fill-in the 398 // resource's parent if one wasn't specified for the alias. 399 // However, some older versions of the CLI don't do this correctly, and this 400 // SDK has always passed along the parent in this way, so we continue doing it 401 // to maintain compatibility with these versions of the CLI. 402 newAliasSpec.setParenturn(parentURN); 403 } 404 newAlias.setSpec(newAliasSpec); 405 } 406 return newAlias; 407 }), 408 ); 409 } 410 411 /** 412 * registerResource registers a new resource object with a given type t and name. It returns the auto-generated 413 * URN and the ID that will resolve after the deployment has completed. All properties will be initialized to property 414 * objects that the registration operation will resolve at the right time (or remain unresolved for deployments). 415 */ 416 export function registerResource( 417 res: Resource, 418 parent: Resource | undefined, 419 t: string, 420 name: string, 421 custom: boolean, 422 remote: boolean, 423 newDependency: (urn: URN) => Resource, 424 props: Inputs, 425 opts: ResourceOptions, 426 sourcePosition?: SourcePosition, 427 ): void { 428 const label = `resource:${name}[${t}]`; 429 log.debug(`Registering resource: t=${t}, name=${name}, custom=${custom}, remote=${remote}`); 430 431 const monitor = getMonitor(); 432 const resopAsync = prepareResource(label, res, parent, custom, remote, props, opts, t, name); 433 434 // In order to present a useful stack trace if an error does occur, we preallocate potential 435 // errors here. V8 captures a stack trace at the moment an Error is created and this stack 436 // trace will lead directly to user code. Throwing in `runAsyncResourceOp` results in an Error 437 // with a non-useful stack trace. 438 const preallocError = new Error(); 439 debuggablePromise( 440 resopAsync.then(async (resop) => { 441 log.debug( 442 `RegisterResource RPC prepared: t=${t}, name=${name}` + 443 (excessiveDebugOutput ? `, obj=${JSON.stringify(resop.serializedProps)}` : ``), 444 ); 445 446 const req = new resproto.RegisterResourceRequest(); 447 req.setType(t); 448 req.setName(name); 449 req.setParent(resop.parentURN || ""); 450 req.setCustom(custom); 451 req.setObject(gstruct.Struct.fromJavaScript(resop.serializedProps)); 452 req.setProtect(opts.protect || false); 453 req.setProvider(resop.providerRef || ""); 454 req.setDependenciesList(Array.from(resop.allDirectDependencyURNs)); 455 req.setDeletebeforereplace((<any>opts).deleteBeforeReplace || false); 456 req.setDeletebeforereplacedefined((<any>opts).deleteBeforeReplace !== undefined); 457 req.setIgnorechangesList(opts.ignoreChanges || []); 458 req.setVersion(opts.version || ""); 459 req.setAcceptsecrets(true); 460 req.setAcceptresources(!utils.disableResourceReferences); 461 req.setAdditionalsecretoutputsList((<any>opts).additionalSecretOutputs || []); 462 if (resop.monitorSupportsStructuredAliases) { 463 const aliasesList = await mapAliasesForRequest(resop.aliases, resop.parentURN); 464 req.setAliasesList(aliasesList); 465 } else { 466 const urns = new Array<string>(); 467 resop.aliases.forEach((v) => { 468 if (typeof v === "string") { 469 urns.push(v); 470 } 471 }); 472 req.setAliasurnsList(urns); 473 } 474 req.setImportid(resop.import || ""); 475 req.setSupportspartialvalues(true); 476 req.setRemote(remote); 477 req.setReplaceonchangesList(opts.replaceOnChanges || []); 478 req.setPlugindownloadurl(opts.pluginDownloadURL || ""); 479 req.setRetainondelete(opts.retainOnDelete || false); 480 req.setDeletedwith(resop.deletedWithURN || ""); 481 req.setAliasspecs(true); 482 req.setSourceposition(marshalSourcePosition(sourcePosition)); 483 484 if (resop.deletedWithURN && !getStore().supportsDeletedWith) { 485 throw new Error( 486 "The Pulumi CLI does not support the DeletedWith option. Please update the Pulumi CLI.", 487 ); 488 } 489 490 const customTimeouts = new resproto.RegisterResourceRequest.CustomTimeouts(); 491 if (opts.customTimeouts != null) { 492 customTimeouts.setCreate(opts.customTimeouts.create || ""); 493 customTimeouts.setUpdate(opts.customTimeouts.update || ""); 494 customTimeouts.setDelete(opts.customTimeouts.delete || ""); 495 } 496 req.setCustomtimeouts(customTimeouts); 497 498 const propertyDependencies = req.getPropertydependenciesMap(); 499 for (const [key, resourceURNs] of resop.propertyToDirectDependencyURNs) { 500 const deps = new resproto.RegisterResourceRequest.PropertyDependencies(); 501 deps.setUrnsList(Array.from(resourceURNs)); 502 propertyDependencies.set(key, deps); 503 } 504 505 const providerRefs = req.getProvidersMap(); 506 for (const [key, ref] of resop.providerRefs) { 507 providerRefs.set(key, ref); 508 } 509 510 // Now run the operation, serializing the invocation if necessary. 511 const opLabel = `monitor.registerResource(${label})`; 512 runAsyncResourceOp(opLabel, async () => { 513 let resp: any = {}; 514 let err: Error | undefined; 515 try { 516 if (monitor) { 517 // If we're running with an attachment to the engine, perform the operation. 518 resp = await debuggablePromise( 519 new Promise((resolve, reject) => 520 monitor.registerResource( 521 req, 522 ( 523 rpcErr: grpc.ServiceError | null, 524 innerResponse: resproto.RegisterResourceResponse | undefined, 525 ) => { 526 if (rpcErr) { 527 err = rpcErr; 528 // If the monitor is unavailable, it is in the process of shutting down or has already 529 // shut down. Don't emit an error and don't do any more RPCs, just exit. 530 if ( 531 rpcErr.code === grpc.status.UNAVAILABLE || 532 rpcErr.code === grpc.status.CANCELLED 533 ) { 534 // Re-emit the message 535 terminateRpcs(); 536 rpcErr.message = "Resource monitor is terminating"; 537 (<any>preallocError).code = rpcErr.code; 538 } 539 540 // Node lets us hack the message as long as we do it before accessing the `stack` property. 541 log.debug( 542 `RegisterResource RPC finished: ${label}; err: ${rpcErr}, resp: ${innerResponse}`, 543 ); 544 preallocError.message = `failed to register new resource ${name} [${t}]: ${rpcErr.message}`; 545 reject(preallocError); 546 } else { 547 log.debug( 548 `RegisterResource RPC finished: ${label}; err: ${rpcErr}, resp: ${innerResponse}`, 549 ); 550 resolve(innerResponse); 551 } 552 }, 553 ), 554 ), 555 opLabel, 556 ); 557 } else { 558 // If we aren't attached to the engine, in test mode, mock up a fake response for testing purposes. 559 const mockurn = await createUrn(req.getName(), req.getType(), req.getParent()).promise(); 560 resp = { 561 getUrn: () => mockurn, 562 getId: () => undefined, 563 getObject: () => req.getObject(), 564 getPropertydependenciesMap: () => undefined, 565 }; 566 } 567 } catch (e) { 568 err = e; 569 resp = { 570 getUrn: () => "", 571 getId: () => undefined, 572 getObject: () => req.getObject(), 573 getPropertydependenciesMap: () => undefined, 574 }; 575 } 576 577 resop.resolveURN(resp.getUrn(), err); 578 579 // Note: 'id || undefined' is intentional. We intentionally collapse falsy values to 580 // undefined so that later parts of our system don't have to deal with values like 'null'. 581 if (resop.resolveID) { 582 const id = resp.getId() || undefined; 583 resop.resolveID(id, id !== undefined, err); 584 } 585 586 const deps: Record<string, Resource[]> = {}; 587 const rpcDeps = resp.getPropertydependenciesMap(); 588 if (rpcDeps) { 589 for (const [k, propertyDeps] of resp.getPropertydependenciesMap().entries()) { 590 const urns = <URN[]>propertyDeps.getUrnsList(); 591 deps[k] = urns.map((urn) => newDependency(urn)); 592 } 593 } 594 595 // Now resolve the output properties. 596 await resolveOutputs(res, t, name, props, resp.getObject(), deps, resop.resolvers, err); 597 }); 598 }), 599 label, 600 ); 601 } 602 603 /** @internal 604 * Prepares for an RPC that will manufacture a resource, and hence deals with input and output 605 * properties. 606 */ 607 export async function prepareResource( 608 label: string, 609 res: Resource, 610 parent: Resource | undefined, 611 custom: boolean, 612 remote: boolean, 613 props: Inputs, 614 opts: ResourceOptions, 615 type?: string, 616 name?: string, 617 ): Promise<ResourceResolverOperation> { 618 // add an entry to the rpc queue while we prepare the request. 619 // automation api inline programs that don't have stack exports can exit quickly. If we don't do this, 620 // sometimes they will exit right after `prepareResource` is called as a part of register resource, but before the 621 // .then() that adds to the queue via `runAsyncResourceOp`. 622 const done: () => void = rpcKeepAlive(); 623 624 try { 625 // Simply initialize the URN property and get prepared to resolve it later on. 626 // Note: a resource urn will always get a value, and thus the output property 627 // for it can always run .apply calls. 628 let resolveURN: (urn: URN, err?: Error) => void; 629 { 630 let resolveValue: (urn: URN) => void; 631 let rejectValue: (err: Error) => void; 632 let resolveIsKnown: (isKnown: boolean) => void; 633 let rejectIsKnown: (err: Error) => void; 634 (res as any).urn = new Output( 635 res, 636 debuggablePromise( 637 new Promise<URN>((resolve, reject) => { 638 resolveValue = resolve; 639 rejectValue = reject; 640 }), 641 `resolveURN(${label})`, 642 ), 643 debuggablePromise( 644 new Promise<boolean>((resolve, reject) => { 645 resolveIsKnown = resolve; 646 rejectIsKnown = reject; 647 }), 648 `resolveURNIsKnown(${label})`, 649 ), 650 /*isSecret:*/ Promise.resolve(false), 651 Promise.resolve(res), 652 ); 653 654 resolveURN = (v, err) => { 655 if (err) { 656 if (isGrpcError(err)) { 657 if (debugPromiseLeaks) { 658 console.error("info: skipped rejection in resolveURN"); 659 } 660 return; 661 } 662 rejectValue(err); 663 rejectIsKnown(err); 664 } else { 665 resolveValue(v); 666 resolveIsKnown(true); 667 } 668 }; 669 } 670 671 // If a custom resource, make room for the ID property. 672 let resolveID: ((v: any, performApply: boolean, err?: Error) => void) | undefined; 673 if (custom) { 674 let resolveValue: (v: ID) => void; 675 let rejectValue: (err: Error) => void; 676 let resolveIsKnown: (v: boolean) => void; 677 let rejectIsKnown: (err: Error) => void; 678 679 (res as any).id = new Output( 680 res, 681 debuggablePromise( 682 new Promise<ID>((resolve, reject) => { 683 resolveValue = resolve; 684 rejectValue = reject; 685 }), 686 `resolveID(${label})`, 687 ), 688 debuggablePromise( 689 new Promise<boolean>((resolve, reject) => { 690 resolveIsKnown = resolve; 691 rejectIsKnown = reject; 692 }), 693 `resolveIDIsKnown(${label})`, 694 ), 695 Promise.resolve(false), 696 Promise.resolve(res), 697 ); 698 699 resolveID = (v, isKnown, err) => { 700 if (err) { 701 if (isGrpcError(err)) { 702 if (debugPromiseLeaks) { 703 console.error("info: skipped rejection in resolveID"); 704 } 705 return; 706 } 707 rejectValue(err); 708 rejectIsKnown(err); 709 } else { 710 resolveValue(v); 711 resolveIsKnown(isKnown); 712 } 713 }; 714 } 715 716 // Now "transfer" all input properties into unresolved Promises on res. This way, 717 // this resource will look like it has all its output properties to anyone it is 718 // passed to. However, those promises won't actually resolve until the registerResource 719 // RPC returns 720 const resolvers = transferProperties(res, label, props); 721 722 /** IMPORTANT! We should never await prior to this line, otherwise the Resource will be partly uninitialized. */ 723 724 // Before we can proceed, all our dependencies must be finished. 725 const explicitDirectDependencies = new Set(await gatherExplicitDependencies(opts.dependsOn)); 726 727 // Serialize out all our props to their final values. In doing so, we'll also collect all 728 // the Resources pointed to by any Dependency objects we encounter, adding them to 'propertyDependencies'. 729 const [serializedProps, propertyToDirectDependencies] = await serializeResourceProperties(label, props, { 730 // To initially scope the use of this new feature, we only keep output values when 731 // remote is true (for multi-lang components). 732 keepOutputValues: remote, 733 }); 734 735 // Wait for the parent to complete. 736 // If no parent was provided, parent to the root resource. 737 const parentURN = parent ? await parent.urn.promise() : undefined; 738 739 let importID: ID | undefined; 740 if (custom) { 741 const customOpts = <CustomResourceOptions>opts; 742 importID = customOpts.import; 743 } 744 745 let providerRef: string | undefined; 746 let sendProvider = custom; 747 if (remote && opts.provider) { 748 // If it's a remote component and a provider was specified, only 749 // send the provider in the request if the provider's package is 750 // the same as the component's package. Otherwise, don't send it 751 // because the user specified `provider: someProvider` as shorthand 752 // for `providers: [someProvider]`. 753 const pkg = pkgFromType(type!); 754 if (pkg && pkg === opts.provider.getPackage()) { 755 sendProvider = true; 756 } 757 } 758 if (sendProvider) { 759 providerRef = await ProviderResource.register(opts.provider); 760 } 761 762 const providerRefs: Map<string, string> = new Map<string, string>(); 763 if (remote || !custom) { 764 const componentOpts = <ComponentResourceOptions>opts; 765 expandProviders(componentOpts); 766 // the <ProviderResource[]> casts are safe because expandProviders 767 // /always/ leaves providers as an array. 768 if (componentOpts.provider !== undefined) { 769 if (componentOpts.providers === undefined) { 770 // We still want to do the promotion, so we define providers 771 componentOpts.providers = [componentOpts.provider]; 772 } else if ((<ProviderResource[]>componentOpts.providers)?.indexOf(componentOpts.provider) !== -1) { 773 const pkg = componentOpts.provider.getPackage(); 774 const message = `There is a conflit between the 'provider' field (${pkg}) and a member of the 'providers' map'. `; 775 const deprecationd = 776 "This will become an error in a future version. See https://github.com/pulumi/pulumi/issues/8799 for more details"; 777 log.warn(message + deprecationd); 778 } else { 779 (<ProviderResource[]>componentOpts.providers).push(componentOpts.provider); 780 } 781 } 782 if (componentOpts.providers) { 783 for (const provider of componentOpts.providers as ProviderResource[]) { 784 const pref = await ProviderResource.register(provider); 785 if (pref) { 786 providerRefs.set(provider.getPackage(), pref); 787 } 788 } 789 } 790 } 791 792 // Collect the URNs for explicit/implicit dependencies for the engine so that it can understand 793 // the dependency graph and optimize operations accordingly. 794 795 // The list of all dependencies (implicit or explicit). 796 const allDirectDependencies = new Set<Resource>(explicitDirectDependencies); 797 798 const exclude = new Set<Resource>([res]); 799 const allDirectDependencyURNs = await getAllTransitivelyReferencedResourceURNs( 800 explicitDirectDependencies, 801 exclude, 802 ); 803 const propertyToDirectDependencyURNs = new Map<string, Set<URN>>(); 804 805 for (const [propertyName, directDependencies] of propertyToDirectDependencies) { 806 addAll(allDirectDependencies, directDependencies); 807 808 const urns = await getAllTransitivelyReferencedResourceURNs(directDependencies, exclude); 809 addAll(allDirectDependencyURNs, urns); 810 propertyToDirectDependencyURNs.set(propertyName, urns); 811 } 812 813 const monitorSupportsStructuredAliases = getStore().supportsAliasSpecs; 814 let computedAliases; 815 if (!monitorSupportsStructuredAliases && parent) { 816 computedAliases = allAliases(opts.aliases || [], name!, type!, parent, parent.__name!); 817 } else { 818 computedAliases = opts.aliases || []; 819 } 820 821 // Wait for all aliases. 822 const aliases = []; 823 const uniqueAliases = new Set<Alias | URN>(); 824 for (const alias of computedAliases || []) { 825 const aliasVal = await output(alias).promise(); 826 if (!uniqueAliases.has(aliasVal)) { 827 uniqueAliases.add(aliasVal); 828 aliases.push(aliasVal); 829 } 830 } 831 832 const deletedWithURN = opts?.deletedWith ? await opts.deletedWith.urn.promise() : undefined; 833 834 return { 835 resolveURN: resolveURN, 836 resolveID: resolveID, 837 resolvers: resolvers, 838 serializedProps: serializedProps, 839 parentURN: parentURN, 840 providerRef: providerRef, 841 providerRefs: providerRefs, 842 allDirectDependencyURNs: allDirectDependencyURNs, 843 propertyToDirectDependencyURNs: propertyToDirectDependencyURNs, 844 aliases: aliases, 845 import: importID, 846 monitorSupportsStructuredAliases, 847 deletedWithURN, 848 }; 849 } finally { 850 // free the RPC queue 851 done(); 852 } 853 } 854 855 function addAll<T>(to: Set<T>, from: Set<T>) { 856 for (const val of from) { 857 to.add(val); 858 } 859 } 860 861 /** @internal */ 862 export async function getAllTransitivelyReferencedResourceURNs( 863 resources: Set<Resource>, 864 exclude: Set<Resource>, 865 ): Promise<Set<string>> { 866 // Go through 'resources', but transitively walk through **Component** resources, collecting any 867 // of their child resources. This way, a Component acts as an aggregation really of all the 868 // reachable resources it parents. This walking will stop when it hits custom resources. 869 // 870 // This function also terminates at remote components, whose children are not known to the Node SDK directly. 871 // Remote components will always wait on all of their children, so ensuring we return the remote component 872 // itself here and waiting on it will accomplish waiting on all of it's children regardless of whether they 873 // are returned explicitly here. 874 // 875 // In other words, if we had: 876 // 877 // Comp1 878 // / | \ 879 // Cust1 Comp2 Remote1 880 // / \ \ 881 // Cust2 Cust3 Comp3 882 // / \ 883 // Cust4 Cust5 884 // 885 // Then the transitively reachable resources of Comp1 will be [Cust1, Cust2, Cust3, Remote1]. 886 // It will *not* include: 887 // * Cust4 because it is a child of a custom resource 888 // * Comp2 because it is a non-remote component resource 889 // * Comp3 and Cust5 because Comp3 is a child of a remote component resource 890 891 // To do this, first we just get the transitively reachable set of resources (not diving 892 // into custom resources). In the above picture, if we start with 'Comp1', this will be 893 // [Comp1, Cust1, Comp2, Cust2, Cust3] 894 const transitivelyReachableResources = await getTransitivelyReferencedChildResourcesOfComponentResources( 895 resources, 896 exclude, 897 ); 898 899 // Then we filter to only include Custom and Remote resources. 900 const transitivelyReachableCustomResources = [...transitivelyReachableResources].filter( 901 (r) => (CustomResource.isInstance(r) || (r as ComponentResource).__remote) && !exclude.has(r), 902 ); 903 const promises = transitivelyReachableCustomResources.map((r) => r.urn.promise()); 904 const urns = await Promise.all(promises); 905 return new Set<string>(urns); 906 } 907 908 /** 909 * Recursively walk the resources passed in, returning them and all resources reachable from 910 * [Resource.__childResources] through any **Component** resources we encounter. 911 */ 912 async function getTransitivelyReferencedChildResourcesOfComponentResources( 913 resources: Set<Resource>, 914 exclude: Set<Resource>, 915 ) { 916 // Recursively walk the dependent resources through their children, adding them to the result set. 917 const result = new Set<Resource>(); 918 await addTransitivelyReferencedChildResourcesOfComponentResources(resources, exclude, result); 919 return result; 920 } 921 922 async function addTransitivelyReferencedChildResourcesOfComponentResources( 923 resources: Set<Resource> | undefined, 924 exclude: Set<Resource>, 925 result: Set<Resource>, 926 ) { 927 if (resources) { 928 for (const resource of resources) { 929 if (!result.has(resource)) { 930 result.add(resource); 931 932 if (ComponentResource.isInstance(resource)) { 933 // Skip including children of a resource in the excluded set to avoid depending on 934 // children that haven't been registered yet. 935 if (exclude.has(resource)) { 936 continue; 937 } 938 939 // This await is safe even if __isConstructed is undefined. Ensure that the 940 // resource has completely finished construction. That way all parent/child 941 // relationships will have been setup. 942 await resource.__data; 943 const children = resource.__childResources; 944 addTransitivelyReferencedChildResourcesOfComponentResources(children, exclude, result); 945 } 946 } 947 } 948 } 949 } 950 951 /** 952 * Gathers explicit dependent Resources from a list of Resources (possibly Promises and/or Outputs). 953 */ 954 async function gatherExplicitDependencies( 955 dependsOn: Input<Input<Resource>[]> | Input<Resource> | undefined, 956 ): Promise<Resource[]> { 957 if (dependsOn) { 958 if (Array.isArray(dependsOn)) { 959 const dos: Resource[] = []; 960 for (const d of dependsOn) { 961 dos.push(...(await gatherExplicitDependencies(d))); 962 } 963 return dos; 964 } else if (dependsOn instanceof Promise) { 965 return gatherExplicitDependencies(await dependsOn); 966 } else if (Output.isInstance(dependsOn)) { 967 // Recursively gather dependencies, await the promise, and append the output's dependencies. 968 const dos = (dependsOn as Output<Input<Resource>[] | Input<Resource>>).apply((v) => 969 gatherExplicitDependencies(v), 970 ); 971 const urns = await dos.promise(); 972 const dosResources = await getAllResources(dos); 973 const implicits = await gatherExplicitDependencies([...dosResources]); 974 return (urns ?? []).concat(implicits); 975 } else { 976 if (!Resource.isInstance(dependsOn)) { 977 throw new Error("'dependsOn' was passed a value that was not a Resource."); 978 } 979 980 return [dependsOn]; 981 } 982 } 983 984 return []; 985 } 986 987 /** 988 * Finishes a resource creation RPC operation by resolving its outputs to the resulting RPC payload. 989 */ 990 async function resolveOutputs( 991 res: Resource, 992 t: string, 993 name: string, 994 props: Inputs, 995 outputs: any, 996 deps: Record<string, Resource[]>, 997 resolvers: OutputResolvers, 998 err?: Error, 999 ): Promise<void> { 1000 // Produce a combined set of property states, starting with inputs and then applying 1001 // outputs. If the same property exists in the inputs and outputs states, the output wins. 1002 const allProps: Record<string, any> = {}; 1003 if (outputs) { 1004 Object.assign(allProps, deserializeProperties(outputs)); 1005 } 1006 1007 const label = `resource:${name}[${t}]#...`; 1008 if (!isDryRun() || isLegacyApplyEnabled()) { 1009 for (const key of Object.keys(props)) { 1010 if (!allProps.hasOwnProperty(key)) { 1011 // input prop the engine didn't give us a final value for. Just use the value passed into the resource 1012 // after round-tripping it through serialization. We do the round-tripping primarily s.t. we ensure that 1013 // Output values are handled properly w.r.t. unknowns. 1014 const inputProp = await serializeProperty(label, props[key], new Set(), { keepOutputValues: false }); 1015 if (inputProp === undefined) { 1016 continue; 1017 } 1018 allProps[key] = deserializeProperty(inputProp); 1019 } 1020 } 1021 } 1022 1023 resolveProperties(res, resolvers, t, name, allProps, deps, err); 1024 } 1025 1026 /** 1027 * registerResourceOutputs completes the resource registration, attaching an optional set of computed outputs. 1028 */ 1029 export function registerResourceOutputs(res: Resource, outputs: Inputs | Promise<Inputs> | Output<Inputs>) { 1030 // Now run the operation. Note that we explicitly do not serialize output registration with 1031 // respect to other resource operations, as outputs may depend on properties of other resources 1032 // that will not resolve until later turns. This would create a circular promise chain that can 1033 // never resolve. 1034 const opLabel = `monitor.registerResourceOutputs(...)`; 1035 runAsyncResourceOp( 1036 opLabel, 1037 async () => { 1038 // The registration could very well still be taking place, so we will need to wait for its URN. 1039 // Additionally, the output properties might have come from other resources, so we must await those too. 1040 const urn = await res.urn.promise(); 1041 const resolved = await serializeProperties(opLabel, { outputs }); 1042 const outputsObj = gstruct.Struct.fromJavaScript(resolved.outputs); 1043 log.debug( 1044 `RegisterResourceOutputs RPC prepared: urn=${urn}` + 1045 (excessiveDebugOutput ? `, outputs=${JSON.stringify(outputsObj)}` : ``), 1046 ); 1047 1048 // Fetch the monitor and make an RPC request. 1049 const monitor = getMonitor(); 1050 if (monitor) { 1051 const req = new resproto.RegisterResourceOutputsRequest(); 1052 req.setUrn(urn); 1053 req.setOutputs(outputsObj); 1054 1055 const label = `monitor.registerResourceOutputs(${urn}, ...)`; 1056 await debuggablePromise( 1057 new Promise<void>((resolve, reject) => 1058 monitor.registerResourceOutputs( 1059 req, 1060 (err: grpc.ServiceError | null, innerResponse: gempty.Empty | undefined) => { 1061 log.debug( 1062 `RegisterResourceOutputs RPC finished: urn=${urn}; ` + 1063 `err: ${err}, resp: ${innerResponse}`, 1064 ); 1065 if (err) { 1066 // If the monitor is unavailable, it is in the process of shutting down or has already 1067 // shut down. Don't emit an error and don't do any more RPCs, just exit. 1068 if (err.code === grpc.status.UNAVAILABLE || err.code === grpc.status.CANCELLED) { 1069 terminateRpcs(); 1070 err.message = "Resource monitor is terminating"; 1071 } 1072 1073 reject(err); 1074 } else { 1075 log.debug( 1076 `RegisterResourceOutputs RPC finished: urn=${urn}; ` + 1077 `err: ${err}, resp: ${innerResponse}`, 1078 ); 1079 resolve(); 1080 } 1081 }, 1082 ), 1083 ), 1084 label, 1085 ); 1086 } 1087 }, 1088 false, 1089 ); 1090 } 1091 1092 function isAny(o: any): o is any { 1093 return true; 1094 } 1095 1096 /** 1097 * listResourceOutputs returns the resource outputs (if any) for a stack, or an error if the stack 1098 * cannot be found. Resources are retrieved from the latest stack snapshot, which may include 1099 * ongoing updates. 1100 * 1101 * @param stackName Name of stack to retrieve resource outputs for. Defaults to the current stack. 1102 * @param typeFilter A [type 1103 * guard](https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards) 1104 * that specifies which resource types to list outputs of. 1105 * 1106 * @example 1107 * const buckets = pulumi.runtime.listResourceOutput(aws.s3.Bucket.isInstance); 1108 */ 1109 export function listResourceOutputs<U extends Resource>( 1110 typeFilter?: (o: any) => o is U, 1111 stackName?: string, 1112 ): query.AsyncQueryable<ResolvedResource<U>> { 1113 if (typeFilter === undefined) { 1114 typeFilter = isAny; 1115 } 1116 1117 return query 1118 .from( 1119 invoke("pulumi:pulumi:readStackResourceOutputs", { 1120 stackName: stackName || getStack(), 1121 }).then<any[]>(({ outputs }) => utils.values(outputs)), 1122 ) 1123 .map<ResolvedResource<U>>(({ type: typ, outputs }) => { 1124 return { ...outputs, __pulumiType: typ }; 1125 }) 1126 .filter(typeFilter); 1127 } 1128 1129 /** 1130 * resourceChain is used to serialize all resource requests. If we don't do this, all resource operations will be 1131 * entirely asynchronous, meaning the dataflow graph that results will determine ordering of operations. This 1132 * causes problems with some resource providers, so for now we will serialize all of them. The issue 1133 * pulumi/pulumi#335 tracks coming up with a long-term solution here. 1134 */ 1135 let resourceChain: Promise<void> = Promise.resolve(); 1136 let resourceChainLabel: string | undefined = undefined; 1137 1138 // runAsyncResourceOp runs an asynchronous resource operation, possibly serializing it as necessary. 1139 function runAsyncResourceOp(label: string, callback: () => Promise<void>, serial?: boolean): void { 1140 // Serialize the invocation if necessary. 1141 if (serial === undefined) { 1142 serial = serialize(); 1143 } 1144 const resourceOp: Promise<void> = suppressUnhandledGrpcRejections( 1145 debuggablePromise( 1146 resourceChain.then(async () => { 1147 if (serial) { 1148 resourceChainLabel = label; 1149 log.debug(`Resource RPC serialization requested: ${label} is current`); 1150 } 1151 return callback(); 1152 }), 1153 label + "-initial", 1154 ), 1155 ); 1156 1157 // Ensure the process won't exit until this RPC call finishes and resolve it when appropriate. 1158 const done: () => void = rpcKeepAlive(); 1159 const finalOp: Promise<void> = debuggablePromise( 1160 resourceOp.then( 1161 () => { 1162 done(); 1163 }, 1164 () => { 1165 done(); 1166 }, 1167 ), 1168 label + "-final", 1169 ); 1170 1171 // Set up another promise that propagates the error, if any, so that it triggers unhandled rejection logic. 1172 resourceOp.catch((err) => Promise.reject(err)); 1173 1174 // If serialization is requested, wait for the prior resource operation to finish before we proceed, serializing 1175 // them, and make this the current resource operation so that everybody piles up on it. 1176 if (serial) { 1177 resourceChain = finalOp; 1178 if (resourceChainLabel) { 1179 log.debug(`Resource RPC serialization requested: ${label} is behind ${resourceChainLabel}`); 1180 } 1181 } 1182 } 1183 1184 /** 1185 * Extract the pkg from the type token of the form "pkg:module:member". 1186 * @internal 1187 */ 1188 export function pkgFromType(type: string): string | undefined { 1189 const parts = type.split(":"); 1190 if (parts.length === 3) { 1191 return parts[0]; 1192 } 1193 return undefined; 1194 }