fix: correct the operator in the webhook payload (#18906)

Fix the incorrect or meaningless operator in the webhook payload.

Fixes: #18438

Signed-off-by: chlins <chenyuzh@vmware.com>
This commit is contained in:
Chlins Zhang 2023-07-19 15:40:29 +08:00 committed by GitHub
parent d4aa9b13c4
commit 970bdab936
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 250 additions and 57 deletions

View File

@ -76,3 +76,12 @@ $$
END LOOP; END LOOP;
END END
$$; $$;
/* Refactor the structure of replication schedule callback_func_param, convert the raw id to json object for extending */
/* callback_func_param
Old: 100
New: {"policy_id": 100}
*/
UPDATE schedule SET callback_func_param = json_build_object('policy_id', callback_func_param::int)::text
WHERE vendor_type='REPLICATION'
AND callback_func_param NOT LIKE '%policy_id%';

View File

@ -25,6 +25,7 @@ import (
"github.com/goharbor/harbor/src/controller/artifact" "github.com/goharbor/harbor/src/controller/artifact"
"github.com/goharbor/harbor/src/controller/event" "github.com/goharbor/harbor/src/controller/event"
"github.com/goharbor/harbor/src/controller/event/operator"
"github.com/goharbor/harbor/src/controller/repository" "github.com/goharbor/harbor/src/controller/repository"
"github.com/goharbor/harbor/src/controller/tag" "github.com/goharbor/harbor/src/controller/tag"
"github.com/goharbor/harbor/src/jobservice/job" "github.com/goharbor/harbor/src/jobservice/job"
@ -246,6 +247,10 @@ func (a *Handler) asyncFlushPullCount(ctx context.Context) {
func (a *Handler) onPush(ctx context.Context, event *event.ArtifactEvent) error { func (a *Handler) onPush(ctx context.Context, event *event.ArtifactEvent) error {
go func() { go func() {
if event.Operator != "" {
ctx = context.WithValue(ctx, operator.ContextKey{}, event.Operator)
}
if err := autoScan(ctx, &artifact.Artifact{Artifact: *event.Artifact}, event.Tags...); err != nil { if err := autoScan(ctx, &artifact.Artifact{Artifact: *event.Artifact}, event.Tags...); err != nil {
log.Errorf("scan artifact %s@%s failed, error: %v", event.Artifact.RepositoryName, event.Artifact.Digest, err) log.Errorf("scan artifact %s@%s failed, error: %v", event.Artifact.RepositoryName, event.Artifact.Digest, err)
} }

View File

@ -27,4 +27,5 @@ const (
type Event struct { type Event struct {
Type string Type string
Resource *model.Resource Resource *model.Resource
Operator string
} }

View File

@ -19,6 +19,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/goharbor/harbor/src/controller/event/operator"
"github.com/goharbor/harbor/src/controller/replication" "github.com/goharbor/harbor/src/controller/replication"
repctlmodel "github.com/goharbor/harbor/src/controller/replication/model" repctlmodel "github.com/goharbor/harbor/src/controller/replication/model"
"github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/log"
@ -51,6 +52,10 @@ func Handle(ctx context.Context, event *Event) error {
return nil return nil
} }
if event.Operator != "" {
ctx = context.WithValue(ctx, operator.ContextKey{}, event.Operator)
}
for _, policy := range policies { for _, policy := range policies {
id, err := replication.Ctl.Start(ctx, policy, event.Resource, task.ExecutionTriggerEvent) id, err := replication.Ctl.Start(ctx, policy, event.Resource, task.ExecutionTriggerEvent)
if err != nil { if err != nil {

View File

@ -109,6 +109,7 @@ func (r *Handler) handlePushArtifact(ctx context.Context, event *event.PushArtif
}}, }},
}, },
}, },
Operator: event.Operator,
} }
return repevent.Handle(ctx, e) return repevent.Handle(ctx, e)
} }
@ -140,6 +141,7 @@ func (r *Handler) handleDeleteArtifact(ctx context.Context, event *event.DeleteA
}, },
Deleted: true, Deleted: true,
}, },
Operator: event.Operator,
} }
return repevent.Handle(ctx, e) return repevent.Handle(ctx, e)
} }
@ -180,6 +182,7 @@ func (r *Handler) handleCreateTag(ctx context.Context, event *event.CreateTagEve
}}, }},
}, },
}, },
Operator: event.Operator,
} }
return repevent.Handle(ctx, e) return repevent.Handle(ctx, e)
} }
@ -212,6 +215,7 @@ func (r *Handler) handleDeleteTag(ctx context.Context, event *event.DeleteTagEve
Deleted: true, Deleted: true,
IsDeleteTag: true, IsDeleteTag: true,
}, },
Operator: event.Operator,
} }
return repevent.Handle(ctx, e) return repevent.Handle(ctx, e)
} }

View File

@ -151,7 +151,7 @@ func constructReplicationPayload(ctx context.Context, event *event.ReplicationEv
payload := &model.Payload{ payload := &model.Payload{
Type: event.EventType, Type: event.EventType,
OccurAt: event.OccurAt.Unix(), OccurAt: event.OccurAt.Unix(),
Operator: string(execution.Trigger), Operator: execution.Operator,
EventData: &model.EventData{ EventData: &model.EventData{
Replication: &ctlModel.Replication{ Replication: &ctlModel.Replication{
HarborHostname: hostname, HarborHostname: hostname,

View File

@ -130,7 +130,7 @@ func (r *RetentionHandler) constructRetentionPayload(ctx context.Context, event
payload := &model.Payload{ payload := &model.Payload{
Type: event.EventType, Type: event.EventType,
OccurAt: event.OccurAt.Unix(), OccurAt: event.OccurAt.Unix(),
Operator: execution.Trigger, Operator: execution.Operator,
EventData: &model.EventData{ EventData: &model.EventData{
Retention: &evtModel.Retention{ Retention: &evtModel.Retention{
Total: event.Total, Total: event.Total,

View File

@ -107,6 +107,7 @@ func constructQuotaPayload(event *event.QuotaEvent) (*notifyModel.Payload, error
}, },
Custom: quotaCustom, Custom: quotaCustom,
}, },
Operator: event.Operator,
} }
if event.Resource != nil { if event.Resource != nil {

View File

@ -32,8 +32,9 @@ type QuotaMetaData struct {
// used to define the event topic // used to define the event topic
Level int Level int
// the msg contains the limitation and current usage of quota // the msg contains the limitation and current usage of quota
Msg string Msg string
OccurAt time.Time OccurAt time.Time
Operator string
} }
// Resolve quota exceed into common image event // Resolve quota exceed into common image event
@ -54,6 +55,7 @@ func (q *QuotaMetaData) Resolve(evt *event.Event) error {
OccurAt: q.OccurAt, OccurAt: q.OccurAt,
RepoName: q.RepoName, RepoName: q.RepoName,
Msg: q.Msg, Msg: q.Msg,
Operator: q.Operator,
} }
if q.Tag != "" || q.Digest != "" { if q.Tag != "" || q.Digest != "" {
data.Resource = &event2.ImgResource{ data.Resource = &event2.ImgResource{

View File

@ -24,14 +24,11 @@ import (
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
) )
const (
autoTriggeredOperator = "auto"
)
// ScanImageMetaData defines meta data of image scanning event // ScanImageMetaData defines meta data of image scanning event
type ScanImageMetaData struct { type ScanImageMetaData struct {
Artifact *v1.Artifact Artifact *v1.Artifact
Status string Status string
Operator string
} }
// Resolve image scanning metadata into common chart event // Resolve image scanning metadata into common chart event
@ -57,7 +54,7 @@ func (si *ScanImageMetaData) Resolve(evt *event.Event) error {
EventType: eventType, EventType: eventType,
Artifact: si.Artifact, Artifact: si.Artifact,
OccurAt: time.Now(), OccurAt: time.Now(),
Operator: autoTriggeredOperator, Operator: si.Operator,
} }
evt.Topic = topic evt.Topic = topic

View File

@ -20,12 +20,23 @@ import (
"github.com/goharbor/harbor/src/common/security" "github.com/goharbor/harbor/src/common/security"
) )
// ContextKey is the key for storing operator in the context.
type ContextKey struct{}
// FromContext return the event operator from context // FromContext return the event operator from context
func FromContext(ctx context.Context) string { func FromContext(ctx context.Context) string {
var operator string
sc, ok := security.FromContext(ctx) sc, ok := security.FromContext(ctx)
if !ok { if ok {
return "" operator = sc.GetUsername()
}
// retrieve from context if not found in security context
if operator == "" {
op, ok := ctx.Value(ContextKey{}).(string)
if ok {
operator = op
}
} }
return sc.GetUsername() return operator
} }

View File

@ -0,0 +1,47 @@
// 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 operator
import (
"context"
"testing"
"github.com/goharbor/harbor/src/common/security"
testsec "github.com/goharbor/harbor/src/testing/common/security"
"github.com/stretchr/testify/assert"
)
func TestFromContext(t *testing.T) {
{
// no security context and operator context should return ""
op := FromContext(context.Background())
assert.Empty(t, op)
}
{
// return operator from security context
secCtx := &testsec.Context{}
secCtx.On("GetUsername").Return("security-context-user")
ctx := security.NewContext(context.Background(), secCtx)
op := FromContext(ctx)
assert.Equal(t, "security-context-user", op)
}
{
// return operator from operator context
ctx := context.WithValue(context.Background(), ContextKey{}, "operator-context-user")
op := FromContext(ctx)
assert.Equal(t, "operator-context-user", op)
}
}

View File

@ -307,6 +307,7 @@ type QuotaEvent struct {
OccurAt time.Time OccurAt time.Time
RepoName string RepoName string
Msg string Msg string
Operator string
} }
func (q *QuotaEvent) String() string { func (q *QuotaEvent) String() string {

View File

@ -19,6 +19,7 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/goharbor/harbor/src/controller/event/operator"
"github.com/goharbor/harbor/src/controller/replication/flow" "github.com/goharbor/harbor/src/controller/replication/flow"
replicationmodel "github.com/goharbor/harbor/src/controller/replication/model" replicationmodel "github.com/goharbor/harbor/src/controller/replication/model"
"github.com/goharbor/harbor/src/jobservice/job" "github.com/goharbor/harbor/src/jobservice/job"
@ -104,7 +105,11 @@ func (c *controller) Start(ctx context.Context, policy *replicationmodel.Policy,
WithMessage("the policy %d is disabled", policy.ID) WithMessage("the policy %d is disabled", policy.ID)
} }
// create an execution record // create an execution record
id, err := c.execMgr.Create(ctx, job.ReplicationVendorType, policy.ID, trigger) extra := make(map[string]interface{})
if op := operator.FromContext(ctx); op != "" {
extra["operator"] = op
}
id, err := c.execMgr.Create(ctx, job.ReplicationVendorType, policy.ID, trigger, extra)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -263,7 +268,7 @@ func (c *controller) GetTaskLog(ctx context.Context, id int64) ([]byte, error) {
} }
func convertExecution(exec *task.Execution) *Execution { func convertExecution(exec *task.Execution) *Execution {
return &Execution{ replicationExec := &Execution{
ID: exec.ID, ID: exec.ID,
PolicyID: exec.VendorID, PolicyID: exec.VendorID,
Status: exec.Status, Status: exec.Status,
@ -273,6 +278,12 @@ func convertExecution(exec *task.Execution) *Execution {
StartTime: exec.StartTime, StartTime: exec.StartTime,
EndTime: exec.EndTime, EndTime: exec.EndTime,
} }
if operator, ok := exec.ExtraAttrs["operator"].(string); ok {
replicationExec.Operator = operator
}
return replicationExec
} }
func convertTask(task *task.Task) *Task { func convertTask(task *task.Task) *Task {

View File

@ -73,7 +73,7 @@ func (r *replicationTestSuite) TestStart() {
r.Require().NotNil(err) r.Require().NotNil(err)
// got error when running the replication flow // got error when running the replication flow
r.execMgr.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil) r.execMgr.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil)
r.execMgr.On("Get", mock.Anything, mock.Anything).Return(&task.Execution{}, nil) r.execMgr.On("Get", mock.Anything, mock.Anything).Return(&task.Execution{}, nil)
r.execMgr.On("StopAndWait", mock.Anything, mock.Anything, mock.Anything).Return(nil) r.execMgr.On("StopAndWait", mock.Anything, mock.Anything, mock.Anything).Return(nil)
r.execMgr.On("MarkError", mock.Anything, mock.Anything, mock.Anything).Return(nil) r.execMgr.On("MarkError", mock.Anything, mock.Anything, mock.Anything).Return(nil)
@ -91,7 +91,7 @@ func (r *replicationTestSuite) TestStart() {
r.SetupTest() r.SetupTest()
// got no error when running the replication flow // got no error when running the replication flow
r.execMgr.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil) r.execMgr.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil)
r.execMgr.On("Get", mock.Anything, mock.Anything).Return(&task.Execution{}, nil) r.execMgr.On("Get", mock.Anything, mock.Anything).Return(&task.Execution{}, nil)
r.flowCtl.On("Start", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) r.flowCtl.On("Start", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
r.ormCreator.On("Create").Return(nil) r.ormCreator.On("Create").Return(nil)

View File

@ -28,6 +28,7 @@ type Execution struct {
StatusMessage string StatusMessage string
Metrics *dao.Metrics Metrics *dao.Metrics
Trigger string Trigger string
Operator string
StartTime time.Time StartTime time.Time
EndTime time.Time EndTime time.Time
} }

View File

@ -16,8 +16,10 @@ package replication
import ( import (
"context" "context"
"strconv" "encoding/json"
"github.com/goharbor/harbor/src/common/secret"
"github.com/goharbor/harbor/src/controller/event/operator"
"github.com/goharbor/harbor/src/controller/replication/model" "github.com/goharbor/harbor/src/controller/replication/model"
"github.com/goharbor/harbor/src/jobservice/job" "github.com/goharbor/harbor/src/jobservice/job"
"github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/log"
@ -31,10 +33,20 @@ const callbackFuncName = "REPLICATION_CALLBACK"
func init() { func init() {
callbackFunc := func(ctx context.Context, param string) error { callbackFunc := func(ctx context.Context, param string) error {
policyID, err := strconv.ParseInt(param, 10, 64) params := make(map[string]interface{})
if err != nil { if err := json.Unmarshal([]byte(param), &params); err != nil {
return err return err
} }
var policyID int64
if id, ok := params["policy_id"].(float64); ok {
policyID = int64(id)
}
if op, ok := params["operator"].(string); ok {
ctx = context.WithValue(ctx, operator.ContextKey{}, op)
}
policy, err := Ctl.GetPolicy(ctx, policyID) policy, err := Ctl.GetPolicy(ctx, policyID)
if err != nil { if err != nil {
return err return err
@ -121,8 +133,13 @@ func (c *controller) CreatePolicy(ctx context.Context, policy *model.Policy) (in
} }
// create schedule if needed // create schedule if needed
if policy.IsScheduledTrigger() { if policy.IsScheduledTrigger() {
cbParams := map[string]interface{}{
"policy_id": id,
// the operator of schedule job is harbor-jobservice
"operator": secret.JobserviceUser,
}
if _, err = c.scheduler.Schedule(ctx, job.ReplicationVendorType, id, "", policy.Trigger.Settings.Cron, if _, err = c.scheduler.Schedule(ctx, job.ReplicationVendorType, id, "", policy.Trigger.Settings.Cron,
callbackFuncName, id, map[string]interface{}{}); err != nil { callbackFuncName, cbParams, map[string]interface{}{}); err != nil {
return 0, err return 0, err
} }
} }
@ -148,8 +165,13 @@ func (c *controller) UpdatePolicy(ctx context.Context, policy *model.Policy, pro
} }
// create schedule if needed // create schedule if needed
if policy.IsScheduledTrigger() { if policy.IsScheduledTrigger() {
cbParams := map[string]interface{}{
"policy_id": policy.ID,
// the operator of schedule job is harbor-jobservice
"operator": secret.JobserviceUser,
}
if _, err := c.scheduler.Schedule(ctx, job.ReplicationVendorType, policy.ID, "", policy.Trigger.Settings.Cron, if _, err := c.scheduler.Schedule(ctx, job.ReplicationVendorType, policy.ID, "", policy.Trigger.Settings.Cron,
callbackFuncName, policy.ID, map[string]interface{}{}); err != nil { callbackFuncName, cbParams, map[string]interface{}{}); err != nil {
return err return err
} }
} }

View File

@ -15,6 +15,8 @@
package replication package replication
import ( import (
"context"
repmodel "github.com/goharbor/harbor/src/controller/replication/model" repmodel "github.com/goharbor/harbor/src/controller/replication/model"
"github.com/goharbor/harbor/src/pkg/reg/model" "github.com/goharbor/harbor/src/pkg/reg/model"
replicationmodel "github.com/goharbor/harbor/src/pkg/replication/model" replicationmodel "github.com/goharbor/harbor/src/pkg/replication/model"
@ -68,7 +70,7 @@ func (r *replicationTestSuite) TestCreatePolicy() {
ID: 1, ID: 1,
}, nil) }, nil)
mock.OnAnything(r.scheduler, "Schedule").Return(int64(1), nil) mock.OnAnything(r.scheduler, "Schedule").Return(int64(1), nil)
id, err := r.ctl.CreatePolicy(nil, &repmodel.Policy{ id, err := r.ctl.CreatePolicy(context.TODO(), &repmodel.Policy{
Name: "rule", Name: "rule",
SrcRegistry: &model.Registry{ SrcRegistry: &model.Registry{
ID: 1, ID: 1,
@ -95,7 +97,7 @@ func (r *replicationTestSuite) TestUpdatePolicy() {
mock.OnAnything(r.scheduler, "UnScheduleByVendor").Return(nil) mock.OnAnything(r.scheduler, "UnScheduleByVendor").Return(nil)
mock.OnAnything(r.scheduler, "Schedule").Return(int64(1), nil) mock.OnAnything(r.scheduler, "Schedule").Return(int64(1), nil)
mock.OnAnything(r.repMgr, "Update").Return(nil) mock.OnAnything(r.repMgr, "Update").Return(nil)
err := r.ctl.UpdatePolicy(nil, &repmodel.Policy{ err := r.ctl.UpdatePolicy(context.TODO(), &repmodel.Policy{
ID: 1, ID: 1,
Name: "rule", Name: "rule",
SrcRegistry: &model.Registry{ SrcRegistry: &model.Registry{

View File

@ -19,6 +19,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/goharbor/harbor/src/controller/event/operator"
"github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/pkg/scheduler" "github.com/goharbor/harbor/src/pkg/scheduler"
) )
@ -35,6 +36,10 @@ func retentionCallback(ctx context.Context, p string) error {
if err := json.Unmarshal([]byte(p), param); err != nil { if err := json.Unmarshal([]byte(p), param); err != nil {
return fmt.Errorf("failed to unmarshal the param: %v", err) return fmt.Errorf("failed to unmarshal the param: %v", err)
} }
if param.Operator != "" {
ctx = context.WithValue(ctx, operator.ContextKey{}, param.Operator)
}
_, err := Ctl.TriggerRetentionExec(ctx, param.PolicyID, param.Trigger, false) _, err := Ctl.TriggerRetentionExec(ctx, param.PolicyID, param.Trigger, false)
return err return err
} }

View File

@ -19,6 +19,8 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/goharbor/harbor/src/common/secret"
"github.com/goharbor/harbor/src/controller/event/operator"
"github.com/goharbor/harbor/src/jobservice/job" "github.com/goharbor/harbor/src/jobservice/job"
"github.com/goharbor/harbor/src/jobservice/logger" "github.com/goharbor/harbor/src/jobservice/logger"
"github.com/goharbor/harbor/src/lib/q" "github.com/goharbor/harbor/src/lib/q"
@ -88,6 +90,7 @@ const (
type TriggerParam struct { type TriggerParam struct {
PolicyID int64 PolicyID int64
Trigger string Trigger string
Operator string
} }
// GetRetention Get Retention // GetRetention Get Retention
@ -109,6 +112,8 @@ func (r *defaultController) CreateRetention(ctx context.Context, p *policy.Metad
if _, err = r.scheduler.Schedule(ctx, schedulerVendorType, id, "", cron.(string), SchedulerCallback, TriggerParam{ if _, err = r.scheduler.Schedule(ctx, schedulerVendorType, id, "", cron.(string), SchedulerCallback, TriggerParam{
PolicyID: id, PolicyID: id,
Trigger: retention.ExecutionTriggerSchedule, Trigger: retention.ExecutionTriggerSchedule,
// the operator of schedule job is harbor-jobservice
Operator: secret.JobserviceUser,
}, extras); err != nil { }, extras); err != nil {
return 0, err return 0, err
} }
@ -169,6 +174,8 @@ func (r *defaultController) UpdateRetention(ctx context.Context, p *policy.Metad
_, err := r.scheduler.Schedule(ctx, schedulerVendorType, p.ID, "", p.Trigger.Settings[policy.TriggerSettingsCron].(string), SchedulerCallback, TriggerParam{ _, err := r.scheduler.Schedule(ctx, schedulerVendorType, p.ID, "", p.Trigger.Settings[policy.TriggerSettingsCron].(string), SchedulerCallback, TriggerParam{
PolicyID: p.ID, PolicyID: p.ID,
Trigger: retention.ExecutionTriggerSchedule, Trigger: retention.ExecutionTriggerSchedule,
// the operator of schedule job is harbor-jobservice
Operator: secret.JobserviceUser,
}, extras) }, extras)
if err != nil { if err != nil {
return err return err
@ -227,11 +234,11 @@ func (r *defaultController) TriggerRetentionExec(ctx context.Context, policyID i
return 0, err return 0, err
} }
id, err := r.execMgr.Create(ctx, job.RetentionVendorType, policyID, trigger, extra := map[string]interface{}{
map[string]interface{}{ "dry_run": dryRun,
"dry_run": dryRun, "operator": operator.FromContext(ctx),
}, }
) id, err := r.execMgr.Create(ctx, job.RetentionVendorType, policyID, trigger, extra)
if num, err := r.launcher.Launch(ctx, p, id, dryRun); err != nil { if num, err := r.launcher.Launch(ctx, p, id, dryRun); err != nil {
if err1 := r.execMgr.StopAndWait(ctx, id, 10*time.Second); err1 != nil { if err1 := r.execMgr.StopAndWait(ctx, id, 10*time.Second); err1 != nil {
logger.Errorf("failed to stop the retention execution %d: %v", id, err1) logger.Errorf("failed to stop the retention execution %d: %v", id, err1)
@ -293,7 +300,7 @@ func (r *defaultController) ListRetentionExecs(ctx context.Context, policyID int
} }
func convertExecution(exec *task.Execution) *retention.Execution { func convertExecution(exec *task.Execution) *retention.Execution {
return &retention.Execution{ retentionExec := &retention.Execution{
ID: exec.ID, ID: exec.ID,
PolicyID: exec.VendorID, PolicyID: exec.VendorID,
StartTime: exec.StartTime, StartTime: exec.StartTime,
@ -303,6 +310,12 @@ func convertExecution(exec *task.Execution) *retention.Execution {
DryRun: exec.ExtraAttrs["dry_run"].(bool), DryRun: exec.ExtraAttrs["dry_run"].(bool),
Type: exec.VendorType, Type: exec.VendorType,
} }
if operator, ok := exec.ExtraAttrs["operator"].(string); ok {
retentionExec.Operator = operator
}
return retentionExec
} }
// GetTotalOfRetentionExecs Count Retention Executions // GetTotalOfRetentionExecs Count Retention Executions

View File

@ -27,6 +27,7 @@ import (
"github.com/goharbor/harbor/src/common/rbac" "github.com/goharbor/harbor/src/common/rbac"
ar "github.com/goharbor/harbor/src/controller/artifact" ar "github.com/goharbor/harbor/src/controller/artifact"
"github.com/goharbor/harbor/src/controller/event/operator"
"github.com/goharbor/harbor/src/controller/robot" "github.com/goharbor/harbor/src/controller/robot"
sc "github.com/goharbor/harbor/src/controller/scanner" sc "github.com/goharbor/harbor/src/controller/scanner"
"github.com/goharbor/harbor/src/controller/tag" "github.com/goharbor/harbor/src/controller/tag"
@ -308,6 +309,9 @@ func (bc *basicController) Scan(ctx context.Context, artifact *ar.Artifact, opti
"name": r.Name, "name": r.Name,
}, },
} }
if op := operator.FromContext(ctx); op != "" {
extraAttrs["operator"] = op
}
executionID, err := bc.execMgr.Create(ctx, job.ImageScanJobVendorType, artifact.ID, task.ExecutionTriggerManual, extraAttrs) executionID, err := bc.execMgr.Create(ctx, job.ImageScanJobVendorType, artifact.ID, task.ExecutionTriggerManual, extraAttrs)
if err != nil { if err != nil {
return err return err
@ -353,7 +357,11 @@ func (bc *basicController) Stop(ctx context.Context, artifact *ar.Artifact) erro
} }
func (bc *basicController) ScanAll(ctx context.Context, trigger string, async bool) (int64, error) { func (bc *basicController) ScanAll(ctx context.Context, trigger string, async bool) (int64, error) {
executionID, err := bc.execMgr.Create(ctx, job.ScanAllVendorType, 0, trigger) extra := make(map[string]interface{})
if op := operator.FromContext(ctx); op != "" {
extra["operator"] = op
}
executionID, err := bc.execMgr.Create(ctx, job.ScanAllVendorType, 0, trigger, extra)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -468,7 +476,18 @@ func (bc *basicController) startScanAll(ctx context.Context, executionID int64)
} }
} }
extraAttrs := map[string]interface{}{"summary": summary} exec, err := bc.execMgr.Get(ctx, executionID)
if err != nil {
return err
}
extraAttrs := exec.ExtraAttrs
if extraAttrs == nil {
extraAttrs = map[string]interface{}{"summary": summary}
} else {
extraAttrs["summary"] = summary
}
if err := bc.execMgr.UpdateExtraAttrs(ctx, executionID, extraAttrs); err != nil { if err := bc.execMgr.UpdateExtraAttrs(ctx, executionID, extraAttrs); err != nil {
log.Errorf("failed to set the summary info for the scan all execution, error: %v", err) log.Errorf("failed to set the summary info for the scan all execution, error: %v", err)
return err return err

View File

@ -532,7 +532,8 @@ func (suite *ControllerTestSuite) TestScanAll() {
suite.execMgr.On( suite.execMgr.On(
"Create", mock.Anything, "SCAN_ALL", int64(0), "SCHEDULE", "Create", mock.Anything, "SCAN_ALL", int64(0), "SCHEDULE",
).Return(executionID, nil).Once() mock.Anything).Return(executionID, nil).Once()
suite.execMgr.On("Get", mock.Anything, mock.Anything).Return(&task.Execution{ID: executionID}, nil).Once()
mock.OnAnything(suite.accessoryMgr, "List").Return([]accessoryModel.Accessory{}, nil).Once() mock.OnAnything(suite.accessoryMgr, "List").Return([]accessoryModel.Accessory{}, nil).Once()
@ -558,7 +559,8 @@ func (suite *ControllerTestSuite) TestScanAll() {
suite.execMgr.On( suite.execMgr.On(
"Create", mock.Anything, "SCAN_ALL", int64(0), "SCHEDULE", "Create", mock.Anything, "SCAN_ALL", int64(0), "SCHEDULE",
).Return(executionID, nil).Once() mock.Anything).Return(executionID, nil).Once()
suite.execMgr.On("Get", mock.Anything, mock.Anything).Return(&task.Execution{ID: executionID}, nil).Once()
mock.OnAnything(suite.accessoryMgr, "List").Return([]accessoryModel.Accessory{}, nil).Once() mock.OnAnything(suite.accessoryMgr, "List").Return([]accessoryModel.Accessory{}, nil).Once()

View File

@ -16,9 +16,11 @@ package scan
import ( import (
"context" "context"
"encoding/json"
"github.com/goharbor/harbor/src/controller/artifact" "github.com/goharbor/harbor/src/controller/artifact"
"github.com/goharbor/harbor/src/controller/event/metadata" "github.com/goharbor/harbor/src/controller/event/metadata"
"github.com/goharbor/harbor/src/controller/event/operator"
"github.com/goharbor/harbor/src/controller/robot" "github.com/goharbor/harbor/src/controller/robot"
"github.com/goharbor/harbor/src/jobservice/job" "github.com/goharbor/harbor/src/jobservice/job"
"github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/log"
@ -38,6 +40,7 @@ var (
robotCtl = robot.Ctl robotCtl = robot.Ctl
scanCtl = DefaultController scanCtl = DefaultController
taskMgr = task.Mgr taskMgr = task.Mgr
execMgr = task.ExecMgr
) )
func init() { func init() {
@ -56,6 +59,17 @@ func init() {
} }
func scanAllCallback(ctx context.Context, param string) error { func scanAllCallback(ctx context.Context, param string) error {
if param != "" {
params := make(map[string]interface{})
if err := json.Unmarshal([]byte(param), &params); err != nil {
return err
}
if op, ok := params["operator"].(string); ok {
ctx = context.WithValue(ctx, operator.ContextKey{}, op)
}
}
_, err := scanCtl.ScanAll(ctx, task.ExecutionTriggerSchedule, true) _, err := scanCtl.ScanAll(ctx, task.ExecutionTriggerSchedule, true)
return err return err
} }
@ -71,6 +85,11 @@ func scanTaskStatusChange(ctx context.Context, taskID int64, status string) (err
return err return err
} }
exec, err := execMgr.Get(ctx, t.ExecutionID)
if err != nil {
return err
}
robotID := getRobotID(t.ExtraAttrs) robotID := getRobotID(t.ExtraAttrs)
if robotID > 0 { if robotID > 0 {
if err := robotCtl.Delete(ctx, robotID); err != nil { if err := robotCtl.Delete(ctx, robotID); err != nil {
@ -97,6 +116,10 @@ func scanTaskStatusChange(ctx context.Context, taskID int64, status string) (err
}, },
Status: status, Status: status,
} }
if operator, ok := exec.ExtraAttrs["operator"].(string); ok {
e.Operator = operator
}
// fire event // fire event
notification.AddEvent(ctx, e) notification.AddEvent(ctx, e)
} }

View File

@ -24,9 +24,11 @@ import (
"github.com/goharbor/harbor/src/controller/artifact" "github.com/goharbor/harbor/src/controller/artifact"
"github.com/goharbor/harbor/src/jobservice/job" "github.com/goharbor/harbor/src/jobservice/job"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/pkg/task" "github.com/goharbor/harbor/src/pkg/task"
artifacttesting "github.com/goharbor/harbor/src/testing/controller/artifact" artifacttesting "github.com/goharbor/harbor/src/testing/controller/artifact"
robottesting "github.com/goharbor/harbor/src/testing/controller/robot" robottesting "github.com/goharbor/harbor/src/testing/controller/robot"
ormtesting "github.com/goharbor/harbor/src/testing/lib/orm"
"github.com/goharbor/harbor/src/testing/mock" "github.com/goharbor/harbor/src/testing/mock"
postprocessorstesting "github.com/goharbor/harbor/src/testing/pkg/scan/postprocessors" postprocessorstesting "github.com/goharbor/harbor/src/testing/pkg/scan/postprocessors"
reporttesting "github.com/goharbor/harbor/src/testing/pkg/scan/report" reporttesting "github.com/goharbor/harbor/src/testing/pkg/scan/report"
@ -36,6 +38,8 @@ import (
type CallbackTestSuite struct { type CallbackTestSuite struct {
suite.Suite suite.Suite
ctx context.Context
artifactCtl *artifacttesting.Controller artifactCtl *artifacttesting.Controller
execMgr *tasktesting.ExecutionManager execMgr *tasktesting.ExecutionManager
@ -51,6 +55,7 @@ type CallbackTestSuite struct {
} }
func (suite *CallbackTestSuite) SetupSuite() { func (suite *CallbackTestSuite) SetupSuite() {
suite.ctx = orm.NewContext(nil, &ormtesting.FakeOrmer{})
suite.artifactCtl = &artifacttesting.Controller{} suite.artifactCtl = &artifacttesting.Controller{}
artifactCtl = suite.artifactCtl artifactCtl = suite.artifactCtl
@ -79,56 +84,56 @@ func (suite *CallbackTestSuite) SetupSuite() {
func (suite *CallbackTestSuite) TestScanTaskStatusChange() { func (suite *CallbackTestSuite) TestScanTaskStatusChange() {
{ {
// get task failed // get task failed
suite.taskMgr.On("Get", context.TODO(), int64(1)).Return(nil, fmt.Errorf("not found")).Once() suite.taskMgr.On("Get", mock.Anything, int64(1)).Return(nil, fmt.Errorf("not found")).Once()
suite.Error(scanTaskStatusChange(context.TODO(), 1, job.SuccessStatus.String())) suite.Error(scanTaskStatusChange(suite.ctx, 1, job.SuccessStatus.String()))
} }
{ {
// status success // status success
suite.taskMgr.On("Get", context.TODO(), int64(1)).Return( suite.taskMgr.On("Get", mock.Anything, int64(1)).Return(
&task.Task{ &task.Task{
ExtraAttrs: suite.makeExtraAttrs(0, 1), ExtraAttrs: suite.makeExtraAttrs(0, 1),
}, },
nil, nil,
).Once() ).Once()
suite.robotCtl.On("Delete", context.TODO(), int64(1)).Return(nil).Once() suite.robotCtl.On("Delete", mock.Anything, int64(1)).Return(nil).Once()
suite.NoError(scanTaskStatusChange(context.TODO(), 1, job.SuccessStatus.String())) suite.NoError(scanTaskStatusChange(suite.ctx, 1, job.SuccessStatus.String()))
} }
{ {
// status success, delete robot failed // status success, delete robot failed
suite.taskMgr.On("Get", context.TODO(), int64(1)).Return( suite.taskMgr.On("Get", mock.Anything, int64(1)).Return(
&task.Task{ &task.Task{
ExtraAttrs: suite.makeExtraAttrs(0, 1), ExtraAttrs: suite.makeExtraAttrs(0, 1),
}, },
nil, nil,
).Once() ).Once()
suite.robotCtl.On("Delete", context.TODO(), int64(1)).Return(fmt.Errorf("failed")).Once() suite.robotCtl.On("Delete", mock.Anything, int64(1)).Return(fmt.Errorf("failed")).Once()
suite.NoError(scanTaskStatusChange(context.TODO(), 1, job.SuccessStatus.String())) suite.NoError(scanTaskStatusChange(suite.ctx, 1, job.SuccessStatus.String()))
} }
{ {
// status success, artifact not found // status success, artifact not found
suite.taskMgr.On("Get", context.TODO(), int64(1)).Return( suite.taskMgr.On("Get", mock.Anything, int64(1)).Return(
&task.Task{ &task.Task{
ExtraAttrs: suite.makeExtraAttrs(1, 0), ExtraAttrs: suite.makeExtraAttrs(1, 0),
}, },
nil, nil,
).Once() ).Once()
suite.artifactCtl.On("Get", context.TODO(), int64(1), (*artifact.Option)(nil)).Return(nil, fmt.Errorf("not found")).Once() suite.artifactCtl.On("Get", mock.Anything, int64(1), (*artifact.Option)(nil)).Return(nil, fmt.Errorf("not found")).Once()
suite.NoError(scanTaskStatusChange(context.TODO(), 1, job.SuccessStatus.String())) suite.NoError(scanTaskStatusChange(suite.ctx, 1, job.SuccessStatus.String()))
} }
{ {
// status success // status success
suite.taskMgr.On("Get", context.TODO(), int64(1)).Return( suite.taskMgr.On("Get", mock.Anything, int64(1)).Return(
&task.Task{ &task.Task{
ExtraAttrs: suite.makeExtraAttrs(1, 0), ExtraAttrs: suite.makeExtraAttrs(1, 0),
}, },
nil, nil,
).Once() ).Once()
suite.artifactCtl.On("Get", context.TODO(), int64(1), (*artifact.Option)(nil)).Return(&artifact.Artifact{}, nil).Once() suite.artifactCtl.On("Get", mock.Anything, int64(1), (*artifact.Option)(nil)).Return(&artifact.Artifact{}, nil).Once()
suite.NoError(scanTaskStatusChange(context.TODO(), 1, job.SuccessStatus.String())) suite.NoError(scanTaskStatusChange(suite.ctx, 1, job.SuccessStatus.String()))
} }
} }
@ -136,21 +141,21 @@ func (suite *CallbackTestSuite) TestScanAllCallback() {
{ {
// create execution failed // create execution failed
suite.execMgr.On( suite.execMgr.On(
"Create", context.TODO(), "SCAN_ALL", int64(0), "SCHEDULE", "Create", mock.Anything, "SCAN_ALL", int64(0), "SCHEDULE",
).Return(int64(0), fmt.Errorf("failed")).Once() mock.Anything).Return(int64(0), fmt.Errorf("failed")).Once()
suite.Error(scanAllCallback(context.TODO(), "")) suite.Error(scanAllCallback(suite.ctx, ""))
} }
{ {
executionID := int64(1) executionID := int64(1)
suite.execMgr.On( suite.execMgr.On(
"Create", context.TODO(), "SCAN_ALL", int64(0), "SCHEDULE", "Create", mock.Anything, "SCAN_ALL", int64(0), "SCHEDULE",
).Return(executionID, nil).Once() mock.Anything).Return(executionID, nil).Once()
suite.execMgr.On( suite.execMgr.On(
"Get", context.TODO(), executionID, "Get", mock.Anything, executionID,
).Return(&task.Execution{}, nil) ).Return(&task.Execution{}, nil)
mock.OnAnything(suite.artifactCtl, "List").Return([]*artifact.Artifact{}, nil).Once() mock.OnAnything(suite.artifactCtl, "List").Return([]*artifact.Artifact{}, nil).Once()
@ -159,7 +164,7 @@ func (suite *CallbackTestSuite) TestScanAllCallback() {
suite.execMgr.On("MarkDone", mock.Anything, executionID, mock.Anything).Return(nil).Once() suite.execMgr.On("MarkDone", mock.Anything, executionID, mock.Anything).Return(nil).Once()
suite.NoError(scanAllCallback(context.TODO(), "")) suite.NoError(scanAllCallback(suite.ctx, ""))
} }
} }

View File

@ -38,6 +38,7 @@ type Execution struct {
Status string `json:"status"` Status string `json:"status"`
Trigger string `json:"trigger"` Trigger string `json:"trigger"`
DryRun bool `json:"dry_run"` DryRun bool `json:"dry_run"`
Operator string `json:"operator"`
Type string `json:"-"` Type string `json:"-"`
} }

View File

@ -22,6 +22,7 @@ import (
"time" "time"
"github.com/goharbor/harbor/src/controller/event/metadata" "github.com/goharbor/harbor/src/controller/event/metadata"
"github.com/goharbor/harbor/src/controller/event/operator"
"github.com/goharbor/harbor/src/controller/quota" "github.com/goharbor/harbor/src/controller/quota"
"github.com/goharbor/harbor/src/lib" "github.com/goharbor/harbor/src/lib"
"github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/log"
@ -100,6 +101,7 @@ func projectResourcesEvent(level int) func(*http.Request, string, string, string
Level: level, Level: level,
Msg: message, Msg: message,
OccurAt: time.Now(), OccurAt: time.Now(),
Operator: operator.FromContext(ctx),
} }
} }
} }

View File

@ -24,6 +24,7 @@ import (
"golang.org/x/text/language" "golang.org/x/text/language"
"github.com/goharbor/harbor/src/common/rbac" "github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/secret"
"github.com/goharbor/harbor/src/controller/scan" "github.com/goharbor/harbor/src/controller/scan"
"github.com/goharbor/harbor/src/controller/scanner" "github.com/goharbor/harbor/src/controller/scanner"
"github.com/goharbor/harbor/src/jobservice/job" "github.com/goharbor/harbor/src/jobservice/job"
@ -203,7 +204,11 @@ func (s *scanAllAPI) createOrUpdateScanAllSchedule(ctx context.Context, cronType
} }
} }
return s.scheduler.Schedule(ctx, job.ScanAllVendorType, 0, cronType, cron, scan.ScanAllCallback, nil, nil) cbParams := map[string]interface{}{
// the operator of schedule job is harbor-jobservice
"operator": secret.JobserviceUser,
}
return s.scheduler.Schedule(ctx, job.ScanAllVendorType, 0, cronType, cron, scan.ScanAllCallback, cbParams, nil)
} }
func (s *scanAllAPI) getScanAllSchedule(ctx context.Context) (*scheduler.Schedule, error) { func (s *scanAllAPI) getScanAllSchedule(ctx context.Context) (*scheduler.Schedule, error) {

View File

@ -136,8 +136,7 @@ func (suite *ScanAllTestSuite) TestAuthorization() {
// system admin required // system admin required
suite.Security.On("IsAuthenticated").Return(true).Once() suite.Security.On("IsAuthenticated").Return(true).Once()
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(false).Once() suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(false).Once()
suite.Security.On("GetUsername").Return("username").Once() suite.Security.On("GetUsername").Return("username")
res, err := suite.DoReq(req.method, req.url, newBody(req.body)) res, err := suite.DoReq(req.method, req.url, newBody(req.body))
suite.NoError(err) suite.NoError(err)
suite.Equal(403, res.StatusCode) suite.Equal(403, res.StatusCode)