mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-22 02:05:41 +01:00
Merge branch 'main' into simplify-registry-build
This commit is contained in:
commit
0628ea5043
2
Makefile
2
Makefile
@ -282,7 +282,7 @@ endef
|
||||
|
||||
# lint swagger doc
|
||||
SPECTRAL_IMAGENAME=$(IMAGENAMESPACE)/spectral
|
||||
SPECTRAL_VERSION=v6.1.0
|
||||
SPECTRAL_VERSION=v6.11.1
|
||||
SPECTRAL_IMAGE_BUILD_CMD=${DOCKERBUILD} -f ${TOOLSPATH}/spectral/Dockerfile --build-arg GOLANG=${GOBUILDIMAGE} --build-arg SPECTRAL_VERSION=${SPECTRAL_VERSION} -t ${SPECTRAL_IMAGENAME}:$(SPECTRAL_VERSION) .
|
||||
SPECTRAL=$(RUNCONTAINER) $(SPECTRAL_IMAGENAME):$(SPECTRAL_VERSION)
|
||||
|
||||
|
@ -7095,6 +7095,9 @@ definitions:
|
||||
type: boolean
|
||||
description: Whether the preheat policy enabled
|
||||
x-omitempty: false
|
||||
scope:
|
||||
type: string
|
||||
description: The scope of preheat policy
|
||||
creation_time:
|
||||
type: string
|
||||
format: date-time
|
||||
|
@ -48,7 +48,7 @@ harbor_admin_password: Harbor12345
|
||||
|
||||
# Harbor DB configuration
|
||||
database:
|
||||
# The password for the root user of Harbor DB. Change this before any production use.
|
||||
# The password for the user('postgres' by default) of Harbor DB. Change this before any production use.
|
||||
password: root123
|
||||
# The maximum number of connections in the idle connection pool. If it <=0, no idle connections are retained.
|
||||
max_idle_conns: 100
|
||||
|
@ -3,3 +3,5 @@ Add new column creator_ref and creator_type for robot table to record the creato
|
||||
*/
|
||||
ALTER TABLE robot ADD COLUMN IF NOT EXISTS creator_ref integer default 0;
|
||||
ALTER TABLE robot ADD COLUMN IF NOT EXISTS creator_type varchar(255);
|
||||
|
||||
ALTER TABLE p2p_preheat_policy ADD COLUMN IF NOT EXISTS scope varchar(255);
|
@ -14,7 +14,9 @@
|
||||
|
||||
package rbac
|
||||
|
||||
import "github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
)
|
||||
|
||||
// const action variables
|
||||
const (
|
||||
@ -81,9 +83,88 @@ const (
|
||||
ResourceSecurityHub = Resource("security-hub")
|
||||
)
|
||||
|
||||
type scope string
|
||||
|
||||
const (
|
||||
ScopeSystem = scope("System")
|
||||
ScopeProject = scope("Project")
|
||||
)
|
||||
|
||||
// RobotPermissionProvider defines the permission provider for robot account
|
||||
type RobotPermissionProvider interface {
|
||||
GetPermissions(s scope) []*types.Policy
|
||||
}
|
||||
|
||||
// GetPermissionProvider gives the robot permission provider
|
||||
func GetPermissionProvider() RobotPermissionProvider {
|
||||
// TODO will determine by the ui configuration
|
||||
return &NolimitProvider{}
|
||||
}
|
||||
|
||||
// BaseProvider ...
|
||||
type BaseProvider struct {
|
||||
}
|
||||
|
||||
// GetPermissions ...
|
||||
func (d *BaseProvider) GetPermissions(s scope) []*types.Policy {
|
||||
return PoliciesMap[s]
|
||||
}
|
||||
|
||||
// NolimitProvider ...
|
||||
type NolimitProvider struct {
|
||||
BaseProvider
|
||||
}
|
||||
|
||||
// GetPermissions ...
|
||||
func (n *NolimitProvider) GetPermissions(s scope) []*types.Policy {
|
||||
if s == ScopeSystem {
|
||||
return append(n.BaseProvider.GetPermissions(ScopeSystem),
|
||||
&types.Policy{Resource: ResourceRobot, Action: ActionCreate},
|
||||
&types.Policy{Resource: ResourceRobot, Action: ActionRead},
|
||||
&types.Policy{Resource: ResourceRobot, Action: ActionUpdate},
|
||||
&types.Policy{Resource: ResourceRobot, Action: ActionList},
|
||||
&types.Policy{Resource: ResourceRobot, Action: ActionDelete},
|
||||
|
||||
&types.Policy{Resource: ResourceUser, Action: ActionCreate},
|
||||
&types.Policy{Resource: ResourceUser, Action: ActionRead},
|
||||
&types.Policy{Resource: ResourceUser, Action: ActionUpdate},
|
||||
&types.Policy{Resource: ResourceUser, Action: ActionList},
|
||||
&types.Policy{Resource: ResourceUser, Action: ActionDelete},
|
||||
|
||||
&types.Policy{Resource: ResourceLdapUser, Action: ActionCreate},
|
||||
&types.Policy{Resource: ResourceLdapUser, Action: ActionList},
|
||||
|
||||
&types.Policy{Resource: ResourceExportCVE, Action: ActionCreate},
|
||||
&types.Policy{Resource: ResourceExportCVE, Action: ActionRead},
|
||||
|
||||
&types.Policy{Resource: ResourceQuota, Action: ActionUpdate},
|
||||
|
||||
&types.Policy{Resource: ResourceUserGroup, Action: ActionCreate},
|
||||
&types.Policy{Resource: ResourceUserGroup, Action: ActionRead},
|
||||
&types.Policy{Resource: ResourceUserGroup, Action: ActionUpdate},
|
||||
&types.Policy{Resource: ResourceUserGroup, Action: ActionList},
|
||||
&types.Policy{Resource: ResourceUserGroup, Action: ActionDelete})
|
||||
}
|
||||
if s == ScopeProject {
|
||||
return append(n.BaseProvider.GetPermissions(ScopeProject),
|
||||
&types.Policy{Resource: ResourceRobot, Action: ActionCreate},
|
||||
&types.Policy{Resource: ResourceRobot, Action: ActionRead},
|
||||
&types.Policy{Resource: ResourceRobot, Action: ActionUpdate},
|
||||
&types.Policy{Resource: ResourceRobot, Action: ActionList},
|
||||
&types.Policy{Resource: ResourceRobot, Action: ActionDelete},
|
||||
|
||||
&types.Policy{Resource: ResourceMember, Action: ActionCreate},
|
||||
&types.Policy{Resource: ResourceMember, Action: ActionRead},
|
||||
&types.Policy{Resource: ResourceMember, Action: ActionUpdate},
|
||||
&types.Policy{Resource: ResourceMember, Action: ActionList},
|
||||
&types.Policy{Resource: ResourceMember, Action: ActionDelete})
|
||||
}
|
||||
return []*types.Policy{}
|
||||
}
|
||||
|
||||
var (
|
||||
PoliciesMap = map[string][]*types.Policy{
|
||||
"System": {
|
||||
PoliciesMap = map[scope][]*types.Policy{
|
||||
ScopeSystem: {
|
||||
{Resource: ResourceAuditLog, Action: ActionList},
|
||||
|
||||
{Resource: ResourcePreatInstance, Action: ActionRead},
|
||||
@ -154,7 +235,7 @@ var (
|
||||
{Resource: ResourceQuota, Action: ActionRead},
|
||||
{Resource: ResourceQuota, Action: ActionList},
|
||||
},
|
||||
"Project": {
|
||||
ScopeProject: {
|
||||
{Resource: ResourceLog, Action: ActionList},
|
||||
|
||||
{Resource: ResourceProject, Action: ActionRead},
|
||||
|
36
src/common/rbac/const_test.go
Normal file
36
src/common/rbac/const_test.go
Normal file
@ -0,0 +1,36 @@
|
||||
package rbac
|
||||
|
||||
import (
|
||||
_ "github.com/goharbor/harbor/src/pkg/config/inmemory"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBaseProvider(t *testing.T) {
|
||||
permissionProvider := &BaseProvider{}
|
||||
sysPermissions := permissionProvider.GetPermissions(ScopeSystem)
|
||||
|
||||
for _, per := range sysPermissions {
|
||||
if per.Action == ActionCreate && per.Resource == ResourceRobot {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNolimitProvider(t *testing.T) {
|
||||
permissionProvider := &BaseProvider{}
|
||||
sysPermissions := permissionProvider.GetPermissions(ScopeSystem)
|
||||
|
||||
for _, per := range sysPermissions {
|
||||
if per.Action == ActionCreate && per.Resource == ResourceRobot {
|
||||
t.Log("no limit provider has the permission of robot account creation")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPermissionProvider(t *testing.T) {
|
||||
defaultPro := GetPermissionProvider()
|
||||
_, ok := defaultPro.(*NolimitProvider)
|
||||
assert.True(t, ok)
|
||||
}
|
@ -402,7 +402,7 @@ func (de *defaultEnforcer) launchExecutions(ctx context.Context, candidates []*s
|
||||
// Start tasks
|
||||
count := 0
|
||||
for _, c := range candidates {
|
||||
if _, err = de.startTask(ctx, eid, c, insData); err != nil {
|
||||
if _, err = de.startTask(ctx, eid, c, insData, pl.Scope); err != nil {
|
||||
// Just log the error and skip
|
||||
log.Errorf("start task error for preheating image: %s/%s:%s@%s", c.Namespace, c.Repository, c.Tags[0], c.Digest)
|
||||
continue
|
||||
@ -421,7 +421,7 @@ func (de *defaultEnforcer) launchExecutions(ctx context.Context, candidates []*s
|
||||
}
|
||||
|
||||
// startTask starts the preheat task(job) for the given candidate
|
||||
func (de *defaultEnforcer) startTask(ctx context.Context, executionID int64, candidate *selector.Candidate, instance string) (int64, error) {
|
||||
func (de *defaultEnforcer) startTask(ctx context.Context, executionID int64, candidate *selector.Candidate, instance, scope string) (int64, error) {
|
||||
u, err := de.fullURLGetter(candidate)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
@ -441,6 +441,7 @@ func (de *defaultEnforcer) startTask(ctx context.Context, executionID int64, can
|
||||
ImageName: fmt.Sprintf("%s/%s", candidate.Namespace, candidate.Repository),
|
||||
Tag: candidate.Tags[0],
|
||||
Digest: candidate.Digest,
|
||||
Scope: scope,
|
||||
}
|
||||
|
||||
piData, err := pi.ToJSON()
|
||||
|
@ -210,6 +210,7 @@ func mockPolicies() []*po.Schema {
|
||||
Type: po.TriggerTypeManual,
|
||||
},
|
||||
Enabled: true,
|
||||
Scope: "single_peer",
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UpdatedTime: time.Now().UTC(),
|
||||
}, {
|
||||
@ -235,6 +236,7 @@ func mockPolicies() []*po.Schema {
|
||||
Trigger: &po.Trigger{
|
||||
Type: po.TriggerTypeEventBased,
|
||||
},
|
||||
Scope: "all_peers",
|
||||
Enabled: true,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UpdatedTime: time.Now().UTC(),
|
||||
|
@ -97,10 +97,6 @@ func (d *controller) Count(ctx context.Context, query *q.Query) (int64, error) {
|
||||
|
||||
// Create ...
|
||||
func (d *controller) Create(ctx context.Context, r *Robot) (int64, string, error) {
|
||||
if err := d.setProject(ctx, r); err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
var expiresAt int64
|
||||
if r.Duration == -1 {
|
||||
expiresAt = -1
|
||||
@ -327,22 +323,6 @@ func (d *controller) populatePermissions(ctx context.Context, r *Robot) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// set the project info if it's a project level robot
|
||||
func (d *controller) setProject(ctx context.Context, r *Robot) error {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
if r.Level == LEVELPROJECT {
|
||||
pro, err := d.proMgr.Get(ctx, r.Permissions[0].Namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.ProjectName = pro.Name
|
||||
r.ProjectID = pro.ProjectID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// convertScope converts the db scope into robot model
|
||||
// /system => Kind: system Namespace: /
|
||||
// /project/* => Kind: project Namespace: *
|
||||
@ -394,6 +374,22 @@ func (d *controller) toScope(ctx context.Context, p *Permission) (string, error)
|
||||
return "", errors.New(nil).WithMessage("unknown robot kind").WithCode(errors.BadRequestCode)
|
||||
}
|
||||
|
||||
// set the project info if it's a project level robot
|
||||
func SetProject(ctx context.Context, r *Robot) error {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
if r.Level == LEVELPROJECT {
|
||||
pro, err := project.New().Get(ctx, r.Permissions[0].Namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.ProjectName = pro.Name
|
||||
r.ProjectID = pro.ProjectID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateSec(salt ...string) (string, string, string, error) {
|
||||
var secret, pwd string
|
||||
options := []retry.Option{
|
||||
|
@ -39,10 +39,11 @@ const (
|
||||
// Robot ...
|
||||
type Robot struct {
|
||||
model.Robot
|
||||
ProjectName string
|
||||
Level string
|
||||
Editable bool `json:"editable"`
|
||||
Permissions []*Permission `json:"permissions"`
|
||||
ProjectName string
|
||||
ProjectNameOrID interface{}
|
||||
Level string
|
||||
Editable bool `json:"editable"`
|
||||
Permissions []*Permission `json:"permissions"`
|
||||
}
|
||||
|
||||
// IsSysLevel, true is a system level robot, others are project level.
|
||||
|
@ -867,7 +867,8 @@ func (bc *basicController) makeRobotAccount(ctx context.Context, projectID int64
|
||||
CreatorType: "local",
|
||||
CreatorRef: int64(0),
|
||||
},
|
||||
Level: robot.LEVELPROJECT,
|
||||
ProjectName: projectName,
|
||||
Level: robot.LEVELPROJECT,
|
||||
Permissions: []*robot.Permission{
|
||||
{
|
||||
Kind: "project",
|
||||
|
@ -238,7 +238,8 @@ func (suite *ControllerTestSuite) SetupSuite() {
|
||||
CreatorType: "local",
|
||||
CreatorRef: int64(0),
|
||||
},
|
||||
Level: robot.LEVELPROJECT,
|
||||
ProjectName: "library",
|
||||
Level: robot.LEVELPROJECT,
|
||||
Permissions: []*robot.Permission{
|
||||
{
|
||||
Kind: "project",
|
||||
|
44
src/go.mod
44
src/go.mod
@ -21,12 +21,12 @@ require (
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7
|
||||
github.com/go-ldap/ldap/v3 v3.4.6
|
||||
github.com/go-openapi/errors v0.22.0
|
||||
github.com/go-openapi/loads v0.22.0 // indirect
|
||||
github.com/go-openapi/loads v0.22.0
|
||||
github.com/go-openapi/runtime v0.28.0
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0
|
||||
github.com/go-openapi/strfmt v0.23.0
|
||||
github.com/go-openapi/swag v0.23.0
|
||||
github.com/go-openapi/validate v0.24.0 // indirect
|
||||
github.com/go-openapi/validate v0.24.0
|
||||
github.com/go-redis/redis/v8 v8.11.4
|
||||
github.com/gocarina/gocsv v0.0.0-20210516172204-ca9e8a8ddea8
|
||||
github.com/gocraft/work v0.5.1
|
||||
@ -48,7 +48,7 @@ require (
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.1.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.19.1
|
||||
github.com/prometheus/client_golang v1.20.4
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
@ -63,21 +63,23 @@ require (
|
||||
go.opentelemetry.io/otel/sdk v1.27.0
|
||||
go.opentelemetry.io/otel/trace v1.29.0
|
||||
go.uber.org/ratelimit v0.3.1
|
||||
golang.org/x/crypto v0.25.0
|
||||
golang.org/x/crypto v0.27.0
|
||||
golang.org/x/net v0.27.0
|
||||
golang.org/x/oauth2 v0.21.0
|
||||
golang.org/x/sync v0.7.0
|
||||
golang.org/x/text v0.16.0
|
||||
golang.org/x/sync v0.8.0
|
||||
golang.org/x/text v0.18.0
|
||||
golang.org/x/time v0.5.0
|
||||
gopkg.in/h2non/gock.v1 v1.1.2
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
helm.sh/helm/v3 v3.15.4
|
||||
k8s.io/api v0.30.3
|
||||
k8s.io/apimachinery v0.30.3
|
||||
k8s.io/client-go v0.30.3
|
||||
k8s.io/api v0.31.1
|
||||
k8s.io/apimachinery v0.31.1
|
||||
k8s.io/client-go v0.31.1
|
||||
sigs.k8s.io/yaml v1.4.0
|
||||
)
|
||||
|
||||
require github.com/prometheus/client_model v0.6.1
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v37.2.0+incompatible // indirect
|
||||
@ -94,7 +96,7 @@ require (
|
||||
github.com/Unknwon/goconfig v0.0.0-20160216183935-5f601ca6ef4d // indirect
|
||||
github.com/benbjohnson/clock v1.3.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/denverdino/aliyungo v0.0.0-20191227032621-df38c6fa730c // indirect
|
||||
@ -105,6 +107,7 @@ require (
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
@ -132,7 +135,7 @@ require (
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.17.2 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
@ -141,13 +144,13 @@ require (
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.48.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/prometheus/common v0.55.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/robfig/cron v1.0.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
@ -164,6 +167,7 @@ require (
|
||||
github.com/vbatts/tar-split v0.11.3 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/volcengine/volc-sdk-golang v1.0.23 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.mongodb.org/mongo-driver v1.14.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.29.0 // indirect
|
||||
@ -172,19 +176,19 @@ require (
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
go.uber.org/zap v1.21.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/term v0.22.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/term v0.24.0 // indirect
|
||||
google.golang.org/api v0.171.0 // indirect
|
||||
google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect
|
||||
google.golang.org/grpc v1.64.1 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/klog/v2 v2.120.1 // indirect
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
)
|
||||
|
80
src/go.sum
80
src/go.sum
@ -81,8 +81,8 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudevents/sdk-go/v2 v2.15.2 h1:54+I5xQEnI73RBhWHxbI1XJcqOFOVJN85vb41+8mHUc=
|
||||
github.com/cloudevents/sdk-go/v2 v2.15.2/go.mod h1:lL7kSWAE/V8VI4Wh0jbL2v/jvqsm6tjmaQBSvxcv4uE=
|
||||
@ -143,6 +143,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
@ -342,8 +344,8 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
|
||||
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
@ -356,6 +358,8 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
@ -414,9 +418,8 @@ github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
|
||||
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE=
|
||||
github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
@ -435,22 +438,22 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI=
|
||||
github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
||||
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
|
||||
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/robfig/cron v1.0.0 h1:slmQxIUH6U9ruw4XoJ7C2pyyx4yYeiHx8S9pNootHsM=
|
||||
github.com/robfig/cron v1.0.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
@ -529,6 +532,8 @@ github.com/volcengine/volc-sdk-golang v1.0.23 h1:anOslb2Qp6ywnsbyq9jqR0ljuO63kg9
|
||||
github.com/volcengine/volc-sdk-golang v1.0.23/go.mod h1:AfG/PZRUkHJ9inETvbjNifTDgut25Wbkm2QoYBTbvyU=
|
||||
github.com/volcengine/volcengine-go-sdk v1.0.138 h1:u1dL+Dc1kWBTrufU4LrspRdvjhkxNESWfMHR/G4Pvcg=
|
||||
github.com/volcengine/volcengine-go-sdk v1.0.138/go.mod h1:oht5AKDJsk0fY6tV2ViqaVlOO14KSRmXZlI8ikK60Tg=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
@ -566,8 +571,9 @@ go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
@ -595,8 +601,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
@ -647,8 +653,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -682,16 +688,16 @@ golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@ -701,8 +707,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
@ -762,8 +768,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@ -800,18 +806,18 @@ helm.sh/helm/v3 v3.15.4/go.mod h1:phOwlxqGSgppCY/ysWBNRhG3MtnpsttOzxaTK+Mt40E=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
k8s.io/api v0.30.3 h1:ImHwK9DCsPA9uoU3rVh4QHAHHK5dTSv1nxJUapx8hoQ=
|
||||
k8s.io/api v0.30.3/go.mod h1:GPc8jlzoe5JG3pb0KJCSLX5oAFIW3/qNJITlDj8BH04=
|
||||
k8s.io/apimachinery v0.30.3 h1:q1laaWCmrszyQuSQCfNB8cFgCuDAoPszKY4ucAjDwHc=
|
||||
k8s.io/apimachinery v0.30.3/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
|
||||
k8s.io/client-go v0.30.3 h1:bHrJu3xQZNXIi8/MoxYtZBBWQQXwy16zqJwloXXfD3k=
|
||||
k8s.io/client-go v0.30.3/go.mod h1:8d4pf8vYu665/kUbsxWAQ/JDBNWqfFeZnvFiVdmx89U=
|
||||
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
|
||||
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU=
|
||||
k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI=
|
||||
k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U=
|
||||
k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
|
||||
k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0=
|
||||
k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
|
||||
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
|
||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
|
||||
|
@ -16,6 +16,7 @@ package instance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
dao "github.com/goharbor/harbor/src/pkg/p2p/preheat/dao/instance"
|
||||
@ -114,7 +115,18 @@ func (dm *manager) Update(ctx context.Context, inst *provider.Instance, props ..
|
||||
|
||||
// Get implements @Manager.Get
|
||||
func (dm *manager) Get(ctx context.Context, id int64) (*provider.Instance, error) {
|
||||
return dm.dao.Get(ctx, id)
|
||||
ins, err := dm.dao.Get(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// mapping auth data to auth info.
|
||||
if len(ins.AuthData) > 0 {
|
||||
if err := json.Unmarshal([]byte(ins.AuthData), &ins.AuthInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return ins, nil
|
||||
}
|
||||
|
||||
// Get implements @Manager.GetByName
|
||||
|
@ -191,6 +191,11 @@ func (j *Job) Run(ctx job.Context, params job.Parameters) error {
|
||||
return preheatJobRunningError(errors.Errorf("preheat failed: %s", s))
|
||||
case provider.PreheatingStatusSuccess:
|
||||
// Finished
|
||||
// log the message if received message from provider.
|
||||
if s.Message != "" {
|
||||
myLogger.Infof("Preheat job finished, message from provider: \n%s", s.Message)
|
||||
}
|
||||
|
||||
return nil
|
||||
default:
|
||||
// do nothing, check again
|
||||
|
@ -16,12 +16,10 @@ package policy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
beego_orm "github.com/beego/beego/v2/client/orm"
|
||||
"github.com/beego/beego/v2/core/validation"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
@ -32,6 +30,9 @@ func init() {
|
||||
beego_orm.RegisterModel(&Schema{})
|
||||
}
|
||||
|
||||
// ScopeType represents the preheat scope type.
|
||||
type ScopeType = string
|
||||
|
||||
const (
|
||||
// Filters:
|
||||
// Repository : type=Repository value=name text (double star pattern used)
|
||||
@ -57,6 +58,11 @@ const (
|
||||
TriggerTypeScheduled TriggerType = "scheduled"
|
||||
// TriggerTypeEventBased represents the event_based trigger type
|
||||
TriggerTypeEventBased TriggerType = "event_based"
|
||||
|
||||
// ScopeTypeSinglePeer represents preheat image to single peer in p2p cluster.
|
||||
ScopeTypeSinglePeer ScopeType = "single_peer"
|
||||
// ScopeTypeAllPeers represents preheat image to all peers in p2p cluster.
|
||||
ScopeTypeAllPeers ScopeType = "all_peers"
|
||||
)
|
||||
|
||||
// Schema defines p2p preheat policy schema
|
||||
@ -72,8 +78,10 @@ type Schema struct {
|
||||
FiltersStr string `orm:"column(filters)" json:"-"`
|
||||
Trigger *Trigger `orm:"-" json:"trigger"`
|
||||
// Use JSON data format (query by trigger type should be supported)
|
||||
TriggerStr string `orm:"column(trigger)" json:"-"`
|
||||
Enabled bool `orm:"column(enabled)" json:"enabled"`
|
||||
TriggerStr string `orm:"column(trigger)" json:"-"`
|
||||
Enabled bool `orm:"column(enabled)" json:"enabled"`
|
||||
// Scope decides the preheat scope.
|
||||
Scope string `orm:"column(scope)" json:"scope"`
|
||||
CreatedAt time.Time `orm:"column(creation_time)" json:"creation_time"`
|
||||
UpdatedTime time.Time `orm:"column(update_time)" json:"update_time"`
|
||||
}
|
||||
@ -127,67 +135,15 @@ func (s *Schema) ValidatePreheatPolicy() error {
|
||||
WithMessage("invalid cron string for scheduled preheat: %s, error: %v", s.Trigger.Settings.Cron, err)
|
||||
}
|
||||
}
|
||||
|
||||
// validate preheat scope
|
||||
if s.Scope != "" && s.Scope != ScopeTypeSinglePeer && s.Scope != ScopeTypeAllPeers {
|
||||
return errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("invalid scope for preheat policy: %s", s.Scope)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Valid the policy
|
||||
func (s *Schema) Valid(v *validation.Validation) {
|
||||
if len(s.Name) == 0 {
|
||||
_ = v.SetError("name", "cannot be empty")
|
||||
}
|
||||
|
||||
// valid the filters
|
||||
for _, filter := range s.Filters {
|
||||
switch filter.Type {
|
||||
case FilterTypeRepository, FilterTypeTag, FilterTypeVulnerability:
|
||||
_, ok := filter.Value.(string)
|
||||
if !ok {
|
||||
_ = v.SetError("filters", "the type of filter value isn't string")
|
||||
break
|
||||
}
|
||||
case FilterTypeSignature:
|
||||
_, ok := filter.Value.(bool)
|
||||
if !ok {
|
||||
_ = v.SetError("filers", "the type of signature filter value isn't bool")
|
||||
break
|
||||
}
|
||||
case FilterTypeLabel:
|
||||
labels, ok := filter.Value.([]interface{})
|
||||
if !ok {
|
||||
_ = v.SetError("filters", "the type of label filter value isn't string slice")
|
||||
break
|
||||
}
|
||||
for _, label := range labels {
|
||||
_, ok := label.(string)
|
||||
if !ok {
|
||||
_ = v.SetError("filters", "the type of label filter value isn't string slice")
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
_ = v.SetError("filters", "invalid filter type")
|
||||
}
|
||||
}
|
||||
|
||||
// valid trigger
|
||||
if s.Trigger != nil {
|
||||
switch s.Trigger.Type {
|
||||
case TriggerTypeManual, TriggerTypeEventBased:
|
||||
case TriggerTypeScheduled:
|
||||
if len(s.Trigger.Settings.Cron) == 0 {
|
||||
_ = v.SetError("trigger", fmt.Sprintf("the cron string cannot be empty when the trigger type is %s", TriggerTypeScheduled))
|
||||
} else {
|
||||
_, err := utils.CronParser().Parse(s.Trigger.Settings.Cron)
|
||||
if err != nil {
|
||||
_ = v.SetError("trigger", fmt.Sprintf("invalid cron string for scheduled trigger: %s", s.Trigger.Settings.Cron))
|
||||
}
|
||||
}
|
||||
default:
|
||||
_ = v.SetError("trigger", "invalid trigger type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Encode encodes policy schema.
|
||||
func (s *Schema) Encode() error {
|
||||
if s.Filters != nil {
|
||||
|
@ -17,8 +17,6 @@ package policy
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/beego/beego/v2/core/validation"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
@ -66,92 +64,13 @@ func (p *PolicyTestSuite) TestValidatePreheatPolicy() {
|
||||
// valid cron string
|
||||
p.schema.Trigger.Settings.Cron = "0 0 0 1 1 *"
|
||||
p.NoError(p.schema.ValidatePreheatPolicy())
|
||||
}
|
||||
|
||||
// TestValid tests Valid method.
|
||||
func (p *PolicyTestSuite) TestValid() {
|
||||
// policy name is empty, should return error
|
||||
v := &validation.Validation{}
|
||||
p.schema.Valid(v)
|
||||
require.True(p.T(), v.HasErrors(), "no policy name should return one error")
|
||||
require.Contains(p.T(), v.Errors[0].Error(), "cannot be empty")
|
||||
|
||||
// policy with name but with error filter type
|
||||
p.schema.Name = "policy-test"
|
||||
p.schema.Filters = []*Filter{
|
||||
{
|
||||
Type: "invalid-type",
|
||||
},
|
||||
}
|
||||
v = &validation.Validation{}
|
||||
p.schema.Valid(v)
|
||||
require.True(p.T(), v.HasErrors(), "invalid filter type should return one error")
|
||||
require.Contains(p.T(), v.Errors[0].Error(), "invalid filter type")
|
||||
|
||||
filterCases := [][]*Filter{
|
||||
{
|
||||
{
|
||||
Type: FilterTypeSignature,
|
||||
Value: "invalid-value",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
{
|
||||
Type: FilterTypeTag,
|
||||
Value: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
Type: FilterTypeLabel,
|
||||
Value: "invalid-value",
|
||||
},
|
||||
},
|
||||
}
|
||||
// with valid filter type but with error value type
|
||||
for _, filters := range filterCases {
|
||||
p.schema.Filters = filters
|
||||
v = &validation.Validation{}
|
||||
p.schema.Valid(v)
|
||||
require.True(p.T(), v.HasErrors(), "invalid filter value type should return one error")
|
||||
}
|
||||
|
||||
// with valid filter but error trigger type
|
||||
p.schema.Filters = []*Filter{
|
||||
{
|
||||
Type: FilterTypeSignature,
|
||||
Value: true,
|
||||
},
|
||||
}
|
||||
p.schema.Trigger = &Trigger{
|
||||
Type: "invalid-type",
|
||||
}
|
||||
v = &validation.Validation{}
|
||||
p.schema.Valid(v)
|
||||
require.True(p.T(), v.HasErrors(), "invalid trigger type should return one error")
|
||||
require.Contains(p.T(), v.Errors[0].Error(), "invalid trigger type")
|
||||
|
||||
// with valid filter but error trigger value
|
||||
p.schema.Trigger = &Trigger{
|
||||
Type: TriggerTypeScheduled,
|
||||
}
|
||||
v = &validation.Validation{}
|
||||
p.schema.Valid(v)
|
||||
require.True(p.T(), v.HasErrors(), "invalid trigger value should return one error")
|
||||
require.Contains(p.T(), v.Errors[0].Error(), "the cron string cannot be empty")
|
||||
// with invalid cron
|
||||
p.schema.Trigger.Settings.Cron = "1111111111111"
|
||||
v = &validation.Validation{}
|
||||
p.schema.Valid(v)
|
||||
require.True(p.T(), v.HasErrors(), "invalid trigger value should return one error")
|
||||
require.Contains(p.T(), v.Errors[0].Error(), "invalid cron string for scheduled trigger")
|
||||
|
||||
// all is well
|
||||
p.schema.Trigger.Settings.Cron = "0/12 * * * * *"
|
||||
v = &validation.Validation{}
|
||||
p.schema.Valid(v)
|
||||
require.False(p.T(), v.HasErrors(), "should return nil error")
|
||||
// invalid preheat scope
|
||||
p.schema.Scope = "invalid scope"
|
||||
p.Error(p.schema.ValidatePreheatPolicy())
|
||||
// valid preheat scope
|
||||
p.schema.Scope = "single_peer"
|
||||
p.NoError(p.schema.ValidatePreheatPolicy())
|
||||
}
|
||||
|
||||
// TestDecode tests decode.
|
||||
@ -167,11 +86,14 @@ func (p *PolicyTestSuite) TestDecode() {
|
||||
Trigger: nil,
|
||||
TriggerStr: "{\"type\":\"event_based\",\"trigger_setting\":{\"cron\":\"\"}}",
|
||||
Enabled: false,
|
||||
Scope: "all_peers",
|
||||
}
|
||||
p.NoError(s.Decode())
|
||||
p.Len(s.Filters, 3)
|
||||
p.NotNil(s.Trigger)
|
||||
|
||||
p.Equal(ScopeTypeAllPeers, s.Scope)
|
||||
|
||||
// invalid filter or trigger
|
||||
s.FiltersStr = ""
|
||||
s.TriggerStr = "invalid"
|
||||
@ -210,8 +132,10 @@ func (p *PolicyTestSuite) TestEncode() {
|
||||
},
|
||||
TriggerStr: "",
|
||||
Enabled: false,
|
||||
Scope: "single_peer",
|
||||
}
|
||||
p.NoError(s.Encode())
|
||||
p.Equal(`[{"type":"repository","value":"**"},{"type":"tag","value":"**"},{"type":"label","value":"test"}]`, s.FiltersStr)
|
||||
p.Equal(`{"type":"event_based","trigger_setting":{}}`, s.TriggerStr)
|
||||
p.Equal(ScopeTypeSinglePeer, s.Scope)
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ func NewHTTPClient(insecure bool) *HTTPClient {
|
||||
// Get content from the url
|
||||
func (hc *HTTPClient) Get(url string, cred *auth.Credential, parmas map[string]string, options map[string]string) ([]byte, error) {
|
||||
bytes, err := hc.get(url, cred, parmas, options)
|
||||
logMsg := fmt.Sprintf("Get %s with cred=%v, params=%v, options=%v", url, cred, parmas, options)
|
||||
logMsg := fmt.Sprintf("Get %s with params=%v, options=%v", url, parmas, options)
|
||||
if err != nil {
|
||||
log.Errorf("%s: %s", logMsg, err)
|
||||
} else {
|
||||
|
@ -15,37 +15,139 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
common_http "github.com/goharbor/harbor/src/common/http"
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/pkg/p2p/preheat/models/provider"
|
||||
"github.com/goharbor/harbor/src/pkg/p2p/preheat/provider/auth"
|
||||
"github.com/goharbor/harbor/src/pkg/p2p/preheat/provider/client"
|
||||
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
const (
|
||||
healthCheckEndpoint = "/_ping"
|
||||
preheatEndpoint = "/preheats"
|
||||
preheatTaskEndpoint = "/preheats/{task_id}"
|
||||
dragonflyPending = "WAITING"
|
||||
dragonflyFailed = "FAILED"
|
||||
// dragonflyHealthPath is the health check path for dragonfly openapi.
|
||||
dragonflyHealthPath = "/healthy"
|
||||
|
||||
// dragonflyJobPath is the job path for dragonfly openapi.
|
||||
dragonflyJobPath = "/oapi/v1/jobs"
|
||||
)
|
||||
|
||||
type dragonflyPreheatCreateResp struct {
|
||||
ID string `json:"ID"`
|
||||
const (
|
||||
// dragonflyJobPendingState is the pending state of the job, which means
|
||||
// the job is waiting to be processed and running.
|
||||
dragonflyJobPendingState = "PENDING"
|
||||
|
||||
// dragonflyJobSuccessState is the success state of the job, which means
|
||||
// the job is processed successfully.
|
||||
dragonflyJobSuccessState = "SUCCESS"
|
||||
|
||||
// dragonflyJobFailureState is the failure state of the job, which means
|
||||
// the job is processed failed.
|
||||
dragonflyJobFailureState = "FAILURE"
|
||||
)
|
||||
|
||||
type dragonflyCreateJobRequest struct {
|
||||
// Type is the job type, support preheat.
|
||||
Type string `json:"type" binding:"required"`
|
||||
|
||||
// Args is the preheating args.
|
||||
Args dragonflyCreateJobRequestArgs `json:"args" binding:"omitempty"`
|
||||
|
||||
// SchedulerClusterIDs is the scheduler cluster ids for preheating.
|
||||
SchedulerClusterIDs []uint `json:"scheduler_cluster_ids" binding:"omitempty"`
|
||||
}
|
||||
|
||||
type dragonflyPreheatInfo struct {
|
||||
ID string `json:"ID"`
|
||||
StartTime string `json:"startTime,omitempty"`
|
||||
FinishTime string `json:"finishTime,omitempty"`
|
||||
ErrorMsg string `json:"errorMsg"`
|
||||
Status string
|
||||
type dragonflyCreateJobRequestArgs struct {
|
||||
// Type is the preheating type, support image and file.
|
||||
Type string `json:"type"`
|
||||
|
||||
// URL is the image url for preheating.
|
||||
URL string `json:"url"`
|
||||
|
||||
// Tag is the tag for preheating.
|
||||
Tag string `json:"tag"`
|
||||
|
||||
// FilteredQueryParams is the filtered query params for preheating.
|
||||
FilteredQueryParams string `json:"filtered_query_params"`
|
||||
|
||||
// Headers is the http headers for authentication.
|
||||
Headers map[string]string `json:"headers"`
|
||||
|
||||
// Scope is the scope for preheating, default is single_peer.
|
||||
Scope string `json:"scope"`
|
||||
|
||||
// BatchSize is the batch size for preheating all peers, default is 50.
|
||||
ConcurrentCount int64 `json:"concurrent_count"`
|
||||
|
||||
// Timeout is the timeout for preheating, default is 30 minutes.
|
||||
Timeout time.Duration `json:"timeout"`
|
||||
}
|
||||
|
||||
type dragonflyJobResponse struct {
|
||||
// ID is the job id.
|
||||
ID int `json:"id"`
|
||||
|
||||
// CreatedAt is the job created time.
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
|
||||
// UpdatedAt is the job updated time.
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
// State is the job state, support PENDING, SUCCESS, FAILURE.
|
||||
State string `json:"state"`
|
||||
|
||||
// Results is the job results.
|
||||
Result struct {
|
||||
|
||||
// JobStates is the job states, including each job state.
|
||||
JobStates []struct {
|
||||
|
||||
// Error is the job error message.
|
||||
Error string `json:"error"`
|
||||
|
||||
// Results is the job results.
|
||||
Results []struct {
|
||||
|
||||
// SuccessTasks is the success tasks.
|
||||
SuccessTasks []*struct {
|
||||
|
||||
// URL is the url of the task, which is the blob url.
|
||||
URL string `json:"url"`
|
||||
|
||||
// Hostname is the hostname of the task.
|
||||
Hostname string `json:"hostname"`
|
||||
|
||||
// IP is the ip of the task.
|
||||
IP string `json:"ip"`
|
||||
} `json:"success_tasks"`
|
||||
|
||||
// FailureTasks is the failure tasks.
|
||||
FailureTasks []*struct {
|
||||
|
||||
// URL is the url of the task, which is the blob url.
|
||||
URL string `json:"url"`
|
||||
|
||||
// Hostname is the hostname of the task.
|
||||
Hostname string `json:"hostname"`
|
||||
|
||||
// IP is the ip of the task.
|
||||
IP string `json:"ip"`
|
||||
|
||||
// Description is the failure description.
|
||||
Description string `json:"description"`
|
||||
} `json:"failure_tasks"`
|
||||
|
||||
// SchedulerClusterID is the scheduler cluster id.
|
||||
SchedulerClusterID uint `json:"scheduler_cluster_id"`
|
||||
} `json:"results"`
|
||||
} `json:"job_states"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
// DragonflyDriver implements the provider driver interface for Alibaba dragonfly.
|
||||
@ -59,10 +161,10 @@ func (dd *DragonflyDriver) Self() *Metadata {
|
||||
return &Metadata{
|
||||
ID: "dragonfly",
|
||||
Name: "Dragonfly",
|
||||
Icon: "https://raw.githubusercontent.com/alibaba/Dragonfly/master/docs/images/logo.png",
|
||||
Version: "0.10.1",
|
||||
Source: "https://github.com/alibaba/Dragonfly",
|
||||
Maintainers: []string{"Jin Zhang/taiyun.zj@alibaba-inc.com"},
|
||||
Icon: "https://raw.githubusercontent.com/dragonflyoss/Dragonfly2/master/docs/images/logo/dragonfly-linear.png",
|
||||
Version: "2.1.59",
|
||||
Source: "https://github.com/dragonflyoss/Dragonfly2",
|
||||
Maintainers: []string{"chlins.zhang@gmail.com", "gaius.qi@gmail.com"},
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,13 +174,13 @@ func (dd *DragonflyDriver) GetHealth() (*DriverStatus, error) {
|
||||
return nil, errors.New("missing instance metadata")
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s%s", strings.TrimSuffix(dd.instance.Endpoint, "/"), healthCheckEndpoint)
|
||||
url := fmt.Sprintf("%s%s", strings.TrimSuffix(dd.instance.Endpoint, "/"), dragonflyHealthPath)
|
||||
url, err := lib.ValidateHTTPURL(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = client.GetHTTPClient(dd.instance.Insecure).Get(url, dd.getCred(), nil, nil)
|
||||
if err != nil {
|
||||
|
||||
if _, err = client.GetHTTPClient(dd.instance.Insecure).Get(url, dd.getCred(), nil, nil); err != nil {
|
||||
// Unhealthy
|
||||
return nil, err
|
||||
}
|
||||
@ -99,97 +201,112 @@ func (dd *DragonflyDriver) Preheat(preheatingImage *PreheatImage) (*PreheatingSt
|
||||
return nil, errors.New("no image specified")
|
||||
}
|
||||
|
||||
taskStatus := provider.PreheatingStatusPending // default
|
||||
url := fmt.Sprintf("%s%s", strings.TrimSuffix(dd.instance.Endpoint, "/"), preheatEndpoint)
|
||||
bytes, err := client.GetHTTPClient(dd.instance.Insecure).Post(url, dd.getCred(), preheatingImage, nil)
|
||||
if err != nil {
|
||||
if httpErr, ok := err.(*common_http.Error); ok && httpErr.Code == http.StatusAlreadyReported {
|
||||
// If the resource was preheated already with empty task ID, we should set preheat status to success.
|
||||
// Otherwise later querying for the task
|
||||
taskStatus = provider.PreheatingStatusSuccess
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
// Construct the preheat job request by the given parameters of the preheating image .
|
||||
req := &dragonflyCreateJobRequest{
|
||||
Type: "preheat",
|
||||
// TODO: Support set SchedulerClusterIDs, FilteredQueryParam, ConcurrentCount and Timeout.
|
||||
Args: dragonflyCreateJobRequestArgs{
|
||||
Type: preheatingImage.Type,
|
||||
URL: preheatingImage.URL,
|
||||
Headers: headerToMapString(preheatingImage.Headers),
|
||||
Scope: preheatingImage.Scope,
|
||||
},
|
||||
}
|
||||
|
||||
result := &dragonflyPreheatCreateResp{}
|
||||
if err := json.Unmarshal(bytes, result); err != nil {
|
||||
url := fmt.Sprintf("%s%s", strings.TrimSuffix(dd.instance.Endpoint, "/"), dragonflyJobPath)
|
||||
data, err := client.GetHTTPClient(dd.instance.Insecure).Post(url, dd.getCred(), req, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := &dragonflyJobResponse{}
|
||||
if err := json.Unmarshal(data, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &PreheatingStatus{
|
||||
TaskID: result.ID,
|
||||
Status: taskStatus,
|
||||
TaskID: fmt.Sprintf("%d", resp.ID),
|
||||
Status: provider.PreheatingStatusPending,
|
||||
StartTime: resp.CreatedAt.Format(time.RFC3339),
|
||||
FinishTime: resp.UpdatedAt.Format(time.RFC3339),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CheckProgress implements @Driver.CheckProgress.
|
||||
func (dd *DragonflyDriver) CheckProgress(taskID string) (*PreheatingStatus, error) {
|
||||
status, err := dd.getProgressStatus(taskID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If preheat job already exists
|
||||
if strings.Contains(status.ErrorMsg, "preheat task already exists, id:") {
|
||||
if taskID, err = getTaskExistedFromErrMsg(status.ErrorMsg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if status, err = dd.getProgressStatus(taskID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if status.Status == dragonflyPending {
|
||||
status.Status = provider.PreheatingStatusPending
|
||||
} else if status.Status == dragonflyFailed {
|
||||
status.Status = provider.PreheatingStatusFail
|
||||
}
|
||||
|
||||
res := &PreheatingStatus{
|
||||
Status: status.Status,
|
||||
TaskID: taskID,
|
||||
}
|
||||
if status.StartTime != "" {
|
||||
res.StartTime = status.StartTime
|
||||
}
|
||||
if status.FinishTime != "" {
|
||||
res.FinishTime = status.FinishTime
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func getTaskExistedFromErrMsg(msg string) (string, error) {
|
||||
begin := strings.Index(msg, "preheat task already exists, id:") + 32
|
||||
end := strings.LastIndex(msg, "\"}")
|
||||
if end-begin <= 0 {
|
||||
return "", errors.Errorf("can't find existed task id by error msg:%s", msg)
|
||||
}
|
||||
return msg[begin:end], nil
|
||||
}
|
||||
|
||||
func (dd *DragonflyDriver) getProgressStatus(taskID string) (*dragonflyPreheatInfo, error) {
|
||||
if dd.instance == nil {
|
||||
return nil, errors.New("missing instance metadata")
|
||||
}
|
||||
|
||||
if len(taskID) == 0 {
|
||||
if taskID == "" {
|
||||
return nil, errors.New("no task ID")
|
||||
}
|
||||
|
||||
path := strings.Replace(preheatTaskEndpoint, "{task_id}", taskID, 1)
|
||||
url := fmt.Sprintf("%s%s", strings.TrimSuffix(dd.instance.Endpoint, "/"), path)
|
||||
bytes, err := client.GetHTTPClient(dd.instance.Insecure).Get(url, dd.getCred(), nil, nil)
|
||||
url := fmt.Sprintf("%s%s/%s", strings.TrimSuffix(dd.instance.Endpoint, "/"), dragonflyJobPath, taskID)
|
||||
data, err := client.GetHTTPClient(dd.instance.Insecure).Get(url, dd.getCred(), nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
status := &dragonflyPreheatInfo{}
|
||||
if err := json.Unmarshal(bytes, status); err != nil {
|
||||
resp := &dragonflyJobResponse{}
|
||||
if err := json.Unmarshal(data, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return status, nil
|
||||
|
||||
var (
|
||||
successMessage string
|
||||
errorMessage string
|
||||
)
|
||||
|
||||
var state string
|
||||
switch resp.State {
|
||||
case dragonflyJobPendingState:
|
||||
state = provider.PreheatingStatusRunning
|
||||
case dragonflyJobSuccessState:
|
||||
state = provider.PreheatingStatusSuccess
|
||||
|
||||
var buffer bytes.Buffer
|
||||
table := tablewriter.NewWriter(&buffer)
|
||||
table.SetHeader([]string{"Blob URL", "Hostname", "IP", "Cluster ID", "State", "Error Message"})
|
||||
for _, jobState := range resp.Result.JobStates {
|
||||
for _, result := range jobState.Results {
|
||||
// Write the success tasks records to the table.
|
||||
for _, successTask := range result.SuccessTasks {
|
||||
table.Append([]string{successTask.URL, successTask.Hostname, successTask.IP, fmt.Sprint(result.SchedulerClusterID), dragonflyJobSuccessState, ""})
|
||||
}
|
||||
|
||||
// Write the failure tasks records to the table.
|
||||
for _, failureTask := range result.FailureTasks {
|
||||
table.Append([]string{failureTask.URL, failureTask.Hostname, failureTask.IP, fmt.Sprint(result.SchedulerClusterID), dragonflyJobFailureState, failureTask.Description})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
table.Render()
|
||||
successMessage = buffer.String()
|
||||
case dragonflyJobFailureState:
|
||||
var errs errors.Errors
|
||||
state = provider.PreheatingStatusFail
|
||||
for _, jobState := range resp.Result.JobStates {
|
||||
errs = append(errs, errors.New(jobState.Error))
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
errorMessage = errs.Error()
|
||||
}
|
||||
default:
|
||||
state = provider.PreheatingStatusFail
|
||||
errorMessage = fmt.Sprintf("unknown state: %s", resp.State)
|
||||
}
|
||||
|
||||
return &PreheatingStatus{
|
||||
TaskID: fmt.Sprintf("%d", resp.ID),
|
||||
Status: state,
|
||||
Message: successMessage,
|
||||
Error: errorMessage,
|
||||
StartTime: resp.CreatedAt.Format(time.RFC3339),
|
||||
FinishTime: resp.UpdatedAt.Format(time.RFC3339),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (dd *DragonflyDriver) getCred() *auth.Credential {
|
||||
@ -198,3 +315,14 @@ func (dd *DragonflyDriver) getCred() *auth.Credential {
|
||||
Data: dd.instance.AuthInfo,
|
||||
}
|
||||
}
|
||||
|
||||
func headerToMapString(header map[string]interface{}) map[string]string {
|
||||
m := make(map[string]string)
|
||||
for k, v := range header {
|
||||
if s, ok := v.(string); ok {
|
||||
m[k] = s
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
@ -79,64 +79,41 @@ func (suite *DragonflyTestSuite) TestGetHealth() {
|
||||
|
||||
// TestPreheat tests Preheat method.
|
||||
func (suite *DragonflyTestSuite) TestPreheat() {
|
||||
// preheat first time
|
||||
st, err := suite.driver.Preheat(&PreheatImage{
|
||||
Type: "image",
|
||||
ImageName: "busybox",
|
||||
Tag: "latest",
|
||||
URL: "https://harbor.com",
|
||||
Digest: "sha256:f3c97e3bd1e27393eb853a5c90b1132f2cda84336d5ba5d100c720dc98524c82",
|
||||
Scope: "single_peer",
|
||||
})
|
||||
require.NoError(suite.T(), err, "preheat image")
|
||||
suite.Equal("dragonfly-id", st.TaskID, "preheat image result")
|
||||
|
||||
// preheat the same image second time
|
||||
st, err = suite.driver.Preheat(&PreheatImage{
|
||||
Type: "image",
|
||||
ImageName: "busybox",
|
||||
Tag: "latest",
|
||||
URL: "https://harbor.com",
|
||||
Digest: "sha256:f3c97e3bd1e27393eb853a5c90b1132f2cda84336d5ba5d100c720dc98524c82",
|
||||
})
|
||||
require.NoError(suite.T(), err, "preheat image")
|
||||
suite.Equal("", st.TaskID, "preheat image result")
|
||||
|
||||
// preheat image digest is empty
|
||||
st, err = suite.driver.Preheat(&PreheatImage{
|
||||
ImageName: "",
|
||||
})
|
||||
require.Error(suite.T(), err, "preheat image")
|
||||
suite.Equal(provider.PreheatingStatusPending, st.Status, "preheat status")
|
||||
suite.Equal("0", st.TaskID, "task id")
|
||||
suite.NotEmptyf(st.StartTime, "start time")
|
||||
suite.NotEmptyf(st.FinishTime, "finish time")
|
||||
}
|
||||
|
||||
// TestCheckProgress tests CheckProgress method.
|
||||
func (suite *DragonflyTestSuite) TestCheckProgress() {
|
||||
st, err := suite.driver.CheckProgress("dragonfly-id")
|
||||
require.NoError(suite.T(), err, "get preheat status")
|
||||
st, err := suite.driver.CheckProgress("1")
|
||||
require.NoError(suite.T(), err, "get image")
|
||||
suite.Equal(provider.PreheatingStatusRunning, st.Status, "preheat status")
|
||||
suite.Equal("1", st.TaskID, "task id")
|
||||
suite.NotEmptyf(st.StartTime, "start time")
|
||||
suite.NotEmptyf(st.FinishTime, "finish time")
|
||||
|
||||
st, err = suite.driver.CheckProgress("2")
|
||||
require.NoError(suite.T(), err, "get image")
|
||||
suite.Equal(provider.PreheatingStatusSuccess, st.Status, "preheat status")
|
||||
suite.Equal("2", st.TaskID, "task id")
|
||||
suite.NotEmptyf(st.StartTime, "start time")
|
||||
suite.NotEmptyf(st.FinishTime, "finish time")
|
||||
|
||||
// preheat job exit but returns no id
|
||||
st, err = suite.driver.CheckProgress("preheat-job-exist-with-no-id")
|
||||
require.Error(suite.T(), err, "get preheat status")
|
||||
|
||||
// preheat job exit returns id but get info with that failed
|
||||
st, err = suite.driver.CheckProgress("preheat-job-exist-with-id-1")
|
||||
require.Error(suite.T(), err, "get preheat status")
|
||||
|
||||
// preheat job normal failed
|
||||
st, err = suite.driver.CheckProgress("preheat-job-normal-failed")
|
||||
require.NoError(suite.T(), err, "get preheat status")
|
||||
st, err = suite.driver.CheckProgress("3")
|
||||
require.NoError(suite.T(), err, "get image")
|
||||
suite.Equal(provider.PreheatingStatusFail, st.Status, "preheat status")
|
||||
|
||||
// instance is empty
|
||||
testDriver := &DragonflyDriver{}
|
||||
st, err = testDriver.CheckProgress("")
|
||||
require.Error(suite.T(), err, "get preheat status")
|
||||
|
||||
// preheat job with no task id
|
||||
st, err = suite.driver.CheckProgress("")
|
||||
require.Error(suite.T(), err, "get preheat status")
|
||||
|
||||
// preheat job with err json response
|
||||
st, err = suite.driver.CheckProgress("preheat-job-err-body-json")
|
||||
require.Error(suite.T(), err, "get preheat status")
|
||||
suite.Equal("3", st.TaskID, "task id")
|
||||
suite.NotEmptyf(st.StartTime, "start time")
|
||||
suite.NotEmptyf(st.FinishTime, "finish time")
|
||||
}
|
||||
|
@ -77,6 +77,7 @@ type DriverStatus struct {
|
||||
type PreheatingStatus struct {
|
||||
TaskID string `json:"task_id"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
StartTime string `json:"start_time"`
|
||||
FinishTime string `json:"finish_time"`
|
||||
|
@ -16,10 +16,10 @@ package provider
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/p2p/preheat/models/notification"
|
||||
@ -32,126 +32,146 @@ var preheatMap = make(map[string]struct{})
|
||||
func MockDragonflyProvider() *httptest.Server {
|
||||
return httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.RequestURI {
|
||||
case healthCheckEndpoint:
|
||||
case dragonflyHealthPath:
|
||||
if r.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
case preheatEndpoint:
|
||||
case dragonflyJobPath:
|
||||
if r.Method != http.MethodPost {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(r.Body)
|
||||
var resp = &dragonflyJobResponse{
|
||||
ID: 0,
|
||||
State: dragonflyJobPendingState,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
bytes, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
image := &PreheatImage{}
|
||||
if err := json.Unmarshal(data, image); err != nil {
|
||||
if _, err := w.Write(bytes); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if image.ImageName == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
case fmt.Sprintf("%s/%s", dragonflyJobPath, "0"):
|
||||
if r.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := preheatMap[image.Digest]; ok {
|
||||
w.WriteHeader(http.StatusAlreadyReported)
|
||||
_, _ = w.Write([]byte(`{"ID":""}`))
|
||||
var resp = &dragonflyJobResponse{
|
||||
ID: 1,
|
||||
State: dragonflyJobSuccessState,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
bytes, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
preheatMap[image.Digest] = struct{}{}
|
||||
|
||||
if image.Type == "image" &&
|
||||
image.URL == "https://harbor.com" &&
|
||||
image.ImageName == "busybox" &&
|
||||
image.Tag == "latest" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(`{"ID":"dragonfly-id"}`))
|
||||
if _, err := w.Write(bytes); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
case fmt.Sprintf("%s/%s", dragonflyJobPath, "1"):
|
||||
if r.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
case strings.Replace(preheatTaskEndpoint, "{task_id}", "dragonfly-id", 1):
|
||||
var resp = &dragonflyJobResponse{
|
||||
ID: 1,
|
||||
State: dragonflyJobPendingState,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
bytes, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := w.Write(bytes); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
case fmt.Sprintf("%s/%s", dragonflyJobPath, "2"):
|
||||
if r.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
status := &dragonflyPreheatInfo{
|
||||
ID: "dragonfly-id",
|
||||
StartTime: time.Now().UTC().String(),
|
||||
FinishTime: time.Now().Add(5 * time.Minute).UTC().String(),
|
||||
Status: "SUCCESS",
|
||||
|
||||
var resp = &dragonflyJobResponse{
|
||||
ID: 2,
|
||||
State: dragonflyJobSuccessState,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
bytes, _ := json.Marshal(status)
|
||||
_, _ = w.Write(bytes)
|
||||
case strings.Replace(preheatTaskEndpoint, "{task_id}", "preheat-job-exist-with-no-id", 1):
|
||||
|
||||
bytes, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := w.Write(bytes); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
case fmt.Sprintf("%s/%s", dragonflyJobPath, "3"):
|
||||
if r.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
status := &dragonflyPreheatInfo{
|
||||
ID: "preheat-exist-with-no-id",
|
||||
StartTime: time.Now().UTC().String(),
|
||||
FinishTime: time.Now().Add(5 * time.Minute).UTC().String(),
|
||||
Status: "FAILED",
|
||||
ErrorMsg: "{\"Code\":208,\"Msg\":\"preheat task already exists, id:\"}",
|
||||
|
||||
var resp = &dragonflyJobResponse{
|
||||
ID: 3,
|
||||
State: dragonflyJobFailureState,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
bytes, _ := json.Marshal(status)
|
||||
_, _ = w.Write(bytes)
|
||||
case strings.Replace(preheatTaskEndpoint, "{task_id}", "preheat-job-normal-failed", 1):
|
||||
if r.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
|
||||
bytes, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
status := &dragonflyPreheatInfo{
|
||||
ID: "preheat-job-exist-with-id-1",
|
||||
StartTime: time.Now().UTC().String(),
|
||||
FinishTime: time.Now().Add(5 * time.Minute).UTC().String(),
|
||||
Status: "FAILED",
|
||||
ErrorMsg: "{\"Code\":208,\"Msg\":\"some msg\"}",
|
||||
}
|
||||
bytes, _ := json.Marshal(status)
|
||||
_, _ = w.Write(bytes)
|
||||
case strings.Replace(preheatTaskEndpoint, "{task_id}", "preheat-job-exist-with-id-1", 1):
|
||||
if r.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
|
||||
if _, err := w.Write(bytes); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
status := &dragonflyPreheatInfo{
|
||||
ID: "preheat-job-exist-with-id-1",
|
||||
StartTime: time.Now().UTC().String(),
|
||||
FinishTime: time.Now().Add(5 * time.Minute).UTC().String(),
|
||||
Status: "FAILED",
|
||||
ErrorMsg: "{\"Code\":208,\"Msg\":\"preheat task already exists, id:preheat-job-exist-with-id-1-1\"}",
|
||||
}
|
||||
bytes, _ := json.Marshal(status)
|
||||
_, _ = w.Write(bytes)
|
||||
case strings.Replace(preheatTaskEndpoint, "{task_id}", "preheat-job-exist-with-id-1-1", 1):
|
||||
if r.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
case strings.Replace(preheatTaskEndpoint, "{task_id}", "preheat-job-err-body-json", 1):
|
||||
if r.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
bodyStr := "\"err body\""
|
||||
_, _ = w.Write([]byte(bodyStr))
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
@ -45,6 +45,9 @@ type PreheatImage struct {
|
||||
|
||||
// Digest of the preheating image
|
||||
Digest string `json:"digest"`
|
||||
|
||||
// Scope indicates the preheat scope.
|
||||
Scope string `json:"scope,omitempty"`
|
||||
}
|
||||
|
||||
// FromJSON build preheating image from the given data.
|
||||
|
5103
src/portal/app-swagger-ui/package-lock.json
generated
5103
src/portal/app-swagger-ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -7,16 +7,16 @@
|
||||
"start": "webpack serve --open --config webpack.dev.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"css-loader": "^6.8.1",
|
||||
"style-loader": "^3.3.3",
|
||||
"swagger-ui": "5.9.1"
|
||||
"css-loader": "^6.11.0",
|
||||
"style-loader": "^3.3.4",
|
||||
"swagger-ui": "5.17.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"html-webpack-plugin": "^5.6.0",
|
||||
"webpack": "^5.94.0",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-dev-server": "^4.10.0"
|
||||
"webpack-dev-server": "^4.15.2"
|
||||
}
|
||||
}
|
||||
|
7623
src/portal/package-lock.json
generated
7623
src/portal/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -43,46 +43,46 @@
|
||||
"@ngx-translate/core": "15.0.0",
|
||||
"@ngx-translate/http-loader": "8.0.0",
|
||||
"cron-validator": "^1.3.1",
|
||||
"echarts": "^5.4.3",
|
||||
"echarts": "^5.5.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"ngx-clipboard": "^15.1.0",
|
||||
"ngx-cookie": "^6.0.1",
|
||||
"ngx-markdown": "16.0.0",
|
||||
"rxjs": "^7.4.0",
|
||||
"tslib": "^2.2.0",
|
||||
"zone.js": "~0.13.0"
|
||||
"tslib": "^2.7.0",
|
||||
"zone.js": "^0.13.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^16.2.12",
|
||||
"@angular-devkit/build-angular": "^16.2.16",
|
||||
"@angular-eslint/builder": "16.1.2",
|
||||
"@angular-eslint/eslint-plugin": "16.1.2",
|
||||
"@angular-eslint/eslint-plugin-template": "16.1.2",
|
||||
"@angular-eslint/schematics": "16.1.2",
|
||||
"@angular-eslint/template-parser": "16.1.2",
|
||||
"@angular/cli": "^16.2.9",
|
||||
"@angular/cli": "^16.2.16",
|
||||
"@angular/compiler-cli": "^16.2.9",
|
||||
"@cypress/schematic": "^2.5.0",
|
||||
"@types/express": "^4.17.12",
|
||||
"@cypress/schematic": "^2.5.2",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/jasmine": "~4.3.1",
|
||||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.6",
|
||||
"@typescript-eslint/parser": "^5.59.6",
|
||||
"@types/node": "^16.18.108",
|
||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||
"@typescript-eslint/parser": "^5.62.0",
|
||||
"cypress": "13.1.0",
|
||||
"eslint": "^8.41.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"express": "^4.19.2",
|
||||
"express": "^4.21.0",
|
||||
"https-proxy-agent": "^5.0.1",
|
||||
"jasmine-core": "~4.5.0",
|
||||
"jasmine-spec-reporter": "~7.0.0",
|
||||
"karma": "6.4.2",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
"karma-coverage": "^2.2.0",
|
||||
"karma-coverage": "^2.2.1",
|
||||
"karma-jasmine": "~4.0.1",
|
||||
"karma-jasmine-html-reporter": "^1.7.0",
|
||||
"ng-swagger-gen": "^1.8.1",
|
||||
"prettier": "^2.6.2",
|
||||
"prettier-eslint": "^14.0.2",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-eslint": "^14.1.0",
|
||||
"stylelint": "^14.16.1",
|
||||
"stylelint-config-prettier": "^9.0.5",
|
||||
"stylelint-config-prettier-scss": "^0.0.1",
|
||||
|
@ -432,6 +432,11 @@ export class DistributionSetupModalComponent implements OnInit, OnDestroy {
|
||||
this.checkBtnState = ClrLoadingState.LOADING;
|
||||
const instance: Instance = clone(this.model);
|
||||
instance.id = 0;
|
||||
if (instance.auth_mode !== AuthMode.NONE) {
|
||||
instance.auth_info = this.authData;
|
||||
} else {
|
||||
delete instance.auth_info;
|
||||
}
|
||||
this.distributionService
|
||||
.PingInstances({
|
||||
instance: this.handleInstance(instance),
|
||||
|
@ -223,6 +223,64 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="clr-form-control mt-05"
|
||||
[ngStyle]="{
|
||||
visibility:
|
||||
isSystemAdmin && enableProxyCache ? 'visible' : 'hidden'
|
||||
}">
|
||||
<label for="bandwidth" class="clr-control-label required">
|
||||
{{ 'PROJECT.BANDWIDTH' | translate }}
|
||||
<clr-tooltip>
|
||||
<clr-icon
|
||||
clrTooltipTrigger
|
||||
shape="info-circle"
|
||||
size="24"></clr-icon>
|
||||
<clr-tooltip-content
|
||||
clrPosition="bottom-left"
|
||||
clrSize="lg"
|
||||
*clrIfOpen>
|
||||
<span>{{
|
||||
'PROJECT.PROXY_CACHE_BANDWIDTH' | translate
|
||||
}}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
<div
|
||||
class="clr-control-container"
|
||||
[class.clr-error]="bandwidthError">
|
||||
<input
|
||||
type="number"
|
||||
id="bandwidth"
|
||||
[(ngModel)]="speedLimit"
|
||||
name="bandwidth"
|
||||
class="clr-input width-164 mr-10"
|
||||
placeholder="Enter bandwidth"
|
||||
autocomplete="off"
|
||||
(ngModelChange)="validateBandwidth()" />
|
||||
<clr-icon
|
||||
*ngIf="bandwidthError"
|
||||
class="clr-validate-icon"
|
||||
shape="exclamation-circle"></clr-icon>
|
||||
<div class="clr-select-wrapper mr-10">
|
||||
<select
|
||||
id="bandwidth_unit"
|
||||
name="bandwidth_unit"
|
||||
[(ngModel)]="selectedSpeedLimitUnit">
|
||||
<option
|
||||
*ngFor="let unit of speedUnits"
|
||||
[value]="unit.UNIT">
|
||||
{{ unit.UNIT }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<clr-control-error
|
||||
*ngIf="bandwidthError"
|
||||
class="tooltip-content">
|
||||
{{ 'PROJECT.SPEED_LIMIT_TIP' | translate }}
|
||||
</clr-control-error>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
@ -39,6 +39,8 @@ import { Project } from '../../../project/project';
|
||||
import {
|
||||
QuotaUnits,
|
||||
QuotaUnlimited,
|
||||
BandwidthUnit,
|
||||
KB_TO_MB,
|
||||
} from '../../../../shared/entities/shared.const';
|
||||
import { QuotaHardInterface } from '../../../../shared/services';
|
||||
import {
|
||||
@ -72,7 +74,17 @@ export class CreateProjectComponent
|
||||
storageDefaultLimit: number;
|
||||
storageDefaultLimitUnit: string;
|
||||
initVal: Project = new Project();
|
||||
|
||||
speedLimit: number = -1;
|
||||
speedLimitUnit: string = BandwidthUnit.KB;
|
||||
selectedSpeedLimitUnit: string = BandwidthUnit.KB;
|
||||
speedUnits = [
|
||||
{
|
||||
UNIT: BandwidthUnit.KB,
|
||||
},
|
||||
{
|
||||
UNIT: BandwidthUnit.MB,
|
||||
},
|
||||
];
|
||||
createProjectOpened: boolean;
|
||||
|
||||
hasChanged: boolean;
|
||||
@ -97,6 +109,9 @@ export class CreateProjectComponent
|
||||
supportedRegistryTypeQueryString: string =
|
||||
'type={docker-hub harbor azure-acr aws-ecr google-gcr quay docker-registry github-ghcr jfrog-artifactory}';
|
||||
|
||||
// **Added property for bandwidth error message**
|
||||
bandwidthError: string | null = null;
|
||||
|
||||
constructor(
|
||||
private projectService: ProjectService,
|
||||
private translateService: TranslateService,
|
||||
@ -297,7 +312,39 @@ export class CreateProjectComponent
|
||||
}
|
||||
}
|
||||
|
||||
// **Added method for validating bandwidth input**
|
||||
validateBandwidth(): void {
|
||||
const value = Number(this.speedLimit);
|
||||
if (
|
||||
isNaN(value) ||
|
||||
(!Number.isInteger(value) && value !== -1) ||
|
||||
(value <= 0 && value !== -1)
|
||||
) {
|
||||
this.bandwidthError =
|
||||
'Please enter -1 or an integer greater than 0.';
|
||||
} else {
|
||||
this.bandwidthError = null;
|
||||
}
|
||||
}
|
||||
convertSpeedValue(realSpeed: number): number {
|
||||
if (this.selectedSpeedLimitUnit == BandwidthUnit.MB) {
|
||||
return realSpeed * KB_TO_MB;
|
||||
} else {
|
||||
return realSpeed ? realSpeed : -1;
|
||||
}
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
// **Invoke bandwidth validation before submission**
|
||||
this.validateBandwidth();
|
||||
if (this.bandwidthError) {
|
||||
this.inlineAlert.showInlineError(this.bandwidthError);
|
||||
return;
|
||||
}
|
||||
|
||||
this.project.metadata.bandwidth = this.convertSpeedValue(
|
||||
this.speedLimit
|
||||
);
|
||||
if (this.isSubmitOnGoing) {
|
||||
return;
|
||||
}
|
||||
@ -315,6 +362,8 @@ export class CreateProjectComponent
|
||||
project_name: this.project.name,
|
||||
metadata: {
|
||||
public: this.project.metadata.public ? 'true' : 'false',
|
||||
proxy_speed_kb:
|
||||
this.project.metadata.bandwidth.toString(),
|
||||
},
|
||||
storage_limit: +storageByte,
|
||||
registry_id: registryId,
|
||||
@ -357,6 +406,8 @@ export class CreateProjectComponent
|
||||
this.inlineAlert.close();
|
||||
this.storageLimit = this.storageDefaultLimit;
|
||||
this.storageLimitUnit = this.storageDefaultLimitUnit;
|
||||
this.selectedSpeedLimitUnit = BandwidthUnit.KB;
|
||||
this.speedLimit = -1;
|
||||
}
|
||||
|
||||
public get isValid(): boolean {
|
||||
@ -365,7 +416,8 @@ export class CreateProjectComponent
|
||||
this.currentForm.valid &&
|
||||
!this.isSubmitOnGoing &&
|
||||
this.isNameValid &&
|
||||
!this.checkOnGoing
|
||||
!this.checkOnGoing &&
|
||||
!this.bandwidthError
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -79,6 +79,11 @@ export const ACTION_RESOURCE_I18N_MAP = {
|
||||
'notification-policy': 'ROBOT_ACCOUNT.NOTIFICATION_POLICY',
|
||||
quota: 'ROBOT_ACCOUNT.QUOTA',
|
||||
sbom: 'ROBOT_ACCOUNT.SBOM',
|
||||
robot: 'ROBOT_ACCOUNT.ROBOT',
|
||||
user: 'ROBOT_ACCOUNT.USER',
|
||||
'user-group': 'ROBOT_ACCOUNT.GROUP',
|
||||
'ldap-user': 'ROBOT_ACCOUNT.LDAPUSER',
|
||||
member: 'ROBOT_ACCOUNT.MEMBER',
|
||||
};
|
||||
|
||||
export function convertKey(key: string) {
|
||||
|
@ -457,6 +457,30 @@
|
||||
(inputvalue)="setCron($event)"></cron-selection>
|
||||
</div>
|
||||
</div>
|
||||
<!-- scope -->
|
||||
<div class="clr-form-control">
|
||||
<clr-select-container>
|
||||
<label class="clr-control-label width-6rem">
|
||||
{{ 'P2P_PROVIDER.SCOPE' | translate }}
|
||||
</label>
|
||||
<select
|
||||
class="width-380"
|
||||
[disabled]="loading"
|
||||
clrSelect
|
||||
name="scope"
|
||||
id="scope"
|
||||
[(ngModel)]="scope"
|
||||
#ngScope="ngModel">
|
||||
<option class="display-none" value=""></option>
|
||||
<option
|
||||
[selected]="policy.scope === item"
|
||||
*ngFor="let item of scopes"
|
||||
value="{{ item }}">
|
||||
{{ getScopeI18n(item) | translate }}
|
||||
</option>
|
||||
</select>
|
||||
</clr-select-container>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
<div class="mt-1 bottom-btn" *ngIf="!isEdit">
|
||||
|
@ -29,6 +29,8 @@ import {
|
||||
PROJECT_SEVERITY_LEVEL_MAP,
|
||||
TRIGGER,
|
||||
TRIGGER_I18N_MAP,
|
||||
SCOPE,
|
||||
SCOPE_I18N_MAP,
|
||||
} from '../p2p-provider.service';
|
||||
import { ProviderUnderProject } from '../../../../../../ng-swagger-gen/models/provider-under-project';
|
||||
import { AppConfigService } from '../../../../services/app-config.service';
|
||||
@ -73,6 +75,7 @@ export class AddP2pPolicyComponent implements OnInit, OnDestroy {
|
||||
severity: number;
|
||||
labels: string;
|
||||
triggerType: string = TRIGGER.MANUAL;
|
||||
scope: string = SCOPE.SINGLE_PEER;
|
||||
cron: string;
|
||||
@ViewChild('policyForm', { static: true }) currentForm: NgForm;
|
||||
loading: boolean = false;
|
||||
@ -96,6 +99,7 @@ export class AddP2pPolicyComponent implements OnInit, OnDestroy {
|
||||
TRIGGER.SCHEDULED,
|
||||
TRIGGER.EVENT_BASED,
|
||||
];
|
||||
scopes: string[] = [SCOPE.SINGLE_PEER, SCOPE.ALL_PEERS];
|
||||
enableContentTrust: boolean = false;
|
||||
private _nameSubject: Subject<string> = new Subject<string>();
|
||||
private _nameSubscription: Subscription;
|
||||
@ -198,6 +202,7 @@ export class AddP2pPolicyComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
this.currentForm.reset({
|
||||
triggerType: 'manual',
|
||||
scope: 'single_peer',
|
||||
severity: PROJECT_SEVERITY_LEVEL_MAP[this.projectSeverity],
|
||||
onlySignedImages: this.enableContentTrust,
|
||||
provider: this.policy.provider_id,
|
||||
@ -303,6 +308,7 @@ export class AddP2pPolicyComponent implements OnInit, OnDestroy {
|
||||
policy.trigger = JSON.stringify(trigger);
|
||||
this.loading = true;
|
||||
this.buttonStatus = ClrLoadingState.LOADING;
|
||||
policy.scope = this.scope ? this.scope : SCOPE.SINGLE_PEER;
|
||||
deleteEmptyKey(policy);
|
||||
if (isAdd) {
|
||||
policy.project_id = this.projectId;
|
||||
@ -404,6 +410,10 @@ export class AddP2pPolicyComponent implements OnInit, OnDestroy {
|
||||
return true;
|
||||
}
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (this.policy.scope != this.scope) {
|
||||
return true;
|
||||
}
|
||||
// eslint-disable-next-line eqeqeq
|
||||
return this.originCronForEdit != this.cron;
|
||||
}
|
||||
isSystemAdmin(): boolean {
|
||||
@ -417,6 +427,14 @@ export class AddP2pPolicyComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
getScopeI18n(scope): string {
|
||||
if (scope) {
|
||||
return SCOPE_I18N_MAP[scope];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
showCron(): boolean {
|
||||
if (this.triggerType) {
|
||||
return this.triggerType === TRIGGER.SCHEDULED;
|
||||
|
@ -77,6 +77,16 @@ export const TRIGGER_I18N_MAP = {
|
||||
'scheduled(paused)': 'JOB_SERVICE_DASHBOARD.SCHEDULE_PAUSED',
|
||||
};
|
||||
|
||||
export enum SCOPE {
|
||||
SINGLE_PEER = 'single_peer',
|
||||
ALL_PEERS = 'all_peers',
|
||||
}
|
||||
|
||||
export const SCOPE_I18N_MAP = {
|
||||
single_peer: 'P2P_PROVIDER.SCOPE_SINGLE_PEER',
|
||||
all_peers: 'P2P_PROVIDER.SCOPE_ALL_PEERS',
|
||||
};
|
||||
|
||||
export const TIME_OUT: number = 7000;
|
||||
|
||||
export const PROJECT_SEVERITY_LEVEL_MAP = {
|
||||
|
@ -490,6 +490,7 @@ export class PolicyComponent implements OnInit, OnDestroy {
|
||||
severity: this.addP2pPolicyComponent.severity,
|
||||
label: this.addP2pPolicyComponent.labels,
|
||||
triggerType: this.addP2pPolicyComponent.triggerType,
|
||||
scope: this.addP2pPolicyComponent.scope,
|
||||
});
|
||||
this.addP2pPolicyComponent.originPolicyForEdit = clone(
|
||||
this.selectedRow
|
||||
|
@ -35,6 +35,7 @@ export class Project {
|
||||
auto_scan: string | boolean;
|
||||
auto_sbom_generation: string | boolean;
|
||||
retention_id: number;
|
||||
bandwidth: number;
|
||||
};
|
||||
constructor() {
|
||||
this.metadata = <any>{};
|
||||
|
@ -389,3 +389,10 @@ export enum ScanTypes {
|
||||
SBOM = 'sbom',
|
||||
VULNERABILITY = 'vulnerability',
|
||||
}
|
||||
|
||||
export const KB_TO_MB: number = 1024;
|
||||
|
||||
export enum BandwidthUnit {
|
||||
MB = 'Mbps',
|
||||
KB = 'Kbps',
|
||||
}
|
||||
|
@ -246,6 +246,9 @@
|
||||
"TOGGLED_SUCCESS": "Projekt erfolgreich geändert.",
|
||||
"FAILED_TO_DELETE_PROJECT": "Das Projekt enthält Repositories, Replikations-Regeln oder Helm-Charts und kann daher nicht gelöscht werden.",
|
||||
"INLINE_HELP_PUBLIC": "Wenn ein Projekt öffentlich ist, hat jeder lesenden Zugriff auf die Repositories innerhalb des Projekts. Der Nutzer muss nicht \"docker login\" vor dem Pull eines Images durchführen.",
|
||||
"PROXY_CACHE_BANDWIDTH":"Set the maximum network bandwidth to pull image from upstream for proxy-cache. For unlimited bandwidth, please enter -1. ",
|
||||
"BANDWIDTH": "Bandwidth",
|
||||
"SPEED_LIMIT_TIP": "Please enter -1 or an integer greater than 0. ",
|
||||
"OF": "von",
|
||||
"COUNT_QUOTA": "Anzahlbeschränkung",
|
||||
"STORAGE_QUOTA": "Project quota limits",
|
||||
@ -423,7 +426,12 @@
|
||||
"SELECT_PERMISSIONS": "Select Permissions",
|
||||
"SELECT_SYSTEM_PERMISSIONS": "Select System Permissions",
|
||||
"SELECT_PROJECT_PERMISSIONS": "Select Project Permissions",
|
||||
"SYSTEM_PERMISSIONS": "System Permissions"
|
||||
"SYSTEM_PERMISSIONS": "System Permissions",
|
||||
"ROBOT": "Robot Account",
|
||||
"USER": "User",
|
||||
"LDAPUSER": "LDAP User",
|
||||
"GROUP": "User Group",
|
||||
"MEMBER": "Project Member"
|
||||
},
|
||||
"WEBHOOK": {
|
||||
"EDIT_BUTTON": "EDIT",
|
||||
@ -1625,6 +1633,9 @@
|
||||
"TRIGGER": "Trigger",
|
||||
"CREATED": "Erzeugt am",
|
||||
"DESCRIPTION": "Beschreibung",
|
||||
"SCOPE": "Umfang",
|
||||
"SCOPE_SINGLE_PEER": "Einzelner Peer",
|
||||
"SCOPE_ALL_PEERS": "Alle Peers",
|
||||
"NO_POLICY": "Keine Regelwerke",
|
||||
"ENABLED_POLICY_SUMMARY": "Soll das Regelwerk {{name}} aktiviert werden?",
|
||||
"DISABLED_POLICY_SUMMARY": "Soll das Regelwerk {{name}} deaktiviert werden?",
|
||||
|
@ -246,6 +246,9 @@
|
||||
"TOGGLED_SUCCESS": "Toggled project successfully.",
|
||||
"FAILED_TO_DELETE_PROJECT": "Project contains repositories or replication rules or helm-charts cannot be deleted.",
|
||||
"INLINE_HELP_PUBLIC": "When a project is set to public, anyone has read permission to the repositories under this project, and the user does not need to run \"docker login\" before pulling images under this project.",
|
||||
"PROXY_CACHE_BANDWIDTH":"Set the maximum network bandwidth to pull image from upstream for proxy-cache. For unlimited bandwidth, please enter -1. ",
|
||||
"BANDWIDTH": "Bandwidth",
|
||||
"SPEED_LIMIT_TIP": "Please enter -1 or an integer greater than 0. ",
|
||||
"OF": "of",
|
||||
"COUNT_QUOTA": "Count quota",
|
||||
"STORAGE_QUOTA": "Project quota limits",
|
||||
@ -423,7 +426,12 @@
|
||||
"SELECT_PERMISSIONS": "Select Permissions",
|
||||
"SELECT_SYSTEM_PERMISSIONS": "Select System Permissions",
|
||||
"SELECT_PROJECT_PERMISSIONS": "Select Project Permissions",
|
||||
"SYSTEM_PERMISSIONS": "System Permissions"
|
||||
"SYSTEM_PERMISSIONS": "System Permissions",
|
||||
"ROBOT": "Robot Account",
|
||||
"USER": "User",
|
||||
"LDAPUSER": "LDAP User",
|
||||
"GROUP": "User Group",
|
||||
"MEMBER": "Project Member"
|
||||
},
|
||||
"WEBHOOK": {
|
||||
"EDIT_BUTTON": "EDIT",
|
||||
@ -1628,6 +1636,9 @@
|
||||
"TRIGGER": "Trigger",
|
||||
"CREATED": "Creation Time",
|
||||
"DESCRIPTION": "Description",
|
||||
"SCOPE": "Scope",
|
||||
"SCOPE_SINGLE_PEER": "Single Peer",
|
||||
"SCOPE_ALL_PEERS": "All Peers",
|
||||
"NO_POLICY": "No policy",
|
||||
"ENABLED_POLICY_SUMMARY": "Do you want to enable policy {{name}}?",
|
||||
"DISABLED_POLICY_SUMMARY": "Do you want to deactivate policy {{name}}?",
|
||||
|
@ -247,6 +247,9 @@
|
||||
"TOGGLED_SUCCESS": "Proyecto alternado satisfactoriamente.",
|
||||
"FAILED_TO_DELETE_PROJECT": "Project contains repositories or replication rules or helm-charts cannot be deleted.",
|
||||
"INLINE_HELP_PUBLIC": "Cuando un proyecto se marca como público, todo el mundo tiene permisos de lectura sobre los repositorio de dicho proyecto, y no hace falta hacer \"docker login\" antes de subir imágenes a ellos.",
|
||||
"PROXY_CACHE_BANDWIDTH":"Set the maximum network bandwidth to pull image from upstream for proxy-cache. For unlimited bandwidth, please enter -1. ",
|
||||
"BANDWIDTH": "Bandwidth",
|
||||
"SPEED_LIMIT_TIP": "Please enter -1 or an integer greater than 0. ",
|
||||
"OF": "of",
|
||||
"COUNT_QUOTA": "Count quota",
|
||||
"STORAGE_QUOTA": "Project quota limits",
|
||||
@ -424,7 +427,12 @@
|
||||
"SELECT_PERMISSIONS": "Select Permissions",
|
||||
"SELECT_SYSTEM_PERMISSIONS": "Select System Permissions",
|
||||
"SELECT_PROJECT_PERMISSIONS": "Select Project Permissions",
|
||||
"SYSTEM_PERMISSIONS": "System Permissions"
|
||||
"SYSTEM_PERMISSIONS": "System Permissions",
|
||||
"ROBOT": "Robot Account",
|
||||
"USER": "User",
|
||||
"LDAPUSER": "LDAP User",
|
||||
"GROUP": "User Group",
|
||||
"MEMBER": "Project Member"
|
||||
},
|
||||
"WEBHOOK": {
|
||||
"EDIT_BUTTON": "EDIT",
|
||||
@ -1622,6 +1630,9 @@
|
||||
"TRIGGER": "Trigger",
|
||||
"CREATED": "Creation Time",
|
||||
"DESCRIPTION": "Description",
|
||||
"SCOPE": "Scope",
|
||||
"SCOPE_SINGLE_PEER": "Single Peer",
|
||||
"SCOPE_ALL_PEERS": "All Peers",
|
||||
"NO_POLICY": "No policy",
|
||||
"ENABLED_POLICY_SUMMARY": "Do you want to enable policy {{name}}?",
|
||||
"DISABLED_POLICY_SUMMARY": "Do you want to disable policy {{name}}?",
|
||||
|
@ -246,6 +246,9 @@
|
||||
"TOGGLED_SUCCESS": "Projet basculé avec succès.",
|
||||
"FAILED_TO_DELETE_PROJECT": "Le projet contient des dépôts, des règles de réplication ou des charts Helm et ne peut pas être supprimé.",
|
||||
"INLINE_HELP_PUBLIC": "Lorsqu'un projet est mis en public, n'importe qui a l'autorisation de lire les dépôts sous ce projet, et l'utilisateur n'a pas besoin d'exécuter \"docker login\" avant de prendre des images de ce projet.",
|
||||
"PROXY_CACHE_BANDWIDTH":"Set the maximum network bandwidth to pull image from upstream for proxy-cache. For unlimited bandwidth, please enter -1. ",
|
||||
"BANDWIDTH": "Bandwidth",
|
||||
"SPEED_LIMIT_TIP": "Please enter -1 or an integer greater than 0. ",
|
||||
"OF": "sur",
|
||||
"COUNT_QUOTA": "Quota de nombre",
|
||||
"STORAGE_QUOTA": "Quota du projet",
|
||||
@ -423,7 +426,12 @@
|
||||
"SELECT_PERMISSIONS": "Selectionner les permissions",
|
||||
"SELECT_SYSTEM_PERMISSIONS": "Selectionner les permissions système",
|
||||
"SELECT_PROJECT_PERMISSIONS": "Selectionner les permissions projet",
|
||||
"SYSTEM_PERMISSIONS": "Permissions système"
|
||||
"SYSTEM_PERMISSIONS": "Permissions système",
|
||||
"ROBOT": "Robot Account",
|
||||
"USER": "User",
|
||||
"LDAPUSER": "LDAP User",
|
||||
"GROUP": "User Group",
|
||||
"MEMBER": "Project Member"
|
||||
},
|
||||
"WEBHOOK": {
|
||||
"EDIT_BUTTON": "Éditer",
|
||||
@ -1625,6 +1633,9 @@
|
||||
"TRIGGER": "Déclencheur",
|
||||
"CREATED": "Date/Heure de création",
|
||||
"DESCRIPTION": "Description",
|
||||
"SCOPE": "Champ d'application",
|
||||
"SCOPE_SINGLE_PEER": "Pair unique",
|
||||
"SCOPE_ALL_PEERS": "Tous les pairs",
|
||||
"NO_POLICY": "Aucune stratégie",
|
||||
"ENABLED_POLICY_SUMMARY": "Voulez-vous activer la stratégie {{name}} ?",
|
||||
"DISABLED_POLICY_SUMMARY": "Voulez-vous désactiver la stratégie {{name}} ?",
|
||||
|
@ -246,6 +246,9 @@
|
||||
"TOGGLED_SUCCESS": "프로젝트가 성공적으로 전환됐습니다.",
|
||||
"FAILED_TO_DELETE_PROJECT": "프로젝트에 리포지토리 또는 복제 규칙이 포함되어 있거나 헬름 차트를 삭제할 수 없습니다.",
|
||||
"INLINE_HELP_PUBLIC": "프로젝트가 공개로 설정되면 누구나 이 프로젝트의 저장소에 대한 읽기 권한을 갖게 되며 사용자는 이 프로젝트에서 이미지를 가져오기 전에 \"docker login\"을 실행할 필요가 없습니다.",
|
||||
"PROXY_CACHE_BANDWIDTH":"Set the maximum network bandwidth to pull image from upstream for proxy-cache. For unlimited bandwidth, please enter -1. ",
|
||||
"BANDWIDTH": "Bandwidth",
|
||||
"SPEED_LIMIT_TIP": "Please enter -1 or an integer greater than 0. ",
|
||||
"OF": "of",
|
||||
"COUNT_QUOTA": "할당량 수",
|
||||
"STORAGE_QUOTA": "프로젝트 할당량 제한",
|
||||
@ -420,7 +423,12 @@
|
||||
"SELECT_PERMISSIONS": "권한 선택",
|
||||
"SELECT_SYSTEM_PERMISSIONS": "시스템 권한 선택",
|
||||
"SELECT_PROJECT_PERMISSIONS": "프로젝트 권한 선택",
|
||||
"SYSTEM_PERMISSIONS": "시스템 권한"
|
||||
"SYSTEM_PERMISSIONS": "시스템 권한",
|
||||
"ROBOT": "Robot Account",
|
||||
"USER": "User",
|
||||
"LDAPUSER": "LDAP User",
|
||||
"GROUP": "User Group",
|
||||
"MEMBER": "Project Member"
|
||||
},
|
||||
"WEBHOOK": {
|
||||
"EDIT_BUTTON": "편집",
|
||||
@ -1619,6 +1627,9 @@
|
||||
"TRIGGER": "트리거",
|
||||
"CREATED": "생성 시간",
|
||||
"DESCRIPTION": "설명",
|
||||
"SCOPE": "범위",
|
||||
"SCOPE_SINGLE_PEER": "싱글 피어",
|
||||
"SCOPE_ALL_PEERS": "모든 피어",
|
||||
"NO_POLICY": "정책 없음",
|
||||
"ENABLED_POLICY_SUMMARY": "정책{{name}}을 활성화하시겠습니까?",
|
||||
"DISABLED_POLICY_SUMMARY": "정책{{name}}을 비활성화하시겠습니까?",
|
||||
|
@ -245,6 +245,9 @@
|
||||
"TOGGLED_SUCCESS": "Projeto alterado com sucesso.",
|
||||
"FAILED_TO_DELETE_PROJECT": "Projeto não pode ser removido porque ainda possui recursos em repositórios, regras de replicação ou helm charts.",
|
||||
"INLINE_HELP_PUBLIC": "Quando o projeto é público, o acesso de leitura aos repositórios é liberado, incluindo usuários anônimos não autenticados. O usuário não precisa executar \"docker login\" para baixar imagens desse projeto.",
|
||||
"PROXY_CACHE_BANDWIDTH":"Set the maximum network bandwidth to pull image from upstream for proxy-cache. For unlimited bandwidth, please enter -1. ",
|
||||
"BANDWIDTH": "Bandwidth",
|
||||
"SPEED_LIMIT_TIP": "Please enter -1 or an integer greater than 0. ",
|
||||
"OF": "de",
|
||||
"COUNT_QUOTA": "Limite de quantidade",
|
||||
"STORAGE_QUOTA": "Project quota limits",
|
||||
@ -421,7 +424,12 @@
|
||||
"SELECT_PERMISSIONS": "Select Permissions",
|
||||
"SELECT_SYSTEM_PERMISSIONS": "Select System Permissions",
|
||||
"SELECT_PROJECT_PERMISSIONS": "Select Project Permissions",
|
||||
"SYSTEM_PERMISSIONS": "System Permissions"
|
||||
"SYSTEM_PERMISSIONS": "System Permissions",
|
||||
"ROBOT": "Robot Account",
|
||||
"USER": "User",
|
||||
"LDAPUSER": "LDAP User",
|
||||
"GROUP": "User Group",
|
||||
"MEMBER": "Project Member"
|
||||
},
|
||||
"GROUP": {
|
||||
"GROUP": "Grupo",
|
||||
@ -1622,6 +1630,9 @@
|
||||
"TRIGGER": "Disparo",
|
||||
"CREATED": "Criado em",
|
||||
"DESCRIPTION": "Descrição",
|
||||
"SCOPE": "Escopo",
|
||||
"SCOPE_SINGLE_PEER": "Par único",
|
||||
"SCOPE_ALL_PEERS": "Todos os pares",
|
||||
"NO_POLICY": "Nenhuma política",
|
||||
"ENABLED_POLICY_SUMMARY": "Gostaria de habilitar a política {{name}}?",
|
||||
"DISABLED_POLICY_SUMMARY": "Gostaria de desabilitar a política {{name}}?",
|
||||
|
@ -246,6 +246,9 @@
|
||||
"TOGGLED_SUCCESS": "Proje başarıyla değiştirildi.",
|
||||
"FAILED_TO_DELETE_PROJECT": "Proje havuzları veya çoğaltma kurallarını içeriyor veya helm tabloları silinemiyor.",
|
||||
"INLINE_HELP_PUBLIC": "Bir proje herkese açık olarak ayarlandığında, herkes bu proje altındaki depoları okuma iznine sahiptir ve kullanıcının bu proje altındaki imajları çekmeden önce \"docker login\" çalıştırması gerekmez.",
|
||||
"PROXY_CACHE_BANDWIDTH":"Set the maximum network bandwidth to pull image from upstream for proxy-cache. For unlimited bandwidth, please enter -1. ",
|
||||
"BANDWIDTH": "Bandwidth",
|
||||
"SPEED_LIMIT_TIP": "Please enter -1 or an integer greater than 0. ",
|
||||
"OF": "of",
|
||||
"COUNT_QUOTA": "Kota",
|
||||
"STORAGE_QUOTA": "Project quota limits",
|
||||
@ -423,7 +426,12 @@
|
||||
"SELECT_PERMISSIONS": "Select Permissions",
|
||||
"SELECT_SYSTEM_PERMISSIONS": "Select System Permissions",
|
||||
"SELECT_PROJECT_PERMISSIONS": "Select Project Permissions",
|
||||
"SYSTEM_PERMISSIONS": "System Permissions"
|
||||
"SYSTEM_PERMISSIONS": "System Permissions",
|
||||
"ROBOT": "Robot Account",
|
||||
"USER": "User",
|
||||
"LDAPUSER": "LDAP User",
|
||||
"GROUP": "User Group",
|
||||
"MEMBER": "Project Member"
|
||||
},
|
||||
"WEBHOOK": {
|
||||
"EDIT_BUTTON": "DÜZENLE",
|
||||
@ -1625,6 +1633,9 @@
|
||||
"TRIGGER": "Trigger",
|
||||
"CREATED": "Creation Time",
|
||||
"DESCRIPTION": "Description",
|
||||
"SCOPE": "Scope",
|
||||
"SCOPE_SINGLE_PEER": "Single Peer",
|
||||
"SCOPE_ALL_PEERS": "All Peers",
|
||||
"NO_POLICY": "No policy",
|
||||
"ENABLED_POLICY_SUMMARY": "Do you want to enable policy {{name}}?",
|
||||
"DISABLED_POLICY_SUMMARY": "Do you want to disable policy {{name}}?",
|
||||
|
@ -246,6 +246,9 @@
|
||||
"TOGGLED_SUCCESS": "切换状态成功。",
|
||||
"FAILED_TO_DELETE_PROJECT": "项目包含镜像仓库或复制规则或Helm Charts,无法删除。",
|
||||
"INLINE_HELP_PUBLIC": "当项目设为公开后,任何人都有此项目下镜像的读权限。命令行用户不需要“docker login”就可以拉取此项目下的镜像。",
|
||||
"PROXY_CACHE_BANDWIDTH":"Set the maximum network bandwidth to pull image from upstream for proxy-cache. For unlimited bandwidth, please enter -1. ",
|
||||
"BANDWIDTH": "Bandwidth",
|
||||
"SPEED_LIMIT_TIP": "Please enter -1 or an integer greater than 0. ",
|
||||
"COUNT_QUOTA": "存储数量",
|
||||
"STORAGE_QUOTA": "项目配额限制",
|
||||
"COUNT_QUOTA_TIP": "请输入一个'1' ~ '100000000'之间的整数, '-1'表示不设置上限。",
|
||||
@ -421,7 +424,12 @@
|
||||
"SELECT_PERMISSIONS": "选择权限",
|
||||
"SELECT_SYSTEM_PERMISSIONS": "选择系统权限",
|
||||
"SELECT_PROJECT_PERMISSIONS": "选择项目权限",
|
||||
"SYSTEM_PERMISSIONS": "系统权限"
|
||||
"SYSTEM_PERMISSIONS": "系统权限",
|
||||
"ROBOT": "机器人账户",
|
||||
"USER": "用户",
|
||||
"LDAPUSER": "LDAP 用户",
|
||||
"GROUP": "用户组",
|
||||
"MEMBER": "项目成员"
|
||||
},
|
||||
"WEBHOOK": {
|
||||
"EDIT_BUTTON": "编辑",
|
||||
@ -1624,6 +1632,9 @@
|
||||
"TRIGGER": "触发器",
|
||||
"CREATED": "创建时间",
|
||||
"DESCRIPTION": "描述",
|
||||
"SCOPE": "范围",
|
||||
"SCOPE_SINGLE_PEER": "单节点",
|
||||
"SCOPE_ALL_PEERS": "全节点",
|
||||
"NO_POLICY": "暂无记录",
|
||||
"ENABLED_POLICY_SUMMARY": "是否启用策略 {{name}}?",
|
||||
"DISABLED_POLICY_SUMMARY": "是否禁用策略 {{name}}?",
|
||||
|
@ -245,6 +245,9 @@
|
||||
"TOGGLED_SUCCESS": "專案切換成功。",
|
||||
"FAILED_TO_DELETE_PROJECT": "由於專案包含儲存庫、複製規則或 helm-charts,無法刪除。",
|
||||
"INLINE_HELP_PUBLIC": "當專案設為公開時,任何人都可讀取此專案下的儲存庫,使用者不需執行 \"docker login\" 即可拉取此專案下的映像檔。",
|
||||
"PROXY_CACHE_BANDWIDTH":"Set the maximum network bandwidth to pull image from upstream for proxy-cache. For unlimited bandwidth, please enter -1. ",
|
||||
"BANDWIDTH": "Bandwidth",
|
||||
"SPEED_LIMIT_TIP": "Please enter -1 or an integer greater than 0. ",
|
||||
"OF": "共計",
|
||||
"COUNT_QUOTA": "數量配額",
|
||||
"STORAGE_QUOTA": "儲存配額限制",
|
||||
@ -422,7 +425,12 @@
|
||||
"SELECT_PERMISSIONS": "Select Permissions",
|
||||
"SELECT_SYSTEM_PERMISSIONS": "Select System Permissions",
|
||||
"SELECT_PROJECT_PERMISSIONS": "Select Project Permissions",
|
||||
"SYSTEM_PERMISSIONS": "System Permissions"
|
||||
"SYSTEM_PERMISSIONS": "System Permissions",
|
||||
"ROBOT": "Robot Account",
|
||||
"USER": "User",
|
||||
"LDAPUSER": "LDAP User",
|
||||
"GROUP": "User Group",
|
||||
"MEMBER": "Project Member"
|
||||
},
|
||||
"WEBHOOK": {
|
||||
"EDIT_BUTTON": "編輯",
|
||||
@ -1620,6 +1628,9 @@
|
||||
"TRIGGER": "觸發器",
|
||||
"CREATED": "建立時間",
|
||||
"DESCRIPTION": "描述",
|
||||
"SCOPE": "範圍",
|
||||
"SCOPE_SINGLE_PEER": "單節點",
|
||||
"SCOPE_ALL_PEERS": "全節點",
|
||||
"NO_POLICY": "無原則",
|
||||
"ENABLED_POLICY_SUMMARY": "您是否要啟用原則 {{name}}?",
|
||||
"DISABLED_POLICY_SUMMARY": "您是否要停用原則 {{name}}?",
|
||||
|
@ -71,11 +71,12 @@ func (p *permissionsAPI) GetPermissions(ctx context.Context, _ permissions.GetPe
|
||||
return p.SendError(ctx, errors.ForbiddenError(errors.New("only admins(system and project) can access permissions")))
|
||||
}
|
||||
|
||||
provider := rbac.GetPermissionProvider()
|
||||
sysPermissions := make([]*types.Policy, 0)
|
||||
proPermissions := rbac.PoliciesMap["Project"]
|
||||
proPermissions := provider.GetPermissions(rbac.ScopeProject)
|
||||
if isSystemAdmin {
|
||||
// project admin cannot see the system level permissions
|
||||
sysPermissions = rbac.PoliciesMap["System"]
|
||||
sysPermissions = provider.GetPermissions(rbac.ScopeSystem)
|
||||
}
|
||||
|
||||
return permissions.NewGetPermissionsOK().WithPayload(p.convertPermissions(sysPermissions, proPermissions))
|
||||
|
@ -483,6 +483,7 @@ func convertPolicyToPayload(policy *policy.Schema) (*models.PreheatPolicy, error
|
||||
ProjectID: policy.ProjectID,
|
||||
ProviderID: policy.ProviderID,
|
||||
Trigger: policy.TriggerStr,
|
||||
Scope: policy.Scope,
|
||||
UpdateTime: strfmt.DateTime(policy.UpdatedTime),
|
||||
}, nil
|
||||
}
|
||||
@ -511,6 +512,7 @@ func convertParamPolicyToModelPolicy(model *models.PreheatPolicy) (*policy.Schem
|
||||
FiltersStr: model.Filters,
|
||||
TriggerStr: model.Trigger,
|
||||
Enabled: model.Enabled,
|
||||
Scope: model.Scope,
|
||||
CreatedAt: time.Time(model.CreationTime),
|
||||
UpdatedTime: time.Time(model.UpdateTime),
|
||||
}, nil
|
||||
@ -563,6 +565,7 @@ func convertParamInstanceToModelInstance(model *models.Instance) (*instanceModel
|
||||
|
||||
return &instanceModel.Instance{
|
||||
AuthData: string(authData),
|
||||
AuthInfo: model.AuthInfo,
|
||||
AuthMode: model.AuthMode,
|
||||
Default: model.Default,
|
||||
Description: model.Description,
|
||||
|
@ -39,7 +39,7 @@ func Test_convertProvidersToFrontend(t *testing.T) {
|
||||
{"",
|
||||
backend,
|
||||
[]*models.Metadata{
|
||||
{ID: "dragonfly", Icon: "https://raw.githubusercontent.com/alibaba/Dragonfly/master/docs/images/logo.png", Maintainers: []string{"Jin Zhang/taiyun.zj@alibaba-inc.com"}, Name: "Dragonfly", Source: "https://github.com/alibaba/Dragonfly", Version: "0.10.1"},
|
||||
{ID: "dragonfly", Icon: "https://raw.githubusercontent.com/dragonflyoss/Dragonfly2/master/docs/images/logo/dragonfly-linear.png", Maintainers: []string{"chlins.zhang@gmail.com", "gaius.qi@gmail.com"}, Name: "Dragonfly", Source: "https://github.com/dragonflyoss/Dragonfly2", Version: "2.1.59"},
|
||||
{Icon: "https://github.com/uber/kraken/blob/master/assets/kraken-logo-color.svg", ID: "kraken", Maintainers: []string{"mmpei/peimingming@corp.netease.com"}, Name: "Kraken", Source: "https://github.com/uber/kraken", Version: "0.1.3"},
|
||||
},
|
||||
},
|
||||
@ -79,6 +79,7 @@ func Test_convertPolicyToPayload(t *testing.T) {
|
||||
Trigger: nil,
|
||||
TriggerStr: "",
|
||||
Enabled: false,
|
||||
Scope: "all_peers",
|
||||
CreatedAt: time.Time{},
|
||||
UpdatedTime: time.Time{},
|
||||
},
|
||||
@ -92,6 +93,7 @@ func Test_convertPolicyToPayload(t *testing.T) {
|
||||
ProjectID: 0,
|
||||
ProviderID: 0,
|
||||
Trigger: "",
|
||||
Scope: "all_peers",
|
||||
UpdateTime: strfmt.DateTime{},
|
||||
},
|
||||
},
|
||||
@ -141,6 +143,7 @@ func Test_convertParamPolicyToModelPolicy(t *testing.T) {
|
||||
ProjectID: 0,
|
||||
ProviderID: 0,
|
||||
Trigger: "",
|
||||
Scope: "single_peer",
|
||||
UpdateTime: strfmt.DateTime{},
|
||||
},
|
||||
expect: &policy.Schema{
|
||||
@ -154,6 +157,7 @@ func Test_convertParamPolicyToModelPolicy(t *testing.T) {
|
||||
Trigger: nil,
|
||||
TriggerStr: "",
|
||||
Enabled: false,
|
||||
Scope: "single_peer",
|
||||
CreatedAt: time.Time{},
|
||||
UpdatedTime: time.Time{},
|
||||
},
|
||||
@ -284,6 +288,7 @@ func Test_convertParamInstanceToModelInstance(t *testing.T) {
|
||||
Endpoint: "https://example.com",
|
||||
AuthMode: "none",
|
||||
AuthData: `{"name":"harbor"}`,
|
||||
AuthInfo: map[string]string{"name": "harbor"},
|
||||
Status: "Unknown",
|
||||
Default: true,
|
||||
Insecure: true,
|
||||
|
@ -31,8 +31,10 @@ import (
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/controller/robot"
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
"github.com/goharbor/harbor/src/lib/config"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
pkg "github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/handler/model"
|
||||
@ -60,12 +62,23 @@ func (rAPI *robotAPI) CreateRobot(ctx context.Context, params operation.CreateRo
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
|
||||
if err := rAPI.requireAccess(ctx, params.Robot.Level, params.Robot.Permissions[0].Namespace, rbac.ActionCreate); err != nil {
|
||||
sc, err := rAPI.GetSecurityContext(ctx)
|
||||
if err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
|
||||
sc, err := rAPI.GetSecurityContext(ctx)
|
||||
if err != nil {
|
||||
r := &robot.Robot{
|
||||
Robot: pkg.Robot{
|
||||
Name: params.Robot.Name,
|
||||
Description: params.Robot.Description,
|
||||
Duration: params.Robot.Duration,
|
||||
Visible: true,
|
||||
},
|
||||
Level: params.Robot.Level,
|
||||
ProjectNameOrID: params.Robot.Permissions[0].Namespace,
|
||||
}
|
||||
|
||||
if err := rAPI.requireAccess(ctx, r, rbac.ActionCreate); err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
|
||||
@ -78,23 +91,36 @@ func (rAPI *robotAPI) CreateRobot(ctx context.Context, params operation.CreateRo
|
||||
default:
|
||||
return rAPI.SendError(ctx, errors.New(nil).WithMessage("invalid security context"))
|
||||
}
|
||||
|
||||
r := &robot.Robot{
|
||||
Robot: pkg.Robot{
|
||||
Name: params.Robot.Name,
|
||||
Description: params.Robot.Description,
|
||||
Duration: params.Robot.Duration,
|
||||
Visible: true,
|
||||
CreatorRef: creatorRef,
|
||||
CreatorType: sc.Name(),
|
||||
},
|
||||
Level: params.Robot.Level,
|
||||
}
|
||||
r.CreatorType = sc.Name()
|
||||
r.CreatorRef = creatorRef
|
||||
|
||||
if err := lib.JSONCopy(&r.Permissions, params.Robot.Permissions); err != nil {
|
||||
log.Warningf("failed to call JSONCopy on robot permission when CreateRobot, error: %v", err)
|
||||
}
|
||||
|
||||
if err := robot.SetProject(ctx, r); err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
|
||||
if _, ok := sc.(*robotSc.SecurityContext); ok {
|
||||
creatorRobots, err := rAPI.robotCtl.List(ctx, q.New(q.KeyWords{
|
||||
"name": strings.TrimPrefix(sc.GetUsername(), config.RobotPrefix(ctx)),
|
||||
"project_id": r.ProjectID,
|
||||
}), &robot.Option{
|
||||
WithPermission: true,
|
||||
})
|
||||
if err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
if len(creatorRobots) == 0 {
|
||||
return rAPI.SendError(ctx, errors.DeniedError(nil))
|
||||
}
|
||||
|
||||
if !isValidPermissionScope(params.Robot.Permissions, creatorRobots[0].Permissions) {
|
||||
return rAPI.SendError(ctx, errors.New(nil).WithMessage("permission scope is invalid. It must be equal to or more restrictive than the creator robot's permissions: %s", creatorRobots[0].Name).WithCode(errors.DENIED))
|
||||
}
|
||||
}
|
||||
|
||||
rid, pwd, err := rAPI.robotCtl.Create(ctx, r)
|
||||
if err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
@ -125,7 +151,7 @@ func (rAPI *robotAPI) DeleteRobot(ctx context.Context, params operation.DeleteRo
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
|
||||
if err := rAPI.requireAccess(ctx, r.Level, r.ProjectID, rbac.ActionDelete); err != nil {
|
||||
if err := rAPI.requireAccess(ctx, r, rbac.ActionDelete); err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
|
||||
@ -174,7 +200,11 @@ func (rAPI *robotAPI) ListRobot(ctx context.Context, params operation.ListRobotP
|
||||
}
|
||||
query.Keywords["Visible"] = true
|
||||
|
||||
if err := rAPI.requireAccess(ctx, level, projectID, rbac.ActionList); err != nil {
|
||||
r := &robot.Robot{
|
||||
ProjectNameOrID: projectID,
|
||||
Level: level,
|
||||
}
|
||||
if err := rAPI.requireAccess(ctx, r, rbac.ActionList); err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
|
||||
@ -212,7 +242,7 @@ func (rAPI *robotAPI) GetRobotByID(ctx context.Context, params operation.GetRobo
|
||||
if err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
if err := rAPI.requireAccess(ctx, r.Level, r.ProjectID, rbac.ActionRead); err != nil {
|
||||
if err := rAPI.requireAccess(ctx, r, rbac.ActionRead); err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
|
||||
@ -253,7 +283,7 @@ func (rAPI *robotAPI) RefreshSec(ctx context.Context, params operation.RefreshSe
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
|
||||
if err := rAPI.requireAccess(ctx, r.Level, r.ProjectID, rbac.ActionUpdate); err != nil {
|
||||
if err := rAPI.requireAccess(ctx, r, rbac.ActionUpdate); err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
|
||||
@ -282,12 +312,21 @@ func (rAPI *robotAPI) RefreshSec(ctx context.Context, params operation.RefreshSe
|
||||
return operation.NewRefreshSecOK().WithPayload(robotSec)
|
||||
}
|
||||
|
||||
func (rAPI *robotAPI) requireAccess(ctx context.Context, level string, projectIDOrName interface{}, action rbac.Action) error {
|
||||
if level == robot.LEVELSYSTEM {
|
||||
func (rAPI *robotAPI) requireAccess(ctx context.Context, r *robot.Robot, action rbac.Action) error {
|
||||
if r.Level == robot.LEVELSYSTEM {
|
||||
return rAPI.RequireSystemAccess(ctx, action, rbac.ResourceRobot)
|
||||
} else if level == robot.LEVELPROJECT {
|
||||
return rAPI.RequireProjectAccess(ctx, projectIDOrName, action, rbac.ResourceRobot)
|
||||
} else if r.Level == robot.LEVELPROJECT {
|
||||
var ns interface{}
|
||||
if r.ProjectNameOrID != nil {
|
||||
ns = r.ProjectNameOrID
|
||||
} else if r.ProjectID > 0 {
|
||||
ns = r.ProjectID
|
||||
} else if r.ProjectName != "" {
|
||||
ns = r.ProjectName
|
||||
}
|
||||
return rAPI.RequireProjectAccess(ctx, ns, action, rbac.ResourceRobot)
|
||||
}
|
||||
|
||||
return errors.ForbiddenError(nil)
|
||||
}
|
||||
|
||||
@ -316,17 +355,18 @@ func (rAPI *robotAPI) validate(d int64, level string, permissions []*models.Robo
|
||||
return errors.New(nil).WithMessage("bad request permission").WithCode(errors.BadRequestCode)
|
||||
}
|
||||
|
||||
provider := rbac.GetPermissionProvider()
|
||||
// to validate the access scope
|
||||
for _, perm := range permissions {
|
||||
if perm.Kind == robot.LEVELSYSTEM {
|
||||
polices := rbac.PoliciesMap["System"]
|
||||
polices := provider.GetPermissions(rbac.ScopeSystem)
|
||||
for _, acc := range perm.Access {
|
||||
if !containsAccess(polices, acc) {
|
||||
return errors.New(nil).WithMessage("bad request permission: %s:%s", acc.Resource, acc.Action).WithCode(errors.BadRequestCode)
|
||||
}
|
||||
}
|
||||
} else if perm.Kind == robot.LEVELPROJECT {
|
||||
polices := rbac.PoliciesMap["Project"]
|
||||
polices := provider.GetPermissions(rbac.ScopeProject)
|
||||
for _, acc := range perm.Access {
|
||||
if !containsAccess(polices, acc) {
|
||||
return errors.New(nil).WithMessage("bad request permission: %s:%s", acc.Resource, acc.Action).WithCode(errors.BadRequestCode)
|
||||
@ -356,7 +396,8 @@ func (rAPI *robotAPI) updateV2Robot(ctx context.Context, params operation.Update
|
||||
return errors.BadRequestError(nil).WithMessage("cannot update the project id of robot")
|
||||
}
|
||||
}
|
||||
if err := rAPI.requireAccess(ctx, params.Robot.Level, params.Robot.Permissions[0].Namespace, rbac.ActionUpdate); err != nil {
|
||||
r.ProjectNameOrID = params.Robot.Permissions[0].Namespace
|
||||
if err := rAPI.requireAccess(ctx, r, rbac.ActionUpdate); err != nil {
|
||||
return err
|
||||
}
|
||||
if params.Robot.Level != r.Level || params.Robot.Name != r.Name {
|
||||
@ -380,6 +421,42 @@ func (rAPI *robotAPI) updateV2Robot(ctx context.Context, params operation.Update
|
||||
}
|
||||
}
|
||||
|
||||
creatorRobot, err := rAPI.robotCtl.Get(ctx, r.CreatorRef, &robot.Option{
|
||||
WithPermission: true,
|
||||
})
|
||||
if err != nil && !errors.IsErr(err, errors.NotFoundCode) {
|
||||
return err
|
||||
}
|
||||
|
||||
// for nested robot only
|
||||
if creatorRobot != nil && r.CreatorType == "robot" {
|
||||
sc, err := rAPI.GetSecurityContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := sc.(*robotSc.SecurityContext); ok {
|
||||
scRobots, err := rAPI.robotCtl.List(ctx, q.New(q.KeyWords{
|
||||
"name": strings.TrimPrefix(sc.GetUsername(), config.RobotPrefix(ctx)),
|
||||
"project_id": r.ProjectID,
|
||||
}), &robot.Option{
|
||||
WithPermission: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(scRobots) == 0 {
|
||||
return errors.DeniedError(nil)
|
||||
}
|
||||
if scRobots[0].ID != creatorRobot.ID && scRobots[0].ID != r.ID {
|
||||
return errors.New(nil).WithMessage("as for a nested robot account, only person who has the right permission or the creator robot or nested robot itself has the permission to update").WithCode(errors.DENIED)
|
||||
}
|
||||
}
|
||||
|
||||
if !isValidPermissionScope(params.Robot.Permissions, creatorRobot.Permissions) {
|
||||
return errors.New(nil).WithMessage("permission scope is invalid. It must be equal to or more restrictive than the creator robot's permissions: %s", creatorRobot.Name).WithCode(errors.DENIED)
|
||||
}
|
||||
}
|
||||
|
||||
if err := rAPI.robotCtl.Update(ctx, r, &robot.Option{
|
||||
WithPermission: true,
|
||||
}); err != nil {
|
||||
@ -414,3 +491,39 @@ func containsAccess(policies []*types.Policy, item *models.Access) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isValidPermissionScope checks if permission slice A is a subset of permission slice B
|
||||
func isValidPermissionScope(creating []*models.RobotPermission, creator []*robot.Permission) bool {
|
||||
creatorMap := make(map[string]*robot.Permission)
|
||||
for _, creatorPerm := range creator {
|
||||
key := fmt.Sprintf("%s:%s", creatorPerm.Kind, creatorPerm.Namespace)
|
||||
creatorMap[key] = creatorPerm
|
||||
}
|
||||
|
||||
hasLessThanOrEqualAccess := func(creating []*models.Access, creator []*types.Policy) bool {
|
||||
creatorMap := make(map[string]*types.Policy)
|
||||
for _, creatorP := range creator {
|
||||
key := fmt.Sprintf("%s:%s:%s", creatorP.Resource, creatorP.Action, creatorP.Effect)
|
||||
creatorMap[key] = creatorP
|
||||
}
|
||||
for _, creatingP := range creating {
|
||||
key := fmt.Sprintf("%s:%s:%s", creatingP.Resource, creatingP.Action, creatingP.Effect)
|
||||
if _, found := creatorMap[key]; !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
for _, pCreating := range creating {
|
||||
key := fmt.Sprintf("%s:%s", pCreating.Kind, pCreating.Namespace)
|
||||
creatingPerm, found := creatorMap[key]
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
if !hasLessThanOrEqualAccess(pCreating.Access, creatingPerm.Access) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -1,10 +1,15 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/controller/robot"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||
)
|
||||
|
||||
func TestValidLevel(t *testing.T) {
|
||||
@ -207,3 +212,181 @@ func TestContainsAccess(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidPermissionScope(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
creatingPerms []*models.RobotPermission
|
||||
creatorPerms []*robot.Permission
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "Project - subset",
|
||||
creatingPerms: []*models.RobotPermission{
|
||||
{
|
||||
Kind: "project",
|
||||
Namespace: "testSubset",
|
||||
Access: []*models.Access{
|
||||
{Resource: "repository", Action: "pull", Effect: "allow"},
|
||||
},
|
||||
},
|
||||
},
|
||||
creatorPerms: []*robot.Permission{
|
||||
{
|
||||
Kind: "project",
|
||||
Namespace: "testSubset",
|
||||
Access: []*types.Policy{
|
||||
{Resource: "repository", Action: "pull", Effect: "allow"},
|
||||
{Resource: "repository", Action: "push", Effect: "allow"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Project - not Subset",
|
||||
creatingPerms: []*models.RobotPermission{
|
||||
{
|
||||
Kind: "project",
|
||||
Namespace: "testNotSubset",
|
||||
Access: []*models.Access{
|
||||
{Resource: "repository", Action: "push", Effect: "allow"},
|
||||
},
|
||||
},
|
||||
},
|
||||
creatorPerms: []*robot.Permission{
|
||||
{
|
||||
Kind: "project",
|
||||
Namespace: "testNotSubset",
|
||||
Access: []*types.Policy{
|
||||
{Resource: "repository", Action: "pull", Effect: "allow"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Project - equal",
|
||||
creatingPerms: []*models.RobotPermission{
|
||||
{
|
||||
Kind: "project",
|
||||
Namespace: "library",
|
||||
Access: []*models.Access{
|
||||
{Resource: "repository", Action: "pull", Effect: "allow"},
|
||||
},
|
||||
},
|
||||
},
|
||||
creatorPerms: []*robot.Permission{
|
||||
{
|
||||
Kind: "project",
|
||||
Namespace: "library",
|
||||
Access: []*types.Policy{
|
||||
{Resource: "repository", Action: "pull", Effect: "allow"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Project - different",
|
||||
creatingPerms: []*models.RobotPermission{
|
||||
{
|
||||
Kind: "project",
|
||||
Namespace: "library",
|
||||
Access: []*models.Access{
|
||||
{Resource: "repository", Action: "pull", Effect: "allow"},
|
||||
},
|
||||
},
|
||||
},
|
||||
creatorPerms: []*robot.Permission{
|
||||
{
|
||||
Kind: "project",
|
||||
Namespace: "other",
|
||||
Access: []*types.Policy{
|
||||
{Resource: "repository", Action: "pull", Effect: "allow"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Project - empty creator",
|
||||
creatingPerms: []*models.RobotPermission{
|
||||
{
|
||||
Kind: "project",
|
||||
Namespace: "library",
|
||||
Access: []*models.Access{
|
||||
{Resource: "repository", Action: "pull", Effect: "allow"},
|
||||
},
|
||||
},
|
||||
},
|
||||
creatorPerms: []*robot.Permission{},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Project - empty creating",
|
||||
creatingPerms: []*models.RobotPermission{},
|
||||
creatorPerms: []*robot.Permission{
|
||||
{
|
||||
Kind: "project",
|
||||
Namespace: "library",
|
||||
Access: []*types.Policy{
|
||||
{Resource: "repository", Action: "pull", Effect: "allow"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "System - subset",
|
||||
creatingPerms: []*models.RobotPermission{
|
||||
{
|
||||
Kind: "system",
|
||||
Namespace: "admin",
|
||||
Access: []*models.Access{
|
||||
{Resource: "user", Action: "create", Effect: "allow"},
|
||||
},
|
||||
},
|
||||
},
|
||||
creatorPerms: []*robot.Permission{
|
||||
{
|
||||
Kind: "system",
|
||||
Namespace: "admin",
|
||||
Access: []*types.Policy{
|
||||
{Resource: "user", Action: "create", Effect: "allow"},
|
||||
{Resource: "user", Action: "delete", Effect: "allow"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "System - not subset",
|
||||
creatingPerms: []*models.RobotPermission{
|
||||
{
|
||||
Kind: "system",
|
||||
Namespace: "admin",
|
||||
Access: []*models.Access{
|
||||
{Resource: "user", Action: "delete", Effect: "allow"},
|
||||
},
|
||||
},
|
||||
},
|
||||
creatorPerms: []*robot.Permission{
|
||||
{
|
||||
Kind: "system",
|
||||
Namespace: "admin",
|
||||
Access: []*types.Policy{
|
||||
{Resource: "user", Action: "create", Effect: "allow"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := isValidPermissionScope(tt.creatingPerms, tt.creatorPerms)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,16 @@ ARG GOLANG
|
||||
FROM ${GOLANG}
|
||||
|
||||
ARG SPECTRAL_VERSION
|
||||
RUN curl -fsSL -o /usr/bin/spectral https://github.com/stoplightio/spectral/releases/download/$SPECTRAL_VERSION/spectral-linux && chmod +x /usr/bin/spectral
|
||||
RUN case "$(dpkg --print-architecture)" in \
|
||||
amd64) ARCH="x64" ;; \
|
||||
arm64) ARCH="arm64" ;; \
|
||||
*) echo "Unsupported architecture" && exit 1 ;; \
|
||||
esac && \
|
||||
echo "Architecture: $ARCH" && \
|
||||
echo "Spectral version: $SPECTRAL_VERSION" && \
|
||||
URL="https://github.com/stoplightio/spectral/releases/download/$SPECTRAL_VERSION/spectral-linux-$ARCH" && \
|
||||
echo "URL: $URL" && \
|
||||
curl -fsSL -o /usr/bin/spectral $URL && chmod +x /usr/bin/spectral
|
||||
|
||||
ENTRYPOINT ["/usr/bin/spectral"]
|
||||
CMD ["--version"]
|
||||
|
Loading…
Reference in New Issue
Block a user