stack secrets from external providers (1Password, HashiCorp Vault, Infisical and Doppler)

This commit is contained in:
jarek committed 2026-08-14 09:35:36 +02:00
1 parent 78d67d2dc1
commit 32a86129d0
11 files changed
+560 -137

No files matched your search

+12
View File
@@ -0,0 +1,12 @@
// The stack env-var name(s) that carry the external-secret-provider bulk selector.
// OP_ENVIRONMENT_ID is the legacy 1Password name; DOCKHAND_SECRET_SELECTOR is the
// generic one the UI writes now. The server resolves either (BULK_SELECTOR_VARS in
// stacks.ts), so both keep working.
//
// The stack modal shows the selector both as a labelled field AND as its plain
// DOCKHAND_SECRET_SELECTOR env row - they are one and the same value (the field is a
// live view of the env var), so the user sees plainly that it IS that variable.
export const SELECTOR_VARS = ['OP_ENVIRONMENT_ID', 'DOCKHAND_SECRET_SELECTOR'];
/** The canonical name the field writes to (a legacy OP_ENVIRONMENT_ID is normalized to this). */
export const BULK_SELECTOR_VAR = 'DOCKHAND_SECRET_SELECTOR';
+40
View File
@@ -0,0 +1,40 @@
/**
* Pure classification for the stack editor's live provider marker.
*
* A compose ${VAR} that is not defined locally is normally red MISSING. When a
* secret provider is bound, we live-probe it: if the key exists in the provider
* it becomes green IN VAULT instead. If the probe failed (provider unreachable /
* bad token / bad path) we keep the old red MISSING - never a false green.
*/
export type InVaultMarkerType = 'required' | 'missing' | 'invault';
/**
* Classifies one required compose var. `providerKeySet` is the set of key names
* the live probe confirmed present in the bound provider. `probeFailed` forces
* MISSING (we can't vouch for the key when the probe didn't succeed).
*/
export function classifyMarker(
name: string,
isMissing: boolean,
providerKeySet: Set<string>,
probeFailed: boolean
): InVaultMarkerType {
if (!isMissing) return 'required';
if (!probeFailed && providerKeySet.has(name)) return 'invault';
return 'missing';
}
/**
* Given the inline op:// (or other provider) references the client submitted as
* `{ varName, ref }` pairs and the SUBSET of ref STRINGS the provider resolved,
* returns the VAR NAMES whose ref resolved. `resolveSecretReferences` keys its
* result by the ref string, not the var name, so the caller maps it back here.
*/
export function resolvedRefVarNames(
refPairs: { varName: string; ref: string }[],
resolvedRefs: string[]
): string[] {
const resolved = new Set(resolvedRefs);
return refPairs.filter((p) => resolved.has(p.ref)).map((p) => p.varName);
}