Merge pull request #16865 from stonezdj/22may17_purge_audit_log_rest_api

Add REST API for purge audit log
This commit is contained in:
stonezdj(Daojun Zhang) 2022-05-25 10:08:47 +08:00 committed by GitHub
commit 4637af8866
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 945 additions and 6 deletions

View File

@ -4174,6 +4174,170 @@ paths:
$ref: '#/responses/403' $ref: '#/responses/403'
'500': '500':
$ref: '#/responses/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: /system/CVEAllowlist:
get: get:
summary: Get the system level allowlist of CVE. summary: Get the system level allowlist of CVE.
@ -5523,6 +5687,13 @@ parameters:
required: true required: true
type: integer type: integer
format: int64 format: int64
purgeId:
name: purge_id
in: path
description: The ID of the purge log
required: true
type: integer
format: int64
labelId: labelId:
name: label_id name: label_id
in: path in: path
@ -7224,6 +7395,37 @@ definitions:
type: string type: string
format: date-time format: date-time
description: the update time of gc job. 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: Schedule:
type: object type: object
properties: properties:

View 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
}

View File

@ -308,8 +308,8 @@ func (bs *Bootstrap) loadAndRunRedisWorkerPool(
job.SampleJob: (*sample.Job)(nil), job.SampleJob: (*sample.Job)(nil),
// Functional jobs // Functional jobs
job.ImageScanJob: (*scan.Job)(nil), job.ImageScanJob: (*scan.Job)(nil),
job.GarbageCollection: (*gc.GarbageCollector)(nil),
job.PurgeAudit: (*purge.Job)(nil), job.PurgeAudit: (*purge.Job)(nil),
job.GarbageCollection: (*gc.GarbageCollector)(nil),
job.Replication: (*replication.Replication)(nil), job.Replication: (*replication.Replication)(nil),
job.Retention: (*retention.Job)(nil), job.Retention: (*retention.Job)(nil),
scheduler.JobNameScheduler: (*scheduler.PeriodicJob)(nil), scheduler.JobNameScheduler: (*scheduler.PeriodicJob)(nil),

View File

@ -14,7 +14,11 @@
package lib package lib
import "strings" import (
"golang.org/x/text/cases"
"golang.org/x/text/language"
"strings"
)
// TrimsLineBreaks trims line breaks in string. // TrimsLineBreaks trims line breaks in string.
func TrimLineBreaks(s string) string { func TrimLineBreaks(s string) string {
@ -22,3 +26,9 @@ func TrimLineBreaks(s string) string {
escaped = strings.ReplaceAll(escaped, "\r", "") escaped = strings.ReplaceAll(escaped, "\r", "")
return escaped 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))
}

View File

@ -32,3 +32,23 @@ def
actual := TrimLineBreaks(s) actual := TrimLineBreaks(s)
assert.Equal(expect, actual, "should trim line breaks") 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)
})
}
}

View File

@ -114,7 +114,13 @@ func (s *scheduler) Schedule(ctx context.Context, vendorType string, vendorID in
return 0, err return 0, err
} }
sched.CallbackFuncParam = string(paramsData) sched.CallbackFuncParam = string(paramsData)
params := map[string]interface{}{}
if len(paramsData) > 0 {
err = json.Unmarshal(paramsData, &params)
if err != nil {
log.Debugf("current paramsData is not a json string")
}
}
extrasData, err := json.Marshal(extraAttrs) extrasData, err := json.Marshal(extraAttrs)
if err != nil { if err != nil {
return 0, err return 0, err
@ -129,7 +135,7 @@ func (s *scheduler) Schedule(ctx context.Context, vendorType string, vendorID in
return 0, err 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 { if err != nil {
return 0, err return 0, err
} }

View File

@ -66,7 +66,7 @@ func (s *schedulerTestSuite) TestSchedule() {
// failed to submit to jobservice // failed to submit to jobservice
s.dao.On("Create", mock.Anything, mock.Anything).Return(int64(1), nil) 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("Create", mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil)
s.taskMgr.On("Get", mock.Anything, mock.Anything).Return(&task.Task{ s.taskMgr.On("Get", mock.Anything, mock.Anything).Return(&task.Task{
ID: 1, ID: 1,
@ -84,7 +84,7 @@ func (s *schedulerTestSuite) TestSchedule() {
s.SetupTest() s.SetupTest()
// pass // 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.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("Create", mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil)
s.taskMgr.On("Get", mock.Anything, mock.Anything).Return(&task.Task{ s.taskMgr.On("Get", mock.Anything, mock.Anything).Return(&task.Task{

View File

@ -64,6 +64,7 @@ func New() http.Handler {
HealthAPI: newHealthAPI(), HealthAPI: newHealthAPI(),
StatisticAPI: newStatisticAPI(), StatisticAPI: newStatisticAPI(),
ProjectMetadataAPI: newProjectMetadaAPI(), ProjectMetadataAPI: newProjectMetadaAPI(),
PurgeAPI: newPurgeAPI(),
}) })
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

View 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),
}
}

View 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
}

View 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")
}
})
}
}

View File

@ -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/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/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/purge --name Controller --output ./purge --outpkg purge
//go:generate mockery --case snake --dir ../../controller/jobservice --name SchedulerController --output ./jobservice --outpkg jobservice

View 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
}

View 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
}

View 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
}