Skip to content

Commit

Permalink
add bulk delete of targets
Browse files Browse the repository at this point in the history
  • Loading branch information
jsbroks committed Sep 4, 2024
1 parent 39861f9 commit de82be8
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 44 deletions.
24 changes: 19 additions & 5 deletions apps/event-worker/src/target-scan/gke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,26 @@ export const getGkeTargets = async (

const clusters = (
await Promise.allSettled(
config.projectIds.map(async (project) => {
const clusters = await getClusters(googleClusterClient, project);
return { project, clusters };
}),
config.projectIds.map(async (project) =>
getClusters(googleClusterClient, project)
.then((clusters) => ({ project, clusters }))
.catch((e) => {
log.error(
`Unable to get clusters for project: ${project} - ${String(e)}`,
);
return { project, clusters: [] };
}),
),
)
)
.filter((result) => result.status === "fulfilled")
.filter(
(
result,
): result is PromiseFulfilledResult<{
project: string;
clusters: any[];
}> => result.status === "fulfilled",
)
.map((v) => v.value);

const kubernetesApiTargets: UpsertTarget[] = clusters.flatMap(
Expand All @@ -49,6 +62,7 @@ export const getGkeTargets = async (
),
),
);

const kubernetesNamespaceTargets = (
await Promise.all(
clusters.flatMap(({ project, clusters }) => {
Expand Down
16 changes: 11 additions & 5 deletions apps/event-worker/src/target-scan/google.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,22 @@ const sourceCredentials = new GoogleAuth({
scopes: ["https://www.googleapis.com/auth/cloud-platform"],
});

export const getGoogleClusterClient = async (targetPrincipal?: string | null) =>
new Container.v1.ClusterManagerClient({
authClient: new Impersonated({
export const getGoogleClusterClient = async (
targetPrincipal?: string | null,
) => {
const clientOptions: { authClient?: Impersonated } = {};

if (targetPrincipal !== null)
clientOptions.authClient = new Impersonated({
sourceClient: await sourceCredentials.getClient(),
targetPrincipal: targetPrincipal ?? undefined,
lifetime: 3600,
delegates: [],
targetScopes: ["https://www.googleapis.com/auth/cloud-platform"],
}),
});
});

return new Container.v1.ClusterManagerClient(clientOptions);
};

export const getClusters = async (
clusterClient: ClusterManagerClient,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,20 @@ import { SiKubernetes } from "react-icons/si";
import { TbLock, TbServer, TbTarget, TbX } from "react-icons/tb";

import { cn } from "@ctrlplane/ui";
import { Button } from "@ctrlplane/ui/button";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@ctrlplane/ui/alert-dialog";
import { Button, buttonVariants } from "@ctrlplane/ui/button";
import { Checkbox } from "@ctrlplane/ui/checkbox";
import { Separator } from "@ctrlplane/ui/separator";
import {
Table,
TableBody,
Expand All @@ -23,6 +35,8 @@ import {
TableRow,
} from "@ctrlplane/ui/table";

import { api } from "~/trpc/react";

const columns: ColumnDef<Target>[] = [
{
id: "select",
Expand Down Expand Up @@ -104,12 +118,24 @@ export const TargetsTable: React.FC<{
targets: Target[];
onTableRowClick?: (target: Target) => void;
}> = ({ targets, onTableRowClick, activeTargetIds }) => {
const deleteTargetsMutation = api.target.delete.useMutation();

const table = useReactTable({
data: targets,
columns,
getCoreRowModel: getCoreRowModel(),
});

const utils = api.useUtils();
const handleDeleteTargets = async () => {
const selectedTargets = table
.getSelectedRowModel()
.rows.map((row) => row.original.id);
await deleteTargetsMutation.mutateAsync(selectedTargets);
await utils.target.byWorkspaceId.invalidate();
table.toggleAllRowsSelected(false);
};

return (
<div className="relative">
<Table>
Expand Down Expand Up @@ -165,6 +191,36 @@ export const TargetsTable: React.FC<{
</span>
<TbX className="h-4 w-4" />
</Button>
<Separator orientation="vertical" className="h-6" />
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
variant="outline"
size="sm"
className="flex items-center gap-2 bg-transparent"
>
Delete
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete
the selected targets.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
className={buttonVariants({ variant: "destructive" })}
onClick={handleDeleteTargets}
>
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</div>
)}
Expand Down
27 changes: 22 additions & 5 deletions packages/api/src/auth/access-query.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { PgColumn, Tx } from "@ctrlplane/db";

import { and, eq } from "@ctrlplane/db";
import { and, eq, inArray } from "@ctrlplane/db";
import {
deployment,
environment,
Expand Down Expand Up @@ -64,6 +64,21 @@ const evaluate =
.where(and(eq(workspaceMember.userId, userId), eq(entityColumn, id)))
.then((a) => a.length > 0);

const evaluateMultiple =
(
base: ReturnType<typeof createBaseQuery>,
userId: string | null | undefined,
entityColumn: PgColumn,
) =>
async (ids: string[]) =>
userId == null
? false
: base
.where(
and(eq(workspaceMember.userId, userId), inArray(entityColumn, ids)),
)
.then((a) => a.length > 0);

export const accessQuery = (db: Tx, userId?: string) => {
const base = createBaseQuery(db);

Expand All @@ -86,19 +101,21 @@ export const accessQuery = (db: Tx, userId?: string) => {
},
};

const targetProviderBase = createTargetProviderBaseQuery(db);
const targetBase = createTargetBaseQuery(db);
const targetAccess = {
id: evaluate(targetBase, userId, target.id),
ids: evaluateMultiple(targetBase, userId, target.id),
};
const targetProviderBase = createTargetProviderBaseQuery(db);
const targetProviderAccess = {
id: evaluate(targetProviderBase, userId, targetProvider.id),
target: {
id: evaluate(targetBase, userId, target.id),
},
};

return {
workspace: {
id: evaluate(base, userId, workspace.id),
slug: evaluate(base, userId, workspace.slug),
target: targetAccess,
targetProvider: targetProviderAccess,
system: systemAccess,
},
Expand Down
11 changes: 5 additions & 6 deletions packages/api/src/router/deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,12 +216,11 @@ export const deploymentRouter = createTRPCRouter({

byTargetId: protectedProcedure
.meta({
access: ({ ctx, input }) =>
ctx.accessQuery().workspace.targetProvider.target.id(input),
access: ({ ctx, input }) => ctx.accessQuery().workspace.target.id(input),
})
.input(z.string())
.query(({ ctx, input }) => {
return ctx.db
.query(({ ctx, input }) =>
ctx.db
.selectDistinctOn([deployment.id])
.from(deployment)
.innerJoin(system, eq(system.id, deployment.systemId))
Expand Down Expand Up @@ -259,8 +258,8 @@ export const deploymentRouter = createTRPCRouter({
release: row.release,
},
})),
);
}),
),
),

byWorkspaceId: protectedProcedure
.meta({
Expand Down
87 changes: 65 additions & 22 deletions packages/api/src/router/target.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
asc,
desc,
eq,
inArray,
like,
or,
sql,
Expand Down Expand Up @@ -184,20 +185,29 @@ export const targetRouter = createTRPCRouter({
labelGroup: labelGroupRouter,
provider: targetProviderRouter,

byId: protectedProcedure.input(z.string()).query(({ ctx, input }) =>
ctx.db
.select()
.from(target)
.innerJoin(targetProvider, eq(target.providerId, targetProvider.id))
.where(eq(target.id, input))
.then(takeFirstOrNull)
.then((a) =>
a == null ? null : { ...a.target, provider: a.target_provider },
),
),
byId: protectedProcedure
.meta({
access: ({ ctx, input }) => ctx.accessQuery().workspace.target.id(input),
})
.input(z.string().uuid())
.query(({ ctx, input }) =>
ctx.db
.select()
.from(target)
.innerJoin(targetProvider, eq(target.providerId, targetProvider.id))
.where(eq(target.id, input))
.then(takeFirstOrNull)
.then((a) =>
a == null ? null : { ...a.target, provider: a.target_provider },
),
),

byWorkspaceId: createTRPCRouter({
list: protectedProcedure
.meta({
access: ({ ctx, input }) =>
ctx.accessQuery().workspace.id(input.workspaceId),
})
.input(
z.object({
workspaceId: z.string().uuid().optional(),
Expand Down Expand Up @@ -250,17 +260,26 @@ export const targetRouter = createTRPCRouter({
}));
}),

kinds: protectedProcedure.input(z.string().uuid()).query(({ ctx, input }) =>
ctx.db
.selectDistinct({ kind: target.kind })
.from(target)
.innerJoin(targetProvider, eq(target.providerId, targetProvider.id))
.innerJoin(workspace, eq(targetProvider.workspaceId, workspace.id))
.where(eq(workspace.id, input))
.then((r) => r.map((row) => row.kind)),
),
kinds: protectedProcedure
.meta({
access: ({ ctx, input }) => ctx.accessQuery().workspace.id(input),
})
.input(z.string().uuid())
.query(({ ctx, input }) =>
ctx.db
.selectDistinct({ kind: target.kind })
.from(target)
.innerJoin(targetProvider, eq(target.providerId, targetProvider.id))
.innerJoin(workspace, eq(targetProvider.workspaceId, workspace.id))
.where(eq(workspace.id, input))
.then((r) => r.map((row) => row.kind)),
),

filtered: protectedProcedure
.meta({
access: ({ ctx, input }) =>
ctx.accessQuery().workspace.id(input.workspaceId),
})
.input(
z.object({
workspaceId: z.string().uuid(),
Expand Down Expand Up @@ -297,12 +316,20 @@ export const targetRouter = createTRPCRouter({
}),

create: protectedProcedure
.meta({
access: ({ ctx, input }) =>
ctx.accessQuery().workspace.id(input.workspaceId),
})
.input(createTarget)
.mutation(({ ctx, input }) =>
ctx.db.insert(target).values(input).returning().then(takeFirst),
),

update: protectedProcedure
.meta({
access: ({ ctx, input }) =>
ctx.accessQuery().workspace.target.id(input.id),
})
.input(z.object({ id: z.string().uuid(), data: updateTarget }))
.mutation(({ ctx, input: { id, data } }) =>
ctx.db
Expand All @@ -313,17 +340,30 @@ export const targetRouter = createTRPCRouter({
.then(takeFirst),
),

delete: protectedProcedure
.meta({
access: ({ ctx, input }) => ctx.accessQuery().workspace.target.ids(input),
})
.input(z.array(z.string().uuid()))
.mutation(async ({ ctx, input }) =>
ctx.db.delete(target).where(inArray(target.id, input)).returning(),
),

labelKeys: protectedProcedure
.input(z.string().optional())
.meta({ access: ({ ctx, input }) => ctx.accessQuery().workspace.id(input) })
.input(z.string())
.query(({ ctx, input }) =>
ctx.db
.selectDistinct({ key: sql<string>`jsonb_object_keys(labels)` })
.from(target)
.where(input != null ? eq(target.workspaceId, input) : undefined)
.where(eq(target.workspaceId, input))
.then((r) => r.map((row) => row.key)),
),

lock: protectedProcedure
.meta({
access: ({ ctx, input }) => ctx.accessQuery().workspace.target.id(input),
})
.input(z.string().uuid())
.mutation(({ ctx, input }) =>
ctx.db
Expand All @@ -335,6 +375,9 @@ export const targetRouter = createTRPCRouter({
),

unlock: protectedProcedure
.meta({
access: ({ ctx, input }) => ctx.accessQuery().workspace.target.id(input),
})
.input(z.string().uuid())
.mutation(({ ctx, input }) =>
ctx.db
Expand Down

0 comments on commit de82be8

Please sign in to comment.