map retention with policy (#8313)

Signed-off-by: Ziming Zhang <zziming@vmware.com>

Implement the API and controller of tag retention
 - API handler
 - retention controller
 - dao
This commit is contained in:
Ziming 2019-07-24 17:22:26 +08:00 committed by Steven Zou
parent 9437b489ac
commit 43c2af9857
20 changed files with 1086 additions and 342 deletions

View File

@ -22,19 +22,18 @@ create table retention_policy
create table retention_execution
(
id integer PRIMARY KEY NOT NULL,
id serial PRIMARY KEY NOT NULL,
policy_id integer,
status varchar(20),
status_text text,
dry boolean,
dry_run boolean,
trigger varchar(20),
total integer,
succeed integer,
failed integer,
in_progress integer,
stopped integer,
start_time time,
end_time time
start_time timestamp,
end_time timestamp
);
create table retention_task

View File

@ -131,21 +131,6 @@ func (b *BaseAPI) GetIDFromURL() (int64, error) {
return id, nil
}
// GetSpecialIDFromURL checks the ID with special name in request URL
func (b *BaseAPI) GetSpecialIDFromURL(name string) (int64, error) {
idStr := b.Ctx.Input.Param(":" + name)
if len(idStr) == 0 {
return 0, fmt.Errorf("invalid %s in URL", name)
}
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil || id <= 0 {
return 0, errors.New("invalid ID in URL")
}
return id, nil
}
// SetPaginationHeader set"Link" and "X-Total-Count" header for pagination request
func (b *BaseAPI) SetPaginationHeader(total, page, pageSize int64) {
b.Ctx.ResponseWriter.Header().Set("X-Total-Count", strconv.FormatInt(total, 10))

View File

@ -27,6 +27,8 @@ const (
ActionUpdate = Action("update")
ActionDelete = Action("delete")
ActionList = Action("list")
ActionOperate = Action("operate")
)
// const resource variables
@ -46,6 +48,7 @@ const (
ResourceReplicationExecution = Resource("replication-execution")
ResourceReplicationTask = Resource("replication-task")
ResourceRepository = Resource("repository")
ResourceTagRetention = Resource("tag-retention")
ResourceRepositoryLabel = Resource("repository-label")
ResourceRepositoryTag = Resource("repository-tag")
ResourceRepositoryTagLabel = Resource("repository-tag-label")

View File

@ -88,6 +88,13 @@ var (
{Resource: rbac.ResourceReplicationTask, Action: rbac.ActionUpdate},
{Resource: rbac.ResourceReplicationTask, Action: rbac.ActionDelete},
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionCreate},
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionRead},
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionUpdate},
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionDelete},
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionList},
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionOperate},
{Resource: rbac.ResourceLabel, Action: rbac.ActionCreate},
{Resource: rbac.ResourceLabel, Action: rbac.ActionRead},
{Resource: rbac.ResourceLabel, Action: rbac.ActionUpdate},

View File

@ -61,6 +61,13 @@ var (
{Resource: rbac.ResourceRepository, Action: rbac.ActionPull},
{Resource: rbac.ResourceRepository, Action: rbac.ActionPush},
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionCreate},
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionRead},
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionUpdate},
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionDelete},
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionList},
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionOperate},
{Resource: rbac.ResourceRepositoryLabel, Action: rbac.ActionCreate},
{Resource: rbac.ResourceRepositoryLabel, Action: rbac.ActionDelete},
{Resource: rbac.ResourceRepositoryLabel, Action: rbac.ActionList},
@ -133,6 +140,13 @@ var (
{Resource: rbac.ResourceRepository, Action: rbac.ActionPush},
{Resource: rbac.ResourceRepository, Action: rbac.ActionPull},
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionCreate},
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionRead},
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionUpdate},
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionDelete},
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionList},
{Resource: rbac.ResourceTagRetention, Action: rbac.ActionOperate},
{Resource: rbac.ResourceRepositoryLabel, Action: rbac.ActionCreate},
{Resource: rbac.ResourceRepositoryLabel, Action: rbac.ActionDelete},
{Resource: rbac.ResourceRepositoryLabel, Action: rbac.ActionList},

View File

@ -16,6 +16,8 @@ package api
import (
"errors"
"github.com/goharbor/harbor/src/pkg/retention"
"github.com/goharbor/harbor/src/pkg/scheduler"
"net/http"
"github.com/ghodss/yaml"
@ -39,8 +41,12 @@ const (
// the managers/controllers used globally
var (
projectMgr project.Manager
repositoryMgr repository.Manager
projectMgr project.Manager
repositoryMgr repository.Manager
retentionScheduler scheduler.Scheduler
retentionMgr retention.Manager
retentionLauncher retention.Launcher
retentionController retention.APIController
)
// BaseController ...
@ -108,7 +114,24 @@ func Init() error {
// init repository manager
initRepositoryManager()
return nil
initRetentionScheduler()
retentionMgr = retention.NewManager()
retentionLauncher = retention.NewLauncher(projectMgr, repositoryMgr, retentionMgr)
retentionController = retention.NewAPIController(projectMgr, repositoryMgr, retentionScheduler, retentionLauncher)
callbackFun := func(p interface{}) error {
r, ok := p.(retention.TriggerParam)
if ok {
return retentionController.TriggerRetentionExec(r.PolicyID, r.Trigger, false)
}
return errors.New("bad retention callback param")
}
err := scheduler.Register(retention.RetentionSchedulerCallback, callbackFun)
return err
}
func initChartController() error {
@ -133,3 +156,7 @@ func initProjectManager() {
func initRepositoryManager() {
repositoryMgr = repository.New(projectMgr, chartController)
}
func initRetentionScheduler() {
retentionScheduler = scheduler.GlobalScheduler
}

392
src/core/api/retention.go Normal file
View File

@ -0,0 +1,392 @@
package api
import (
"errors"
"fmt"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/core/filter"
"github.com/goharbor/harbor/src/core/promgr"
"github.com/goharbor/harbor/src/pkg/retention"
"github.com/goharbor/harbor/src/pkg/retention/policy"
"github.com/goharbor/harbor/src/pkg/retention/q"
"net/http"
"strconv"
)
// RetentionAPI ...
type RetentionAPI struct {
BaseController
pm promgr.ProjectManager
}
// Prepare validates the user
func (r *RetentionAPI) Prepare() {
r.BaseController.Prepare()
if !r.SecurityCtx.IsAuthenticated() {
r.SendUnAuthorizedError(errors.New("UnAuthorized"))
return
}
pm, e := filter.GetProjectManager(r.Ctx.Request)
if e != nil {
r.SendInternalServerError(e)
return
}
r.pm = pm
}
// GetMetadatas Get Metadatas
func (r *RetentionAPI) GetMetadatas() {
data := `
{
"templates": [
{
"rule_template": "lastXDays",
"display_text": "the images from the last # days",
"action": "retain",
"params": [
{
"type": "int",
"unit": "DAYS",
"required": true
}
]
},
{
"rule_template": "latestActiveK",
"display_text": "the most recent active # images",
"action": "retain",
"params": [
{
"type": "int",
"unit": "COUNT",
"required": true
}
]
},
{
"rule_template": "latestK",
"display_text": "the most recently pushed # images",
"action": "retain",
"params": [
{
"type": "int",
"unit": "COUNT",
"required": true
}
]
},
{
"rule_template": "latestPulledK",
"display_text": "the most recently pulled # images",
"action": "retain",
"params": [
{
"type": "int",
"unit": "COUNT",
"required": true
}
]
},
{
"rule_template": "always",
"display_text": "always",
"action": "retain",
"params": [
{
"type": "int",
"unit": "COUNT",
"required": true
}
]
}
],
"scope_selectors": [
{
"display_text": "Repositories",
"kind": "doublestar",
"decorations": [
"repoMatches",
"repoExcludes"
]
}
],
"tag_selectors": [
{
"display_text": "Labels",
"kind": "label",
"decorations": [
"withLabels",
"withoutLabels"
]
},
{
"display_text": "Tags",
"kind": "doublestar",
"decorations": [
"matches",
"excludes"
]
}
]
}
`
r.WriteJSONData(data)
}
// GetRetention Get Retention
func (r *RetentionAPI) GetRetention() {
id, err := r.GetIDFromURL()
if err != nil {
r.SendBadRequestError(err)
return
}
p, err := retentionController.GetRetention(id)
if err != nil {
r.SendBadRequestError(err)
return
}
if !r.requireAccess(p, rbac.ActionRead) {
return
}
r.WriteJSONData(p)
}
// CreateRetention Create Retention
func (r *RetentionAPI) CreateRetention() {
p := &policy.Metadata{}
isValid, err := r.DecodeJSONReqAndValidate(p)
if !isValid {
r.SendBadRequestError(err)
return
}
if !r.requireAccess(p, rbac.ActionCreate) {
return
}
switch p.Scope.Level {
case policy.ScopeLevelProject:
if p.Scope.Reference <= 0 {
r.SendBadRequestError(fmt.Errorf("Invalid Project id %d", p.Scope.Reference))
return
}
proj, err := r.pm.Get(p.Scope.Reference)
if err != nil {
r.SendBadRequestError(err)
}
if proj == nil {
r.SendBadRequestError(fmt.Errorf("Invalid Project id %d", p.Scope.Reference))
}
default:
r.SendBadRequestError(fmt.Errorf("scope %s is not support", p.Scope.Level))
return
}
if err = retentionController.CreateRetention(p); err != nil {
r.SendInternalServerError(err)
return
}
if err := r.pm.GetMetadataManager().Add(p.Scope.Reference,
map[string]string{"retention_id": strconv.FormatInt(p.Scope.Reference, 10)}); err != nil {
}
}
// UpdateRetention Update Retention
func (r *RetentionAPI) UpdateRetention() {
id, err := r.GetIDFromURL()
if err != nil {
r.SendBadRequestError(err)
return
}
p := &policy.Metadata{}
isValid, err := r.DecodeJSONReqAndValidate(p)
if !isValid {
r.SendBadRequestError(err)
return
}
p.ID = id
if !r.requireAccess(p, rbac.ActionUpdate) {
return
}
if err = retentionController.UpdateRetention(p); err != nil {
r.SendInternalServerError(err)
return
}
}
// TriggerRetentionExec Trigger Retention Execution
func (r *RetentionAPI) TriggerRetentionExec() {
id, err := r.GetIDFromURL()
if err != nil {
r.SendBadRequestError(err)
return
}
d := &struct {
DryRun bool `json:"dry_run"`
}{
DryRun: false,
}
isValid, err := r.DecodeJSONReqAndValidate(d)
if !isValid {
r.SendBadRequestError(err)
return
}
p, err := retentionController.GetRetention(id)
if err != nil {
r.SendBadRequestError(err)
return
}
if !r.requireAccess(p, rbac.ActionUpdate) {
return
}
if err = retentionController.TriggerRetentionExec(id, retention.ExecutionTriggerManual, d.DryRun); err != nil {
r.SendInternalServerError(err)
return
}
}
// OperateRetentionExec Operate Retention Execution
func (r *RetentionAPI) OperateRetentionExec() {
id, err := r.GetIDFromURL()
if err != nil {
r.SendBadRequestError(err)
return
}
eid, err := r.GetInt64FromPath(":eid")
if err != nil {
r.SendBadRequestError(err)
return
}
a := &struct {
Action string `json:"action" valid:"Required"`
}{}
isValid, err := r.DecodeJSONReqAndValidate(a)
if !isValid {
r.SendBadRequestError(err)
return
}
p, err := retentionController.GetRetention(id)
if err != nil {
r.SendBadRequestError(err)
return
}
if !r.requireAccess(p, rbac.ActionUpdate) {
return
}
if err = retentionController.OperateRetentionExec(eid, a.Action); err != nil {
r.SendInternalServerError(err)
return
}
}
// ListRetentionExecs List Retention Execution
func (r *RetentionAPI) ListRetentionExecs() {
id, err := r.GetIDFromURL()
if err != nil {
r.SendBadRequestError(err)
return
}
page, size, err := r.GetPaginationParams()
if err != nil {
r.SendInternalServerError(err)
return
}
query := &q.Query{
PageNumber: page,
PageSize: size,
}
p, err := retentionController.GetRetention(id)
if err != nil {
r.SendBadRequestError(err)
return
}
if !r.requireAccess(p, rbac.ActionList) {
return
}
execs, err := retentionController.ListRetentionExecs(id, query)
if err != nil {
r.SendInternalServerError(err)
return
}
r.WriteJSONData(execs)
}
// ListRetentionExecTasks List Retention Execution Tasks
func (r *RetentionAPI) ListRetentionExecTasks() {
id, err := r.GetIDFromURL()
if err != nil {
r.SendBadRequestError(err)
return
}
eid, err := r.GetInt64FromPath(":eid")
if err != nil {
r.SendBadRequestError(err)
return
}
page, size, err := r.GetPaginationParams()
if err != nil {
r.SendInternalServerError(err)
return
}
query := &q.Query{
PageNumber: page,
PageSize: size,
}
p, err := retentionController.GetRetention(id)
if err != nil {
r.SendBadRequestError(err)
return
}
if !r.requireAccess(p, rbac.ActionList) {
return
}
his, err := retentionController.ListRetentionExecTasks(eid, query)
if err != nil {
r.SendInternalServerError(err)
return
}
r.WriteJSONData(his)
}
// GetRetentionExecTaskLog Get Retention Execution Task log
func (r *RetentionAPI) GetRetentionExecTaskLog() {
tid, err := r.GetInt64FromPath(":tid")
if err != nil {
r.SendBadRequestError(err)
return
}
log, err := retentionController.GetRetentionExecTaskLog(tid)
if err != nil {
r.SendInternalServerError(err)
return
}
w := r.Ctx.ResponseWriter
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
w.Write(log)
}
func (r *RetentionAPI) requireAccess(p *policy.Metadata, action rbac.Action, subresources ...rbac.Resource) bool {
var hasPermission bool
switch p.Scope.Level {
case "project":
if len(subresources) == 0 {
subresources = append(subresources, rbac.ResourceTagRetention)
}
resource := rbac.NewProjectNamespace(p.Scope.Reference).Resource(subresources...)
hasPermission = r.SecurityCtx.Can(action, resource)
default:
hasPermission = r.SecurityCtx.IsSysAdmin()
}
if !hasPermission {
if !r.SecurityCtx.IsAuthenticated() {
r.SendUnAuthorizedError(errors.New("UnAuthorized"))
} else {
r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
}
return false
}
return true
}

View File

@ -25,7 +25,6 @@ import (
"github.com/goharbor/harbor/src/core/service/notifications/registry"
"github.com/goharbor/harbor/src/core/service/notifications/scheduler"
"github.com/goharbor/harbor/src/core/service/token"
retentionCtl "github.com/goharbor/harbor/src/pkg/retention/controllers"
"github.com/astaxie/beego"
)
@ -142,15 +141,15 @@ func initRouters() {
beego.Router("/api/registries/:id/info", &api.RegistryAPI{}, "get:GetInfo")
beego.Router("/api/registries/:id/namespace", &api.RegistryAPI{}, "get:GetNamespace")
beego.Router("/api/retentions/:id", &retentionCtl.RetentionAPI{}, "get:GetRetention")
beego.Router("/api/retentions", &retentionCtl.RetentionAPI{}, "post:CreateRetention")
beego.Router("/api/retentions/:id", &retentionCtl.RetentionAPI{}, "put:UpdateRetention")
beego.Router("/api/retentions/:id", &retentionCtl.RetentionAPI{}, "delete:DeleteRetention")
beego.Router("/api/retentions/:id/executions", &retentionCtl.RetentionAPI{}, "post:TriggerRetentionExec")
beego.Router("/api/retentions/:id/executions/:eid", &retentionCtl.RetentionAPI{}, "put:OperateRetentionExec")
beego.Router("/api/retentions/:id/executions/:eid", &retentionCtl.RetentionAPI{}, "get:GetRetentionExec")
beego.Router("/api/retentions/:id/executions", &retentionCtl.RetentionAPI{}, "get:ListRetentionExec")
beego.Router("/api/retentions/:id/executions/:eid/histories", &retentionCtl.RetentionAPI{}, "get:ListRetentionExecHistory")
beego.Router("/api/retentions/metadatas", &api.RetentionAPI{}, "get:GetMetadatas")
beego.Router("/api/retentions/:id", &api.RetentionAPI{}, "get:GetRetention")
beego.Router("/api/retentions", &api.RetentionAPI{}, "post:CreateRetention")
beego.Router("/api/retentions/:id", &api.RetentionAPI{}, "put:UpdateRetention")
beego.Router("/api/retentions/:id/executions", &api.RetentionAPI{}, "post:TriggerRetentionExec")
beego.Router("/api/retentions/:id/executions/:eid", &api.RetentionAPI{}, "patch:OperateRetentionExec")
beego.Router("/api/retentions/:id/executions", &api.RetentionAPI{}, "get:ListRetentionExecs")
beego.Router("/api/retentions/:id/executions/:eid/tasks", &api.RetentionAPI{}, "get:ListRetentionExecTasks")
beego.Router("/api/retentions/:id/executions/:eid/tasks/:tid", &api.RetentionAPI{}, "get:GetRetentionExecTaskLog")
beego.Router("/v2/*", &controllers.RegistryProxy{}, "*:Handle")

View File

@ -14,17 +14,259 @@
package retention
import "github.com/goharbor/harbor/src/jobservice/job"
import (
"fmt"
"github.com/goharbor/harbor/src/jobservice/job"
"github.com/goharbor/harbor/src/pkg/project"
"github.com/goharbor/harbor/src/pkg/repository"
"github.com/goharbor/harbor/src/pkg/retention/policy"
"github.com/goharbor/harbor/src/pkg/retention/q"
"github.com/goharbor/harbor/src/pkg/scheduler"
"time"
)
// APIController to handle the requests related with retention
type APIController interface {
// Handle the related hooks from the job service and launch the corresponding actions if needed
//
// Arguments:
// policyID string : uuid of the retention policy
// PolicyID string : uuid of the retention policy
// event *job.StatusChange : event object sent by job service
//
// Returns:
// common error object if any errors occurred
HandleHook(policyID string, event *job.StatusChange) error
GetRetention(id int64) (*policy.Metadata, error)
CreateRetention(p *policy.Metadata) error
UpdateRetention(p *policy.Metadata) error
DeleteRetention(id int64) error
TriggerRetentionExec(policyID int64, trigger string, dryRun bool) error
OperateRetentionExec(eid int64, action string) error
ListRetentionExecs(policyID int64, query *q.Query) ([]*Execution, error)
ListRetentionExecTasks(executionID int64, query *q.Query) ([]*Task, error)
GetRetentionExecTaskLog(taskID int64) ([]byte, error)
}
// DefaultAPIController ...
type DefaultAPIController struct {
manager Manager
launcher Launcher
projectManager project.Manager
repositoryMgr repository.Manager
scheduler scheduler.Scheduler
}
const (
// RetentionSchedulerCallback ...
RetentionSchedulerCallback = "RetentionSchedulerCallback"
)
// TriggerParam ...
type TriggerParam struct {
PolicyID int64
Trigger string
}
// GetRetention Get Retention
func (r *DefaultAPIController) GetRetention(id int64) (*policy.Metadata, error) {
return r.manager.GetPolicy(id)
}
// CreateRetention Create Retention
func (r *DefaultAPIController) CreateRetention(p *policy.Metadata) error {
if p.Trigger.Kind == policy.TriggerKindSchedule {
if p.Trigger.Settings != nil {
cron, ok := p.Trigger.Settings[policy.TriggerSettingsCron]
if ok {
jobid, err := r.scheduler.Schedule(cron.(string), RetentionSchedulerCallback, TriggerParam{
PolicyID: p.ID,
Trigger: ExecutionTriggerSchedule,
})
if err != nil {
return err
}
p.Trigger.References[policy.TriggerReferencesJobid] = jobid
}
}
}
if _, err := r.manager.CreatePolicy(p); err != nil {
return err
}
return nil
}
// UpdateRetention Update Retention
func (r *DefaultAPIController) UpdateRetention(p *policy.Metadata) error {
p0, err := r.manager.GetPolicy(p.ID)
if err != nil {
return err
}
needUn := false
needSch := false
if p0.Trigger.Kind != p.Trigger.Kind {
if p0.Trigger.Kind == policy.TriggerKindSchedule {
needUn = true
}
if p.Trigger.Kind == policy.TriggerKindSchedule {
needSch = true
}
} else {
switch p.Trigger.Kind {
case policy.TriggerKindSchedule:
if p0.Trigger.Settings["cron"] != p.Trigger.Settings["cron"] {
// unschedule old
if len(p0.Trigger.References[policy.TriggerReferencesJobid].(string)) > 0 {
needUn = true
}
// schedule new
if len(p.Trigger.Settings[policy.TriggerSettingsCron].(string)) > 0 {
// valid cron
needSch = true
}
}
case "":
default:
return fmt.Errorf("Not support Trigger %s", p.Trigger.Kind)
}
}
if needUn {
err = r.scheduler.UnSchedule(p0.Trigger.References[policy.TriggerReferencesJobid].(int64))
if err != nil {
return err
}
}
if needSch {
jobid, err := r.scheduler.Schedule(p.Trigger.Settings[policy.TriggerSettingsCron].(string), RetentionSchedulerCallback, TriggerParam{
PolicyID: p.ID,
Trigger: ExecutionTriggerSchedule,
})
if err != nil {
return err
}
p.Trigger.References[policy.TriggerReferencesJobid] = jobid
}
return r.manager.UpdatePolicy(p)
}
// DeleteRetention Delete Retention
func (r *DefaultAPIController) DeleteRetention(id int64) error {
p, err := r.manager.GetPolicy(id)
if err != nil {
return err
}
if p.Trigger.Kind == policy.TriggerKindSchedule && len(p.Trigger.Settings[policy.TriggerSettingsCron].(string)) > 0 {
err = r.scheduler.UnSchedule(p.Trigger.References[policy.TriggerReferencesJobid].(int64))
if err != nil {
return err
}
}
return r.manager.DeletePolicyAndExec(id)
}
// TriggerRetentionExec Trigger Retention Execution
func (r *DefaultAPIController) TriggerRetentionExec(policyID int64, trigger string, dryRun bool) error {
p, err := r.manager.GetPolicy(policyID)
if err != nil {
return err
}
exec := &Execution{
PolicyID: policyID,
StartTime: time.Now(),
Status: ExecutionStatusInProgress,
Trigger: trigger,
DryRun: dryRun,
}
id, err := r.manager.CreateExecution(exec)
// TODO launcher with DryRun param
num, err := r.launcher.Launch(p, id)
if err != nil {
return err
}
if num == 0 {
exec := &Execution{
ID: id,
EndTime: time.Now(),
Status: ExecutionStatusSucceed,
}
err = r.manager.UpdateExecution(exec)
if err != nil {
return err
}
}
return err
}
// OperateRetentionExec Operate Retention Execution
func (r *DefaultAPIController) OperateRetentionExec(eid int64, action string) error {
e, err := r.manager.GetExecution(eid)
if err != nil {
return err
}
exec := &Execution{}
switch action {
case "stop":
if e.Status != ExecutionStatusInProgress {
return fmt.Errorf("Can't abort, current status is %s", e.Status)
}
exec.ID = eid
exec.Status = ExecutionStatusStopped
exec.EndTime = time.Now()
// TODO stop the execution
default:
return fmt.Errorf("not support action %s", action)
}
return r.manager.UpdateExecution(exec)
}
// ListRetentionExecs List Retention Executions
func (r *DefaultAPIController) ListRetentionExecs(policyID int64, query *q.Query) ([]*Execution, error) {
return r.manager.ListExecutions(policyID, query)
}
// ListRetentionExecTasks List Retention Execution Histories
func (r *DefaultAPIController) ListRetentionExecTasks(executionID int64, query *q.Query) ([]*Task, error) {
q1 := &q.TaskQuery{
ExecutionID: executionID,
PageNumber: query.PageNumber,
PageSize: query.PageSize,
}
return r.manager.ListTasks(q1)
}
// GetRetentionExecTaskLog Get Retention Execution Task Log
func (r *DefaultAPIController) GetRetentionExecTaskLog(taskID int64) ([]byte, error) {
return r.manager.GetTaskLog(taskID)
}
// HandleHook HandleHook
func (r *DefaultAPIController) HandleHook(policyID string, event *job.StatusChange) error {
panic("implement me")
}
// NewAPIController ...
func NewAPIController(projectManager project.Manager, repositoryMgr repository.Manager, scheduler scheduler.Scheduler, retentionLauncher Launcher) APIController {
return &DefaultAPIController{
manager: NewManager(),
launcher: retentionLauncher,
projectManager: projectManager,
repositoryMgr: repositoryMgr,
scheduler: scheduler,
}
}

View File

@ -1,170 +0,0 @@
package controllers
import (
"time"
"github.com/goharbor/harbor/src/core/api"
"github.com/goharbor/harbor/src/pkg/retention"
"github.com/goharbor/harbor/src/pkg/retention/policy"
"github.com/goharbor/harbor/src/pkg/retention/q"
)
// RetentionAPI ...
type RetentionAPI struct {
api.BaseController
manager retention.Manager
}
// Prepare validates the user
func (r *RetentionAPI) Prepare() {
r.BaseController.Prepare()
r.manager = retention.NewManager()
}
// GetRetention Get Retention
func (r *RetentionAPI) GetRetention() {
id, err := r.GetIDFromURL()
if err != nil {
r.SendBadRequestError(err)
return
}
p, err := r.manager.GetPolicy(id)
if err != nil {
r.SendBadRequestError(err)
return
}
r.Data["json"] = p
r.ServeJSON()
}
// CreateRetention Create Retention
func (r *RetentionAPI) CreateRetention() {
p := &policy.Metadata{}
isValid, err := r.DecodeJSONReqAndValidate(p)
if !isValid {
r.SendBadRequestError(err)
return
}
r.manager.CreatePolicy(p)
}
// UpdateRetention Update Retention
func (r *RetentionAPI) UpdateRetention() {
id, err := r.GetIDFromURL()
if err != nil {
r.SendBadRequestError(err)
return
}
p := &policy.Metadata{}
isValid, err := r.DecodeJSONReqAndValidate(p)
if !isValid {
r.SendBadRequestError(err)
return
}
p.ID = id
r.manager.UpdatePolicy(p)
}
// DeleteRetention Delete Retention
func (r *RetentionAPI) DeleteRetention() {
id, err := r.GetIDFromURL()
if err != nil {
r.SendBadRequestError(err)
return
}
r.manager.DeletePolicy(id)
}
// TriggerRetentionExec Trigger Retention Execution
func (r *RetentionAPI) TriggerRetentionExec() {
id, err := r.GetIDFromURL()
if err != nil {
r.SendBadRequestError(err)
return
}
exec := &retention.Execution{
PolicyID: id,
StartTime: time.Now(),
Status: "Running",
}
r.manager.CreateExecution(exec)
}
// OperateRetentionExec Operate Retention Execution
func (r *RetentionAPI) OperateRetentionExec() {
eid, err := r.GetSpecialIDFromURL("eid")
if err != nil {
r.SendBadRequestError(err)
return
}
exec := &retention.Execution{}
isValid, err := r.DecodeJSONReqAndValidate(exec)
if !isValid {
r.SendBadRequestError(err)
return
}
exec.ID = eid
r.manager.UpdateExecution(nil)
}
// GetRetentionExec Get Retention Execution
func (r *RetentionAPI) GetRetentionExec() {
eid, err := r.GetSpecialIDFromURL("eid")
if err != nil {
r.SendBadRequestError(err)
return
}
exec, err := r.manager.GetExecution(eid)
if err != nil {
r.SendBadRequestError(err)
return
}
r.Data["json"] = exec
r.ServeJSON()
}
// ListRetentionExec List Retention Execution
func (r *RetentionAPI) ListRetentionExec() {
page, size, err := r.GetPaginationParams()
if err != nil {
r.SendInternalServerError(err)
return
}
query := &q.Query{
PageNumber: page,
PageSize: size,
}
execs, err := r.manager.ListExecutions(query)
if err != nil {
r.SendBadRequestError(err)
return
}
r.Data["json"] = execs
r.ServeJSON()
}
// ListRetentionExecHistory List Retention Execution Histories
func (r *RetentionAPI) ListRetentionExecHistory() {
eid, err := r.GetSpecialIDFromURL("eid")
if err != nil {
r.SendBadRequestError(err)
return
}
page, size, err := r.GetPaginationParams()
if err != nil {
r.SendInternalServerError(err)
return
}
tasks, err := r.manager.ListTasks(&q.TaskQuery{
ExecutionID: eid,
PageNumber: page,
PageSize: size,
})
if err != nil {
r.SendBadRequestError(err)
return
}
r.Data["json"] = tasks
r.ServeJSON()
}

View File

@ -30,11 +30,10 @@ type RetentionPolicy struct {
// RetentionExecution Retention Execution
type RetentionExecution struct {
ID int64 `orm:"pk;auto;column(id)" json:"id"`
PolicyID int64
Status string
StatusText string
Dry bool
ID int64 `orm:"pk;auto;column(id)" json:"id"`
PolicyID int64 `orm:"column(policy_id)"`
Status string
DryRun bool
// manual, scheduled
Trigger string
Total int
@ -46,18 +45,6 @@ type RetentionExecution struct {
EndTime time.Time
}
/*
// RetentionTask Retention Task
type RetentionTask struct {
ID int64
ExecutionID int64
RuleID int
RuleDisplayText string
Artifact string
Timestamp time.Time
}
*/
// RetentionTask ...
type RetentionTask struct {
ID int64 `orm:"pk;auto;column(id)"`
@ -69,10 +56,10 @@ type RetentionTask struct {
// RetentionScheduleJob Retention Schedule Job
type RetentionScheduleJob struct {
ID int64
ID int64 `orm:"pk;auto;column(id)" json:"id"`
Status string
PolicyID int64
JobID int64
PolicyID int64 `orm:"column(policy_id)"`
JobID int64 `orm:"column(job_id)"`
CreateTime time.Time
UpdateTime time.Time
}

View File

@ -16,15 +16,26 @@ func CreatePolicy(p *models.RetentionPolicy) (int64, error) {
}
// UpdatePolicy Update Policy
func UpdatePolicy(p *models.RetentionPolicy) error {
func UpdatePolicy(p *models.RetentionPolicy, cols ...string) error {
o := dao.GetOrmer()
_, err := o.Update(p)
_, err := o.Update(p, cols...)
return err
}
// DeletePolicy Delete Policy
func DeletePolicy(id int64) error {
// DeletePolicyAndExec Delete Policy and Exec
func DeletePolicyAndExec(id int64) error {
o := dao.GetOrmer()
if _, err := o.Raw("delete from retention_task where execution_id in (select id from retention_execution where policy_id = ?) ", id).Exec(); err != nil {
return nil
}
if _, err := o.Raw("delete from retention_execution where policy_id = ?", id).Exec(); err != nil {
return err
}
if _, err := o.Delete(&models.RetentionExecution{
PolicyID: id,
}); err != nil {
return err
}
_, err := o.Delete(&models.RetentionPolicy{
ID: id,
})
@ -50,9 +61,9 @@ func CreateExecution(e *models.RetentionExecution) (int64, error) {
}
// UpdateExecution Update Execution
func UpdateExecution(e *models.RetentionExecution) error {
func UpdateExecution(e *models.RetentionExecution, cols ...string) error {
o := dao.GetOrmer()
_, err := o.Update(e)
_, err := o.Update(e, cols...)
return err
}
@ -78,10 +89,14 @@ func GetExecution(id int64) (*models.RetentionExecution, error) {
}
// ListExecutions List Executions
func ListExecutions(query *q.Query) ([]*models.RetentionExecution, error) {
func ListExecutions(policyID int64, query *q.Query) ([]*models.RetentionExecution, error) {
o := dao.GetOrmer()
qs := o.QueryTable(new(models.RetentionExecution))
qs.Limit(query.PageSize, (query.PageNumber-1)*query.PageSize)
qs = qs.Filter("policy_id", policyID)
if query != nil {
qs = qs.Limit(query.PageSize, (query.PageNumber-1)*query.PageSize)
}
var execs []*models.RetentionExecution
_, err := qs.All(&execs)
if err != nil {
@ -95,8 +110,10 @@ func ListExecutions(query *q.Query) ([]*models.RetentionExecution, error) {
func ListExecHistories(executionID int64, query *q.Query) ([]*models.RetentionTask, error) {
o := dao.GetOrmer()
qs := o.QueryTable(new(models.RetentionTask))
qs.Filter("Execution_ID", executionID)
qs.Limit(query.PageSize, (query.PageNumber-1)*query.PageSize)
qs = qs.Filter("Execution_ID", executionID)
if query != nil {
qs = qs.Limit(query.PageSize, (query.PageNumber-1)*query.PageSize)
}
var tasks []*models.RetentionTask
_, err := qs.All(&tasks)
if err != nil {

View File

@ -91,7 +91,7 @@ func TestPolicy(t *testing.T) {
assert.Nil(t, err)
assert.EqualValues(t, "test", p1.ScopeLevel)
err = DeletePolicy(id)
err = DeletePolicyAndExec(id)
assert.Nil(t, err)
p1, err = GetPolicy(id)
@ -99,6 +99,86 @@ func TestPolicy(t *testing.T) {
assert.True(t, strings.Contains(err.Error(), "no row found"))
}
func TestExecution(t *testing.T) {
p := &policy.Metadata{
Algorithm: "OR",
Rules: []rule.Metadata{
{
ID: 1,
Priority: 1,
Template: "recentXdays",
Parameters: rule.Parameters{
"num": 10,
},
TagSelectors: []*rule.Selector{
{
Kind: "label",
Decoration: "with",
Pattern: "latest",
},
{
Kind: "regularExpression",
Decoration: "matches",
Pattern: "release-[\\d\\.]+",
},
},
ScopeSelectors: map[string][]*rule.Selector{
"repository": {
{
Kind: "regularExpression",
Decoration: "matches",
Pattern: ".+",
},
},
},
},
},
Trigger: &policy.Trigger{
Kind: "Schedule",
Settings: map[string]interface{}{
"cron": "* 22 11 * * *",
},
},
Scope: &policy.Scope{
Level: "project",
Reference: 1,
},
}
p1 := &models.RetentionPolicy{
ScopeLevel: p.Scope.Level,
TriggerKind: p.Trigger.Kind,
CreateTime: time.Now(),
UpdateTime: time.Now(),
}
data, _ := json.Marshal(p)
p1.Data = string(data)
policyID, err := CreatePolicy(p1)
assert.Nil(t, err)
assert.True(t, policyID > 0)
e := &models.RetentionExecution{
PolicyID: policyID,
Status: "Running",
DryRun: false,
Trigger: "manual",
Total: 10,
StartTime: time.Now(),
}
id, err := CreateExecution(e)
assert.Nil(t, err)
assert.True(t, id > 0)
e1, err := GetExecution(id)
assert.Nil(t, err)
assert.NotNil(t, e1)
assert.EqualValues(t, id, e1.ID)
es, err := ListExecutions(policyID, nil)
assert.Nil(t, err)
assert.EqualValues(t, 1, len(es))
}
func TestTask(t *testing.T) {
task := &models.RetentionTask{
ExecutionID: 1,

View File

@ -82,7 +82,7 @@ func (f *fakeRetentionManager) CreatePolicy(p *policy.Metadata) (int64, error) {
func (f *fakeRetentionManager) UpdatePolicy(p *policy.Metadata) error {
return nil
}
func (f *fakeRetentionManager) DeletePolicy(ID int64) error {
func (f *fakeRetentionManager) DeletePolicyAndExec(ID int64) error {
return nil
}
func (f *fakeRetentionManager) GetPolicy(ID int64) (*policy.Metadata, error) {
@ -109,11 +109,11 @@ func (f *fakeRetentionManager) UpdateTask(task *Task, cols ...string) error {
func (f *fakeRetentionManager) GetTaskLog(taskID int64) ([]byte, error) {
return nil, nil
}
func (f *fakeRetentionManager) ListExecutions(query *q.Query) ([]*Execution, error) {
func (f *fakeRetentionManager) ListExecutions(policyID int64, query *q.Query) ([]*Execution, error) {
return nil, nil
}
func (f *fakeRetentionManager) AppendHistory(history *History) error {
return nil
func (f *fakeRetentionManager) AppendHistory(history *History) (int64, error) {
return 0, nil
}
func (f *fakeRetentionManager) ListHistories(executionID int64, query *q.Query) ([]*History, error) {
return nil, nil

View File

@ -18,6 +18,7 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/astaxie/beego/orm"
"time"
"github.com/goharbor/harbor/src/pkg/retention/dao"
@ -35,7 +36,7 @@ type Manager interface {
UpdatePolicy(p *policy.Metadata) error
// Delete the specified policy
// No actual use so far
DeletePolicy(ID int64) error
DeletePolicyAndExec(ID int64) error
// Get the specified policy
GetPolicy(ID int64) (*policy.Metadata, error)
// Create a new retention execution
@ -45,7 +46,7 @@ type Manager interface {
// Get the specified execution
GetExecution(eid int64) (*Execution, error)
// List execution histories
ListExecutions(query *q.Query) ([]*Execution, error)
ListExecutions(policyID int64, query *q.Query) ([]*Execution, error)
// List tasks histories
ListTasks(query ...*q.TaskQuery) ([]*Task, error)
// Create a new retention task
@ -62,7 +63,7 @@ type DefaultManager struct {
// CreatePolicy Create Policy
func (d *DefaultManager) CreatePolicy(p *policy.Metadata) (int64, error) {
var p1 *models.RetentionPolicy
p1 := &models.RetentionPolicy{}
p1.ScopeLevel = p.Scope.Level
p1.TriggerKind = p.Trigger.Kind
data, _ := json.Marshal(p)
@ -74,7 +75,7 @@ func (d *DefaultManager) CreatePolicy(p *policy.Metadata) (int64, error) {
// UpdatePolicy Update Policy
func (d *DefaultManager) UpdatePolicy(p *policy.Metadata) error {
var p1 *models.RetentionPolicy
p1 := &models.RetentionPolicy{}
p1.ID = p.ID
p1.ScopeLevel = p.Scope.Level
p1.TriggerKind = p.Trigger.Kind
@ -83,55 +84,63 @@ func (d *DefaultManager) UpdatePolicy(p *policy.Metadata) error {
p.ID = p1.ID
p1.Data = string(data)
p1.UpdateTime = time.Now()
return dao.UpdatePolicy(p1)
return dao.UpdatePolicy(p1, "scope_level", "trigger_kind", "data", "update_time")
}
// DeletePolicy Delete Policy
func (d *DefaultManager) DeletePolicy(id int64) error {
return dao.DeletePolicy(id)
// DeletePolicyAndExec Delete Policy
func (d *DefaultManager) DeletePolicyAndExec(id int64) error {
return dao.DeletePolicyAndExec(id)
}
// GetPolicy Get Policy
func (d *DefaultManager) GetPolicy(id int64) (*policy.Metadata, error) {
p1, err := dao.GetPolicy(id)
if err != nil {
if err == orm.ErrNoRows {
return nil, nil
}
return nil, err
}
var p *policy.Metadata
p := &policy.Metadata{}
if err = json.Unmarshal([]byte(p1.Data), p); err != nil {
return nil, err
}
p.ID = id
return p, nil
}
// CreateExecution Create Execution
func (d *DefaultManager) CreateExecution(execution *Execution) (int64, error) {
var exec *models.RetentionExecution
exec := &models.RetentionExecution{}
exec.PolicyID = execution.PolicyID
exec.StartTime = time.Now()
exec.DryRun = execution.DryRun
exec.Status = "Running"
exec.Trigger = "manual"
return dao.CreateExecution(exec)
}
// UpdateExecution Update Execution
func (d *DefaultManager) UpdateExecution(execution *Execution) error {
var exec *models.RetentionExecution
exec := &models.RetentionExecution{}
exec.ID = execution.ID
exec.PolicyID = execution.PolicyID
exec.StartTime = time.Now()
exec.Status = "Running"
return dao.UpdateExecution(exec)
exec.EndTime = execution.EndTime
exec.Status = execution.Status
return dao.UpdateExecution(exec, "end_time", "status")
}
// ListExecutions List Executions
func (d *DefaultManager) ListExecutions(query *q.Query) ([]*Execution, error) {
execs, err := dao.ListExecutions(query)
func (d *DefaultManager) ListExecutions(policyID int64, query *q.Query) ([]*Execution, error) {
execs, err := dao.ListExecutions(policyID, query)
if err != nil {
if err == orm.ErrNoRows {
return nil, nil
}
return nil, err
}
var execs1 []*Execution
for _, e := range execs {
var e1 *Execution
e1 := &Execution{}
e1.ID = e.ID
e1.PolicyID = e.PolicyID
e1.Status = e.Status
@ -148,7 +157,7 @@ func (d *DefaultManager) GetExecution(eid int64) (*Execution, error) {
if err != nil {
return nil, err
}
var e1 *Execution
e1 := &Execution{}
e1.ID = e.ID
e1.PolicyID = e.PolicyID
e1.Status = e.Status
@ -175,6 +184,9 @@ func (d *DefaultManager) CreateTask(task *Task) (int64, error) {
func (d *DefaultManager) ListTasks(query ...*q.TaskQuery) ([]*Task, error) {
ts, err := dao.ListTask(query...)
if err != nil {
if err == orm.ErrNoRows {
return nil, nil
}
return nil, err
}
tasks := []*Task{}
@ -212,39 +224,6 @@ func (d *DefaultManager) GetTaskLog(taskID int64) ([]byte, error) {
panic("implement me")
}
/*
// ListHistories List Histories
func (d *DefaultManager) ListHistories(executionID int64, query *q.Query) ([]*History, error) {
his, err := dao.ListExecHistories(executionID, query)
if err != nil {
return nil, err
}
var his1 []*History
for _, h := range his {
var h1 *History
h1.ExecutionID = h.ExecutionID
h1.Artifact = h.Artifact
h1.Rule.ID = h.RuleID
h1.Rule.DisplayText = h.RuleDisplayText
h1.Timestamp = h.Timestamp
his1 = append(his1, h1)
}
return his1, nil
}
// AppendHistory Append History
func (d *DefaultManager) AppendHistory(h *History) error {
var h1 *models.RetentionTask
h1.ExecutionID = h.ExecutionID
h1.Artifact = h.Artifact
h1.RuleID = h.Rule.ID
h1.RuleDisplayText = h.Rule.DisplayText
h1.Timestamp = h.Timestamp
_, err := dao.AppendExecHistory(h1)
return err
}
*/
// NewManager ...
func NewManager() Manager {
return &DefaultManager{}

View File

@ -0,0 +1,203 @@
package retention
import (
"github.com/goharbor/harbor/src/pkg/retention/q"
"github.com/stretchr/testify/require"
"os"
"testing"
"time"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/pkg/retention/policy"
"github.com/goharbor/harbor/src/pkg/retention/policy/rule"
"github.com/stretchr/testify/assert"
)
func TestMain(m *testing.M) {
dao.PrepareTestForPostgresSQL()
os.Exit(m.Run())
}
func TestPolicy(t *testing.T) {
m := NewManager()
p1 := &policy.Metadata{
Algorithm: "OR",
Rules: []rule.Metadata{
{
ID: 1,
Priority: 1,
Template: "recentXdays",
Parameters: rule.Parameters{
"num": 10,
},
TagSelectors: []*rule.Selector{
{
Kind: "label",
Decoration: "with",
Pattern: "latest",
},
{
Kind: "regularExpression",
Decoration: "matches",
Pattern: "release-[\\d\\.]+",
},
},
ScopeSelectors: map[string][]*rule.Selector{
"repository": {
{
Kind: "regularExpression",
Decoration: "matches",
Pattern: ".+",
},
},
},
},
},
Trigger: &policy.Trigger{
Kind: "Schedule",
Settings: map[string]interface{}{
"cron": "* 22 11 * * *",
},
},
Scope: &policy.Scope{
Level: "project",
Reference: 1,
},
}
id, err := m.CreatePolicy(p1)
assert.Nil(t, err)
assert.True(t, id > 0)
p1, err = m.GetPolicy(id)
assert.Nil(t, err)
assert.EqualValues(t, "project", p1.Scope.Level)
assert.True(t, p1.ID > 0)
p1.Scope.Level = "test"
err = m.UpdatePolicy(p1)
assert.Nil(t, err)
p1, err = m.GetPolicy(id)
assert.Nil(t, err)
assert.EqualValues(t, "test", p1.Scope.Level)
err = m.DeletePolicyAndExec(id)
assert.Nil(t, err)
p1, err = m.GetPolicy(id)
assert.Nil(t, err)
assert.Nil(t, p1)
}
func TestExecution(t *testing.T) {
m := NewManager()
p1 := &policy.Metadata{
Algorithm: "OR",
Rules: []rule.Metadata{
{
ID: 1,
Priority: 1,
Template: "recentXdays",
Parameters: rule.Parameters{
"num": 10,
},
TagSelectors: []*rule.Selector{
{
Kind: "label",
Decoration: "with",
Pattern: "latest",
},
{
Kind: "regularExpression",
Decoration: "matches",
Pattern: "release-[\\d\\.]+",
},
},
ScopeSelectors: map[string][]*rule.Selector{
"repository": {
{
Kind: "regularExpression",
Decoration: "matches",
Pattern: ".+",
},
},
},
},
},
Trigger: &policy.Trigger{
Kind: "Schedule",
Settings: map[string]interface{}{
"cron": "* 22 11 * * *",
},
},
Scope: &policy.Scope{
Level: "project",
Reference: 1,
},
}
policyID, err := m.CreatePolicy(p1)
assert.Nil(t, err)
assert.True(t, policyID > 0)
e1 := &Execution{
PolicyID: policyID,
StartTime: time.Now(),
Status: ExecutionStatusInProgress,
Trigger: ExecutionTriggerManual,
DryRun: false,
}
id, err := m.CreateExecution(e1)
assert.Nil(t, err)
assert.True(t, id > 0)
e1, err = m.GetExecution(id)
assert.Nil(t, err)
assert.NotNil(t, e1)
assert.EqualValues(t, id, e1.ID)
e1.Status = ExecutionStatusFailed
err = m.UpdateExecution(e1)
assert.Nil(t, err)
e1, err = m.GetExecution(id)
assert.Nil(t, err)
assert.NotNil(t, e1)
assert.EqualValues(t, ExecutionStatusFailed, e1.Status)
es, err := m.ListExecutions(policyID, nil)
assert.Nil(t, err)
assert.EqualValues(t, 1, len(es))
}
func TestTask(t *testing.T) {
m := NewManager()
task := &Task{
ExecutionID: 1,
Status: TaskStatusPending,
StartTime: time.Now(),
}
// create
id, err := m.CreateTask(task)
require.Nil(t, err)
// update
task.ID = id
task.Status = TaskStatusInProgress
err = m.UpdateTask(task, "Status")
require.Nil(t, err)
// list
tasks, err := m.ListTasks(&q.TaskQuery{
ExecutionID: 1,
Status: TaskStatusInProgress,
})
require.Nil(t, err)
require.Equal(t, 1, len(tasks))
assert.Equal(t, int64(1), tasks[0].ExecutionID)
assert.Equal(t, TaskStatusInProgress, tasks[0].Status)
task.Status = TaskStatusFailed
err = m.UpdateTask(task, "Status")
require.Nil(t, err)
}

View File

@ -31,6 +31,9 @@ const (
CandidateKindImage string = "image"
CandidateKindChart string = "chart"
ExecutionTriggerManual string = "Manual"
ExecutionTriggerSchedule string = "Schedule"
)
// Execution of retention
@ -40,6 +43,8 @@ type Execution struct {
StartTime time.Time `json:"start_time"`
EndTime time.Time `json:"end_time,omitempty"`
Status string `json:"status"`
Trigger string `json:"Trigger"`
DryRun bool `json:"dry_run"`
}
// Task of retention
@ -53,6 +58,7 @@ type Task struct {
// History of retention
type History struct {
ID int64 `json:"id,omitempty"`
ExecutionID int64 `json:"execution_id"`
Rule struct {
ID int `json:"id"`

View File

@ -21,6 +21,17 @@ import (
const (
// AlgorithmOR for OR algorithm
AlgorithmOR = "or"
// TriggerKindSchedule Schedule
TriggerKindSchedule = "Schedule"
// TriggerReferencesJobid job_id
TriggerReferencesJobid = "job_id"
// TriggerSettingsCron cron
TriggerSettingsCron = "cron"
// ScopeLevelProject project
ScopeLevelProject = "project"
)
// Metadata of policy
@ -30,16 +41,16 @@ type Metadata struct {
// Algorithm applied to the rules
// "OR" / "AND"
Algorithm string `json:"algorithm"`
Algorithm string `json:"algorithm" valid:"Required;Match(/^(OR|AND)$/)"`
// Rule collection
Rules []rule.Metadata `json:"rules"`
// Trigger about how to launch the policy
Trigger *Trigger `json:"trigger"`
Trigger *Trigger `json:"trigger" valid:"Required"`
// Which scope the policy will be applied to
Scope *Scope `json:"scope"`
Scope *Scope `json:"scope" valid:"Required"`
// The max number of rules in a policy
Capacity int `json:"cap"`
@ -64,9 +75,9 @@ type Trigger struct {
type Scope struct {
// Scope level declaration
// 'system', 'project' and 'repository'
Level string `json:"level"`
Level string `json:"level" valid:"Required;Match(/^(project)$/)"`
// The reference identity for the specified level
// 0 for 'system', project ID for 'project' and repo ID for 'repository'
Reference int64 `json:"ref"`
Reference int64 `json:"ref" valid:"Required"`
}

View File

@ -20,38 +20,38 @@ type Metadata struct {
ID int `json:"id"`
// Priority of rule when doing calculating
Priority int `json:"priority"`
Priority int `json:"priority" valid:"Required"`
// Action of the rule performs
// "retain"
Action string `json:"action"`
Action string `json:"action" valid:"Required"`
// Template ID
Template string `json:"template"`
Template string `json:"template" valid:"Required"`
// The parameters of this rule
Parameters Parameters `json:"params"`
// Selector attached to the rule for filtering tags
TagSelectors []*Selector `json:"tag_selectors"`
TagSelectors []*Selector `json:"tag_selectors" valid:"Required"`
// Selector attached to the rule for filtering scope (e.g: repositories or namespaces)
ScopeSelectors map[string][]*Selector `json:"scope_selectors"`
ScopeSelectors map[string][]*Selector `json:"scope_selectors" valid:"Required"`
}
// Selector to narrow down the list
type Selector struct {
// Kind of the selector
// "regularExpression" or "label"
Kind string `json:"kind"`
Kind string `json:"kind" valid:"Required"`
// Decorated the selector
// for "regularExpression" : "matches" and "excludes"
// for "label" : "with" and "without"
Decoration string `json:"decoration"`
Decoration string `json:"decoration" valid:"Required"`
// Param for the selector
Pattern string `json:"pattern"`
Pattern string `json:"pattern" valid:"Required"`
}
// Parameters of rule, indexed by the key

View File

@ -1,37 +0,0 @@
// 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 retention
// Scheduler of launching retention jobs
type Scheduler interface {
// Schedule the job to periodically run the retentions
//
// Arguments:
// policyID string : uuid of the retention policy
// cron string : cron pattern like `0-59/5 12 * * * *`
// Returns:
// the returned job ID
// common error object if any errors occurred
Schedule(policyID string, cron string) (string, error)
// Unschedule the specified retention policy
//
// Arguments:
// policyID string : uuid of the retention policy
//
// Returns:
// common error object if any errors occurred
UnSchedule(policyID string) error
}