mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-08 00:03:07 +00:00
feat(git-sync): sync extra_perms for variables (#11004)
* feat(git-sync): sync extra_perms for variables Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hvv5B8VP5Di4dbcCiVyZyE * refactor: trim the variable ACL-sync comment to the 4-line limit Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hvv5B8VP5Di4dbcCiVyZyE * test: cover the revoke direction of variable extra_perms sync Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hvv5B8VP5Di4dbcCiVyZyE --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
670404ffe2
commit
ee9e550a48
@@ -104,7 +104,7 @@ export async function downloadZip(
|
||||
// from v1 the on-behalf-of address is stripped below, so the tarball sends the
|
||||
// `has_on_behalf_of` marker instead and never resolves an address.
|
||||
// `preserve_extra_perms=true` opts the tarball into surfacing granular ACLs
|
||||
// on flow / script / app rows. Default-off on the server protects cross-
|
||||
// on script / flow / app / variable rows. Default-off on the server protects cross-
|
||||
// workspace tarball imports from carrying ACLs that reference identities
|
||||
// missing in the target workspace; the CLI sync flow explicitly wants them.
|
||||
const baseParams = `&plain_secret=${plainSecrets ?? false
|
||||
|
||||
@@ -19,6 +19,7 @@ import { sep as SEP } from "node:path";
|
||||
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
import { ListableVariable } from "../../../gen/types.gen.ts";
|
||||
import { applyExtraPermsDiff } from "../../core/extra_perms.ts";
|
||||
|
||||
async function list(opts: GlobalOptions & { json?: boolean }) {
|
||||
if (opts.json) log.setSilent(true);
|
||||
@@ -97,6 +98,7 @@ export interface VariableFile {
|
||||
description: string;
|
||||
account?: number;
|
||||
is_oauth?: boolean;
|
||||
extra_perms?: Record<string, boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,37 +154,42 @@ export async function pushVariable(
|
||||
log.debug(`Variable ${remotePath} does not exist on remote`);
|
||||
}
|
||||
|
||||
// extra_perms is synced independently via /acls/* (see applyExtraPermsDiff)
|
||||
// so a perm-only edit never rewrites the variable value. Strip the field from
|
||||
// the body that goes to update_variable / create_variable and treat it as a
|
||||
// separate step both for the up-to-date short-circuit and after the write.
|
||||
const { extra_perms: localPerms, ...localVariableBody } = localVariable;
|
||||
|
||||
if (variable) {
|
||||
if (isSuperset(localVariable, variable)) {
|
||||
if (isSuperset(localVariableBody, variable)) {
|
||||
log.debug(`Variable ${remotePath} is up-to-date`);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
log.debug(`Variable ${remotePath} is not up-to-date, updating`);
|
||||
|
||||
log.debug(`Variable ${remotePath} is not up-to-date, updating`);
|
||||
|
||||
// Apply is_secret only when it differs from the remote (the value is always
|
||||
// sent, so the server allows the flag change). Upgrades (non-secret->secret)
|
||||
// always apply; downgrades only when explicitly allowed (single-file push) —
|
||||
// see allowSecretDowngrade. `undefined` leaves the flag untouched.
|
||||
let nextIsSecret: boolean | undefined = undefined;
|
||||
if (localVariable.is_secret !== variable.is_secret) {
|
||||
if (localVariable.is_secret) {
|
||||
nextIsSecret = true;
|
||||
} else if (allowSecretDowngrade) {
|
||||
nextIsSecret = false;
|
||||
// Apply is_secret only when it differs from the remote (the value is always
|
||||
// sent, so the server allows the flag change). Upgrades (non-secret->secret)
|
||||
// always apply; downgrades only when explicitly allowed (single-file push) —
|
||||
// see allowSecretDowngrade. `undefined` leaves the flag untouched.
|
||||
let nextIsSecret: boolean | undefined = undefined;
|
||||
if (localVariableBody.is_secret !== variable.is_secret) {
|
||||
if (localVariableBody.is_secret) {
|
||||
nextIsSecret = true;
|
||||
} else if (allowSecretDowngrade) {
|
||||
nextIsSecret = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await wmill.updateVariable({
|
||||
workspace,
|
||||
path: remotePath.replaceAll(SEP, "/"),
|
||||
alreadyEncrypted: !plainSecrets,
|
||||
requestBody: {
|
||||
...localVariable,
|
||||
is_secret: nextIsSecret,
|
||||
...(wsSpecific !== undefined ? { ws_specific: wsSpecific } : {}),
|
||||
},
|
||||
});
|
||||
await wmill.updateVariable({
|
||||
workspace,
|
||||
path: remotePath.replaceAll(SEP, "/"),
|
||||
alreadyEncrypted: !plainSecrets,
|
||||
requestBody: {
|
||||
...localVariableBody,
|
||||
is_secret: nextIsSecret,
|
||||
...(wsSpecific !== undefined ? { ws_specific: wsSpecific } : {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
log.info(colors.yellow.bold(`Creating new variable ${remotePath}...`));
|
||||
await wmill.createVariable({
|
||||
@@ -190,11 +197,22 @@ export async function pushVariable(
|
||||
alreadyEncrypted: !plainSecrets,
|
||||
requestBody: {
|
||||
path: remotePath.replaceAll(SEP, "/"),
|
||||
...localVariable,
|
||||
...localVariableBody,
|
||||
...(wsSpecific !== undefined ? { ws_specific: wsSpecific } : {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Synced whether or not the body changed. No refetch: folder perms are never
|
||||
// merged onto item.extra_perms, and the update/create body carries no
|
||||
// extra_perms, so the value getVariable read above is still the remote one.
|
||||
await applyExtraPermsDiff(
|
||||
workspace,
|
||||
"variable",
|
||||
remotePath.replaceAll(SEP, "/"),
|
||||
localPerms,
|
||||
(variable as any)?.extra_perms,
|
||||
);
|
||||
}
|
||||
|
||||
async function push(
|
||||
|
||||
Reference in New Issue
Block a user