mirror of
https://github.com/tormachris/cf-workers-status-page.git
synced 2024-12-18 00:15:43 +01:00
Merge pull request #13 from eidam/e/kv-gc
feat: move gc monitors from cron schedule to deploy postCommands
This commit is contained in:
commit
293dff9425
6
.github/workflows/deploy.yml
vendored
6
.github/workflows/deploy.yml
vendored
@ -24,10 +24,12 @@ jobs:
|
|||||||
preCommands: |
|
preCommands: |
|
||||||
wrangler kv:namespace create KV_STATUS_PAGE
|
wrangler kv:namespace create KV_STATUS_PAGE
|
||||||
apt-get update && apt-get install -y jq
|
apt-get update && apt-get install -y jq
|
||||||
kv_namespace_id=$(wrangler kv:namespace list | jq -c 'map(select(.title | contains("KV_STATUS_PAGE")))' | jq ".[0].id")
|
export KV_NAMESPACE_ID=$(wrangler kv:namespace list | jq -c 'map(select(.title | contains("KV_STATUS_PAGE")))' | jq -r ".[0].id")
|
||||||
echo "[env.production]" >> wrangler.toml
|
echo "[env.production]" >> wrangler.toml
|
||||||
echo "kv_namespaces = [{binding=\"KV_STATUS_PAGE\", id=${kv_namespace_id}}]" >> wrangler.toml
|
echo "kv_namespaces = [{binding=\"KV_STATUS_PAGE\", id=\"${KV_NAMESPACE_ID}\"}]" >> wrangler.toml
|
||||||
[ -z "$SECRET_SLACK_WEBHOOK_URL" ] && echo "Secret SECRET_SLACK_WEBHOOK_URL not set, creating dummy one..." && SECRET_SLACK_WEBHOOK_URL="default-gh-action-secret" || true
|
[ -z "$SECRET_SLACK_WEBHOOK_URL" ] && echo "Secret SECRET_SLACK_WEBHOOK_URL not set, creating dummy one..." && SECRET_SLACK_WEBHOOK_URL="default-gh-action-secret" || true
|
||||||
|
postCommands: |
|
||||||
|
yarn kv-gc
|
||||||
secrets: |
|
secrets: |
|
||||||
SECRET_SLACK_WEBHOOK_URL
|
SECRET_SLACK_WEBHOOK_URL
|
||||||
environment: production
|
environment: production
|
||||||
|
19
README.md
19
README.md
@ -12,7 +12,7 @@ You'll need a [Cloudflare Workers account](https://dash.cloudflare.com/sign-up/w
|
|||||||
|
|
||||||
* A workers domain set up
|
* A workers domain set up
|
||||||
* The Workers Bundled subscription \($5/mo\)
|
* The Workers Bundled subscription \($5/mo\)
|
||||||
* [Try it now with the free tier!](https://blog.cloudflare.com/workers-kv-free-tier/) Stay tuned while we make some changes so it will completely fit in the Free Tier with a 5min check interval.
|
* [Try it now with the free tier!](https://blog.cloudflare.com/workers-kv-free-tier/) Check [more info](#workers-kv-free-tier) on how to run on Workers Free.
|
||||||
* Some websites/APIs to watch 🙂
|
* Some websites/APIs to watch 🙂
|
||||||
|
|
||||||
Also, prepare the following secrets
|
Also, prepare the following secrets
|
||||||
@ -40,7 +40,7 @@ You can either deploy with **Cloudflare Deploy Button** using GitHub Actions or
|
|||||||
- Value: your-slack-webhook-url
|
- Value: your-slack-webhook-url
|
||||||
```
|
```
|
||||||
3. Navigate to the **Actions** settings in your repository and enable them
|
3. Navigate to the **Actions** settings in your repository and enable them
|
||||||
4. Edit [config.yaml](https://github.com/eidam/cf-workers-status-page/blob/main/config.yaml) to adjust configuration and list all of your websites/APIs you want to monitor
|
4. Edit [config.yaml](./config.yaml) to adjust configuration and list all of your websites/APIs you want to monitor
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
settings:
|
settings:
|
||||||
@ -74,16 +74,20 @@ You can either deploy with **Cloudflare Deploy Button** using GitHub Actions or
|
|||||||
6. 🎉
|
6. 🎉
|
||||||
7. _\(optional\)_ Go to [Cloudflare Workers settings](https://dash.cloudflare.com/?to=/workers) and assign custom domain/route
|
7. _\(optional\)_ Go to [Cloudflare Workers settings](https://dash.cloudflare.com/?to=/workers) and assign custom domain/route
|
||||||
* e.g. `status-page.eidam.dev/*` _\(make sure you include `/*` as the Worker also serve static files\)_
|
* e.g. `status-page.eidam.dev/*` _\(make sure you include `/*` as the Worker also serve static files\)_
|
||||||
8. _\(optional\)_ Edit [wrangler.toml](https://github.com/eidam/cf-workers-github-releases/blob/main/wrangler.toml) to adjust Worker settings or CRON Trigger schedule
|
8. _\(optional\)_ Edit [wrangler.toml](./wrangler.toml) to adjust Worker settings or CRON Trigger schedule, especially if you are on [Workers Free plan](#workers-kv-free-tier)
|
||||||
|
|
||||||
### Deploy on your own
|
### Deploy on your own
|
||||||
|
|
||||||
You can clone the repository yourself and use Wrangler CLI to develop/deploy, extra list of things you need to take care of:
|
You can clone the repository yourself and use Wrangler CLI to develop/deploy, extra list of things you need to take care of:
|
||||||
|
|
||||||
* create KV namespace and add the `KV_STATUS_PAGE` binding to [wrangler.toml](https://github.com/eidam/cf-workers-github-releases/blob/main/wrangler.toml)
|
* create KV namespace and add the `KV_STATUS_PAGE` binding to [wrangler.toml](./wrangler.toml)
|
||||||
* create Worker secrets _\(optional\)_
|
* create Worker secrets _\(optional\)_
|
||||||
* `SECRET_SLACK_WEBHOOK_URL`
|
* `SECRET_SLACK_WEBHOOK_URL`
|
||||||
|
|
||||||
|
## Workers KV free tier
|
||||||
|
The Workers Free plan includes limited KV usage, in order to not deplete the quota and still have enough room for monitor status changes we recommend the following changes:
|
||||||
|
* Change the CRON trigger to 5 minutes interval (`crons = ["*/5 * * * *"]`) in [wrangler.toml](./wrangler.toml)
|
||||||
|
|
||||||
## Known issues
|
## Known issues
|
||||||
|
|
||||||
* **Max 25 monitors to watch in case you are using Slack notifications**, due to the limit of subrequests Cloudflare Worker can make \(50\).
|
* **Max 25 monitors to watch in case you are using Slack notifications**, due to the limit of subrequests Cloudflare Worker can make \(50\).
|
||||||
@ -93,3 +97,10 @@ You can clone the repository yourself and use Wrangler CLI to develop/deploy, ex
|
|||||||
* **KV replication lag** - You might get Slack notification instantly, however it may take couple of more seconds to see the change on your status page as [Cron Triggers are usually running on underutilized quiet hours machines](https://blog.cloudflare.com/introducing-cron-triggers-for-cloudflare-workers/#how-are-you-able-to-offer-this-feature-at-no-additional-cost).
|
* **KV replication lag** - You might get Slack notification instantly, however it may take couple of more seconds to see the change on your status page as [Cron Triggers are usually running on underutilized quiet hours machines](https://blog.cloudflare.com/introducing-cron-triggers-for-cloudflare-workers/#how-are-you-able-to-offer-this-feature-at-no-additional-cost).
|
||||||
|
|
||||||
* **Initial delay (no data)** - It takes couple of minutes to schedule and run CRON Triggers for the first time
|
* **Initial delay (no data)** - It takes couple of minutes to schedule and run CRON Triggers for the first time
|
||||||
|
|
||||||
|
* **Slack message for monitor just removed from the config** - It takes a couple of minutes to schedule new version of CRON Triggers, so older version of the configuration might be scheduled after deployment/gc - just ignore it or re-run the latest Github Action after a couple of minutes again.
|
||||||
|
|
||||||
|
## Future plans
|
||||||
|
Stay tuned for more features coming in, like leveraging the fact that CRON instances are scheduled around the world during the day
|
||||||
|
so we can monitor the response times. However, we will most probably wait for the [Durable Objects](https://blog.cloudflare.com/introducing-workers-durable-objects/) to be in open beta
|
||||||
|
as they are better fit to reliably store such info.
|
||||||
|
@ -9,16 +9,18 @@
|
|||||||
"dev": "flareact dev",
|
"dev": "flareact dev",
|
||||||
"build": "flareact build",
|
"build": "flareact build",
|
||||||
"deploy": "flareact publish",
|
"deploy": "flareact publish",
|
||||||
|
"kv-gc": "node ./src/cli/gcMonitors.js",
|
||||||
"format": "prettier --write '**/*.{js,css,json,md}'"
|
"format": "prettier --write '**/*.{js,css,json,md}'"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"flareact": "^0.8.0",
|
"flareact": "^0.9.0",
|
||||||
"laco": "^1.2.1",
|
"laco": "^1.2.1",
|
||||||
"laco-react": "^1.1.0",
|
"laco-react": "^1.1.0",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-dom": "^16.13.1"
|
"react-dom": "^16.13.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"node-fetch": "^2.6.1",
|
||||||
"prettier": "^1.18.2",
|
"prettier": "^1.18.2",
|
||||||
"yaml-loader": "^0.6.0"
|
"yaml-loader": "^0.6.0"
|
||||||
}
|
}
|
||||||
|
74
src/cli/gcMonitors.js
Normal file
74
src/cli/gcMonitors.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
const yaml = require('yaml-loader')
|
||||||
|
const fetch = require('node-fetch')
|
||||||
|
const fs = require('fs')
|
||||||
|
|
||||||
|
const accountId = process.env.CF_ACCOUNT_ID
|
||||||
|
const namespaceId = process.env.KV_NAMESPACE_ID
|
||||||
|
const apiToken = process.env.CF_API_TOKEN
|
||||||
|
|
||||||
|
const kvPrefix = 's_'
|
||||||
|
|
||||||
|
if (!accountId || !namespaceId || !apiToken) {
|
||||||
|
console.error("Missing required environment variables: CF_ACCOUNT_ID, KV_NAMESPACE_ID, CF_API_TOKEN")
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getKvMonitors(kvPrefix) {
|
||||||
|
const init = {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${apiToken}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(
|
||||||
|
`https://api.cloudflare.com/client/v4/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/keys?limit=100&prefix=${kvPrefix}`,
|
||||||
|
init,
|
||||||
|
)
|
||||||
|
const json = await res.json()
|
||||||
|
return json.result
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteKvBulk(keys) {
|
||||||
|
const init = {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${apiToken}`,
|
||||||
|
},
|
||||||
|
method: 'DELETE',
|
||||||
|
body: JSON.stringify(keys),
|
||||||
|
}
|
||||||
|
|
||||||
|
return await fetch(
|
||||||
|
`https://api.cloudflare.com/client/v4/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/bulk`,
|
||||||
|
init,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadConfig() {
|
||||||
|
const configFile = fs.readFileSync('./config.yaml', 'utf8')
|
||||||
|
const config = yaml(configFile)
|
||||||
|
return JSON.parse(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
getKvMonitors(kvPrefix).then(async kvMonitors => {
|
||||||
|
const config = loadConfig()
|
||||||
|
const monitors = config.monitors.map(key => {
|
||||||
|
return key.id
|
||||||
|
})
|
||||||
|
const kvState = kvMonitors.map(key => {
|
||||||
|
return key.name
|
||||||
|
})
|
||||||
|
const keysForRemoval = kvState.filter(
|
||||||
|
x => !monitors.includes(x.replace(kvPrefix, '')),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (keysForRemoval.length > 0) {
|
||||||
|
console.log(
|
||||||
|
`Removing following keys from KV storage as they are no longer in the config: ${keysForRemoval.join(
|
||||||
|
', ',
|
||||||
|
)}`,
|
||||||
|
)
|
||||||
|
await deleteKvBulk(keysForRemoval)
|
||||||
|
}
|
||||||
|
}).catch(e => console.log(e))
|
@ -3,7 +3,6 @@ import config from '../../config.yaml'
|
|||||||
import {
|
import {
|
||||||
setKV,
|
setKV,
|
||||||
getKVWithMetadata,
|
getKVWithMetadata,
|
||||||
gcMonitors,
|
|
||||||
getKV,
|
getKV,
|
||||||
notifySlack,
|
notifySlack,
|
||||||
} from './helpers'
|
} from './helpers'
|
||||||
@ -70,8 +69,5 @@ export async function processCronTrigger(event) {
|
|||||||
const loc = res.headers.get('cf-ray').split('-')[1]
|
const loc = res.headers.get('cf-ray').split('-')[1]
|
||||||
await setKV('lastUpdate', Date.now(), { loc })
|
await setKV('lastUpdate', Date.now(), { loc })
|
||||||
|
|
||||||
// gc monitor statuses
|
|
||||||
event.waitUntil(gcMonitors(config))
|
|
||||||
|
|
||||||
return new Response('OK')
|
return new Response('OK')
|
||||||
}
|
}
|
||||||
|
@ -56,26 +56,6 @@ export async function deleteKV(key) {
|
|||||||
return KV_STATUS_PAGE.delete(key)
|
return KV_STATUS_PAGE.delete(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function gcMonitors(config) {
|
|
||||||
const checkKvPrefix = 's_'
|
|
||||||
|
|
||||||
const monitors = config.monitors.map(key => {
|
|
||||||
return key.id
|
|
||||||
})
|
|
||||||
|
|
||||||
const kvMonitors = await listKV(checkKvPrefix)
|
|
||||||
const kvState = kvMonitors.keys.map(key => {
|
|
||||||
return key.metadata.id
|
|
||||||
})
|
|
||||||
|
|
||||||
const keysForRemoval = kvState.filter(x => !monitors.includes(x))
|
|
||||||
|
|
||||||
for (const key of keysForRemoval) {
|
|
||||||
console.log('gc: deleting ' + checkKvPrefix + key)
|
|
||||||
await deleteKV(checkKvPrefix + key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function notifySlack(monitor, newMetadata) {
|
export async function notifySlack(monitor, newMetadata) {
|
||||||
const payload = {
|
const payload = {
|
||||||
attachments: [
|
attachments: [
|
||||||
|
13
yarn.lock
13
yarn.lock
@ -2947,10 +2947,10 @@ findup-sync@^3.0.0:
|
|||||||
micromatch "^3.0.4"
|
micromatch "^3.0.4"
|
||||||
resolve-dir "^1.0.1"
|
resolve-dir "^1.0.1"
|
||||||
|
|
||||||
flareact@^0.8.0:
|
flareact@^0.9.0:
|
||||||
version "0.8.0"
|
version "0.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/flareact/-/flareact-0.8.0.tgz#c7653f7278abee04353c5bf087af4105b7895c0a"
|
resolved "https://registry.yarnpkg.com/flareact/-/flareact-0.9.0.tgz#c16ded48f217010452a509e02b754f84eb26878c"
|
||||||
integrity sha512-ewjFqrxSXPBppyZtVBTD3W4iIcMB2wORpYX1QofOa9QRuy7dJ2nK4oMvCNmPJPelqdN5MF+fUBTIfMGqO3A1OA==
|
integrity sha512-YT1nGqusHTJDreU5gQezKQNU2Pszez+M3v5IrKIEtOD3ABQal+cVoWzRQGQTWMKryrUpWB0Z0nRhLYDutD8xdQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core" "^7.11.0"
|
"@babel/core" "^7.11.0"
|
||||||
"@babel/plugin-transform-runtime" "^7.11.0"
|
"@babel/plugin-transform-runtime" "^7.11.0"
|
||||||
@ -4354,6 +4354,11 @@ nice-try@^1.0.4:
|
|||||||
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
||||||
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
|
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
|
||||||
|
|
||||||
|
node-fetch@^2.6.1:
|
||||||
|
version "2.6.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
||||||
|
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
||||||
|
|
||||||
node-forge@^0.10.0:
|
node-forge@^0.10.0:
|
||||||
version "0.10.0"
|
version "0.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
|
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
|
||||||
|
Loading…
Reference in New Issue
Block a user