deleting a secret provider warns which stacks it unbinds

This commit is contained in:
Jarek Krochmalski committed 2026-09-06 17:03:47 +05:30
1 parent 3a5dce99e2
commit 07f29f5afc
9 files changed
+186 -10

No files matched your search

+27 -1
View File
@@ -26,6 +26,13 @@
/** Bound provider type/name, for the injected banner + pills. */
providerType?: string | null;
providerName?: string | null;
/**
* Whether a secret provider is CURRENTLY bound to this stack. When false but
* injectedSecretKeys is non-empty, the keys are historical (from the last deploy)
* and the next deploy will drop them - the banner says so instead of implying they
* are still active (#1522).
*/
providerBound?: boolean;
/** Set when the live provider probe failed - shown as an amber line. */
probeError?: string | null;
/** Key NAMES the live probe found in the bound provider (present RIGHT NOW).
@@ -52,6 +59,7 @@
injectedSecretKeys = [],
providerType = null,
providerName = null,
providerBound = true,
probeError = null,
providerKeySet = new Set<string>(),
showInterpolationHint = false,
@@ -524,7 +532,7 @@
</div>
{/if}
<!-- Provider-injected secrets loaded at the last deploy -->
{#if injectedSecretKeys.length > 0}
{#if injectedSecretKeys.length > 0 && providerBound}
<div class="flex items-start gap-2 px-2.5 py-2 rounded bg-emerald-50 dark:bg-emerald-900/20 border border-emerald-200 dark:border-emerald-800/50">
<Check class="w-4 h-4 text-emerald-500 shrink-0 mt-0.5" />
<div class="text-xs text-emerald-700 dark:text-emerald-300 min-w-0">
@@ -552,6 +560,24 @@
</div>
</div>
</div>
{:else if injectedSecretKeys.length > 0 && !providerBound}
<!-- Historical: keys were injected on a previous deploy but no provider is bound now (#1522). -->
<div class="flex items-start gap-2 px-2.5 py-2 rounded bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800/50">
<AlertTriangle class="w-4 h-4 text-amber-500 shrink-0 mt-0.5" />
<div class="text-xs text-amber-700 dark:text-amber-300 min-w-0">
<div class="font-semibold">No secret provider is bound</div>
<p class="text-amber-600 dark:text-amber-400 mt-0.5">
These {injectedSecretKeys.length} secret{injectedSecretKeys.length === 1 ? ' was' : 's were'} injected on the last deploy but the stack is no longer bound to a provider. The next deploy will drop {injectedSecretKeys.length === 1 ? 'it' : 'them'}. Reselect a provider to keep them.
</p>
<div class="flex flex-wrap gap-1.5 mt-1.5">
{#each injectedSecretKeys as key}
<span class="inline-flex items-center gap-1 font-mono text-2xs font-semibold px-2 py-0.5 rounded-full bg-amber-100 dark:bg-amber-800/40 border border-amber-300 dark:border-amber-700 line-through decoration-amber-500/60">
<KeyRound class="w-2.5 h-2.5" />{key}
</span>
{/each}
</div>
</div>
</div>
{/if}
<!-- Live provider probe couldn't reach the provider: keys fall back to MISSING -->
{#if probeError}
+14
View File
@@ -354,6 +354,20 @@ export async function getSecretProviderById(id: number): Promise<SecretProviderW
};
}
/** Lightweight existence check (no config decrypt), for validating a bound provider id. */
export async function secretProviderExists(id: number): Promise<boolean> {
const rows = await db.select({ id: secretProviders.id }).from(secretProviders).where(eq(secretProviders.id, id));
return rows.length > 0;
}
/** Stacks currently bound to a secret provider, so a delete can warn which ones lose it. */
export async function getStacksUsingSecretProvider(id: number): Promise<Array<{ stackName: string; environmentId: number | null }>> {
return db
.select({ stackName: stackSources.stackName, environmentId: stackSources.environmentId })
.from(stackSources)
.where(eq(stackSources.secretProviderId, id));
}
export async function createSecretProvider(data: {
type: SecretProviderType;
name: string;
+33
View File
@@ -0,0 +1,33 @@
/**
* Pure git-stack deploy decisions, extracted so they are unit-testable without the
* I/O-heavy deployGitStack. A manual/one-off deploy (`force`) deploys but does NOT
* force-recreate, so only shouldDeployGitStack takes it.
*/
/**
* Whether to deploy at all. A manual/one-off deploy (`force`) or a real git change
* always deploys; the "always redeploy on webhook/scheduled sync" setting forces a
* deploy even when git reported no changes.
*/
export function shouldDeployGitStack(input: {
force: boolean;
forceRedeploy: boolean;
gitUpdated: boolean;
}): boolean {
return input.force || input.forceRedeploy || input.gitUpdated;
}
/**
* Whether to pass `--force-recreate`. Recreate on a real git change OR when the user
* enabled "always redeploy": that setting exists to force the container back in step
* with its configuration (a changed env var, secret, or rebound provider leaves no git
* diff), and a plain `up` without --force-recreate would no-op and never re-inject the
* shell-env secrets (#1523). Default (setting off, no git change) does NOT recreate, so
* an ordinary sync never causes a surprise recreate.
*/
export function shouldForceRecreateGitStack(input: {
forceRedeploy: boolean;
gitUpdated: boolean;
}): boolean {
return input.gitUpdated || input.forceRedeploy;
}