mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-22 18:25:56 +01:00
Merge pull request #16865 from stonezdj/22may17_purge_audit_log_rest_api
Add REST API for purge audit log
This commit is contained in:
commit
4637af8866
@ -4174,6 +4174,170 @@ paths:
|
||||
$ref: '#/responses/403'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
/system/purgeaudit:
|
||||
get:
|
||||
summary: Get purge job results.
|
||||
description: get purge job execution history.
|
||||
tags:
|
||||
- purge
|
||||
operationId: getPurgeHistory
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/query'
|
||||
- $ref: '#/parameters/sort'
|
||||
- $ref: '#/parameters/page'
|
||||
- $ref: '#/parameters/pageSize'
|
||||
responses:
|
||||
'200':
|
||||
description: Get purge job results successfully.
|
||||
headers:
|
||||
X-Total-Count:
|
||||
description: The total count of history
|
||||
type: integer
|
||||
Link:
|
||||
description: Link refers to the previous page and next page
|
||||
type: string
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/ExecHistory'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
/system/purgeaudit/{purge_id}:
|
||||
get:
|
||||
summary: Get purge job status.
|
||||
description: This endpoint let user get purge job status filtered by specific ID.
|
||||
operationId: getPurgeJob
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/purgeId'
|
||||
tags:
|
||||
- purge
|
||||
responses:
|
||||
'200':
|
||||
description: Get purge job results successfully.
|
||||
schema:
|
||||
$ref: '#/definitions/ExecHistory'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'404':
|
||||
$ref: '#/responses/404'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
/system/purgeaudit/{purge_id}/log:
|
||||
get:
|
||||
summary: Get purge job log.
|
||||
description: This endpoint let user get purge job logs filtered by specific ID.
|
||||
operationId: getPurgeJobLog
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/purgeId'
|
||||
tags:
|
||||
- purge
|
||||
produces:
|
||||
- text/plain
|
||||
responses:
|
||||
'200':
|
||||
description: Get successfully.
|
||||
schema:
|
||||
type: string
|
||||
'400':
|
||||
$ref: '#/responses/400'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'404':
|
||||
$ref: '#/responses/404'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
/system/purgeaudit/schedule:
|
||||
get:
|
||||
summary: Get purge's schedule.
|
||||
description: This endpoint is for get schedule of purge job.
|
||||
operationId: getPurgeSchedule
|
||||
tags:
|
||||
- purge
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
responses:
|
||||
'200':
|
||||
description: Get purge job's schedule.
|
||||
schema:
|
||||
$ref: '#/definitions/ExecHistory'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
post:
|
||||
summary: Create a purge job schedule.
|
||||
description: |
|
||||
This endpoint is for update purge job schedule.
|
||||
operationId: createPurgeSchedule
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- name: schedule
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/Schedule'
|
||||
description: |
|
||||
The purge job's schedule, it is a json object. |
|
||||
The sample format is |
|
||||
{"parameters":{"audit_retention_hour":168,"dry_run":true, "include_operations":"create,delete,pull"},"schedule":{"type":"Hourly","cron":"0 0 * * * *"}} |
|
||||
the include_operation should be a comma separated string, e.g. create,delete,pull, if it is empty, no operation will be purged.
|
||||
tags:
|
||||
- purge
|
||||
responses:
|
||||
'201':
|
||||
$ref: '#/responses/201'
|
||||
'400':
|
||||
$ref: '#/responses/400'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
put:
|
||||
summary: Update purge job's schedule.
|
||||
description: |
|
||||
This endpoint is for update purge job schedule.
|
||||
operationId: updatePurgeSchedule
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- name: schedule
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/Schedule'
|
||||
description: |
|
||||
The purge job's schedule, it is a json object. |
|
||||
The sample format is |
|
||||
{"parameters":{"audit_retention_hour":168,"dry_run":true, "include_operations":"create,delete,pull"},"schedule":{"type":"Hourly","cron":"0 0 * * * *"}} |
|
||||
the include_operation should be a comma separated string, e.g. create,delete,pull, if it is empty, no operation will be purged.
|
||||
tags:
|
||||
- purge
|
||||
responses:
|
||||
'200':
|
||||
description: Updated purge's schedule successfully.
|
||||
'400':
|
||||
$ref: '#/responses/400'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
|
||||
/system/CVEAllowlist:
|
||||
get:
|
||||
summary: Get the system level allowlist of CVE.
|
||||
@ -5523,6 +5687,13 @@ parameters:
|
||||
required: true
|
||||
type: integer
|
||||
format: int64
|
||||
purgeId:
|
||||
name: purge_id
|
||||
in: path
|
||||
description: The ID of the purge log
|
||||
required: true
|
||||
type: integer
|
||||
format: int64
|
||||
labelId:
|
||||
name: label_id
|
||||
in: path
|
||||
@ -7224,6 +7395,37 @@ definitions:
|
||||
type: string
|
||||
format: date-time
|
||||
description: the update time of gc job.
|
||||
ExecHistory:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
description: the id of purge job.
|
||||
job_name:
|
||||
type: string
|
||||
description: the job name of purge job.
|
||||
job_kind:
|
||||
type: string
|
||||
description: the job kind of purge job.
|
||||
job_parameters:
|
||||
type: string
|
||||
description: the job parameters of purge job.
|
||||
schedule:
|
||||
$ref: '#/definitions/ScheduleObj'
|
||||
job_status:
|
||||
type: string
|
||||
description: the status of purge job.
|
||||
deleted:
|
||||
type: boolean
|
||||
description: if purge job was deleted.
|
||||
creation_time:
|
||||
type: string
|
||||
format: date-time
|
||||
description: the creation time of purge job.
|
||||
update_time:
|
||||
type: string
|
||||
format: date-time
|
||||
description: the update time of purge job.
|
||||
Schedule:
|
||||
type: object
|
||||
properties:
|
||||
|
28
src/controller/jobservice/model.go
Normal file
28
src/controller/jobservice/model.go
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package jobservice
|
||||
|
||||
import "time"
|
||||
|
||||
// Execution model for replication
|
||||
type Execution struct {
|
||||
ID int64
|
||||
Status string
|
||||
StatusMessage string
|
||||
Trigger string
|
||||
ExtraAttrs map[string]interface{}
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
}
|
@ -308,8 +308,8 @@ func (bs *Bootstrap) loadAndRunRedisWorkerPool(
|
||||
job.SampleJob: (*sample.Job)(nil),
|
||||
// Functional jobs
|
||||
job.ImageScanJob: (*scan.Job)(nil),
|
||||
job.GarbageCollection: (*gc.GarbageCollector)(nil),
|
||||
job.PurgeAudit: (*purge.Job)(nil),
|
||||
job.GarbageCollection: (*gc.GarbageCollector)(nil),
|
||||
job.Replication: (*replication.Replication)(nil),
|
||||
job.Retention: (*retention.Job)(nil),
|
||||
scheduler.JobNameScheduler: (*scheduler.PeriodicJob)(nil),
|
||||
|
@ -14,7 +14,11 @@
|
||||
|
||||
package lib
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TrimsLineBreaks trims line breaks in string.
|
||||
func TrimLineBreaks(s string) string {
|
||||
@ -22,3 +26,9 @@ func TrimLineBreaks(s string) string {
|
||||
escaped = strings.ReplaceAll(escaped, "\r", "")
|
||||
return escaped
|
||||
}
|
||||
|
||||
// Title uppercase the first character, and lower case the rest, for example covert MANUAL to Manual
|
||||
func Title(s string) string {
|
||||
title := cases.Title(language.Und)
|
||||
return title.String(strings.ToLower(s))
|
||||
}
|
||||
|
@ -32,3 +32,23 @@ def
|
||||
actual := TrimLineBreaks(s)
|
||||
assert.Equal(expect, actual, "should trim line breaks")
|
||||
}
|
||||
|
||||
func TestTitle(t *testing.T) {
|
||||
type args struct {
|
||||
s string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{"upper case", args{"MANUAL"}, "Manual"},
|
||||
{"lower case", args{"manual"}, "Manual"},
|
||||
{"empty", args{""}, ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equalf(t, tt.want, Title(tt.args.s), "Title(%v)", tt.args.s)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -114,7 +114,13 @@ func (s *scheduler) Schedule(ctx context.Context, vendorType string, vendorID in
|
||||
return 0, err
|
||||
}
|
||||
sched.CallbackFuncParam = string(paramsData)
|
||||
|
||||
params := map[string]interface{}{}
|
||||
if len(paramsData) > 0 {
|
||||
err = json.Unmarshal(paramsData, ¶ms)
|
||||
if err != nil {
|
||||
log.Debugf("current paramsData is not a json string")
|
||||
}
|
||||
}
|
||||
extrasData, err := json.Marshal(extraAttrs)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@ -129,7 +135,7 @@ func (s *scheduler) Schedule(ctx context.Context, vendorType string, vendorID in
|
||||
return 0, err
|
||||
}
|
||||
|
||||
execID, err := s.execMgr.Create(ctx, JobNameScheduler, id, task.ExecutionTriggerManual)
|
||||
execID, err := s.execMgr.Create(ctx, JobNameScheduler, id, task.ExecutionTriggerManual, params)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ func (s *schedulerTestSuite) TestSchedule() {
|
||||
|
||||
// failed to submit to jobservice
|
||||
s.dao.On("Create", mock.Anything, mock.Anything).Return(int64(1), nil)
|
||||
s.execMgr.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil)
|
||||
s.execMgr.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil)
|
||||
s.taskMgr.On("Create", mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil)
|
||||
s.taskMgr.On("Get", mock.Anything, mock.Anything).Return(&task.Task{
|
||||
ID: 1,
|
||||
@ -84,7 +84,7 @@ func (s *schedulerTestSuite) TestSchedule() {
|
||||
s.SetupTest()
|
||||
|
||||
// pass
|
||||
s.execMgr.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil)
|
||||
s.execMgr.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil)
|
||||
s.dao.On("Create", mock.Anything, mock.Anything).Return(int64(1), nil)
|
||||
s.taskMgr.On("Create", mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil)
|
||||
s.taskMgr.On("Get", mock.Anything, mock.Anything).Return(&task.Task{
|
||||
|
@ -64,6 +64,7 @@ func New() http.Handler {
|
||||
HealthAPI: newHealthAPI(),
|
||||
StatisticAPI: newStatisticAPI(),
|
||||
ProjectMetadataAPI: newProjectMetadaAPI(),
|
||||
PurgeAPI: newPurgeAPI(),
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
57
src/server/v2.0/handler/model/jobservice.go
Normal file
57
src/server/v2.0/handler/model/jobservice.go
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ExecHistory execution history
|
||||
type ExecHistory struct {
|
||||
Schedule *ScheduleParam `json:"schedule"`
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"job_name"`
|
||||
Kind string `json:"job_kind"`
|
||||
Parameters string `json:"job_parameters"`
|
||||
Status string `json:"job_status"`
|
||||
UUID string `json:"-"`
|
||||
Deleted bool `json:"deleted"`
|
||||
CreationTime time.Time `json:"creation_time"`
|
||||
UpdateTime time.Time `json:"update_time"`
|
||||
}
|
||||
|
||||
// ToSwagger converts the history to the swagger model
|
||||
func (h *ExecHistory) ToSwagger() *models.ExecHistory {
|
||||
return &models.ExecHistory{
|
||||
ID: h.ID,
|
||||
JobName: h.Name,
|
||||
JobKind: h.Kind,
|
||||
JobParameters: h.Parameters,
|
||||
Deleted: h.Deleted,
|
||||
JobStatus: h.Status,
|
||||
Schedule: &models.ScheduleObj{
|
||||
// covert MANUAL to Manual because the type of the ScheduleObj
|
||||
// must be 'Hourly', 'Daily', 'Weekly', 'Custom', 'Manual' and 'None'
|
||||
Type: lib.Title(strings.ToLower(h.Schedule.Type)),
|
||||
Cron: h.Schedule.Cron,
|
||||
},
|
||||
CreationTime: strfmt.DateTime(h.CreationTime),
|
||||
UpdateTime: strfmt.DateTime(h.UpdateTime),
|
||||
}
|
||||
}
|
301
src/server/v2.0/handler/purge.go
Normal file
301
src/server/v2.0/handler/purge.go
Normal file
@ -0,0 +1,301 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/controller/jobservice"
|
||||
pg "github.com/goharbor/harbor/src/controller/purge"
|
||||
"github.com/goharbor/harbor/src/controller/task"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
taskPkg "github.com/goharbor/harbor/src/pkg/task"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/handler/model"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/restapi/operations/purge"
|
||||
operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/purge"
|
||||
"path"
|
||||
)
|
||||
|
||||
type purgeAPI struct {
|
||||
BaseAPI
|
||||
purgeCtr pg.Controller
|
||||
schedulerCtl jobservice.SchedulerController
|
||||
taskCtl task.Controller
|
||||
executionCtl task.ExecutionController
|
||||
}
|
||||
|
||||
func newPurgeAPI() *purgeAPI {
|
||||
return &purgeAPI{
|
||||
purgeCtr: pg.Ctrl,
|
||||
schedulerCtl: jobservice.SchedulerCtl,
|
||||
taskCtl: task.Ctl,
|
||||
executionCtl: task.ExecutionCtl,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *purgeAPI) CreatePurgeSchedule(ctx context.Context, params purge.CreatePurgeScheduleParams) middleware.Responder {
|
||||
if err := p.RequireSystemAccess(ctx, rbac.ActionCreate, rbac.ResourcePurgeAuditLog); err != nil {
|
||||
return p.SendError(ctx, err)
|
||||
}
|
||||
if err := verifyCreateRequest(params); err != nil {
|
||||
return p.SendError(ctx, err)
|
||||
}
|
||||
id, err := p.kick(ctx, pg.VendorType, params.Schedule.Schedule.Type, params.Schedule.Schedule.Cron, params.Schedule.Parameters)
|
||||
if err != nil {
|
||||
return p.SendError(ctx, err)
|
||||
}
|
||||
location := path.Join(params.HTTPRequest.URL.Path, fmt.Sprintf("../%d", id))
|
||||
return purge.NewCreatePurgeScheduleCreated().WithLocation(location)
|
||||
}
|
||||
|
||||
func verifyCreateRequest(params purge.CreatePurgeScheduleParams) error {
|
||||
if params.Schedule == nil || params.Schedule.Schedule == nil {
|
||||
return errors.BadRequestError(fmt.Errorf("schedule cann't be empty"))
|
||||
}
|
||||
if len(params.Schedule.Parameters) == 0 {
|
||||
return errors.BadRequestError(fmt.Errorf("schedule parameter cann't be empty"))
|
||||
}
|
||||
if _, exist := params.Schedule.Parameters[common.PurgeAuditRetentionHour]; !exist {
|
||||
return errors.BadRequestError(fmt.Errorf("audit_retention_hour should provide"))
|
||||
}
|
||||
if _, exist := params.Schedule.Parameters[common.PurgeAuditIncludeOperations]; !exist {
|
||||
return errors.BadRequestError(fmt.Errorf("include_operations should provide"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *purgeAPI) kick(ctx context.Context, vendorType string, scheType string, cron string, parameters map[string]interface{}) (int64, error) {
|
||||
if parameters == nil {
|
||||
parameters = make(map[string]interface{})
|
||||
}
|
||||
var err error
|
||||
var id int64
|
||||
|
||||
policy := pg.JobPolicy{
|
||||
ExtraAttrs: parameters,
|
||||
}
|
||||
if dryRun, ok := parameters[common.PurgeAuditDryRun].(bool); ok {
|
||||
policy.DryRun = dryRun
|
||||
}
|
||||
if includeOperations, ok := parameters[common.PurgeAuditIncludeOperations].(string); ok {
|
||||
policy.IncludeOperations = includeOperations
|
||||
}
|
||||
if retentionHour, ok := parameters[common.PurgeAuditRetentionHour]; ok {
|
||||
if rh, ok := retentionHour.(json.Number); ok {
|
||||
ret, err := rh.Int64()
|
||||
if err != nil {
|
||||
return 0, errors.BadRequestError(fmt.Errorf("failed to convert audit_retention_hour, error: %v", err))
|
||||
}
|
||||
policy.RetentionHour = int(ret)
|
||||
}
|
||||
}
|
||||
|
||||
switch scheType {
|
||||
case ScheduleManual:
|
||||
id, err = p.purgeCtr.Start(ctx, policy, taskPkg.ExecutionTriggerManual)
|
||||
case ScheduleNone:
|
||||
// delete the schedule of purge
|
||||
err = p.schedulerCtl.Delete(ctx, vendorType)
|
||||
case ScheduleHourly, ScheduleDaily, ScheduleWeekly, ScheduleCustom:
|
||||
err = p.updateSchedule(ctx, vendorType, scheType, cron, policy, parameters)
|
||||
}
|
||||
return id, err
|
||||
}
|
||||
|
||||
func (p *purgeAPI) updateSchedule(ctx context.Context, vendorType, cronType, cron string, policy pg.JobPolicy, extraParams map[string]interface{}) error {
|
||||
if err := p.schedulerCtl.Delete(ctx, vendorType); err != nil {
|
||||
return err
|
||||
}
|
||||
return p.createSchedule(ctx, vendorType, cronType, cron, policy, extraParams)
|
||||
}
|
||||
|
||||
func (p *purgeAPI) GetPurgeHistory(ctx context.Context, params purge.GetPurgeHistoryParams) middleware.Responder {
|
||||
if err := p.RequireSystemAccess(ctx, rbac.ActionList, rbac.ResourcePurgeAuditLog); err != nil {
|
||||
return p.SendError(ctx, err)
|
||||
}
|
||||
query, err := p.BuildQuery(ctx, params.Q, params.Sort, params.Page, params.PageSize)
|
||||
query.Keywords["VendorType"] = pg.VendorType
|
||||
if err != nil {
|
||||
return p.SendError(ctx, err)
|
||||
}
|
||||
total, err := p.executionCtl.Count(ctx, query)
|
||||
if err != nil {
|
||||
return p.SendError(ctx, err)
|
||||
}
|
||||
execs, err := p.executionCtl.List(ctx, query)
|
||||
if err != nil {
|
||||
p.SendError(ctx, err)
|
||||
}
|
||||
|
||||
var hs []*model.ExecHistory
|
||||
for _, exec := range execs {
|
||||
extraAttrsString, err := json.Marshal(exec.ExtraAttrs)
|
||||
if err != nil {
|
||||
return p.SendError(ctx, err)
|
||||
}
|
||||
hs = append(hs, &model.ExecHistory{
|
||||
ID: exec.ID,
|
||||
Name: pg.VendorType,
|
||||
Kind: exec.Trigger,
|
||||
Parameters: string(extraAttrsString),
|
||||
Schedule: &model.ScheduleParam{
|
||||
Type: exec.Trigger,
|
||||
},
|
||||
Status: exec.Status,
|
||||
CreationTime: exec.StartTime,
|
||||
UpdateTime: exec.EndTime,
|
||||
})
|
||||
}
|
||||
var results []*models.ExecHistory
|
||||
for _, h := range hs {
|
||||
results = append(results, h.ToSwagger())
|
||||
}
|
||||
|
||||
return operation.NewGetPurgeHistoryOK().
|
||||
WithXTotalCount(total).
|
||||
WithLink(p.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String()).
|
||||
WithPayload(results)
|
||||
}
|
||||
|
||||
func (p *purgeAPI) GetPurgeJob(ctx context.Context, params purge.GetPurgeJobParams) middleware.Responder {
|
||||
if err := p.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourcePurgeAuditLog); err != nil {
|
||||
return p.SendError(ctx, err)
|
||||
}
|
||||
|
||||
exec, err := p.executionCtl.Get(ctx, params.PurgeID)
|
||||
if err != nil {
|
||||
return p.SendError(ctx, err)
|
||||
}
|
||||
|
||||
extraAttrsString, err := json.Marshal(exec.ExtraAttrs)
|
||||
if err != nil {
|
||||
return p.SendError(ctx, err)
|
||||
}
|
||||
|
||||
res := &model.ExecHistory{
|
||||
ID: exec.ID,
|
||||
Name: pg.VendorType,
|
||||
Kind: exec.Trigger,
|
||||
Parameters: string(extraAttrsString),
|
||||
Status: exec.Status,
|
||||
Schedule: &model.ScheduleParam{
|
||||
Type: exec.Trigger,
|
||||
},
|
||||
CreationTime: exec.StartTime,
|
||||
UpdateTime: exec.EndTime,
|
||||
}
|
||||
|
||||
return operation.NewGetPurgeJobOK().WithPayload(res.ToSwagger())
|
||||
}
|
||||
|
||||
func (p *purgeAPI) GetPurgeJobLog(ctx context.Context, params purge.GetPurgeJobLogParams) middleware.Responder {
|
||||
if err := p.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourcePurgeAuditLog); err != nil {
|
||||
return p.SendError(ctx, err)
|
||||
}
|
||||
tasks, err := p.taskCtl.List(ctx, q.New(q.KeyWords{
|
||||
"ExecutionID": params.PurgeID,
|
||||
"VendorType": pg.VendorType,
|
||||
}))
|
||||
if err != nil {
|
||||
return p.SendError(ctx, err)
|
||||
}
|
||||
if len(tasks) == 0 {
|
||||
return p.SendError(ctx,
|
||||
errors.New(nil).WithCode(errors.NotFoundCode).
|
||||
WithMessage("purge job with execution ID: %d taskLog is not found", params.PurgeID))
|
||||
}
|
||||
taskLog, err := p.taskCtl.GetLog(ctx, tasks[0].ID)
|
||||
if err != nil {
|
||||
return p.SendError(ctx, err)
|
||||
}
|
||||
return operation.NewGetPurgeJobLogOK().WithPayload(string(taskLog))
|
||||
}
|
||||
|
||||
func (p *purgeAPI) GetPurgeSchedule(ctx context.Context, params purge.GetPurgeScheduleParams) middleware.Responder {
|
||||
if err := p.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourcePurgeAuditLog); err != nil {
|
||||
return p.SendError(ctx, err)
|
||||
}
|
||||
sch, err := p.schedulerCtl.Get(ctx, pg.VendorType)
|
||||
if errors.IsNotFoundErr(err) {
|
||||
return operation.NewGetPurgeScheduleOK()
|
||||
}
|
||||
if err != nil {
|
||||
return p.SendError(ctx, err)
|
||||
}
|
||||
execHistory := &models.ExecHistory{
|
||||
ID: sch.ID,
|
||||
JobName: "",
|
||||
JobKind: sch.CRON,
|
||||
JobParameters: pg.String(sch.ExtraAttrs),
|
||||
Deleted: false,
|
||||
JobStatus: sch.Status,
|
||||
Schedule: &models.ScheduleObj{
|
||||
Cron: sch.CRON,
|
||||
Type: sch.CRONType,
|
||||
},
|
||||
CreationTime: strfmt.DateTime(sch.CreationTime),
|
||||
UpdateTime: strfmt.DateTime(sch.UpdateTime),
|
||||
}
|
||||
return operation.NewGetPurgeScheduleOK().WithPayload(execHistory)
|
||||
}
|
||||
|
||||
func (p *purgeAPI) UpdatePurgeSchedule(ctx context.Context, params purge.UpdatePurgeScheduleParams) middleware.Responder {
|
||||
if err := p.RequireSystemAccess(ctx, rbac.ActionUpdate, rbac.ResourcePurgeAuditLog); err != nil {
|
||||
return p.SendError(ctx, err)
|
||||
}
|
||||
if err := verifyUpdateRequest(params); err != nil {
|
||||
return p.SendError(ctx, err)
|
||||
}
|
||||
_, err := p.kick(ctx, pg.VendorType, params.Schedule.Schedule.Type, params.Schedule.Schedule.Cron, params.Schedule.Parameters)
|
||||
if err != nil {
|
||||
return p.SendError(ctx, err)
|
||||
}
|
||||
return operation.NewUpdatePurgeScheduleOK()
|
||||
}
|
||||
|
||||
func verifyUpdateRequest(params operation.UpdatePurgeScheduleParams) error {
|
||||
if params.Schedule == nil || params.Schedule.Schedule == nil {
|
||||
return errors.BadRequestError(fmt.Errorf("schedule cann't be empty"))
|
||||
}
|
||||
if len(params.Schedule.Parameters) == 0 {
|
||||
return errors.BadRequestError(fmt.Errorf("schedule parameter cann't be empty"))
|
||||
}
|
||||
if _, exist := params.Schedule.Parameters[common.PurgeAuditRetentionHour]; !exist {
|
||||
return errors.BadRequestError(fmt.Errorf("audit_retention_hour should provide"))
|
||||
}
|
||||
if _, exist := params.Schedule.Parameters[common.PurgeAuditIncludeOperations]; !exist {
|
||||
return errors.BadRequestError(fmt.Errorf("include_operations should provide"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *purgeAPI) createSchedule(ctx context.Context, vendorType string, cronType string, cron string, policy pg.JobPolicy, extraParam map[string]interface{}) error {
|
||||
if cron == "" {
|
||||
return errors.New(nil).WithCode(errors.BadRequestCode).
|
||||
WithMessage("empty cron string for schedule")
|
||||
}
|
||||
_, err := p.schedulerCtl.Create(ctx, vendorType, cronType, cron, pg.SchedulerCallback, policy, extraParam)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
69
src/server/v2.0/handler/purge_test.go
Normal file
69
src/server/v2.0/handler/purge_test.go
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/restapi/operations/purge"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_verifyUpdateRequest(t *testing.T) {
|
||||
type args struct {
|
||||
params purge.UpdatePurgeScheduleParams
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{"normal", args{purge.UpdatePurgeScheduleParams{Schedule: &models.Schedule{Schedule: &models.ScheduleObj{}, Parameters: map[string]interface{}{common.PurgeAuditRetentionHour: "168", common.PurgeAuditIncludeOperations: "pull"}}}}, false},
|
||||
{"missing_schedule", args{purge.UpdatePurgeScheduleParams{Schedule: &models.Schedule{Parameters: map[string]interface{}{common.PurgeAuditRetentionHour: "168", common.PurgeAuditIncludeOperations: "pull"}}}}, true},
|
||||
{"missing_retention_hour", args{purge.UpdatePurgeScheduleParams{Schedule: &models.Schedule{Schedule: &models.ScheduleObj{}, Parameters: map[string]interface{}{common.PurgeAuditIncludeOperations: "pull"}}}}, true},
|
||||
{"missing_operations", args{purge.UpdatePurgeScheduleParams{Schedule: &models.Schedule{Schedule: &models.ScheduleObj{}, Parameters: map[string]interface{}{common.PurgeAuditRetentionHour: "168"}}}}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := verifyUpdateRequest(tt.args.params)
|
||||
if tt.wantErr != (err != nil) {
|
||||
t.Error("test failed")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
func Test_verifyCreateRequest(t *testing.T) {
|
||||
type args struct {
|
||||
params purge.CreatePurgeScheduleParams
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{"normal", args{purge.CreatePurgeScheduleParams{Schedule: &models.Schedule{Schedule: &models.ScheduleObj{}, Parameters: map[string]interface{}{common.PurgeAuditRetentionHour: "168", common.PurgeAuditIncludeOperations: "pull"}}}}, false},
|
||||
{"missing_schedule", args{purge.CreatePurgeScheduleParams{Schedule: &models.Schedule{Parameters: map[string]interface{}{common.PurgeAuditRetentionHour: "168", common.PurgeAuditIncludeOperations: "pull"}}}}, true},
|
||||
{"missing_retention_hour", args{purge.CreatePurgeScheduleParams{Schedule: &models.Schedule{Schedule: &models.ScheduleObj{}, Parameters: map[string]interface{}{common.PurgeAuditIncludeOperations: "pull"}}}}, true},
|
||||
{"missing_operations", args{purge.CreatePurgeScheduleParams{Schedule: &models.Schedule{Schedule: &models.ScheduleObj{}, Parameters: map[string]interface{}{common.PurgeAuditRetentionHour: "168"}}}}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := verifyCreateRequest(tt.args.params)
|
||||
if tt.wantErr != (err != nil) {
|
||||
t.Error("test failed")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -29,3 +29,4 @@ package controller
|
||||
//go:generate mockery --case snake --dir ../../controller/user --name Controller --output ./user --outpkg user
|
||||
//go:generate mockery --case snake --dir ../../controller/repository --name Controller --output ./repository --outpkg repository
|
||||
//go:generate mockery --case snake --dir ../../controller/purge --name Controller --output ./purge --outpkg purge
|
||||
//go:generate mockery --case snake --dir ../../controller/jobservice --name SchedulerController --output ./jobservice --outpkg jobservice
|
||||
|
84
src/testing/controller/jobservice/execution_controller.go
Normal file
84
src/testing/controller/jobservice/execution_controller.go
Normal file
@ -0,0 +1,84 @@
|
||||
// Code generated by mockery v2.1.0. DO NOT EDIT.
|
||||
|
||||
package jobservice
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
jobservice "github.com/goharbor/harbor/src/controller/jobservice"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
q "github.com/goharbor/harbor/src/lib/q"
|
||||
)
|
||||
|
||||
// ExecutionController is an autogenerated mock type for the ExecutionController type
|
||||
type ExecutionController struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Count provides a mock function with given fields: ctx, vendorType, query
|
||||
func (_m *ExecutionController) Count(ctx context.Context, vendorType string, query *q.Query) (int64, error) {
|
||||
ret := _m.Called(ctx, vendorType, query)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, *q.Query) int64); ok {
|
||||
r0 = rf(ctx, vendorType, query)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, *q.Query) error); ok {
|
||||
r1 = rf(ctx, vendorType, query)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: ctx, vendorType, executionID
|
||||
func (_m *ExecutionController) Get(ctx context.Context, vendorType string, executionID int64) (*jobservice.Execution, error) {
|
||||
ret := _m.Called(ctx, vendorType, executionID)
|
||||
|
||||
var r0 *jobservice.Execution
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, int64) *jobservice.Execution); ok {
|
||||
r0 = rf(ctx, vendorType, executionID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*jobservice.Execution)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, int64) error); ok {
|
||||
r1 = rf(ctx, vendorType, executionID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// List provides a mock function with given fields: ctx, vendorType, query
|
||||
func (_m *ExecutionController) List(ctx context.Context, vendorType string, query *q.Query) ([]*jobservice.Execution, error) {
|
||||
ret := _m.Called(ctx, vendorType, query)
|
||||
|
||||
var r0 []*jobservice.Execution
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, *q.Query) []*jobservice.Execution); ok {
|
||||
r0 = rf(ctx, vendorType, query)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*jobservice.Execution)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, *q.Query) error); ok {
|
||||
r1 = rf(ctx, vendorType, query)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
74
src/testing/controller/jobservice/scheduler_controller.go
Normal file
74
src/testing/controller/jobservice/scheduler_controller.go
Normal file
@ -0,0 +1,74 @@
|
||||
// Code generated by mockery v2.1.0. DO NOT EDIT.
|
||||
|
||||
package jobservice
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
scheduler "github.com/goharbor/harbor/src/pkg/scheduler"
|
||||
)
|
||||
|
||||
// SchedulerController is an autogenerated mock type for the SchedulerController type
|
||||
type SchedulerController struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Create provides a mock function with given fields: ctx, vendorType, cronType, cron, callbackFuncName, policy, extrasParam
|
||||
func (_m *SchedulerController) Create(ctx context.Context, vendorType string, cronType string, cron string, callbackFuncName string, policy interface{}, extrasParam map[string]interface{}) (int64, error) {
|
||||
ret := _m.Called(ctx, vendorType, cronType, cron, callbackFuncName, policy, extrasParam)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, interface{}, map[string]interface{}) int64); ok {
|
||||
r0 = rf(ctx, vendorType, cronType, cron, callbackFuncName, policy, extrasParam)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, string, interface{}, map[string]interface{}) error); ok {
|
||||
r1 = rf(ctx, vendorType, cronType, cron, callbackFuncName, policy, extrasParam)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Delete provides a mock function with given fields: ctx, vendorType
|
||||
func (_m *SchedulerController) Delete(ctx context.Context, vendorType string) error {
|
||||
ret := _m.Called(ctx, vendorType)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
|
||||
r0 = rf(ctx, vendorType)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: ctx, vendorType
|
||||
func (_m *SchedulerController) Get(ctx context.Context, vendorType string) (*scheduler.Schedule, error) {
|
||||
ret := _m.Called(ctx, vendorType)
|
||||
|
||||
var r0 *scheduler.Schedule
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) *scheduler.Schedule); ok {
|
||||
r0 = rf(ctx, vendorType)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*scheduler.Schedule)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
||||
r1 = rf(ctx, vendorType)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
86
src/testing/controller/jobservice/task_controller.go
Normal file
86
src/testing/controller/jobservice/task_controller.go
Normal file
@ -0,0 +1,86 @@
|
||||
// Code generated by mockery v2.1.0. DO NOT EDIT.
|
||||
|
||||
package jobservice
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
jobservice "github.com/goharbor/harbor/src/controller/jobservice"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
q "github.com/goharbor/harbor/src/lib/q"
|
||||
)
|
||||
|
||||
// TaskController is an autogenerated mock type for the TaskController type
|
||||
type TaskController struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: ctx, vendorType, id
|
||||
func (_m *TaskController) Get(ctx context.Context, vendorType string, id int64) (*jobservice.Task, error) {
|
||||
ret := _m.Called(ctx, vendorType, id)
|
||||
|
||||
var r0 *jobservice.Task
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, int64) *jobservice.Task); ok {
|
||||
r0 = rf(ctx, vendorType, id)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*jobservice.Task)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, int64) error); ok {
|
||||
r1 = rf(ctx, vendorType, id)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetLog provides a mock function with given fields: ctx, vendorType, id
|
||||
func (_m *TaskController) GetLog(ctx context.Context, vendorType string, id int64) ([]byte, error) {
|
||||
ret := _m.Called(ctx, vendorType, id)
|
||||
|
||||
var r0 []byte
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, int64) []byte); ok {
|
||||
r0 = rf(ctx, vendorType, id)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]byte)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, int64) error); ok {
|
||||
r1 = rf(ctx, vendorType, id)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// List provides a mock function with given fields: ctx, vendorType, query
|
||||
func (_m *TaskController) List(ctx context.Context, vendorType string, query *q.Query) ([]*jobservice.Task, error) {
|
||||
ret := _m.Called(ctx, vendorType, query)
|
||||
|
||||
var r0 []*jobservice.Task
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, *q.Query) []*jobservice.Task); ok {
|
||||
r0 = rf(ctx, vendorType, query)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*jobservice.Task)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, *q.Query) error); ok {
|
||||
r1 = rf(ctx, vendorType, query)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
Loading…
Reference in New Issue
Block a user