-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: Deployment retry policy #248
Changes from all commits
6a0b379
c3efad4
5cead0b
ec7fc59
fde49d3
c220e0d
66067a9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -3,43 +3,31 @@ import { NextResponse } from "next/server"; | |||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
import { and, eq, isNull, takeFirstOrNull } from "@ctrlplane/db"; | ||||||||||||||||||||||||||||||||||
import { db } from "@ctrlplane/db/client"; | ||||||||||||||||||||||||||||||||||
import { | ||||||||||||||||||||||||||||||||||
deployment, | ||||||||||||||||||||||||||||||||||
environment, | ||||||||||||||||||||||||||||||||||
environmentPolicyApproval, | ||||||||||||||||||||||||||||||||||
job, | ||||||||||||||||||||||||||||||||||
jobVariable, | ||||||||||||||||||||||||||||||||||
release, | ||||||||||||||||||||||||||||||||||
releaseJobTrigger, | ||||||||||||||||||||||||||||||||||
resource, | ||||||||||||||||||||||||||||||||||
resourceMetadata, | ||||||||||||||||||||||||||||||||||
runbook, | ||||||||||||||||||||||||||||||||||
runbookJobTrigger, | ||||||||||||||||||||||||||||||||||
updateJob, | ||||||||||||||||||||||||||||||||||
user, | ||||||||||||||||||||||||||||||||||
} from "@ctrlplane/db/schema"; | ||||||||||||||||||||||||||||||||||
import { onJobCompletion } from "@ctrlplane/job-dispatch"; | ||||||||||||||||||||||||||||||||||
import * as schema from "@ctrlplane/db/schema"; | ||||||||||||||||||||||||||||||||||
import { updateJob } from "@ctrlplane/job-dispatch"; | ||||||||||||||||||||||||||||||||||
import { variablesAES256 } from "@ctrlplane/secrets"; | ||||||||||||||||||||||||||||||||||
import { Permission } from "@ctrlplane/validators/auth"; | ||||||||||||||||||||||||||||||||||
import { JobStatus } from "@ctrlplane/validators/jobs"; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
import { authn, authz } from "~/app/api/v1/auth"; | ||||||||||||||||||||||||||||||||||
import { request } from "~/app/api/v1/middleware"; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
type ApprovalJoinResult = { | ||||||||||||||||||||||||||||||||||
environment_policy_approval: typeof environmentPolicyApproval.$inferSelect; | ||||||||||||||||||||||||||||||||||
user: typeof user.$inferSelect | null; | ||||||||||||||||||||||||||||||||||
environment_policy_approval: typeof schema.environmentPolicyApproval.$inferSelect; | ||||||||||||||||||||||||||||||||||
user: typeof schema.user.$inferSelect | null; | ||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const getApprovalDetails = async (releaseId: string, policyId: string) => | ||||||||||||||||||||||||||||||||||
db | ||||||||||||||||||||||||||||||||||
.select() | ||||||||||||||||||||||||||||||||||
.from(environmentPolicyApproval) | ||||||||||||||||||||||||||||||||||
.leftJoin(user, eq(environmentPolicyApproval.userId, user.id)) | ||||||||||||||||||||||||||||||||||
.from(schema.environmentPolicyApproval) | ||||||||||||||||||||||||||||||||||
.leftJoin( | ||||||||||||||||||||||||||||||||||
schema.user, | ||||||||||||||||||||||||||||||||||
eq(schema.environmentPolicyApproval.userId, schema.user.id), | ||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||
.where( | ||||||||||||||||||||||||||||||||||
and( | ||||||||||||||||||||||||||||||||||
eq(environmentPolicyApproval.releaseId, releaseId), | ||||||||||||||||||||||||||||||||||
eq(environmentPolicyApproval.policyId, policyId), | ||||||||||||||||||||||||||||||||||
eq(schema.environmentPolicyApproval.releaseId, releaseId), | ||||||||||||||||||||||||||||||||||
eq(schema.environmentPolicyApproval.policyId, policyId), | ||||||||||||||||||||||||||||||||||
), | ||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||
.then(takeFirstOrNull) | ||||||||||||||||||||||||||||||||||
|
@@ -72,18 +60,38 @@ export const GET = request() | |||||||||||||||||||||||||||||||||
.handle<object, { params: { jobId: string } }>(async ({ db }, { params }) => { | ||||||||||||||||||||||||||||||||||
const row = await db | ||||||||||||||||||||||||||||||||||
.select() | ||||||||||||||||||||||||||||||||||
.from(job) | ||||||||||||||||||||||||||||||||||
.leftJoin(runbookJobTrigger, eq(runbookJobTrigger.jobId, job.id)) | ||||||||||||||||||||||||||||||||||
.leftJoin(runbook, eq(runbookJobTrigger.runbookId, runbook.id)) | ||||||||||||||||||||||||||||||||||
.leftJoin(releaseJobTrigger, eq(releaseJobTrigger.jobId, job.id)) | ||||||||||||||||||||||||||||||||||
.from(schema.job) | ||||||||||||||||||||||||||||||||||
.leftJoin( | ||||||||||||||||||||||||||||||||||
schema.runbookJobTrigger, | ||||||||||||||||||||||||||||||||||
eq(schema.runbookJobTrigger.jobId, schema.job.id), | ||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||
.leftJoin( | ||||||||||||||||||||||||||||||||||
schema.runbook, | ||||||||||||||||||||||||||||||||||
eq(schema.runbookJobTrigger.runbookId, schema.runbook.id), | ||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||
.leftJoin( | ||||||||||||||||||||||||||||||||||
schema.releaseJobTrigger, | ||||||||||||||||||||||||||||||||||
eq(schema.releaseJobTrigger.jobId, schema.job.id), | ||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||
.leftJoin( | ||||||||||||||||||||||||||||||||||
schema.environment, | ||||||||||||||||||||||||||||||||||
eq(schema.releaseJobTrigger.environmentId, schema.environment.id), | ||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||
.leftJoin( | ||||||||||||||||||||||||||||||||||
schema.resource, | ||||||||||||||||||||||||||||||||||
eq(schema.releaseJobTrigger.resourceId, schema.resource.id), | ||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||
.leftJoin( | ||||||||||||||||||||||||||||||||||
schema.release, | ||||||||||||||||||||||||||||||||||
eq(schema.releaseJobTrigger.releaseId, schema.release.id), | ||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||
.leftJoin( | ||||||||||||||||||||||||||||||||||
environment, | ||||||||||||||||||||||||||||||||||
eq(environment.id, releaseJobTrigger.environmentId), | ||||||||||||||||||||||||||||||||||
schema.deployment, | ||||||||||||||||||||||||||||||||||
eq(schema.release.deploymentId, schema.deployment.id), | ||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||
.where( | ||||||||||||||||||||||||||||||||||
and(eq(schema.job.id, params.jobId), isNull(schema.resource.deletedAt)), | ||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||
.leftJoin(resource, eq(resource.id, releaseJobTrigger.resourceId)) | ||||||||||||||||||||||||||||||||||
.leftJoin(release, eq(release.id, releaseJobTrigger.releaseId)) | ||||||||||||||||||||||||||||||||||
.leftJoin(deployment, eq(deployment.id, release.deploymentId)) | ||||||||||||||||||||||||||||||||||
.where(and(eq(job.id, params.jobId), isNull(resource.deletedAt))) | ||||||||||||||||||||||||||||||||||
.then(takeFirstOrNull); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
if (row == null) | ||||||||||||||||||||||||||||||||||
|
@@ -110,8 +118,8 @@ export const GET = request() | |||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const jobVariableRows = await db | ||||||||||||||||||||||||||||||||||
.select() | ||||||||||||||||||||||||||||||||||
.from(jobVariable) | ||||||||||||||||||||||||||||||||||
.where(eq(jobVariable.jobId, params.jobId)); | ||||||||||||||||||||||||||||||||||
.from(schema.jobVariable) | ||||||||||||||||||||||||||||||||||
.where(eq(schema.jobVariable.jobId, params.jobId)); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const variables = Object.fromEntries( | ||||||||||||||||||||||||||||||||||
jobVariableRows.map((v) => { | ||||||||||||||||||||||||||||||||||
|
@@ -126,8 +134,8 @@ export const GET = request() | |||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const metadata = await db | ||||||||||||||||||||||||||||||||||
.select() | ||||||||||||||||||||||||||||||||||
.from(resourceMetadata) | ||||||||||||||||||||||||||||||||||
.where(eq(resourceMetadata.resourceId, je.resource.id)) | ||||||||||||||||||||||||||||||||||
.from(schema.resourceMetadata) | ||||||||||||||||||||||||||||||||||
.where(eq(schema.resourceMetadata.resourceId, je.resource.id)) | ||||||||||||||||||||||||||||||||||
.then((rows) => Object.fromEntries(rows.map((m) => [m.key, m.value]))); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
return NextResponse.json({ | ||||||||||||||||||||||||||||||||||
|
@@ -137,7 +145,7 @@ export const GET = request() | |||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const bodySchema = updateJob; | ||||||||||||||||||||||||||||||||||
const bodySchema = schema.updateJob; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
export const PATCH = async ( | ||||||||||||||||||||||||||||||||||
req: NextRequest, | ||||||||||||||||||||||||||||||||||
|
@@ -146,21 +154,7 @@ export const PATCH = async ( | |||||||||||||||||||||||||||||||||
const response = await req.json(); | ||||||||||||||||||||||||||||||||||
const body = bodySchema.parse(response); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const je = await db | ||||||||||||||||||||||||||||||||||
.update(job) | ||||||||||||||||||||||||||||||||||
.set(body) | ||||||||||||||||||||||||||||||||||
.where(and(eq(job.id, params.jobId))) | ||||||||||||||||||||||||||||||||||
.returning() | ||||||||||||||||||||||||||||||||||
.then(takeFirstOrNull); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
if (je == null) | ||||||||||||||||||||||||||||||||||
return NextResponse.json( | ||||||||||||||||||||||||||||||||||
{ error: "Job execution not found" }, | ||||||||||||||||||||||||||||||||||
{ status: 404 }, | ||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
if (je.status === JobStatus.Completed) | ||||||||||||||||||||||||||||||||||
onJobCompletion(je).catch(console.error); | ||||||||||||||||||||||||||||||||||
const job = await updateJob(params.jobId, body); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
return NextResponse.json(je); | ||||||||||||||||||||||||||||||||||
return NextResponse.json(job); | ||||||||||||||||||||||||||||||||||
Comment on lines
+157
to
+159
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for updateJob operation The PATCH handler should include proper error handling for the updateJob operation, including handling of the "Job not found" error. - const job = await updateJob(params.jobId, body);
-
- return NextResponse.json(job);
+ try {
+ const job = await updateJob(params.jobId, body);
+ return NextResponse.json(job);
+ } catch (error) {
+ if (error instanceof Error && error.message === "Job not found") {
+ return NextResponse.json(
+ { error: "Job not found" },
+ { status: 404 }
+ );
+ }
+ throw error;
+ } 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Handle potential
NaN
values frome.target.valueAsNumber
When using
e.target.valueAsNumber
in theonChange
handler, if the input is empty or invalid (e.g., the user deletes the content), it may result inNaN
. This could cause issues with form validation and data submission. Consider adding a check to handleNaN
values and default to0
or an empty string.Apply this diff to handle
NaN
values:📝 Committable suggestion