Skip to content

Commit cf0894e

Browse files
committed
trigger github action
0 parents  commit cf0894e

File tree

10 files changed

+3408
-0
lines changed

10 files changed

+3408
-0
lines changed

.github/example-workflow.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
name: example
3+
4+
on:
5+
# Allows you to trigger this workflow via the GitHub API
6+
workflow_dispatch:
7+
8+
env:
9+
GIT_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
10+
11+
jobs:
12+
print-trigger-info:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: checkout
16+
uses: actions/checkout@v4
17+
with:
18+
ref: ${{ env.GIT_SHA }}
19+
- name: Print Info
20+
run: |
21+
echo "hello"

.gitignore

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
lerna-debug.log*
8+
.pnpm-debug.log*
9+
10+
# Diagnostic reports (https://nodejs.org/api/report.html)
11+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12+
13+
# Runtime data
14+
pids
15+
*.pid
16+
*.seed
17+
*.pid.lock
18+
19+
# Directory for instrumented libs generated by jscoverage/JSCover
20+
lib-cov
21+
22+
# Coverage directory used by tools like istanbul
23+
coverage
24+
*.lcov
25+
26+
# nyc test coverage
27+
.nyc_output
28+
29+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30+
.grunt
31+
32+
# Bower dependency directory (https://bower.io/)
33+
bower_components
34+
35+
# node-waf configuration
36+
.lock-wscript
37+
38+
# Compiled binary addons (https://nodejs.org/api/addons.html)
39+
build/Release
40+
41+
# Dependency directories
42+
node_modules/
43+
jspm_packages/
44+
45+
# Snowpack dependency directory (https://snowpack.dev/)
46+
web_modules/
47+
48+
# TypeScript cache
49+
*.tsbuildinfo
50+
51+
# Optional npm cache directory
52+
.npm
53+
54+
# Optional eslint cache
55+
.eslintcache
56+
57+
# Optional stylelint cache
58+
.stylelintcache
59+
60+
# Microbundle cache
61+
.rpt2_cache/
62+
.rts2_cache_cjs/
63+
.rts2_cache_es/
64+
.rts2_cache_umd/
65+
66+
# Optional REPL history
67+
.node_repl_history
68+
69+
# Output of 'npm pack'
70+
*.tgz
71+
72+
# Yarn Integrity file
73+
.yarn-integrity
74+
75+
# dotenv environment variable files
76+
.env
77+
.env.development.local
78+
.env.test.local
79+
.env.production.local
80+
.env.local
81+
82+
# parcel-bundler cache (https://parceljs.org/)
83+
.cache
84+
.parcel-cache
85+
86+
# Next.js build output
87+
.next
88+
out
89+
90+
# Nuxt.js build / generate output
91+
.nuxt
92+
dist
93+
94+
# Gatsby files
95+
.cache/
96+
# Comment in the public line in if your project uses Gatsby and not Next.js
97+
# https://nextjs.org/blog/next-9-1#public-directory-support
98+
# public
99+
100+
# vuepress build output
101+
.vuepress/dist
102+
103+
# vuepress v2.x temp and cache directory
104+
.temp
105+
.cache
106+
107+
# Docusaurus cache and generated files
108+
.docusaurus
109+
110+
# Serverless directories
111+
.serverless/
112+
113+
# FuseBox cache
114+
.fusebox/
115+
116+
# DynamoDB Local files
117+
.dynamodb/
118+
119+
# TernJS port file
120+
.tern-port
121+
122+
# Stores VSCode versions used for testing VSCode extensions
123+
.vscode-test
124+
125+
# yarn v2
126+
.yarn/cache
127+
.yarn/unplugged
128+
.yarn/build-state.yml
129+
.yarn/install-state.gz
130+
.pnp.*
131+
132+
#IntelliJ
133+
.idea

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Render Examples
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Example Webhook Receiver
2+
3+
This basic example receives webhooks and logs the payload.
4+
It will fetch service, Postgres, or Key Value store information from the RENDER API for certain webhook types.
5+
6+
## Deploy to Render
7+
8+
1. Use the button below to deploy to Render </br>
9+
<a href="https://render.com/deploy?repo=https://github.com/render-examples/twingate-example/tree/main"><img src="https://render.com/images/deploy-to-render-button.svg" alt="Deploy to Render"></a>
10+
11+
2. Follow [instructions](https://render.com/docs/webhooks) to create a webhook with the URL from your service and `/webhook` path
12+
3. Follow [instructions](https://render.com/docs/api#1-create-an-api-key) to create a Render API Key
13+
4. Set `RENDER_WEBHOOK_SECRET` environment variable to the secret from the webhook created in step 2. SET `RENDER_API_KEY` to the key created in step 3.
14+
5. Trigger a service deploy and watch the webhooks roll in.

app.ts

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import { Octokit } from "@octokit/core";
2+
import express, {NextFunction, Request, Response} from "express";
3+
import {Webhook, WebhookUnbrandedRequiredHeaders, WebhookVerificationError} from "standardwebhooks"
4+
5+
const app = express();
6+
const port = process.env.PORT || 3001;
7+
const renderWebhookSecret = process.env.RENDER_WEBHOOK_SECRET || '';
8+
9+
const renderAPIURL = process.env.RENDER_API_URL || "https://api.render.com/v1"
10+
11+
// To create a Render API token, follow instructions here: https://render.com/docs/api#1-create-an-api-key
12+
const renderAPIToken = process.env.RENDER_API_TOKEN || '';
13+
14+
const githubAPIToken = process.env.GITHUB_TOKEN || '';
15+
const githubOwnerName = process.env.GITHUB_OWNER_NAME || '';
16+
const githubRepoName = process.env.GITHUB_REPO_NAME || '';
17+
18+
const octokit = new Octokit({
19+
auth: githubAPIToken
20+
})
21+
22+
interface WebhookData {
23+
id: string
24+
serviceId: string
25+
}
26+
27+
interface WebhookPayload {
28+
type: string
29+
timestamp: Date
30+
data: WebhookData
31+
}
32+
33+
interface RenderService {
34+
id: string
35+
name: string
36+
repo: string
37+
}
38+
39+
app.post("/webhook", express.raw({type: 'application/json'}), (req: Request, res: Response, next: NextFunction) => {
40+
try {
41+
validateWebhook(req);
42+
} catch (error) {
43+
return next(error)
44+
}
45+
46+
const payload: WebhookPayload = JSON.parse(req.body)
47+
48+
res.status(200).send({}).end()
49+
50+
// handle the webhook async so we don't timeout the request
51+
handleWebhook(payload)
52+
});
53+
54+
app.use((err: any, req: Request, res: Response, next: NextFunction) => {
55+
console.error(err);
56+
if (err instanceof WebhookVerificationError) {
57+
res.status(400).send({}).end()
58+
} else {
59+
res.status(500).send({}).end()
60+
}
61+
});
62+
63+
const server = app.listen(port, () => console.log(`Example app listening on port ${port}!`));
64+
65+
function validateWebhook(req: Request) {
66+
const headers: WebhookUnbrandedRequiredHeaders = {
67+
"webhook-id": req.header("webhook-id") || "",
68+
"webhook-timestamp": req.header("webhook-timestamp") || "",
69+
"webhook-signature": req.header("webhook-signature") || ""
70+
}
71+
72+
const wh = new Webhook(renderWebhookSecret);
73+
wh.verify(req.body, headers);
74+
}
75+
76+
async function handleWebhook(payload: WebhookPayload) {
77+
try {
78+
switch (payload.type) {
79+
case "deploy_ended":
80+
const event = await fetchEventInfo(payload)
81+
82+
// TODO add human readable status
83+
if (event.details.status != 2) {
84+
console.log(`deploy ended for service ${payload.data.serviceId} with unsuccessful status`)
85+
return
86+
}
87+
88+
const service: RenderService = await fetchServiceInfo(payload)
89+
90+
if (! service.repo.includes(`${githubOwnerName}/${githubRepoName}`)) {
91+
console.log(`received deploy success for another service: ${service.name}`)
92+
return
93+
}
94+
95+
// TODO trigger github action
96+
console.log(`triggering github workflow for ${githubOwnerName}/${githubRepoName} for ${service.name}`)
97+
await triggerWorkflow()
98+
return
99+
default:
100+
console.log(`unhandled webhook type ${payload.type} for service ${payload.data.serviceId}`)
101+
}
102+
} catch (error) {
103+
console.error(error)
104+
}
105+
}
106+
107+
async function triggerWorkflow() {
108+
await octokit.request('GET /repos/{owner}/{repo}/actions/workflows', {
109+
owner: githubOwnerName,
110+
repo: githubRepoName,
111+
headers: {
112+
'X-GitHub-Api-Version': '2022-11-28'
113+
}
114+
})
115+
}
116+
117+
// fetchEventInfo fetches the event that triggered the webhook
118+
// some events have additional information that isn't in the webhook payload
119+
// for example, deploy events have the deploy id
120+
async function fetchEventInfo(payload: WebhookPayload) {
121+
const res = await fetch(
122+
`${renderAPIURL}/events/${payload.data.id}`,
123+
{
124+
method: "get",
125+
headers: {
126+
"Content-Type": "application/json",
127+
Accept: "application/json",
128+
Authorization: `Bearer ${renderAPIToken}`,
129+
},
130+
},
131+
)
132+
if (res.ok) {
133+
return res.json()
134+
} else {
135+
throw new Error(`unable to fetch event info; received code :${res.status.toString()}`)
136+
}
137+
}
138+
139+
async function fetchServiceInfo(payload: WebhookPayload) {
140+
const res = await fetch(
141+
`${renderAPIURL}/services/${payload.data.serviceId}`,
142+
{
143+
method: "get",
144+
headers: {
145+
"Content-Type": "application/json",
146+
Accept: "application/json",
147+
Authorization: `Bearer ${renderAPIToken}`,
148+
},
149+
},
150+
)
151+
if (res.ok) {
152+
return res.json()
153+
} else {
154+
throw new Error(`unable to fetch service info; received code :${res.status.toString()}`)
155+
}
156+
}
157+
158+
process.on('SIGTERM', () => {
159+
console.debug('SIGTERM signal received: closing HTTP server')
160+
server.close(() => {
161+
console.debug('HTTP server closed')
162+
})
163+
})

0 commit comments

Comments
 (0)