@varde-flyt/vfac 0.10.0 → 0.12.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 +63 -13
  2. package/dist/vfac.mjs +86 -23
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -74,14 +74,21 @@ vfac manifest init --product <productId> --name "My resource" > resource.yaml
74
74
 
75
75
  ```sh
76
76
  vfac validate -f resource.yaml
77
- vfac plan -f resource.yaml
77
+ vfac plan -f resource.yaml --secret SETTING=env:VARIABLE
78
78
  vfac apply -f resource.yaml --secret SETTING=env:VARIABLE
79
79
  vfac get operation <operationId>
80
80
  vfac get product-instance <productInstanceId>
81
81
  ```
82
82
 
83
83
  Secrets are supplied per apply and never belong in the manifest — a manifest is a
84
- file you commit. `vfac plan` lists the ones an apply will still need.
84
+ file you commit. **Plan the run that sets one with the same `--secret` inputs the
85
+ apply will carry**: `plan` takes the flag exactly as `apply` does, and without it
86
+ the plan describes a different request.
87
+
88
+ `requiredSecrets` in a plan is what the Product requires for the configuration in
89
+ your document — not a list of what the platform is missing. It names a credential
90
+ whether or not this resource already holds one, so it is not a shopping list to
91
+ act on. `secretsToWrite` is the field that says what this apply would write.
85
92
 
86
93
  ### `metadata.key` is the name you choose
87
94
 
@@ -91,13 +98,14 @@ identifier in a manifest that is yours: a resource key is 3–63 characters of
91
98
  lowercase letters, digits and hyphens, starting with a letter and ending with a
92
99
  letter or digit.
93
100
 
94
- **A key is claimed for good, and a delete does not release it.** The record of a
95
- deleted resource keeps its key, so a manifest that named it can never apply again
96
- `vfac plan` answers `GONE` and a replacement needs a different key. That is
97
- worth knowing before the delete rather than after it, because after it there is
98
- nothing to undo; `vfac lifecycle delete` says the same thing at the prompt. If
99
- what you want is to stop the service and keep the option of bringing it back,
100
- `vfac lifecycle suspend` is the reversible one.
101
+ **A live resource holds its key, and a finished delete releases it.** The key
102
+ stays claimed while the teardown runs, so nothing can take the name before the
103
+ original is gone. Once the delete completes the name is free and the same
104
+ manifest applies again as a `CREATE`, building a NEW resource with nothing in
105
+ it. Reuse is not a restore, which is worth knowing before the delete rather than
106
+ after it: the apply succeeds, so nothing will stop you. `vfac lifecycle delete`
107
+ says the same thing at the prompt. If what you want is to stop the service and
108
+ keep its data, `vfac lifecycle suspend` is the reversible one.
101
109
 
102
110
  ## Reading a setting from another resource
103
111
 
@@ -175,7 +183,8 @@ spec:
175
183
  `vfac manifest init` names the exact property for every dependency you have to
176
184
  choose, and `vfac plan` prints the connections an apply would establish before
177
185
  you approve it. The dependency key comes from the Product, not from you —
178
- `vfac product show <productId>` lists them.
186
+ `vfac product show <productId>` lists every dependency with its key, its
187
+ capability, and whether a setting is what turns it on.
179
188
 
180
189
  This is not the same thing as the section above, and the difference is worth
181
190
  holding on to:
@@ -193,6 +202,44 @@ credential the connection delivers never appears in your manifest, in a plan, or
193
202
  in any `vfac` output — the providing service issues it and the platform stores
194
203
  it.
195
204
 
205
+ ## For an agent or a pipeline
206
+
207
+ Every command takes `--output json`, and it is the same data rather than a
208
+ prettier subset — the human format of `vfac product show` prints a readable
209
+ selection on purpose and says so at the foot of its output, and the JSON carries
210
+ the whole Product contract.
211
+
212
+ ```sh
213
+ vfac product show <productId> --output json
214
+ vfac plan -f resource.yaml --output json
215
+ vfac get product-instance <productInstanceId> --output json
216
+ vfac get product-instances --output json
217
+ ```
218
+
219
+ **Decide from the action, not from the exit code.** A plan that REFUSES still
220
+ exits 0: a plan reporting `UPGRADE_REQUIRED` did what it was asked, and the
221
+ answer is "not like this". A job that reads only the exit code treats that as
222
+ success and applies nothing.
223
+
224
+ ```sh
225
+ ACTION="$(vfac plan -f resource.yaml --output json | jq -r .action)"
226
+ ```
227
+
228
+ **And a non-zero exit is a real failure.** The code is not decoration: a
229
+ `NO_CHANGE` against a resource that is not actually serving exits 1, with
230
+ `--output json` as without it, because "nothing needed applying" and "it works"
231
+ are different answers. Read the action for what the plan MEANS and the exit code
232
+ for whether anything is WRONG — neither substitutes for the other.
233
+
234
+ The envelopes are stable at the top level:
235
+
236
+ | Command | Answers with |
237
+ | --- | --- |
238
+ | `vfac get product-instance <id> --output json` | `resource` and `lifecycle` — `status`, `outputs` and `conditions` are under `resource` |
239
+ | `vfac get product-instances --output json` | `productInstances` and `nextCursor` |
240
+
241
+ `vfac guide` is authoritative for what is inside them.
242
+
196
243
  ## Running it again
197
244
 
198
245
  **Send no secret.** A pipeline re-applies the same manifest on every push:
@@ -223,12 +270,15 @@ so this answers `UPDATE` and redeploys, even when the value is the value already
223
270
  stored:
224
271
 
225
272
  ```sh
273
+ vfac plan -f resource.yaml --secret SETTING=env:VARIABLE
226
274
  vfac apply -f resource.yaml --secret SETTING=env:VARIABLE
227
275
  ```
228
276
 
229
- Send secrets on the apply that creates the resource and on the run that rotates
230
- one, and on no other run. A pipeline that passes its secrets every run restarts
231
- your service every time somebody pushes a README.
277
+ Three runs carry a secret: the apply that creates the resource, the first time a
278
+ setting needs a credential on a resource that already exists, and a rotation. Send
279
+ none on any other run a pipeline that passes its secrets every run restarts your
280
+ service every time somebody pushes a README. Plan each of those three with the
281
+ same `--secret` inputs: with no secret to write, a rotation plans as `NO_CHANGE`.
232
282
 
233
283
  ## Where the Gate Secret goes
234
284
 
package/dist/vfac.mjs CHANGED
@@ -7932,8 +7932,9 @@ function renderOperation(operationId, data) {
7932
7932
  if (typeof retryable === "boolean") {
7933
7933
  const again = data["type"] === "PRODUCT_INSTANCE_UPGRADE" ? "`vfac lifecycle upgrade <pri_\u2026>`" : "`vfac lifecycle reconcile <pri_\u2026>`";
7934
7934
  const changeIt = data["type"] === "PRODUCT_INSTANCE_UPGRADE" ? "" : ", or `vfac apply` a corrected manifest";
7935
+ const cause2 = failure["cause"];
7935
7936
  lines.push(
7936
- retryable ? ` Another attempt can succeed: ${again}.` : ` Repeating this unchanged fails again \u2014 fix what the lines above name, then ${again}${changeIt}.`
7937
+ retryable ? ` Another attempt can succeed: ${again}.` : cause2 === "unknown" ? ` The platform could not classify this, so there is nothing here to correct. Read the whole record with \`vfac get operation ${operationId} --output json\`, and quote that id if you ask for help. Repeating it unchanged fails the same way.` : ` Repeating this unchanged fails again \u2014 fix what the lines above name, then ${again}${changeIt}.`
7937
7938
  );
7938
7939
  }
7939
7940
  }
@@ -13255,6 +13256,7 @@ function conditionHolds(condition, configuration) {
13255
13256
  // ../product-configuration/dist/customer-configuration-input.js
13256
13257
  var ABSOLUTE_MAX_STRING = MAX_DECLARABLE_STRING_LENGTH;
13257
13258
  var MAX_REPORTED_UNDECLARED_KEYS = 10;
13259
+ var UNDECLARED_SETTING_MESSAGE = "This Product does not declare a setting with that name.";
13258
13260
  function isPlainObject2(value) {
13259
13261
  return typeof value === "object" && value !== null && !Array.isArray(value);
13260
13262
  }
@@ -13338,7 +13340,7 @@ function validateObject(declaration, input, container, walk, root) {
13338
13340
  };
13339
13341
  const undeclared = Object.keys(input).filter((name) => !Object.hasOwn(declaration.properties, name));
13340
13342
  for (const name of undeclared.slice(0, MAX_REPORTED_UNDECLARED_KEYS)) {
13341
- report(walk, fieldFor(name), "This Product does not declare a setting with that name.");
13343
+ report(walk, fieldFor(name), UNDECLARED_SETTING_MESSAGE);
13342
13344
  }
13343
13345
  if (undeclared.length > MAX_REPORTED_UNDECLARED_KEYS) {
13344
13346
  report(walk, container === null ? "" : container, `${undeclared.length - MAX_REPORTED_UNDECLARED_KEYS} further settings are not declared by this Product.`);
@@ -13604,7 +13606,11 @@ function checkString(field, property, value, issues) {
13604
13606
  return void 0;
13605
13607
  }
13606
13608
  if (property.pattern !== void 0 && !matches(property.pattern, value)) {
13607
- issues.push({ field, message: "Value is not in the required format." });
13609
+ const expectation = property.description;
13610
+ issues.push({
13611
+ field,
13612
+ message: expectation === void 0 ? "Value is not in the required format." : `Value is not in the required format. ${expectation.replace(/\s+$/, "")}`
13613
+ });
13608
13614
  return void 0;
13609
13615
  }
13610
13616
  return value;
@@ -16801,12 +16807,27 @@ var MANIFEST_ACTIONS = [
16801
16807
  "ADOPT",
16802
16808
  /** The key names a resource and something in the document differs. */
16803
16809
  "UPDATE",
16804
- /** The key names a resource and the document matches it. A pipeline that runs
16805
- * on every push and changes nothing reports this, and it is SUCCESS. */
16810
+ /** The key names a resource and this REQUEST asks for nothing: no field
16811
+ * differs and no secret would be written.
16812
+ *
16813
+ * NOT "the document matches it", which is how it read and is a stronger claim
16814
+ * than the planner makes. What a document OMITS is left alone rather than
16815
+ * compared — a manifest naming no connections answers this beside a resource
16816
+ * that holds several — so this never means the file mirrors stored state.
16817
+ *
16818
+ * A pipeline that runs on every push and changes nothing reports it, and it
16819
+ * is SUCCESS. */
16806
16820
  "NO_CHANGE",
16807
- /** The key names a resource that was DELETED. Its own verb, because both
16821
+ /** `adopt` points at a resource that was DELETED. Its own verb, because both
16808
16822
  * alternatives are wrong: `NO_CHANGE` would call a missing resource fine, and
16809
- * `CREATE` cannot be honoured while the tombstone holds the key. */
16823
+ * `CREATE` would silently ignore the `adopt` the caller asked for.
16824
+ *
16825
+ * IT USED TO COVER A SECOND, COMMONER CASE: a manifest whose `metadata.key`
16826
+ * named a deleted resource, back when a key was claimed for ever. A completed
16827
+ * delete now releases the key, so that manifest is planned as a `CREATE` and
16828
+ * the verb narrowed to the one case a caller reaches by naming a `pri_…` by
16829
+ * hand. Kept in the vocabulary rather than removed: it is still produced, and
16830
+ * a Control Plane that still answers it must still be understood. */
16810
16831
  "GONE",
16811
16832
  /** The document pins a different release. Done through `:upgrade`, which has
16812
16833
  * the Definition's own capability check, its own version resolution and its
@@ -17082,7 +17103,9 @@ function secretWriteConsequence(plan) {
17082
17103
  const lines = ["the value you send REPLACES the stored one, and the resource redeploys."];
17083
17104
  if (plan.action === "UPDATE" && (plan.changes ?? []).length === 0) {
17084
17105
  lines.push("Nothing else changed \u2014 sending a secret is itself the change.");
17085
- lines.push("Omit --secret on runs that are not rotating one to get NO_CHANGE.");
17106
+ lines.push(
17107
+ "Omit --secret on a run that is not setting or changing one; that is what answers NO_CHANGE."
17108
+ );
17086
17109
  }
17087
17110
  return lines;
17088
17111
  }
@@ -17886,9 +17909,9 @@ async function runGet(parsed) {
17886
17909
  lines.push("", " Outputs");
17887
17910
  const width = Math.max(...names.map((name) => name.length));
17888
17911
  for (const name of names) {
17889
- lines.push(
17890
- ` ${name.padEnd(width)} ${isSecret2(name) ? "[secret]" : String(outputs[name] ?? "")}`
17891
- );
17912
+ const value = outputs[name];
17913
+ const shown = isSecret2(name) ? "[secret]" : value === void 0 || value === null ? "[not available]" : String(value);
17914
+ lines.push(` ${name.padEnd(width)} ${shown}`);
17892
17915
  const meaning = declared.find((entry) => entry["key"] === name);
17893
17916
  const description = meaning?.["description"];
17894
17917
  if (typeof description === "string") {
@@ -18078,14 +18101,41 @@ function renderProduct(product) {
18078
18101
  lines.push(` ${setting.label} ${setting.type ?? ""}`.trimEnd());
18079
18102
  }
18080
18103
  }
18081
- const needed = (product.dependencies ?? []).filter((dependency) => dependency.required);
18082
- if (needed.length > 0) {
18083
- lines.push("", " Requires a connection to another resource:");
18084
- for (const dependency of needed) {
18085
- lines.push(` ${dependency.productId ?? "(a Product providing a service)"}`);
18104
+ const dependencies = product.dependencies ?? [];
18105
+ if (dependencies.length > 0) {
18106
+ lines.push("", " Connections to another resource");
18107
+ for (const dependency of dependencies) {
18108
+ const when = dependency.requiredWhen?.property;
18109
+ const need = dependency.required ? "required" : when === void 0 ? "optional" : `required when ${when} is ${(dependency.requiredWhen?.anyOf ?? []).join(" or ")}`;
18110
+ lines.push(` ${dependency.key ?? "(no key)"} ${need}`);
18111
+ const capability = dependency.interface?.id;
18112
+ if (capability !== void 0) {
18113
+ lines.push(
18114
+ ` ${capability}${dependency.interface?.version ? `/${dependency.interface.version}` : ""}`
18115
+ );
18116
+ } else if (dependency.productId !== void 0) {
18117
+ lines.push(` ${dependency.productId}`);
18118
+ }
18086
18119
  if (dependency.reason) lines.push(` ${dependency.reason}`);
18087
18120
  }
18088
18121
  }
18122
+ const outputs = product.outputs ?? [];
18123
+ if (outputs.length > 0) {
18124
+ lines.push("", " Publishes");
18125
+ for (const output of outputs) {
18126
+ lines.push(
18127
+ ` ${output.key ?? ""} ${output.secret === true ? "secret" : output.type ?? ""}`.trimEnd()
18128
+ );
18129
+ }
18130
+ }
18131
+ const conditions = product.conditions ?? [];
18132
+ if (conditions.length > 0) {
18133
+ lines.push("", " Needs setting up outside the platform");
18134
+ for (const condition of conditions) {
18135
+ lines.push(` ${condition.key ?? ""} ${condition.displayName ?? ""}`.trimEnd());
18136
+ }
18137
+ }
18138
+ lines.push("", " `--output json` carries the whole declaration, this is a readable subset.");
18089
18139
  return lines.join("\n");
18090
18140
  }
18091
18141
  function wrap(text, width) {
@@ -18434,11 +18484,18 @@ Run \`vfac get product-instance pri_\u2026\` to see which ones this credential m
18434
18484
  `);
18435
18485
  return 2;
18436
18486
  }
18487
+ if (parsed.booleans.has("wait")) {
18488
+ process.stderr.write(
18489
+ "`--wait` belongs to `vfac apply`; `vfac lifecycle` does not support it.\nThis command answers with an operation id. Follow it with:\n vfac get operation op_\u2026\nand branch on the status it reports: SUCCEEDED, FAILED or CANCELLED. That\ncommand's exit code says whether the READ worked, never whether the operation\ndid \u2014 so a script that only checks the exit code reports a failed deploy as a\nsuccess.\n"
18490
+ );
18491
+ return 2;
18492
+ }
18437
18493
  if (command === "delete" && !parsed.booleans.has("yes")) {
18438
18494
  process.stderr.write(
18439
18495
  `delete removes ${instanceId} and everything it is serving. This cannot be undone.
18440
- If it was deployed from a manifest, its \`metadata.key\` stays claimed: a replacement
18441
- needs a different key, and re-applying the same file will answer GONE.
18496
+ If it was deployed from a manifest, its \`metadata.key\` is held until this delete
18497
+ FINISHES, then released. Applying the same file afterwards creates a new, empty
18498
+ resource under that key. It does not bring this one back.
18442
18499
  Re-run with --yes to confirm: vfac lifecycle delete ${instanceId} --yes
18443
18500
  Or suspend it instead, which stops it and keeps it: \`vfac lifecycle suspend\`.
18444
18501
  `
@@ -18539,7 +18596,7 @@ var USAGE = `vfac \u2014 deploy Varde Flyt resources from a manifest
18539
18596
  vfac manifest init --product <productId> --name <name> [--key <key>]
18540
18597
  [--region <region>] [--profile <profile>]
18541
18598
  vfac validate -f resource.yaml
18542
- vfac plan -f resource.yaml
18599
+ vfac plan -f resource.yaml [--secret NAME=env:VAR]
18543
18600
  vfac apply -f resource.yaml [--secret NAME=env:VAR] [--adopt pri_\u2026] [--wait]
18544
18601
  vfac get product-instances
18545
18602
  vfac get product-instance pri_\u2026 what it is, and its outputs
@@ -18556,9 +18613,12 @@ Options
18556
18613
  --project the Project to act in. Defaults to the stored context
18557
18614
  --endpoint your organization's portal. Defaults to VFAC_ENDPOINT, then the
18558
18615
  stored context
18559
- --secret NAME=env:VARIABLE \u2014 the value is read from the environment.
18560
- A value on the command line is in your shell history and in the
18561
- process table, so it is refused
18616
+ --secret plan and apply: NAME=env:VARIABLE \u2014 the value is read from the
18617
+ environment. Plan a run that sets a secret with the same
18618
+ --secret inputs the apply will carry; without them the plan
18619
+ answers about a different request. A value on the command line
18620
+ is in your shell history and in the process table, so it is
18621
+ refused
18562
18622
  --product which Product to write a starting manifest for
18563
18623
  --name what a person will call the resource
18564
18624
  --key its stable identity. Derived from --name when omitted
@@ -18597,7 +18657,10 @@ Authentication
18597
18657
 
18598
18658
  Exit codes
18599
18659
  0 done \u2014 including "no change", which is what a pipeline that changes nothing
18600
- should report
18660
+ should report, AND including a plan that refuses. plan answers what would
18661
+ happen; "it would be refused" is an answer, so it exits 0. Read the action,
18662
+ not the code: vfac plan -f resource.yaml --output json, then branch on
18663
+ .action
18601
18664
  1 refused, the request was wrong, or the resource matches your manifest and is
18602
18665
  NOT serving. "Nothing needed applying" is not the same as "it works", and a
18603
18666
  NO_CHANGE that could be a false green is the case this one exists for
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@varde-flyt/vfac",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "Deploy and manage Varde Flyt Products from a manifest.",
5
5
  "repository": {
6
6
  "type": "git",