@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.
Files changed (3) hide show
  1. package/README.md +35 -0
  2. package/dist/vfac.mjs +65 -18
  3. 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: 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."),
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: 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.").optional()
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.interface?.id ?? "a service connection"} \u2014 optional here, but this Product may require it depending on your settings. A manifest cannot supply one yet`
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 ?? [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@varde-flyt/vfac",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Deploy and manage Varde Flyt Products from a manifest.",
5
5
  "repository": {
6
6
  "type": "git",