@tonbo/cli 0.14.0 → 0.14.1
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/dist/bin/tonbo.js +206 -7
- package/package.json +1 -1
package/dist/bin/tonbo.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/app.ts
|
|
4
4
|
import { Command, InvalidArgumentError, Option } from "commander";
|
|
5
|
-
import { readFileSync } from "node:fs";
|
|
5
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
6
6
|
|
|
7
7
|
// ../../packages/tonbo-cli-core/dist/src/generated/contracts.js
|
|
8
8
|
var agentNameContract = {
|
|
@@ -815,6 +815,38 @@ var managementOperations = {
|
|
|
815
815
|
"idempotencyKey": false,
|
|
816
816
|
"cli": "tonbo machine shapes"
|
|
817
817
|
},
|
|
818
|
+
"listMachineSecrets": {
|
|
819
|
+
"method": "GET",
|
|
820
|
+
"path": "/machines/{name}/secrets",
|
|
821
|
+
"pathParams": [
|
|
822
|
+
"name"
|
|
823
|
+
],
|
|
824
|
+
"query": [],
|
|
825
|
+
"idempotencyKey": false,
|
|
826
|
+
"cli": "tonbo machine secret list"
|
|
827
|
+
},
|
|
828
|
+
"setMachineSecret": {
|
|
829
|
+
"method": "PUT",
|
|
830
|
+
"path": "/machines/{name}/secrets/{key}",
|
|
831
|
+
"pathParams": [
|
|
832
|
+
"name",
|
|
833
|
+
"key"
|
|
834
|
+
],
|
|
835
|
+
"query": [],
|
|
836
|
+
"idempotencyKey": false,
|
|
837
|
+
"cli": "tonbo machine secret set"
|
|
838
|
+
},
|
|
839
|
+
"unsetMachineSecret": {
|
|
840
|
+
"method": "DELETE",
|
|
841
|
+
"path": "/machines/{name}/secrets/{key}",
|
|
842
|
+
"pathParams": [
|
|
843
|
+
"name",
|
|
844
|
+
"key"
|
|
845
|
+
],
|
|
846
|
+
"query": [],
|
|
847
|
+
"idempotencyKey": false,
|
|
848
|
+
"cli": "tonbo machine secret unset"
|
|
849
|
+
},
|
|
818
850
|
"execMachineCommand": {
|
|
819
851
|
"method": "POST",
|
|
820
852
|
"path": "/machines/{name}/exec",
|
|
@@ -1644,6 +1676,11 @@ var machineTimings = {
|
|
|
1644
1676
|
"wakeHoldSeconds": 25,
|
|
1645
1677
|
"earlyReclaimSeconds": 120
|
|
1646
1678
|
};
|
|
1679
|
+
var machineSecretLimits = {
|
|
1680
|
+
"perMachine": 64,
|
|
1681
|
+
"valueMaxBytes": 65536,
|
|
1682
|
+
"totalMaxBytes": 262144
|
|
1683
|
+
};
|
|
1647
1684
|
|
|
1648
1685
|
// ../../packages/tonbo-cli-core/dist/src/bearer.js
|
|
1649
1686
|
var MANAGEMENT_AUDIENCE = cliSessionContract.audiences.management;
|
|
@@ -1923,6 +1960,21 @@ var ManagementApi = class {
|
|
|
1923
1960
|
listMachines(key, page) {
|
|
1924
1961
|
return this.page("listMachines", key, page);
|
|
1925
1962
|
}
|
|
1963
|
+
async listMachineSecrets(key, name) {
|
|
1964
|
+
const answer = await this.request("listMachineSecrets", key, {
|
|
1965
|
+
params: { name }
|
|
1966
|
+
});
|
|
1967
|
+
return answer.data;
|
|
1968
|
+
}
|
|
1969
|
+
setMachineSecret(key, name, secretKey, value) {
|
|
1970
|
+
return this.call("setMachineSecret", key, {
|
|
1971
|
+
params: { name, key: secretKey },
|
|
1972
|
+
body: value
|
|
1973
|
+
});
|
|
1974
|
+
}
|
|
1975
|
+
unsetMachineSecret(key, name, secretKey) {
|
|
1976
|
+
return this.request("unsetMachineSecret", key, { params: { name, key: secretKey } });
|
|
1977
|
+
}
|
|
1926
1978
|
/**
|
|
1927
1979
|
* A replay with the same idempotency key returns the Machine the first
|
|
1928
1980
|
* request created, so a transport failure or a 5xx answer is retried here
|
|
@@ -4442,6 +4494,7 @@ import { Help } from "commander";
|
|
|
4442
4494
|
|
|
4443
4495
|
// src/machine.ts
|
|
4444
4496
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
4497
|
+
import { readFileSync } from "node:fs";
|
|
4445
4498
|
|
|
4446
4499
|
// src/volume.ts
|
|
4447
4500
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
@@ -4479,7 +4532,19 @@ function volumeFields(volume) {
|
|
|
4479
4532
|
["Machine", boundText(volume)],
|
|
4480
4533
|
["Size", `${volume.size_gib} GiB`],
|
|
4481
4534
|
["Live bytes", `${bytes(volume.live_bytes)} (billed)`],
|
|
4482
|
-
["Last
|
|
4535
|
+
["Last meter report", volume.last_commit_at ?? "none yet"],
|
|
4536
|
+
[
|
|
4537
|
+
"Restored to",
|
|
4538
|
+
volume.restored_to ? `${volume.restored_to.committed_at} (generation ${volume.restored_to.data_generation})` : "none"
|
|
4539
|
+
],
|
|
4540
|
+
[
|
|
4541
|
+
"Backed up to",
|
|
4542
|
+
volume.backed_up_to ? volume.backed_up_to.committed_at ? `${volume.backed_up_to.committed_at} (generation ${volume.backed_up_to.data_generation}; ${volume.backed_up_to.dirty_bytes} bytes pending copy)` : `empty snapshot (generation ${volume.backed_up_to.data_generation}; ${volume.backed_up_to.dirty_bytes} bytes pending copy)` : "unknown"
|
|
4543
|
+
],
|
|
4544
|
+
[
|
|
4545
|
+
"Cold GETs",
|
|
4546
|
+
volume.cold ? `${volume.cold.demand_gets} demand, ${volume.cold.hydrate_gets} hydration` : "none"
|
|
4547
|
+
],
|
|
4483
4548
|
["Durability", VOLUME_DURABILITY_LINK],
|
|
4484
4549
|
["Created", volume.created_at]
|
|
4485
4550
|
]);
|
|
@@ -4595,7 +4660,9 @@ var MACHINE_ERROR_HINTS = {
|
|
|
4595
4660
|
image_pulling: "The Machine's image is still being pulled; the request was not run. Try again shortly; `tonbo machine show` reports the image's state.",
|
|
4596
4661
|
image_failed: "The Machine's image could not be pulled; `tonbo machine show` says why. Fix the image, then name it again with `tonbo machine update --image`.",
|
|
4597
4662
|
machine_kind_mismatch: "That Machine is one of your computers: `tonbo machine show` and `tonbo machine delete` serve it, and nothing else does.",
|
|
4598
|
-
volumes_not_offered: "Volumes are not offered to this account in the Machine's region yet; nothing was created or changed."
|
|
4663
|
+
volumes_not_offered: "Volumes are not offered to this account in the Machine's region yet; nothing was created or changed.",
|
|
4664
|
+
secret_env_conflict: "Use another secret key or remove the non-secret env variable with that name.",
|
|
4665
|
+
secret_limit: "Reduce the number or byte size of this Machine's secrets."
|
|
4599
4666
|
};
|
|
4600
4667
|
var MACHINE_IMAGE_FAILURE_HINTS = {
|
|
4601
4668
|
image_unavailable: "The registry no longer serves that image publicly, or served other bytes for it: publish it again, or use another image",
|
|
@@ -4704,7 +4771,9 @@ var MACHINE_WAKE_BLOCKED_HINTS = {
|
|
|
4704
4771
|
meter_lag: "Tonbo could not confirm the account's usage in time; the next request tries again.",
|
|
4705
4772
|
crash_backoff: "Its recent wakes failed; see `tonbo machine activations <name>`.",
|
|
4706
4773
|
capacity: "No host had room for its shape; the next request tries again.",
|
|
4707
|
-
region_closed: "Machines are closed in its region for now; the next request after they reopen wakes it."
|
|
4774
|
+
region_closed: "Machines are closed in its region for now; the next request after they reopen wakes it.",
|
|
4775
|
+
budget_slowdown: "Its budget is close to running out, and this wake would pass the budget's slow-down rate: sleep another Machine of the budget, or lower this one's shape, then wake it again. Machines already awake keep running.",
|
|
4776
|
+
budget_exhausted: "Its budget is used up for the month: it wakes after the budget resets, or once a person raises it in the console."
|
|
4708
4777
|
};
|
|
4709
4778
|
var MACHINE_ACCOUNT_EGRESS_BLOCKED_HINT = "If this is the free egress cap: 10 GiB a month without purchased credit; buy credit at <billing> to lift it.";
|
|
4710
4779
|
function conditionText(name, condition, billing) {
|
|
@@ -4885,7 +4954,7 @@ function specWarnings(deps, _ports, envKeys) {
|
|
|
4885
4954
|
const secret = envKeys.filter((key) => SECRET_LOOKING.test(key)).sort();
|
|
4886
4955
|
if (secret.length)
|
|
4887
4956
|
deps.notice(
|
|
4888
|
-
`${secret.join(", ")} ${secret.length === 1 ? "looks" : "look"} secret: env values are readable by every member through the API; Machine secrets
|
|
4957
|
+
`${secret.join(", ")} ${secret.length === 1 ? "looks" : "look"} secret: env values are readable by every member through the API; use Machine secrets for private values.`
|
|
4889
4958
|
);
|
|
4890
4959
|
}
|
|
4891
4960
|
function waitTimeout(options) {
|
|
@@ -4912,6 +4981,41 @@ async function waitForMachine(deps, key, name, firstWait, every, timeout, settle
|
|
|
4912
4981
|
wait = every;
|
|
4913
4982
|
}
|
|
4914
4983
|
}
|
|
4984
|
+
function secretFromFile(key, file, delivery) {
|
|
4985
|
+
if (!key || !file) throw new Error("Pass a secret as KEY=FILE.");
|
|
4986
|
+
const bytes2 = readFileSync(file === "-" ? 0 : file);
|
|
4987
|
+
if (bytes2.byteLength > machineSecretLimits.valueMaxBytes)
|
|
4988
|
+
throw new Error(`Secret ${key} exceeds the ${machineSecretLimits.valueMaxBytes}-byte limit.`);
|
|
4989
|
+
if (delivery === "file") return { delivery, value_base64: bytes2.toString("base64") };
|
|
4990
|
+
let value;
|
|
4991
|
+
try {
|
|
4992
|
+
value = new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
|
|
4993
|
+
} catch {
|
|
4994
|
+
throw new Error(`Secret ${key} is not UTF-8; use --secret-file for binary bytes.`);
|
|
4995
|
+
}
|
|
4996
|
+
return { delivery, value };
|
|
4997
|
+
}
|
|
4998
|
+
function createSecrets(options) {
|
|
4999
|
+
const values = {};
|
|
5000
|
+
let total = 0;
|
|
5001
|
+
for (const [delivery, entries] of [
|
|
5002
|
+
["env", options.secretEnv],
|
|
5003
|
+
["file", options.secretFile]
|
|
5004
|
+
]) {
|
|
5005
|
+
for (const entry of entries ?? []) {
|
|
5006
|
+
const separator = entry.indexOf("=");
|
|
5007
|
+
if (separator < 1) throw new Error("Pass a secret as KEY=FILE.");
|
|
5008
|
+
const key = entry.slice(0, separator);
|
|
5009
|
+
if (Object.hasOwn(values, key)) throw new Error(`Secret ${key} was named twice.`);
|
|
5010
|
+
const secret = secretFromFile(key, entry.slice(separator + 1), delivery);
|
|
5011
|
+
total += "value" in secret ? Buffer.byteLength(secret.value, "utf8") : Buffer.from(secret.value_base64, "base64").byteLength;
|
|
5012
|
+
if (Object.keys(values).length >= machineSecretLimits.perMachine || total > machineSecretLimits.totalMaxBytes)
|
|
5013
|
+
throw new Error("Machine secrets exceed their count or total byte limit.");
|
|
5014
|
+
values[key] = secret;
|
|
5015
|
+
}
|
|
5016
|
+
}
|
|
5017
|
+
return values;
|
|
5018
|
+
}
|
|
4915
5019
|
function deleteAfterSeconds(option) {
|
|
4916
5020
|
if (typeof option === "number") return option;
|
|
4917
5021
|
return option === false ? null : void 0;
|
|
@@ -4964,6 +5068,50 @@ async function machineListCommand(deps, options = {}) {
|
|
|
4964
5068
|
);
|
|
4965
5069
|
});
|
|
4966
5070
|
}
|
|
5071
|
+
async function machineSecretListCommand(deps, name, options = {}) {
|
|
5072
|
+
await machineCommand(async () => {
|
|
5073
|
+
const secrets = await deps.api.listMachineSecrets(await deps.auth.apiKey(), name);
|
|
5074
|
+
print(
|
|
5075
|
+
deps,
|
|
5076
|
+
options.json,
|
|
5077
|
+
secrets,
|
|
5078
|
+
secrets.length ? table(
|
|
5079
|
+
["KEY", "DELIVERY", "VERSION", "UPDATED", "BY"],
|
|
5080
|
+
secrets.map((secret) => [
|
|
5081
|
+
secret.key,
|
|
5082
|
+
secret.delivery,
|
|
5083
|
+
String(secret.version),
|
|
5084
|
+
secret.updated_at,
|
|
5085
|
+
secret.updated_by.sub
|
|
5086
|
+
])
|
|
5087
|
+
) : `Machine ${name} has no secrets.`
|
|
5088
|
+
);
|
|
5089
|
+
});
|
|
5090
|
+
}
|
|
5091
|
+
async function machineSecretSetCommand(deps, name, secretKey, options) {
|
|
5092
|
+
await machineCommand(async () => {
|
|
5093
|
+
const entry = await deps.api.setMachineSecret(
|
|
5094
|
+
await deps.auth.apiKey(),
|
|
5095
|
+
name,
|
|
5096
|
+
secretKey,
|
|
5097
|
+
secretFromFile(secretKey, options.from, options.delivery ?? "env")
|
|
5098
|
+
);
|
|
5099
|
+
print(
|
|
5100
|
+
deps,
|
|
5101
|
+
options.json,
|
|
5102
|
+
entry,
|
|
5103
|
+
`Set ${entry.key} on Machine ${name} (version ${entry.version}; applies at the next activation).`
|
|
5104
|
+
);
|
|
5105
|
+
});
|
|
5106
|
+
}
|
|
5107
|
+
async function machineSecretUnsetCommand(deps, name, secretKey) {
|
|
5108
|
+
await machineCommand(async () => {
|
|
5109
|
+
await deps.api.unsetMachineSecret(await deps.auth.apiKey(), name, secretKey);
|
|
5110
|
+
deps.output(
|
|
5111
|
+
`Removed ${secretKey} from Machine ${name}; the next activation will not receive it.`
|
|
5112
|
+
);
|
|
5113
|
+
});
|
|
5114
|
+
}
|
|
4967
5115
|
function conditionsCell(machine) {
|
|
4968
5116
|
const conditions = machine.status.conditions ?? [];
|
|
4969
5117
|
if (!conditions.length) return "-";
|
|
@@ -5046,6 +5194,7 @@ async function machineCreateCommand(deps, name, command, options = {}) {
|
|
|
5046
5194
|
if (!options.image) throw new Error("Pass --image <reference>.");
|
|
5047
5195
|
const timeout = waitTimeout(options);
|
|
5048
5196
|
const env = parseEnv(options.env ?? []);
|
|
5197
|
+
const secrets = createSecrets(options);
|
|
5049
5198
|
const ports = withDefault(parsePorts(options.port ?? []), options.defaultPort);
|
|
5050
5199
|
const key = await deps.auth.apiKey();
|
|
5051
5200
|
const bound = typeof options.volume === "string" ? await resolveVolumeBinding(deps, key, options.volume) : null;
|
|
@@ -5068,6 +5217,7 @@ async function machineCreateCommand(deps, name, command, options = {}) {
|
|
|
5068
5217
|
command: command.length ? command : void 0,
|
|
5069
5218
|
ports: ports.length ? ports : void 0,
|
|
5070
5219
|
env: Object.keys(env).length ? env : void 0,
|
|
5220
|
+
...Object.keys(secrets).length ? { secrets } : {},
|
|
5071
5221
|
sleep_after_seconds: options.sleepAfter,
|
|
5072
5222
|
idle_action: options.idleAction,
|
|
5073
5223
|
stop_grace_seconds: options.stopGrace,
|
|
@@ -5713,7 +5863,7 @@ var COMMAND_HELP = {
|
|
|
5713
5863
|
"tonbo machine create": {
|
|
5714
5864
|
about: [
|
|
5715
5865
|
"Creates a Machine, a VM booted from an image in one region. It has no database of its own: `--database <name>` binds one of the account's databases of its region, which it then reaches at `TONBO_DATABASE_URL`, and a database binds one Machine at a time; its files can last on a Volume (`--volume`), and its program can open any other database by URL with a key. The image is a public OCI image with a linux/arm64 variant, from Docker Hub or another public registry (`nginx:1.27`, `ghcr.io/org/app:v1`, or one pinned by digest), or `tonbo.dev/platform`, the platform image. A registry reachable only over IPv6, one named by an IP address, and one that redirects its manifests are refused. The CLI resolves a registry image to its linux/arm64 digest, and the image is pulled before the Machine leaves `creating`.",
|
|
5716
|
-
"The Machine is served at `https://<name>-<account>.tonbo.sh` on its default port. `--port <port>` serves a private port, which takes a member's token, and `--port <port>:public` an open one, which needs the `machines.public` capability. A command after `--` replaces the image's CMD. The CLI warns on stderr when the Machine has no port, since nothing can wake it then, and when an `--env` key looks secret: env values are readable by every member through the API.",
|
|
5866
|
+
"The Machine is served at `https://<name>-<account>.tonbo.sh` on its default port. `--port <port>` serves a private port, which takes a member's token, and `--port <port>:public` an open one, which needs the `machines.public` capability. A command after `--` replaces the image's CMD. `--secret-env KEY=FILE` and `--secret-file KEY=FILE` read initial secrets from local files and store them with the Machine before its first boot; their values are never returned. The CLI warns on stderr when the Machine has no port, since nothing can wake it then, and when an `--env` key looks secret: env values are readable by every member through the API.",
|
|
5717
5867
|
"The answer is the Machine while it is `creating`. With `--wait` the CLI reads it until it is `active` or `failed`, or `--timeout` passes: a failure prints what to do and exits 1, and a timeout says it is still creating and undoes nothing. A creation the network or a 5xx answer did not confirm is sent at most three times with the same idempotency key, each retry after its `Retry-After` (at most 30 s) or a short backoff; a `name_taken` refusal says which Machine holds the name and how old it is, in case it is yours from a lost reply. In a region not open for Machines the creation is refused with `region_not_offered`, and nothing is created."
|
|
5718
5868
|
],
|
|
5719
5869
|
examples: ["tonbo machine create web --image nginx:1.27 --port 80:public --wait"],
|
|
@@ -5738,6 +5888,28 @@ var COMMAND_HELP = {
|
|
|
5738
5888
|
examples: ["tonbo machine show web"],
|
|
5739
5889
|
arguments: NAME("Machine")
|
|
5740
5890
|
},
|
|
5891
|
+
"tonbo machine secret list": {
|
|
5892
|
+
about: [
|
|
5893
|
+
"Lists a Machine's secret keys, delivery, version and last writer. The values are never returned."
|
|
5894
|
+
],
|
|
5895
|
+
examples: ["tonbo machine secret list web"],
|
|
5896
|
+
arguments: { name: "the Machine whose secrets to list" }
|
|
5897
|
+
},
|
|
5898
|
+
"tonbo machine secret set": {
|
|
5899
|
+
about: [
|
|
5900
|
+
"Reads the secret from a local file, or standard input with `--from -`, without putting its value in shell history. `env` delivers UTF-8 text as an environment variable; `file` delivers arbitrary bytes under `$TONBO_SECRETS_DIR`. The new value applies at the next activation."
|
|
5901
|
+
],
|
|
5902
|
+
examples: ["tonbo machine secret set web API_KEY --from ./api-key.txt"],
|
|
5903
|
+
arguments: { name: "the Machine to change", key: "the secret's environment or file name" },
|
|
5904
|
+
defaults: { "--delivery": "env" }
|
|
5905
|
+
},
|
|
5906
|
+
"tonbo machine secret unset": {
|
|
5907
|
+
about: [
|
|
5908
|
+
"Removes a Machine secret whether or not it exists. A running activation keeps its existing value until the next activation."
|
|
5909
|
+
],
|
|
5910
|
+
examples: ["tonbo machine secret unset web API_KEY"],
|
|
5911
|
+
arguments: { name: "the Machine to change", key: "the secret to remove" }
|
|
5912
|
+
},
|
|
5741
5913
|
"tonbo machine update": {
|
|
5742
5914
|
about: [
|
|
5743
5915
|
"Changes only what its options name. It reads the Machine and sends the ETag it read, or `--if-match`'s, so a change someone made in between is refused with `precondition_failed` rather than overwritten; a Machine whose creation failed is refused with `machine_failed`. `--env` and `--unset-env` change single variables; `--port` replaces every port, and the current default port stays the default while it is still listed.",
|
|
@@ -6526,7 +6698,7 @@ async function sshCertificateIssueCommand(deps, options) {
|
|
|
6526
6698
|
|
|
6527
6699
|
// src/app.ts
|
|
6528
6700
|
var packageVersion = JSON.parse(
|
|
6529
|
-
|
|
6701
|
+
readFileSync2(new URL("../../package.json", import.meta.url), "utf8")
|
|
6530
6702
|
);
|
|
6531
6703
|
function createDependencies(selection = {}) {
|
|
6532
6704
|
const api = apiOrigin();
|
|
@@ -6900,6 +7072,10 @@ function createProgram(makeDependencies = createDependencies) {
|
|
|
6900
7072
|
).option("--no-database", "bind no database (the default)"),
|
|
6901
7073
|
false
|
|
6902
7074
|
).option(
|
|
7075
|
+
"--secret-env <KEY=FILE>",
|
|
7076
|
+
"read a UTF-8 environment secret from FILE; repeat",
|
|
7077
|
+
repeated
|
|
7078
|
+
).option("--secret-file <KEY=FILE>", "read binary file secret from FILE; repeat", repeated).option(
|
|
6903
7079
|
"--idempotency-key <key>",
|
|
6904
7080
|
"reuse the key of an unconfirmed creation to get the Machine it created"
|
|
6905
7081
|
).option("--wait", "wait until the Machine is active, or say why it failed").option("--timeout <seconds>", "how long --wait waits, in seconds", parseWhole),
|
|
@@ -6913,6 +7089,29 @@ function createProgram(makeDependencies = createDependencies) {
|
|
|
6913
7089
|
).action(
|
|
6914
7090
|
async (name, options) => machineShowCommand(dependencies(), name, options)
|
|
6915
7091
|
);
|
|
7092
|
+
const machineSecret = machine.command("secret").description("manage a Machine's write-only secrets");
|
|
7093
|
+
operationOptions(
|
|
7094
|
+
machineSecret.command("list <name>").description("list secret keys and delivery, never values"),
|
|
7095
|
+
"listMachineSecrets"
|
|
7096
|
+
).action(
|
|
7097
|
+
async (name, options) => machineSecretListCommand(dependencies(), name, options)
|
|
7098
|
+
);
|
|
7099
|
+
operationOptions(
|
|
7100
|
+
machineSecret.command("set <name> <key>").description(
|
|
7101
|
+
"set a secret from a local file or standard input; applies at the next activation"
|
|
7102
|
+
).requiredOption("--from <file>", "read the value from FILE, or - for standard input").addOption(
|
|
7103
|
+
new Option("--delivery <mode>", "env or file (binary)").choices(["env", "file"]).default("env")
|
|
7104
|
+
),
|
|
7105
|
+
"setMachineSecret"
|
|
7106
|
+
).action(
|
|
7107
|
+
async (name, key, options) => machineSecretSetCommand(dependencies(), name, key, options)
|
|
7108
|
+
);
|
|
7109
|
+
operationOptions(
|
|
7110
|
+
machineSecret.command("unset <name> <key>").description("remove a secret at the next activation"),
|
|
7111
|
+
"unsetMachineSecret"
|
|
7112
|
+
).action(
|
|
7113
|
+
async (name, key) => machineSecretUnsetCommand(dependencies(), name, key)
|
|
7114
|
+
);
|
|
6916
7115
|
operationOptions(
|
|
6917
7116
|
machineSpecOptions(
|
|
6918
7117
|
machine.command("update <name> [command...]").description(
|