Skip to content

Commit

Permalink
Add Gitea-specific webhook (#2198)
Browse files Browse the repository at this point in the history
  • Loading branch information
Toby Bellwood authored Dec 22, 2020
1 parent 4cc622e commit dbdfd07
Show file tree
Hide file tree
Showing 9 changed files with 490 additions and 2 deletions.
2 changes: 1 addition & 1 deletion services/webhook-handler/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ The main Lagoon entrypoint for webhooks originating from other services. Every
incoming webhook is parsed and validated before being queued for processing
later.

Examples of webhooks Lagoon is interested in: GitHub/Bitbucket/GitLab repository
Examples of webhooks Lagoon is interested in: GitHub/Gitea/Bitbucket/GitLab repository
activity, Lagoon project environment backup events.

## Technology
Expand Down
7 changes: 6 additions & 1 deletion services/webhook-handler/src/extractWebhookData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@ export function extractWebhookData(req: IncomingMessage, body: string): WebhookR
throw new Error(`Request body is not parsable as JSON. Are you sure you have enabled application/json as the webhook content type? ${e}.`);
}

if ('x-github-event' in req.headers) {
if ('x-gitea-event' in req.headers) {
webhooktype = 'gitea';
event = req.headers['x-gitea-event'];
uuid = req.headers['x-gitea-delivery'];
giturl = R.path(['repository', 'ssh_url'], bodyObj);
} else if ('x-github-event' in req.headers) {
webhooktype = 'github';
event = req.headers['x-github-event'];
uuid = req.headers['x-github-delivery'];
Expand Down
44 changes: 44 additions & 0 deletions services/webhook-handler/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,20 @@ export interface GithubPushEvent {
},
};

// See: https://docs.gitea.io/en-us/webhooks/
export interface GiteaPushEvent {
event: 'push',
webhooktype: 'gitea',
uuid: string,
body: {
repository: {
ssh_url: string,
},
ref: string,
after: string,
},
};

// See: https://developer.github.com/v3/activity/events/types/#pullrequestevent
export interface GithubPullRequestEvent {
event: 'pull_request',
Expand All @@ -38,6 +52,20 @@ export interface GithubPullRequestEvent {
}
};

// See: https://docs.gitea.io/en-us/webhooks/
export interface GiteaPullRequestEvent {
event: 'pull_request',
webhooktype: 'gitea',
uuid: string,
body: {
action: string,
repository: {
ssh_url: string,
},
number: number,
}
};

// See: https://developer.github.com/v3/activity/events/types/#deleteevent
export interface GithubDeleteEvent {
event: 'delete',
Expand All @@ -52,6 +80,19 @@ export interface GithubDeleteEvent {
}
};

// See: https://docs.gitea.io/en-us/webhooks/
export interface GiteaDeleteEvent {
event: 'delete',
webhooktype: 'gitea',
uuid: string,
body: {
ref_type: string,
repository: {
ssh_url: string,
}
}
};

export interface CustomPushEvent {
event: 'push',
webhooktype: 'custom',
Expand All @@ -78,6 +119,9 @@ export type RawData =
GithubPushEvent
| GithubPullRequestEvent
| GithubDeleteEvent
| GiteaPushEvent
| GiteaPullRequestEvent
| GiteaDeleteEvent
| CustomPushEvent
| any;

Expand Down
59 changes: 59 additions & 0 deletions services/webhooks2tasks/src/handlers/giteaBranchDeleted.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { sendToLagoonLogs } from '@lagoon/commons/dist/logs';
import { createRemoveTask } from '@lagoon/commons/dist/tasks';

import { WebhookRequestData, removeData, Project } from '../types';

export async function giteaBranchDeleted(webhook: WebhookRequestData, project: Project) {
const {
webhooktype,
event,
giturl,
uuid,
body,
} = webhook;

const meta: { [key: string]: any } = {
projectName: project.name,
branch: body.ref.replace('refs/heads/',''),
branchName: body.ref.replace('refs/heads/','')
}

const data: removeData = {
projectName: project.name,
branch: meta.branch,
branchName: meta.branchName,
forceDeleteProductionEnvironment: false,
type: 'branch'
}

try {
await createRemoveTask(data);
sendToLagoonLogs('info', project.name, uuid, `${webhooktype}:delete:handled`, meta,
`*[${project.name}]* \`${meta.branch}\` deleted in <${body.repository.html_url}|${body.repository.full_name}>`
)
return;
} catch (error) {
meta.error = error
switch (error.name) {
case "ProjectNotFound":
case "NoActiveSystemsDefined":
case "UnknownActiveSystem":
// These are not real errors and also they will happen many times. We just log them locally but not throw an error
sendToLagoonLogs('info', project.name, uuid, `${webhooktype}:${event}:handledButNoTask`, meta,
`*[${project.name}]* \`${meta.branch}\` deleted. No remove task created, reason: ${error}`
)
return;

case "CannotDeleteProductionEnvironment":
// These are not real errors and also they will happen many times. We just log them locally but not throw an error
sendToLagoonLogs('warning', project.name, uuid, `${webhooktype}:${event}:CannotDeleteProductionEnvironment`, meta,
`*[${project.name}]* \`${meta.branch}\` not deleted. ${error}`
)
return;

default:
// Other messages are real errors and should reschedule the message in RabbitMQ in order to try again
throw error
}
}
}
64 changes: 64 additions & 0 deletions services/webhooks2tasks/src/handlers/giteaPullRequestClosed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { sendToLagoonLogs } from '@lagoon/commons/dist/logs';
import { createRemoveTask } from '@lagoon/commons/dist/tasks';

import { WebhookRequestData, removeData, Project } from '../types';

export async function giteaPullRequestClosed(webhook: WebhookRequestData, project: Project) {

const {
webhooktype,
event,
giturl,
uuid,
body,
user,
sender,
} = webhook;

const meta: { [key: string]: any } = {
projectName: project.name,
pullrequestTitle: body.pull_request.title,
pullrequestNumber: body.number,
pullrequestUrl: body.pull_request.html_url,
repoName: body.repository.full_name,
repoUrl: body.repository.html_url,
}

const data: removeData = {
projectName: project.name,
pullrequestNumber: body.number,
pullrequestTitle: body.pull_request.title,
type: 'pullrequest'
}

try {
await createRemoveTask(data);
sendToLagoonLogs('info', project.name, uuid, `${webhooktype}:${event}:closed:handled`, meta,
`*[${project.name}]* PR <${body.pull_request.html_url}|#${body.number} (${body.pull_request.title})> by <${body.pull_request.user.login}> changed by <${body.sender.login}> closed in <${body.repository.html_url}|${body.repository.full_name}>`
)
return;
} catch (error) {
meta.error = error
switch (error.name) {
case "ProjectNotFound":
case "NoActiveSystemsDefined":
case "UnknownActiveSystem":
// These are not real errors and also they will happen many times. We just log them locally but not throw an error
sendToLagoonLogs('info', project.name, uuid, `${webhooktype}:${event}:handledButNoTask`, meta,
`*[${project.name}]* PR ${body.number} closed. No remove task created, reason: ${error}`
)
return;

case "CannotDeleteProductionEnvironment":
// These are not real errors and also they will happen many times. We just log them locally but not throw an error
sendToLagoonLogs('warning', project.name, uuid, `${webhooktype}:${event}:CannotDeleteProductionEnvironment`, meta,
`*[${project.name}]* \`${meta.branch}\` not deleted. ${error}`
)
return;

default:
// Other messages are real errors and should reschedule the message in RabbitMQ in order to try again
throw error
}
}
}
78 changes: 78 additions & 0 deletions services/webhooks2tasks/src/handlers/giteaPullRequestOpened.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import R from 'ramda';
import { sendToLagoonLogs } from '@lagoon/commons/dist/logs';
import { createDeployTask } from '@lagoon/commons/dist/tasks';

import { WebhookRequestData, deployData, Project } from '../types';

export async function giteaPullRequestOpened(webhook: WebhookRequestData, project: Project) {

const {
webhooktype,
event,
giturl,
uuid,
body,
} = webhook;

const headRepoId = body.pull_request.head.repo.id
const headBranchName = body.pull_request.head.ref
const headSha = body.pull_request.head.sha
const baseRepoId = body.pull_request.base.repo.id
const baseBranchName = body.pull_request.base.ref
const baseSha = body.pull_request.base.sha

const meta = {
projectName: project.name,
pullrequestTitle: body.pull_request.title,
pullrequestNumber: body.number,
pullrequestUrl: body.pull_request.html_url,
repoName: body.repository.full_name,
repoUrl: body.repository.html_url,
}

// Don't trigger deploy if the head and base repos are different
if (!R.equals(headRepoId, baseRepoId)) {
sendToLagoonLogs('info', project.name, uuid, `${webhooktype}:${event}:handledButNoTask`, meta,
`*[${project.name}]* PR ${body.number} opened. No deploy task created, reason: Head/Base not same repo`
)
return;
}

const data: deployData = {
repoUrl: body.repository.html_url,
repoName: body.repository.full_name,
pullrequestTitle: body.pull_request.title,
pullrequestNumber: body.number,
pullrequestUrl: body.pull_request.html_url,
projectName: project.name,
type: 'pullrequest',
headBranchName: headBranchName,
headSha: headSha,
baseBranchName: baseBranchName,
baseSha: baseSha,
branchName: `pr-${body.number}`,
}

try {
await createDeployTask(data);
sendToLagoonLogs('info', project.name, uuid, `${webhooktype}:${event}:opened:handled`, data,
`*[${project.name}]* PR <${body.pull_request.html_url}|#${body.number} (${body.pull_request.title})> opened in <${body.repository.html_url}|${body.repository.full_name}>`
)
return;
} catch (error) {
switch (error.name) {
case "ProjectNotFound":
case "NoActiveSystemsDefined":
case "UnknownActiveSystem":
// These are not real errors and also they will happen many times. We just log them locally but not throw an error
sendToLagoonLogs('info', project.name, uuid, `${webhooktype}:${event}:handledButNoTask`, meta,
`*[${project.name}]* PR ${body.number} opened. No deploy task created, reason: ${error}`
)
return;

default:
// Other messages are real errors and should reschedule the message in RabbitMQ in order to try again
throw error
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import R from 'ramda';
import { sendToLagoonLogs } from '@lagoon/commons/dist/logs';
import { createDeployTask } from '@lagoon/commons/dist/tasks';

import { WebhookRequestData, deployData, Project } from '../types';

const isEditAction = R.propEq('action', 'edited');

const onlyBodyChanges = R.pipe(
R.propOr({}, 'changes'),
R.keys,
R.equals(['body']),
);

const skipRedeploy = R.and(isEditAction, onlyBodyChanges);

export async function giteaPullRequestSynchronize(webhook: WebhookRequestData, project: Project) {

const {
webhooktype,
event,
giturl,
uuid,
body,
} = webhook;

const headRepoId = body.pull_request.head.repo.id
const headBranchName = body.pull_request.head.ref
const headSha = body.pull_request.head.sha
const baseRepoId = body.pull_request.base.repo.id
const baseBranchName = body.pull_request.base.ref
const baseSha = body.pull_request.base.sha

const meta = {
projectName: project.name,
pullrequestTitle: body.pull_request.title,
pullrequestNumber: body.number,
pullrequestUrl: body.pull_request.html_url,
repoName: body.repository.full_name,
repoUrl: body.repository.html_url,
}

// Don't trigger deploy if only the PR body was edited.
if (skipRedeploy(body)) {
sendToLagoonLogs('info', project.name, uuid, `${webhooktype}:${event}:handledButNoTask`, meta,
`*[${project.name}]* PR ${body.number} updated. No deploy task created, reason: Only body changed`
)
return;
}

// Don't trigger deploy if the head and base repos are different
if (!R.equals(headRepoId, baseRepoId)) {
sendToLagoonLogs('info', project.name, uuid, `${webhooktype}:${event}:handledButNoTask`, meta,
`*[${project.name}]* PR ${body.number} updated. No deploy task created, reason: Head/Base not same repo`
)
return;
}

const data: deployData = {
repoName: body.repository.full_name,
repoUrl: body.repository.html_url,
pullrequestUrl: body.pull_request.html_url,
pullrequestTitle: body.pull_request.title,
pullrequestNumber: body.number,
projectName: project.name,
type: 'pullrequest',
headBranchName: headBranchName,
headSha: headSha,
baseBranchName: baseBranchName,
baseSha: baseSha,
branchName: `pr-${body.number}`,
}

try {
await createDeployTask(data);
sendToLagoonLogs('info', project.name, uuid, `${webhooktype}:${event}:synchronize:handled`, data,
`*[${project.name}]* PR <${body.pull_request.html_url}|#${body.number} (${body.pull_request.title})> updated in <${body.repository.html_url}|${body.repository.full_name}>`
)
return;
} catch (error) {
switch (error.name) {
case "ProjectNotFound":
case "NoActiveSystemsDefined":
case "UnknownActiveSystem":
// These are not real errors and also they will happen many times. We just log them locally but not throw an error
sendToLagoonLogs('info', project.name, uuid, `${webhooktype}:${event}:handledButNoTask`, meta,
`*[${project.name}]* PR ${body.number} opened. No deploy task created, reason: ${error}`
)
return;

default:
// Other messages are real errors and should reschedule the message in RabbitMQ in order to try again
throw error
}
}
}
Loading

0 comments on commit dbdfd07

Please sign in to comment.