mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-10 16:05:58 +00:00
feat(cli): add --yes, --secret/--no-secret and --description to variable add (#9548)
* feat(cli): add --yes, --secret/--no-secret and --description to variable add Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(cli): cover variable add create/update flag semantics Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(cli): warn on secret downgrade in variable add and pin preserve semantics in test Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
3585716872
commit
4e9e0c024b
@@ -185,7 +185,13 @@ async function push(
|
||||
}
|
||||
|
||||
async function add(
|
||||
opts: GlobalOptions & { public?: boolean; plainSecrets?: boolean },
|
||||
opts: GlobalOptions & {
|
||||
public?: boolean;
|
||||
plainSecrets?: boolean;
|
||||
yes?: boolean;
|
||||
secret?: boolean;
|
||||
description?: string;
|
||||
},
|
||||
value: string,
|
||||
remotePath: string
|
||||
) {
|
||||
@@ -196,6 +202,10 @@ async function add(
|
||||
return;
|
||||
}
|
||||
|
||||
// --secret/--no-secret take precedence over the legacy --public flag;
|
||||
// undefined means "secret on create, preserve current setting on update"
|
||||
const isSecret = opts.secret ?? (opts.public ? false : undefined);
|
||||
|
||||
if (
|
||||
await wmill.existsVariable({
|
||||
workspace: workspace.workspaceId,
|
||||
@@ -203,6 +213,7 @@ async function add(
|
||||
})
|
||||
) {
|
||||
if (
|
||||
!opts.yes &&
|
||||
!(await Confirm.prompt({
|
||||
message: `Variable already exist, do you want to update its value?`,
|
||||
default: true,
|
||||
@@ -210,22 +221,46 @@ async function add(
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (isSecret === false) {
|
||||
const existing = await wmill.getVariable({
|
||||
workspace: workspace.workspaceId,
|
||||
path: remotePath,
|
||||
decryptSecret: false,
|
||||
});
|
||||
if (existing.is_secret) {
|
||||
log.warn(
|
||||
colors.yellow(
|
||||
`Variable ${remotePath} is currently secret and will be downgraded to non-secret: its value will be stored in plaintext`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
log.info(colors.bold.yellow("Updating variable..."));
|
||||
await wmill.updateVariable({
|
||||
workspace: workspace.workspaceId,
|
||||
path: remotePath,
|
||||
alreadyEncrypted: false, // value from CLI is always plaintext
|
||||
requestBody: {
|
||||
value,
|
||||
...(isSecret !== undefined ? { is_secret: isSecret } : {}),
|
||||
...(opts.description !== undefined
|
||||
? { description: opts.description }
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
log.info(colors.bold.yellow("Creating variable..."));
|
||||
await wmill.createVariable({
|
||||
workspace: workspace.workspaceId,
|
||||
alreadyEncrypted: false, // value from CLI is always plaintext
|
||||
requestBody: {
|
||||
path: remotePath,
|
||||
value,
|
||||
is_secret: isSecret ?? true,
|
||||
description: opts.description ?? "",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
log.info(colors.bold.yellow("Pushing variable..."));
|
||||
|
||||
await pushVariable(
|
||||
workspace.workspaceId,
|
||||
remotePath + ".variable.yaml",
|
||||
undefined,
|
||||
{
|
||||
value,
|
||||
is_secret: !opts.public,
|
||||
description: "",
|
||||
},
|
||||
true // value from CLI is always plaintext — tell API not to treat it as pre-encrypted
|
||||
);
|
||||
log.info(colors.bold.underline.green(`Variable ${remotePath} pushed`));
|
||||
}
|
||||
|
||||
@@ -255,8 +290,24 @@ const command = new Command()
|
||||
"Create a new variable on the remote. This will update the variable if it already exists."
|
||||
)
|
||||
.arguments("<value:string> <remote_path:string>")
|
||||
.option(
|
||||
"--yes",
|
||||
"Skip confirmation prompt when updating an existing variable"
|
||||
)
|
||||
.option(
|
||||
"--secret",
|
||||
"Mark the variable as secret (default when creating a new variable)"
|
||||
)
|
||||
.option(
|
||||
"--no-secret",
|
||||
"Mark the variable as non-secret (when updating, the existing setting is preserved if neither --secret nor --no-secret is passed)"
|
||||
)
|
||||
.option(
|
||||
"--description <description:string>",
|
||||
"Set the variable description (when updating, the existing description is preserved if not passed)"
|
||||
)
|
||||
.option("--plain-secrets", "Push secrets as plain text")
|
||||
.option("--public", "Legacy option, use --plain-secrets instead")
|
||||
.option("--public", "Legacy option, use --no-secret instead")
|
||||
|
||||
.action(add as any);
|
||||
|
||||
|
||||
@@ -6705,8 +6705,12 @@ variable related commands
|
||||
- \`variable push <file_path:string> <remote_path:string>\` - Push a local variable spec. This overrides any remote versions.
|
||||
- \`--plain-secrets\` - Push secrets as plain text
|
||||
- \`variable add <value:string> <remote_path:string>\` - Create a new variable on the remote. This will update the variable if it already exists.
|
||||
- \`--yes\` - Skip confirmation prompt when updating an existing variable
|
||||
- \`--secret\` - Mark the variable as secret (default when creating a new variable)
|
||||
- \`--no-secret\` - Mark the variable as non-secret (when updating, the existing setting is preserved if neither --secret nor --no-secret is passed)
|
||||
- \`--description <description:string>\` - Set the variable description (when updating, the existing description is preserved if not passed)
|
||||
- \`--plain-secrets\` - Push secrets as plain text
|
||||
- \`--public\` - Legacy option, use --plain-secrets instead
|
||||
- \`--public\` - Legacy option, use --no-secret instead
|
||||
|
||||
### version
|
||||
|
||||
|
||||
@@ -133,6 +133,92 @@ describe("variable", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("add creates secret by default and preserves fields on update", async () => {
|
||||
await withTestBackend(async (backend, tempDir) => {
|
||||
await setupWorkspaceProfile(backend);
|
||||
|
||||
const uniqueId = Date.now();
|
||||
const varPath = `f/test/add_var_${uniqueId}`;
|
||||
|
||||
// Create: secret by default, --description sets the description
|
||||
const createResult = await backend.runCLICommand(
|
||||
["variable", "add", "v1", varPath, "--description", "first desc"],
|
||||
tempDir
|
||||
);
|
||||
expect(createResult.code).toEqual(0);
|
||||
|
||||
let apiResp = await backend.apiRequest!(
|
||||
`/api/w/${backend.workspace}/variables/get/${varPath}`
|
||||
);
|
||||
expect(apiResp.status).toEqual(200);
|
||||
let varData = await apiResp.json();
|
||||
expect(varData.is_secret).toBe(true);
|
||||
expect(varData.description).toBe("first desc");
|
||||
|
||||
// Update with --yes only: no prompt, is_secret and description preserved
|
||||
const updateResult = await backend.runCLICommand(
|
||||
["variable", "add", "v2", varPath, "--yes"],
|
||||
tempDir
|
||||
);
|
||||
expect(updateResult.code).toEqual(0);
|
||||
|
||||
apiResp = await backend.apiRequest!(
|
||||
`/api/w/${backend.workspace}/variables/get/${varPath}`
|
||||
);
|
||||
expect(apiResp.status).toEqual(200);
|
||||
varData = await apiResp.json();
|
||||
expect(varData.is_secret).toBe(true);
|
||||
expect(varData.description).toBe("first desc");
|
||||
expect(varData.value).toBe("v2");
|
||||
|
||||
// Update with --no-secret: flips to non-secret
|
||||
const noSecretResult = await backend.runCLICommand(
|
||||
["variable", "add", "v3", varPath, "--yes", "--no-secret"],
|
||||
tempDir
|
||||
);
|
||||
expect(noSecretResult.code).toEqual(0);
|
||||
|
||||
apiResp = await backend.apiRequest!(
|
||||
`/api/w/${backend.workspace}/variables/get/${varPath}`
|
||||
);
|
||||
expect(apiResp.status).toEqual(200);
|
||||
varData = await apiResp.json();
|
||||
expect(varData.is_secret).toBe(false);
|
||||
expect(varData.value).toBe("v3");
|
||||
|
||||
// Update the non-secret variable with no secret flags: is_secret must
|
||||
// stay false (preserved, not re-defaulted to secret)
|
||||
const preserveResult = await backend.runCLICommand(
|
||||
["variable", "add", "v4", varPath, "--yes"],
|
||||
tempDir
|
||||
);
|
||||
expect(preserveResult.code).toEqual(0);
|
||||
|
||||
apiResp = await backend.apiRequest!(
|
||||
`/api/w/${backend.workspace}/variables/get/${varPath}`
|
||||
);
|
||||
expect(apiResp.status).toEqual(200);
|
||||
varData = await apiResp.json();
|
||||
expect(varData.is_secret).toBe(false);
|
||||
expect(varData.value).toBe("v4");
|
||||
|
||||
// Explicit --secret flips it back
|
||||
const secretResult = await backend.runCLICommand(
|
||||
["variable", "add", "v5", varPath, "--yes", "--secret"],
|
||||
tempDir
|
||||
);
|
||||
expect(secretResult.code).toEqual(0);
|
||||
|
||||
apiResp = await backend.apiRequest!(
|
||||
`/api/w/${backend.workspace}/variables/get/${varPath}`
|
||||
);
|
||||
expect(apiResp.status).toEqual(200);
|
||||
varData = await apiResp.json();
|
||||
expect(varData.is_secret).toBe(true);
|
||||
expect(varData.value).toBe("v5");
|
||||
});
|
||||
});
|
||||
|
||||
test("pull retrieves variables into local files", async () => {
|
||||
await withTestBackend(async (backend, tempDir) => {
|
||||
await setupWorkspaceProfile(backend);
|
||||
|
||||
@@ -668,8 +668,12 @@ variable related commands
|
||||
- `variable push <file_path:string> <remote_path:string>` - Push a local variable spec. This overrides any remote versions.
|
||||
- `--plain-secrets` - Push secrets as plain text
|
||||
- `variable add <value:string> <remote_path:string>` - Create a new variable on the remote. This will update the variable if it already exists.
|
||||
- `--yes` - Skip confirmation prompt when updating an existing variable
|
||||
- `--secret` - Mark the variable as secret (default when creating a new variable)
|
||||
- `--no-secret` - Mark the variable as non-secret (when updating, the existing setting is preserved if neither --secret nor --no-secret is passed)
|
||||
- `--description <description:string>` - Set the variable description (when updating, the existing description is preserved if not passed)
|
||||
- `--plain-secrets` - Push secrets as plain text
|
||||
- `--public` - Legacy option, use --plain-secrets instead
|
||||
- `--public` - Legacy option, use --no-secret instead
|
||||
|
||||
### version
|
||||
|
||||
|
||||
@@ -3219,8 +3219,12 @@ variable related commands
|
||||
- \`variable push <file_path:string> <remote_path:string>\` - Push a local variable spec. This overrides any remote versions.
|
||||
- \`--plain-secrets\` - Push secrets as plain text
|
||||
- \`variable add <value:string> <remote_path:string>\` - Create a new variable on the remote. This will update the variable if it already exists.
|
||||
- \`--yes\` - Skip confirmation prompt when updating an existing variable
|
||||
- \`--secret\` - Mark the variable as secret (default when creating a new variable)
|
||||
- \`--no-secret\` - Mark the variable as non-secret (when updating, the existing setting is preserved if neither --secret nor --no-secret is passed)
|
||||
- \`--description <description:string>\` - Set the variable description (when updating, the existing description is preserved if not passed)
|
||||
- \`--plain-secrets\` - Push secrets as plain text
|
||||
- \`--public\` - Legacy option, use --plain-secrets instead
|
||||
- \`--public\` - Legacy option, use --no-secret instead
|
||||
|
||||
### version
|
||||
|
||||
|
||||
@@ -673,8 +673,12 @@ variable related commands
|
||||
- `variable push <file_path:string> <remote_path:string>` - Push a local variable spec. This overrides any remote versions.
|
||||
- `--plain-secrets` - Push secrets as plain text
|
||||
- `variable add <value:string> <remote_path:string>` - Create a new variable on the remote. This will update the variable if it already exists.
|
||||
- `--yes` - Skip confirmation prompt when updating an existing variable
|
||||
- `--secret` - Mark the variable as secret (default when creating a new variable)
|
||||
- `--no-secret` - Mark the variable as non-secret (when updating, the existing setting is preserved if neither --secret nor --no-secret is passed)
|
||||
- `--description <description:string>` - Set the variable description (when updating, the existing description is preserved if not passed)
|
||||
- `--plain-secrets` - Push secrets as plain text
|
||||
- `--public` - Legacy option, use --plain-secrets instead
|
||||
- `--public` - Legacy option, use --no-secret instead
|
||||
|
||||
### version
|
||||
|
||||
|
||||
Reference in New Issue
Block a user