mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-06 07:36:37 +08:00
feat(pages): 支持 Remote 部署源同步
新增部署源配置与运行态模型、安全下载、租约续期、原子激活和失败补偿。 接入内部任务与脱敏前端交互,并阻止数据库 Trace 和日志展开敏感查询参数。
This commit is contained in:
@@ -66,7 +66,7 @@ func DispatchTask(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
meta := task.GetTaskMeta(req.TaskType)
|
||||
meta := getAdminTaskMeta(req.TaskType)
|
||||
if meta == nil {
|
||||
response.AbortBadRequest(c, InvalidTaskType)
|
||||
return
|
||||
@@ -256,7 +256,7 @@ func CreateSchedule(c *gin.Context) {
|
||||
}
|
||||
|
||||
// 校验关联的异步任务类型
|
||||
meta := task.GetTaskMeta(req.TaskType)
|
||||
meta := getAdminTaskMeta(req.TaskType)
|
||||
if meta == nil {
|
||||
response.AbortBadRequest(c, InvalidTaskType)
|
||||
return
|
||||
@@ -338,6 +338,10 @@ func UpdateSchedule(c *gin.Context) {
|
||||
response.AbortNotFound(c, ScheduleNotFound)
|
||||
return
|
||||
}
|
||||
if existingMeta := task.GetTaskMeta(schedule.TaskType); existingMeta != nil && existingMeta.InternalOnly {
|
||||
response.AbortBadRequest(c, InvalidTaskType)
|
||||
return
|
||||
}
|
||||
|
||||
// 校验 Cron 表达式
|
||||
if _, err := cron.ParseStandard(req.Cron); err != nil {
|
||||
@@ -346,7 +350,7 @@ func UpdateSchedule(c *gin.Context) {
|
||||
}
|
||||
|
||||
// 校验关联的异步任务类型
|
||||
meta := task.GetTaskMeta(req.TaskType)
|
||||
meta := getAdminTaskMeta(req.TaskType)
|
||||
if meta == nil {
|
||||
response.AbortBadRequest(c, InvalidTaskType)
|
||||
return
|
||||
@@ -382,6 +386,14 @@ func UpdateSchedule(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, response.OK(schedule))
|
||||
}
|
||||
|
||||
func getAdminTaskMeta(taskType string) *task.TaskMeta {
|
||||
meta := task.GetTaskMeta(taskType)
|
||||
if meta == nil || meta.InternalOnly {
|
||||
return nil
|
||||
}
|
||||
return meta
|
||||
}
|
||||
|
||||
// DeleteSchedule 删除定时任务
|
||||
// @Summary 删除定时任务
|
||||
// @Description 删除指定的定时任务配置,并触发调度器热加载,需要管理员权限
|
||||
|
||||
@@ -29,6 +29,20 @@ import (
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response"
|
||||
)
|
||||
|
||||
const (
|
||||
testInternalOnlyTaskType = "test_internal_only_admin"
|
||||
testInternalOnlyAsynqTask = "test:internal_only_admin"
|
||||
)
|
||||
|
||||
func registerInternalOnlyTaskMeta() {
|
||||
task.RegisterTaskMeta(task.TaskMeta{
|
||||
Type: testInternalOnlyTaskType,
|
||||
AsynqTask: testInternalOnlyAsynqTask,
|
||||
Name: "内部测试任务",
|
||||
InternalOnly: true,
|
||||
})
|
||||
}
|
||||
|
||||
func setupTaskTestEnvironment(t *testing.T) func() {
|
||||
_, mr, cleanup := testhelper.SetupTestEnvironment(t)
|
||||
bootstrap.RegisterTasks()
|
||||
@@ -61,12 +75,15 @@ func setupTestRouter(authUser *model.User) *gin.Engine {
|
||||
adminGroup.GET("/tasks/executions", ListTaskExecutions)
|
||||
adminGroup.GET("/tasks/executions/:id", GetTaskExecution)
|
||||
adminGroup.POST("/tasks/executions/:id/retry", RetryTask)
|
||||
adminGroup.POST("/tasks/schedules", CreateSchedule)
|
||||
adminGroup.PUT("/tasks/schedules/:id", UpdateSchedule)
|
||||
return r
|
||||
}
|
||||
|
||||
func TestListTaskTypes(t *testing.T) {
|
||||
cleanup := setupTaskTestEnvironment(t)
|
||||
defer cleanup()
|
||||
registerInternalOnlyTaskMeta()
|
||||
|
||||
adminUser := &model.User{ID: 1001, Username: "admin", IsAdmin: true}
|
||||
router := setupTestRouter(adminUser)
|
||||
@@ -93,6 +110,9 @@ func TestListTaskTypes(t *testing.T) {
|
||||
foundCleanup := false
|
||||
foundWarmImageCache := false
|
||||
for _, m := range taskMetas {
|
||||
if m.Type == testInternalOnlyTaskType {
|
||||
t.Errorf("internal-only task type %s must not be listed", testInternalOnlyTaskType)
|
||||
}
|
||||
if m.Type == uploadtask.TaskTypeSystemCleanup {
|
||||
foundCleanup = true
|
||||
}
|
||||
@@ -108,6 +128,119 @@ func TestListTaskTypes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInternalOnlyTaskAdminBoundaries(t *testing.T) {
|
||||
cleanup := setupTaskTestEnvironment(t)
|
||||
defer cleanup()
|
||||
registerInternalOnlyTaskMeta()
|
||||
|
||||
adminUser := &model.User{ID: 1001, Username: "admin", IsAdmin: true}
|
||||
router := setupTestRouter(adminUser)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("dispatch rejects internal-only task", func(t *testing.T) {
|
||||
body, err := json.Marshal(DispatchTaskRequest{TaskType: testInternalOnlyTaskType})
|
||||
require.NoError(t, err)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/tasks/dispatch", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
var resp response.Any
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
assert.Equal(t, InvalidTaskType, resp.ErrorMsg)
|
||||
})
|
||||
|
||||
t.Run("create schedule rejects internal-only task", func(t *testing.T) {
|
||||
isActive := true
|
||||
body, err := json.Marshal(CreateScheduleRequest{
|
||||
Name: "内部任务排程",
|
||||
TaskType: testInternalOnlyTaskType,
|
||||
Cron: "0 * * * *",
|
||||
IsActive: &isActive,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/tasks/schedules", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
var resp response.Any
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
assert.Equal(t, InvalidTaskType, resp.ErrorMsg)
|
||||
})
|
||||
|
||||
t.Run("update cannot convert existing internal schedule to public task", func(t *testing.T) {
|
||||
schedule := &model.Schedule{
|
||||
Name: "系统内部排程",
|
||||
TaskType: testInternalOnlyTaskType,
|
||||
Cron: "0 * * * *",
|
||||
IsActive: true,
|
||||
}
|
||||
require.NoError(t, model.CreateSchedule(ctx, schedule))
|
||||
isActive := false
|
||||
body, err := json.Marshal(UpdateScheduleRequest{
|
||||
Name: "尝试修改内部排程",
|
||||
TaskType: uploadtask.TaskTypeSystemCleanup,
|
||||
Cron: "5 * * * *",
|
||||
IsActive: &isActive,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
req := httptest.NewRequest(
|
||||
http.MethodPut,
|
||||
fmt.Sprintf("/api/v1/admin/tasks/schedules/%d", schedule.ID),
|
||||
bytes.NewReader(body),
|
||||
)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
unchanged, err := model.GetScheduleByID(ctx, schedule.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "系统内部排程", unchanged.Name)
|
||||
assert.Equal(t, testInternalOnlyTaskType, unchanged.TaskType)
|
||||
assert.True(t, unchanged.IsActive)
|
||||
})
|
||||
|
||||
t.Run("update public schedule rejects internal-only target task", func(t *testing.T) {
|
||||
schedule := &model.Schedule{
|
||||
Name: "公开排程",
|
||||
TaskType: uploadtask.TaskTypeSystemCleanup,
|
||||
Cron: "0 * * * *",
|
||||
IsActive: true,
|
||||
}
|
||||
require.NoError(t, model.CreateSchedule(ctx, schedule))
|
||||
isActive := true
|
||||
body, err := json.Marshal(UpdateScheduleRequest{
|
||||
Name: "尝试切入内部任务",
|
||||
TaskType: testInternalOnlyTaskType,
|
||||
Cron: "10 * * * *",
|
||||
IsActive: &isActive,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
req := httptest.NewRequest(
|
||||
http.MethodPut,
|
||||
fmt.Sprintf("/api/v1/admin/tasks/schedules/%d", schedule.ID),
|
||||
bytes.NewReader(body),
|
||||
)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
unchanged, err := model.GetScheduleByID(ctx, schedule.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "公开排程", unchanged.Name)
|
||||
assert.Equal(t, uploadtask.TaskTypeSystemCleanup, unchanged.TaskType)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDispatchTask(t *testing.T) {
|
||||
cleanup := setupTaskTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
Reference in New Issue
Block a user