feat(github): add triggerType field to GitHub provider and handle tag creation events

This commit is contained in:
Theo D 2025-04-02 22:22:43 +02:00
parent 0bdaa81263
commit fcb8a2bded
6 changed files with 159 additions and 3 deletions

View File

@ -57,6 +57,7 @@ const GithubProviderSchema = z.object({
branch: z.string().min(1, "Branch is required"),
githubId: z.string().min(1, "Github Provider is required"),
watchPaths: z.array(z.string()).optional(),
triggerType: z.enum(["push", "tag"]).default("push"),
});
type GithubProvider = z.infer<typeof GithubProviderSchema>;
@ -124,6 +125,7 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
buildPath: data.buildPath || "/",
githubId: data.githubId || "",
watchPaths: data.watchPaths || [],
triggerType: data.triggerType || "push",
});
}
}, [form.reset, data, form]);
@ -137,6 +139,7 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
buildPath: data.buildPath,
githubId: data.githubId,
watchPaths: data.watchPaths || [],
triggerType: data.triggerType,
})
.then(async () => {
toast.success("Service Provided Saved");
@ -379,6 +382,45 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
</FormItem>
)}
/>
<FormField
control={form.control}
name="triggerType"
render={({ field }) => (
<FormItem>
<div className="flex items-center gap-2">
<FormLabel>Trigger Type</FormLabel>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger>
<TooltipContent>
<p>
Choose when to trigger deployments: on push to the selected branch or when a new tag is created.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
value={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a trigger type" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="push">On Push</SelectItem>
<SelectItem value="tag">On Tag</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="watchPaths"

View File

@ -0,0 +1,5 @@
-- Add triggerType column to application table
ALTER TABLE "application" ADD COLUMN IF NOT EXISTS "triggerType" text DEFAULT 'push';
-- Add triggerType column to compose table
ALTER TABLE "compose" ADD COLUMN IF NOT EXISTS "triggerType" text DEFAULT 'push';

View File

@ -62,11 +62,12 @@ export default async function handler(
if (
req.headers["x-github-event"] !== "push" &&
req.headers["x-github-event"] !== "pull_request"
req.headers["x-github-event"] !== "pull_request" &&
req.headers["x-github-event"] !== "create"
) {
res
.status(400)
.json({ message: "We only accept push events or pull_request events" });
.json({ message: "We only accept push, pull_request, or create events" });
return;
}
@ -89,6 +90,106 @@ export default async function handler(
return;
}
// Handle tag creation event
if (req.headers["x-github-event"] === "create" && githubBody?.ref_type === "tag") {
try {
const tagName = githubBody?.ref;
const repository = githubBody?.repository?.name;
const owner = githubBody?.repository?.owner?.name || githubBody?.repository?.owner?.login;
const deploymentTitle = `Tag created: ${tagName}`;
const deploymentHash = githubBody?.master_branch || "";
// Find applications configured to deploy on tag
const apps = await db.query.applications.findMany({
where: and(
eq(applications.sourceType, "github"),
eq(applications.autoDeploy, true),
eq(applications.triggerType, "tag"),
eq(applications.repository, repository),
eq(applications.owner, owner),
eq(applications.githubId, githubResult.githubId),
),
});
for (const app of apps) {
const jobData: DeploymentJob = {
applicationId: app.applicationId as string,
titleLog: deploymentTitle,
descriptionLog: `Tag: ${tagName}`,
type: "deploy",
applicationType: "application",
server: !!app.serverId,
};
if (IS_CLOUD && app.serverId) {
jobData.serverId = app.serverId;
await deploy(jobData);
continue;
}
await myQueue.add(
"deployments",
{ ...jobData },
{
removeOnComplete: true,
removeOnFail: true,
},
);
}
// Find compose apps configured to deploy on tag
const composeApps = await db.query.compose.findMany({
where: and(
eq(compose.sourceType, "github"),
eq(compose.autoDeploy, true),
eq(compose.triggerType, "tag"),
eq(compose.repository, repository),
eq(compose.owner, owner),
eq(compose.githubId, githubResult.githubId),
),
});
for (const composeApp of composeApps) {
const jobData: DeploymentJob = {
composeId: composeApp.composeId as string,
titleLog: deploymentTitle,
type: "deploy",
applicationType: "compose",
descriptionLog: `Tag: ${tagName}`,
server: !!composeApp.serverId,
};
if (IS_CLOUD && composeApp.serverId) {
jobData.serverId = composeApp.serverId;
await deploy(jobData);
continue;
}
await myQueue.add(
"deployments",
{ ...jobData },
{
removeOnComplete: true,
removeOnFail: true,
},
);
}
const totalApps = apps.length + composeApps.length;
if (totalApps === 0) {
res.status(200).json({ message: "No apps configured to deploy on tag" });
return;
}
res.status(200).json({ message: `Deployed ${totalApps} apps based on tag ${tagName}` });
return;
} catch (error) {
console.error("Error deploying applications on tag:", error);
res.status(400).json({ message: "Error deploying applications on tag", error });
return;
}
}
if (req.headers["x-github-event"] === "push") {
try {
const branchName = githubBody?.ref?.replace("refs/heads/", "");
@ -105,6 +206,7 @@ export default async function handler(
where: and(
eq(applications.sourceType, "github"),
eq(applications.autoDeploy, true),
eq(applications.triggerType, "push"),
eq(applications.branch, branchName),
eq(applications.repository, repository),
eq(applications.owner, owner),
@ -150,6 +252,7 @@ export default async function handler(
where: and(
eq(compose.sourceType, "github"),
eq(compose.autoDeploy, true),
eq(compose.triggerType, "push"),
eq(compose.branch, branchName),
eq(compose.repository, repository),
eq(compose.owner, owner),

View File

@ -346,6 +346,7 @@ export const applicationRouter = createTRPCRouter({
applicationStatus: "idle",
githubId: input.githubId,
watchPaths: input.watchPaths,
triggerType: input.triggerType,
});
return true;

View File

@ -149,6 +149,7 @@ export const applications = pgTable("application", {
owner: text("owner"),
branch: text("branch"),
buildPath: text("buildPath").default("/"),
triggerType: text("triggerType").default("push"),
autoDeploy: boolean("autoDeploy").$defaultFn(() => true),
// Gitlab
gitlabProjectId: integer("gitlabProjectId"),
@ -471,7 +472,10 @@ export const apiSaveGithubProvider = createSchema
githubId: true,
watchPaths: true,
})
.required();
.required()
.extend({
triggerType: z.enum(["push", "tag"]).default("push"),
});
export const apiSaveGitlabProvider = createSchema
.pick({

View File

@ -76,6 +76,7 @@ export const compose = pgTable("compose", {
suffix: text("suffix").notNull().default(""),
randomize: boolean("randomize").notNull().default(false),
isolatedDeployment: boolean("isolatedDeployment").notNull().default(false),
triggerType: text("triggerType").default("push"),
composeStatus: applicationStatus("composeStatus").notNull().default("idle"),
projectId: text("projectId")
.notNull()