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 HttpError from "../interfaces/http-error";
|
||||||
import HttpStatus from "../interfaces/http-status";
|
import HttpStatus from "../interfaces/http-status";
|
||||||
|
import { randomUUID } from "crypto";
|
||||||
|
|
||||||
function getEnvVar(environmentName: string) {
|
function getEnvVar(environmentName: string) {
|
||||||
const environmentValue = process.env[environmentName];
|
const environmentValue = process.env[environmentName];
|
||||||
|
|
@ -241,4 +242,171 @@ export class BackupController extends Controller {
|
||||||
return data;
|
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:
|
modules:
|
||||||
- id: c
|
- id: c
|
||||||
value:
|
value:
|
||||||
|
tag: ""
|
||||||
lock: |-
|
lock: |-
|
||||||
{
|
{
|
||||||
"dependencies": {}
|
"dependencies": {}
|
||||||
|
|
@ -195,6 +196,32 @@ value:
|
||||||
databaseBackupBucket:
|
databaseBackupBucket:
|
||||||
expr: "`${flow_input.database.s3_bucket}`"
|
expr: "`${flow_input.database.s3_bucket}`"
|
||||||
type: javascript
|
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
|
- id: a
|
||||||
value:
|
value:
|
||||||
path: f/storage/backup_s3
|
path: f/storage/backup_s3
|
||||||
|
|
@ -204,7 +231,7 @@ value:
|
||||||
expr: "`${flow_input.storage.s3_dest_access}`"
|
expr: "`${flow_input.storage.s3_dest_access}`"
|
||||||
type: javascript
|
type: javascript
|
||||||
s3_dest_bucket:
|
s3_dest_bucket:
|
||||||
expr: "`${flow_input.backup_name}`"
|
expr: "`${results.d}`"
|
||||||
type: javascript
|
type: javascript
|
||||||
s3_dest_secret:
|
s3_dest_secret:
|
||||||
expr: "`${flow_input.storage.s3_dest_secret}`"
|
expr: "`${flow_input.storage.s3_dest_secret}`"
|
||||||
|
|
@ -261,7 +288,7 @@ value:
|
||||||
expr: "`${flow_input.database.s3_endpoint}`"
|
expr: "`${flow_input.database.s3_endpoint}`"
|
||||||
type: javascript
|
type: javascript
|
||||||
backup_filename:
|
backup_filename:
|
||||||
expr: "`${flow_input.backup_name}`"
|
expr: "`${results.d}`"
|
||||||
type: javascript
|
type: javascript
|
||||||
schema:
|
schema:
|
||||||
$schema: https://json-schema.org/draft/2020-12/schema
|
$schema: https://json-schema.org/draft/2020-12/schema
|
||||||
|
|
@ -495,6 +522,9 @@ schema:
|
||||||
- s3_dest_access
|
- s3_dest_access
|
||||||
- s3_dest_secret
|
- s3_dest_secret
|
||||||
description: ""
|
description: ""
|
||||||
|
prefix_timestamp:
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
required:
|
required:
|
||||||
- database
|
- database
|
||||||
- storage
|
- storage
|
||||||
|
|
@ -504,6 +534,7 @@ schema:
|
||||||
- backup_name
|
- backup_name
|
||||||
- database
|
- database
|
||||||
- storage
|
- storage
|
||||||
|
- prefix_timestamp
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue