fix(deploy): fail loud when stored build env vars cannot be read
Review feedback: a corrupt or undecryptable envelope previously returned an empty record, indistinguishable from no vars at all, letting the remote build run without its build-time secrets. The endpoint now returns an error so the build aborts with an actionable message. An empty record remains the response only for deployments that genuinely have none or are terminal. Also cancel the deployment best-effort when the version-skew guard aborts, instead of leaving it pending until the queue timeout reaps it.
This commit is contained in:
@@ -75,6 +75,10 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
|
||||
});
|
||||
}
|
||||
|
||||
// Vars exist but can't be read: fail LOUD. Returning an empty record here would
|
||||
// be indistinguishable from "there were none" and let the build run without its
|
||||
// build-time secrets (confusing failure at best, silently-wrong image at worst).
|
||||
// Concrete trigger: ENCRYPTION_KEY rotation during the build window.
|
||||
const envelope = EncryptedSecretValueSchema.safeParse(deployment.buildEnvVars);
|
||||
|
||||
if (!envelope.success) {
|
||||
@@ -82,9 +86,10 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
|
||||
deploymentId,
|
||||
environmentId: authenticatedEnv.id,
|
||||
});
|
||||
return json({ variables: {} } satisfies GetDeploymentBuildEnvVarsResponseBody, {
|
||||
status: 200,
|
||||
});
|
||||
return json(
|
||||
{ error: "The stored build environment variables could not be read. Retry the deploy." },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
let variables: Record<string, string>;
|
||||
@@ -98,9 +103,12 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
|
||||
environmentId: authenticatedEnv.id,
|
||||
error,
|
||||
});
|
||||
return json({ variables: {} } satisfies GetDeploymentBuildEnvVarsResponseBody, {
|
||||
status: 200,
|
||||
});
|
||||
return json(
|
||||
{
|
||||
error: "The stored build environment variables could not be decrypted. Retry the deploy.",
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
return json({ variables } satisfies GetDeploymentBuildEnvVarsResponseBody, { status: 200 });
|
||||
|
||||
@@ -690,6 +690,19 @@ export class CliApiClient {
|
||||
);
|
||||
}
|
||||
|
||||
// Best-effort cancel (204 on success, no body) — callers may ignore failures.
|
||||
async cancelDeployment(deploymentId: string, reason?: string) {
|
||||
if (!this.accessToken) {
|
||||
throw new Error("cancelDeployment: No access token");
|
||||
}
|
||||
|
||||
return fetch(`${this.apiURL}/api/v1/deployments/${deploymentId}/cancel`, {
|
||||
method: "POST",
|
||||
headers: this.getHeaders(),
|
||||
body: JSON.stringify({ reason }),
|
||||
});
|
||||
}
|
||||
|
||||
async getDeploymentBuildEnvVars(deploymentId: string) {
|
||||
if (!this.accessToken) {
|
||||
throw new Error("getDeploymentBuildEnvVars: No access token");
|
||||
|
||||
@@ -1556,6 +1556,18 @@ async function handleNativeBuildServerDeploy({
|
||||
Object.keys(bundleBuildEnvVars).length > 0 &&
|
||||
!deployment.buildEnvVarsStored
|
||||
) {
|
||||
// Courtesy cancel so the deployment doesn't linger as PENDING until the
|
||||
// queue timeout reaps it. Best-effort: the hard error below is what matters.
|
||||
const [cancelError] = await tryCatch(
|
||||
apiClient.cancelDeployment(deployment.id, "Build environment variables were not stored")
|
||||
);
|
||||
if (cancelError) {
|
||||
logger.debug("Failed to cancel deployment after missing build env vars ack", {
|
||||
deploymentId: deployment.id,
|
||||
error: cancelError,
|
||||
});
|
||||
}
|
||||
|
||||
$deploymentSpinner.stop("Failed to initialize deployment");
|
||||
log.error(
|
||||
chalk.bold(
|
||||
|
||||
Reference in New Issue
Block a user