Add scheduled GitHub Action task
This commit is contained in:
34
.github/workflows/daily-task.yml
vendored
Normal file
34
.github/workflows/daily-task.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
name: Daily Task
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 2 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run-daily-task:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
|
||||||
|
- name: Run scheduled task
|
||||||
|
env:
|
||||||
|
TARGET_URL: ${{ secrets.TARGET_URL }}
|
||||||
|
HTTP_METHOD: ${{ vars.HTTP_METHOD }}
|
||||||
|
REQUEST_BODY: ${{ secrets.REQUEST_BODY }}
|
||||||
|
AUTH_TOKEN: ${{ secrets.AUTH_TOKEN }}
|
||||||
|
HEADERS_JSON: ${{ secrets.HEADERS_JSON }}
|
||||||
|
EXPECTED_STATUS: ${{ vars.EXPECTED_STATUS }}
|
||||||
|
RETRY_COUNT: ${{ vars.RETRY_COUNT }}
|
||||||
|
RETRY_DELAY_MS: ${{ vars.RETRY_DELAY_MS }}
|
||||||
|
run: node scripts/daily-task.js
|
||||||
39
README.md
39
README.md
@@ -1,2 +1,41 @@
|
|||||||
# card-api
|
# card-api
|
||||||
|
|
||||||
|
## GitHub Action: daily task
|
||||||
|
|
||||||
|
This repository includes a scheduled GitHub Action at .github/workflows/daily-task.yml.
|
||||||
|
|
||||||
|
### Trigger mode
|
||||||
|
|
||||||
|
- Runs every day at 02:00 UTC
|
||||||
|
- Can also be started manually from the GitHub Actions page
|
||||||
|
|
||||||
|
### Required configuration
|
||||||
|
|
||||||
|
Configure these repository secrets before enabling the workflow:
|
||||||
|
|
||||||
|
- TARGET_URL: target API endpoint to call
|
||||||
|
|
||||||
|
Optional secrets:
|
||||||
|
|
||||||
|
- AUTH_TOKEN: bearer token appended to the Authorization header
|
||||||
|
- REQUEST_BODY: raw JSON string sent as the request body
|
||||||
|
- HEADERS_JSON: JSON object string for extra request headers
|
||||||
|
|
||||||
|
Optional repository variables:
|
||||||
|
|
||||||
|
- HTTP_METHOD: defaults to GET
|
||||||
|
- EXPECTED_STATUS: defaults to 200
|
||||||
|
- RETRY_COUNT: defaults to 3
|
||||||
|
- RETRY_DELAY_MS: defaults to 5000
|
||||||
|
|
||||||
|
### Behavior
|
||||||
|
|
||||||
|
The workflow runs scripts/daily-task.js, which:
|
||||||
|
|
||||||
|
- calls the configured endpoint
|
||||||
|
- validates the returned status code
|
||||||
|
- retries on failure
|
||||||
|
- exits with a non-zero code when all retries fail
|
||||||
|
|
||||||
|
If you want this action to do something more specific, such as syncing data, generating reports, or updating files in the repository, the script can be extended from the same entry point.
|
||||||
|
|
||||||
|
|||||||
97
scripts/daily-task.js
Normal file
97
scripts/daily-task.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
const DEFAULT_METHOD = 'GET';
|
||||||
|
const DEFAULT_EXPECTED_STATUS = 200;
|
||||||
|
const DEFAULT_RETRY_COUNT = 3;
|
||||||
|
const DEFAULT_RETRY_DELAY_MS = 5000;
|
||||||
|
|
||||||
|
function readNumber(value, fallback) {
|
||||||
|
if (!value) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsed = Number.parseInt(value, 10);
|
||||||
|
return Number.isNaN(parsed) ? fallback : parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildHeaders() {
|
||||||
|
const headers = {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (process.env.HEADERS_JSON) {
|
||||||
|
const customHeaders = JSON.parse(process.env.HEADERS_JSON);
|
||||||
|
Object.assign(headers, customHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.AUTH_TOKEN) {
|
||||||
|
headers.authorization = `Bearer ${process.env.AUTH_TOKEN}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!process.env.REQUEST_BODY) {
|
||||||
|
delete headers['content-type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runTask() {
|
||||||
|
const targetUrl = process.env.TARGET_URL;
|
||||||
|
if (!targetUrl) {
|
||||||
|
throw new Error('Missing TARGET_URL secret.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const method = (process.env.HTTP_METHOD || DEFAULT_METHOD).toUpperCase();
|
||||||
|
const expectedStatus = readNumber(process.env.EXPECTED_STATUS, DEFAULT_EXPECTED_STATUS);
|
||||||
|
const retryCount = readNumber(process.env.RETRY_COUNT, DEFAULT_RETRY_COUNT);
|
||||||
|
const retryDelayMs = readNumber(process.env.RETRY_DELAY_MS, DEFAULT_RETRY_DELAY_MS);
|
||||||
|
const headers = buildHeaders();
|
||||||
|
|
||||||
|
const requestOptions = {
|
||||||
|
method,
|
||||||
|
headers,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (process.env.REQUEST_BODY) {
|
||||||
|
requestOptions.body = process.env.REQUEST_BODY;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let attempt = 1; attempt <= retryCount; attempt += 1) {
|
||||||
|
console.log(`Attempt ${attempt}/${retryCount}: ${method} ${targetUrl}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(targetUrl, requestOptions);
|
||||||
|
const responseText = await response.text();
|
||||||
|
|
||||||
|
console.log(`Response status: ${response.status}`);
|
||||||
|
if (responseText) {
|
||||||
|
console.log(`Response body: ${responseText.slice(0, 1000)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.status !== expectedStatus) {
|
||||||
|
throw new Error(`Expected status ${expectedStatus}, received ${response.status}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Scheduled task completed successfully.');
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
console.error(`Attempt ${attempt} failed: ${message}`);
|
||||||
|
|
||||||
|
if (attempt === retryCount) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Waiting ${retryDelayMs}ms before retry.`);
|
||||||
|
await sleep(retryDelayMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runTask().catch((error) => {
|
||||||
|
console.error('Scheduled task failed.');
|
||||||
|
console.error(error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user