feat: schedule backup
This commit is contained in:
parent
d070d46525
commit
ca12d80aa8
2 changed files with 202 additions and 3 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { Body, Controller, Delete, Get, Post, Route, Security } from "tsoa";
|
||||
import { Body, Controller, Delete, Get, Path, Post, Put, Route, Security } from "tsoa";
|
||||
import HttpError from "../interfaces/http-error";
|
||||
import HttpStatus from "../interfaces/http-status";
|
||||
import { randomUUID } from "crypto";
|
||||
|
||||
function getEnvVar(environmentName: string) {
|
||||
const environmentValue = process.env[environmentName];
|
||||
|
|
@ -241,4 +242,171 @@ export class BackupController extends Controller {
|
|||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
@Get("schedule")
|
||||
async listSchedule() {
|
||||
const result = await fetch(
|
||||
`${WINDMILL_URL}/api/w/${WINDMILL_WORKSPACE}/schedules/list?path=${WINDMILL_BACKUP_FLOW_PATH}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${WINDMILL_API_KEY}`,
|
||||
},
|
||||
},
|
||||
).then(async (r) => {
|
||||
const data = await r.json();
|
||||
if (typeof data === "object" && "error" in data) {
|
||||
console.error(data);
|
||||
throw new Error("Cannot get status.");
|
||||
}
|
||||
return data as Record<string, any>[];
|
||||
});
|
||||
|
||||
return result.map((v) => ({
|
||||
id: v.path.replace("f/backup_schedule/", ""),
|
||||
name: v.summary,
|
||||
schedule: v.schedule,
|
||||
}));
|
||||
}
|
||||
|
||||
@Post("schedule")
|
||||
async createSchedule(@Body() body: { name: string; schedule: string; timezone?: string }) {
|
||||
if (!/^[a-zA-Z0-9\-]+$/.test(body.name)) {
|
||||
throw new HttpError(HttpStatus.BAD_REQUEST, "Invalid name.");
|
||||
}
|
||||
|
||||
return await fetch(`${WINDMILL_URL}/api/w/${WINDMILL_WORKSPACE}/schedules/create`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${WINDMILL_API_KEY}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
schedule: body.schedule,
|
||||
summary: body.name,
|
||||
path: `f/backup_schedule/${randomUUID()}`,
|
||||
enabled: true,
|
||||
is_flow: true,
|
||||
script_path: WINDMILL_BACKUP_FLOW_PATH,
|
||||
timezone: body.timezone || "Asia/Bangkok",
|
||||
args: {
|
||||
backup_name: body.name,
|
||||
prefix_timestamp: true,
|
||||
storage: {
|
||||
s3_source_endpoint: `${MAIN_MINIO_USE_SSL === "true" ? "https://" : "http://"}${MAIN_MINIO_HOST}${(MAIN_MINIO_PORT && ":" + MAIN_MINIO_PORT) || ""}`,
|
||||
s3_source_access: MAIN_MINIO_ACCESS_KEY,
|
||||
s3_source_secret: MAIN_MINIO_SECRET_KEY,
|
||||
s3_source_bucket: MAIN_MINIO_BUCKET,
|
||||
s3_dest_endpoint: `${BACKUP_MINIO_USE_SSL === "true" ? "https" : "http://"}${BACKUP_MINIO_HOST}${(BACKUP_MINIO_PORT && ":" + BACKUP_MINIO_PORT) || ""}`,
|
||||
s3_dest_access: BACKUP_MINIO_ACCESS_KEY,
|
||||
s3_dest_secret: BACKUP_MINIO_SECRET_KEY,
|
||||
},
|
||||
database: {
|
||||
s3_endpoint: `${BACKUP_MINIO_USE_SSL === "true" ? "https" : "http://"}${BACKUP_MINIO_HOST}${(BACKUP_MINIO_PORT && ":" + BACKUP_MINIO_PORT) || ""}`,
|
||||
s3_access: BACKUP_MINIO_ACCESS_KEY,
|
||||
s3_secret: BACKUP_MINIO_SECRET_KEY,
|
||||
s3_bucket: BACKUP_MINIO_BUCKET,
|
||||
db_host: DB_HOST,
|
||||
db_port: DB_PORT,
|
||||
db_user: DB_USERNAME,
|
||||
db_password: DB_PASSWORD,
|
||||
db_list: DB_LIST?.replace(",", " "),
|
||||
},
|
||||
},
|
||||
}),
|
||||
}).then(async (r) => {
|
||||
const data = await r.text();
|
||||
if (data.includes("error")) {
|
||||
console.error(data);
|
||||
throw new Error("Error create schedule.");
|
||||
}
|
||||
console.log(data);
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
@Put("schedule/{id}")
|
||||
async updateSchedule(
|
||||
@Path() id: string,
|
||||
@Body() body: { name: string; schedule: string; timezone?: string },
|
||||
) {
|
||||
if (!/^[a-zA-Z0-9\-]+$/.test(body.name)) {
|
||||
throw new HttpError(HttpStatus.BAD_REQUEST, "Invalid name.");
|
||||
}
|
||||
|
||||
return await fetch(
|
||||
`${WINDMILL_URL}/api/w/${WINDMILL_WORKSPACE}/schedules/update/f/backup_schedule/${id}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${WINDMILL_API_KEY}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
summary: body.name,
|
||||
schedule: body.schedule,
|
||||
timezone: body.timezone || "Asia/Bangkok",
|
||||
args: {
|
||||
backup_name: body.name,
|
||||
prefix_timestamp: true,
|
||||
storage: {
|
||||
s3_source_endpoint: `${MAIN_MINIO_USE_SSL === "true" ? "https://" : "http://"}${MAIN_MINIO_HOST}${(MAIN_MINIO_PORT && ":" + MAIN_MINIO_PORT) || ""}`,
|
||||
s3_source_access: MAIN_MINIO_ACCESS_KEY,
|
||||
s3_source_secret: MAIN_MINIO_SECRET_KEY,
|
||||
s3_source_bucket: MAIN_MINIO_BUCKET,
|
||||
s3_dest_endpoint: `${BACKUP_MINIO_USE_SSL === "true" ? "https" : "http://"}${BACKUP_MINIO_HOST}${(BACKUP_MINIO_PORT && ":" + BACKUP_MINIO_PORT) || ""}`,
|
||||
s3_dest_access: BACKUP_MINIO_ACCESS_KEY,
|
||||
s3_dest_secret: BACKUP_MINIO_SECRET_KEY,
|
||||
},
|
||||
database: {
|
||||
s3_endpoint: `${BACKUP_MINIO_USE_SSL === "true" ? "https" : "http://"}${BACKUP_MINIO_HOST}${(BACKUP_MINIO_PORT && ":" + BACKUP_MINIO_PORT) || ""}`,
|
||||
s3_access: BACKUP_MINIO_ACCESS_KEY,
|
||||
s3_secret: BACKUP_MINIO_SECRET_KEY,
|
||||
s3_bucket: BACKUP_MINIO_BUCKET,
|
||||
db_host: DB_HOST,
|
||||
db_port: DB_PORT,
|
||||
db_user: DB_USERNAME,
|
||||
db_password: DB_PASSWORD,
|
||||
db_list: DB_LIST?.replace(",", " "),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
).then(async (r) => {
|
||||
if (r.status >= 400) {
|
||||
console.log(await r.json());
|
||||
throw new Error("Error trying to run script/flow.");
|
||||
}
|
||||
const data = await r.text();
|
||||
|
||||
if (data.includes("error")) {
|
||||
console.error(data);
|
||||
throw new Error("Error create schedule.");
|
||||
}
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
@Delete("schedule/{id}")
|
||||
async deleteSchedule(@Path() id: string) {
|
||||
return await fetch(
|
||||
`${WINDMILL_URL}/api/w/${WINDMILL_WORKSPACE}/schedules/delete/f/backup_schedule/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
Authorization: `Bearer ${WINDMILL_API_KEY}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
).then(async (r) => {
|
||||
if (r.status === 404) {
|
||||
throw new HttpError(HttpStatus.NOT_FOUND, "Schedule not found.");
|
||||
}
|
||||
|
||||
const data = await r.text();
|
||||
if (data.includes("error")) {
|
||||
console.error(data);
|
||||
throw new Error("Error create schedule.");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -174,6 +174,7 @@ value:
|
|||
modules:
|
||||
- id: c
|
||||
value:
|
||||
tag: ""
|
||||
lock: |-
|
||||
{
|
||||
"dependencies": {}
|
||||
|
|
@ -195,6 +196,32 @@ value:
|
|||
databaseBackupBucket:
|
||||
expr: "`${flow_input.database.s3_bucket}`"
|
||||
type: javascript
|
||||
summary: Conflict Backup Database Bucket Check
|
||||
- id: d
|
||||
value:
|
||||
tag: ""
|
||||
lock: |-
|
||||
{
|
||||
"dependencies": {}
|
||||
}
|
||||
//bun.lockb
|
||||
<empty>
|
||||
type: rawscript
|
||||
content: >
|
||||
export async function main(prefixTimestamp: boolean, bucketName:
|
||||
string) {
|
||||
if (prefixTimestamp) return `${Math.round(Date.now() / 1000)}-${bucketName}`;
|
||||
return bucketName;
|
||||
}
|
||||
language: bun
|
||||
input_transforms:
|
||||
bucketName:
|
||||
expr: flow_input.backup_name
|
||||
type: javascript
|
||||
prefixTimestamp:
|
||||
expr: flow_input.prefix_timestamp
|
||||
type: javascript
|
||||
summary: Prefix backup name with timestamp
|
||||
- id: a
|
||||
value:
|
||||
path: f/storage/backup_s3
|
||||
|
|
@ -204,7 +231,7 @@ value:
|
|||
expr: "`${flow_input.storage.s3_dest_access}`"
|
||||
type: javascript
|
||||
s3_dest_bucket:
|
||||
expr: "`${flow_input.backup_name}`"
|
||||
expr: "`${results.d}`"
|
||||
type: javascript
|
||||
s3_dest_secret:
|
||||
expr: "`${flow_input.storage.s3_dest_secret}`"
|
||||
|
|
@ -261,7 +288,7 @@ value:
|
|||
expr: "`${flow_input.database.s3_endpoint}`"
|
||||
type: javascript
|
||||
backup_filename:
|
||||
expr: "`${flow_input.backup_name}`"
|
||||
expr: "`${results.d}`"
|
||||
type: javascript
|
||||
schema:
|
||||
$schema: https://json-schema.org/draft/2020-12/schema
|
||||
|
|
@ -495,6 +522,9 @@ schema:
|
|||
- s3_dest_access
|
||||
- s3_dest_secret
|
||||
description: ""
|
||||
prefix_timestamp:
|
||||
default: false
|
||||
type: boolean
|
||||
required:
|
||||
- database
|
||||
- storage
|
||||
|
|
@ -504,6 +534,7 @@ schema:
|
|||
- backup_name
|
||||
- database
|
||||
- storage
|
||||
- prefix_timestamp
|
||||
```
|
||||
|
||||
```yaml
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue