@varde-flyt/vfac 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -0
- package/dist/vfac.mjs +65 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -91,6 +91,41 @@ service, exactly as it resolves the service's own credentials. So there is no
|
|
|
91
91
|
second copy to rotate, and no value for you to read — `vfac get product-instance`
|
|
92
92
|
shows one as `[secret]`. `vfac guide` is authoritative.
|
|
93
93
|
|
|
94
|
+
## Connecting to another resource
|
|
95
|
+
|
|
96
|
+
A Product may declare a **service dependency** — a connection to another service
|
|
97
|
+
that signs your people in, or that it looks a directory up in. When the platform
|
|
98
|
+
says the customer chooses which resource provides it, name that resource:
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
spec:
|
|
102
|
+
dependencyBindings:
|
|
103
|
+
<dependency-key>:
|
|
104
|
+
resourceKey: the-providing-resource
|
|
105
|
+
projectId: prj_… # only if it is in another Project
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`vfac manifest init` names the exact property for every dependency you have to
|
|
109
|
+
choose, and `vfac plan` prints the connections an apply would establish before
|
|
110
|
+
you approve it. The dependency key comes from the Product, not from you —
|
|
111
|
+
`vfac product show <productId>` lists them.
|
|
112
|
+
|
|
113
|
+
This is not the same thing as the section above, and the difference is worth
|
|
114
|
+
holding on to:
|
|
115
|
+
|
|
116
|
+
| | |
|
|
117
|
+
| --- | --- |
|
|
118
|
+
| `valueFrom.resourceOutput` | read a value another resource publishes |
|
|
119
|
+
| `dependencyBindings` | use that resource as the PROVIDER for a declared dependency |
|
|
120
|
+
|
|
121
|
+
A connection is established once. **A manifest can make one and cannot move
|
|
122
|
+
one**: pointing an existing connection at a different resource is refused rather
|
|
123
|
+
than performed, because a file you re-apply on every push must not be able to
|
|
124
|
+
silently move a running service onto a different identity provider. The
|
|
125
|
+
credential the connection delivers never appears in your manifest, in a plan, or
|
|
126
|
+
in any `vfac` output — the providing service issues it and the platform stores
|
|
127
|
+
it.
|
|
128
|
+
|
|
94
129
|
## Running it again
|
|
95
130
|
|
|
96
131
|
**Send no secret.** A pipeline re-applies the same manifest on every push:
|
package/dist/vfac.mjs
CHANGED
|
@@ -15430,18 +15430,43 @@ function isTerminalAction(action) {
|
|
|
15430
15430
|
}
|
|
15431
15431
|
var ManifestActionSchema = external_exports.enum(MANIFEST_ACTIONS);
|
|
15432
15432
|
|
|
15433
|
+
// ../product-contracts/dist/resource-key.js
|
|
15434
|
+
var ResourceKeySchema = external_exports.string().regex(/^[a-z][a-z0-9-]{1,61}[a-z0-9]$/, "A resource key is 3\u201363 characters of lowercase letters, digits and hyphens, starting with a letter and ending with a letter or digit.");
|
|
15435
|
+
|
|
15433
15436
|
// ../product-contracts/dist/resource-output-reference.js
|
|
15434
15437
|
var ResourceOutputReferenceSchema = external_exports.object({
|
|
15435
15438
|
valueFrom: external_exports.object({
|
|
15436
15439
|
resourceOutput: external_exports.object({
|
|
15437
15440
|
/** The producing Resource's `metadata.key`, in the same Project. */
|
|
15438
|
-
resourceKey:
|
|
15441
|
+
resourceKey: ResourceKeySchema,
|
|
15439
15442
|
/** The key of an output that Resource's Product declares. */
|
|
15440
15443
|
output: external_exports.string().regex(/^[a-z][a-zA-Z0-9]{1,47}$/, "Output keys are camelCase.")
|
|
15441
15444
|
}).strict()
|
|
15442
15445
|
}).strict()
|
|
15443
15446
|
}).strict();
|
|
15444
15447
|
|
|
15448
|
+
// ../product-contracts/dist/dependency-binding.js
|
|
15449
|
+
var DependencyBindingSelectorSchema = external_exports.object({
|
|
15450
|
+
/** The providing Resource's `metadata.key`. */
|
|
15451
|
+
resourceKey: ResourceKeySchema,
|
|
15452
|
+
/**
|
|
15453
|
+
* The Project that Resource lives in. Absent means the consumer's own.
|
|
15454
|
+
*
|
|
15455
|
+
* PRESENT BECAUSE A BINDING IS TENANT-SCOPED AND A PROJECT IS NOT A RUNTIME
|
|
15456
|
+
* BOUNDARY: a provider anywhere in the Tenant can satisfy a consumer
|
|
15457
|
+
* anywhere in it, and that has been true since bindings existed. What the
|
|
15458
|
+
* field does NOT do is grant anything — the Control Plane opens that
|
|
15459
|
+
* Project from the caller's own assertion, and a Project the caller holds
|
|
15460
|
+
* nothing in answers exactly as a Project that does not exist.
|
|
15461
|
+
*
|
|
15462
|
+
* A plain bounded string, exactly as `spec.projectId` is: an id that does
|
|
15463
|
+
* not name a readable Project is refused where the refusal can be phrased
|
|
15464
|
+
* without leaking, which is not here.
|
|
15465
|
+
*/
|
|
15466
|
+
projectId: external_exports.string().min(1).max(64).optional()
|
|
15467
|
+
}).strict();
|
|
15468
|
+
var DependencyBindingsSchema = external_exports.record(ServiceBindingKeySchema, DependencyBindingSelectorSchema);
|
|
15469
|
+
|
|
15445
15470
|
// ../product-contracts/dist/product-instance-template.schema.js
|
|
15446
15471
|
var ProductInstanceTemplateSchema = external_exports.object({
|
|
15447
15472
|
apiVersion: ResourceApiVersionSchema,
|
|
@@ -15502,7 +15527,7 @@ var ProductInstanceTemplateSchema = external_exports.object({
|
|
|
15502
15527
|
* Shaped like a DNS label: it becomes a database predicate and a log
|
|
15503
15528
|
* field, and neither wants free text.
|
|
15504
15529
|
*/
|
|
15505
|
-
key:
|
|
15530
|
+
key: ResourceKeySchema.optional()
|
|
15506
15531
|
}).strict(),
|
|
15507
15532
|
spec: external_exports.object({
|
|
15508
15533
|
productId: external_exports.string().min(1).max(64),
|
|
@@ -15531,7 +15556,27 @@ var ProductInstanceTemplateSchema = external_exports.object({
|
|
|
15531
15556
|
* configuration schema by `resolveTemplate`, which is where the real
|
|
15532
15557
|
* shape lives — it differs per Product, so it cannot be stated here.
|
|
15533
15558
|
*/
|
|
15534
|
-
configuration: external_exports.record(external_exports.unknown()).optional()
|
|
15559
|
+
configuration: external_exports.record(external_exports.unknown()).optional(),
|
|
15560
|
+
/**
|
|
15561
|
+
* Which Resource satisfies each declared service dependency.
|
|
15562
|
+
*
|
|
15563
|
+
* A RESOURCE RELATION, NOT A SETTING, which is why it is a sibling of
|
|
15564
|
+
* `configuration` rather than a key inside it. `configuration` is the
|
|
15565
|
+
* Product's own settings and is validated against the Definition's
|
|
15566
|
+
* schema; this names another Resource, and the create envelope made the
|
|
15567
|
+
* identical split for the identical reason.
|
|
15568
|
+
*
|
|
15569
|
+
* OPTIONAL, PERMANENTLY. Every manifest written before this field
|
|
15570
|
+
* existed omits it, and absence has to keep meaning "leave the
|
|
15571
|
+
* connections alone" — otherwise re-applying an unchanged file would
|
|
15572
|
+
* release a binding somebody made in the portal.
|
|
15573
|
+
*
|
|
15574
|
+
* The keys the platform will ACCEPT here are narrower than the shape:
|
|
15575
|
+
* only a dependency whose interface the registry calls
|
|
15576
|
+
* `tenant-multiple` is one a customer chooses. `resolveTemplate` says
|
|
15577
|
+
* so against the pinned Definition, so the refusal names the field.
|
|
15578
|
+
*/
|
|
15579
|
+
dependencyBindings: DependencyBindingsSchema.optional()
|
|
15535
15580
|
}).strict()
|
|
15536
15581
|
}).strict();
|
|
15537
15582
|
|
|
@@ -15637,6 +15682,21 @@ function printPlan(plan, json) {
|
|
|
15637
15682
|
` from ${reference.resourceKey}.${reference.output}${reference.secret ? " [secret]" : ""}`
|
|
15638
15683
|
);
|
|
15639
15684
|
}
|
|
15685
|
+
const bindings = plan.dependencyBindings ?? [];
|
|
15686
|
+
if (bindings.length > 0) {
|
|
15687
|
+
lines.push(" dependency bindings:");
|
|
15688
|
+
for (const binding of bindings) {
|
|
15689
|
+
lines.push(` ${binding.dependencyKey}`);
|
|
15690
|
+
lines.push(
|
|
15691
|
+
` resource: ${binding.resourceKey}${binding.projectId ? ` (in ${binding.projectId})` : ""}`
|
|
15692
|
+
);
|
|
15693
|
+
if (binding.interfaceId !== void 0) {
|
|
15694
|
+
lines.push(
|
|
15695
|
+
` interface: ${binding.interfaceId}${binding.interfaceVersion ? `/${binding.interfaceVersion}` : ""}`
|
|
15696
|
+
);
|
|
15697
|
+
}
|
|
15698
|
+
}
|
|
15699
|
+
}
|
|
15640
15700
|
const required = plan.requiredSecrets ?? [];
|
|
15641
15701
|
if (required.length > 0) {
|
|
15642
15702
|
lines.push(" required secrets:");
|
|
@@ -16656,23 +16716,10 @@ async function runManifestInit(parsed) {
|
|
|
16656
16716
|
const chooseable = (product.dependencies ?? []).filter(
|
|
16657
16717
|
(dependency) => dependency.interface?.cardinality === "tenant-multiple"
|
|
16658
16718
|
);
|
|
16659
|
-
const mustChoose = chooseable.filter((dependency) => dependency.required === true);
|
|
16660
|
-
if (mustChoose.length > 0) {
|
|
16661
|
-
process.stderr.write(
|
|
16662
|
-
`${product.productId} requires a customer-selected service dependency that
|
|
16663
|
-
Product Deployment as Code v1 cannot express yet. Deploy it from the portal.
|
|
16664
|
-
`
|
|
16665
|
-
);
|
|
16666
|
-
for (const dependency of mustChoose) {
|
|
16667
|
-
process.stderr.write(` ${dependency.interface?.id ?? "a service"}`);
|
|
16668
|
-
process.stderr.write(dependency.reason ? ` \u2014 ${dependency.reason}
|
|
16669
|
-
` : "\n");
|
|
16670
|
-
}
|
|
16671
|
-
return 1;
|
|
16672
|
-
}
|
|
16673
16719
|
for (const dependency of chooseable) {
|
|
16720
|
+
const property = dependency.key === void 0 ? "spec.dependencyBindings.<key>.resourceKey" : `spec.dependencyBindings.${dependency.key}.resourceKey`;
|
|
16674
16721
|
notes.push(
|
|
16675
|
-
`${dependency.
|
|
16722
|
+
`${property} \u2014 ${dependency.required === true ? "required" : "optional, unless your settings make it required"}${dependency.interface?.id ? `. Provides ${dependency.interface.id}` : ""}. Name an existing resource: vfac get product-instances`
|
|
16676
16723
|
);
|
|
16677
16724
|
}
|
|
16678
16725
|
const regions = product.supportedRegions ?? [];
|