forked from pulumi/pulumi
-
Notifications
You must be signed in to change notification settings - Fork 0
/
resource.ts
965 lines (862 loc) · 39.1 KB
/
resource.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { ResourceError } from "./errors";
import { Input, Inputs, interpolate, Output, output } from "./output";
import { getStackResource, unknownValue } from "./runtime";
import { readResource, registerResource, registerResourceOutputs } from "./runtime/resource";
import { getProject, getStack } from "./runtime/settings";
import * as utils from "./utils";
export type ID = string; // a provider-assigned ID.
export type URN = string; // an automatically generated logical URN, used to stably identify resources.
/**
* createUrn computes a URN from the combination of a resource name, resource type, optional parent,
* optional project and optional stack.
*/
export function createUrn(name: Input<string>, type: Input<string>, parent?: Resource | Input<URN>, project?: string, stack?: string): Output<string> {
let parentPrefix: Output<string>;
if (parent) {
let parentUrn: Output<string>;
if (Resource.isInstance(parent)) {
parentUrn = parent.urn;
} else {
parentUrn = output(parent);
}
parentPrefix = parentUrn.apply(parentUrnString => parentUrnString.substring(0, parentUrnString.lastIndexOf("::")) + "$");
} else {
parentPrefix = output(`urn:pulumi:${stack || getStack()}::${project || getProject()}::`);
}
return interpolate`${parentPrefix}${type}::${name}`;
}
// inheritedChildAlias computes the alias that should be applied to a child based on an alias applied to it's parent.
// This may involve changing the name of the resource in cases where the resource has a named derived from the name of
// the parent, and the parent name changed.
function inheritedChildAlias(childName: string, parentName: string, parentAlias: Input<string>, childType: string): Output<string> {
// If the child name has the parent name as a prefix, then we make the assumption that it was
// constructed from the convention of using `{name}-details` as the name of the child resource. To
// ensure this is aliased correctly, we must then also replace the parent aliases name in the prefix of
// the child resource name.
//
// For example:
// * name: "newapp-function"
// * opts.parent.__name: "newapp"
// * parentAlias: "urn:pulumi:stackname::projectname::awsx:ec2:Vpc::app"
// * parentAliasName: "app"
// * aliasName: "app-function"
// * childAlias: "urn:pulumi:stackname::projectname::aws:s3/bucket:Bucket::app-function"
let aliasName = output(childName);
if (childName.startsWith(parentName)) {
aliasName = output(parentAlias).apply(parentAliasUrn => {
const parentAliasName = parentAliasUrn.substring(parentAliasUrn.lastIndexOf("::") + 2);
return parentAliasName + childName.substring(parentName.length);
});
}
return createUrn(aliasName, childType, parentAlias);
}
/**
* Resource represents a class whose CRUD operations are implemented by a provider plugin.
*/
export abstract class Resource {
/**
* A private field to help with RTTI that works in SxS scenarios.
* @internal
*/
// tslint:disable-next-line:variable-name
public readonly __pulumiResource: boolean = true;
/**
* The optional parent of this resource.
* @internal
*/
// tslint:disable-next-line:variable-name
public readonly __parentResource: Resource | undefined;
/**
* The child resources of this resource. We use these (only from a ComponentResource) to allow
* code to dependOn a ComponentResource and have that effectively mean that it is depending on
* all the CustomResource children of that component.
*
* Important! We only walk through ComponentResources. They're the only resources that serve
* as an aggregation of other primitive (i.e. custom) resources. While a custom resource can be
* a parent of other resources, we don't want to ever depend on those child resource. If we do,
* it's simple to end up in a situation where we end up depending on a child resource that has a
* data cycle dependency due to the data passed into it.
*
* An example of how this would be bad is:
*
* ```ts
* var c1 = new CustomResource("c1");
* var c2 = new CustomResource("c2", { parentId: c1.id }, { parent: c1 });
* var c3 = new CustomResource("c3", { parentId: c1.id }, { parent: c1 });
* ```
*
* The problem here is that 'c2' has a data dependency on 'c1'. If it tries to wait on 'c1' it
* will walk to the children and wait on them. This will mean it will wait on 'c3'. But 'c3'
* will be waiting in the same manner on 'c2', and a cycle forms.
*
* This normally does not happen with ComponentResources as they do not have any data flowing
* into them. The only way you would be able to have a problem is if you had this sort of coding
* pattern:
*
* ```ts
* var c1 = new ComponentResource("c1");
* var c2 = new CustomResource("c2", { parentId: c1.urn }, { parent: c1 });
* var c3 = new CustomResource("c3", { parentId: c1.urn }, { parent: c1 });
* ```
*
* However, this would be pretty nonsensical as there is zero need for a custom resource to ever
* need to reference the urn of a component resource. So it's acceptable if that sort of
* pattern failed in practice.
*
* @internal
*/
// tslint:disable-next-line:variable-name
public __childResources: Set<Resource> | undefined;
/**
* urn is the stable logical URN used to distinctly address a resource, both before and after
* deployments.
*/
public readonly urn!: Output<URN>;
/**
* When set to true, protect ensures this resource cannot be deleted.
* @internal
*/
// tslint:disable-next-line:variable-name
private readonly __protect: boolean;
/**
* A collection of transformations to apply as part of resource registration.
*
* Note: This is marked optional only because older versions of this library may not have had
* this property, and marking optional forces consumers of the property to defensively handle
* cases where they are passed "old" resources.
* @internal
*/
// tslint:disable-next-line:variable-name
__transformations?: ResourceTransformation[];
/**
* A list of aliases applied to this resource.
*
* Note: This is marked optional only because older versions of this library may not have had
* this property, and marking optional forces consumers of the property to defensively handle
* cases where they are passed "old" resources.
* @internal
*/
// tslint:disable-next-line:variable-name
readonly __aliases?: Input<URN>[];
/**
* The name assigned to the resource at construction.
*
* Note: This is marked optional only because older versions of this library may not have had
* this property, and marking optional forces consumers of the property to defensively handle
* cases where they are passed "old" resources.
* @internal
*/
// tslint:disable-next-line:variable-name
private readonly __name?: string;
/**
* The set of providers to use for child resources. Keyed by package name (e.g. "aws").
* @internal
*/
// tslint:disable-next-line:variable-name
private readonly __providers: Record<string, ProviderResource>;
public static isInstance(obj: any): obj is Resource {
return utils.isInstance<Resource>(obj, "__pulumiResource");
}
// getProvider fetches the provider for the given module member, if any.
public getProvider(moduleMember: string): ProviderResource | undefined {
const memComponents = moduleMember.split(":");
if (memComponents.length !== 3) {
return undefined;
}
const pkg = memComponents[0];
return this.__providers[pkg];
}
/**
* Creates and registers a new resource object. [t] is the fully qualified type token and
* [name] is the "name" part to use in creating a stable and globally unique URN for the object.
* dependsOn is an optional list of other resources that this resource depends on, controlling
* the order in which we perform resource operations.
*
* @param t The type of the resource.
* @param name The _unique_ name of the resource.
* @param custom True to indicate that this is a custom resource, managed by a plugin.
* @param props The arguments to use to populate the new resource.
* @param opts A bag of options that control this resource's behavior.
*/
constructor(t: string, name: string, custom: boolean, props: Inputs = {}, opts: ResourceOptions = {}) {
if (opts.parent && !Resource.isInstance(opts.parent)) {
throw new Error(`Resource parent is not a valid Resource: ${opts.parent}`);
}
if (!t) {
throw new ResourceError("Missing resource type argument", opts.parent);
}
if (!name) {
throw new ResourceError("Missing resource name argument (for URN creation)", opts.parent);
}
// Before anything else - if there are transformations registered, invoke them in order to transform the properties and
// options assigned to this resource.
const parent = opts.parent || getStackResource() || { __transformations: undefined };
this.__transformations = [ ...(opts.transformations || []), ...(parent.__transformations || []) ];
for (const transformation of this.__transformations) {
const tres = transformation({ resource: this, type: t, name, props, opts });
if (tres) {
if (tres.opts.parent !== opts.parent) {
// This is currently not allowed because the parent tree is needed to establish what
// transformation to apply in the first place, and to compute inheritance of other
// resource options in the Resource constructor before transformations are run (so
// modifying it here would only even partially take affect). It's theoretically
// possible this restriction could be lifted in the future, but for now just
// disallow re-parenting resources in transformations to be safe.
throw new Error("Transformations cannot currently be used to change the `parent` of a resource.");
}
props = tres.props;
opts = tres.opts;
}
}
this.__name = name;
// Make a shallow clone of opts to ensure we don't modify the value passed in.
opts = Object.assign({}, opts);
if (opts.provider && (<ComponentResourceOptions>opts).providers) {
throw new ResourceError("Do not supply both 'provider' and 'providers' options to a ComponentResource.", opts.parent);
}
// Check the parent type if one exists and fill in any default options.
this.__providers = {};
if (opts.parent) {
this.__parentResource = opts.parent;
this.__parentResource.__childResources = this.__parentResource.__childResources || new Set();
this.__parentResource.__childResources.add(this);
if (opts.protect === undefined) {
opts.protect = opts.parent.__protect;
}
// Make a copy of the aliases array, and add to it any implicit aliases inherited from its parent
opts.aliases = [...(opts.aliases || [])];
if (opts.parent.__name) {
for (const parentAlias of (opts.parent.__aliases || [])) {
opts.aliases.push(inheritedChildAlias(name, opts.parent.__name, parentAlias, t));
}
}
this.__providers = opts.parent.__providers;
}
if (custom) {
const provider = opts.provider;
if (provider === undefined) {
if (opts.parent) {
// If no provider was given, but we have a parent, then inherit the
// provider from our parent.
opts.provider = opts.parent.getProvider(t);
}
} else {
// If a provider was specified, add it to the providers map under this type's package so that
// any children of this resource inherit its provider.
const typeComponents = t.split(":");
if (typeComponents.length === 3) {
const pkg = typeComponents[0];
this.__providers = { ...this.__providers, [pkg]: provider };
}
}
}
else {
// Note: we checked above that at most one of opts.provider or opts.providers is set.
// If opts.provider is set, treat that as if we were given a array of provider with that
// single value in it. Otherwise, take the array or map of providers, convert it to a
// map and combine with any providers we've already set from our parent.
const providers = opts.provider
? convertToProvidersMap([opts.provider])
: convertToProvidersMap((<ComponentResourceOptions>opts).providers);
this.__providers = { ...this.__providers, ...providers };
}
this.__protect = !!opts.protect;
// Collapse any `Alias`es down to URNs. We have to wait until this point to do so because we do not know the
// default `name` and `type` to apply until we are inside the resource constructor.
this.__aliases = [];
if (opts.aliases) {
for (const alias of opts.aliases) {
this.__aliases.push(collapseAliasToUrn(alias, name, t, opts.parent));
}
}
if (opts.id) {
// If this resource already exists, read its state rather than registering it anew.
if (!custom) {
throw new ResourceError(
"Cannot read an existing resource unless it has a custom provider", opts.parent);
}
readResource(this, t, name, props, opts);
} else {
// Kick off the resource registration. If we are actually performing a deployment, this
// resource's properties will be resolved asynchronously after the operation completes, so
// that dependent computations resolve normally. If we are just planning, on the other
// hand, values will never resolve.
registerResource(this, t, name, custom, props, opts);
}
}
}
function convertToProvidersMap(providers: Record<string, ProviderResource> | ProviderResource[] | undefined) {
if (!providers) {
return {};
}
if (!Array.isArray(providers)) {
return providers;
}
const result: Record<string, ProviderResource> = {};
for (const provider of providers) {
result[provider.getPackage()] = provider;
}
return result;
}
(<any>Resource).doNotCapture = true;
/**
* Constant to represent the 'root stack' resource for a Pulumi application. The purpose of this is
* solely to make it easy to write an [Alias] like so:
*
* `aliases: [{ parent: rootStackResource }]`.
*
* This indicates that the prior name for a resource was created based on it being parented directly
* by the stack itself and no other resources. Note: this is equivalent to:
*
* `aliases: [{ parent: undefined }]`
*
* However, the former form is preferable as it is more self-descriptive, while the latter may look
* a bit confusing and may incorrectly look like something that could be removed without changing
* semantics.
*/
export const rootStackResource: Resource = undefined!;
/**
* Alias is a partial description of prior named used for a resource. It can be processed in the
* context of a resource creation to determine what the full aliased URN would be.
*
* Note there is a semantic difference between properties being absent from this type and properties
* having the `undefined` value. Specifically, there is a difference between:
*
* ```ts
* { name: "foo", parent: undefined } // and
* { name: "foo" }
* ```
*
* The presence of a property indicates if its value should be used. If absent, then the value is
* not used. So, in the above while `alias.parent` is `undefined` for both, the first alias means
* "the original urn had no parent" while the second alias means "use the current parent".
*
* Note: to indicate that a resource was previously parented by the root stack, it is recommended
* that you use:
*
* `aliases: [{ parent: pulumi.rootStackResource }]`
*
* This form is self-descriptive and makes the intent clearer than using:
*
* `aliases: [{ parent: undefined }]`
*/
export interface Alias {
/**
* The previous name of the resource. If not provided, the current name of the resource is
* used.
*/
name?: Input<string>;
/**
* The previous type of the resource. If not provided, the current type of the resource is used.
*/
type?: Input<string>;
/**
* The previous parent of the resource. If not provided (i.e. `{ name: "foo" }`), the current
* parent of the resource is used (`opts.parent` if provided, else the implicit stack resource
* parent).
*
* To specify no original parent, use `{ parent: pulumi.rootStackResource }`.
*/
parent?: Resource | Input<URN>;
/**
* The previous stack of the resource. If not provided, defaults to `pulumi.getStack()`.
*/
stack?: Input<string>;
/**
* The previous project of the resource. If not provided, defaults to `pulumi.getProject()`.
*/
project?: Input<string>;
}
// collapseAliasToUrn turns an Alias into a URN given a set of default data
function collapseAliasToUrn(
alias: Input<Alias | string>,
defaultName: string,
defaultType: string,
defaultParent: Resource | undefined): Output<URN> {
return output(alias).apply(a => {
if (typeof a === "string") {
return output(a);
}
const name = a.hasOwnProperty("name") ? a.name : defaultName;
const type = a.hasOwnProperty("type") ? a.type : defaultType;
const parent = a.hasOwnProperty("parent") ? a.parent : defaultParent;
const project = a.hasOwnProperty("project") ? a.project : getProject();
const stack = a.hasOwnProperty("stack") ? a.stack : getStack();
if (name === undefined) {
throw new Error("No valid 'name' passed in for alias.");
}
if (type === undefined) {
throw new Error("No valid 'type' passed in for alias.");
}
return createUrn(name, type, parent, project, stack);
});
}
/**
* ResourceOptions is a bag of optional settings that control a resource's behavior.
*/
export interface ResourceOptions {
// !!! IMPORTANT !!! If you add a new field to this type, make sure to add test that verifies
// that mergeOptions works properly for it.
/**
* An optional existing ID to load, rather than create.
*/
id?: Input<ID>;
/**
* An optional parent resource to which this resource belongs.
*/
parent?: Resource;
/**
* An optional additional explicit dependencies on other resources.
*/
dependsOn?: Input<Input<Resource>[]> | Input<Resource>;
/**
* When set to true, protect ensures this resource cannot be deleted.
*/
protect?: boolean;
/**
* Ignore changes to any of the specified properties.
*/
ignoreChanges?: string[];
/**
* An optional version, corresponding to the version of the provider plugin that should be used when operating on
* this resource. This version overrides the version information inferred from the current package and should
* rarely be used.
*/
version?: string;
/**
* An optional list of aliases to treat this resource as matching.
*/
aliases?: Input<URN | Alias>[];
/**
* An optional provider to use for this resource's CRUD operations. If no provider is supplied,
* the default provider for the resource's package will be used. The default provider is pulled
* from the parent's provider bag (see also ComponentResourceOptions.providers).
*
* If this is a [ComponentResourceOptions] do not provide both [provider] and [providers]
*/
provider?: ProviderResource;
/**
* An optional customTimeouts configuration block.
*/
customTimeouts?: CustomTimeouts;
/**
* Optional list of transformations to apply to this resource during construction. The
* transformations are applied in order, and are applied prior to transformation applied to
* parents walking from the resource up to the stack.
*/
transformations?: ResourceTransformation[];
// !!! IMPORTANT !!! If you add a new field to this type, make sure to add test that verifies
// that mergeOptions works properly for it.
}
export interface CustomTimeouts {
/**
* The optional create timeout represented as a string e.g. 5m, 40s, 1d.
*/
create?: string;
/**
* The optional update timeout represented as a string e.g. 5m, 40s, 1d.
*/
update?: string;
/**
* The optional delete timeout represented as a string e.g. 5m, 40s, 1d.
*/
delete?: string;
}
/**
* ResourceTransformation is the callback signature for the `transformations` resource option. A
* transformation is passed the same set of inputs provided to the `Resource` constructor, and can
* optionally return back alternate values for the `props` and/or `opts` prior to the resource
* actually being created. The effect will be as though those props and opts were passed in place
* of the original call to the `Resource` constructor. If the transformation returns undefined,
* this indicates that the resource will not be transformed.
*/
export type ResourceTransformation = (args: ResourceTransformationArgs) => ResourceTransformationResult | undefined;
/**
* ResourceTransformationArgs is the argument bag passed to a resource transformation.
*/
export interface ResourceTransformationArgs {
/**
* The Resource instance that is being transformed.
*/
resource: Resource;
/**
* The type of the Resource.
*/
type: string;
/**
* The name of the Resource.
*/
name: string;
/**
* The original properties passed to the Resource constructor.
*/
props: Inputs;
/**
* The original resource options passed to the Resource constructor.
*/
opts: ResourceOptions;
}
/**
* ResourceTransformationResult is the result that must be returned by a resource transformation
* callback. It includes new values to use for the `props` and `opts` of the `Resource` in place of
* the originally provided values.
*/
export interface ResourceTransformationResult {
/**
* The new properties to use in place of the original `props`
*/
props: Inputs;
/**
* The new resource options to use in place of the original `opts`
*/
opts: ResourceOptions;
}
/**
* CustomResourceOptions is a bag of optional settings that control a custom resource's behavior.
*/
export interface CustomResourceOptions extends ResourceOptions {
// !!! IMPORTANT !!! If you add a new field to this type, make sure to add test that verifies
// that mergeOptions works properly for it.
/**
* When set to true, deleteBeforeReplace indicates that this resource should be deleted before its replacement
* is created when replacement is necessary.
*/
deleteBeforeReplace?: boolean;
/**
* The names of outputs for this resource that should be treated as secrets. This augments the list that
* the resource provider and pulumi engine already determine based on inputs to your resource. It can be used
* to mark certain ouputs as a secrets on a per resource basis.
*/
additionalSecretOutputs?: string[];
/**
* When provided with a resource ID, import indicates that this resource's provider should import its state from
* the cloud resource with the given ID. The inputs to the resource's constructor must align with the resource's
* current state. Once a resource has been imported, the import property must be removed from the resource's
* options.
*/
import?: ID;
// !!! IMPORTANT !!! If you add a new field to this type, make sure to add test that verifies
// that mergeOptions works properly for it.
}
/**
* ComponentResourceOptions is a bag of optional settings that control a component resource's behavior.
*/
export interface ComponentResourceOptions extends ResourceOptions {
// !!! IMPORTANT !!! If you add a new field to this type, make sure to add test that verifies
// that mergeOptions works properly for it.
/**
* An optional set of providers to use for child resources. Either keyed by package name (e.g.
* "aws"), or just provided as an array. In the latter case, the package name will be retrieved
* from the provider itself.
*
* In the case of a single provider, the options can be simplified to just pass along `provider: theProvider`
*
* Note: do not provide both [provider] and [providers];
*/
providers?: Record<string, ProviderResource> | ProviderResource[];
// !!! IMPORTANT !!! If you add a new field to this type, make sure to add test that verifies
// that mergeOptions works properly for it.
}
/**
* CustomResource is a resource whose create, read, update, and delete (CRUD) operations are managed
* by performing external operations on some physical entity. The engine understands how to diff
* and perform partial updates of them, and these CRUD operations are implemented in a dynamically
* loaded plugin for the defining package.
*/
export abstract class CustomResource extends Resource {
/**
* A private field to help with RTTI that works in SxS scenarios.
* @internal
*/
// tslint:disable-next-line:variable-name
public readonly __pulumiCustomResource: boolean;
/**
* Private field containing the type ID for this object. Useful for implementing `isInstance` on
* classes that inherit from `CustomResource`.
* @internal
*/
// tslint:disable-next-line:variable-name
public readonly __pulumiType: string;
/**
* id is the provider-assigned unique ID for this managed resource. It is set during
* deployments and may be missing (undefined) during planning phases.
*/
public readonly id!: Output<ID>;
/**
* Returns true if the given object is an instance of CustomResource. This is designed to work even when
* multiple copies of the Pulumi SDK have been loaded into the same process.
*/
public static isInstance(obj: any): obj is CustomResource {
return utils.isInstance<CustomResource>(obj, "__pulumiCustomResource");
}
/**
* Creates and registers a new managed resource. t is the fully qualified type token and name
* is the "name" part to use in creating a stable and globally unique URN for the object.
* dependsOn is an optional list of other resources that this resource depends on, controlling
* the order in which we perform resource operations. Creating an instance does not necessarily
* perform a create on the physical entity which it represents, and instead, this is dependent
* upon the diffing of the new goal state compared to the current known resource state.
*
* @param t The type of the resource.
* @param name The _unique_ name of the resource.
* @param props The arguments to use to populate the new resource.
* @param opts A bag of options that control this resource's behavior.
*/
constructor(t: string, name: string, props?: Inputs, opts: CustomResourceOptions = {}) {
if ((<ComponentResourceOptions>opts).providers) {
throw new ResourceError("Do not supply 'providers' option to a CustomResource. Did you mean 'provider' instead?", opts.parent);
}
super(t, name, true, props, opts);
this.__pulumiCustomResource = true;
this.__pulumiType = t;
}
}
(<any>CustomResource).doNotCapture = true;
/**
* ProviderResource is a resource that implements CRUD operations for other custom resources. These resources are
* managed similarly to other resources, including the usual diffing and update semantics.
*/
export abstract class ProviderResource extends CustomResource {
/** @internal */
private readonly pkg: string;
/** @internal */
// tslint:disable-next-line: variable-name
public __registrationId?: string;
public static async register(provider: ProviderResource | undefined): Promise<string | undefined> {
if (provider === undefined) {
return undefined;
}
if (!provider.__registrationId) {
const providerURN = await provider.urn.promise();
const providerID = await provider.id.promise() || unknownValue;
provider.__registrationId = `${providerURN}::${providerID}`;
}
return provider.__registrationId;
}
/**
* Creates and registers a new provider resource for a particular package.
*
* @param pkg The package associated with this provider.
* @param name The _unique_ name of the provider.
* @param props The configuration to use for this provider.
* @param opts A bag of options that control this provider's behavior.
*/
constructor(pkg: string, name: string, props?: Inputs, opts: ResourceOptions = {}) {
super(`pulumi:providers:${pkg}`, name, props, opts);
this.pkg = pkg;
}
/** @internal */
public getPackage() {
return this.pkg;
}
}
/**
* ComponentResource is a resource that aggregates one or more other child resources into a higher
* level abstraction. The component resource itself is a resource, but does not require custom CRUD
* operations for provisioning.
*/
export class ComponentResource<TData = any> extends Resource {
/**
* A private field to help with RTTI that works in SxS scenarios.
* @internal
*/
// tslint:disable-next-line:variable-name
public readonly __pulumiComponentResource = true;
/** @internal */
// tslint:disable-next-line:variable-name
public readonly __data: Promise<TData>;
/** @internal */
// tslint:disable-next-line:variable-name
private __registered = false;
/**
* Returns true if the given object is an instance of CustomResource. This is designed to work even when
* multiple copies of the Pulumi SDK have been loaded into the same process.
*/
public static isInstance(obj: any): obj is ComponentResource {
return utils.isInstance<ComponentResource>(obj, "__pulumiComponentResource");
}
/**
* Creates and registers a new component resource. [type] is the fully qualified type token and
* [name] is the "name" part to use in creating a stable and globally unique URN for the object.
* [opts.parent] is the optional parent for this component, and [opts.dependsOn] is an optional
* list of other resources that this resource depends on, controlling the order in which we
* perform resource operations.
*
* @param t The type of the resource.
* @param name The _unique_ name of the resource.
* @param args Information passed to [initialize] method.
* @param opts A bag of options that control this resource's behavior.
*/
constructor(type: string, name: string, args: Inputs = {}, opts: ComponentResourceOptions = {}) {
// Explicitly ignore the props passed in. We allow them for back compat reasons. However,
// we explicitly do not want to pass them along to the engine. The ComponentResource acts
// only as a container for other resources. Another way to think about this is that a normal
// 'custom resource' corresponds to real piece of cloud infrastructure. So, when it changes
// in some way, the cloud resource needs to be updated (and vice versa). That is not true
// for a component resource. The component is just used for organizational purposes and does
// not correspond to a real piece of cloud infrastructure. As such, changes to it *itself*
// do not have any effect on the cloud side of things at all.
super(type, name, /*custom:*/ false, /*props:*/ {}, opts);
this.__data = this.initializeAndRegisterOutputs(args);
}
/** @internal */
private async initializeAndRegisterOutputs(args: Inputs) {
const data = await this.initialize(args);
this.registerOutputs();
return data;
}
/**
* Can be overridden by a subclass to asynchronously initialize data for this Component
* automatically when constructed. The data will be available immediately for subclass
* constructors to use. To access the data use `.getData`.
*/
protected async initialize(args: Inputs): Promise<TData> {
return <TData>undefined!;
}
/**
* Retrieves the data produces by [initialize]. The data is immediately available in a
* derived class's constructor after the `super(...)` call to `ComponentResource`.
*/
protected getData(): Promise<TData> {
return this.__data;
}
/**
* registerOutputs registers synthetic outputs that a component has initialized, usually by
* allocating other child sub-resources and propagating their resulting property values.
*
* ComponentResources can call this at the end of their constructor to indicate that they are
* done creating child resources. This is not strictly necessary as this will automatically be
* called after the `initialize` method completes.
*/
protected registerOutputs(outputs?: Inputs | Promise<Inputs> | Output<Inputs>): void {
if (this.__registered) {
return;
}
this.__registered = true;
registerResourceOutputs(this, outputs || {});
}
}
(<any>ComponentResource).doNotCapture = true;
(<any>ComponentResource.prototype).registerOutputs.doNotCapture = true;
(<any>ComponentResource.prototype).initialize.doNotCapture = true;
(<any>ComponentResource.prototype).initializeAndRegisterOutputs.doNotCapture = true;
/** @internal */
export const testingOptions = {
isDryRun: false,
};
/**
* [mergeOptions] takes two ResourceOptions values and produces a new ResourceOptions with the
* respective properties of `opts2` merged over the same properties in `opts1`. The original
* options objects will be unchanged.
*
* Conceptually property merging follows these basic rules:
* 1. if the property is a collection, the final value will be a collection containing the values
* from each options object.
* 2. Simple scaler values from `opts2` (i.e. strings, numbers, bools) will replace the values of
* `opts1`.
* 3. `opts2` can have properties explicitly provided with `null` or `undefined` as the value. If
* explicitly provided, then that will be the final value in the result.
* 4. For the purposes of merging `dependsOn`, `provider` and `providers` are always treated as
* collections, even if only a single value was provided.
*/
export function mergeOptions(opts1: CustomResourceOptions | undefined, opts2: CustomResourceOptions | undefined): CustomResourceOptions;
export function mergeOptions(opts1: ComponentResourceOptions | undefined, opts2: ComponentResourceOptions | undefined): ComponentResourceOptions;
export function mergeOptions(opts1: ResourceOptions | undefined, opts2: ResourceOptions | undefined): ResourceOptions;
export function mergeOptions(opts1: ResourceOptions | undefined, opts2: ResourceOptions | undefined): ResourceOptions {
const dest = <any>{ ...opts1 };
const source = <any>{ ...opts2 };
// Ensure provider/providers are all expanded into the `ProviderResource[]` form.
// This makes merging simple.
expandProviders(dest);
expandProviders(source);
// iterate specifically over the supplied properties in [source]. Note: there may not be an
// corresponding value in [dest].
for (const key of Object.keys(source)) {
const destVal = dest[key];
const sourceVal = source[key];
// For 'dependsOn' we might have singleton resources in both options bags. We
// want to make sure we combine them into a collection.
if (key === "dependsOn") {
dest[key] = merge(destVal, sourceVal, /*alwaysCreateArray:*/ true);
continue;
}
dest[key] = merge(destVal, sourceVal, /*alwaysCreateArray:*/ false);
}
// Now, if we are left with a .providers that is just a single key/value pair, then
// collapse that down into .provider form.
normalizeProviders(dest);
return dest;
}
function isPromiseOrOutput(val: any): boolean {
return val instanceof Promise || Output.isInstance(val);
}
function expandProviders(options: ComponentResourceOptions) {
// Move 'provider' up to 'providers' if we have it.
if (options.provider) {
options.providers = [options.provider];
}
// Convert 'providers' map to array form.
if (options.providers && !Array.isArray(options.providers)) {
options.providers = utils.values(options.providers);
}
delete options.provider;
}
function normalizeProviders(opts: ComponentResourceOptions) {
// If we have only 0-1 providers, then merge that back down to the .provider field.
const providers = <ProviderResource[]>opts.providers;
if (providers) {
if (providers.length === 0) {
delete opts.providers;
}
else if (providers.length === 1) {
opts.provider = providers[0];
delete opts.providers;
}
else {
opts.providers = {};
for (const res of providers) {
opts.providers[res.getPackage()] = res;
}
}
}
}
/** @internal for testing purposes. */
export function merge(dest: any, source: any, alwaysCreateArray: boolean): any {
// unwind any top level promise/outputs.
if (isPromiseOrOutput(dest)) {
return output(dest).apply(d => merge(d, source, alwaysCreateArray));
}
if (isPromiseOrOutput(source)) {
return output(source).apply(s => merge(dest, s, alwaysCreateArray));
}
// If either are an array, make a new array and merge the values into it.
// Otherwise, just overwrite the destination with the source value.
if (alwaysCreateArray || Array.isArray(dest) || Array.isArray(source)) {
const result: any[] = [];
addToArray(result, dest);
addToArray(result, source);
return result;
}
return source;
}
function addToArray(resultArray: any[], value: any) {
if (Array.isArray(value)) {
resultArray.push(...value);
}
else if (value !== undefined && value !== null) {
resultArray.push(value);
}
}