Skip to content
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

init new schema updates #110

Merged
merged 5 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export const CreateReleaseDialog: React.FC<{
const router = useRouter();
const utils = api.useUtils();
const onSubmit = form.handleSubmit(async (data) => {
const release = await create.mutateAsync(data);
const release = await create.mutateAsync({ ...data, name: data.version });
await utils.release.list.invalidate({ deploymentId: release.deploymentId });

const deployment = deployments.data?.find(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,83 +42,85 @@ export const TargetReleaseTable: React.FC<TargetReleaseTableProps> = ({
{_.chain(releaseJobTriggerQuery.data)
.groupBy((r) => r.environmentId)
.entries()
.map(([envId, jobs]) => (
<Fragment key={envId}>
<TableRow className={cn("sticky bg-neutral-800/40")}>
<TableCell colSpan={6}>
{jobs[0]?.environment != null && (
<div className="flex items-center gap-4">
<div className="flex-grow">
{jobs[0].environment.name}
.map(([envId, jobs]) => {
return (
<Fragment key={envId}>
<TableRow className={cn("sticky bg-neutral-800/40")}>
<TableCell colSpan={6}>
{jobs[0]?.environment != null && (
<div className="flex items-center gap-4">
<div className="flex-grow">
{jobs[0].environment.name}
</div>
</div>
</div>
)}
</TableCell>
</TableRow>
{jobs.map((job, idx) => (
<TableRow
key={job.id}
className={cn(
idx !== jobs.length - 1 && "border-b-neutral-800/50",
)}
>
<TableCell className="hover:bg-neutral-800/55">
<Link
href={`${pathname}?target_id=${job.target?.id}`}
className="block w-full hover:text-blue-300"
>
{job.target?.name}
</Link>
</TableCell>
<TableCell>
<div className="flex items-center gap-1">
<JobTableStatusIcon status={job.job.status} />
{capitalCase(job.job.status)}
</div>
</TableCell>
<TableCell>{job.type}</TableCell>
<TableCell>
{job.job.externalId != null ? (
<code className="font-mono text-xs">
{job.job.externalId}
</code>
) : (
<span className="text-sm text-muted-foreground">
No external ID
</span>
)}
</TableCell>
<TableCell>
{job.job.externalUrl != null ? (
</TableRow>
{jobs.map((job, idx) => (
<TableRow
key={job.id}
className={cn(
idx !== jobs.length - 1 && "border-b-neutral-800/50",
)}
>
<TableCell className="hover:bg-neutral-800/55">
<Link
href={job.job.externalUrl}
rel="nofollow noreferrer"
target="_blank"
href={`${pathname}?target_id=${job.target?.id}`}
className="block w-full hover:text-blue-300"
>
{job.job.externalUrl}
{job.target?.name}
</Link>
) : (
<span className="text-sm text-muted-foreground">
No external URL
</span>
)}
</TableCell>
<TableCell>
<TargetDropdownMenu
release={release}
deploymentName={deploymentName}
target={job.target}
environmentId={job.environmentId}
job={{
id: job.job.id,
status: job.job.status as JobStatus,
}}
/>
</TableCell>
</TableRow>
))}
</Fragment>
))
</TableCell>
<TableCell>
<div className="flex items-center gap-1">
<JobTableStatusIcon status={job.job.status} />
{capitalCase(job.job.status)}
</div>
</TableCell>
<TableCell>{job.type}</TableCell>
<TableCell>
{job.job.externalId != null ? (
<code className="font-mono text-xs">
{job.job.externalId}
</code>
) : (
<span className="text-sm text-muted-foreground">
No external ID
</span>
)}
</TableCell>
<TableCell>
{/* {job.job.externalUrl != null ? (
<Link
href={job.job.externalUrl}
rel="nofollow noreferrer"
target="_blank"
>
{job.job.externalUrl}
</Link>
) : (
<span className="text-sm text-muted-foreground">
No external URL
</span>
)} */}
</TableCell>
<TableCell>
<TargetDropdownMenu
release={release}
deploymentName={deploymentName}
target={job.target}
environmentId={job.environmentId}
job={{
id: job.job.id,
status: job.job.status as JobStatus,
}}
/>
</TableCell>
</TableRow>
))}
</Fragment>
);
})
.value()}
</TableBody>
</Table>
Expand Down
26 changes: 22 additions & 4 deletions apps/webservice/src/app/api/github/webhook/workflow/handler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { WorkflowRunEvent } from "@octokit/webhooks-types";

import { eq, takeFirstOrNull } from "@ctrlplane/db";
import { and, eq, takeFirstOrNull } from "@ctrlplane/db";
import { db } from "@ctrlplane/db/client";
import * as schema from "@ctrlplane/db/schema";
import { onJobCompletion } from "@ctrlplane/job-dispatch";
Expand Down Expand Up @@ -29,16 +29,14 @@ export const handleWorkflowWebhookEvent = async (event: WorkflowRunEvent) => {
repository,
} = event.workflow_run;

const externalUrl = `https://github.com/${repository.owner.login}/${repository.name}/actions/runs/${id}`;

const status =
conclusion != null
? convertConclusion(conclusion)
: convertStatus(externalStatus);

const job = await db
.update(schema.job)
.set({ status, externalUrl })
.set({ status })
.where(eq(schema.job.externalId, id.toString()))
.returning()
.then(takeFirstOrNull);
Expand All @@ -49,5 +47,25 @@ export const handleWorkflowWebhookEvent = async (event: WorkflowRunEvent) => {
// the first event lacks the run ID, so we skip it and wait for the next event.
if (job == null) return;

const existingUrlMetadata = await db
.select()
.from(schema.jobMetadata)
.where(
and(
eq(schema.jobMetadata.jobId, job.id),
eq(schema.jobMetadata.key, "ctrlplane/links"),
),
)
.then(takeFirstOrNull);

const links = JSON.stringify({
...JSON.parse(existingUrlMetadata?.value ?? "{}"),
GitHub: `https://github.com/${repository.owner.login}/${repository.name}/actions/runs/${id}`,
});

await db
.insert(schema.jobMetadata)
.values([{ jobId: job.id, key: "ctrlplane/links", value: links }]);

if (job.status === JobStatus.Completed) return onJobCompletion(job);
};
20 changes: 17 additions & 3 deletions apps/webservice/src/app/api/v1/releases/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,21 @@ import { Permission } from "@ctrlplane/validators/auth";

import { getUser } from "~/app/api/v1/auth";

const bodySchema = createRelease.and(
z.object({ metadata: z.record(z.string()).optional() }),
);

export const POST = async (req: NextRequest) => {
const user = await getUser(req);
if (!user)
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });

try {
const response = await req.json();
const body = createRelease.safeParse(response);
const body = bodySchema.safeParse(response);
if (!body.success) return NextResponse.json(body.error, { status: 400 });

const { metadata = {}, ...releaseData } = body.data;
const canCreateReleases = await can()
.user(user.id)
.perform(Permission.ReleaseCreate)
Expand All @@ -31,11 +36,20 @@ export const POST = async (req: NextRequest) => {

const release = await db
.insert(schema.release)
.values(body.data)
.values(releaseData)
.returning()
.then(takeFirst);

return NextResponse.json(release, { status: 201 });
if (Object.keys(metadata).length > 0)
await db.insert(schema.releaseMetadata).values(
Object.entries(metadata).map(([key, value]) => ({
releaseId: release.id,
key,
value,
})),
);

return NextResponse.json({ ...release, metadata }, { status: 201 });
} catch (error) {
if (error instanceof z.ZodError)
return NextResponse.json({ error: error.errors }, { status: 400 });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const variableOptions = [
{ key: "workspace.name", description: "Workspace Name" },
{ key: "workspace.id", description: "Workspace ID" },
{ key: "job.id", description: "Job ID" },
{ key: "job.externalRunId", description: "Job External Run ID" },
{ key: "job.externalId", description: "Job Run ID" },
{ key: "job.status", description: "Job Status" },
{ key: "job.message", description: "Job Message" },
];
Expand Down
6 changes: 3 additions & 3 deletions integrations/kubernetes-job-agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const deployManifest = async (
jobId,
updateJobRequest: {
status: "in_progress",
externalRunId: `${namespace}/${name}`,
externalId: `${namespace}/${name}`,
message: "Job created successfully.",
},
});
Expand Down Expand Up @@ -116,11 +116,11 @@ const updateExecutionStatus = async (agentId: string) => {
logger.info(`Found ${jobs.length} running execution(s)`);
await Promise.allSettled(
jobs.map(async (job) => {
const [namespace, name] = job.externalRunId?.split("/") ?? [];
const [namespace, name] = job.externalId?.split("/") ?? [];
if (namespace == null || name == null) {
logger.error("Invalid external run ID", {
jobId: job.id,
externalRunId: job.externalRunId,
externalId: job.externalId,
});
return;
}
Expand Down
52 changes: 52 additions & 0 deletions packages/db/drizzle/0009_flaky_hellfire_club.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
CREATE TABLE IF NOT EXISTS "target_relationship" (
"uuid" uuid,
"source_id" uuid NOT NULL,
"relationship_type" "target_relationship_type" NOT NULL,
"target_id" uuid NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "release_metadata" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"release_id" uuid NOT NULL,
"key" text NOT NULL,
"value" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "job_metadata" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"job_id" uuid NOT NULL,
"key" text NOT NULL,
"value" text NOT NULL
);
--> statement-breakpoint
ALTER TABLE "release" RENAME COLUMN "metadata" TO "config";--> statement-breakpoint
ALTER TABLE "release" ADD COLUMN "name" text NOT NULL;--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "target_relationship" ADD CONSTRAINT "target_relationship_source_id_target_id_fk" FOREIGN KEY ("source_id") REFERENCES "public"."target"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "target_relationship" ADD CONSTRAINT "target_relationship_target_id_target_id_fk" FOREIGN KEY ("target_id") REFERENCES "public"."target"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "release_metadata" ADD CONSTRAINT "release_metadata_release_id_release_id_fk" FOREIGN KEY ("release_id") REFERENCES "public"."release"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "job_metadata" ADD CONSTRAINT "job_metadata_job_id_job_id_fk" FOREIGN KEY ("job_id") REFERENCES "public"."job"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "target_relationship_target_id_source_id_index" ON "target_relationship" USING btree ("target_id","source_id");--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "release_metadata_key_release_id_index" ON "release_metadata" USING btree ("key","release_id");--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "job_metadata_key_job_id_index" ON "job_metadata" USING btree ("key","job_id");--> statement-breakpoint
ALTER TABLE "release" DROP COLUMN IF EXISTS "notes";--> statement-breakpoint
ALTER TABLE "job" DROP COLUMN IF EXISTS "external_url";
Loading
Loading