From ee6f61c50274734de4cfb970d88a94fbc9a8b28f Mon Sep 17 00:00:00 2001 From: zycupup <70937795+zycupup@users.noreply.github.com> Date: Fri, 19 Jan 2024 14:15:49 +0800 Subject: [PATCH 001/145] feat: volc cr adapter (#19456) feat: support volcEngine replication Signed-off-by: zhuyuchen.1 --- src/go.mod | 8 +- src/go.sum | 9 + .../job/impl/replication/replication.go | 2 + src/pkg/reg/adapter/volcenginecr/adapter.go | 167 +++++++++ .../reg/adapter/volcenginecr/adapter_test.go | 187 ++++++++++ .../adapter/volcenginecr/artifact_registry.go | 205 +++++++++++ .../volcenginecr/artifact_registry_test.go | 55 +++ src/pkg/reg/adapter/volcenginecr/auth.go | 93 +++++ src/pkg/reg/adapter/volcenginecr/auth_test.go | 29 ++ src/pkg/reg/adapter/volcenginecr/consts.go | 33 ++ src/pkg/reg/adapter/volcenginecr/helper.go | 66 ++++ .../reg/adapter/volcenginecr/helper_test.go | 56 +++ src/pkg/reg/adapter/volcenginecr/volccr.go | 334 ++++++++++++++++++ .../reg/adapter/volcenginecr/volccr_test.go | 70 ++++ src/pkg/reg/manager.go | 6 +- src/pkg/reg/model/registry.go | 1 + .../app/shared/services/endpoint.service.ts | 1 + 17 files changed, 1317 insertions(+), 5 deletions(-) create mode 100644 src/pkg/reg/adapter/volcenginecr/adapter.go create mode 100644 src/pkg/reg/adapter/volcenginecr/adapter_test.go create mode 100644 src/pkg/reg/adapter/volcenginecr/artifact_registry.go create mode 100644 src/pkg/reg/adapter/volcenginecr/artifact_registry_test.go create mode 100644 src/pkg/reg/adapter/volcenginecr/auth.go create mode 100644 src/pkg/reg/adapter/volcenginecr/auth_test.go create mode 100644 src/pkg/reg/adapter/volcenginecr/consts.go create mode 100644 src/pkg/reg/adapter/volcenginecr/helper.go create mode 100644 src/pkg/reg/adapter/volcenginecr/helper_test.go create mode 100644 src/pkg/reg/adapter/volcenginecr/volccr.go create mode 100644 src/pkg/reg/adapter/volcenginecr/volccr_test.go diff --git a/src/go.mod b/src/go.mod index dd2f7adb7..86913fd02 100644 --- a/src/go.mod +++ b/src/go.mod @@ -21,12 +21,12 @@ require ( github.com/go-asn1-ber/asn1-ber v1.5.1 github.com/go-ldap/ldap/v3 v3.2.4 github.com/go-openapi/errors v0.20.4 - github.com/go-openapi/loads v0.21.2 // indirect + github.com/go-openapi/loads v0.21.2 github.com/go-openapi/runtime v0.26.2 - github.com/go-openapi/spec v0.20.11 // indirect + github.com/go-openapi/spec v0.20.11 github.com/go-openapi/strfmt v0.21.8 github.com/go-openapi/swag v0.22.7 - github.com/go-openapi/validate v0.22.3 // indirect + github.com/go-openapi/validate v0.22.3 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 @@ -53,6 +53,7 @@ require ( github.com/stretchr/testify v1.8.4 github.com/tencentcloud/tencentcloud-sdk-go v1.0.62 github.com/vmihailenco/msgpack/v5 v5.4.1 + github.com/volcengine/volcengine-go-sdk v1.0.97 go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.46.1 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 go.opentelemetry.io/otel v1.21.0 @@ -153,6 +154,7 @@ require ( github.com/stretchr/objx v0.5.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/volcengine/volc-sdk-golang v1.0.23 // indirect go.mongodb.org/mongo-driver v1.13.1 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect go.opentelemetry.io/otel/metric v1.21.0 // indirect diff --git a/src/go.sum b/src/go.sum index e9c9e8e69..2edddf473 100644 --- a/src/go.sum +++ b/src/go.sum @@ -74,6 +74,7 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk= github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= @@ -246,6 +247,7 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= @@ -278,6 +280,7 @@ github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OI github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -403,6 +406,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxv github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -595,6 +599,10 @@ github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IU github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/volcengine/volc-sdk-golang v1.0.23 h1:anOslb2Qp6ywnsbyq9jqR0ljuO63kg9PY+4OehIk5R8= +github.com/volcengine/volc-sdk-golang v1.0.23/go.mod h1:AfG/PZRUkHJ9inETvbjNifTDgut25Wbkm2QoYBTbvyU= +github.com/volcengine/volcengine-go-sdk v1.0.97 h1:JykYagPlleFuFIrk90uigS1UyIZPRIYX6TnC6FErWP4= +github.com/volcengine/volcengine-go-sdk v1.0.97/go.mod h1:oht5AKDJsk0fY6tV2ViqaVlOO14KSRmXZlI8ikK60Tg= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= @@ -893,6 +901,7 @@ google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw 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= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/src/jobservice/job/impl/replication/replication.go b/src/jobservice/job/impl/replication/replication.go index 740ea3797..453fa5e03 100644 --- a/src/jobservice/job/impl/replication/replication.go +++ b/src/jobservice/job/impl/replication/replication.go @@ -51,6 +51,8 @@ import ( _ "github.com/goharbor/harbor/src/pkg/reg/adapter/quay" // import tencentcr adapter _ "github.com/goharbor/harbor/src/pkg/reg/adapter/tencentcr" + // register the VolcEngine CR Registry adapter + _ "github.com/goharbor/harbor/src/pkg/reg/adapter/volcenginecr" "github.com/goharbor/harbor/src/pkg/reg/model" ) diff --git a/src/pkg/reg/adapter/volcenginecr/adapter.go b/src/pkg/reg/adapter/volcenginecr/adapter.go new file mode 100644 index 000000000..106ba76cb --- /dev/null +++ b/src/pkg/reg/adapter/volcenginecr/adapter.go @@ -0,0 +1,167 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package volcenginecr + +import ( + "errors" + "path" + "strings" + + volcCR "github.com/volcengine/volcengine-go-sdk/service/cr" + "github.com/volcengine/volcengine-go-sdk/volcengine" + "github.com/volcengine/volcengine-go-sdk/volcengine/credentials" + volcSession "github.com/volcengine/volcengine-go-sdk/volcengine/session" + + "github.com/goharbor/harbor/src/lib/log" + adp "github.com/goharbor/harbor/src/pkg/reg/adapter" + "github.com/goharbor/harbor/src/pkg/reg/adapter/native" + "github.com/goharbor/harbor/src/pkg/reg/model" + "github.com/goharbor/harbor/src/pkg/reg/util" + "github.com/goharbor/harbor/src/pkg/registry/auth/bearer" +) + +func init() { + if err := adp.RegisterFactory(model.RegistryTypeVolcCR, new(factory)); err != nil { + log.Errorf("failed to register factory for %s: %v", model.RegistryTypeVolcCR, err) + return + } + log.Infof("the factory for adapter %s registered", model.RegistryTypeVolcCR) +} + +type factory struct{} + +/** + * Implement Factory Interface +**/ +var _ adp.Factory = &factory{} + +type adapter struct { + *native.Adapter + registryName *string + volcCrClient *volcCR.CR + registry *model.Registry +} + +// Create ... +func (f *factory) Create(r *model.Registry) (adp.Adapter, error) { + return newAdapter(r) +} + +// AdapterPattern ... +func (f *factory) AdapterPattern() *model.AdapterPattern { + return getAdapterInfo() +} + +func getAdapterInfo() *model.AdapterPattern { + return &model.AdapterPattern{} +} + +func newAdapter(registry *model.Registry) (a *adapter, err error) { + // get region and registryName from url + region, registryName, err := getRegionRegistryName(registry.URL) + if err != nil { + log.Errorf("getRegion failed. error=%v", err) + return nil, err + } + + // Create VolcCR API client + config := volcengine.NewConfig(). + WithCredentials(credentials.NewStaticCredentials(registry.Credential.AccessKey, registry.Credential.AccessSecret, "")). + WithRegion(region) + sess, err := volcSession.NewSession(config) + if err != nil { + log.Errorf("getSession error. error=%v", err) + return nil, err + } + client := volcCR.New(sess) + + // Get AuthorizationToken for docker login + bearRealm, bearService, err := getRealmService(registry.URL, registry.Insecure) + if err != nil { + log.Error("fail to ping the registry", "url", registry.URL) + return nil, err + } + cred := NewAuth(client, registryName) + var transport = util.GetHTTPTransport(registry.Insecure) + authorizer := bearer.NewAuthorizer(bearRealm, bearService, cred, transport) + + return &adapter{ + registry: registry, + registryName: ®istryName, + volcCrClient: client, + Adapter: native.NewAdapterWithAuthorizer(registry, authorizer), + }, nil +} + +func (a *adapter) Info() (info *model.RegistryInfo, err error) { + info = &model.RegistryInfo{ + Type: model.RegistryTypeVolcCR, + SupportedResourceTypes: []string{ + model.ResourceTypeImage, + }, + SupportedResourceFilters: []*model.FilterStyle{ + { + Type: model.FilterTypeName, + Style: model.FilterStyleTypeText, + }, + { + Type: model.FilterTypeTag, + Style: model.FilterStyleTypeText, + }, + }, + SupportedTriggers: []string{ + model.TriggerTypeManual, + model.TriggerTypeScheduled, + }, + } + return +} + +func (a *adapter) PrepareForPush(resources []*model.Resource) (err error) { + for _, resource := range resources { + if resource == nil { + return errors.New("the resource cannot be null") + } + if resource.Metadata == nil { + return errors.New("[volcengine-cr.PrepareForPush] the metadata of resource cannot be null") + } + if resource.Metadata.Repository == nil { + return errors.New("[volcengine-cr.PrepareForPush] the namespace of resource cannot be null") + } + if len(resource.Metadata.Repository.Name) == 0 { + return errors.New("[volcengine-cr.PrepareForPush] the name of the namespace cannot be null") + } + var paths = strings.Split(resource.Metadata.Repository.Name, "/") + if len(paths) < 2 { + return errors.New("[volcengine-cr.PrepareForPush] the name of the repository and namespace cannot be null") + } + var namespace = paths[0] + var repository = path.Join(paths[1:]...) + + log.Debugf("namespace=%s", namespace) + err = a.createNamespace(namespace) + if err != nil { + log.Errorf("PrepareForPush error :%v", err) + return + } + log.Debugf("namespace=%s, repository=%s", namespace, repository) + err = a.createRepository(namespace, repository) + if err != nil { + log.Errorf("PrepareForPush error :%v", err) + return + } + } + return +} diff --git a/src/pkg/reg/adapter/volcenginecr/adapter_test.go b/src/pkg/reg/adapter/volcenginecr/adapter_test.go new file mode 100644 index 000000000..5ddb5b6b7 --- /dev/null +++ b/src/pkg/reg/adapter/volcenginecr/adapter_test.go @@ -0,0 +1,187 @@ +package volcenginecr + +import ( + "fmt" + "io" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/goharbor/harbor/src/common/utils/test" + adp "github.com/goharbor/harbor/src/pkg/reg/adapter" + "github.com/goharbor/harbor/src/pkg/reg/adapter/native" + "github.com/goharbor/harbor/src/pkg/reg/model" + "github.com/stretchr/testify/assert" + volcCR "github.com/volcengine/volcengine-go-sdk/service/cr" + "github.com/volcengine/volcengine-go-sdk/volcengine" + "github.com/volcengine/volcengine-go-sdk/volcengine/credentials" + volcSession "github.com/volcengine/volcengine-go-sdk/volcengine/session" +) + +func getMockAdapter_withoutCred(t *testing.T, hasCred, health bool) (*adapter, *httptest.Server) { + server := test.NewServer( + &test.RequestHandlerMapping{ + Method: http.MethodGet, + Pattern: "/v2/", + Handler: func(w http.ResponseWriter, r *http.Request) { + fmt.Println(r.Method, r.URL) + if health { + w.WriteHeader(http.StatusOK) + } else { + w.WriteHeader(http.StatusBadRequest) + } + }, + }, + &test.RequestHandlerMapping{ + Method: http.MethodGet, + Pattern: "/", + Handler: func(w http.ResponseWriter, r *http.Request) { + fmt.Println(r.Method, r.URL) + w.WriteHeader(http.StatusOK) + }, + }, + &test.RequestHandlerMapping{ + Method: http.MethodPost, + Pattern: "/", + Handler: func(w http.ResponseWriter, r *http.Request) { + fmt.Println(r.Method, r.URL) + if buf, e := ioutil.ReadAll(&io.LimitedReader{R: r.Body, N: 80}); e == nil { + fmt.Println("\t", string(buf)) + } + w.WriteHeader(http.StatusOK) + }, + }, + ) + + registry := &model.Registry{ + Type: model.RegistryTypeVolcCR, + URL: server.URL, + } + if hasCred { + registry.Credential = &model.Credential{ + AccessKey: "MockAccessKey", + AccessSecret: "MockAccessSecret", + } + } + name := "test-registry" + config := volcengine.NewConfig(). + WithCredentials(credentials.NewStaticCredentials("", "", "")). + WithRegion("cn-beijing") + sess, _ := volcSession.NewSession(config) + client := volcCR.New(sess) + return &adapter{ + Adapter: native.NewAdapter(registry), + registryName: &name, + volcCrClient: client, + registry: registry, + }, server +} + +func TestAdapter_NewAdapter_InvalidURL(t *testing.T) { + factory, err := adp.GetFactory("BadName") + assert.Nil(t, factory) + assert.Error(t, err) + + factory, err = adp.GetFactory(model.RegistryTypeVolcCR) + assert.NoError(t, err) + assert.NotNil(t, factory) + adapter, err := factory.Create(&model.Registry{ + Type: model.RegistryTypeVolcCR, + Credential: &model.Credential{}, + }) + assert.Error(t, err) + assert.Nil(t, adapter) +} + +func TestAdapter_NewAdapter_PingFailed(t *testing.T) { + factory, _ := adp.GetFactory(model.RegistryTypeVolcCR) + adapter, err := factory.Create(&model.Registry{ + Type: model.RegistryTypeVolcCR, + Credential: &model.Credential{}, + URL: "https://cr-test-cn-beijing.cr.volces.com", + }) + assert.Error(t, err) + assert.Nil(t, adapter) +} + +func TestAdapter_Info(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + info, err := a.Info() + assert.Nil(t, err) + assert.NotNil(t, info) + + assert.EqualValues(t, 1, len(info.SupportedResourceTypes)) + assert.EqualValues(t, model.ResourceTypeImage, info.SupportedResourceTypes[0]) +} + +func TestAdapter_PrepareForPush(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + resources := []*model.Resource{ + { + Type: model.ResourceTypeImage, + Metadata: &model.ResourceMetadata{ + Repository: &model.Repository{ + Name: "busybox", + }, + }, + }, + } + + err := a.PrepareForPush(resources) + assert.Error(t, err) +} + +func TestAdapter_PrepareForPush_NilResource(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + var resources = []*model.Resource{nil} + + err := a.PrepareForPush(resources) + assert.Error(t, err) +} + +func TestAdapter_PrepareForPush_NilMeta(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + resources := []*model.Resource{ + { + Type: model.ResourceTypeImage, + }, + } + + err := a.PrepareForPush(resources) + assert.Error(t, err) +} + +func TestAdapter_PrepareForPush_NilRepository(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + resources := []*model.Resource{ + { + Type: model.ResourceTypeImage, + Metadata: &model.ResourceMetadata{}, + }, + } + + err := a.PrepareForPush(resources) + assert.Error(t, err) +} + +func TestAdapter_PrepareForPush_NilRepositoryName(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + resources := []*model.Resource{ + { + Type: model.ResourceTypeImage, + Metadata: &model.ResourceMetadata{ + Repository: &model.Repository{}, + }, + }, + } + + err := a.PrepareForPush(resources) + assert.Error(t, err) +} diff --git a/src/pkg/reg/adapter/volcenginecr/artifact_registry.go b/src/pkg/reg/adapter/volcenginecr/artifact_registry.go new file mode 100644 index 000000000..ff3d74f77 --- /dev/null +++ b/src/pkg/reg/adapter/volcenginecr/artifact_registry.go @@ -0,0 +1,205 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package volcenginecr + +import ( + "fmt" + "strings" + + "github.com/opencontainers/go-digest" + "github.com/volcengine/volcengine-go-sdk/service/cr" + + "github.com/goharbor/harbor/src/common/utils" + "github.com/goharbor/harbor/src/lib/log" + "github.com/goharbor/harbor/src/pkg/reg/model" + "github.com/goharbor/harbor/src/pkg/reg/util" +) + +// DeleteManifest VolcCR will use our own openAPI to delete Manifest +func (a *adapter) DeleteManifest(repository, reference string) (err error) { + parts := strings.SplitN(repository, "/", 2) + if len(parts) != 2 { + return fmt.Errorf("VolcEngineCR only support repo in format /, but got: %s", repository) + } + log.Warningf("namespace=%s, repository=%s, tag=%s", parts[0], parts[1], reference) + + if _, err := digest.Parse(reference); err != nil { + // get digest + resp, err := a.volcCrClient.ListTags(&cr.ListTagsInput{ + Registry: a.registryName, + Namespace: &parts[0], + Repository: &parts[1], + Filter: &cr.FilterForListTagsInput{ + Names: []*string{ + &reference, + }, + }, + }) + if err != nil { + return err + } + if resp == nil || resp.TotalCount == nil { + return fmt.Errorf("[VolcEngineCR.DeleteManifest] ListTags resp nil") + } + if *resp.TotalCount == 0 { + return nil + } + if resp.Items[0] == nil { + return fmt.Errorf("[VolcEngineCR.DeleteManifest] ListTags resp nil") + } + reference = *resp.Items[0].Digest + } + // listCandidateTags based on digest + tags, err := a.listCandidateTags(parts[0], parts[1], reference) + if err != nil { + log.Errorf("DeleteManifest error :%v", err) + return err + } + // deleteTags + err = a.deleteTags(parts[0], parts[1], tags) + if err != nil { + log.Errorf("DeleteManifest error :%v", err) + } + return +} + +// DeleteTag VolcCR will use our own openAPI to delete tag +func (a *adapter) DeleteTag(repository, tag string) (err error) { + parts := strings.SplitN(repository, "/", 2) + if len(parts) != 2 { + return fmt.Errorf("VolcEngineCR only support repo in format /, but got: %s", repository) + } + log.Warningf("namespace=%s, repository=%s, tag=%s", parts[0], parts[1], tag) + + err = a.deleteTags(parts[0], parts[1], []*string{ + &tag, + }) + if err != nil { + log.Errorf("deleteTag error: %v", err) + } + return +} + +// FetchArtifacts VolcCR not support /v2/_catalog of Registry +func (a *adapter) FetchArtifacts(filters []*model.Filter) ([]*model.Resource, error) { + log.Debug("FetchArtifacts filters", "filters", filters) + // 1. get filter pattern + var repoPattern string + var tagsPattern string + for _, filter := range filters { + if filter.Type == model.FilterTypeName { + repoPattern = filter.Value.(string) + } + if filter.Type == model.FilterTypeTag { + tagsPattern = filter.Value.(string) + } + } + namespacePattern := strings.Split(repoPattern, "/")[0] + + log.Debug("read in filter patterns", "repoPattern", repoPattern, "tagsPattern", tagsPattern) + + // 2. list namespace candidtes + namespaces, err := a.listCandidateNamespaces(namespacePattern) + if err != nil { + log.Errorf("FetchArtifacts error: %v", err) + return nil, err + } + log.Debug("FetchArtifacts filtered namespace", "namespace", namespaces) + + // 3. list repos + var nsRepos []string + for _, ns := range namespaces { + repoCandidates, err := a.listRepositories(ns) + if err != nil { + log.Error("FetchArtifacts error", "error", err) + return nil, err + } + log.Debug(" FetchArtifacts list repo", "repos: ", repoCandidates) + for _, r := range repoCandidates { + nsRepoCandidate := fmt.Sprintf("%s/%s", ns, r) + ok, err := util.Match(repoPattern, nsRepoCandidate) + if err != nil { + log.Error("FetchArtifacts error", "error", err) + return nil, err + } + log.Debug("filter namespaced repository", "repoPattern: ", repoPattern, "repo: ", nsRepoCandidate) + if ok { + nsRepos = append(nsRepos, nsRepoCandidate) + } + } + } + log.Debug("filter namespaced repository", "length", len(nsRepos)) + + // 4. list tags + var rawResources = make([]*model.Resource, len(nsRepos)) + resources := make([]*model.Resource, 0) + runner := utils.NewLimitedConcurrentRunner(concurrentLimit) + + for idx, repo := range nsRepos { + i := idx + nsRepo := repo + runner.AddTask(func() error { + repoArr := strings.SplitN(nsRepo, "/", 2) + // note list tag don't tell different oci types now + candidateTags, err := a.listAllTags(repoArr[0], repoArr[1]) + if err != nil { + log.Error("fail to list all tags", "nsRepo", nsRepo) + return fmt.Errorf("volcengineCR fail to list all tags %w", err) + } + + tags := make([]string, 0) + if tagsPattern != "" { + for _, candidateTag := range candidateTags { + ok, err := util.Match(tagsPattern, candidateTag) + if err != nil { + return fmt.Errorf("fail to match tag pattern, error=%w", err) + } + if ok { + tags = append(tags, candidateTag) + } + } + } else { + tags = candidateTags + } + + log.Debug("filter tags") + + if len(tags) > 0 { + rawResources[i] = &model.Resource{ + Type: model.ResourceTypeImage, + Registry: a.registry, + Metadata: &model.ResourceMetadata{ + Repository: &model.Repository{ + Name: nsRepo, + }, + Vtags: tags, + }, + } + } + return nil + }) + } + if err = runner.Wait(); err != nil { + return nil, fmt.Errorf("failed to fetch artifacts: %w", err) + } + + for _, res := range rawResources { + if res != nil { + resources = append(resources, res) + } + } + + return resources, nil +} diff --git a/src/pkg/reg/adapter/volcenginecr/artifact_registry_test.go b/src/pkg/reg/adapter/volcenginecr/artifact_registry_test.go new file mode 100644 index 000000000..a9645ddc7 --- /dev/null +++ b/src/pkg/reg/adapter/volcenginecr/artifact_registry_test.go @@ -0,0 +1,55 @@ +package volcenginecr + +import ( + "github.com/goharbor/harbor/src/pkg/reg/model" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestArtifactRegistry_DeleteManifest(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + err := a.DeleteManifest("ut_test/ut_test", "sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc") + assert.Error(t, err) +} + +func TestArtifactRegistry_DeleteTag(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + err := a.DeleteTag("ut_test/ut_test", "v1") + assert.Error(t, err) +} + +func TestArtifactRegistry_FetchArtifacts(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + tests := []struct { + name string + filter model.Filter + wantErr bool + }{ + {"filter name", + model.Filter{ + Type: model.FilterTypeName, + Value: "ut_test", + }, + true}, + {"filter tag", + model.Filter{ + Type: model.FilterTypeTag, + Value: "v1", + }, + true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := a.FetchArtifacts([]*model.Filter{&tt.filter}) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/src/pkg/reg/adapter/volcenginecr/auth.go b/src/pkg/reg/adapter/volcenginecr/auth.go new file mode 100644 index 000000000..b0a08e924 --- /dev/null +++ b/src/pkg/reg/adapter/volcenginecr/auth.go @@ -0,0 +1,93 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package volcenginecr + +import ( + "errors" + "fmt" + "net/http" + "time" + + "github.com/volcengine/volcengine-go-sdk/service/cr" + + "github.com/goharbor/harbor/src/common/http/modifier" + "github.com/goharbor/harbor/src/lib/log" +) + +// Credential ... +type Credential modifier.Modifier + +type volcCredential struct { + client *cr.CR + registry string + authCache *authCache +} + +type authCache struct { + username string + password string + expireAt *time.Time +} + +var _ Credential = &volcCredential{} + +// NewAuth will get a temporary username and password via cr GetAuthorizationToken action for docker login +func NewAuth(client *cr.CR, registry string) Credential { + return &volcCredential{ + client: client, + registry: registry, + authCache: &authCache{}, + } +} + +func (c *volcCredential) Modify(r *http.Request) (err error) { + if c.client == nil { + return errNilVolcCrClient + } + if !c.isCacheAuthValid() { + log.Debugf("update token %s\n", r.Host) + authResp, err := c.client.GetAuthorizationToken(&cr.GetAuthorizationTokenInput{ + Registry: &c.registry, + }) + if err != nil { + return err + } + if authResp == nil || authResp.Username == nil || authResp.Token == nil || authResp.ExpireTime == nil { + return errors.New("[VolcengineCR] GetAuthorizationToken output nil") + } + c.authCache.username = *authResp.Username + c.authCache.password = *authResp.Token + expireTime, err := time.Parse(time.RFC3339, *authResp.ExpireTime) + if err != nil { + log.Errorf("fail to parse expire time returned: %v", err) + return fmt.Errorf("[VolcengineCR] fail to parse expire time returned: %v", err) + } + c.authCache.expireAt = &expireTime + } else { + log.Debug("token cached") + } + r.SetBasicAuth(c.authCache.username, c.authCache.password) + return nil +} + +func (c *volcCredential) isCacheAuthValid() bool { + if c.authCache == nil || c.authCache.expireAt == nil { + return false + } + if time.Now().After(*c.authCache.expireAt) { + return false + } + return true +} diff --git a/src/pkg/reg/adapter/volcenginecr/auth_test.go b/src/pkg/reg/adapter/volcenginecr/auth_test.go new file mode 100644 index 000000000..6af5566f6 --- /dev/null +++ b/src/pkg/reg/adapter/volcenginecr/auth_test.go @@ -0,0 +1,29 @@ +package volcenginecr + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + volcCR "github.com/volcengine/volcengine-go-sdk/service/cr" + "github.com/volcengine/volcengine-go-sdk/volcengine" + "github.com/volcengine/volcengine-go-sdk/volcengine/credentials" + volcSession "github.com/volcengine/volcengine-go-sdk/volcengine/session" +) + +func Test_Modify_nilCR(t *testing.T) { + c := &volcCredential{} + err := c.Modify(&http.Request{}) + assert.Error(t, err) +} + +func Test_Modify(t *testing.T) { + config := volcengine.NewConfig(). + WithCredentials(credentials.NewStaticCredentials("", "", "")). + WithRegion("cn-beijing") + sess, _ := volcSession.NewSession(config) + client := volcCR.New(sess) + c := &volcCredential{client: client} + err := c.Modify(&http.Request{}) + assert.Error(t, err) +} diff --git a/src/pkg/reg/adapter/volcenginecr/consts.go b/src/pkg/reg/adapter/volcenginecr/consts.go new file mode 100644 index 000000000..d519d2bb0 --- /dev/null +++ b/src/pkg/reg/adapter/volcenginecr/consts.go @@ -0,0 +1,33 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package volcenginecr + +import "errors" + +var ( + regionRegs = map[string]interface{}{ + "(.*)-(cn-.*)": nil, + } + errListNamespaceResp = errors.New("[VolcengineCR adapt] ListNamespaces resp nil") + errListRepositoriesResp = errors.New("[VolcengineCR adapt] ListRepositories resp nil") + errListTagsResp = errors.New("[VolcengineCR adapt] ListTags resp nil") + errPareseDigest = errors.New("[VolcengineCR adapt] fail to parse reference") + errNilVolcCrClient = errors.New("[volcengine-cr.createRepository] nil volcCr client") +) + +const ( + MaxPageSize int64 = 100 + concurrentLimit int = 3 +) diff --git a/src/pkg/reg/adapter/volcenginecr/helper.go b/src/pkg/reg/adapter/volcenginecr/helper.go new file mode 100644 index 000000000..8af780f31 --- /dev/null +++ b/src/pkg/reg/adapter/volcenginecr/helper.go @@ -0,0 +1,66 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package volcenginecr + +import ( + "errors" + "fmt" + "net/http" + "regexp" + + "github.com/docker/distribution/registry/client/auth/challenge" + + "github.com/goharbor/harbor/src/lib/log" + "github.com/goharbor/harbor/src/pkg/reg/util" +) + +func getRegionRegistryName(url string) (string, string, error) { + reg := regexp.MustCompile(`https://(.*)\.cr\.volces|ivolces\.com`) + rs := reg.FindStringSubmatch(url) + if rs == nil || len(rs) != 2 { + return "", "", errors.New("Invalid url") + } + registryNameRegion := rs[1] + for regionReg := range regionRegs { + reg = regexp.MustCompile(regionReg) + res := reg.FindStringSubmatch(registryNameRegion) + if res == nil || len(res) != 3 { + log.Debug("fail to match", "reg", regionReg) + continue + } + return res[2], res[1], nil + } + + return "", "", errors.New("invalid region") +} + +func getRealmService(host string, insecure bool) (string, string, error) { + client := &http.Client{ + Transport: util.GetHTTPTransport(insecure), + } + + resp, err := client.Get(host + "/v2/") + if err != nil { + return "", "", err + } + defer resp.Body.Close() // nolint + challenges := challenge.ResponseChallenges(resp) + for _, challenge := range challenges { + if challenge.Scheme == "bearer" { + return challenge.Parameters["realm"], challenge.Parameters["service"], nil + } + } + return "", "", fmt.Errorf("bearer auth scheme isn't supported: %v", challenges) +} diff --git a/src/pkg/reg/adapter/volcenginecr/helper_test.go b/src/pkg/reg/adapter/volcenginecr/helper_test.go new file mode 100644 index 000000000..e08f72797 --- /dev/null +++ b/src/pkg/reg/adapter/volcenginecr/helper_test.go @@ -0,0 +1,56 @@ +package volcenginecr + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_getRegionRegistryNamer(t *testing.T) { + tests := []struct { + name string + url string + wantRegion string + wantRegistry string + wantErr bool + }{ + {"registry beijing", "https://enterprise-cn-beijing.cr.volces.com", "cn-beijing", "enterprise", false}, + {"invalid url", "http://enterprise-cn-beijing.cr.volces.com", "", "", true}, + {"invalid region", "https://enterprise-us-test.cr.volces.com", "", "", true}, + {"invalid suffix", "https://enterprise-us-test.cr-test.volces.com", "", "", true}, + {"registry shanghai", "https://cn-beijing-cn-shanghai.cr.volces.com", "cn-shanghai", "cn-beijing", false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotRegion, gotRegistry, err := getRegionRegistryName(tt.url) + if tt.wantErr { + assert.NotNil(t, err) + } + assert.Equal(t, tt.wantRegion, gotRegion) + assert.Equal(t, tt.wantRegistry, gotRegistry) + }) + } +} + +func Test_getRealmService(t *testing.T) { + tests := []struct { + name string + host string + insecure bool + wantErr bool + }{ + {"ping success", "https://cr-cn-beijing.volces.com", false, false}, + {"ping success", "https://cr-cn-beijing.volces.com", true, false}, + {"ping error", "https://cr-test-cn-beijing.volces.com", true, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, _, err := getRealmService(tt.host, tt.insecure) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/src/pkg/reg/adapter/volcenginecr/volccr.go b/src/pkg/reg/adapter/volcenginecr/volccr.go new file mode 100644 index 000000000..753cfe599 --- /dev/null +++ b/src/pkg/reg/adapter/volcenginecr/volccr.go @@ -0,0 +1,334 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package volcenginecr + +import ( + "math" + + "github.com/opencontainers/go-digest" + "github.com/volcengine/volcengine-go-sdk/service/cr" + + "github.com/goharbor/harbor/src/lib/log" + "github.com/goharbor/harbor/src/pkg/reg/util" +) + +func (a *adapter) createNamespace(namespace string) (err error) { + if a.volcCrClient == nil { + return errNilVolcCrClient + } + // check if exists + exist, err := a.namespaceExist(namespace) + if err != nil { + return + } + // if exists; skip create + if exist { + return nil + } + + // create Namespace + _, err = a.volcCrClient.CreateNamespace(&cr.CreateNamespaceInput{ + Registry: a.registryName, + Name: &namespace, + }) + if err != nil { + log.Debugf("CreateNamespace error:%v", err) + return err + } + return nil +} + +func (a *adapter) createRepository(namespace, repository string) (err error) { + if a.volcCrClient == nil { + return errNilVolcCrClient + } + // check if exists + res, err := a.volcCrClient.ListRepositories(&cr.ListRepositoriesInput{ + Registry: a.registryName, + Filter: &cr.FilterForListRepositoriesInput{ + Names: []*string{ + &repository, + }, + Namespaces: []*string{ + &namespace, + }, + }, + }) + if err != nil { + log.Debugf("ListRepositories error:%v", err) + return err + } + // if exists; skip create + if res != nil && res.TotalCount != nil && *res.TotalCount > 0 { + return nil + } + + // create Repository + _, err = a.volcCrClient.CreateRepository(&cr.CreateRepositoryInput{ + Registry: a.registryName, + Namespace: &namespace, + Name: &repository, + }) + if err != nil { + log.Debugf("CreateRepository error:%v", err) + return err + } + return nil +} + +func (a *adapter) deleteTags(namespace, repository string, tags []*string) error { + _, err := a.volcCrClient.DeleteTags(&cr.DeleteTagsInput{ + Registry: a.registryName, + Namespace: &namespace, + Repository: &repository, + Names: tags, + }) + return err +} + +func (a *adapter) listCandidateNamespaces(namespacePattern string) ([]string, error) { + if a.volcCrClient == nil { + return []string{}, errNilVolcCrClient + } + namespaces := make([]string, 0) + // filter namespaces + if len(namespacePattern) > 0 { + if nms, ok := util.IsSpecificPathComponent(namespacePattern); ok { + // Check if namespace exist + for _, ns := range nms { + exist, err := a.namespaceExist(ns) + if err != nil { + return nil, err + } + if !exist { + continue + } + namespaces = append(namespaces, nms...) + } + } + } + + if len(namespaces) > 0 { + log.Debug("list candidate namespace", "pattern", namespacePattern, "namespaces", namespaces) + return namespaces, nil + } + + // list all + return a.listNamespaces() +} + +func (a *adapter) listNamespaces() ([]string, error) { + if a.volcCrClient == nil { + return []string{}, errNilVolcCrClient + } + pageSize := MaxPageSize + pageNumber := int64(1) + initCondition := true + var remain int64 = math.MaxInt64 + var nsList []string + + for remain > 0 { + resp, err := a.volcCrClient.ListNamespaces( + &cr.ListNamespacesInput{ + Registry: a.registryName, + PageSize: &pageSize, + PageNumber: &pageNumber, + }) + + if err != nil { + return nil, err + } + if resp == nil || resp.TotalCount == nil { + return nil, errListNamespaceResp + } + if initCondition { + nsList = make([]string, 0, *resp.TotalCount) + remain = *resp.TotalCount - pageSize + initCondition = false + } else { + remain -= pageSize + } + // be careful with state machine. + pageNumber++ + for _, nsInfo := range resp.Items { + if nsInfo != nil && nsInfo.Name != nil { + nsList = append(nsList, *nsInfo.Name) + } + } + } + return nsList, nil +} + +func (a *adapter) listRepositories(namespace string) ([]string, error) { + if a.volcCrClient == nil { + return []string{}, errNilVolcCrClient + } + pageSize := MaxPageSize + pageNumber := int64(1) + initCondition := true + var remain int64 = math.MaxInt64 + var repoList []string + + for remain > 0 { + resp, err := a.volcCrClient.ListRepositories( + &cr.ListRepositoriesInput{ + Registry: a.registryName, + PageSize: &pageSize, + PageNumber: &pageNumber, + Filter: &cr.FilterForListRepositoriesInput{ + Namespaces: []*string{&namespace}, + }, + }) + if err != nil { + return nil, err + } + if resp == nil || resp.TotalCount == nil { + return nil, errListRepositoriesResp + } + if initCondition { + repoList = make([]string, 0, *resp.TotalCount) + remain = *resp.TotalCount - pageSize + initCondition = false + } else { + remain -= pageSize + } + // be careful with state machine. + pageNumber++ + for _, repoInfo := range resp.Items { + if repoInfo != nil && repoInfo.Name != nil { + repoList = append(repoList, *repoInfo.Name) + } + } + } + return repoList, nil +} + +// listAllTags list all tags of different artifacts with given namespace and repo +func (a *adapter) listAllTags(namespace, repo string) ([]string, error) { + if a.volcCrClient == nil { + return []string{}, errNilVolcCrClient + } + pageSize := MaxPageSize + pageNumber := int64(1) + initCondition := true + var remain int64 = math.MaxInt64 + var tagList []string + + for remain > 0 { + resp, err := a.volcCrClient.ListTags( + &cr.ListTagsInput{ + Registry: a.registryName, + Namespace: &namespace, + Repository: &repo, + PageSize: &pageSize, + PageNumber: &pageNumber, + }) + if err != nil { + return nil, err + } + if resp == nil || resp.TotalCount == nil { + return nil, errListTagsResp + } + if initCondition { + tagList = make([]string, 0, *resp.TotalCount) + remain = *resp.TotalCount - pageSize + initCondition = false + } else { + remain -= pageSize + } + pageNumber++ + for _, tagInfo := range resp.Items { + tagList = append(tagList, *tagInfo.Name) + } + } + return tagList, nil +} + +func (a *adapter) listCandidateTags(namespace, repository, reference string) ([]*string, error) { + if a.volcCrClient == nil { + return []*string{}, errNilVolcCrClient + } + pageSize := MaxPageSize + pageNumber := int64(1) + initCondition := true + var remain int64 = math.MaxInt64 + var tagList []*string + desiredDig, err := digest.Parse(reference) + if err != nil { + return tagList, errPareseDigest + } + + for remain > 0 { + resp, err := a.volcCrClient.ListTags( + &cr.ListTagsInput{ + Registry: a.registryName, + Namespace: &namespace, + Repository: &repository, + PageSize: &pageSize, + PageNumber: &pageNumber, + }) + if err != nil { + return nil, err + } + if resp == nil || resp.TotalCount == nil { + return nil, errPareseDigest + } + if initCondition { + tagList = make([]*string, 0, *resp.TotalCount) + remain = *resp.TotalCount - pageSize + initCondition = false + } else { + remain -= pageSize + } + pageNumber++ + for _, tagInfo := range resp.Items { + if tagInfo != nil && tagInfo.Name != nil && tagInfo.Digest != nil { + dig, err := digest.Parse(*tagInfo.Digest) + if err != nil { + log.Debug("fail to parase digest", "tag", tagInfo) + continue + } + if desiredDig.String() == dig.String() { + tagList = append(tagList, tagInfo.Name) + } + } + } + } + return tagList, nil +} + +func (a *adapter) namespaceExist(namespace string) (bool, error) { + if a.volcCrClient == nil { + return false, errNilVolcCrClient + } + resp, err := a.volcCrClient.ListNamespaces(&cr.ListNamespacesInput{ + Registry: a.registryName, + Filter: &cr.FilterForListNamespacesInput{ + Names: []*string{ + &namespace, + }, + }, + }) + if err != nil { + return false, err + } + if resp == nil || resp.TotalCount == nil { + return false, errListNamespaceResp + } + if *resp.TotalCount > 0 { + return true, nil + } + return false, nil +} diff --git a/src/pkg/reg/adapter/volcenginecr/volccr_test.go b/src/pkg/reg/adapter/volcenginecr/volccr_test.go new file mode 100644 index 000000000..961df39eb --- /dev/null +++ b/src/pkg/reg/adapter/volcenginecr/volccr_test.go @@ -0,0 +1,70 @@ +package volcenginecr + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestVolccr_createNamespace(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + err := a.createNamespace("ut_test") + assert.Error(t, err) +} + +func TestVolccr_createRepository(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + err := a.createRepository("ut_test", "ut_test") + assert.Error(t, err) +} + +func TestVolccr_deleteTags(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + err := a.deleteTags("ut_test", "ut_test", []*string{}) + assert.Error(t, err) +} + +func TestVolccr_listCandidateNamespaces(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + _, err := a.listCandidateNamespaces("ut_test") + assert.Error(t, err) +} + +func TestVolccr_listNamespaces(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + _, err := a.listNamespaces() + assert.Error(t, err) +} + +func TestVolccr_listRepositories(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + _, err := a.listRepositories("ut_test") + assert.Error(t, err) +} + +func TestVolccr_listAllTags(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + _, err := a.listAllTags("ut_test", "ut_test") + assert.Error(t, err) +} + +func TestVolccr_listCandidateTags(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + _, err := a.listCandidateTags("ut_test", "ut_test", "sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc") + assert.Error(t, err) +} + +func TestVolccr_namespaceExist(t *testing.T) { + a, s := getMockAdapter_withoutCred(t, true, true) + defer s.Close() + _, err := a.namespaceExist("ut_test") + assert.Error(t, err) +} diff --git a/src/pkg/reg/manager.go b/src/pkg/reg/manager.go index 251aec47b..c1d37671d 100644 --- a/src/pkg/reg/manager.go +++ b/src/pkg/reg/manager.go @@ -22,6 +22,8 @@ import ( "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/q" "github.com/goharbor/harbor/src/pkg/reg/adapter" + "github.com/goharbor/harbor/src/pkg/reg/dao" + "github.com/goharbor/harbor/src/pkg/reg/model" // register the AliACR adapter _ "github.com/goharbor/harbor/src/pkg/reg/adapter/aliacr" @@ -51,8 +53,8 @@ import ( _ "github.com/goharbor/harbor/src/pkg/reg/adapter/quay" // register the TencentCloud TCR adapter _ "github.com/goharbor/harbor/src/pkg/reg/adapter/tencentcr" - "github.com/goharbor/harbor/src/pkg/reg/dao" - "github.com/goharbor/harbor/src/pkg/reg/model" + // register the VolcEngine CR Registry adapter + _ "github.com/goharbor/harbor/src/pkg/reg/adapter/volcenginecr" ) var ( diff --git a/src/pkg/reg/model/registry.go b/src/pkg/reg/model/registry.go index 6c8a09cc4..1b4f945d7 100644 --- a/src/pkg/reg/model/registry.go +++ b/src/pkg/reg/model/registry.go @@ -34,6 +34,7 @@ const ( RegistryTypeDTR = "dtr" RegistryTypeTencentTcr = "tencent-tcr" RegistryTypeGithubCR = "github-ghcr" + RegistryTypeVolcCR = "volcengine-cr" RegistryTypeHelmHub = "helm-hub" RegistryTypeArtifactHub = "artifact-hub" diff --git a/src/portal/src/app/shared/services/endpoint.service.ts b/src/portal/src/app/shared/services/endpoint.service.ts index a22a1d6fd..0b007bca0 100644 --- a/src/portal/src/app/shared/services/endpoint.service.ts +++ b/src/portal/src/app/shared/services/endpoint.service.ts @@ -29,6 +29,7 @@ export const ADAPTERS_MAP = { dtr: 'DTR', 'tencent-tcr': 'Tencent TCR', 'github-ghcr': 'Github GHCR', + 'volcengine-cr': 'VolcEngine CR', }; /** From 5a576174b1d31dd7a26b6970aa8b590ef1750bcb Mon Sep 17 00:00:00 2001 From: Bin Liu Date: Fri, 19 Jan 2024 15:11:32 +0800 Subject: [PATCH 002/145] skip transaction for POST /service/token (#19339) Habor doesnot support POST /service/token endpoint, put this request into a transaction will consume extra database resources and generate many logs. Signed-off-by: bin liu --- src/core/middlewares/middlewares.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/middlewares/middlewares.go b/src/core/middlewares/middlewares.go index 0ae29bb02..555ca5674 100644 --- a/src/core/middlewares/middlewares.go +++ b/src/core/middlewares/middlewares.go @@ -55,6 +55,7 @@ var ( dbTxSkippers = []middleware.Skipper{ middleware.MethodAndPathSkipper(http.MethodPatch, distribution.BlobUploadURLRegexp), middleware.MethodAndPathSkipper(http.MethodPut, distribution.BlobUploadURLRegexp), + middleware.MethodAndPathSkipper(http.MethodPost, match("^/service/token")), func(r *http.Request) bool { // skip tx for GET, HEAD and Options requests m := r.Method return m == http.MethodGet || m == http.MethodHead || m == http.MethodOptions From d88a32089a9ba669df16085b275cf81ab97a97ea Mon Sep 17 00:00:00 2001 From: Shengwen YU Date: Wed, 24 Jan 2024 14:13:46 +0800 Subject: [PATCH 003/145] =?UTF-8?q?feat:=20add=20auto=5Fsbom=5Fgeneration?= =?UTF-8?q?=20for=20SBOM=20auto=20generation=20on=20pushing=20a=20?= =?UTF-8?q?=E2=80=A6=20(#19869)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat: add auto_sbom_generation for SBOM auto generation on pushing a subject artifact Signed-off-by: Shengwen Yu --- api/v2.0/swagger.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index 523aaad62..094076925 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -7164,6 +7164,10 @@ definitions: type: string description: 'Whether scan images automatically when pushing. The valid values are "true", "false".' x-nullable: true + auto_sbom_generation: + type: string + description: 'Whether generating SBOM automatically when pushing a subject artifact. The valid values are "true", "false".' + x-nullable: true reuse_sys_cve_allowlist: type: string description: 'Whether this project reuse the system level CVE allowlist as the allowlist of its own. The valid values are "true", "false". From 0db0d217a7223bd23dd0fc89d4e47d3002a952b9 Mon Sep 17 00:00:00 2001 From: Yang Jiao <72076317+YangJiao0817@users.noreply.github.com> Date: Wed, 24 Jan 2024 15:43:41 +0800 Subject: [PATCH 004/145] Update replication rule filter label xpath (#19895) Signed-off-by: Yang Jiao --- tests/resources/Harbor-Pages/Replication_Elements.robot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/resources/Harbor-Pages/Replication_Elements.robot b/tests/resources/Harbor-Pages/Replication_Elements.robot index 88013c424..fa61c82f3 100644 --- a/tests/resources/Harbor-Pages/Replication_Elements.robot +++ b/tests/resources/Harbor-Pages/Replication_Elements.robot @@ -64,7 +64,7 @@ ${replication_mode_radio_pull} //clr-main-container//hbr-create-edit-rule//labe ${filter_name_id} //input[@id='filter_name'] ${filter_tag_model_select} //div[@class='filterSelect ng-star-inserted'][2]//select ${filter_tag_id} //input[@id='filter_tag'] -${filter_label_xpath} //form//clr-dropdown[contains(@class,'dropdown')] +${filter_label_xpath} //form//clr-dropdown[contains(@class,'dropdown')]//clr-icon ${filter_label_model_select} //div[@class='filterSelect ng-star-inserted'][3]//select ${rule_resource_selector} //*[@id='select_resource'] ${trigger_mode_selector} //*[@id='ruleTrigger'] From 522f96b5cf1c56ec5a4a1a1fabe97255e8d5841e Mon Sep 17 00:00:00 2001 From: MinerYang Date: Wed, 24 Jan 2024 17:24:42 +0800 Subject: [PATCH 005/145] add v6 port for nginx and portal config (#19868) add v6 port for nginx ad portal config Signed-off-by: yminer --- make/photon/prepare/templates/nginx/nginx.https.conf.jinja | 1 + make/photon/prepare/templates/portal/nginx.conf.jinja | 1 + 2 files changed, 2 insertions(+) diff --git a/make/photon/prepare/templates/nginx/nginx.https.conf.jinja b/make/photon/prepare/templates/nginx/nginx.https.conf.jinja index 29a57b44c..4c62ae79a 100644 --- a/make/photon/prepare/templates/nginx/nginx.https.conf.jinja +++ b/make/photon/prepare/templates/nginx/nginx.https.conf.jinja @@ -51,6 +51,7 @@ http { server { listen 8443 ssl; + listen [::]:8443 ssl; # server_name harbordomain.com; server_tokens off; # SSL diff --git a/make/photon/prepare/templates/portal/nginx.conf.jinja b/make/photon/prepare/templates/portal/nginx.conf.jinja index 89dc14bc8..01de758fe 100644 --- a/make/photon/prepare/templates/portal/nginx.conf.jinja +++ b/make/photon/prepare/templates/portal/nginx.conf.jinja @@ -17,6 +17,7 @@ http { server { {% if internal_tls.enabled %} listen 8443 ssl; + listen [::]:8443 ssl; # SSL ssl_certificate /etc/harbor/tls/portal.crt; ssl_certificate_key /etc/harbor/tls/portal.key; From 09d1f8e9fcffca6fe581cbab3c6f6eabfe780ee7 Mon Sep 17 00:00:00 2001 From: Shengwen YU Date: Fri, 2 Feb 2024 14:07:30 +0800 Subject: [PATCH 006/145] fix: cve export label filter xpath (#19931) Signed-off-by: Shengwen Yu --- tests/resources/Harbor-Pages/Project.robot | 2 +- tests/resources/Harbor-Pages/SecurityHub.robot | 9 +++++++++ tests/resources/Harbor-Pages/SecurityHub_Elements.robot | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/resources/Harbor-Pages/Project.robot b/tests/resources/Harbor-Pages/Project.robot index adedc655b..4d469f258 100644 --- a/tests/resources/Harbor-Pages/Project.robot +++ b/tests/resources/Harbor-Pages/Project.robot @@ -421,7 +421,7 @@ Export CVEs Retry Button Click ${export_cve_btn} Retry Text Input ${export_cve_filter_repo_input} ${repositories} Retry Text Input ${export_cve_filter_tag_input} ${tags} - Select Filter Label @{labels} + Select Filter Label For CVE Export @{labels} Retry Text Input ${export_cve_filter_cveid_input} ${cve_ids} Retry Double Keywords When Error Retry Button Click ${export_btn} Retry Wait Until Page Contains Trigger exporting CVEs successfully! diff --git a/tests/resources/Harbor-Pages/SecurityHub.robot b/tests/resources/Harbor-Pages/SecurityHub.robot index a0b293284..16366fc2a 100644 --- a/tests/resources/Harbor-Pages/SecurityHub.robot +++ b/tests/resources/Harbor-Pages/SecurityHub.robot @@ -273,3 +273,12 @@ Check The Quick Search Should Be Equal As Strings ${cve_input_value} ${cve} ${row_count}= Get Element Count ${vulnerabilities_datagrid_row} Retry Wait Element Count //div[@class='datagrid']//clr-dg-cell[1]//a[text()='${cve}'] ${row_count} + +Select Filter Label For CVE Export + [Arguments] @{labels} + Retry Element Click ${vulnerabilities_filter_label_xpath} + FOR ${label} IN @{labels} + Log ${label} + Retry Element Click //hbr-label-piece//span[contains(text(), '${label}')] + END + Retry Element Click ${vulnerabilities_filter_label_xpath} diff --git a/tests/resources/Harbor-Pages/SecurityHub_Elements.robot b/tests/resources/Harbor-Pages/SecurityHub_Elements.robot index 105678422..ade73bc6f 100644 --- a/tests/resources/Harbor-Pages/SecurityHub_Elements.robot +++ b/tests/resources/Harbor-Pages/SecurityHub_Elements.robot @@ -28,3 +28,4 @@ ${vulnerabilities_count_xpath} //clr-dg-footer//div[contains(@class,'datagrid-f ${vulnerabilities_filter_select} (//form//div[@class='clr-select-wrapper']//select) ${vulnerabilities_filter_input} (//form[contains(@class,'clr-form')]//input) ${vulnerabilities_datagrid_row} //clr-datagrid//clr-dg-row +${vulnerabilities_filter_label_xpath} //form//clr-dropdown[contains(@class,'dropdown')] From fb1e82854742d74b7ef035fe022b96d6f1ec9652 Mon Sep 17 00:00:00 2001 From: Shengwen YU Date: Fri, 2 Feb 2024 14:20:40 +0800 Subject: [PATCH 007/145] add UI test for project quota sorting (#19935) Signed-off-by: Shengwen Yu --- .../Administration-Project-Quotas.robot | 35 +++++++++++++++++++ ...ministration-Project-Quotas_Elements.robot | 20 +++++++++++ tests/resources/Util.robot | 2 ++ .../Group1-Nightly/Common_GC.robot | 19 ++++++++++ 4 files changed, 76 insertions(+) create mode 100644 tests/resources/Harbor-Pages/Administration-Project-Quotas.robot create mode 100644 tests/resources/Harbor-Pages/Administration-Project-Quotas_Elements.robot diff --git a/tests/resources/Harbor-Pages/Administration-Project-Quotas.robot b/tests/resources/Harbor-Pages/Administration-Project-Quotas.robot new file mode 100644 index 000000000..a7a515c8b --- /dev/null +++ b/tests/resources/Harbor-Pages/Administration-Project-Quotas.robot @@ -0,0 +1,35 @@ +# Copyright Project Harbor Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + +*** Settings *** +Documentation This resource provides any keywords related to the Harbor private registry appliance +Resource ../../resources/Util.robot + +*** Variables *** + +*** Keywords *** +Switch to Project Quotas Tag + Retry Element Click xpath=${administration_project_quotas_tag_xpath} + Sleep 1 + +Check Project Quota Sorting + [Arguments] ${proj1} ${proj2} + # check project quota sorting in ascending order + Retry Element Click xpath=${sort_used_storage_button} + Retry Wait Element Visible //div[@class='datagrid-table']//clr-dg-row[2]//clr-dg-cell[1]//a[contains(text(), '${proj1}')] + Retry Wait Element Visible //div[@class='datagrid-table']//clr-dg-row[3]//clr-dg-cell[1]//a[contains(text(), '${proj2}')] + # check project quota sorting in descending order + Retry Element Click xpath=${sort_used_storage_button} + Retry Wait Element Visible //div[@class='datagrid-table']//clr-dg-row[1]//clr-dg-cell[1]//a[contains(text(), '${proj2}')] + Retry Wait Element Visible //div[@class='datagrid-table']//clr-dg-row[2]//clr-dg-cell[1]//a[contains(text(), '${proj1}')] diff --git a/tests/resources/Harbor-Pages/Administration-Project-Quotas_Elements.robot b/tests/resources/Harbor-Pages/Administration-Project-Quotas_Elements.robot new file mode 100644 index 000000000..b73926f78 --- /dev/null +++ b/tests/resources/Harbor-Pages/Administration-Project-Quotas_Elements.robot @@ -0,0 +1,20 @@ +# Copyright Project Harbor Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + +*** Settings *** +Documentation This resource provides any keywords related to the Harbor private registry appliance + +*** Variables *** +${administration_project_quotas_tag_xpath} //clr-vertical-nav-group-children/a[contains(.,'Project Quotas')] +${sort_used_storage_button} //div[@class='datagrid-table']//div[@class='datagrid-header']//button[normalize-space()='Storage'] \ No newline at end of file diff --git a/tests/resources/Util.robot b/tests/resources/Util.robot index b368e9dbb..54972975c 100644 --- a/tests/resources/Util.robot +++ b/tests/resources/Util.robot @@ -50,6 +50,8 @@ Resource Harbor-Pages/Replication.robot Resource Harbor-Pages/Replication_Elements.robot Resource Harbor-Pages/UserProfile.robot Resource Harbor-Pages/UserProfile_Elements.robot +Resource Harbor-Pages/Administration-Project-Quotas.robot +Resource Harbor-Pages/Administration-Project-Quotas_Elements.robot Resource Harbor-Pages/Administration-Users.robot Resource Harbor-Pages/Administration-Users_Elements.robot Resource Harbor-Pages/GC.robot diff --git a/tests/robot-cases/Group1-Nightly/Common_GC.robot b/tests/robot-cases/Group1-Nightly/Common_GC.robot index 53befb63f..f1fae6442 100644 --- a/tests/robot-cases/Group1-Nightly/Common_GC.robot +++ b/tests/robot-cases/Group1-Nightly/Common_GC.robot @@ -23,6 +23,25 @@ ${SSH_USER} root ${HARBOR_ADMIN} admin *** Test Cases *** +Test Case - Project Quota Sorting + [Tags] project_quota_sorting + Init Chrome Driver + Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} + ${d1}= Get Current Date result_format=%m%s + Create An New Project And Go Into Project project${d1} + Push Image With Tag ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} project${d1} alpine 2.6 2.6 + ${d2}= Get Current Date result_format=%m%s + Create An New Project And Go Into Project project${d2} + Push Image With Tag ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} project${d2} photon 2.0 2.0 + Switch to Project Quotas Tag + Check Project Quota Sorting project${d1} project${d2} + Go Into Project project${d1} + Delete Repo project${d1} alpine + Go Into Project project${d2} + Delete Repo project${d2} photon + GC Now + Close Browser + Test Case - Garbage Collection Init Chrome Driver ${d}= Get Current Date result_format=%m%s From a3e1b1eb7983ea9897fcbb999bec1a10c053849d Mon Sep 17 00:00:00 2001 From: MinerYang Date: Fri, 2 Feb 2024 18:15:25 +0800 Subject: [PATCH 008/145] add ip_family config in harbor.yml (#19934) add ipFamily config in values.yaml Signed-off-by: yminer update name update prepare and migration update comments Signed-off-by: yminer remove print msg update migrate template update default value update migrating template --- make/harbor.yml.tmpl | 10 +++++++++ .../version_2_10_0/harbor.yml.jinja | 22 +++++++++++++++++++ .../migrations/version_2_9_0/harbor.yml.jinja | 22 +++++++++++++++++++ .../templates/nginx/nginx.https.conf.jinja | 4 ++++ .../prepare/templates/portal/nginx.conf.jinja | 5 +++++ make/photon/prepare/utils/configs.py | 3 +++ make/photon/prepare/utils/nginx.py | 3 ++- make/photon/prepare/utils/portal.py | 1 + 8 files changed, 69 insertions(+), 1 deletion(-) diff --git a/make/harbor.yml.tmpl b/make/harbor.yml.tmpl index 19f1c17ec..763855fd2 100644 --- a/make/harbor.yml.tmpl +++ b/make/harbor.yml.tmpl @@ -17,6 +17,16 @@ https: certificate: /your/certificate/path private_key: /your/private/key/path +# # Harbor will set ipv4 enabled only by defualt if this block is not configured +# # Otherwise, please uncomment this block to configure your own ip_family stacks +# ip_family: +# # ipv6Enabled set to true if ipv6 is enabled in docker network, currently it affected the nginx related component +# ipv6: +# enabled: false +# # ipv4Enabled set to true by default, currently it affected the nginx related component +# ipv4: +# enabled: true + # # Uncomment following will enable tls communication between all harbor components # internal_tls: # # set enabled to true means internal tls is enabled diff --git a/make/photon/prepare/migrations/version_2_10_0/harbor.yml.jinja b/make/photon/prepare/migrations/version_2_10_0/harbor.yml.jinja index 44a46968f..23e25dcb3 100644 --- a/make/photon/prepare/migrations/version_2_10_0/harbor.yml.jinja +++ b/make/photon/prepare/migrations/version_2_10_0/harbor.yml.jinja @@ -33,6 +33,28 @@ https: # private_key: /your/private/key/path {% endif %} +{% if ip_family is defined %} +# # Harbor will set ipv4 enabled only by defualt if this block is not configured +# # Otherwise, please uncomment this block to configure your own ip_family stacks +ip_family: + # ipv6Enabled set to true if ipv6 is enabled in docker network, currently it affected the nginx related component + ipv6: + enabled: {{ ip_family.ipv6.enabled | lower }} + # ipv4Enabled set to true by default, currently it affected the nginx related component + ipv4: + enabled: {{ ip_family.ipv4.enabled | lower }} +{% else %} +# # Harbor will set ipv4 enabled only by defualt if this block is not configured +# # Otherwise, please uncomment this block to configure your own ip_family stacks +ip_family: + # ipv6Enabled set to true if ipv6 is enabled in docker network, currently it affected the nginx related component + ipv6: + enabled: false + # ipv4Enabled set to true by default, currently it affected the nginx related component + ipv4: + enabled: true +{% endif %} + {% if internal_tls is defined %} # Uncomment following will enable tls communication between all harbor components internal_tls: diff --git a/make/photon/prepare/migrations/version_2_9_0/harbor.yml.jinja b/make/photon/prepare/migrations/version_2_9_0/harbor.yml.jinja index 1ab5ee906..2c429365a 100644 --- a/make/photon/prepare/migrations/version_2_9_0/harbor.yml.jinja +++ b/make/photon/prepare/migrations/version_2_9_0/harbor.yml.jinja @@ -33,6 +33,28 @@ https: # private_key: /your/private/key/path {% endif %} +{% if ip_family is defined %} +# # Harbor will set ipv4 enabled only by defualt if this block is not configured +# # Otherwise, please uncomment this block to configure your own ip_family stacks +ip_family: + # ipv6Enabled set to true if ipv6 is enabled in docker network, currently it affected the nginx related component + ipv6: + enabled: {{ ip_family.ipv6.enabled | lower }} + # ipv4Enabled set to true by default, currently it affected the nginx related component + ipv4: + enabled: {{ ip_family.ipv4.enabled | lower }} +{% else %} +# # Harbor will set ipv4 enabled only by defualt if this block is not configured +# # Otherwise, please uncomment this block to configure your own ip_family stacks +ip_family: + # ipv6Enabled set to true if ipv6 is enabled in docker network, currently it affected the nginx related component + ipv6: + enabled: false + # ipv4Enabled set to true by default, currently it affected the nginx related component + ipv4: + enabled: true +{% endif %} + {% if internal_tls is defined %} # Uncomment following will enable tls communication between all harbor components internal_tls: diff --git a/make/photon/prepare/templates/nginx/nginx.https.conf.jinja b/make/photon/prepare/templates/nginx/nginx.https.conf.jinja index 4c62ae79a..a5d0d8037 100644 --- a/make/photon/prepare/templates/nginx/nginx.https.conf.jinja +++ b/make/photon/prepare/templates/nginx/nginx.https.conf.jinja @@ -50,8 +50,12 @@ http { include /etc/nginx/conf.d/*.server.conf; server { + {% if ip_family.ipv4.enabled %} listen 8443 ssl; + {% endif %} + {% if ip_family.ipv6.enabled %} listen [::]:8443 ssl; + {% endif %} # server_name harbordomain.com; server_tokens off; # SSL diff --git a/make/photon/prepare/templates/portal/nginx.conf.jinja b/make/photon/prepare/templates/portal/nginx.conf.jinja index 01de758fe..3f17e7176 100644 --- a/make/photon/prepare/templates/portal/nginx.conf.jinja +++ b/make/photon/prepare/templates/portal/nginx.conf.jinja @@ -16,8 +16,13 @@ http { server { {% if internal_tls.enabled %} + #ip_family + {% if ip_family.ipv4.enabled %} listen 8443 ssl; + {% endif %} + {% if ip_family.ipv6.enabled %} listen [::]:8443 ssl; + {% endif %} # SSL ssl_certificate /etc/harbor/tls/portal.crt; ssl_certificate_key /etc/harbor/tls/portal.key; diff --git a/make/photon/prepare/utils/configs.py b/make/photon/prepare/utils/configs.py index bb3024935..f47ff4c40 100644 --- a/make/photon/prepare/utils/configs.py +++ b/make/photon/prepare/utils/configs.py @@ -298,6 +298,9 @@ def parse_yaml_config(config_file_path, with_trivy): external_database=config_dict['external_database']) else: config_dict['internal_tls'] = InternalTLS() + + # ip_family config + config_dict['ip_family'] = configs.get('ip_family') or {'ipv4': {'enabled': True}, 'ipv6': {'enabled': False}} # metric configs metric_config = configs.get('metric') diff --git a/make/photon/prepare/utils/nginx.py b/make/photon/prepare/utils/nginx.py index 54d4305d4..0b1ffb8a4 100644 --- a/make/photon/prepare/utils/nginx.py +++ b/make/photon/prepare/utils/nginx.py @@ -63,7 +63,8 @@ def render_nginx_template(config_dict): ssl_cert=SSL_CERT_PATH, ssl_cert_key=SSL_CERT_KEY_PATH, internal_tls=config_dict['internal_tls'], - metric=config_dict['metric']) + metric=config_dict['metric'], + ip_family=config_dict['ip_family']) location_file_pattern = CUSTOM_NGINX_LOCATION_FILE_PATTERN_HTTPS else: diff --git a/make/photon/prepare/utils/portal.py b/make/photon/prepare/utils/portal.py index a2524827b..d41de1264 100644 --- a/make/photon/prepare/utils/portal.py +++ b/make/photon/prepare/utils/portal.py @@ -14,5 +14,6 @@ def prepare_portal(config_dict): str(portal_conf_template_path), portal_conf, internal_tls=config_dict['internal_tls'], + ip_family=config_dict['ip_family'], uid=DEFAULT_UID, gid=DEFAULT_GID) From dbbc0207d9677d877ca090e73a6d0065ab5a3a4b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 4 Feb 2024 17:04:44 +0800 Subject: [PATCH 009/145] Bump codecov/codecov-action from 3 to 4 (#19936) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v3...v4) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- .github/workflows/CI.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 56ef0fb85..00bc21378 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -89,7 +89,7 @@ jobs: bash ./tests/showtime.sh ./tests/ci/ut_run.sh $IP df -h - name: Codecov For BackEnd - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: file: ./src/github.com/goharbor/harbor/profile.cov flags: unittests @@ -331,7 +331,7 @@ jobs: bash ./tests/showtime.sh ./tests/ci/ui_ut_run.sh df -h - name: Codecov For UI - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: file: ./src/github.com/goharbor/harbor/src/portal/coverage/lcov.info flags: unittests From 42256ed331074d20d4b941bf0070249b722b16ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 4 Feb 2024 17:41:42 +0800 Subject: [PATCH 010/145] Bump kentaro-m/auto-assign-action from 1.2.6 to 2.0.0 (#19929) Bumps [kentaro-m/auto-assign-action](https://github.com/kentaro-m/auto-assign-action) from 1.2.6 to 2.0.0. - [Release notes](https://github.com/kentaro-m/auto-assign-action/releases) - [Commits](https://github.com/kentaro-m/auto-assign-action/compare/v1.2.6...v2.0.0) --- updated-dependencies: - dependency-name: kentaro-m/auto-assign-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- .github/workflows/auto_assign_prs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto_assign_prs.yml b/.github/workflows/auto_assign_prs.yml index 711eba7c5..7331fd620 100644 --- a/.github/workflows/auto_assign_prs.yml +++ b/.github/workflows/auto_assign_prs.yml @@ -13,6 +13,6 @@ jobs: runs-on: ubuntu-latest steps: - name: Set the author of a PR as the assignee - uses: kentaro-m/auto-assign-action@v1.2.6 + uses: kentaro-m/auto-assign-action@v2.0.0 with: configuration-path: ".github/auto-assignees.yml" From f562c3016d9df141cf1c91fe0da82229f42c0991 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 4 Feb 2024 18:32:12 +0800 Subject: [PATCH 011/145] Bump github.com/aws/aws-sdk-go from 1.34.28 to 1.50.5 in /src (#19920) Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.34.28 to 1.50.5. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.34.28...v1.50.5) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- src/go.mod | 8 ++++---- src/go.sum | 6 ++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/go.mod b/src/go.mod index 86913fd02..567ce793c 100644 --- a/src/go.mod +++ b/src/go.mod @@ -7,7 +7,7 @@ require ( github.com/Masterminds/semver v1.5.0 github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190726115642-cd293c93fd97 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 - github.com/aws/aws-sdk-go v1.34.28 + github.com/aws/aws-sdk-go v1.50.5 github.com/beego/beego/v2 v2.0.6 github.com/beego/i18n v0.0.0-20140604031826-e87155e8f0c0 github.com/bmatcuk/doublestar v1.3.4 @@ -21,12 +21,12 @@ require ( github.com/go-asn1-ber/asn1-ber v1.5.1 github.com/go-ldap/ldap/v3 v3.2.4 github.com/go-openapi/errors v0.20.4 - github.com/go-openapi/loads v0.21.2 + github.com/go-openapi/loads v0.21.2 // indirect github.com/go-openapi/runtime v0.26.2 - github.com/go-openapi/spec v0.20.11 + github.com/go-openapi/spec v0.20.11 // indirect github.com/go-openapi/strfmt v0.21.8 github.com/go-openapi/swag v0.22.7 - github.com/go-openapi/validate v0.22.3 + github.com/go-openapi/validate v0.22.3 // indirect 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 diff --git a/src/go.sum b/src/go.sum index 2edddf473..7aebc1853 100644 --- a/src/go.sum +++ b/src/go.sum @@ -75,8 +75,8 @@ github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:W github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= -github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk= -github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= +github.com/aws/aws-sdk-go v1.50.5 h1:H2Aadcgwr7a2aqS6ZwcE+l1mA6ZrTseYCvjw2QLmxIA= +github.com/aws/aws-sdk-go v1.50.5/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= github.com/beego/beego/v2 v2.0.6 h1:21Aqz3+RzUE1yP9a5xdU6LK54n9Z7NLEJtR4PE7NrPQ= github.com/beego/beego/v2 v2.0.6/go.mod h1:CH2/JIaB4ceGYVQlYqTAFft4pVk/ol1ZkakUrUvAyns= @@ -209,7 +209,6 @@ github.com/go-openapi/validate v0.22.3 h1:KxG9mu5HBRYbecRb37KRCihvGGtND2aXziBAv0 github.com/go-openapi/validate v0.22.3/go.mod h1:kVxh31KbfsxU8ZyoHaDbLBWU5CnMdqBUEtadQ2G4d5M= github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -727,7 +726,6 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= From 7cfc685b7aa3863a3b93f0c993eb0941f6279507 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 4 Feb 2024 19:27:48 +0800 Subject: [PATCH 012/145] Bump github.com/go-openapi/errors from 0.20.4 to 0.21.0 in /src (#19890) Bumps [github.com/go-openapi/errors](https://github.com/go-openapi/errors) from 0.20.4 to 0.21.0. - [Commits](https://github.com/go-openapi/errors/compare/v0.20.4...v0.21.0) --- updated-dependencies: - dependency-name: github.com/go-openapi/errors dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: Shengwen YU Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- src/go.mod | 2 +- src/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/go.mod b/src/go.mod index 567ce793c..4c264efb5 100644 --- a/src/go.mod +++ b/src/go.mod @@ -20,7 +20,7 @@ require ( github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 github.com/go-asn1-ber/asn1-ber v1.5.1 github.com/go-ldap/ldap/v3 v3.2.4 - github.com/go-openapi/errors v0.20.4 + github.com/go-openapi/errors v0.21.0 github.com/go-openapi/loads v0.21.2 // indirect github.com/go-openapi/runtime v0.26.2 github.com/go-openapi/spec v0.20.11 // indirect diff --git a/src/go.sum b/src/go.sum index 7aebc1853..9914a4f0a 100644 --- a/src/go.sum +++ b/src/go.sum @@ -179,8 +179,8 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc= github.com/go-openapi/analysis v0.21.4/go.mod h1:4zQ35W4neeZTqh3ol0rv/O8JBbka9QyAgQRPp9y3pfo= github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/errors v0.20.4 h1:unTcVm6PispJsMECE3zWgvG4xTiKda1LIR5rCRWLG6M= -github.com/go-openapi/errors v0.20.4/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk= +github.com/go-openapi/errors v0.21.0 h1:FhChC/duCnfoLj1gZ0BgaBmzhJC2SL/sJr8a2vAobSY= +github.com/go-openapi/errors v0.21.0/go.mod h1:jxNTMUxRCKj65yb/okJGEtahVd7uvWnuWfj53bse4ho= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= From db20b3b6aca833f5b6a8ca893141508db56a4433 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 4 Feb 2024 20:08:50 +0800 Subject: [PATCH 013/145] Bump github.com/go-ldap/ldap/v3 from 3.2.4 to 3.4.6 in /src (#19889) Bumps [github.com/go-ldap/ldap/v3](https://github.com/go-ldap/ldap) from 3.2.4 to 3.4.6. - [Release notes](https://github.com/go-ldap/ldap/releases) - [Commits](https://github.com/go-ldap/ldap/compare/v3.2.4...v3.4.6) --- updated-dependencies: - dependency-name: github.com/go-ldap/ldap/v3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: Shengwen YU Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- src/go.mod | 6 +++--- src/go.sum | 26 +++++++++++++++++++------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/go.mod b/src/go.mod index 4c264efb5..e8a84ace4 100644 --- a/src/go.mod +++ b/src/go.mod @@ -18,8 +18,8 @@ require ( github.com/dghubble/sling v1.1.0 github.com/docker/distribution v2.8.2+incompatible github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 - github.com/go-asn1-ber/asn1-ber v1.5.1 - github.com/go-ldap/ldap/v3 v3.2.4 + github.com/go-asn1-ber/asn1-ber v1.5.5 + github.com/go-ldap/ldap/v3 v3.4.6 github.com/go-openapi/errors v0.21.0 github.com/go-openapi/loads v0.21.2 // indirect github.com/go-openapi/runtime v0.26.2 @@ -88,7 +88,7 @@ require ( github.com/Azure/go-autorest/autorest/to v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c // indirect + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/Unknwon/goconfig v0.0.0-20160216183935-5f601ca6ef4d // indirect diff --git a/src/go.sum b/src/go.sum index 9914a4f0a..1824a5427 100644 --- a/src/go.sum +++ b/src/go.sum @@ -43,8 +43,8 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= -github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/FZambia/sentinel v1.1.0 h1:qrCBfxc8SvJihYNjBWgwUI93ZCvFe/PJIPTHKmlp8a8= @@ -62,6 +62,8 @@ github.com/Unknwon/goconfig v0.0.0-20160216183935-5f601ca6ef4d h1:RjxaKUAINjr+fY github.com/Unknwon/goconfig v0.0.0-20160216183935-5f601ca6ef4d/go.mod h1:wngxua9XCNjvHjDiTiV26DaKDT+0c63QR6H5hjVUUxw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= +github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190726115642-cd293c93fd97 h1:bNE5ID4C3YOkROfvBjXJUG53gyb+8az3TQN02LqnGBk= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190726115642-cd293c93fd97/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ= github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= @@ -159,15 +161,15 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= -github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= +github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-ldap/ldap/v3 v3.2.4 h1:PFavAq2xTgzo/loE8qNXcQaofAaqIpI4WgaLdv+1l3E= -github.com/go-ldap/ldap/v3 v3.2.4/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg= +github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A= +github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= @@ -674,7 +676,6 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -683,6 +684,7 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -709,6 +711,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -736,6 +739,7 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -752,6 +756,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -791,12 +796,16 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.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.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -808,6 +817,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -841,6 +852,7 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From f910c5654baf64a090f5b423ee6990d9f9adbc61 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:03:07 +0800 Subject: [PATCH 014/145] Bump vite and @angular-devkit/build-angular in /src/portal (#19945) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) to 4.5.2 and updates ancestor dependency [@angular-devkit/build-angular](https://github.com/angular/angular-cli). These dependencies need to be updated together. Updates `vite` from 4.4.7 to 4.5.2 - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v4.5.2/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v4.5.2/packages/vite) Updates `@angular-devkit/build-angular` from 16.2.9 to 16.2.12 - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/16.2.9...16.2.12) --- updated-dependencies: - dependency-name: vite dependency-type: indirect - dependency-name: "@angular-devkit/build-angular" dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- src/portal/package-lock.json | 172 ++++++++++++++++++++++++----------- src/portal/package.json | 2 +- 2 files changed, 122 insertions(+), 52 deletions(-) diff --git a/src/portal/package-lock.json b/src/portal/package-lock.json index 68ae5a709..d39be637c 100644 --- a/src/portal/package-lock.json +++ b/src/portal/package-lock.json @@ -35,7 +35,7 @@ "zone.js": "~0.13.0" }, "devDependencies": { - "@angular-devkit/build-angular": "^16.2.9", + "@angular-devkit/build-angular": "^16.2.12", "@angular-eslint/builder": "16.1.2", "@angular-eslint/eslint-plugin": "16.1.2", "@angular-eslint/eslint-plugin-template": "16.1.2", @@ -101,15 +101,15 @@ } }, "node_modules/@angular-devkit/build-angular": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-16.2.9.tgz", - "integrity": "sha512-S1C4UYxRVyNt3C0wCxbT2jZ1dN5i37kS0mol3PQjbR8gQ0GQzHmzhjTBl1oImo8aouET9yhrk9etk65oat4mBQ==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-16.2.12.tgz", + "integrity": "sha512-VVGKZ0N3gyR0DP7VrcZl4io3ruWYT94mrlyJsJMLlrYy/EX8JCvqrJC9c+dscrtKjhZzjwdyhszkJQY4JfwACA==", "dev": true, "dependencies": { "@ampproject/remapping": "2.2.1", - "@angular-devkit/architect": "0.1602.9", - "@angular-devkit/build-webpack": "0.1602.9", - "@angular-devkit/core": "16.2.9", + "@angular-devkit/architect": "0.1602.12", + "@angular-devkit/build-webpack": "0.1602.12", + "@angular-devkit/core": "16.2.12", "@babel/core": "7.22.9", "@babel/generator": "7.22.9", "@babel/helper-annotate-as-pure": "7.22.5", @@ -121,7 +121,7 @@ "@babel/runtime": "7.22.6", "@babel/template": "7.22.5", "@discoveryjs/json-ext": "0.5.7", - "@ngtools/webpack": "16.2.9", + "@ngtools/webpack": "16.2.12", "@vitejs/plugin-basic-ssl": "1.0.1", "ansi-colors": "4.1.3", "autoprefixer": "10.4.14", @@ -164,7 +164,7 @@ "text-table": "0.2.0", "tree-kill": "1.2.2", "tslib": "2.6.1", - "vite": "4.4.7", + "vite": "4.5.2", "webpack": "5.88.2", "webpack-dev-middleware": "6.1.1", "webpack-dev-server": "4.15.1", @@ -222,6 +222,48 @@ } } }, + "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/architect": { + "version": "0.1602.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1602.12.tgz", + "integrity": "sha512-19Fwwfx+KvJ01SyI6cstRgqT9+cwer8Ro1T27t1JqlGyOX8tY3pV78ulwxy2+wCzPjR18V6W7cb7Cv6fyK4xog==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "16.2.12", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": { + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.12.tgz", + "integrity": "sha512-o6ziQs+EcEonFezrsA46jbZqkQrs4ckS1bAQj93g5ZjGtieUz8l/U3lclvKpL/iEzWkGVViSYuP2KyW2oqTDiQ==", + "dev": true, + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.0", + "picomatch": "2.3.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, "node_modules/@angular-devkit/build-angular/node_modules/@babel/core": { "version": "7.22.9", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.9.tgz", @@ -701,12 +743,12 @@ "dev": true }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1602.9", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1602.9.tgz", - "integrity": "sha512-+3IxovfBPR2Vy730mGa0SVKkd5LQVom85gjXOs7WcnnnZmfc1q/BtFlqTgW1UWvTxP8IQdm7UYWVclQfL/WExw==", + "version": "0.1602.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1602.12.tgz", + "integrity": "sha512-1lmR4jCkxPJuAFXReesEY3CB+/5jSebGE5ry6qJJvNm6kuSc9bzfTytrcwosVY+Q7kAA2ij7kAYw0loGbTjLWA==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1602.9", + "@angular-devkit/architect": "0.1602.12", "rxjs": "7.8.1" }, "engines": { @@ -719,6 +761,48 @@ "webpack-dev-server": "^4.0.0" } }, + "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/architect": { + "version": "0.1602.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1602.12.tgz", + "integrity": "sha512-19Fwwfx+KvJ01SyI6cstRgqT9+cwer8Ro1T27t1JqlGyOX8tY3pV78ulwxy2+wCzPjR18V6W7cb7Cv6fyK4xog==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "16.2.12", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/core": { + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.12.tgz", + "integrity": "sha512-o6ziQs+EcEonFezrsA46jbZqkQrs4ckS1bAQj93g5ZjGtieUz8l/U3lclvKpL/iEzWkGVViSYuP2KyW2oqTDiQ==", + "dev": true, + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.0", + "picomatch": "2.3.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, "node_modules/@angular-devkit/core": { "version": "16.2.9", "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.9.tgz", @@ -3912,9 +3996,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-16.2.9.tgz", - "integrity": "sha512-rOclD7FfT4OSwVA0nDnULbJS6TORJ0+sQiuT2ebaNFErYr3LOm6Zut05tnmzFw8q1cePrILbG+xpnbggNr9Pyw==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-16.2.12.tgz", + "integrity": "sha512-f9R9Qsk8v+ffDxryl6PQ7Wnf2JCNd4dDXOH+d/AuF06VFiwcwGDRDZpmqkAXbFxQfcWTbT1FFvfoJ+SFcJgXLA==", "dev": true, "engines": { "node": "^16.14.0 || >=18.10.0", @@ -4504,9 +4588,9 @@ } }, "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.3.tgz", - "integrity": "sha512-6mfQ6iNvhSKCZJoY6sIG3m0pKkdUcweVNOLuBBKvoWGzl2yRxOJcYOTRyLKt3nxXvBLJWa6QkW//tgbIwJehmA==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", "dev": true, "dependencies": { "@types/express-serve-static-core": "*", @@ -4623,9 +4707,9 @@ "dev": true }, "node_modules/@types/node-forge": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.9.tgz", - "integrity": "sha512-meK88cx/sTalPSLSoCzkiUB4VPIFHmxtXm5FaaqRDqBX2i/Sy8bJ4odsan0b20RBjPh06dAQ+OTTdnyQyhJZyQ==", + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", "dev": true, "dependencies": { "@types/node": "*" @@ -4735,9 +4819,9 @@ "integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==" }, "node_modules/@types/ws": { - "version": "8.5.9", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.9.tgz", - "integrity": "sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg==", + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", "dev": true, "dependencies": { "@types/node": "*" @@ -6235,23 +6319,15 @@ "dev": true }, "node_modules/bonjour-service": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", - "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", + "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", "dev": true, "dependencies": { - "array-flatten": "^2.1.2", - "dns-equal": "^1.0.0", "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } }, - "node_modules/bonjour-service/node_modules/array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", - "dev": true - }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -8633,12 +8709,6 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "dev": true }, - "node_modules/dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", - "dev": true - }, "node_modules/dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", @@ -19006,14 +19076,14 @@ } }, "node_modules/vite": { - "version": "4.4.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.7.tgz", - "integrity": "sha512-6pYf9QJ1mHylfVh39HpuSfMPojPSKVxZvnclX1K1FyZ1PXDOcLBibdq5t1qxJSnL63ca8Wf4zts6mD8u8oc9Fw==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz", + "integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==", "dev": true, "dependencies": { "esbuild": "^0.18.10", - "postcss": "^8.4.26", - "rollup": "^3.25.2" + "postcss": "^8.4.27", + "rollup": "^3.27.1" }, "bin": { "vite": "bin/vite.js" @@ -19484,9 +19554,9 @@ } }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "dev": true, "engines": { "node": ">=10.0.0" @@ -20043,4 +20113,4 @@ "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" } } -} \ No newline at end of file +} diff --git a/src/portal/package.json b/src/portal/package.json index 6a25a0b2a..791516e3d 100644 --- a/src/portal/package.json +++ b/src/portal/package.json @@ -53,7 +53,7 @@ "zone.js": "~0.13.0" }, "devDependencies": { - "@angular-devkit/build-angular": "^16.2.9", + "@angular-devkit/build-angular": "^16.2.12", "@angular-eslint/builder": "16.1.2", "@angular-eslint/eslint-plugin": "16.1.2", "@angular-eslint/eslint-plugin-template": "16.1.2", From 84b31aaf7f07830867bbe7271e73920ebdcb52d9 Mon Sep 17 00:00:00 2001 From: MinerYang Date: Wed, 7 Feb 2024 16:41:16 +0800 Subject: [PATCH 015/145] remove ipfamily config migrate jinja in 2.9 and 2.10 (#19949) remove ipfamily config migrate in 2.9 and 2.10 Signed-off-by: yminer --- .../version_2_10_0/harbor.yml.jinja | 22 ------------------- .../migrations/version_2_9_0/harbor.yml.jinja | 22 ------------------- 2 files changed, 44 deletions(-) diff --git a/make/photon/prepare/migrations/version_2_10_0/harbor.yml.jinja b/make/photon/prepare/migrations/version_2_10_0/harbor.yml.jinja index 23e25dcb3..44a46968f 100644 --- a/make/photon/prepare/migrations/version_2_10_0/harbor.yml.jinja +++ b/make/photon/prepare/migrations/version_2_10_0/harbor.yml.jinja @@ -33,28 +33,6 @@ https: # private_key: /your/private/key/path {% endif %} -{% if ip_family is defined %} -# # Harbor will set ipv4 enabled only by defualt if this block is not configured -# # Otherwise, please uncomment this block to configure your own ip_family stacks -ip_family: - # ipv6Enabled set to true if ipv6 is enabled in docker network, currently it affected the nginx related component - ipv6: - enabled: {{ ip_family.ipv6.enabled | lower }} - # ipv4Enabled set to true by default, currently it affected the nginx related component - ipv4: - enabled: {{ ip_family.ipv4.enabled | lower }} -{% else %} -# # Harbor will set ipv4 enabled only by defualt if this block is not configured -# # Otherwise, please uncomment this block to configure your own ip_family stacks -ip_family: - # ipv6Enabled set to true if ipv6 is enabled in docker network, currently it affected the nginx related component - ipv6: - enabled: false - # ipv4Enabled set to true by default, currently it affected the nginx related component - ipv4: - enabled: true -{% endif %} - {% if internal_tls is defined %} # Uncomment following will enable tls communication between all harbor components internal_tls: diff --git a/make/photon/prepare/migrations/version_2_9_0/harbor.yml.jinja b/make/photon/prepare/migrations/version_2_9_0/harbor.yml.jinja index 2c429365a..1ab5ee906 100644 --- a/make/photon/prepare/migrations/version_2_9_0/harbor.yml.jinja +++ b/make/photon/prepare/migrations/version_2_9_0/harbor.yml.jinja @@ -33,28 +33,6 @@ https: # private_key: /your/private/key/path {% endif %} -{% if ip_family is defined %} -# # Harbor will set ipv4 enabled only by defualt if this block is not configured -# # Otherwise, please uncomment this block to configure your own ip_family stacks -ip_family: - # ipv6Enabled set to true if ipv6 is enabled in docker network, currently it affected the nginx related component - ipv6: - enabled: {{ ip_family.ipv6.enabled | lower }} - # ipv4Enabled set to true by default, currently it affected the nginx related component - ipv4: - enabled: {{ ip_family.ipv4.enabled | lower }} -{% else %} -# # Harbor will set ipv4 enabled only by defualt if this block is not configured -# # Otherwise, please uncomment this block to configure your own ip_family stacks -ip_family: - # ipv6Enabled set to true if ipv6 is enabled in docker network, currently it affected the nginx related component - ipv6: - enabled: false - # ipv4Enabled set to true by default, currently it affected the nginx related component - ipv4: - enabled: true -{% endif %} - {% if internal_tls is defined %} # Uncomment following will enable tls communication between all harbor components internal_tls: From 1e85cab33aee22d85d533e819a101afb7575ba44 Mon Sep 17 00:00:00 2001 From: Shengwen YU Date: Sun, 11 Feb 2024 19:24:57 +0800 Subject: [PATCH 016/145] update retry of get_scan_data_export_execution from 5 to 15 (#19959) Signed-off-by: Shengwen Yu --- tests/apitests/python/test_scan_data_export.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/apitests/python/test_scan_data_export.py b/tests/apitests/python/test_scan_data_export.py index ad9fa249f..d8d2ef9e5 100644 --- a/tests/apitests/python/test_scan_data_export.py +++ b/tests/apitests/python/test_scan_data_export.py @@ -93,7 +93,7 @@ class TestScanDataExport(unittest.TestCase): # 10. Wait for the export scan data execution to succeed execution = None - for i in range(5): + for i in range(15): print("wait for the job to finish:", i) execution = self.scan_data_export.get_scan_data_export_execution(execution_id, **user_client) if execution.status == "Success": From 0e580836bb80fc60c3b5ec65058d47b05b8c78a1 Mon Sep 17 00:00:00 2001 From: tostt Date: Sun, 11 Feb 2024 15:21:10 +0100 Subject: [PATCH 017/145] Updated internationalisation : fr-fr (#19915) * Internationalisation : fr-fr Edited file fr-fr-lang.json: - Translation of new strings in French - Entries have been sorted the same way as en-us-lang.json Signed-off-by: tostt * Update fr-fr-lang.json Corrections Signed-off-by: tostt * Update src/portal/src/i18n/lang/fr-fr-lang.json Co-authored-by: Thomas Coudert Signed-off-by: tostt * Update src/portal/src/i18n/lang/fr-fr-lang.json Co-authored-by: Thomas Coudert Signed-off-by: tostt * Added missing translation Signed-off-by: tostt --------- Signed-off-by: tostt Co-authored-by: Thomas Coudert --- src/portal/src/i18n/lang/fr-fr-lang.json | 74 ++++++++++++++++-------- 1 file changed, 50 insertions(+), 24 deletions(-) diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json index 91b09b8b5..3f11ca729 100644 --- a/src/portal/src/i18n/lang/fr-fr-lang.json +++ b/src/portal/src/i18n/lang/fr-fr-lang.json @@ -44,6 +44,8 @@ "NEGATIVE": "Négatif", "COPY": "Copier", "EDIT": "Éditer", + "SWITCH": "Basculer", + "REPLICATE": "Répliquer", "ACTIONS": "Actions", "BROWSE": "Parcourir", "UPLOAD": "Téléverser", @@ -92,6 +94,8 @@ "CRON_REQUIRED": "Le champ est obligatoire et doit être au format cron.", "EMAIL_EXISTING": "L'adresse e-mail existe déjà.", "USER_EXISTING": "Le nom d'utilisateur est déjà utilisé.", + "RULE_USER_EXISTING": "Le nom est déjà utilisé.", + "EMPTY": "Le nom est requis", "NONEMPTY": "Ne peut pas être vide", "REPO_TOOLTIP": "Les utilisateurs ne peuvent effectuer aucune opération sur les images dans ce mode.", "ENDPOINT_FORMAT": "L'endpoint doit commencer par HTTP:// or HTTPS://.", @@ -127,9 +131,11 @@ "RENAME_SUCCESS": "Renommage effectué !", "RENAME_CONFIRM_INFO": "Attention, changer le nom d'utilisateur pour \"admin@harbor.local\" ne peut pas être annulé.", "CLI_PASSWORD": "CLI secret", - "CLI_PASSWORD_TIP": "The CLI secret can be used as a password, for Docker or Helm client. With the OIDC auth mode enabled, we strongly recommend using robot accounts, as CLI secrets depend on the validity of the ID token and require the user to regularly log in to the UI to refresh the token.", + "CLI_PASSWORD_TIP": "Le secret CLI peut être utilisé comme mot de passe pour le client Docker ou Helm. Lorsque le mode d'authentification est OIDC, nous recommandons fortement d'utiliser des comptes robots, car les secrets CLI dépendent de la validité du jeton ID et nécessitent que l'utilisateur se connecte régulièrement à l'interface utilisateur pour rafraîchir le jeton.", "COPY_SUCCESS": "Copie effectuée", "COPY_ERROR": "Copie échouée", + "ADMIN_CLI_SECRET_BUTTON": "Générer le secret", + "ADMIN_CLI_SECRET_RESET_BUTTON": "Téléversez votre secret", "NEW_SECRET": "Secret", "CONFIRM_SECRET": "Ré-entrez votre Secret", "GENERATE_SUCCESS": "Mise en place du CLI secret effectuée", @@ -287,8 +293,19 @@ "NEW_MEMBER": "Nouveau membre", "MEMBER": "Membre", "NAME": "Nom", - "EMAIL": "Email", "ROLE": "Rôle", + "SYS_ADMIN": "Administrateur système", + "PROJECT_ADMIN": "Administrateur de projet", + "PROJECT_MAINTAINER": "Mainteneur du projet", + "DEVELOPER": "Développeur", + "GUEST": "Invité", + "LIMITED_GUEST": "Invité avec droits limités", + "DELETE": "Supprimer", + "ITEMS": "entrées", + "ACTIONS": "Actions", + "USER": " Utilisateur", + "USERS": "Utilisateurs", + "EMAIL": "Email", "ADD_USER": "Ajouter un utilisateur", "NEW_USER_INFO": "Ajouter un utilisateur pour être membre de ce projet avec le rôle spécifié", "NEW_GROUP": "Nouveau groupe", @@ -302,20 +319,9 @@ "LDAP_GROUPS": "Groupes", "LDAP_PROPERTY": "Propriété", "ACTION": "Action", - "USER": " Utilisateur", - "USERS": "Utilisateurs", "MEMBER_TYPE": "Type de membre", "GROUP_TYPE": "Groupe", "USER_TYPE": "Utilisateur", - "SYS_ADMIN": "Administrateur système", - "PROJECT_ADMIN": "Administrateur de projet", - "PROJECT_MAINTAINER": "Mainteneur du projet", - "DEVELOPER": "Développeur", - "GUEST": "Invité", - "LIMITED_GUEST": "Invité avec droits limités", - "DELETE": "Supprimer", - "ITEMS": "entrées", - "ACTIONS": "Actions", "USERNAME_IS_REQUIRED": "Nom d'utilisateur requis", "USERNAME_DOES_NOT_EXISTS": "Ce nom d'utilisateur n'existe pas.", "USERNAME_ALREADY_EXISTS": "Ce nom d'utilisateur existe déjà.", @@ -327,6 +333,8 @@ "DELETED_SUCCESS": "Membre supprimé avec succès.", "SWITCHED_SUCCESS": "Rôle du membre changé avec succès.", "OF": "sur", + "SWITCH_TITLE": "Confirmez le changement de membres projet", + "SWITCH_SUMMARY": "Voulez-vous changer les membres projet {{param}}?", "SET_ROLE": "Définir Role", "REMOVE": "Remove", "GROUP_NAME_REQUIRED": "Le nom du groupe est requis", @@ -339,11 +347,11 @@ "TOKEN": "Jeton", "NEW_ROBOT_ACCOUNT": "Nouveau compte robot", "ENABLED_STATE": "Etat d'activation", + "NUMBER_REQUIRED": "Le champ est requis et doit être un entier autre que 0.", + "DESCRIPTION": "Description", "CREATION": "Date et heure de création", "EXPIRATION": "Date et heure d'expiration", - "NUMBER_REQUIRED": "Le champ est requis et doit être un entier autre que 0.", "TOKEN_EXPIRATION": "Expiration du jeton du compte robot (jours)", - "DESCRIPTION": "Description", "ACTION": "Action", "EDIT": "Éditer", "ITEMS": "entrées", @@ -542,6 +550,7 @@ "OVERRIDE": "Surcharger", "ENABLED_RULE": "Activer la règle", "OVERRIDE_INFO": "Surcharger", + "OPERATION": "Opération", "CURRENT": "courant", "FILTER_PLACEHOLDER": "Filtrer les tâches", "STOP_TITLE": "Confirmer l'arrêt des exécutions", @@ -558,8 +567,6 @@ "SUCCESS": "Succès", "FAILURE": "Échec", "IN_PROGRESS": "En cours", - "STOP_EXECUTIONS": "Arrêter les exécutions", - "ID": "ID", "REPLICATION_RULE": "Règle de réplication", "NEW_REPLICATION_RULE": "Nouvelle règle de réplication", "ENDPOINTS": "Endpoints", @@ -567,8 +574,11 @@ "FILTER_EXECUTIONS_PLACEHOLDER": "Filtrer les exécutions", "DELETION_TITLE": "Confirmer la suppression de la règle", "DELETION_SUMMARY": "Voulez-vous supprimer la règle {{param}} ?", + "REPLICATION_TITLE": "Confirmer la règle de réplication", + "REPLICATION_SUMMARY": "Voulez-vous répliquer la règle {{param}}?", "DELETION_TITLE_FAILURE": "la règle {{param}} n'a pas été supprimée", "DELETION_SUMMARY_FAILURE": "{{param}} ont le statut en attente/en fonctionnement/en train de réessayer", + "REPLICATE_SUMMARY_FAILURE": "ont le statut pending/running", "FILTER_TARGETS_PLACEHOLDER": "Filtrer les endpoints", "DELETION_TITLE_TARGET": "Confirmer la suppression de l'endpoint", "DELETION_SUMMARY_TARGET": "Voulez-vous supprimer l'endpoint {{param}} ?", @@ -580,8 +590,8 @@ "TESTING_CONNECTION": "En train de tester la connexion...", "TEST_CONNECTION_SUCCESS": "Connexion testée avec succès.", "TEST_CONNECTION_FAILURE": "Échec du ping de l'endpoint.", + "ID": "ID", "NAME": "Nom", - "PROJECT": "Projet", "NAME_IS_REQUIRED": "Le nom est obligatoire.", "DESCRIPTION": "Description", "ENABLE": "Activer", @@ -602,8 +612,8 @@ "ACTIVATION": "Activation", "REPLICATION_EXECUTION": "Travaux de réplication", "REPLICATION_EXECUTIONS": "Travaux de réplication", + "STOPJOB": "Stop", "ALL": "Tous", - "END_TIME": "Fin", "PENDING": "En attente", "RUNNING": "En fonctionnement", "ERROR": "Erreur", @@ -617,6 +627,7 @@ "OPERATION": "Opération", "CREATION_TIME": "Heure de départ", "UPDATE_TIME": "Heure de mise à jour", + "END_TIME": "Fin", "LOGS": "Logs", "OF": "sur", "ITEMS": "entrées", @@ -712,6 +723,7 @@ "TEST_CONNECTION": "Test de connexion", "TITLE_EDIT": "Éditer l'endpoint", "TITLE_ADD": "Nouveau endpoint de registre", + "EDIT": "Éditer", "DELETE": "Supprimer l'endpoint", "TESTING_CONNECTION": "En train de tester la connexion...", "TEST_CONNECTION_SUCCESS": "Connexion testée avec succès.", @@ -773,12 +785,14 @@ "REPOSITORIES": "Dépôts", "OF": "sur", "ITEMS": "entrées", + "NO_ITEMS": "Aucune entrée", "POP_REPOS": "Dépôts populaires", "DELETED_REPO_SUCCESS": "Dépôt supprimé avec succès.", "DELETED_TAG_SUCCESS": "Tag supprimé avec succès.", "COPY": "Copier", "NOTARY_IS_UNDETERMINED": "Ne peut pas déterminer la signature de ce tag.", "PLACEHOLDER": "Nous n'avons trouvé aucun dépôt !", + "INFO": "Info", "NO_INFO": "Pas de description pour ce dépôt. Vous pouvez l'ajouter à ce dépôt.", "IMAGE": "Images", "LABELS": "Labels", @@ -830,10 +844,13 @@ "AUTH": "Authentification", "REPLICATION": "Réplication", "LABEL": "Labels", + "REPOSITORY": "Dépôt", "REPO_READ_ONLY": "Dépôt en lecture seule", "WEBHOOK_NOTIFICATION_ENABLED": "Webhooks activés", "SYSTEM": "Réglages Système", "PROJECT_QUOTAS": "Quotas de projet", + "VULNERABILITY": "Vulnérabilité", + "GC": "Purge", "CONFIRM_TITLE": "Confirmer pour annuler", "CONFIRM_SUMMARY": "Certaines modifications n'ont pas été sauvegardées. Voulez-vous les annuler ?", "SAVE_SUCCESS": "La configuration a été sauvegardée avec succès.", @@ -847,6 +864,7 @@ "SELF_REGISTRATION": "Autoriser l'inscription", "AUTH_MODE_DB": "Base de données", "AUTH_MODE_LDAP": "LDAP", + "AUTH_MODE_UAA": "UAA", "AUTH_MODE_HTTP": "Authentification HTTP", "AUTH_MODE_OIDC": "OIDC", "SCOPE_BASE": "Base", @@ -856,12 +874,10 @@ "PRO_CREATION_ADMIN": "Administrateurs seulement", "ROOT_CERT": "Enregistrer le certificat racine", "ROOT_CERT_LINK": "Télécharger", - "GC": "Purge", "REGISTRY_CERTIFICATE": "Certificat du registre", "NO_CHANGE": "Enregistrement abandonné, rien n'a changé", "SKIP_SCANNER_PULL_TIME": "Conserver la date/heure de pull lors d'un scan", "TOOLTIP": { - "REPO_TOOLTIP": "Les utilisateurs ne peuvent effectuer aucune opération sur les images dans ce mode.", "SELF_REGISTRATION_ENABLE": "Activer l'inscription.", "SELF_REGISTRATION_DISABLE": "Désactiver l'inscription.", "VERIFY_REMOTE_CERT": "Détermine si la réplication de l'image doit vérifier le certificat d'un dépôt Harbor distant. Décochez cette case lorsque le registre distant utilise un certificat auto-signé ou non approuvé.", @@ -877,6 +893,8 @@ "PRO_CREATION_RESTRICTION": "L'indicateur pour définir quels utilisateurs ont le droit de créer des projets. Par défaut, tout le monde peut créer un projet. Définissez sur 'Administrateur Seulement' pour que seul un administrateur puisse créer un projet.", "ROOT_CERT_DOWNLOAD": "Télécharger le certificat racine du dépôt.", "SCANNING_POLICY": "Définissez la politique d'analyse des images en fonction des différentes exigences. 'Aucune' : pas de politique active; 'Tous les jours à' : déclenchement de l'analyse à l'heure spécifiée tous les jours.", + "VERIFY_CERT": "Verifier le certificat du serveur LDAP", + "REPO_TOOLTIP": "Les utilisateurs ne peuvent effectuer aucune opération sur les images dans ce mode.", "WEBHOOK_TOOLTIP": "Activez les webhooks pour recevoir des callbacks sur vos endpoint désignés lorsque certaines actions telles que l'image ou le chart est poussé, tiré, supprimé, scanné sont effectuées", "HOURLY_CRON": "Exécuter une fois par heure, au début de l'heure. Équivalent à 0 0 * * * *.", "WEEKLY_CRON": "Exécuter une fois par semaine, à minuit entre Sam. et Dim. Équivalent à 0 0 0 * * 0.", @@ -905,6 +923,12 @@ "GROUP_SCOPE": "Scope des groupes LDAP", "GROUP_SCOPE_INFO": "Scope dans lequel faire la recherche, 'subtree' par défaut." }, + "UAA": { + "ENDPOINT": "Endpoint UAA", + "CLIENT_ID": "ID client UAA", + "CLIENT_SECRET": "Secret client UAA", + "VERIFY_CERT": "Verification certificat UAA" + }, "HTTP_AUTH": { "ENDPOINT": "Endpoint Serveur", "TOKEN_REVIEW": "Endpoint de revue de token", @@ -1034,8 +1058,6 @@ "HIGH": "Haut", "MEDIUM": "Moyen", "LOW": "Bas", - "NEGLIGIBLE": "Négligeable", - "UNKNOWN": "Inconnue", "NONE": "Aucune" }, "SINGULAR": "vulnérabilité", @@ -1106,6 +1128,7 @@ "HAS": "a", "SCAN_COMPLETION_TIME": "Analyse terminée", "IMAGE_VULNERABILITIES": "Vulnérabilités de l'image", + "LEVEL_VULNERABILITIES": "Vulnérabilités de niveau", "PLACEHOLDER": "Nous ne trouvons aucun tag !", "COPY_ERROR": "Copie échouée, veuillez essayer de copier manuellement.", "FILTER_FOR_TAGS": "Filtrer les tags", @@ -1178,6 +1201,7 @@ "ALL": "Tous", "RUNNING": "En cours", "FAILED": "Échoués", + "STOP_EXECUTIONS": "Interrompre l'exécution", "DELETE_PROJECT": "Suppression de projet", "DELETE_REPO": "Suppression de dépôt", "DELETE_TAG": "Suppression de tag", @@ -1187,6 +1211,8 @@ "DELETE_REPLICATION": "Suppression de réplication", "DELETE_MEMBER": "Suppression de membre", "DELETE_GROUP": "Suppression de membre d'un groupe", + "DELETE_CHART_VERSION": "Supprimer la version de chart", + "DELETE_CHART": "Supprimer chart", "SWITCH_ROLE": "Changement de rôle", "ADD_GROUP": "Ajout de membre à un groupe", "ADD_USER": "Ajout de membre utilisateur", @@ -1854,7 +1880,7 @@ "PREVIOUS_PAGE": "Page précédente", "CURRENT_PAGE": "Page courante", "TOTAL_PAGE": "Pages totales", - "FILTER_ITEMS": "Filtrer les éléments", + "FILTER_ITEMS": "Filtrer les entrées", "MIN_VALUE": "Valeur min", "MAX_VALUE": "Valeur max", "MODAL_CONTENT_START": "Début de la modale", From bca9b14bbfd75e7f7d4c6c9223aaefb76ec07968 Mon Sep 17 00:00:00 2001 From: Shengwen YU Date: Wed, 21 Feb 2024 10:13:52 +0800 Subject: [PATCH 018/145] feat: enable configuration of skip_java_db_update (#19996) Signed-off-by: Shengwen Yu --- Makefile | 4 ++-- make/harbor.yml.tmpl | 4 ++++ make/photon/prepare/templates/trivy-adapter/env.jinja | 1 + make/photon/prepare/utils/configs.py | 1 + 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 857db70f6..e617ef882 100644 --- a/Makefile +++ b/Makefile @@ -104,8 +104,8 @@ PREPARE_VERSION_NAME=versions #versions REGISTRYVERSION=v2.8.3-patch-redis -TRIVYVERSION=v0.47.0 -TRIVYADAPTERVERSION=v0.30.19 +TRIVYVERSION=v0.49.1 +TRIVYADAPTERVERSION=v0.30.22 # version of registry for pulling the source code REGISTRY_SRC_TAG=v2.8.3 diff --git a/make/harbor.yml.tmpl b/make/harbor.yml.tmpl index 763855fd2..d60c1fadd 100644 --- a/make/harbor.yml.tmpl +++ b/make/harbor.yml.tmpl @@ -97,6 +97,10 @@ trivy: # `metadata.json` files and mount them in the `/home/scanner/.cache/trivy/db` path. skip_update: false # + # skipJavaDBUpdate If the flag is enabled you have to manually download the `trivy-java.db` file and mount it in the + # `/home/scanner/.cache/trivy/java-db/trivy-java.db` path + skip_java_db_update: false + # # The offline_scan option prevents Trivy from sending API requests to identify dependencies. # Scanning JAR files and pom.xml may require Internet access for better detection, but this option tries to avoid it. # For example, the offline mode will not try to resolve transitive dependencies in pom.xml when the dependency doesn't diff --git a/make/photon/prepare/templates/trivy-adapter/env.jinja b/make/photon/prepare/templates/trivy-adapter/env.jinja index c9402a136..406e6a91a 100644 --- a/make/photon/prepare/templates/trivy-adapter/env.jinja +++ b/make/photon/prepare/templates/trivy-adapter/env.jinja @@ -10,6 +10,7 @@ SCANNER_TRIVY_VULN_TYPE=os,library SCANNER_TRIVY_SEVERITY=UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL SCANNER_TRIVY_IGNORE_UNFIXED={{trivy_ignore_unfixed}} SCANNER_TRIVY_SKIP_UPDATE={{trivy_skip_update}} +SCANNER_TRIVY_SKIP_JAVA_DB_UPDATE={{trivy_skip_java_db_update}} SCANNER_TRIVY_OFFLINE_SCAN={{trivy_offline_scan}} SCANNER_TRIVY_SECURITY_CHECKS={{trivy_security_check}} SCANNER_TRIVY_GITHUB_TOKEN={{trivy_github_token}} diff --git a/make/photon/prepare/utils/configs.py b/make/photon/prepare/utils/configs.py index f47ff4c40..a2ecc31af 100644 --- a/make/photon/prepare/utils/configs.py +++ b/make/photon/prepare/utils/configs.py @@ -212,6 +212,7 @@ def parse_yaml_config(config_file_path, with_trivy): trivy_configs = configs.get("trivy") or {} config_dict['trivy_github_token'] = trivy_configs.get("github_token") or '' config_dict['trivy_skip_update'] = trivy_configs.get("skip_update") or False + config_dict['trivy_skip_java_db_update'] = trivy_configs.get("skip_java_db_update") or False config_dict['trivy_offline_scan'] = trivy_configs.get("offline_scan") or False config_dict['trivy_security_check'] = trivy_configs.get("security_check") or 'vuln' config_dict['trivy_ignore_unfixed'] = trivy_configs.get("ignore_unfixed") or False From 73c2884e58948b55cbdbb2d95d6e112a195cf9c4 Mon Sep 17 00:00:00 2001 From: Antoine Jouve Date: Fri, 23 Feb 2024 04:30:13 +0100 Subject: [PATCH 019/145] [Token/JWT] Update to golang-jwt v5.2.0 (#19802) * feat: update to golang-jwt v5.2.0 Signed-off-by: Antoine Jouve * fix: module issues and robot claims Signed-off-by: Antoine Jouve * fix: add missing time import Signed-off-by: Antoine Jouve * feat: set jwt validation leeway to 60s Signed-off-by: Antoine Jouve * fix: update leeways that were still set to 10s Signed-off-by: Antoine Jouve * feat: update go.sum Signed-off-by: Antoine Jouve * feat: add two leeway related test cases Signed-off-by: Antoine Jouve * fix: correct jwt audience validation Signed-off-by: Antoine Jouve * fix: gofmt v2_token.go Signed-off-by: Antoine Jouve * feat: take into account review comments Signed-off-by: Antoine Jouve * feat: use a common constant to store JWT leeway Signed-off-by: Antoine Jouve --------- Signed-off-by: Antoine Jouve Signed-off-by: Antoine Jouve Co-authored-by: MinerYang --- src/common/const.go | 5 ++ src/core/service/token/authutils.go | 2 +- src/core/service/token/token_test.go | 2 +- src/go.mod | 3 +- src/go.sum | 6 +- src/pkg/token/claims/robot/robot.go | 8 ++- src/pkg/token/claims/v2/claims.go | 12 ++-- src/pkg/token/claims/v2/claims_test.go | 2 +- src/pkg/token/option_test.go | 2 +- src/pkg/token/options.go | 2 +- src/pkg/token/token.go | 13 ++-- src/pkg/token/token_test.go | 82 +++++++++++++++++++++- src/server/middleware/security/v2_token.go | 17 ++--- 13 files changed, 118 insertions(+), 38 deletions(-) diff --git a/src/common/const.go b/src/common/const.go index 64b38da33..aaa3c3fbe 100644 --- a/src/common/const.go +++ b/src/common/const.go @@ -14,6 +14,8 @@ package common +import "time" + type contextKey string // const variables @@ -241,4 +243,7 @@ const ( BeegoMaxUploadSizeBytes = "beego_max_upload_size_bytes" // DefaultBeegoMaxUploadSizeBytes sets default max upload size to 128GB DefaultBeegoMaxUploadSizeBytes = 1 << 37 + + // Global Leeway used for token validation + JwtLeeway = 60 * time.Second ) diff --git a/src/core/service/token/authutils.go b/src/core/service/token/authutils.go index 259727089..da392b19b 100644 --- a/src/core/service/token/authutils.go +++ b/src/core/service/token/authutils.go @@ -22,7 +22,7 @@ import ( "github.com/docker/distribution/registry/auth/token" "github.com/docker/libtrust" - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" "github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/security" diff --git a/src/core/service/token/token_test.go b/src/core/service/token/token_test.go index 6525df9c6..f0881904e 100644 --- a/src/core/service/token/token_test.go +++ b/src/core/service/token/token_test.go @@ -27,7 +27,7 @@ import ( "testing" "github.com/docker/distribution/registry/auth/token" - jwt "github.com/golang-jwt/jwt/v4" + jwt "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/assert" "github.com/goharbor/harbor/src/common/rbac" diff --git a/src/go.mod b/src/go.mod index e8a84ace4..d4f3a6576 100644 --- a/src/go.mod +++ b/src/go.mod @@ -30,7 +30,7 @@ require ( 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 - github.com/golang-jwt/jwt/v4 v4.5.0 + github.com/golang-jwt/jwt/v5 v5.2.0 github.com/golang-migrate/migrate/v4 v4.16.2 github.com/gomodule/redigo v2.0.0+incompatible github.com/google/uuid v1.3.1 @@ -109,6 +109,7 @@ require ( github.com/go-openapi/jsonpointer v0.20.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v4 v4.4.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect diff --git a/src/go.sum b/src/go.sum index 1824a5427..de181e561 100644 --- a/src/go.sum +++ b/src/go.sum @@ -228,8 +228,10 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= +github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-migrate/migrate/v4 v4.16.2 h1:8coYbMKUyInrFk1lfGfRovTLAW7PhWp8qQDT2iKfuoA= github.com/golang-migrate/migrate/v4 v4.16.2/go.mod h1:pfcJX4nPHaVdc5nmdCikFBWtm+UBpiZjRNNsyBbp0/o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= diff --git a/src/pkg/token/claims/robot/robot.go b/src/pkg/token/claims/robot/robot.go index 11de0bd7e..323a84ee1 100644 --- a/src/pkg/token/claims/robot/robot.go +++ b/src/pkg/token/claims/robot/robot.go @@ -17,8 +17,9 @@ package robot import ( "errors" - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" + "github.com/goharbor/harbor/src/common" "github.com/goharbor/harbor/src/pkg/permission/types" ) @@ -45,8 +46,9 @@ func (rc Claim) Valid() error { if rc.Access == nil { return errors.New("the access info cannot be nil") } - stdErr := rc.RegisteredClaims.Valid() - if stdErr != nil { + var v = jwt.NewValidator(jwt.WithLeeway(common.JwtLeeway)) + + if stdErr := v.Validate(rc.RegisteredClaims); stdErr != nil { return stdErr } return nil diff --git a/src/pkg/token/claims/v2/claims.go b/src/pkg/token/claims/v2/claims.go index 687c6af61..9558b9b85 100644 --- a/src/pkg/token/claims/v2/claims.go +++ b/src/pkg/token/claims/v2/claims.go @@ -15,11 +15,10 @@ package v2 import ( - "crypto/subtle" - "fmt" + "github.com/goharbor/harbor/src/common" "github.com/docker/distribution/registry/auth/token" - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" ) func init() { @@ -39,11 +38,10 @@ type Claims struct { // Valid checks if the issuer is harbor func (c *Claims) Valid() error { - if err := c.RegisteredClaims.Valid(); err != nil { + var v = jwt.NewValidator(jwt.WithLeeway(common.JwtLeeway), jwt.WithIssuer(Issuer)) + + if err := v.Validate(c.RegisteredClaims); err != nil { return err } - if subtle.ConstantTimeCompare([]byte(c.Issuer), []byte(Issuer)) == 0 { - return fmt.Errorf("invalid token issuer: %s", c.Issuer) - } return nil } diff --git a/src/pkg/token/claims/v2/claims_test.go b/src/pkg/token/claims/v2/claims_test.go index 6af09ae22..6d107b3cc 100644 --- a/src/pkg/token/claims/v2/claims_test.go +++ b/src/pkg/token/claims/v2/claims_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/docker/distribution/registry/auth/token" - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/assert" ) diff --git a/src/pkg/token/option_test.go b/src/pkg/token/option_test.go index 5139afbd1..2d23c1040 100644 --- a/src/pkg/token/option_test.go +++ b/src/pkg/token/option_test.go @@ -3,7 +3,7 @@ package token import ( "testing" - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/assert" ) diff --git a/src/pkg/token/options.go b/src/pkg/token/options.go index 0c8fe614b..7f639dfdc 100644 --- a/src/pkg/token/options.go +++ b/src/pkg/token/options.go @@ -19,7 +19,7 @@ import ( "fmt" "os" - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/log" diff --git a/src/pkg/token/token.go b/src/pkg/token/token.go index fe95421a8..920b587af 100644 --- a/src/pkg/token/token.go +++ b/src/pkg/token/token.go @@ -20,8 +20,9 @@ import ( "errors" "fmt" - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" + "github.com/goharbor/harbor/src/common" "github.com/goharbor/harbor/src/lib/log" ) @@ -34,8 +35,8 @@ type Token struct { // New ... func New(opt *Options, claims jwt.Claims) (*Token, error) { - err := claims.Valid() - if err != nil { + var v = jwt.NewValidator(jwt.WithLeeway(common.JwtLeeway)) + if err := v.Validate(claims); err != nil { return nil, err } return &Token{ @@ -65,10 +66,8 @@ func Parse(opt *Options, rawToken string, claims jwt.Claims) (*Token, error) { if err != nil { return nil, err } - token, err := jwt.ParseWithClaims(rawToken, claims, func(token *jwt.Token) (interface{}, error) { - if token.Method.Alg() != opt.SignMethod.Alg() { - return nil, errors.New("invalid signing method") - } + var parser = jwt.NewParser(jwt.WithLeeway(common.JwtLeeway), jwt.WithValidMethods([]string{opt.SignMethod.Alg()})) + token, err := parser.ParseWithClaims(rawToken, claims, func(token *jwt.Token) (interface{}, error) { switch k := key.(type) { case *rsa.PrivateKey: return &k.PublicKey, nil diff --git a/src/pkg/token/token_test.go b/src/pkg/token/token_test.go index fa87d7518..b3bd3a9cf 100644 --- a/src/pkg/token/token_test.go +++ b/src/pkg/token/token_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - jwt "github.com/golang-jwt/jwt/v4" + jwt "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/assert" "github.com/goharbor/harbor/src/lib/config" @@ -92,6 +92,42 @@ func TestRaw(t *testing.T) { assert.NotNil(t, rawTk) } +func TestNewWithClockSkew(t *testing.T) { + rbacPolicy := &types.Policy{ + Resource: "/project/library/repository", + Action: "pull", + } + var policies []*types.Policy + policies = append(policies, rbacPolicy) + + tokenID := int64(123) + projectID := int64(321) + + expiresAt := time.Now().UTC().Add(-50 * time.Second) + robot := robot_claim.Claim{ + TokenID: tokenID, + ProjectID: projectID, + Access: policies, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(expiresAt), + }, + } + defaultOpt := DefaultTokenOptions() + if defaultOpt == nil { + assert.NotNil(t, defaultOpt) + return + } + token, err := New(defaultOpt, robot) + if err != nil { + assert.Nil(t, err) + return + } + + rawTk, err := token.Raw() + assert.Nil(t, err) + assert.NotNil(t, rawTk) +} + func TestParseWithClaims(t *testing.T) { rawTk := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MTIzLCJQcm9qZWN0SUQiOjAsIkFjY2VzcyI6W3siUmVzb3VyY2UiOiIvcHJvamVjdC9saWJyYXkvcmVwb3NpdG9yeSIsIkFjdGlvbiI6InB1bGwiLCJFZmZlY3QiOiIifV0sIlN0YW5kYXJkQ2xhaW1zIjp7ImV4cCI6MTU0ODE0MDIyOSwiaXNzIjoiaGFyYm9yLXRva2VuLWlzc3VlciJ9fQ.Jc3qSKN4SJVUzAvBvemVpRcSOZaHlu0Avqms04qzPm4ru9-r9IRIl3mnSkI6m9XkzLUeJ7Kiwyw63ghngnVKw_PupeclOGC6s3TK5Cfmo4h-lflecXjZWwyy-dtH_e7Us_ItS-R3nXDJtzSLEpsGHCcAj-1X2s93RB2qD8LNSylvYeDezVkTzqRzzfawPJheKKh9JTrz-3eUxCwQard9-xjlwvfUYULoHTn9npNAUq4-jqhipW4uE8HL-ym33AGF57la8U0RO11hmDM5K8-PiYknbqJ_oONeS3HBNym2pEFeGjtTv2co213wl4T5lemlg4SGolMBuJ03L7_beVZ0o-MKTkKDqDwJalb6_PM-7u3RbxC9IzJMiwZKIPnD3FvV10iPxUUQHaH8Jz5UZ2pFIhi_8BNnlBfT0JOPFVYATtLjHMczZelj2YvAeR1UHBzq3E0jPpjjwlqIFgaHCaN_KMwEvadTo_Fi2sEH4pNGP7M3yehU_72oLJQgF4paJarsmEoij6ZtPs6xekBz1fccVitq_8WNIz9aeCUdkUBRwI5QKw1RdW4ua-w74ld5MZStWJA8veyoLkEb_Q9eq2oAj5KWFjJbW5-ltiIfM8gxKflsrkWAidYGcEIYcuXr7UdqEKXxtPiWM0xb3B91ovYvO5402bn3f9-UGtlcestxNHA" rClaims := &robot_claim.Claim{} @@ -104,3 +140,47 @@ func TestParseWithClaims(t *testing.T) { assert.Equal(t, int64(0), rClaims.ProjectID) assert.Equal(t, "/project/libray/repository", rClaims.Access[0].Resource.String()) } + +func TestParseWithClaimsWithClockSkew(t *testing.T) { + rbacPolicy := &types.Policy{ + Resource: "/project/library/repository", + Action: "push", + } + var policies []*types.Policy + policies = append(policies, rbacPolicy) + + tokenID := int64(123) + projectID := int64(321) + + now := time.Now().UTC() + expiresAt := jwt.NewNumericDate(now.Add(time.Duration(10) * 24 * time.Hour)) + notBefore := jwt.NewNumericDate(now.Add(50 * time.Second)) + issuedAt := jwt.NewNumericDate(now.Add(50 * time.Second)) + robot := robot_claim.Claim{ + TokenID: tokenID, + ProjectID: projectID, + Access: policies, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: expiresAt, + NotBefore: notBefore, + IssuedAt: issuedAt, + }, + } + defaultOpt := DefaultTokenOptions() + if defaultOpt == nil { + assert.NotNil(t, defaultOpt) + return + } + token, err := New(defaultOpt, robot) + if err != nil { + assert.Nil(t, err) + return + } + rawTk, err := token.Raw() + assert.Nil(t, err) + rClaims := &robot_claim.Claim{} + token, err = Parse(defaultOpt, rawTk, rClaims) + assert.Nil(t, err) + assert.Equal(t, token.Token.Claims.(*robot_claim.Claim).Access[0].Resource, types.Resource("/project/library/repository")) + assert.Equal(t, token.Token.Claims.(*robot_claim.Claim).Access[0].Action, types.Action("push")) +} diff --git a/src/server/middleware/security/v2_token.go b/src/server/middleware/security/v2_token.go index a4b4a3069..3c13afcc3 100644 --- a/src/server/middleware/security/v2_token.go +++ b/src/server/middleware/security/v2_token.go @@ -15,12 +15,14 @@ package security import ( - "fmt" "net/http" "strings" + "github.com/golang-jwt/jwt/v5" + registry_token "github.com/docker/distribution/registry/auth/token" + "github.com/goharbor/harbor/src/common" "github.com/goharbor/harbor/src/common/security" "github.com/goharbor/harbor/src/common/security/v2token" svc_token "github.com/goharbor/harbor/src/core/service/token" @@ -34,16 +36,6 @@ type v2TokenClaims struct { Access []*registry_token.ResourceActions `json:"access"` } -func (vtc *v2TokenClaims) Valid() error { - if err := vtc.Claims.Valid(); err != nil { - return err - } - if !vtc.VerifyAudience(svc_token.Registry, true) { - return fmt.Errorf("invalid token audience: %s", vtc.Audience) - } - return nil -} - type v2Token struct{} func (vt *v2Token) Generate(req *http.Request) security.Context { @@ -67,7 +59,8 @@ func (vt *v2Token) Generate(req *http.Request) security.Context { logger.Warningf("failed to decode bearer token: %v", err) return nil } - if err := t.Claims.Valid(); err != nil { + var v = jwt.NewValidator(jwt.WithLeeway(common.JwtLeeway), jwt.WithAudience(svc_token.Registry)) + if err := v.Validate(t.Claims); err != nil { logger.Warningf("failed to decode bearer token: %v", err) return nil } From fa6b13871f4d9aaf531d91cc415f6896a311e331 Mon Sep 17 00:00:00 2001 From: Shijun Sun <30999793+AllForNothing@users.noreply.github.com> Date: Fri, 23 Feb 2024 13:32:46 +0800 Subject: [PATCH 020/145] Remove redundant file package-lock.json under src folder (#20007) Signed-off-by: Shijun Sun <373492212@qq.com> Co-authored-by: MinerYang --- package-lock.json | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index a23291ee7..000000000 --- a/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "harbor", - "lockfileVersion": 3, - "requires": true, - "packages": {} -} From 54819ba8cd973e77505843424bd86e836b3442ea Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Fri, 23 Feb 2024 15:40:13 +0800 Subject: [PATCH 021/145] Limit url to local site (#20013) Signed-off-by: stonezdj Co-authored-by: stonezdj --- src/core/controllers/oidc.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/controllers/oidc.go b/src/core/controllers/oidc.go index 998786784..c929676c2 100644 --- a/src/core/controllers/oidc.go +++ b/src/core/controllers/oidc.go @@ -63,7 +63,13 @@ func (oc *OIDCController) RedirectLogin() { oc.SendInternalServerError(err) return } - if err := oc.SetSession(redirectURLKey, oc.Ctx.Request.URL.Query().Get("redirect_url")); err != nil { + redirectURL := oc.Ctx.Request.URL.Query().Get("redirect_url") + if strings.HasPrefix(redirectURL, "//") { + log.Errorf("invalid redirect url: %v", redirectURL) + oc.SendBadRequestError(fmt.Errorf("cannot redirect to other site")) + return + } + if err := oc.SetSession(redirectURLKey, redirectURL); err != nil { log.Errorf("failed to set session for key: %s, error: %v", redirectURLKey, err) oc.SendInternalServerError(err) return From 35f98344e6e70b394f56748c2e7b334c1b55679e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Feb 2024 16:54:30 +0800 Subject: [PATCH 022/145] Bump go.opentelemetry.io/otel from 1.21.0 to 1.23.1 in /src (#19972) Bumps [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go) from 1.21.0 to 1.23.1. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.21.0...v1.23.1) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- src/go.mod | 8 ++++---- src/go.sum | 15 ++++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/go.mod b/src/go.mod index d4f3a6576..22738cbea 100644 --- a/src/go.mod +++ b/src/go.mod @@ -56,11 +56,11 @@ require ( github.com/volcengine/volcengine-go-sdk v1.0.97 go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.46.1 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 - go.opentelemetry.io/otel v1.21.0 + go.opentelemetry.io/otel v1.23.1 go.opentelemetry.io/otel/exporters/jaeger v1.0.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 go.opentelemetry.io/otel/sdk v1.21.0 - go.opentelemetry.io/otel/trace v1.21.0 + go.opentelemetry.io/otel/trace v1.23.1 go.uber.org/ratelimit v0.2.0 golang.org/x/crypto v0.17.0 golang.org/x/net v0.17.0 @@ -103,7 +103,7 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/go-jose/go-jose/v3 v3.0.1 // indirect - github.com/go-logr/logr v1.3.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.21.4 // indirect github.com/go-openapi/jsonpointer v0.20.0 // indirect @@ -158,7 +158,7 @@ require ( github.com/volcengine/volc-sdk-golang v1.0.23 // indirect go.mongodb.org/mongo-driver v1.13.1 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect - go.opentelemetry.io/otel/metric v1.21.0 // indirect + go.opentelemetry.io/otel/metric v1.23.1 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect diff --git a/src/go.sum b/src/go.sum index de181e561..a2f2dd2d0 100644 --- a/src/go.sum +++ b/src/go.sum @@ -174,8 +174,9 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc= @@ -629,22 +630,22 @@ go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.46 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= go.opentelemetry.io/otel v1.0.0/go.mod h1:AjRVh9A5/5DE7S+mZtTR6t8vpKKryam+0lREnfmS4cg= -go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= -go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel v1.23.1 h1:Za4UzOqJYS+MUczKI320AtqZHZb7EqxO00jAHE0jmQY= +go.opentelemetry.io/otel v1.23.1/go.mod h1:Td0134eafDLcTS4y+zQ26GE8u3dEuRBiBCTUIRHaikA= go.opentelemetry.io/otel/exporters/jaeger v1.0.0 h1:cLhx8llHw02h5JTqGqaRbYn+QVKHmrzD9vEbKnSPk5U= go.opentelemetry.io/otel/exporters/jaeger v1.0.0/go.mod h1:q10N1AolE1JjqKrFJK2tYw0iZpmX+HBaXBtuCzRnBGQ= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= -go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= -go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/metric v1.23.1 h1:PQJmqJ9u2QaJLBOELl1cxIdPcpbwzbkjfEyelTl2rlo= +go.opentelemetry.io/otel/metric v1.23.1/go.mod h1:mpG2QPlAfnK8yNhNJAxDZruU9Y1/HubbC+KyH8FaCWI= go.opentelemetry.io/otel/sdk v1.0.0/go.mod h1:PCrDHlSy5x1kjezSdL37PhbFUMjrsLRshJ2zCzeXwbM= go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= go.opentelemetry.io/otel/trace v1.0.0/go.mod h1:PXTWqayeFUlJV1YDNhsJYB184+IvAH814St6o6ajzIs= -go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= -go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= +go.opentelemetry.io/otel/trace v1.23.1 h1:4LrmmEd8AU2rFvU1zegmvqW7+kWarxtNOPyeL6HmYY8= +go.opentelemetry.io/otel/trace v1.23.1/go.mod h1:4IpnpJFwr1mo/6HL8XIPJaE9y0+u1KcVmuW7dwFSVrI= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= From cb04005098c06da9d1a3398f2cfef1b3e5ab3606 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:40:46 +0800 Subject: [PATCH 023/145] Bump github.com/go-openapi/strfmt from 0.21.8 to 0.22.0 in /src (#19955) Bumps [github.com/go-openapi/strfmt](https://github.com/go-openapi/strfmt) from 0.21.8 to 0.22.0. - [Commits](https://github.com/go-openapi/strfmt/compare/v0.21.8...v0.22.0) --- updated-dependencies: - dependency-name: github.com/go-openapi/strfmt dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- src/go.mod | 4 ++-- src/go.sum | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/go.mod b/src/go.mod index 22738cbea..ac62e910d 100644 --- a/src/go.mod +++ b/src/go.mod @@ -24,7 +24,7 @@ require ( github.com/go-openapi/loads v0.21.2 // indirect github.com/go-openapi/runtime v0.26.2 github.com/go-openapi/spec v0.20.11 // indirect - github.com/go-openapi/strfmt v0.21.8 + github.com/go-openapi/strfmt v0.22.0 github.com/go-openapi/swag v0.22.7 github.com/go-openapi/validate v0.22.3 // indirect github.com/go-redis/redis/v8 v8.11.4 @@ -33,7 +33,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.0 github.com/golang-migrate/migrate/v4 v4.16.2 github.com/gomodule/redigo v2.0.0+incompatible - github.com/google/uuid v1.3.1 + github.com/google/uuid v1.5.0 github.com/gorilla/csrf v1.6.2 github.com/gorilla/handlers v1.5.2 github.com/gorilla/mux v1.8.1 diff --git a/src/go.sum b/src/go.sum index a2f2dd2d0..cde8d6993 100644 --- a/src/go.sum +++ b/src/go.sum @@ -200,8 +200,8 @@ github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6 github.com/go-openapi/spec v0.20.11 h1:J/TzFDLTt4Rcl/l1PmyErvkqlJDncGvPTMnCI39I4gY= github.com/go-openapi/spec v0.20.11/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg= -github.com/go-openapi/strfmt v0.21.8 h1:VYBUoKYRLAlgKDrIxR/I0lKrztDQ0tuTDrbhLVP8Erg= -github.com/go-openapi/strfmt v0.21.8/go.mod h1:adeGTkxE44sPyLk0JV235VQAO/ZXUr8KAzYjclFs3ew= +github.com/go-openapi/strfmt v0.22.0 h1:Ew9PnEYc246TwrEspvBdDHS4BVKXy/AOVsfqGDgAcaI= +github.com/go-openapi/strfmt v0.22.0/go.mod h1:HzJ9kokGIju3/K6ap8jL+OlGAbjpSv27135Yr9OivU4= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= @@ -285,8 +285,9 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= From 056c41fd80b7f1f2199f421315bafee106aacc11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Feb 2024 18:19:40 +0800 Subject: [PATCH 024/145] Bump github.com/google/uuid from 1.3.1 to 1.6.0 in /src (#19954) Bumps [github.com/google/uuid](https://github.com/google/uuid) from 1.3.1 to 1.6.0. - [Release notes](https://github.com/google/uuid/releases) - [Changelog](https://github.com/google/uuid/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/uuid/compare/v1.3.1...v1.6.0) --- updated-dependencies: - dependency-name: github.com/google/uuid dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: Shengwen YU Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- src/go.mod | 2 +- src/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/go.mod b/src/go.mod index ac62e910d..867c3fbbb 100644 --- a/src/go.mod +++ b/src/go.mod @@ -33,7 +33,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.0 github.com/golang-migrate/migrate/v4 v4.16.2 github.com/gomodule/redigo v2.0.0+incompatible - github.com/google/uuid v1.5.0 + github.com/google/uuid v1.6.0 github.com/gorilla/csrf v1.6.2 github.com/gorilla/handlers v1.5.2 github.com/gorilla/mux v1.8.1 diff --git a/src/go.sum b/src/go.sum index cde8d6993..f0c313753 100644 --- a/src/go.sum +++ b/src/go.sum @@ -286,8 +286,8 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= From 5b832c1724717b9d82fa4e148ccaa2bbfa8669aa Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Sat, 24 Feb 2024 09:34:30 +0800 Subject: [PATCH 025/145] Limit url to local path (#20025) --- src/common/utils/utils.go | 5 +++++ src/common/utils/utils_test.go | 22 ++++++++++++++++++++++ src/core/controllers/oidc.go | 2 +- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/common/utils/utils.go b/src/common/utils/utils.go index ab07c3ed3..07597492e 100644 --- a/src/common/utils/utils.go +++ b/src/common/utils/utils.go @@ -332,3 +332,8 @@ func MostMatchSorter(a, b string, matchWord string) bool { } return len(a) < len(b) } + +// IsLocalPath checks if path is local +func IsLocalPath(path string) bool { + return strings.HasPrefix(path, "/") && !strings.HasPrefix(path, "//") +} diff --git a/src/common/utils/utils_test.go b/src/common/utils/utils_test.go index 73d9fb28b..8849e1f0c 100644 --- a/src/common/utils/utils_test.go +++ b/src/common/utils/utils_test.go @@ -486,3 +486,25 @@ func TestValidateCronString(t *testing.T) { } } } + +func TestIsLocalPath(t *testing.T) { + type args struct { + path string + } + tests := []struct { + name string + args args + want bool + }{ + {"normal test", args{"/harbor/project"}, true}, + {"failed", args{"www.myexample.com"}, false}, + {"other_site1", args{"//www.myexample.com"}, false}, + {"other_site2", args{"https://www.myexample.com"}, false}, + {"other_site", args{"http://www.myexample.com"}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, IsLocalPath(tt.args.path), "IsLocalPath(%v)", tt.args.path) + }) + } +} diff --git a/src/core/controllers/oidc.go b/src/core/controllers/oidc.go index c929676c2..295acb2cf 100644 --- a/src/core/controllers/oidc.go +++ b/src/core/controllers/oidc.go @@ -64,7 +64,7 @@ func (oc *OIDCController) RedirectLogin() { return } redirectURL := oc.Ctx.Request.URL.Query().Get("redirect_url") - if strings.HasPrefix(redirectURL, "//") { + if !utils.IsLocalPath(redirectURL) { log.Errorf("invalid redirect url: %v", redirectURL) oc.SendBadRequestError(fmt.Errorf("cannot redirect to other site")) return From 2fd458878272a53d16c8c89993228e95f66fdf61 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 10:08:51 +0800 Subject: [PATCH 026/145] Bump helm.sh/helm/v3 from 3.11.3 to 3.14.2 in /src (#20017) Bumps [helm.sh/helm/v3](https://github.com/helm/helm) from 3.11.3 to 3.14.2. - [Release notes](https://github.com/helm/helm/releases) - [Commits](https://github.com/helm/helm/compare/v3.11.3...v3.14.2) --- updated-dependencies: - dependency-name: helm.sh/helm/v3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- src/go.mod | 8 ++++---- src/go.sum | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/go.mod b/src/go.mod index 867c3fbbb..f61f9b35c 100644 --- a/src/go.mod +++ b/src/go.mod @@ -45,7 +45,7 @@ require ( github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/olekukonko/tablewriter v0.0.5 github.com/opencontainers/go-digest v1.0.0 - github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b + github.com/opencontainers/image-spec v1.1.0-rc5 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.17.0 github.com/robfig/cron/v3 v3.0.1 @@ -70,7 +70,7 @@ require ( 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.11.3 + helm.sh/helm/v3 v3.14.2 k8s.io/api v0.29.0 k8s.io/apimachinery v0.29.0 k8s.io/client-go v0.29.0 @@ -90,7 +90,7 @@ require ( github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect - github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Unknwon/goconfig v0.0.0-20160216183935-5f601ca6ef4d // indirect github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -147,7 +147,7 @@ require ( github.com/robfig/cron v1.0.0 // indirect github.com/satori/go.uuid v1.2.0 // indirect github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect - github.com/sirupsen/logrus v1.9.2 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/afero v1.6.0 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect diff --git a/src/go.sum b/src/go.sum index f0c313753..7bde240e9 100644 --- a/src/go.sum +++ b/src/go.sum @@ -54,8 +54,8 @@ github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= -github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Unknwon/goconfig v0.0.0-20160216183935-5f601ca6ef4d h1:RjxaKUAINjr+fYbaYjpdBUZc8R3+wF/Yr2XkDHho4Sg= @@ -133,8 +133,8 @@ github.com/distribution/distribution v2.8.2+incompatible h1:k9+4DKdOG+quPFZXT/mU github.com/distribution/distribution v2.8.2+incompatible/go.mod h1:EgLm2NgWtdKgzF9NpMzUKgzmR7AMmb0VQi2B+ZzDRjc= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= -github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE= -github.com/docker/docker v20.10.24+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= +github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= @@ -424,8 +424,8 @@ 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= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= -github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -500,8 +500,8 @@ github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= 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-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8= -github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= +github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= +github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -558,8 +558,8 @@ github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFR github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= -github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= @@ -945,8 +945,8 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -helm.sh/helm/v3 v3.11.3 h1:n1X5yaQTP5DYywlBOZMl2gX398Gp6YwFp/IAVj6+5D4= -helm.sh/helm/v3 v3.11.3/go.mod h1:S+sOdQc3BLvt09a9rSlKKVs9x0N/yx+No0y3qFw+FQ8= +helm.sh/helm/v3 v3.14.2 h1:V71fv+NGZv0icBlr+in1MJXuUIHCiPG1hW9gEBISTIA= +helm.sh/helm/v3 v3.14.2/go.mod h1:2itvvDv2WSZXTllknfQo6j7u3VVgMAvm8POCDgYH424= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From c5790ced14f09561494e9d5c2b133b9bd47e7e2f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 12:05:17 +0800 Subject: [PATCH 027/145] Bump github.com/aws/aws-sdk-go from 1.50.5 to 1.50.24 in /src (#20018) Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.50.5 to 1.50.24. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.50.5...v1.50.24) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- src/go.mod | 2 +- src/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/go.mod b/src/go.mod index f61f9b35c..12855b83c 100644 --- a/src/go.mod +++ b/src/go.mod @@ -7,7 +7,7 @@ require ( github.com/Masterminds/semver v1.5.0 github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190726115642-cd293c93fd97 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 - github.com/aws/aws-sdk-go v1.50.5 + github.com/aws/aws-sdk-go v1.50.24 github.com/beego/beego/v2 v2.0.6 github.com/beego/i18n v0.0.0-20140604031826-e87155e8f0c0 github.com/bmatcuk/doublestar v1.3.4 diff --git a/src/go.sum b/src/go.sum index 7bde240e9..f6aeb03d7 100644 --- a/src/go.sum +++ b/src/go.sum @@ -77,8 +77,8 @@ github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:W github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= -github.com/aws/aws-sdk-go v1.50.5 h1:H2Aadcgwr7a2aqS6ZwcE+l1mA6ZrTseYCvjw2QLmxIA= -github.com/aws/aws-sdk-go v1.50.5/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.50.24 h1:3o2Pg7mOoVL0jv54vWtuafoZqAeEXLhm1tltWA2GcEw= +github.com/aws/aws-sdk-go v1.50.24/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= github.com/beego/beego/v2 v2.0.6 h1:21Aqz3+RzUE1yP9a5xdU6LK54n9Z7NLEJtR4PE7NrPQ= github.com/beego/beego/v2 v2.0.6/go.mod h1:CH2/JIaB4ceGYVQlYqTAFft4pVk/ol1ZkakUrUvAyns= From 2b6608fb52cf7b44f48ef792078635bce46256e8 Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Mon, 26 Feb 2024 13:08:35 +0800 Subject: [PATCH 028/145] Move strong_ssl_ciphers to top level in harbor.yaml (#19914) fixes #19912 Signed-off-by: stonezdj --- make/harbor.yml.tmpl | 5 +++-- .../migrations/version_2_10_0/harbor.yml.jinja | 16 +++++++++------- .../templates/nginx/nginx.https.conf.jinja | 2 +- .../prepare/templates/portal/nginx.conf.jinja | 2 +- make/photon/prepare/utils/configs.py | 10 +++++++++- make/photon/prepare/utils/migration.py | 6 ++++++ make/photon/prepare/utils/nginx.py | 1 + make/photon/prepare/utils/portal.py | 4 +++- 8 files changed, 33 insertions(+), 13 deletions(-) diff --git a/make/harbor.yml.tmpl b/make/harbor.yml.tmpl index d60c1fadd..8c5abe071 100644 --- a/make/harbor.yml.tmpl +++ b/make/harbor.yml.tmpl @@ -16,6 +16,8 @@ https: # The path of cert and key files for nginx certificate: /your/certificate/path private_key: /your/private/key/path + # enable strong ssl ciphers (default: false) + # strong_ssl_ciphers: false # # Harbor will set ipv4 enabled only by defualt if this block is not configured # # Otherwise, please uncomment this block to configure your own ip_family stacks @@ -33,8 +35,7 @@ https: # enabled: true # # put your cert and key files on dir # dir: /etc/harbor/tls/internal -# # enable strong ssl ciphers (default: false) -# strong_ssl_ciphers: false + # Uncomment external_url if you want to enable external proxy # And when it enabled the hostname will no longer used diff --git a/make/photon/prepare/migrations/version_2_10_0/harbor.yml.jinja b/make/photon/prepare/migrations/version_2_10_0/harbor.yml.jinja index 44a46968f..a6e07e915 100644 --- a/make/photon/prepare/migrations/version_2_10_0/harbor.yml.jinja +++ b/make/photon/prepare/migrations/version_2_10_0/harbor.yml.jinja @@ -23,6 +23,12 @@ https: # The path of cert and key files for nginx certificate: {{ https.certificate }} private_key: {{ https.private_key }} + # enable strong ssl ciphers (default: false) + {% if strong_ssl_ciphers is defined %} + strong_ssl_ciphers: {{ strong_ssl_ciphers | lower }} + {% else %} + strong_ssl_ciphers: false + {% endif %} {% else %} # https related config # https: @@ -31,6 +37,8 @@ https: # # The path of cert and key files for nginx # certificate: /your/certificate/path # private_key: /your/private/key/path +# enable strong ssl ciphers (default: false) +# strong_ssl_ciphers: false {% endif %} {% if internal_tls is defined %} @@ -38,13 +46,9 @@ https: internal_tls: # set enabled to true means internal tls is enabled enabled: {{ internal_tls.enabled | lower }} + {% if internal_tls.dir is defined %} # put your cert and key files on dir dir: {{ internal_tls.dir }} - # enable strong ssl ciphers (default: false) - {% if internal_tls.strong_ssl_ciphers is defined %} - strong_ssl_ciphers: {{ internal_tls.strong_ssl_ciphers | lower }} - {% else %} - strong_ssl_ciphers: false {% endif %} {% else %} # internal_tls: @@ -52,8 +56,6 @@ internal_tls: # enabled: true # # put your cert and key files on dir # dir: /etc/harbor/tls/internal -# # enable strong ssl ciphers (default: false) -# strong_ssl_ciphers: false {% endif %} # Uncomment external_url if you want to enable external proxy diff --git a/make/photon/prepare/templates/nginx/nginx.https.conf.jinja b/make/photon/prepare/templates/nginx/nginx.https.conf.jinja index a5d0d8037..6fa2bae78 100644 --- a/make/photon/prepare/templates/nginx/nginx.https.conf.jinja +++ b/make/photon/prepare/templates/nginx/nginx.https.conf.jinja @@ -64,7 +64,7 @@ http { # Recommendations from https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html ssl_protocols TLSv1.2 TLSv1.3; -{% if internal_tls.strong_ssl_ciphers %} +{% if strong_ssl_ciphers %} ssl_ciphers ECDHE+AESGCM:DHE+AESGCM:ECDHE+RSA+SHA256:DHE+RSA+SHA256:!AES128; {% else %} ssl_ciphers '!aNULL:kECDH+AESGCM:ECDH+AESGCM:RSA+AESGCM:kECDH+AES:ECDH+AES:RSA+AES:'; diff --git a/make/photon/prepare/templates/portal/nginx.conf.jinja b/make/photon/prepare/templates/portal/nginx.conf.jinja index 3f17e7176..85a68a094 100644 --- a/make/photon/prepare/templates/portal/nginx.conf.jinja +++ b/make/photon/prepare/templates/portal/nginx.conf.jinja @@ -28,7 +28,7 @@ http { ssl_certificate_key /etc/harbor/tls/portal.key; ssl_protocols TLSv1.2 TLSv1.3; - {% if internal_tls.strong_ssl_ciphers %} + {% if strong_ssl_ciphers %} ssl_ciphers ECDHE+AESGCM:DHE+AESGCM:ECDHE+RSA+SHA256:DHE+RSA+SHA256:!AES128; {% else %} ssl_ciphers '!aNULL:kECDH+AESGCM:ECDH+AESGCM:RSA+AESGCM:kECDH+AES:ECDH+AES:RSA+AES:'; diff --git a/make/photon/prepare/utils/configs.py b/make/photon/prepare/utils/configs.py index a2ecc31af..3a1266215 100644 --- a/make/photon/prepare/utils/configs.py +++ b/make/photon/prepare/utils/configs.py @@ -299,7 +299,15 @@ def parse_yaml_config(config_file_path, with_trivy): external_database=config_dict['external_database']) else: config_dict['internal_tls'] = InternalTLS() - + # the configure item apply to internal and external tls communication + # for compatibility, user could configure the strong_ssl_ciphers either in https section or under internal_tls section, + # but it is more reasonable to configure it in https_config + if https_config: + config_dict['strong_ssl_ciphers'] = https_config.get('strong_ssl_ciphers') or internal_tls_config.get('strong_ssl_ciphers') + else: + config_dict['strong_ssl_ciphers'] = False + + # ip_family config config_dict['ip_family'] = configs.get('ip_family') or {'ipv4': {'enabled': True}, 'ipv6': {'enabled': False}} diff --git a/make/photon/prepare/utils/migration.py b/make/photon/prepare/utils/migration.py index 1389bae45..a29b1b9df 100644 --- a/make/photon/prepare/utils/migration.py +++ b/make/photon/prepare/utils/migration.py @@ -27,6 +27,12 @@ def read_conf(path): with open(path) as f: try: d = yaml.safe_load(f) + # the strong_ssl_ciphers configure item apply to internal and external tls communication + # for compatibility, user could configure the strong_ssl_ciphers either in https section or under internal_tls section, + # but it will move to https section after migration + https_config = d.get("https") or {} + internal_tls = d.get('internal_tls') or {} + d['strong_ssl_ciphers'] = https_config.get('strong_ssl_ciphers') or internal_tls.get('strong_ssl_ciphers') except Exception as e: click.echo("parse config file err, make sure your harbor config version is above 1.8.0", e) exit(-1) diff --git a/make/photon/prepare/utils/nginx.py b/make/photon/prepare/utils/nginx.py index 0b1ffb8a4..2872bafbe 100644 --- a/make/photon/prepare/utils/nginx.py +++ b/make/photon/prepare/utils/nginx.py @@ -64,6 +64,7 @@ def render_nginx_template(config_dict): ssl_cert_key=SSL_CERT_KEY_PATH, internal_tls=config_dict['internal_tls'], metric=config_dict['metric'], + strong_ssl_ciphers=config_dict['strong_ssl_ciphers'], ip_family=config_dict['ip_family']) location_file_pattern = CUSTOM_NGINX_LOCATION_FILE_PATTERN_HTTPS diff --git a/make/photon/prepare/utils/portal.py b/make/photon/prepare/utils/portal.py index d41de1264..9211a5df7 100644 --- a/make/photon/prepare/utils/portal.py +++ b/make/photon/prepare/utils/portal.py @@ -16,4 +16,6 @@ def prepare_portal(config_dict): internal_tls=config_dict['internal_tls'], ip_family=config_dict['ip_family'], uid=DEFAULT_UID, - gid=DEFAULT_GID) + gid=DEFAULT_GID, + strong_ssl_ciphers=config_dict['strong_ssl_ciphers'] + ) From 47546a5f9d9f277230699cf7666dc493e67ddabc Mon Sep 17 00:00:00 2001 From: Lichao Xue <68891670+xuelichao@users.noreply.github.com> Date: Mon, 26 Feb 2024 16:57:24 +0800 Subject: [PATCH 029/145] update help message for bandwidth of replication rule (#20016) Signed-off-by: xuelichao Co-authored-by: Wang Yan Co-authored-by: Shengwen YU --- .../create-edit-rule.component.html | 4 +++- .../create-edit-rule/create-edit-rule.component.ts | 14 ++++++++++++++ src/portal/src/i18n/lang/de-de-lang.json | 2 +- src/portal/src/i18n/lang/en-us-lang.json | 2 +- src/portal/src/i18n/lang/es-es-lang.json | 2 +- src/portal/src/i18n/lang/fr-fr-lang.json | 2 +- src/portal/src/i18n/lang/pt-br-lang.json | 2 +- src/portal/src/i18n/lang/tr-tr-lang.json | 2 +- src/portal/src/i18n/lang/zh-cn-lang.json | 2 +- src/portal/src/i18n/lang/zh-tw-lang.json | 2 +- 10 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.html b/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.html index c45d209c5..a08f281cd 100644 --- a/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.html +++ b/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.html @@ -736,7 +736,9 @@ clrSize="lg" *clrIfOpen> {{ - 'REPLICATION.BANDWIDTH_TOOLTIP' | translate + 'REPLICATION.BANDWIDTH_TOOLTIP' + | translate + : { max_job_workers: maxJobWorkers } }} diff --git a/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts b/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts index 4b3e56725..5b6612514 100644 --- a/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts +++ b/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts @@ -41,6 +41,7 @@ import { ErrorHandler } from '../../../../../shared/units/error-handler'; import { TranslateService } from '@ngx-translate/core'; import { cronRegex } from '../../../../../shared/units/utils'; import { FilterType } from '../../../../../shared/entities/shared.const'; +import { JobserviceService } from 'ng-swagger-gen/services'; import { RegistryService } from '../../../../../../../ng-swagger-gen/services/registry.service'; import { Registry } from '../../../../../../../ng-swagger-gen/models/registry'; import { Label } from '../../../../../../../ng-swagger-gen/models/label'; @@ -80,6 +81,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy { headerTitle = 'REPLICATION.ADD_POLICY'; createEditRuleOpened: boolean; + maxJobWorkers = 10; inProgress = false; onGoing = false; inNameChecking = false; @@ -125,6 +127,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy { private endpointService: RegistryService, private errorHandler: ErrorHandler, private translateService: TranslateService, + private jobServiceService: JobserviceService, private labelService: LabelService ) { this.createForm(); @@ -238,6 +241,7 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy { } } }); + this.initMaxJobWorkers(); } trimText(event) { if (event.target.value) { @@ -906,4 +910,14 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy { } return false; } + + initMaxJobWorkers() { + this.jobServiceService.getWorkerPools().subscribe({ + next: pools => { + if ((pools ?? []).length > 0) { + this.maxJobWorkers = pools[0].concurrency; + } + }, + }); + } } diff --git a/src/portal/src/i18n/lang/de-de-lang.json b/src/portal/src/i18n/lang/de-de-lang.json index 02be72d26..a1ed8993f 100644 --- a/src/portal/src/i18n/lang/de-de-lang.json +++ b/src/portal/src/i18n/lang/de-de-lang.json @@ -699,7 +699,7 @@ "FLATTEN_LEVEL_TIP_3": "'Reduzierung um 3 Ebenen': 'a/b/c/d/img' -> 'ns/d/img'", "BANDWIDTH": "Bandbreite", "BANDWIDTH_ERROR_TIP": "Bitte -1 oder einen Integer-Wert größer als 0 eingeben", - "BANDWIDTH_TOOLTIP": "Legt die maximale Netzwerkbandbreite für jede Ausführung fest. Bitte auf die Anzahl der parallelen Ausführungen achten. Für umbegrenzte Bandbreite, bitte -1 eingeben", + "BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each replication worker. Please pay attention to the number of concurrent executions (max. {{max_job_workers}}). For unlimited bandwidth, please enter -1.", "UNLIMITED": "Unbegrenzt", "UNREACHABLE_SOURCE_REGISTRY": "Verbindung zur Quell-Registry fehlgeschlagen, bitte sicherstellen, dass die Quell-Registry verfügbar ist, bevor diese Regel angepasst wird: {{error}}", "CRON_ERROR_TIP": "The 1st field of the cron string must be 0 and the 2nd filed can not be \"*\"", diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json index 2fb912feb..0f1b9c62f 100644 --- a/src/portal/src/i18n/lang/en-us-lang.json +++ b/src/portal/src/i18n/lang/en-us-lang.json @@ -700,7 +700,7 @@ "FLATTEN_LEVEL_TIP_3": "'Flatten 3 Levels': 'a/b/c/d/img' -> 'ns/d/img'", "BANDWIDTH": "Bandwidth", "BANDWIDTH_ERROR_TIP": "Please enter -1 or an integer greater than 0", - "BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each execution. Please pay attention to the number of concurrent executions. For unlimited bandwidth, please enter -1", + "BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each replication worker. Please pay attention to the number of concurrent executions (max. {{max_job_workers}}). For unlimited bandwidth, please enter -1.", "UNLIMITED": "Unlimited", "UNREACHABLE_SOURCE_REGISTRY": "Failed to connect to the source registry, please make sure the source registry is available before editing this rule: {{error}}", "CRON_ERROR_TIP": "The 1st field of the cron string must be 0 and the 2nd filed can not be \"*\"", diff --git a/src/portal/src/i18n/lang/es-es-lang.json b/src/portal/src/i18n/lang/es-es-lang.json index dfa6c5449..e2bc7cec8 100644 --- a/src/portal/src/i18n/lang/es-es-lang.json +++ b/src/portal/src/i18n/lang/es-es-lang.json @@ -700,7 +700,7 @@ "FLATTEN_LEVEL_TIP_3": "'Flatten 3 Levels': 'a/b/c/d/img' -> 'ns/d/img'", "BANDWIDTH": "Bandwidth", "BANDWIDTH_ERROR_TIP": "Please enter -1 or an integer greater than 0", - "BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each execution. Please pay attention to the number of concurrent executions. For unlimited bandwidth, please enter -1", + "BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each replication worker. Please pay attention to the number of concurrent executions (max. {{max_job_workers}}). For unlimited bandwidth, please enter -1.", "UNLIMITED": "Unlimited", "UNREACHABLE_SOURCE_REGISTRY": "Failed to connect to the source registry, please make sure the source registry is available before editing this rule: {{error}}", "CRON_ERROR_TIP": "The 1st field of the cron string must be 0 and the 2nd filed can not be \"*\"", diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json index 3f11ca729..07cb73b8e 100644 --- a/src/portal/src/i18n/lang/fr-fr-lang.json +++ b/src/portal/src/i18n/lang/fr-fr-lang.json @@ -699,7 +699,7 @@ "FLATTEN_LEVEL_TIP_3": "'Aplanissement sur 3 niveaux' : 'a/b/c/d/img' -> 'ns/d/img'", "BANDWIDTH": "Bande passante", "BANDWIDTH_ERROR_TIP": "Veuillez saisir -1 ou un nombre entier supérieur à 0", - "BANDWIDTH_TOOLTIP": "Définissez la bande passante réseau maximale pour chaque exécution. Veuillez faire attention au nombre d'exécutions simultanées. Pour une bande passante illimitée, veuillez entrer -1", + "BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each replication worker. Please pay attention to the number of concurrent executions (max. {{max_job_workers}}). For unlimited bandwidth, please enter -1.", "UNLIMITED": "Illimitée", "UNREACHABLE_SOURCE_REGISTRY": "Échec de connexion au registre source. Veuillez vérifier que le registre source est disponible avant d'éditer cette règle: {{error}}", "CRON_ERROR_TIP": "The 1st field of the cron string must be 0 and the 2nd filed can not be \"*\"", diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json index 686ac647a..08c74b311 100644 --- a/src/portal/src/i18n/lang/pt-br-lang.json +++ b/src/portal/src/i18n/lang/pt-br-lang.json @@ -698,7 +698,7 @@ "FLATTEN_LEVEL_TIP_3": "'Nivelar 3 posições': 'a/b/c/d/img' -> 'ns/d/img'", "BANDWIDTH": "Banda", "BANDWIDTH_ERROR_TIP": "Informe um número inteiro maior que 0 (zero) ou -1", - "BANDWIDTH_TOOLTIP": "Informe o limite de banda para cada execução. Tome cuidado e observe a relação com o número de execuções simultâneas. Para remover qualquer limite, informe -1", + "BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each replication worker. Please pay attention to the number of concurrent executions (max. {{max_job_workers}}). For unlimited bandwidth, please enter -1.", "UNLIMITED": "Ilimitado", "UNREACHABLE_SOURCE_REGISTRY": "Failed to connect to the source registry, please make sure the source registry is available before editing this rule: {{error}}", "CRON_ERROR_TIP": "The 1st field of the cron string must be 0 and the 2nd filed can not be \"*\"", diff --git a/src/portal/src/i18n/lang/tr-tr-lang.json b/src/portal/src/i18n/lang/tr-tr-lang.json index dafe49651..d64c5708b 100644 --- a/src/portal/src/i18n/lang/tr-tr-lang.json +++ b/src/portal/src/i18n/lang/tr-tr-lang.json @@ -699,7 +699,7 @@ "FLATTEN_LEVEL_TIP_3": "'Flatten 3 Levels': 'a/b/c/d/img' -> 'ns/d/img'", "BANDWIDTH": "Bandwidth", "BANDWIDTH_ERROR_TIP": "Please enter -1 or an integer greater than 0", - "BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each execution. Please pay attention to the number of concurrent executions. For unlimited bandwidth, please enter -1", + "BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each replication worker. Please pay attention to the number of concurrent executions (max. {{max_job_workers}}). For unlimited bandwidth, please enter -1.", "UNLIMITED": "Unlimited", "UNREACHABLE_SOURCE_REGISTRY": "Failed to connect to the source registry, please make sure the source registry is available before editing this rule: {{error}}", "CRON_ERROR_TIP": "The 1st field of the cron string must be 0 and the 2nd filed can not be \"*\"", diff --git a/src/portal/src/i18n/lang/zh-cn-lang.json b/src/portal/src/i18n/lang/zh-cn-lang.json index 206e4e726..5a6cbadcc 100644 --- a/src/portal/src/i18n/lang/zh-cn-lang.json +++ b/src/portal/src/i18n/lang/zh-cn-lang.json @@ -698,7 +698,7 @@ "FLATTEN_LEVEL_TIP_3": "'替换3级': 'a/b/c/d/img' -> 'ns/d/img'", "BANDWIDTH": "带宽", "BANDWIDTH_ERROR_TIP": "请输入-1或者大于0的整数", - "BANDWIDTH_TOOLTIP": "设置执行该条同步规则时的最大网络带宽。实际总带宽需要考虑并发执行的情况。如无需限制,请输入-1", + "BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each replication worker. Please pay attention to the number of concurrent executions (max. {{max_job_workers}}). For unlimited bandwidth, please enter -1.", "UNLIMITED": "无限制", "UNREACHABLE_SOURCE_REGISTRY": "连接源仓库失败,在编辑此规则前请先确保源仓库可用: {{error}}", "CRON_ERROR_TIP": "Cron 字符串的第一项必须为0且第二项不能为\"*\"", diff --git a/src/portal/src/i18n/lang/zh-tw-lang.json b/src/portal/src/i18n/lang/zh-tw-lang.json index 281c2c94b..0a7c37440 100644 --- a/src/portal/src/i18n/lang/zh-tw-lang.json +++ b/src/portal/src/i18n/lang/zh-tw-lang.json @@ -698,7 +698,7 @@ "FLATTEN_LEVEL_TIP_3": "'平整化 3 等級': 'a/b/c/d/img' -> 'ns/d/img'", "BANDWIDTH": "頻寬", "BANDWIDTH_ERROR_TIP": "請輸入 -1 或大於 0 的整數", - "BANDWIDTH_TOOLTIP": "設定每個執行的最大網路頻寬。請注意並發執行的數量。若要無限制頻寬,請輸入 -1", + "BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each replication worker. Please pay attention to the number of concurrent executions (max. {{max_job_workers}}). For unlimited bandwidth, please enter -1.", "UNLIMITED": "無限制", "UNREACHABLE_SOURCE_REGISTRY": "無法連線到來源 Registry,請在編輯此規則之前確保來源 Registry 可用: {{error}}", "CRON_ERROR_TIP": "cron 字串的第一個欄位必須是 0,第二個欄位不能是 \"*\"", From 29cdc398e048b739c19a23fa0ffb5073df256009 Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Mon, 26 Feb 2024 17:46:40 +0800 Subject: [PATCH 030/145] =?UTF-8?q?Check=20if=20the=20internal=5Ftls=5Fcon?= =?UTF-8?q?fig=20is=20not=20null=20when=20get=20strong=5Fssl=5Fciph?= =?UTF-8?q?=E2=80=A6=20(#20032)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Check if the internal_tls_config is not null when get strong_ssl_cipher value Signed-off-by: stonezdj Co-authored-by: stonezdj --- make/photon/prepare/utils/configs.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/make/photon/prepare/utils/configs.py b/make/photon/prepare/utils/configs.py index 3a1266215..aff786729 100644 --- a/make/photon/prepare/utils/configs.py +++ b/make/photon/prepare/utils/configs.py @@ -303,10 +303,13 @@ def parse_yaml_config(config_file_path, with_trivy): # for compatibility, user could configure the strong_ssl_ciphers either in https section or under internal_tls section, # but it is more reasonable to configure it in https_config if https_config: - config_dict['strong_ssl_ciphers'] = https_config.get('strong_ssl_ciphers') or internal_tls_config.get('strong_ssl_ciphers') + config_dict['strong_ssl_ciphers'] = https_config.get('strong_ssl_ciphers') else: config_dict['strong_ssl_ciphers'] = False + if internal_tls_config: + config_dict['strong_ssl_ciphers'] = config_dict['strong_ssl_ciphers'] or internal_tls_config.get('strong_ssl_ciphers') + # ip_family config config_dict['ip_family'] = configs.get('ip_family') or {'ipv4': {'enabled': True}, 'ipv6': {'enabled': False}} From d79e4b11763963c8d6dce1a8e33a9e02677568d7 Mon Sep 17 00:00:00 2001 From: Lichao Xue <68891670+xuelichao@users.noreply.github.com> Date: Thu, 29 Feb 2024 14:37:26 +0800 Subject: [PATCH 031/145] revise the tags of Interrogation Services (#20049) Signed-off-by: xuelichao Co-authored-by: Wang Yan --- .../interrogation-services.component.html | 16 ++++++++-------- .../interrogation-services.module.ts | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/interrogation-services.component.html b/src/portal/src/app/base/left-side-nav/interrogation-services/interrogation-services.component.html index 46ea92d7e..b050c24ea 100644 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/interrogation-services.component.html +++ b/src/portal/src/app/base/left-side-nav/interrogation-services/interrogation-services.component.html @@ -3,6 +3,14 @@ diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/interrogation-services.module.ts b/src/portal/src/app/base/left-side-nav/interrogation-services/interrogation-services.module.ts index 588371790..e75db841d 100644 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/interrogation-services.module.ts +++ b/src/portal/src/app/base/left-side-nav/interrogation-services/interrogation-services.module.ts @@ -50,7 +50,7 @@ const routes: Routes = [ }, { path: '', - redirectTo: 'scanners', + redirectTo: 'security-hub', pathMatch: 'full', }, ], From 3782bab80a650526a8869547eaa65aa23472bf56 Mon Sep 17 00:00:00 2001 From: Wang Yan Date: Fri, 1 Mar 2024 14:40:02 +0800 Subject: [PATCH 032/145] add sbom settings for project (#20069) Add a new switcher for sbom generation, by default is false. Signed-off-by: wang yan --- src/pkg/project/models/pro_meta.go | 1 + src/pkg/project/models/project.go | 9 +++++++++ src/server/v2.0/handler/project_metadata.go | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/pkg/project/models/pro_meta.go b/src/pkg/project/models/pro_meta.go index bc4fe3ca3..a8a0659fe 100644 --- a/src/pkg/project/models/pro_meta.go +++ b/src/pkg/project/models/pro_meta.go @@ -23,4 +23,5 @@ const ( ProMetaSeverity = "severity" ProMetaAutoScan = "auto_scan" ProMetaReuseSysCVEAllowlist = "reuse_sys_cve_allowlist" + ProMetaAutoSBOMGen = "auto_sbom_generation" ) diff --git a/src/pkg/project/models/project.go b/src/pkg/project/models/project.go index 5a379468a..e533bd911 100644 --- a/src/pkg/project/models/project.go +++ b/src/pkg/project/models/project.go @@ -147,6 +147,15 @@ func (p *Project) AutoScan() bool { return isTrue(auto) } +// AutoSBOMGen ... +func (p *Project) AutoSBOMGen() bool { + auto, exist := p.GetMetadata(ProMetaAutoSBOMGen) + if !exist { + return false + } + return isTrue(auto) +} + // FilterByPublic returns orm.QuerySeter with public filter func (p *Project) FilterByPublic(_ context.Context, qs orm.QuerySeter, _ string, value interface{}) orm.QuerySeter { subQuery := `SELECT project_id FROM project_metadata WHERE name = 'public' AND value = '%s'` diff --git a/src/server/v2.0/handler/project_metadata.go b/src/server/v2.0/handler/project_metadata.go index 8a2ba6cb7..a5bde2e45 100644 --- a/src/server/v2.0/handler/project_metadata.go +++ b/src/server/v2.0/handler/project_metadata.go @@ -143,7 +143,7 @@ func (p *projectMetadataAPI) validate(metas map[string]string) (map[string]strin switch key { case proModels.ProMetaPublic, proModels.ProMetaEnableContentTrust, proModels.ProMetaEnableContentTrustCosign, - proModels.ProMetaPreventVul, proModels.ProMetaAutoScan, proModels.ProMetaReuseSysCVEAllowlist: + proModels.ProMetaAutoSBOMGen, proModels.ProMetaPreventVul, proModels.ProMetaAutoScan, proModels.ProMetaReuseSysCVEAllowlist: v, err := strconv.ParseBool(value) if err != nil { return nil, errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("invalid value: %s", value) From d25f3556a94a687d58faeac1197dd5090b17ec29 Mon Sep 17 00:00:00 2001 From: Wang Yan Date: Fri, 1 Mar 2024 15:25:38 +0800 Subject: [PATCH 033/145] update referrers api (#20068) Update the referrers API according to the changeset in distribution spec 1.1, available at https://github.com/opencontainers/distribution-spec/pull/491/files. Signed-off-by: wang yan --- src/server/registry/referrers.go | 14 +------------- src/server/registry/referrers_test.go | 9 ++++----- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/server/registry/referrers.go b/src/server/registry/referrers.go index 3213889df..799d04acd 100644 --- a/src/server/registry/referrers.go +++ b/src/server/registry/referrers.go @@ -63,20 +63,8 @@ func (r *referrersHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - // Get the artifact by reference - art, err := r.artifactManager.GetByDigest(ctx, repository, reference) - if err != nil { - if errors.IsNotFoundErr(err) { - // If artifact not found, return empty json - newListReferrersOK().WithPayload(nil).WriteResponse(w) - return - } - lib_http.SendError(w, err) - return - } - // Query accessories with matching subject artifact digest - query := q.New(q.KeyWords{"SubjectArtifactDigest": art.Digest, "SubjectArtifactRepo": art.RepositoryName}) + query := q.New(q.KeyWords{"SubjectArtifactDigest": reference, "SubjectArtifactRepo": repository}) total, err := r.accessoryManager.Count(ctx, query) if err != nil { lib_http.SendError(w, err) diff --git a/src/server/registry/referrers_test.go b/src/server/registry/referrers_test.go index d9c960a01..473fb0c57 100644 --- a/src/server/registry/referrers_test.go +++ b/src/server/registry/referrers_test.go @@ -3,7 +3,6 @@ package registry import ( "context" "encoding/json" - "fmt" "net/http" "net/http/httptest" "testing" @@ -11,7 +10,6 @@ import ( beegocontext "github.com/beego/beego/v2/server/web/context" ocispec "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/goharbor/harbor/src/lib/errors" accessorymodel "github.com/goharbor/harbor/src/pkg/accessory/model" basemodel "github.com/goharbor/harbor/src/pkg/accessory/model/base" "github.com/goharbor/harbor/src/pkg/artifact" @@ -93,8 +91,10 @@ func TestReferrersHandlerEmpty(t *testing.T) { artifactMock := &arttesting.Manager{} accessoryMock := &accessorytesting.Manager{} - artifactMock.On("GetByDigest", mock.Anything, mock.Anything, mock.Anything). - Return(nil, errors.NotFoundError(nil)) + accessoryMock.On("Count", mock.Anything, mock.Anything). + Return(int64(0), nil) + accessoryMock.On("List", mock.Anything, mock.Anything). + Return([]accessorymodel.Accessory{}, nil) handler := &referrersHandler{ artifactManager: artifactMock, @@ -109,7 +109,6 @@ func TestReferrersHandlerEmpty(t *testing.T) { } index := &ocispec.Index{} json.Unmarshal([]byte(rec.Body.String()), index) - fmt.Println(index) if index.SchemaVersion != 0 && len(index.Manifests) != -0 { t.Errorf("Expected empty response body, but got %s", rec.Body.String()) } From 950fc06a87abe66bde7730bf481e78c45b6638d6 Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Wed, 6 Mar 2024 02:51:38 +0800 Subject: [PATCH 034/145] Update migration tools (#20075) update migration tools Signed-off-by: stonezdj Co-authored-by: stonezdj --- tools/migrate_chart/Dockerfile | 1 + tools/migrate_chart/migrate_chart.py | 52 +++++++++++++++++++---- tools/migrate_chart/test_migrate_chart.py | 45 ++++++++++++++++++++ 3 files changed, 89 insertions(+), 9 deletions(-) create mode 100644 tools/migrate_chart/test_migrate_chart.py diff --git a/tools/migrate_chart/Dockerfile b/tools/migrate_chart/Dockerfile index 53c1183c5..91f9c813b 100644 --- a/tools/migrate_chart/Dockerfile +++ b/tools/migrate_chart/Dockerfile @@ -9,6 +9,7 @@ ADD https://get.helm.sh/helm-v3.9.1-linux-amd64.tar.gz / RUN tar zxvf /helm-v3.9.1-linux-amd64.tar.gz && \ pip install click==7.1.2 && \ pip install requests==2.24.0 && \ + pip install pyyaml && \ chmod +x /migrate_chart.sh ./migrate_chart.py ENTRYPOINT [ "/migrate_chart.py" ] \ No newline at end of file diff --git a/tools/migrate_chart/migrate_chart.py b/tools/migrate_chart/migrate_chart.py index 70a1f00cd..71bfc4470 100644 --- a/tools/migrate_chart/migrate_chart.py +++ b/tools/migrate_chart/migrate_chart.py @@ -3,7 +3,10 @@ import subprocess import signal import sys +import os from pathlib import Path +import tarfile +import yaml import click import requests @@ -32,24 +35,51 @@ def graceful_exit(signum, frame): signal.signal(signal.SIGINT, graceful_exit) signal.signal(signal.SIGTERM, graceful_exit) +def find_chart_yaml(tar, path=''): + # Iterate through the members of the tarfile + for member in tar.getmembers(): + # If the member is a directory, recursively search within it + if member.isdir(): + find_chart_yaml(tar, os.path.join(path, member.name)) + # If the member is a file and its name is 'chart.yaml', return its path + if "Chart.yaml" in member.name: + return os.path.join(path, member.name) + +def read_chart_version(chart_tgz_path): + # Open the chart tgz file + with tarfile.open(chart_tgz_path, 'r:gz') as tar: + # Find the path to chart.yaml within the tarball + chart_yaml_path = find_chart_yaml(tar) + if chart_yaml_path: + # Extract the chart.yaml file + chart_yaml_file = tar.extractfile(chart_yaml_path) + if chart_yaml_file is not None: + # Load the YAML content from chart.yaml + chart_data = yaml.safe_load(chart_yaml_file) + # Read the version from chart.yaml + version = chart_data.get('version') + name = chart_data.get('name') + return name, version + else: + raise Exception("Failed to read chart.yaml from the chart tgz file. filename {}".format(chart_tgz_path)) + else: + raise Exception("chart.yaml not found in the chart tgz file. filename {}".format(chart_tgz_path)) + class ChartV2: def __init__(self, filepath:Path): self.filepath = filepath self.project = self.filepath.parts[-2] - parts = self.filepath.stem.split('-') - flag = False + self.name = "" + self.version = "" try: - for i in range(len(parts)-1, -1, -1): - if parts[i][0].isnumeric() or ((parts[i][0]=='v' or parts[i][0]=='v') and parts[i][1].isnumeric()) : - self.name, self.version = '-'.join(parts[:i]), '-'.join(parts[i:]) - flag = True - break - if not flag: + self.name, self.version = read_chart_version(filepath) + if self.name == "" or self.version == "" or self.name is None or self.version is None : raise Exception('chart name: {} is illegal'.format('-'.join(parts))) except Exception as e: click.echo("Skipped chart: {} due to illegal chart name. Error: {}".format(filepath, e), err=True) return + def __check_exist(self, hostname, username, password): return requests.get(CHART_URL_PATTERN.format( host=hostname, @@ -90,6 +120,9 @@ def migrate(hostname, username, password): item_show_func=lambda x: "{}/{}:{} total errors: {}".format(x.project, x.name, x.version, len(errs)) if x else '') as bar: for chart in bar: try: + if chart.name == "" or chart.version == "" : + print("skip the chart {} has no name or version info".format(chart.filepath)) + continue result = chart.migrate(hostname, username, password) if result.stderr: errs.append("chart: {name}:{version} in {project} has err: {err}".format( @@ -99,10 +132,11 @@ def migrate(hostname, username, password): err=result.stderr )) except Exception as e: - errs.append("chart: {name}:{version} in {project} has err: {err}".format( + errs.append("chart: {name}:{version} in {project}, path {path} has err: {err}".format( name=chart.name, version=chart.version, project=chart.project, + path = chart.filepath, err=e)) click.echo("Migration is Done.") print_exist_errs() diff --git a/tools/migrate_chart/test_migrate_chart.py b/tools/migrate_chart/test_migrate_chart.py new file mode 100644 index 000000000..b3d5e01ac --- /dev/null +++ b/tools/migrate_chart/test_migrate_chart.py @@ -0,0 +1,45 @@ + +import unittest +import semver +import tempfile +import os +import tarfile +import shutil +from pathlib import Path +from migrate_chart import extract_chart_name_and_version +from migrate_chart import read_chart_version + +class TestExtractChartNameAndVersion(unittest.TestCase): + def test_valid_chart_name(self): + filepath = Path("my-project/my-chart-v1.0.0.tgz") + name, version = extract_chart_name_and_version(filepath) + self.assertEqual(name, "my-chart") + self.assertEqual(version, "v1.0.0") + + def test_invalid_chart_name(self): + filepath = Path("my-project/mychart.tgz") + name, version = extract_chart_name_and_version(filepath) + self.assertIsNone(name) + self.assertIsNone(version) + + # def test_pure_digit(self): + # filepath = Path("my-project/my-chart-8.0.0-5.tgz") + # name, version = extract_chart_name_and_version(filepath) + # self.assertEqual(name, "my-chart") + # self.assertEqual(version, "8.0.0") + + # def test_digit_startv(self): + # filepath = Path("my-project/my-chart-v8.0.0-5.tgz") + # name, version = extract_chart_name_and_version(filepath) + # self.assertEqual(name, "my-chart") + # self.assertEqual(version, "8.0.0") + + def test_parse_version(self): + temp_dir = tempfile.mkdtemp() + file_name = "/Users/daojunz/Downloads/cert-manager/sample/cert-manager-8.0.0-5.tgz" + name, version = read_chart_version(file_name) + print(name) + print(version) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From 9b7c1a2274fbc5ea16e19a484532f86c08926577 Mon Sep 17 00:00:00 2001 From: Lichao Xue <68891670+xuelichao@users.noreply.github.com> Date: Wed, 6 Mar 2024 16:06:25 +0800 Subject: [PATCH 035/145] Configure to auto generate SBOM or not on project configuration page. (#20059) Signed-off-by: xuelichao Co-authored-by: Wang Yan --- .../project-policy-config.component.html | 17 +++++++++++++++++ .../project-policy-config.component.spec.ts | 5 +++++ .../project-policy-config.component.ts | 3 +++ .../project-policy-config/project.ts | 2 ++ src/portal/src/app/base/project/project.ts | 1 + .../src/app/shared/services/project.service.ts | 3 +++ src/portal/src/i18n/lang/de-de-lang.json | 5 ++++- src/portal/src/i18n/lang/en-us-lang.json | 5 ++++- src/portal/src/i18n/lang/es-es-lang.json | 5 ++++- src/portal/src/i18n/lang/fr-fr-lang.json | 5 ++++- src/portal/src/i18n/lang/pt-br-lang.json | 5 ++++- src/portal/src/i18n/lang/tr-tr-lang.json | 5 ++++- src/portal/src/i18n/lang/zh-cn-lang.json | 5 ++++- src/portal/src/i18n/lang/zh-tw-lang.json | 5 ++++- 14 files changed, 63 insertions(+), 8 deletions(-) diff --git a/src/portal/src/app/base/project/project-config/project-policy-config/project-policy-config.component.html b/src/portal/src/app/base/project/project-config/project-policy-config/project-policy-config.component.html index 4daac1a79..c0092c78b 100644 --- a/src/portal/src/app/base/project/project-config/project-policy-config/project-policy-config.component.html +++ b/src/portal/src/app/base/project/project-config/project-policy-config/project-policy-config.component.html @@ -121,6 +121,23 @@ {{ 'PROJECT_CONFIG.AUTOSCAN_POLICY' | translate }} + + + + + + + + {{ 'PROJECT_CONFIG.AUTOSBOM_POLICY' | translate }} + +
{ expect( component.projectPolicyConfigComponent.projectPolicy.ScanImgOnPush ).toBeTruthy(); + expect( + component.projectPolicyConfigComponent.projectPolicy + .GenerateSbomOnPush + ).toBeTruthy(); }); it('should get hasChangeConfigRole', () => { expect( diff --git a/src/portal/src/app/base/project/project-config/project-policy-config/project-policy-config.component.ts b/src/portal/src/app/base/project/project-config/project-policy-config/project-policy-config.component.ts index 60d30e251..2be4f3407 100644 --- a/src/portal/src/app/base/project/project-config/project-policy-config/project-policy-config.component.ts +++ b/src/portal/src/app/base/project/project-config/project-policy-config/project-policy-config.component.ts @@ -32,6 +32,7 @@ export class ProjectPolicy { PreventVulImg: boolean; PreventVulImgSeverity: string; ScanImgOnPush: boolean; + GenerateSbomOnPush: boolean; constructor() { this.Public = false; @@ -40,6 +41,7 @@ export class ProjectPolicy { this.PreventVulImg = false; this.PreventVulImgSeverity = LOW; this.ScanImgOnPush = false; + this.GenerateSbomOnPush = false; } initByProject(pro: Project) { @@ -52,6 +54,7 @@ export class ProjectPolicy { this.PreventVulImgSeverity = pro.metadata.severity; } this.ScanImgOnPush = pro.metadata.auto_scan === 'true'; + this.GenerateSbomOnPush = pro.metadata.auto_sbom_generation === 'true'; } } diff --git a/src/portal/src/app/base/project/project-config/project-policy-config/project.ts b/src/portal/src/app/base/project/project-config/project-policy-config/project.ts index e94749945..8772eab01 100644 --- a/src/portal/src/app/base/project/project-config/project-policy-config/project.ts +++ b/src/portal/src/app/base/project/project-config/project-policy-config/project.ts @@ -19,6 +19,7 @@ export class Project { prevent_vul: string | boolean; severity: string; auto_scan: string | boolean; + auto_sbom_generation: string | boolean; reuse_sys_cve_allowlist?: string; }; cve_allowlist?: object; @@ -28,5 +29,6 @@ export class Project { this.metadata.prevent_vul = false; this.metadata.severity = 'low'; this.metadata.auto_scan = false; + this.metadata.auto_sbom_generation = false; } } diff --git a/src/portal/src/app/base/project/project.ts b/src/portal/src/app/base/project/project.ts index 495edf664..d72d22f11 100644 --- a/src/portal/src/app/base/project/project.ts +++ b/src/portal/src/app/base/project/project.ts @@ -33,6 +33,7 @@ export class Project { prevent_vul: string | boolean; severity: string; auto_scan: string | boolean; + auto_sbom_generation: string | boolean; retention_id: number; }; constructor() { diff --git a/src/portal/src/app/shared/services/project.service.ts b/src/portal/src/app/shared/services/project.service.ts index 0a7a7ff8d..6b8fa2e74 100644 --- a/src/portal/src/app/shared/services/project.service.ts +++ b/src/portal/src/app/shared/services/project.service.ts @@ -158,6 +158,9 @@ export class ProjectDefaultService extends ProjectService { auto_scan: projectPolicy.ScanImgOnPush ? 'true' : 'false', + auto_sbom_generation: projectPolicy.GenerateSbomOnPush + ? 'true' + : 'false', reuse_sys_cve_allowlist: reuseSysCVEVAllowlist, }, cve_allowlist: projectAllowlist, diff --git a/src/portal/src/i18n/lang/de-de-lang.json b/src/portal/src/i18n/lang/de-de-lang.json index a1ed8993f..409668644 100644 --- a/src/portal/src/i18n/lang/de-de-lang.json +++ b/src/portal/src/i18n/lang/de-de-lang.json @@ -286,7 +286,10 @@ "PREVENT_VULNERABLE_2": "und darüber.", "SCAN": "Scannen auf Schwachstellen", "AUTOSCAN_TOGGLE": "Images automatisch beim Hochladen scannen", - "AUTOSCAN_POLICY": "Scanne Images automatisch, wenn sie in das Projekt hochgeladen werden." + "AUTOSCAN_POLICY": "Scanne Images automatisch, wenn sie in das Projekt hochgeladen werden.", + "SBOM": "SBOM generation", + "AUTOSBOM_TOGGLE": "Automatically generate SBOM on push", + "AUTOSBOM_POLICY": "Automatically generate SBOM when the images are pushed to the project registry." }, "MEMBER": { "NEW_USER": "Nutzer als Mitglied hinzufügen", diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json index 0f1b9c62f..3fdca65f9 100644 --- a/src/portal/src/i18n/lang/en-us-lang.json +++ b/src/portal/src/i18n/lang/en-us-lang.json @@ -286,7 +286,10 @@ "PREVENT_VULNERABLE_2": "and above from being deployed.", "SCAN": "Vulnerability scanning", "AUTOSCAN_TOGGLE": "Automatically scan images on push", - "AUTOSCAN_POLICY": "Automatically scan images when they are pushed to the project registry." + "AUTOSCAN_POLICY": "Automatically scan images when they are pushed to the project registry.", + "SBOM": "SBOM generation", + "AUTOSBOM_TOGGLE": "Automatically generate SBOM on push", + "AUTOSBOM_POLICY": "Automatically generate SBOM when the images are pushed to the project registry." }, "MEMBER": { "NEW_USER": "Add User Member", diff --git a/src/portal/src/i18n/lang/es-es-lang.json b/src/portal/src/i18n/lang/es-es-lang.json index e2bc7cec8..7b0055b53 100644 --- a/src/portal/src/i18n/lang/es-es-lang.json +++ b/src/portal/src/i18n/lang/es-es-lang.json @@ -287,7 +287,10 @@ "PREVENT_VULNERABLE_2": "y más arriba de ser desplegado.", "SCAN": "Escaneo de vulnerabilidad", "AUTOSCAN_TOGGLE": "Escanee automáticamente las imágenes al instante", - "AUTOSCAN_POLICY": "Escanee automáticamente las imágenes cuando son enviadas al registro del proyecto." + "AUTOSCAN_POLICY": "Escanee automáticamente las imágenes cuando son enviadas al registro del proyecto.", + "SBOM": "SBOM generation", + "AUTOSBOM_TOGGLE": "Automatically generate SBOM on push", + "AUTOSBOM_POLICY": "Automatically generate SBOM when the images are pushed to the project registry." }, "MEMBER": { "NEW_USER": "Add User Member", diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json index 07cb73b8e..df74ba9ee 100644 --- a/src/portal/src/i18n/lang/fr-fr-lang.json +++ b/src/portal/src/i18n/lang/fr-fr-lang.json @@ -286,7 +286,10 @@ "PREVENT_VULNERABLE_2": "et au-dessus d'être déployées.", "SCAN": "Analyse des vulnérabilités", "AUTOSCAN_TOGGLE": "Analyse automatique des images lors de l'envoi", - "AUTOSCAN_POLICY": "Analyser automatiquement les images lorsqu'elles sont envoyées au projet du registre." + "AUTOSCAN_POLICY": "Analyser automatiquement les images lorsqu'elles sont envoyées au projet du registre.", + "SBOM": "SBOM generation", + "AUTOSBOM_TOGGLE": "Automatically generate SBOM on push", + "AUTOSBOM_POLICY": "Automatically generate SBOM when the images are pushed to the project registry." }, "MEMBER": { "NEW_USER": "Ajouter un nouveau membre", diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json index 08c74b311..7dcdaf6bb 100644 --- a/src/portal/src/i18n/lang/pt-br-lang.json +++ b/src/portal/src/i18n/lang/pt-br-lang.json @@ -284,7 +284,10 @@ "PREVENT_VULNERABLE_2": "e acima de serem utilizadas.", "SCAN": "Análise de vulnerabilidades", "AUTOSCAN_TOGGLE": "Verificar imagens automaticamente", - "AUTOSCAN_POLICY": "Imagens serão analisadas automaticamente quando enviadas ao repositório do projeto." + "AUTOSCAN_POLICY": "Imagens serão analisadas automaticamente quando enviadas ao repositório do projeto.", + "SBOM": "SBOM generation", + "AUTOSBOM_TOGGLE": "Automatically generate SBOM on push", + "AUTOSBOM_POLICY": "Automatically generate SBOM when the images are pushed to the project registry." }, "MEMBER": { "NEW_USER": "Adicionar um usuário", diff --git a/src/portal/src/i18n/lang/tr-tr-lang.json b/src/portal/src/i18n/lang/tr-tr-lang.json index d64c5708b..f4538bb8c 100644 --- a/src/portal/src/i18n/lang/tr-tr-lang.json +++ b/src/portal/src/i18n/lang/tr-tr-lang.json @@ -286,7 +286,10 @@ "PREVENT_VULNERABLE_2": "ve yukarıda yüklenilmekte.", "SCAN": "Güvenlik açığı taraması", "AUTOSCAN_TOGGLE": "İmajları yüklerken anında tarayın", - "AUTOSCAN_POLICY": "İmajlar proje kayıt defterine yüklenildiğinde otomatik olarak tarayın." + "AUTOSCAN_POLICY": "İmajlar proje kayıt defterine yüklenildiğinde otomatik olarak tarayın.", + "SBOM": "SBOM generation", + "AUTOSBOM_TOGGLE": "Automatically generate SBOM on push", + "AUTOSBOM_POLICY": "Automatically generate SBOM when the images are pushed to the project registry." }, "MEMBER": { "NEW_USER": "Kullanıcı Üyesi Ekle", diff --git a/src/portal/src/i18n/lang/zh-cn-lang.json b/src/portal/src/i18n/lang/zh-cn-lang.json index 5a6cbadcc..80b355fef 100644 --- a/src/portal/src/i18n/lang/zh-cn-lang.json +++ b/src/portal/src/i18n/lang/zh-cn-lang.json @@ -285,7 +285,10 @@ "PREVENT_VULNERABLE_2": "以上的镜像运行。", "SCAN": "漏洞扫描", "AUTOSCAN_TOGGLE": "自动扫描镜像", - "AUTOSCAN_POLICY": "当镜像上传后,自动进行扫描" + "AUTOSCAN_POLICY": "当镜像上传后,自动进行扫描", + "SBOM": "SBOM generation", + "AUTOSBOM_TOGGLE": "Automatically generate SBOM on push", + "AUTOSBOM_POLICY": "Automatically generate SBOM when the images are pushed to the project registry." }, "MEMBER": { "NEW_USER": "添加用户成员", diff --git a/src/portal/src/i18n/lang/zh-tw-lang.json b/src/portal/src/i18n/lang/zh-tw-lang.json index 0a7c37440..1f81aa489 100644 --- a/src/portal/src/i18n/lang/zh-tw-lang.json +++ b/src/portal/src/i18n/lang/zh-tw-lang.json @@ -285,7 +285,10 @@ "PREVENT_VULNERABLE_2": "或更高危險級別的映像檔部署。", "SCAN": "弱點掃描", "AUTOSCAN_TOGGLE": "推送時自動掃描映像檔", - "AUTOSCAN_POLICY": "當映像檔推送到專案儲存庫時自動掃描。" + "AUTOSCAN_POLICY": "當映像檔推送到專案儲存庫時自動掃描。", + "SBOM": "SBOM generation", + "AUTOSBOM_TOGGLE": "Automatically generate SBOM on push", + "AUTOSBOM_POLICY": "Automatically generate SBOM when the images are pushed to the project registry." }, "MEMBER": { "NEW_USER": "新增使用者成員", From 6ca30a3732ae0f970026b9ca4986394ab7e103f0 Mon Sep 17 00:00:00 2001 From: guangwu Date: Mon, 11 Mar 2024 16:28:23 +0800 Subject: [PATCH 036/145] fix: typos (#20106) Signed-off-by: guangwu --- make/harbor.yml.tmpl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/make/harbor.yml.tmpl b/make/harbor.yml.tmpl index 8c5abe071..72c9dff44 100644 --- a/make/harbor.yml.tmpl +++ b/make/harbor.yml.tmpl @@ -19,7 +19,7 @@ https: # enable strong ssl ciphers (default: false) # strong_ssl_ciphers: false -# # Harbor will set ipv4 enabled only by defualt if this block is not configured +# # Harbor will set ipv4 enabled only by default if this block is not configured # # Otherwise, please uncomment this block to configure your own ip_family stacks # ip_family: # # ipv6Enabled set to true if ipv6 is enabled in docker network, currently it affected the nginx related component @@ -253,7 +253,7 @@ proxy: # enabled: true # # set sample_rate to 1 if you wanna sampling 100% of trace data; set 0.5 if you wanna sampling 50% of trace data, and so forth # sample_rate: 1 -# # # namespace used to differenciate different harbor services +# # # namespace used to differentiate different harbor services # # namespace: # # # attributes is a key value dict contains user defined attributes used to initialize trace provider # # attributes: @@ -306,6 +306,6 @@ cache: # # can improve the performance of high concurrent pushing to the same project, # # and reduce the database connections spike and occupies. # # By redis will bring up some delay for quota usage updation for display, so only -# # suggest switch provider to redis if you were ran into the db connections spike aroud -# # the scenario of high concurrent pushing to same project, no improvment for other scenes. +# # suggest switch provider to redis if you were ran into the db connections spike around +# # the scenario of high concurrent pushing to same project, no improvement for other scenes. # quota_update_provider: redis # Or db From 8bec57ffd45b4bd31e190ae3b499086eb5d9cb07 Mon Sep 17 00:00:00 2001 From: jm-nab <146757414+jm-nab@users.noreply.github.com> Date: Mon, 11 Mar 2024 22:00:42 -0500 Subject: [PATCH 037/145] Update swagger.yaml bad request permission: helm-chart:read (#20094) * Update swagger.yaml Signed-off-by: jm-nab <146757414+jm-nab@users.noreply.github.com> * Update swagger.yaml reference the rbac/const.go source code Signed-off-by: jm-nab <146757414+jm-nab@users.noreply.github.com> --------- Signed-off-by: jm-nab <146757414+jm-nab@users.noreply.github.com> --- api/v2.0/swagger.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index 094076925..c0f3bf596 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -7761,7 +7761,7 @@ definitions: properties: resource: type: string - description: The resource of the access. Possible resources are *, artifact, artifact-addition, artifact-label, audit-log, catalog, configuration, distribution, garbage-collection, helm-chart, helm-chart-version, helm-chart-version-label, immutable-tag, label, ldap-user, log, member, metadata, notification-policy, preheat-instance, preheat-policy, project, quota, registry, replication, replication-adapter, replication-policy, repository, robot, scan, scan-all, scanner, system-volumes, tag, tag-retention, user, user-group or "" (for self-reference). + description: The resource of the access. Possible resources are listed here for system and project level https://github.com/goharbor/harbor/blob/main/src/common/rbac/const.go action: type: string description: The action of the access. Possible actions are *, pull, push, create, read, update, delete, list, operate, scanner-pull and stop. From dbe9790147b8ec4ad6aabe7a6665edfe998d99d5 Mon Sep 17 00:00:00 2001 From: Wang Yan Date: Tue, 12 Mar 2024 12:27:34 +0800 Subject: [PATCH 038/145] add generate sbom object utility (#20097) * add generate sbom object utility Leverage the go-containerregistry to generate the oci object for sbom and add it as an accessory of the subject artifact. Signed-off-by: wang yan * remove vendor Signed-off-by: wang yan * resolve comments Signed-off-by: wang yan * fix ut Signed-off-by: wang yan * resolve comments Signed-off-by: wang yan * remove the todo comments Signed-off-by: wang yan --------- Signed-off-by: wang yan --- src/controller/scan/base_controller.go | 1 + src/go.mod | 14 ++- src/go.sum | 22 +++++ src/pkg/scan/rest/v1/models.go | 2 + src/pkg/scan/util.go | 95 +++++++++++++++++++ src/pkg/scan/util_test.go | 64 +++++++++++++ src/server/middleware/subject/subject_test.go | 12 +-- src/server/registry/referrers_test.go | 6 +- 8 files changed, 204 insertions(+), 12 deletions(-) create mode 100644 src/pkg/scan/util.go create mode 100644 src/pkg/scan/util_test.go diff --git a/src/controller/scan/base_controller.go b/src/controller/scan/base_controller.go index d5e36737f..143687516 100644 --- a/src/controller/scan/base_controller.go +++ b/src/controller/scan/base_controller.go @@ -994,6 +994,7 @@ func (bc *basicController) launchScanJob(ctx context.Context, param *launchScanJ Digest: param.Artifact.Digest, Tag: param.Tag, MimeType: param.Artifact.ManifestMediaType, + Size: param.Artifact.Size, }, } diff --git a/src/go.mod b/src/go.mod index 12855b83c..72cce223f 100644 --- a/src/go.mod +++ b/src/go.mod @@ -21,18 +21,19 @@ require ( github.com/go-asn1-ber/asn1-ber v1.5.5 github.com/go-ldap/ldap/v3 v3.4.6 github.com/go-openapi/errors v0.21.0 - github.com/go-openapi/loads v0.21.2 // indirect + github.com/go-openapi/loads v0.21.2 github.com/go-openapi/runtime v0.26.2 - github.com/go-openapi/spec v0.20.11 // indirect + github.com/go-openapi/spec v0.20.11 github.com/go-openapi/strfmt v0.22.0 github.com/go-openapi/swag v0.22.7 - github.com/go-openapi/validate v0.22.3 // indirect + github.com/go-openapi/validate v0.22.3 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 github.com/golang-jwt/jwt/v5 v5.2.0 github.com/golang-migrate/migrate/v4 v4.16.2 github.com/gomodule/redigo v2.0.0+incompatible + github.com/google/go-containerregistry v0.19.0 github.com/google/uuid v1.6.0 github.com/gorilla/csrf v1.6.2 github.com/gorilla/handlers v1.5.2 @@ -95,10 +96,14 @@ require ( github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/denverdino/aliyungo v0.0.0-20191227032621-df38c6fa730c // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dnaeon/go-vcr v1.2.0 // indirect + github.com/docker/cli v24.0.6+incompatible // indirect + github.com/docker/docker v24.0.7+incompatible // indirect + github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect @@ -130,10 +135,12 @@ 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.16.5 // indirect github.com/magiconair/properties v1.8.5 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect 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 @@ -154,6 +161,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect + 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 go.mongodb.org/mongo-driver v1.13.1 // indirect diff --git a/src/go.sum b/src/go.sum index f6aeb03d7..2ca9fa57f 100644 --- a/src/go.sum +++ b/src/go.sum @@ -46,6 +46,7 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/FZambia/sentinel v1.1.0 h1:qrCBfxc8SvJihYNjBWgwUI93ZCvFe/PJIPTHKmlp8a8= github.com/FZambia/sentinel v1.1.0/go.mod h1:ytL1Am/RLlAoAXG6Kj5LNuw/TRRQrv2rt2FT26vP5gI= @@ -109,12 +110,15 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= +github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= github.com/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo= github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -133,8 +137,12 @@ github.com/distribution/distribution v2.8.2+incompatible h1:k9+4DKdOG+quPFZXT/mU github.com/distribution/distribution v2.8.2+incompatible/go.mod h1:EgLm2NgWtdKgzF9NpMzUKgzmR7AMmb0VQi2B+ZzDRjc= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/docker/cli v24.0.6+incompatible h1:fF+XCQCgJjjQNIMjzaSmiKJSCcfcXb3TWTcc7GAneOY= +github.com/docker/cli v24.0.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= +github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= @@ -274,6 +282,8 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-containerregistry v0.19.0 h1:uIsMRBV7m/HDkDxE/nXMnv1q+lOOSPlQ/ywc5JbB8Ic= +github.com/google/go-containerregistry v0.19.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -406,6 +416,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V 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.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= +github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 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/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= @@ -450,6 +462,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfr github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= @@ -545,6 +559,7 @@ github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUz github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= @@ -558,6 +573,7 @@ github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFR github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= @@ -598,8 +614,11 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69 github.com/tencentcloud/tencentcloud-sdk-go v1.0.62 h1:Vnr3IqaafEuQUciG6D6EaeLJm26Mg8sjAfbI4OoeauM= github.com/tencentcloud/tencentcloud-sdk-go v1.0.62/go.mod h1:asUz5BPXxgoPGaRgZaVm1iGcUAuHyYUo1nXqKa83cvI= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck= +github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= @@ -799,6 +818,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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= @@ -945,6 +965,8 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= helm.sh/helm/v3 v3.14.2 h1:V71fv+NGZv0icBlr+in1MJXuUIHCiPG1hW9gEBISTIA= helm.sh/helm/v3 v3.14.2/go.mod h1:2itvvDv2WSZXTllknfQo6j7u3VVgMAvm8POCDgYH424= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/src/pkg/scan/rest/v1/models.go b/src/pkg/scan/rest/v1/models.go index c7579cd74..50de0cbf5 100644 --- a/src/pkg/scan/rest/v1/models.go +++ b/src/pkg/scan/rest/v1/models.go @@ -150,6 +150,8 @@ type Artifact struct { Digest string `json:"digest"` // The mime type of the scanned artifact MimeType string `json:"mime_type"` + // The size the scanned artifact + Size int64 `json:"size"` } // Registry represents Registry connection settings. diff --git a/src/pkg/scan/util.go b/src/pkg/scan/util.go new file mode 100644 index 000000000..e7b65b1e0 --- /dev/null +++ b/src/pkg/scan/util.go @@ -0,0 +1,95 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package scan + +import ( + "crypto/tls" + "fmt" + "net/http" + + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/empty" + "github.com/google/go-containerregistry/pkg/v1/mutate" + "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/google/go-containerregistry/pkg/v1/static" + "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + + "github.com/goharbor/harbor/src/controller/robot" + v1sq "github.com/goharbor/harbor/src/pkg/scan/rest/v1" +) + +type Insecure bool + +func (i Insecure) RemoteOptions() []remote.Option { + tr := http.DefaultTransport.(*http.Transport).Clone() + tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: bool(i)} + return []remote.Option{remote.WithTransport(tr)} +} + +type referrer struct { + Insecure +} + +// GenAccessoryArt composes the accessory oci object and push it back to harbor core as an accessory of the scanned artifact. +func GenAccessoryArt(sq v1sq.ScanRequest, accData []byte, accAnnotations map[string]string, mediaType string, robot robot.Robot) (string, error) { + accArt, err := mutate.Append(empty.Image, mutate.Addendum{ + Layer: static.NewLayer(accData, ocispec.MediaTypeImageLayer), + History: v1.History{ + Author: "harbor", + CreatedBy: "harbor", + Created: v1.Time{}, // static + }, + }) + if err != nil { + return "", err + } + + dg, err := digest.Parse(sq.Artifact.Digest) + if err != nil { + return "", err + } + accSubArt := &v1.Descriptor{ + MediaType: types.MediaType(sq.Artifact.MimeType), + Size: sq.Artifact.Size, + Digest: v1.Hash{ + Algorithm: dg.Algorithm().String(), + Hex: dg.Hex(), + }, + } + // TODO to leverage the artifactType of distribution spec v1.1 to specify the sbom type. + // https://github.com/google/go-containerregistry/issues/1832 + accArt = mutate.MediaType(accArt, ocispec.MediaTypeImageManifest) + accArt = mutate.ConfigMediaType(accArt, types.MediaType(mediaType)) + accArt = mutate.Subject(accArt, *accSubArt).(v1.Image) + accArt = mutate.Annotations(accArt, accAnnotations).(v1.Image) + + digest, err := accArt.Digest() + if err != nil { + return "", err + } + accRef, err := name.ParseReference(fmt.Sprintf("%s/%s@%s", sq.Registry.URL, sq.Artifact.Repository, digest.String())) + if err != nil { + return "", err + } + opts := append(referrer{Insecure: true}.RemoteOptions(), remote.WithAuth(&authn.Basic{Username: robot.Name, Password: robot.Secret})) + if err := remote.Write(accRef, accArt, opts...); err != nil { + return "", err + } + return digest.String(), nil +} diff --git a/src/pkg/scan/util_test.go b/src/pkg/scan/util_test.go new file mode 100644 index 000000000..f293f026a --- /dev/null +++ b/src/pkg/scan/util_test.go @@ -0,0 +1,64 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package scan + +import ( + "net/http/httptest" + "net/url" + "testing" + + "github.com/google/go-containerregistry/pkg/registry" + "github.com/stretchr/testify/assert" + + "github.com/goharbor/harbor/src/controller/robot" + rm "github.com/goharbor/harbor/src/pkg/robot/model" + v1sq "github.com/goharbor/harbor/src/pkg/scan/rest/v1" +) + +func TestGenAccessoryArt(t *testing.T) { + server := httptest.NewServer(registry.New(registry.WithReferrersSupport(true))) + defer server.Close() + u, err := url.Parse(server.URL) + if err != nil { + t.Fatal(err) + } + + sq := v1sq.ScanRequest{ + Registry: &v1sq.Registry{ + URL: u.Host, + }, + Artifact: &v1sq.Artifact{ + Repository: "library/hello-world", + Tag: "latest", + Size: 1234, + MimeType: "application/vnd.docker.distribution.manifest.v2+json", + Digest: "sha256:d37ada95d47ad12224c205a938129df7a3e52345828b4fa27b03a98825d1e2e7", + }, + } + r := robot.Robot{ + Robot: rm.Robot{ + Name: "admin", + Secret: "Harbor12345", + }, + } + + annotations := map[string]string{ + "created-by": "trivy", + "org.opencontainers.artifact.description": "SPDX JSON SBOM", + } + s, err := GenAccessoryArt(sq, []byte(`{"name": "harborAccTest", "version": "1.0"}`), annotations, "application/vnd.goharbor.harbor.main.v1", r) + assert.Nil(t, err) + assert.Equal(t, "sha256:8de6104b79deca0253ff8667692f03e34753494c77ec81f631b45aad69223c18", s) +} diff --git a/src/server/middleware/subject/subject_test.go b/src/server/middleware/subject/subject_test.go index 5dc11c333..323b0c78b 100644 --- a/src/server/middleware/subject/subject_test.go +++ b/src/server/middleware/subject/subject_test.go @@ -44,19 +44,19 @@ func (suite *MiddlewareTestSuite) prepare(name, digset string, withoutSub ...boo "schemaVersion":2, "mediaType":"application/vnd.oci.image.manifest.v1+json", "config":{ - "mediaType":"application/vnd.example.sbom", + "mediaType":"application/vnd.example.main", "size":2, "digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a" }, "layers":[ { - "mediaType":"application/vnd.example.sbom.text", + "mediaType":"application/vnd.example.main.text", "size":37, "digest":"sha256:45592a729ef6884ea3297e9510d79104f27aeef5f4919b3a921e3abb7f469709" } ], "annotations":{ - "org.example.sbom.format":"text" + "org.example.main.format":"text" }, "subject":{ "mediaType":"application/vnd.oci.image.manifest.v1+json", @@ -70,19 +70,19 @@ func (suite *MiddlewareTestSuite) prepare(name, digset string, withoutSub ...boo "schemaVersion":2, "mediaType":"application/vnd.oci.image.manifest.v1+json", "config":{ - "mediaType":"application/vnd.example.sbom", + "mediaType":"application/vnd.example.main", "size":2, "digest":"%s" }, "layers":[ { - "mediaType":"application/vnd.example.sbom.text", + "mediaType":"application/vnd.example.main.text", "size":37, "digest":"sha256:45592a729ef6884ea3297e9510d79104f27aeef5f4919b3a921e3abb7f469709" } ], "annotations":{ - "org.example.sbom.format":"text" + "org.example.main.format":"text" }}`, digset) } diff --git a/src/server/registry/referrers_test.go b/src/server/registry/referrers_test.go index 473fb0c57..27f1deec3 100644 --- a/src/server/registry/referrers_test.go +++ b/src/server/registry/referrers_test.go @@ -37,7 +37,7 @@ func TestReferrersHandlerOK(t *testing.T) { Return(&artifact.Artifact{ Digest: digestVal, ManifestMediaType: "application/vnd.oci.image.manifest.v1+json", - MediaType: "application/vnd.example.sbom", + MediaType: "application/vnd.example.main", Size: 1000, Annotations: map[string]string{ "name": "test-image", @@ -72,8 +72,8 @@ func TestReferrersHandlerOK(t *testing.T) { } index := &ocispec.Index{} json.Unmarshal([]byte(rec.Body.String()), index) - if index.Manifests[0].ArtifactType != "application/vnd.example.sbom" { - t.Errorf("Expected response body %s, but got %s", "application/vnd.example.sbom", rec.Body.String()) + if index.Manifests[0].ArtifactType != "application/vnd.example.main" { + t.Errorf("Expected response body %s, but got %s", "application/vnd.example.main", rec.Body.String()) } } From a269b4f31cb35676cf268c424b3e7eeb1bfe5c6b Mon Sep 17 00:00:00 2001 From: MinerYang Date: Tue, 12 Mar 2024 21:52:56 +0800 Subject: [PATCH 039/145] Update support for artifactType for both manifest and index (#20030) add artifact_type for artifact model to support artifactType filter Signed-off-by: yminer add 2.11 sql schema & update index artifactType omitted Signed-off-by: yminer update UT update migrate sql for artifact_type Signed-off-by: yminer remove debug line --- .../postgresql/0140_2.11.0_schema.up.sql | 31 +++ src/controller/artifact/abstractor.go | 20 +- src/controller/artifact/abstractor_test.go | 177 +++++++++++++++++- .../artifact/processor/default_test.go | 105 ++++++++++- src/go.mod | 2 +- src/go.sum | 4 +- src/pkg/artifact/dao/model.go | 1 + src/pkg/artifact/model.go | 3 + src/pkg/artifact/model_test.go | 4 + src/pkg/blob/dao/dao_test.go | 2 +- src/pkg/blob/manager_test.go | 4 +- src/pkg/securityhub/dao/security_test.go | 12 +- src/server/middleware/subject/subject.go | 38 ++-- src/server/registry/referrers.go | 6 +- src/server/registry/referrers_test.go | 7 +- 15 files changed, 374 insertions(+), 42 deletions(-) create mode 100644 make/migrations/postgresql/0140_2.11.0_schema.up.sql diff --git a/make/migrations/postgresql/0140_2.11.0_schema.up.sql b/make/migrations/postgresql/0140_2.11.0_schema.up.sql new file mode 100644 index 000000000..b43f6072f --- /dev/null +++ b/make/migrations/postgresql/0140_2.11.0_schema.up.sql @@ -0,0 +1,31 @@ +/* +table artifact: + id SERIAL PRIMARY KEY NOT NULL, + type varchar(255) NOT NULL, + media_type varchar(255) NOT NULL, + manifest_media_type varchar(255) NOT NULL, + artifact_type varchar(255) NOT NULL, + project_id int NOT NULL, + repository_id int NOT NULL, + repository_name varchar(255) NOT NULL, + digest varchar(255) NOT NULL, + size bigint, + push_time timestamp default CURRENT_TIMESTAMP, + pull_time timestamp, + extra_attrs text, + annotations jsonb, + CONSTRAINT unique_artifact UNIQUE (repository_id, digest) +*/ + +/* +Add new column artifact_type for artifact table to work with oci-spec v1.1.0 list referrer api +*/ +ALTER TABLE artifact ADD COLUMN artifact_type varchar(255); + +/* +set value for artifact_type +then set column artifact_type as not null +*/ +UPDATE artifact SET artifact_type = media_type; + +ALTER TABLE artifact ALTER COLUMN artifact_type SET NOT NULL; \ No newline at end of file diff --git a/src/controller/artifact/abstractor.go b/src/controller/artifact/abstractor.go index 233008157..bbf75a1fa 100644 --- a/src/controller/artifact/abstractor.go +++ b/src/controller/artifact/abstractor.go @@ -127,10 +127,18 @@ func (a *abstractor) abstractManifestV2Metadata(artifact *artifact.Artifact, con } // use the "manifest.config.mediatype" as the media type of the artifact artifact.MediaType = manifest.Config.MediaType - if manifest.Annotations[wasm.AnnotationVariantKey] == wasm.AnnotationVariantValue || manifest.Annotations[wasm.AnnotationHandlerKey] == wasm.AnnotationHandlerValue { artifact.MediaType = wasm.MediaType } + /* + https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#listing-referrers + For referrers list, if the artifactType is empty or missing in the image manifest, the value of artifactType MUST be set to the config descriptor mediaType value + */ + if manifest.ArtifactType != "" { + artifact.ArtifactType = manifest.ArtifactType + } else { + artifact.ArtifactType = manifest.Config.MediaType + } // set size artifact.Size = int64(len(content)) + manifest.Config.Size @@ -153,6 +161,16 @@ func (a *abstractor) abstractIndexMetadata(ctx context.Context, art *artifact.Ar return err } + /* + https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#listing-referrers + For referrers list, If the artifactType is empty or missing in an index, the artifactType MUST be omitted. + */ + if index.ArtifactType != "" { + art.ArtifactType = index.ArtifactType + } else { + art.ArtifactType = "" + } + // set annotations art.Annotations = index.Annotations diff --git a/src/controller/artifact/abstractor_test.go b/src/controller/artifact/abstractor_test.go index 47b340f04..e7955ed1c 100644 --- a/src/controller/artifact/abstractor_test.go +++ b/src/controller/artifact/abstractor_test.go @@ -15,6 +15,7 @@ package artifact import ( + "context" "testing" "github.com/docker/distribution" @@ -175,7 +176,66 @@ var ( "com.example.key1": "value1" } }` - + OCIManifest = `{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.example.config.v1+json", + "digest": "sha256:5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", + "size": 123 + }, + "layers": [ + { + "mediaType": "application/vnd.example.data.v1.tar+gzip", + "digest": "sha256:e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317", + "size": 1234 + } + ], + "annotations": { + "com.example.key1": "value1" + } +}` + OCIManifestWithArtifactType = `{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "artifactType": "application/vnd.example+type", + "config": { + "mediaType": "application/vnd.example.config.v1+json", + "digest": "sha256:5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", + "size": 123 + }, + "layers": [ + { + "mediaType": "application/vnd.example.data.v1.tar+gzip", + "digest": "sha256:e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317", + "size": 1234 + } + ], + "annotations": { + "com.example.key1": "value1" + } +}` + OCIManifestWithEmptyConfig = `{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "artifactType": "application/vnd.example+type", + "config": { + "mediaType": "application/vnd.oci.empty.v1+json", + "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", + "size": 2 + }, + "layers": [ + { + "mediaType": "application/vnd.example+type", + "digest": "sha256:e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317", + "size": 1234 + } + ], + "annotations": { + "oci.opencontainers.image.created": "2023-01-02T03:04:05Z", + "com.example.data": "payload" + } +}` index = `{ "schemaVersion": 2, "manifests": [ @@ -202,6 +262,34 @@ var ( "com.example.key1": "value1" } }` + indexWithArtifactType = `{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.index.v1+json", + "artifactType": "application/vnd.food.stand", + "manifests": [ + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "size": 7143, + "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f", + "platform": { + "architecture": "ppc64le", + "os": "linux" + } + }, + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "size": 7682, + "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270", + "platform": { + "architecture": "amd64", + "os": "linux" + } + } + ], + "annotations": { + "com.example.key1": "value1" + } + }` ) type abstractorTestSuite struct { @@ -267,6 +355,67 @@ func (a *abstractorTestSuite) TestAbstractMetadataOfV2Manifest() { a.Equal("value1", artifact.Annotations["com.example.key1"]) } +// oci-spec v1 +func (a *abstractorTestSuite) TestAbstractMetadataOfOCIManifest() { + manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(OCIManifest)) + a.Require().Nil(err) + a.regCli.On("PullManifest", mock.Anything, mock.Anything).Return(manifest, "", nil) + artifact := &artifact.Artifact{ + ID: 1, + } + a.processor.On("AbstractMetadata", mock.Anything, mock.Anything, mock.Anything).Return(nil) + err = a.abstractor.AbstractMetadata(context.TODO(), artifact) + a.Require().Nil(err) + a.Assert().Equal(int64(1), artifact.ID) + a.Assert().Equal(v1.MediaTypeImageManifest, artifact.ManifestMediaType) + a.Assert().Equal("application/vnd.example.config.v1+json", artifact.MediaType) + a.Assert().Equal("application/vnd.example.config.v1+json", artifact.ArtifactType) + a.Assert().Equal(int64(1916), artifact.Size) + a.Require().Len(artifact.Annotations, 1) + a.Equal("value1", artifact.Annotations["com.example.key1"]) +} + +// oci-spec v1.1.0 with artifactType +func (a *abstractorTestSuite) TestAbstractMetadataOfOCIManifestWithArtifactType() { + manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(OCIManifestWithArtifactType)) + a.Require().Nil(err) + a.regCli.On("PullManifest", mock.Anything, mock.Anything).Return(manifest, "", nil) + artifact := &artifact.Artifact{ + ID: 1, + } + a.processor.On("AbstractMetadata", mock.Anything, mock.Anything, mock.Anything).Return(nil) + err = a.abstractor.AbstractMetadata(context.TODO(), artifact) + a.Require().Nil(err) + a.Assert().Equal(int64(1), artifact.ID) + a.Assert().Equal(v1.MediaTypeImageManifest, artifact.ManifestMediaType) + a.Assert().Equal("application/vnd.example.config.v1+json", artifact.MediaType) + a.Assert().Equal("application/vnd.example+type", artifact.ArtifactType) + a.Assert().Equal(int64(1966), artifact.Size) + a.Require().Len(artifact.Annotations, 1) + a.Equal("value1", artifact.Annotations["com.example.key1"]) +} + +// empty config with artifactType +func (a *abstractorTestSuite) TestAbstractMetadataOfV2ManifestWithEmptyConfig() { + // v1.MediaTypeImageManifest + manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(OCIManifestWithEmptyConfig)) + a.Require().Nil(err) + a.regCli.On("PullManifest", mock.Anything, mock.Anything).Return(manifest, "", nil) + artifact := &artifact.Artifact{ + ID: 1, + } + a.processor.On("AbstractMetadata", mock.Anything, mock.Anything, mock.Anything).Return(nil) + err = a.abstractor.AbstractMetadata(context.TODO(), artifact) + a.Require().Nil(err) + a.Assert().Equal(int64(1), artifact.ID) + a.Assert().Equal(v1.MediaTypeImageManifest, artifact.ManifestMediaType) + a.Assert().Equal(v1.MediaTypeEmptyJSON, artifact.MediaType) + a.Assert().Equal("application/vnd.example+type", artifact.ArtifactType) + a.Assert().Equal(int64(1880), artifact.Size) + a.Require().Len(artifact.Annotations, 2) + a.Equal("payload", artifact.Annotations["com.example.data"]) +} + // OCI index func (a *abstractorTestSuite) TestAbstractMetadataOfIndex() { manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageIndex, []byte(index)) @@ -279,17 +428,41 @@ func (a *abstractorTestSuite) TestAbstractMetadataOfIndex() { artifact := &artifact.Artifact{ ID: 1, } - err = a.abstractor.AbstractMetadata(nil, artifact) + err = a.abstractor.AbstractMetadata(context.TODO(), artifact) a.Require().Nil(err) a.Assert().Equal(int64(1), artifact.ID) a.Assert().Equal(v1.MediaTypeImageIndex, artifact.ManifestMediaType) a.Assert().Equal(v1.MediaTypeImageIndex, artifact.MediaType) + a.Assert().Equal("", artifact.ArtifactType) a.Assert().Equal(int64(668), artifact.Size) a.Require().Len(artifact.Annotations, 1) a.Assert().Equal("value1", artifact.Annotations["com.example.key1"]) a.Len(artifact.References, 2) } +func (a *abstractorTestSuite) TestAbstractMetadataOfIndexWithArtifactType() { + manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageIndex, []byte(indexWithArtifactType)) + a.Require().Nil(err) + a.regCli.On("PullManifest", mock.Anything, mock.Anything).Return(manifest, "", nil) + a.argMgr.On("GetByDigest", mock.Anything, mock.Anything, mock.Anything).Return(&artifact.Artifact{ + ID: 2, + Size: 10, + }, nil) + artifact := &artifact.Artifact{ + ID: 1, + } + err = a.abstractor.AbstractMetadata(context.TODO(), artifact) + a.Require().Nil(err) + a.Assert().Equal(int64(1), artifact.ID) + a.Assert().Equal(v1.MediaTypeImageIndex, artifact.ManifestMediaType) + a.Assert().Equal(v1.MediaTypeImageIndex, artifact.MediaType) + a.Assert().Equal("application/vnd.food.stand", artifact.ArtifactType) + a.Assert().Equal(int64(801), artifact.Size) + a.Require().Len(artifact.Annotations, 1) + a.Assert().Equal("value1", artifact.Annotations["com.example.key1"]) + a.Len(artifact.References, 2) +} + type unknownManifest struct{} func (u *unknownManifest) References() []distribution.Descriptor { diff --git a/src/controller/artifact/processor/default_test.go b/src/controller/artifact/processor/default_test.go index 0f6af7734..489941539 100644 --- a/src/controller/artifact/processor/default_test.go +++ b/src/controller/artifact/processor/default_test.go @@ -117,7 +117,31 @@ var ( } ] }` - v2ManifestWithUnknownConfig = `{ + OCIManifestWithUnknownJsonConfig = `{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.exmaple.config.v1+json", + "digest": "sha256:48ef4a53c0770222d9752cd0588431dbda54667046208c79804e34c15c1579cd", + "size": 129 + }, + "layers": [ + { + "mediaType": "application/vnd.example.data.v1.tar+gzip", + "digest": "sha256:e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317", + "size": 1234 + } + ], + "annotations": { + "com.example.key1": "value1" + } + }` + UnknownJsonConfig = `{ + "author": "yminer", + "architecture": "amd64", + "selfdefined": "true" +}` + OCIManifestWithUnknownConfig = `{ "schemaVersion": 2, "mediaType": "application/vnd.oci.image.manifest.v1+json", "config": { @@ -141,7 +165,30 @@ var ( "newUnspecifiedField": null } }` - unknownConfig = `{NHL Peanut Butter on my NHL bagel}` + UnknownConfig = `{NHL Peanut Butter on my NHL bagel}` + + OCIManifestWithEmptyConfig = `{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "artifactType": "application/vnd.example+type", + "config": { + "mediaType": "application/vnd.oci.empty.v1+json", + "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", + "size": 2 + }, + "layers": [ + { + "mediaType": "application/vnd.example+type", + "digest": "sha256:e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317", + "size": 1234 + } + ], + "annotations": { + "oci.opencontainers.image.created": "2023-01-02T03:04:05Z", + "com.example.data": "payload" + } + }` + emptyConfig = `{}` ) type defaultProcessorTestSuite struct { @@ -190,6 +237,12 @@ func (d *defaultProcessorTestSuite) TestGetArtifactType() { typee = processor.GetArtifactType(nil, art) d.Equal("IMAGE", typee) + mediaType = "application/vnd.example.config.v1+json" + art = &artifact.Artifact{MediaType: mediaType} + processor = &defaultProcessor{} + typee = processor.GetArtifactType(nil, art) + d.Equal(ArtifactTypeUnknown, typee) + mediaType = "application/vnd.cncf.helm.chart.config.v1+json" art = &artifact.Artifact{MediaType: mediaType} processor = &defaultProcessor{} @@ -229,19 +282,53 @@ func (d *defaultProcessorTestSuite) TestAbstractMetadata() { d.Len(art.ExtraAttrs, 12) } -func (d *defaultProcessorTestSuite) TestAbstractMetadataWithUnknownConfig() { - manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(v2ManifestWithUnknownConfig)) +func (d *defaultProcessorTestSuite) TestAbstractMetadataOfOCIManifesttWithUnknownJsonConfig() { + manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(OCIManifestWithUnknownJsonConfig)) d.Require().Nil(err) manifestMediaType, content, err := manifest.Payload() d.Require().Nil(err) - configBlob := io.NopCloser(strings.NewReader(unknownConfig)) - d.regCli.On("PullBlob", mock.Anything, mock.Anything).Return(int64(0), configBlob, nil) - art := &artifact.Artifact{ManifestMediaType: manifestMediaType} - err = d.processor.AbstractMetadata(nil, art, content) + configBlob := io.NopCloser(strings.NewReader(UnknownJsonConfig)) + metadata := map[string]interface{}{} + err = json.NewDecoder(configBlob).Decode(&metadata) + d.Require().Nil(err) + + art := &artifact.Artifact{ManifestMediaType: manifestMediaType, MediaType: "application/vnd.example.config.v1+json"} + + d.regCli.On("PullBlob", mock.Anything, mock.Anything).Return(int64(129), configBlob, nil) + d.parser.On("Parse", context.TODO(), mock.AnythingOfType("*artifact.Artifact"), mock.AnythingOfType("[]byte")).Return(nil) + err = d.processor.AbstractMetadata(context.TODO(), art, content) d.Require().Nil(err) d.Len(art.ExtraAttrs, 0) - d.Len(unknownConfig, 35) + d.NotEqual(art.ExtraAttrs, len(metadata)) + +} + +func (d *defaultProcessorTestSuite) TestAbstractMetadataWithUnknownConfig() { + manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(OCIManifestWithUnknownConfig)) + d.Require().Nil(err) + manifestMediaType, content, err := manifest.Payload() + d.Require().Nil(err) + + configBlob := io.NopCloser(strings.NewReader(UnknownConfig)) + d.regCli.On("PullBlob", mock.Anything, mock.Anything).Return(int64(0), configBlob, nil) + art := &artifact.Artifact{ManifestMediaType: manifestMediaType, MediaType: "application/vnd.nhl.peanut.butter.bagel"} + err = d.processor.AbstractMetadata(context.TODO(), art, content) + d.Require().Nil(err) + d.Len(art.ExtraAttrs, 0) +} + +func (d *defaultProcessorTestSuite) TestAbstractMetadataWithEmptyConfig() { + manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(OCIManifestWithEmptyConfig)) + d.Require().Nil(err) + manifestMediaType, content, err := manifest.Payload() + d.Require().Nil(err) + + art := &artifact.Artifact{ManifestMediaType: manifestMediaType, MediaType: "application/vnd.oci.empty.v1+json"} + err = d.processor.AbstractMetadata(context.TODO(), art, content) + d.Assert().Equal(0, len(art.ExtraAttrs)) + d.Assert().Equal(2, len(emptyConfig)) + d.Require().Nil(err) } func TestDefaultProcessorTestSuite(t *testing.T) { diff --git a/src/go.mod b/src/go.mod index 72cce223f..07ff72277 100644 --- a/src/go.mod +++ b/src/go.mod @@ -46,7 +46,7 @@ require ( github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/olekukonko/tablewriter v0.0.5 github.com/opencontainers/go-digest v1.0.0 - github.com/opencontainers/image-spec v1.1.0-rc5 + github.com/opencontainers/image-spec v1.1.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.17.0 github.com/robfig/cron/v3 v3.0.1 diff --git a/src/go.sum b/src/go.sum index 2ca9fa57f..b8532ba34 100644 --- a/src/go.sum +++ b/src/go.sum @@ -514,8 +514,8 @@ github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= 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-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= -github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= diff --git a/src/pkg/artifact/dao/model.go b/src/pkg/artifact/dao/model.go index c9ba1447b..7bb7c36ab 100644 --- a/src/pkg/artifact/dao/model.go +++ b/src/pkg/artifact/dao/model.go @@ -33,6 +33,7 @@ type Artifact struct { Type string `orm:"column(type)"` // image, chart or other OCI compatible MediaType string `orm:"column(media_type)"` // the media type of artifact ManifestMediaType string `orm:"column(manifest_media_type)"` // the media type of manifest/index + ArtifactType string `orm:"colume(artifact_type)"` // the artifactType of manifest/index ProjectID int64 `orm:"column(project_id)"` // needed for quota RepositoryID int64 `orm:"column(repository_id)"` RepositoryName string `orm:"column(repository_name)"` diff --git a/src/pkg/artifact/model.go b/src/pkg/artifact/model.go index 944e370b4..464a31924 100644 --- a/src/pkg/artifact/model.go +++ b/src/pkg/artifact/model.go @@ -34,6 +34,7 @@ type Artifact struct { Type string `json:"type"` // image, chart or other OCI compatible MediaType string `json:"media_type"` // the media type of artifact. Mostly, it's the value of `manifest.config.mediatype` ManifestMediaType string `json:"manifest_media_type"` // the media type of manifest/index + ArtifactType string `json:"artifact_type"` // the artifactType of manifest/index ProjectID int64 `json:"project_id"` RepositoryID int64 `json:"repository_id"` RepositoryName string `json:"repository_name"` @@ -63,6 +64,7 @@ func (a *Artifact) From(art *dao.Artifact) { a.Type = art.Type a.MediaType = art.MediaType a.ManifestMediaType = art.ManifestMediaType + a.ArtifactType = art.ArtifactType a.ProjectID = art.ProjectID a.RepositoryID = art.RepositoryID a.RepositoryName = art.RepositoryName @@ -92,6 +94,7 @@ func (a *Artifact) To() *dao.Artifact { Type: a.Type, MediaType: a.MediaType, ManifestMediaType: a.ManifestMediaType, + ArtifactType: a.ArtifactType, ProjectID: a.ProjectID, RepositoryID: a.RepositoryID, RepositoryName: a.RepositoryName, diff --git a/src/pkg/artifact/model_test.go b/src/pkg/artifact/model_test.go index 537658faf..24bd77eba 100644 --- a/src/pkg/artifact/model_test.go +++ b/src/pkg/artifact/model_test.go @@ -37,6 +37,7 @@ func (m *modelTestSuite) TestArtifactFrom() { Type: "IMAGE", MediaType: "application/vnd.oci.image.config.v1+json", ManifestMediaType: "application/vnd.oci.image.manifest.v1+json", + ArtifactType: "application/vnd.example+type", ProjectID: 1, RepositoryID: 1, Digest: "sha256:418fb88ec412e340cdbef913b8ca1bbe8f9e8dc705f9617414c1f2c8db980180", @@ -52,6 +53,7 @@ func (m *modelTestSuite) TestArtifactFrom() { assert.Equal(t, dbArt.Type, art.Type) assert.Equal(t, dbArt.MediaType, art.MediaType) assert.Equal(t, dbArt.ManifestMediaType, art.ManifestMediaType) + assert.Equal(t, dbArt.ArtifactType, art.ArtifactType) assert.Equal(t, dbArt.ProjectID, art.ProjectID) assert.Equal(t, dbArt.RepositoryID, art.RepositoryID) assert.Equal(t, dbArt.Digest, art.Digest) @@ -71,6 +73,7 @@ func (m *modelTestSuite) TestArtifactTo() { RepositoryID: 1, MediaType: "application/vnd.oci.image.config.v1+json", ManifestMediaType: "application/vnd.oci.image.manifest.v1+json", + ArtifactType: "application/vnd.example+type", Digest: "sha256:418fb88ec412e340cdbef913b8ca1bbe8f9e8dc705f9617414c1f2c8db980180", Size: 1024, PushTime: time.Now(), @@ -87,6 +90,7 @@ func (m *modelTestSuite) TestArtifactTo() { assert.Equal(t, art.Type, dbArt.Type) assert.Equal(t, art.MediaType, dbArt.MediaType) assert.Equal(t, art.ManifestMediaType, dbArt.ManifestMediaType) + assert.Equal(t, art.ArtifactType, dbArt.ArtifactType) assert.Equal(t, art.ProjectID, dbArt.ProjectID) assert.Equal(t, art.RepositoryID, dbArt.RepositoryID) assert.Equal(t, art.Digest, dbArt.Digest) diff --git a/src/pkg/blob/dao/dao_test.go b/src/pkg/blob/dao/dao_test.go index 00c63e94c..69771a89f 100644 --- a/src/pkg/blob/dao/dao_test.go +++ b/src/pkg/blob/dao/dao_test.go @@ -269,7 +269,7 @@ func (suite *DaoTestSuite) TestFindBlobsShouldUnassociatedWithProject() { artifact1 := suite.DigestString() artifact2 := suite.DigestString() - sql := `INSERT INTO artifact ("type", media_type, manifest_media_type, digest, project_id, repository_id, repository_name) VALUES ('image', 'media_type', 'manifest_media_type', ?, ?, ?, 'library/hello-world')` + sql := `INSERT INTO artifact ("type", media_type, manifest_media_type, digest, project_id, repository_id, repository_name, artifact_type) VALUES ('image', 'media_type', 'manifest_media_type', ?, ?, ?, 'library/hello-world', 'artifact_type')` suite.ExecSQL(sql, artifact1, projectID, 10) suite.ExecSQL(sql, artifact2, projectID, 10) diff --git a/src/pkg/blob/manager_test.go b/src/pkg/blob/manager_test.go index c688bb34e..8ed541970 100644 --- a/src/pkg/blob/manager_test.go +++ b/src/pkg/blob/manager_test.go @@ -130,7 +130,7 @@ func (suite *ManagerTestSuite) TestCleanupAssociationsForProject() { artifact1 := suite.DigestString() artifact2 := suite.DigestString() - sql := `INSERT INTO artifact ("type", media_type, manifest_media_type, digest, project_id, repository_id, repository_name) VALUES ('image', 'media_type', 'manifest_media_type', ?, ?, ?, 'library/hello-world')` + sql := `INSERT INTO artifact ("type", media_type, manifest_media_type, digest, project_id, repository_id, repository_name, artifact_type) VALUES ('image', 'media_type', 'manifest_media_type', ?, ?, ?, 'library/hello-world', 'artifact_type')` suite.ExecSQL(sql, artifact1, projectID, 10) suite.ExecSQL(sql, artifact2, projectID, 10) @@ -200,7 +200,7 @@ func (suite *ManagerTestSuite) TestFindBlobsShouldUnassociatedWithProject() { artifact1 := suite.DigestString() artifact2 := suite.DigestString() - sql := `INSERT INTO artifact ("type", media_type, manifest_media_type, digest, project_id, repository_id, repository_name) VALUES ('image', 'media_type', 'manifest_media_type', ?, ?, ?, 'library/hello-world')` + sql := `INSERT INTO artifact ("type", media_type, manifest_media_type, digest, project_id, repository_id, repository_name, artifact_type) VALUES ('image', 'media_type', 'manifest_media_type', ?, ?, ?, 'library/hello-world', 'artifact_type')` suite.ExecSQL(sql, artifact1, projectID, 11) suite.ExecSQL(sql, artifact2, projectID, 11) diff --git a/src/pkg/securityhub/dao/security_test.go b/src/pkg/securityhub/dao/security_test.go index f6033b75f..ae6025b30 100644 --- a/src/pkg/securityhub/dao/security_test.go +++ b/src/pkg/securityhub/dao/security_test.go @@ -49,12 +49,12 @@ func (suite *SecurityDaoTestSuite) SetupTest() { `delete from artifact_accessory`, `delete from artifact`, `insert into scan_report(uuid, digest, registration_uuid, mime_type, critical_cnt, high_cnt, medium_cnt, low_cnt, unknown_cnt, fixable_cnt) values('uuid', 'digest1001', 'ruuid', 'application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0', 50, 50, 50, 0, 0, 20)`, - `insert into artifact (id, project_id, repository_name, digest, type, pull_time, push_time, repository_id, media_type, manifest_media_type, size, extra_attrs, annotations, icon) -values (1001, 1, 'library/hello-world', 'digest1001', 'IMAGE', '2023-06-02 09:16:47.838778', '2023-06-02 01:45:55.050785', 1742, 'application/vnd.docker.container.image.v1+json', 'application/vnd.docker.distribution.manifest.v2+json', 4452, '{"architecture":"amd64","author":"","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"]},"created":"2023-05-04T17:37:03.872958712Z","os":"linux"}', null, '');`, - `insert into artifact (id, project_id, repository_name, digest, type, pull_time, push_time, repository_id, media_type, manifest_media_type, size, extra_attrs, annotations, icon) -values (1002, 1, 'library/hello-world', 'digest1002', 'IMAGE', '2023-06-02 09:16:47.838778', '2023-06-02 01:45:55.050785', 1742, 'application/vnd.docker.container.image.v1+json', 'application/vnd.oci.image.config.v1+json', 4452, '{"architecture":"amd64","author":"","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"]},"created":"2023-05-04T17:37:03.872958712Z","os":"linux"}', null, '');`, - `insert into artifact (id, project_id, repository_name, digest, type, pull_time, push_time, repository_id, media_type, manifest_media_type, size, extra_attrs, annotations, icon) -values (1003, 1, 'library/hello-world', 'digest1003', 'IMAGE', '2023-06-02 09:16:47.838778', '2023-06-02 01:45:55.050785', 1742, 'application/vnd.docker.container.image.v1+json', 'application/vnd.oci.image.config.v1+json', 4452, '{"architecture":"amd64","author":"","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"]},"created":"2023-05-04T17:37:03.872958712Z","os":"linux"}', null, '');`, + `insert into artifact (id, project_id, repository_name, digest, type, pull_time, push_time, repository_id, media_type, manifest_media_type, size, extra_attrs, annotations, icon, artifact_type) +values (1001, 1, 'library/hello-world', 'digest1001', 'IMAGE', '2023-06-02 09:16:47.838778', '2023-06-02 01:45:55.050785', 1742, 'application/vnd.docker.container.image.v1+json', 'application/vnd.docker.distribution.manifest.v2+json', 4452, '{"architecture":"amd64","author":"","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"]},"created":"2023-05-04T17:37:03.872958712Z","os":"linux"}', null, '', 'application/vnd.docker.container.image.v1+json');`, + `insert into artifact (id, project_id, repository_name, digest, type, pull_time, push_time, repository_id, media_type, manifest_media_type, size, extra_attrs, annotations, icon, artifact_type) +values (1002, 1, 'library/hello-world', 'digest1002', 'IMAGE', '2023-06-02 09:16:47.838778', '2023-06-02 01:45:55.050785', 1742, 'application/vnd.docker.container.image.v1+json', 'application/vnd.oci.image.config.v1+json', 4452, '{"architecture":"amd64","author":"","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"]},"created":"2023-05-04T17:37:03.872958712Z","os":"linux"}', null, '', 'application/vnd.docker.container.image.v1+json');`, + `insert into artifact (id, project_id, repository_name, digest, type, pull_time, push_time, repository_id, media_type, manifest_media_type, size, extra_attrs, annotations, icon, artifact_type) +values (1003, 1, 'library/hello-world', 'digest1003', 'IMAGE', '2023-06-02 09:16:47.838778', '2023-06-02 01:45:55.050785', 1742, 'application/vnd.docker.container.image.v1+json', 'application/vnd.oci.image.config.v1+json', 4452, '{"architecture":"amd64","author":"","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"]},"created":"2023-05-04T17:37:03.872958712Z","os":"linux"}', null, '', 'application/vnd.docker.container.image.v1+json');`, `insert into tag (id, repository_id, artifact_id, name, push_time, pull_time) values (1001, 1742, 1001, 'latest', '2023-06-02 01:45:55.050785', '2023-06-02 09:16:47.838778')`, `INSERT INTO artifact_accessory (id, artifact_id, subject_artifact_id, type, size, digest, creation_time, subject_artifact_digest, subject_artifact_repo) VALUES (1001, 1002, 1, 'signature.cosign', 2109, 'sha256:08c64c0de2667abcf3974b4b75b82903f294680b81584318adc4826d0dcb7a9c', '2023-08-03 04:54:32.102928', 'sha256:a97a153152fcd6410bdf4fb64f5622ecf97a753f07dcc89dab14509d059736cf', 'library/nuxeo')`, `INSERT INTO artifact_reference (id, parent_id, child_id, child_digest, platform, urls, annotations) VALUES (1001, 1001, 1003, 'sha256:d2b2f2980e9ccc570e5726b56b54580f23a018b7b7314c9eaff7e5e479c78657', '{"architecture":"amd64","os":"linux"}', '', null)`, diff --git a/src/server/middleware/subject/subject.go b/src/server/middleware/subject/subject.go index 0719b5c34..c4b86863e 100644 --- a/src/server/middleware/subject/subject.go +++ b/src/server/middleware/subject/subject.go @@ -106,6 +106,15 @@ func Middleware() func(http.Handler) http.Handler { return err } + /* + when an images is pushed, it could be + 1. single image (do nothing) + 2. an accesory image + 3. a subject image + 4. both as an accessory and a subject image + and a subject image or accessory image could be pushed in either order + */ + if mf.Subject != nil { subjectArt, err := artifact.Ctl.GetByReference(ctx, info.Repository, mf.Subject.Digest.String(), nil) if err != nil { @@ -113,7 +122,7 @@ func Middleware() func(http.Handler) http.Handler { logger.Errorf("failed to get subject artifact: %s, error: %v", mf.Subject.Digest, err) return err } - log.Debug("the subject of the signature doesn't exist.") + log.Debug("the subject artifact doesn't exist.") } art, err := artifact.Ctl.GetByReference(ctx, info.Repository, info.Reference, nil) if err != nil { @@ -128,7 +137,12 @@ func Middleware() func(http.Handler) http.Handler { Digest: art.Digest, } accData.Type = model.TypeSubject - switch mf.Config.MediaType { + // since oci-spec 1.1, image type may from artifactType if presents, otherwise would be Config.MediaType + fromType := mf.Config.MediaType + if mf.ArtifactType != "" { + fromType = mf.ArtifactType + } + switch fromType { case ocispec.MediaTypeImageConfig, schema2.MediaTypeImageConfig: if isNydusImage(mf) { accData.Type = model.TypeNydusAccelerator @@ -152,18 +166,18 @@ func Middleware() func(http.Handler) http.Handler { // when subject artifact is pushed after accessory artifact, current subject artifact do not exist. // so we use reference manifest subject digest instead of subjectArt.Digest w.Header().Set("OCI-Subject", mf.Subject.Digest.String()) - } else { + } + + // check if images is a Subject artifact + digest := digest.FromBytes(body) + accs, err := accessory.Mgr.List(ctx, q.New(q.KeyWords{"SubjectArtifactDigest": digest, "SubjectArtifactRepo": info.Repository})) + if err != nil { + logger.Errorf("failed to list accessory artifact: %s, error: %v", digest, err) + return err + } + if len(accs) > 0 { // In certain cases, the OCI client may push the subject artifact and accessory in either order. // Therefore, it is necessary to handle situations where the client pushes the accessory ahead of the subject artifact. - digest := digest.FromBytes(body) - accs, err := accessory.Mgr.List(ctx, q.New(q.KeyWords{"SubjectArtifactDigest": digest, "SubjectArtifactRepo": info.Repository})) - if err != nil { - logger.Errorf("failed to list accessory artifact: %s, error: %v", digest, err) - return err - } - if len(accs) <= 0 { - return nil - } art, err := artifact.Ctl.GetByReference(ctx, info.Repository, digest.String(), nil) if err != nil { logger.Errorf("failed to list artifact: %s, error: %v", digest, err) diff --git a/src/server/registry/referrers.go b/src/server/registry/referrers.go index 799d04acd..ee715faba 100644 --- a/src/server/registry/referrers.go +++ b/src/server/registry/referrers.go @@ -89,11 +89,11 @@ func (r *referrersHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { Size: accArt.Size, Digest: digest.Digest(accArt.Digest), Annotations: accArt.Annotations, - ArtifactType: accArt.MediaType, + ArtifactType: accArt.ArtifactType, } - // filter by the artifactType since the artifactType is actually the config media type of the artifact. + // filter use accArt.ArtifactType as artifactType if at != "" { - if accArt.MediaType == at { + if accArt.ArtifactType == at { mfs = append(mfs, mf) } } else { diff --git a/src/server/registry/referrers_test.go b/src/server/registry/referrers_test.go index 27f1deec3..f8d8abc22 100644 --- a/src/server/registry/referrers_test.go +++ b/src/server/registry/referrers_test.go @@ -37,7 +37,8 @@ func TestReferrersHandlerOK(t *testing.T) { Return(&artifact.Artifact{ Digest: digestVal, ManifestMediaType: "application/vnd.oci.image.manifest.v1+json", - MediaType: "application/vnd.example.main", + MediaType: "application/vnd.example.sbom", + ArtifactType: "application/vnd.example+type", Size: 1000, Annotations: map[string]string{ "name": "test-image", @@ -72,8 +73,8 @@ func TestReferrersHandlerOK(t *testing.T) { } index := &ocispec.Index{} json.Unmarshal([]byte(rec.Body.String()), index) - if index.Manifests[0].ArtifactType != "application/vnd.example.main" { - t.Errorf("Expected response body %s, but got %s", "application/vnd.example.main", rec.Body.String()) + if index.Manifests[0].ArtifactType != "application/vnd.example+type" { + t.Errorf("Expected response body %s, but got %s", "application/vnd.example+type", rec.Body.String()) } } From f7a33920206b7271fa41ec0c53ae95857b658d56 Mon Sep 17 00:00:00 2001 From: MinerYang Date: Wed, 13 Mar 2024 14:46:11 +0800 Subject: [PATCH 040/145] Update deletion for index type of accessory (#20073) update delete for index accessory Signed-off-by: yminer revert error code update lint and comments --- src/controller/artifact/controller.go | 18 ++++++++++++++++-- src/server/registry/manifest.go | 2 ++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/controller/artifact/controller.go b/src/controller/artifact/controller.go index 0e710c064..cc100211f 100644 --- a/src/controller/artifact/controller.go +++ b/src/controller/artifact/controller.go @@ -356,7 +356,16 @@ func (c *controller) deleteDeeply(ctx context.Context, id int64, isRoot, isAcces for _, acc := range art.Accessories { // only hard ref accessory should be removed if acc.IsHard() { - if err = c.deleteDeeply(ctx, acc.GetData().ArtifactID, true, true); err != nil { + // if this acc artifact has parent(is child), set isRoot to false + parents, err := c.artMgr.ListReferences(ctx, &q.Query{ + Keywords: map[string]interface{}{ + "ChildID": acc.GetData().ArtifactID, + }, + }) + if err != nil { + return err + } + if err = c.deleteDeeply(ctx, acc.GetData().ArtifactID, len(parents) == 0, true); err != nil { return err } } @@ -369,7 +378,12 @@ func (c *controller) deleteDeeply(ctx context.Context, id int64, isRoot, isAcces !errors.IsErr(err, errors.NotFoundCode) { return err } - if err = c.deleteDeeply(ctx, reference.ChildID, false, false); err != nil { + // if the child artifact is an accessory, set isAccessory to true + accs, err := c.accessoryMgr.List(ctx, q.New(q.KeyWords{"ArtifactID": reference.ChildID})) + if err != nil { + return err + } + if err = c.deleteDeeply(ctx, reference.ChildID, false, len(accs) > 0); err != nil { return err } } diff --git a/src/server/registry/manifest.go b/src/server/registry/manifest.go index 4693c3a7e..6cf02486a 100644 --- a/src/server/registry/manifest.go +++ b/src/server/registry/manifest.go @@ -145,6 +145,8 @@ func deleteManifest(w http.ResponseWriter, req *http.Request) { // add parse digest here is to return ErrDigestInvalidFormat before GetByReference throws an NOT_FOUND(404) // Do not add the logic into GetByReference as it's a shared method for PUT/GET/DELETE/Internal call, // and NOT_FOUND satisfy PUT/GET/Internal call. + // According to https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#deleting-tags + // If tag deletion is disabled, the registry MUST respond with either a 400 Bad Request or a 405 Method Not Allowed if _, err := digest.Parse(reference); err != nil { lib_http.SendError(w, errors.Wrapf(err, "unsupported digest %s", reference).WithCode(errors.UNSUPPORTED)) return From 69fc957d7e8745474169f9a2df78d869be8d4650 Mon Sep 17 00:00:00 2001 From: "okestro-yj.yoo" <153485739+yj-yoo@users.noreply.github.com> Date: Thu, 14 Mar 2024 12:52:45 +0900 Subject: [PATCH 041/145] [new-feature]Add Korean Translation (#19883) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Korean Translation Signed-off-by: Youngjun * Add Korean Translation Signed-off-by: Youngjun * Update src/portal/src/i18n/lang/ko-kr-lang.json Co-authored-by: Brian Hong Signed-off-by: okestro-yj.yoo <153485739+yj-yoo@users.noreply.github.com> * Update src/portal/src/i18n/lang/ko-kr-lang.json Co-authored-by: Brian Hong Signed-off-by: okestro-yj.yoo <153485739+yj-yoo@users.noreply.github.com> * Update src/portal/src/i18n/lang/ko-kr-lang.json Co-authored-by: Brian Hong Signed-off-by: okestro-yj.yoo <153485739+yj-yoo@users.noreply.github.com> * Update src/portal/src/i18n/lang/ko-kr-lang.json Co-authored-by: Brian Hong Signed-off-by: okestro-yj.yoo <153485739+yj-yoo@users.noreply.github.com> * Update src/portal/src/i18n/lang/ko-kr-lang.json Co-authored-by: Brian Hong Signed-off-by: okestro-yj.yoo <153485739+yj-yoo@users.noreply.github.com> * Update src/portal/src/i18n/lang/ko-kr-lang.json Co-authored-by: Brian Hong Signed-off-by: okestro-yj.yoo <153485739+yj-yoo@users.noreply.github.com> * Update src/portal/src/i18n/lang/ko-kr-lang.json Co-authored-by: Brian Hong Signed-off-by: okestro-yj.yoo <153485739+yj-yoo@users.noreply.github.com> * Update src/portal/src/i18n/lang/ko-kr-lang.json Co-authored-by: Brian Hong Signed-off-by: okestro-yj.yoo <153485739+yj-yoo@users.noreply.github.com> * Update src/portal/src/i18n/lang/ko-kr-lang.json Co-authored-by: Brian Hong Signed-off-by: okestro-yj.yoo <153485739+yj-yoo@users.noreply.github.com> * Update src/portal/src/i18n/lang/ko-kr-lang.json Co-authored-by: Brian Hong Signed-off-by: okestro-yj.yoo <153485739+yj-yoo@users.noreply.github.com> * Change '푸쉬' to '푸시' Signed-off-by: Youngjun * Update src/portal/src/i18n/lang/ko-kr-lang.json Co-authored-by: Brian Hong Signed-off-by: okestro-yj.yoo <153485739+yj-yoo@users.noreply.github.com> * Update src/portal/src/i18n/lang/ko-kr-lang.json Co-authored-by: Sion Kang Signed-off-by: okestro-yj.yoo <153485739+yj-yoo@users.noreply.github.com> * Update src/portal/src/i18n/lang/ko-kr-lang.json Co-authored-by: Sion Kang Signed-off-by: okestro-yj.yoo <153485739+yj-yoo@users.noreply.github.com> * Update src/portal/src/i18n/lang/ko-kr-lang.json Co-authored-by: Sion Kang Signed-off-by: okestro-yj.yoo <153485739+yj-yoo@users.noreply.github.com> * Update src/portal/src/i18n/lang/ko-kr-lang.json Co-authored-by: Sion Kang Signed-off-by: okestro-yj.yoo <153485739+yj-yoo@users.noreply.github.com> * Update src/portal/src/i18n/lang/ko-kr-lang.json Co-authored-by: Sion Kang Signed-off-by: okestro-yj.yoo <153485739+yj-yoo@users.noreply.github.com> * Update src/portal/src/i18n/lang/ko-kr-lang.json Co-authored-by: Sion Kang Signed-off-by: okestro-yj.yoo <153485739+yj-yoo@users.noreply.github.com> * unify technical vocabulary consistently. Signed-off-by: Youngjun --------- Signed-off-by: Youngjun Signed-off-by: okestro-yj.yoo <153485739+yj-yoo@users.noreply.github.com> Co-authored-by: Brian Hong Co-authored-by: Sion Kang Co-authored-by: MinerYang --- src/portal/src/i18n/lang/ko-kr-lang.json | 1943 ++++++++++++++++++++++ 1 file changed, 1943 insertions(+) create mode 100644 src/portal/src/i18n/lang/ko-kr-lang.json diff --git a/src/portal/src/i18n/lang/ko-kr-lang.json b/src/portal/src/i18n/lang/ko-kr-lang.json new file mode 100644 index 000000000..20de6aa90 --- /dev/null +++ b/src/portal/src/i18n/lang/ko-kr-lang.json @@ -0,0 +1,1943 @@ +{ + "APP_TITLE": { + "VMW_HARBOR": "Harbor", + "HARBOR": "Harbor", + "VIC": "vSphere Integrated Containers", + "MGMT": "관리", + "REG": "레지스트리", + "HARBOR_SWAGGER": "Harbor Swagger", + "THEME_DARK_TEXT": "다크 모드", + "THEME_LIGHT_TEXT": "라이트 모드" + }, + "SIGN_IN": { + "REMEMBER": "사용자 정보 기억하기", + "INVALID_MSG": "유효하지 않은 사용자 이름 또는 비밀번호입니다.", + "FORGOT_PWD": "비밀번호 찾기", + "HEADER_LINK": "로그인", + "CORE_SERVICE_NOT_AVAILABLE": "핵심 서비스를 이용할 수 없습니다.", + "OR": "OR", + "VIA_LOCAL_DB": "로컬 DB를 통해 로그인" + }, + "SIGN_UP": { + "TITLE": "등록하기" + }, + "BUTTON": { + "STOP": "중지", + "CANCEL": "취소", + "OK": "확인", + "DELETE": "삭제", + "LOG_IN": "로그인", + "LOG_IN_OIDC": "OIDC 공급자를 통해 로그인", + "LOG_IN_OIDC_WITH_PROVIDER_NAME": "{{providerName}}을 통해 로그인", + "SIGN_UP_LINK": "계정을 등록합니다.", + "SIGN_UP": "등록", + "CONFIRM": "확인", + "SEND": "전송", + "SAVE": "저장", + "TEST_MAIL": "메일 서버 테스트", + "CLOSE": "닫기", + "TEST_LDAP": "LDAP 서버 테스트", + "TEST_OIDC": "OIDC 서버 테스트", + "MORE_INFO": "추가 정보...", + "YES": "예", + "NO": "아니요", + "NEGATIVE": "아니요", + "COPY": "복사", + "EDIT": "편집", + "SWITCH": "전환", + "REPLICATE": "복제", + "ACTIONS": "동작", + "BROWSE": "검색", + "UPLOAD": "업로드", + "NO_FILE": "파일이 선택되지 않았습니다.", + "ADD": "추가", + "RUN": "실행", + "CONTINUE": "계속", + "ENABLE": "가능", + "DISABLE": "비활성화" + }, + "BATCH": { + "DELETED_SUCCESS": "성공적으로 삭제됐습니다.", + "DELETED_FAILURE": "삭제 실패 또는 부분 실패", + "SWITCH_SUCCESS": "성공적으로 전환했습니다.", + "SWITCH_FAILURE": "전환에 실패했습니다.", + "REPLICATE_SUCCESS": "성공적으로 시작됐습니다.", + "REPLICATE_FAILURE": "시작에 실패했습니다.", + "STOP_SUCCESS": "성공적으로 중지됐습니다.", + "STOP_FAILURE": "작업 중지에 실패했습니다.", + "TIME_OUT": "게이트웨이 타임아웃" + }, + "TOOLTIP": { + "NAME_FILTER": "리소스 이름을 필터링합니다. 모두 일치시키려면 비워두거나 '**'을 입력하세요. 'library/**'는 'library' 아래의 리소스만 일치합니다. 더 많은 패턴에 대해서는 사용자 가이드를 참조하세요.", + "TAG_FILTER": "리소스의 태그/버전 부분을 필터링합니다. 모두 일치시키려면 비워 두거나 '**'를 사용하세요. '1.0*'은 '1.0'으로 시작하는 태그에만 일치합니다. 더 많은 패턴에 대해서는 사용자 가이드를 참조하세요.", + "LABEL_FILTER": "라벨에 따라 리소스를 필터링합니다.", + "RESOURCE_FILTER": "리소스 유형을 필터링합니다.", + "PUSH_BASED": "로컬 'Harbor'의 리소스를 원격 레지스트리로 푸시합니다", + "PULL_BASED": "원격 레지스트리의 리소스를 로컬 'Harbor'로 가져옵니다.", + "DESTINATION_NAMESPACE": "대상 네임스페이스를 지정합니다. 비어 있으면 리소스는 소스와 동일한 네임스페이스에 배치됩니다.", + "OVERRIDE": "동일한 이름의 리소스가 있는 경우 대상의 리소스를 재정의할지 여부를 지정합니다.", + "EMAIL": "이메일은 name@example.com과 같은 유효한 이메일 주소여야 합니다.", + "USER_NAME": "특수 문자를 포함할 수 없으며 최대 길이는 255자입니다.", + "FULL_NAME": "최대 길이는 20자입니다.", + "COMMENT": "코멘트 길이는 30자 미만입니다.", + "CURRENT_PWD": "현재 비밀번호가 필요합니다.", + "PASSWORD": "비밀번호는 8~128자 길이로 대문자 1개, 소문자 1개, 숫자 1개 이상이 포함되어야 합니다.", + "CONFIRM_PWD": "비밀번호가 일치하지 않습니다.", + "SIGN_IN_USERNAME": "사용자 이름을 입력하세요.", + "SIGN_IN_PWD": "비밀번호를 입력하세요", + "SIGN_UP_MAIL": "이메일은 비밀번호를 재설정하는 용도로만 사용됩니다.", + "SIGN_UP_REAL_NAME": "성과 이름", + "ITEM_REQUIRED": "필드를 입력하세요.", + "SCOPE_REQUIRED": "필드는 필수 항목이며 범위 형식이어야 합니다.", + "NUMBER_REQUIRED": "필드는 필수 항목이며 숫자 형식이어야 합니다.", + "PORT_REQUIRED": "필드는 필수 항목이며 유효한 포트 번호여야 합니다.", + "CRON_REQUIRED": "필드는 필수이며 cron 형식이어야 합니다.", + "EMAIL_EXISTING": "이메일 주소가 이미 존재합니다.", + "USER_EXISTING": "이미 사용중인 사용자 이름입니다.", + "RULE_USER_EXISTING": "이미 사용중인 이름입니다", + "EMPTY": "이름을 입력하세요.", + "NONEMPTY": "공란은 허용하지 않습니다", + "REPO_TOOLTIP": "이 모드에서는 사용자가 이미지에 어떤 작업도 수행할 수 없습니다.", + "ENDPOINT_FORMAT": "엔드포인트는 반드시 HTTP:// 또는 HTTPS://로 시작해야합니다.", + "OIDC_ENDPOINT_FORMAT": "엔드포인트는 반드시 HTTPS://로 시작해야합니다.", + "OIDC_NAME": "OIDC 공급자의 이름입니다.", + "OIDC_ENDPOINT": "OIDC 호환 서버의 URL입니다.", + "OIDC_SCOPE": "인증 시 OIDC 서버로 전송되는 범위입니다. “openid”와 “offline_access”를 포함해야 합니다. 'Google'을 사용하는 경우 이 필드에서 'offline_access'를 삭제하세요.", + "OIDC_VERIFYCERT": "OIDC 서버가 자체 서명된 인증서를 통해 호스팅되는 경우 이 상자를 선택 취소하세요.", + "OIDC_AUTOONBOARD": "온보딩 화면을 건너뛰면 사용자가 사용자 이름을 변경할 수 없습니다. 사용자 이름은 ID 토큰에서 제공됩니다.", + "OIDC_USER_CLAIM": "사용자 이름을 검색하는 ID 토큰의 클레임 이름입니다. 지정하지 않으면 기본값은 'name'입니다.", + "NEW_SECRET": "시크릿은 대문자 1개, 소문자 1개, 숫자 1개 이상이 포함된 8자 이상이여야합니다" + }, + "PLACEHOLDER": { + "CURRENT_PWD": "현재 비밀번호를 입력하세요", + "NEW_PWD": "새 비밀번호를 입력하세요", + "CONFIRM_PWD": "새 비밀번호를 확인합니다", + "USER_NAME": "사용자 이름을 입력하세요", + "MAIL": "이메일 주소를 입력하세요", + "FULL_NAME": "성과 이름을 포함한 전체 이름을 입력하세요", + "SIGN_IN_NAME": "사용자 이름", + "SIGN_IN_PWD": "비밀번호" + }, + "PROFILE": { + "TITLE": "사용자 프로필", + "USER_NAME": "사용자 이름", + "EMAIL": "이메일", + "FULL_NAME": "성과 이름", + "COMMENT": "코맨트", + "PASSWORD": "비밀번호", + "SAVE_SUCCESS": "사용자 프로필이 성공적으로 저장됐습니다.", + "ADMIN_RENAME_BUTTON": "사용자 이름 변경", + "ADMIN_RENAME_TIP": "사용자 이름을 \"admin@harbor.local\"로 변경하려면 버튼을 선택하세요. 이 작업은 취소할 수 없습니다.", + "RENAME_SUCCESS": "이름이 성공적으로 변경됐습니다!", + "RENAME_CONFIRM_INFO": "경고, 이름을 admin@harbor.local로 변경한 후에는 취소할 수 없습니다.", + "CLI_PASSWORD": "CLI 시크릿", + "CLI_PASSWORD_TIP": "CLI 시크릿 도커 또는 헬름 클라이언트의 비밀번호로 사용될 수 있습니다. OIDC 인증 모드가 활성화되면 로봇 계정을 사용하는 것이 좋습니다. CLI 비밀번호는 ID 토큰의 유효성에 따라 달라지며 사용자가 정기적으로 UI에 로그인하여 토큰을 새로 고쳐야 하기 때문입니다.", + "COPY_SUCCESS": "복사 성공", + "COPY_ERROR": "복사 실패", + "ADMIN_CLI_SECRET_BUTTON": "시크릿 생성", + "ADMIN_CLI_SECRET_RESET_BUTTON": "시크릿 업로드", + "NEW_SECRET": "시크릿", + "CONFIRM_SECRET": "시크릿 재입력", + "GENERATE_SUCCESS": "Cli 시크릿이 성공적으로 설정됐습니다", + "GENERATE_ERROR": "Cli 시크릿 설정에 실패했습니다", + "CONFIRM_TITLE_CLI_GENERATE": "시크릿을 다시 생성할 수 있습니까?", + "CONFIRM_BODY_CLI_GENERATE": "Cli 시크릿을 재생성하면 이전 Cli 시크릿이 삭제됩니다" + }, + "CHANGE_PWD": { + "TITLE": "비밀번호 변경", + "CURRENT_PWD": "현재 비밀번호", + "NEW_PWD": "새 비밀번호", + "CONFIRM_PWD": "비밀번호 확인", + "SAVE_SUCCESS": "사용자 비밀번호를 성공적으로 변경했습니다.", + "PASS_TIPS": "최소 1개의 대문자, 1개의 소문자, 1개의 숫자가 포함된 8~128자 길이" + }, + "ACCOUNT_SETTINGS": { + "PROFILE": "사용자 프로필", + "CHANGE_PWD": "비밀번호 변경", + "ABOUT": "About", + "LOGOUT": "로그아웃" + }, + "GLOBAL_SEARCH": { + "PLACEHOLDER": "검색 {{param}}...", + "PLACEHOLDER_VIC": "레지스트리 검색..." + }, + "TOP_NAV": { + "DATETIME_RENDERING_DEFAULT": "디폴트" + }, + "SIDE_NAV": { + "DASHBOARD": "데시보드", + "PROJECTS": "프로젝트", + "SYSTEM_MGMT": { + "NAME": "관리", + "USER": "사용자", + "GROUP": "그룹", + "REGISTRY": "레지스트리", + "REPLICATION": "복제", + "CONFIG": "설정", + "VULNERABILITY": "취약점", + "GARBAGE_COLLECTION": "가비지 컬렉션", + "INTERROGATION_SERVICES": "질의 서비스" + }, + "LOGS": "로그", + "TASKS": "테스크", + "API_EXPLORER": "Api 탐색기", + "HARBOR_API_MANAGEMENT": "Harbor API V2.0", + "HELM_API_MANAGEMENT": "Harbor API", + "DISTRIBUTIONS": { + "NAME": "배포", + "INSTANCES": "인스턴스" + } + }, + "USER": { + "ADD_ACTION": "새 사용자", + "ENABLE_ADMIN_ACTION": "관리자로 설정", + "DISABLE_ADMIN_ACTION": "관리자 취소", + "DEL_ACTION": "삭제", + "FILTER_PLACEHOLDER": "유저 필터", + "COLUMN_NAME": "이름", + "COLUMN_ADMIN": "관리자", + "COLUMN_EMAIL": "이메일", + "COLUMN_REG_NAME": "등록 시간", + "IS_ADMIN": "예", + "IS_NOT_ADMIN": "아니요", + "ADD_USER_TITLE": "새 사용자", + "SAVE_SUCCESS": "새 사용자가 성공적으로 생성됐습니다.", + "DELETION_TITLE": "사용자 삭제 확인", + "DELETION_SUMMARY": "사용자{{param}}를 정말 삭제하시겠습니까?", + "DELETE_SUCCESS": "사용자를 성공적으로 삭제했습니다.", + "ITEMS": "아이템", + "OF": "of", + "RESET_OK": "사용자 비밀번호를 성공적으로 초기화했습니다", + "EXISTING_PASSWORD": "새 비밀번호는 이전 비밀번호와 동일할 수 없습니다", + "UNKNOWN": "알 수 없음", + "UNKNOWN_TIP": "값이 \"알 수 없음\"인 경우 ID 공급자 시스템을 통해 사용자가 관리자 상태인지 확인하세요" + }, + "PROJECT": { + "PROJECTS": "프로젝트", + "NAME": "프로젝트 이름", + "ROLE": "역할", + "PUBLIC_OR_PRIVATE": "액세스 레벨", + "REPO_COUNT": "저장소 수", + "CHART_COUNT": "차트 수", + "CREATION_TIME": "생성 시간", + "ACCESS_LEVEL": "액세스 레벨", + "PUBLIC": "공개", + "PRIVATE": "비공개", + "MAKE": "만들기", + "NEW_POLICY": "새 복제 규칙", + "DELETE": "삭제", + "ALL_PROJECTS": "모든 프로젝트", + "PRIVATE_PROJECTS": "공개 프로젝트", + "PUBLIC_PROJECTS": "비공개 프로젝트", + "PROJECT": "프로젝트", + "NEW_PROJECT": "새 프로젝트", + "NAME_TOOLTIP": "프로젝트 이름은 1~255자(영문 소문자, 숫자, ._- 포함)이어야 하며 문자나 숫자로 시작해야 합니다.", + "NAME_IS_REQUIRED": "프로젝트 이름은 필수항목입니다.", + "NAME_ALREADY_EXISTS": "이미 존재하는 프로젝트 이름입니다.", + "NAME_IS_ILLEGAL": "프로젝트 이름이 잘못되었습니다.", + "UNKNOWN_ERROR": "프로젝트를 생성하는 중에 알 수 없는 오류가 발생했습니다.", + "ITEMS": "아이템", + "DELETION_TITLE": "프로젝트 삭제 확인", + "DELETION_SUMMARY": "프로젝트{{param}}를 정말 삭제하시겠습니까?", + "FILTER_PLACEHOLDER": "프로젝트 필터", + "REPLICATION_RULE": "복제 규칙", + "CREATED_SUCCESS": "프로젝트가 성공적으로 생성됐습니다.", + "DELETED_SUCCESS": "프로젝트가 성공적으로 삭제됐습니다.", + "TOGGLED_SUCCESS": "프로젝트가 성공적으로 전환됐습니다.", + "FAILED_TO_DELETE_PROJECT": "프로젝트에 리포지토리 또는 복제 규칙이 포함되어 있거나 헬름 차트를 삭제할 수 없습니다.", + "INLINE_HELP_PUBLIC": "프로젝트가 공개로 설정되면 누구나 이 프로젝트의 저장소에 대한 읽기 권한을 갖게 되며 사용자는 이 프로젝트에서 이미지를 가져오기 전에 \"docker login\"을 실행할 필요가 없습니다.", + "OF": "of", + "COUNT_QUOTA": "할당량 수", + "STORAGE_QUOTA": "프로젝트 할당량 제한", + "COUNT_QUOTA_TIP": "'1'과 '100,000,000' 사이의 정수를 입력하세요. 무제한인 경우 '-1'을 입력하세요.", + "STORAGE_QUOTA_TIP": "저장 장치 할당량의 상한은 '1024TB'로 제한되는 정수 값만 사용합니다. 할당량을 무제한으로 지정하려면 '-1'을 입력하세요.", + "QUOTA_UNLIMIT_TIP": "프로젝트에서 사용할 수 있는 최대 논리적 공간입니다. 할당량을 무제한으로 설정하려면 '-1'을 입력하세요.", + "TYPE": "종류", + "PROXY_CACHE": "프록시 캐시", + "PROXY_CACHE_TOOLTIP": "이 프로젝트가 특정 대상 레지스트리 인스턴스에 대한 풀스루 캐시 역할을 할 수 있도록 하려면 이 옵션을 활성화합니다. Harbor는 DockerHub, Docker Registry, Harbor, Aws ECR, Azure ACR, Quay, Google GCR, Github GHCR 및 JFrog Artifactory 레지스트리에 대해서만 프록시 역할을 할 수 있습니다.", + "ENDPOINT": "엔드포인트", + "PROXY_CACHE_ENDPOINT": "프록시 캐시 엔드포인트", + "NO_PROJECT": "프로젝트를 찾을 수 없습니다" + }, + "PROJECT_DETAIL": { + "SUMMARY": "요약", + "REPOSITORIES": "저장소", + "REPLICATION": "복제", + "USERS": "맴버", + "LOGS": "로그", + "LABELS": "라벨", + "PROJECTS": "프로젝트", + "CONFIG": "설정", + "HELMCHART": "헬름 차트", + "ROBOT_ACCOUNTS": "로봇 계정", + "WEBHOOKS": "웹훅", + "IMMUTABLE_TAG": "Tag Immutability", + "POLICY": "정책" + }, + "PROJECT_CONFIG": { + "REGISTRY": "프로젝트 레지스트리", + "PUBLIC_TOGGLE": "공개", + "PUBLIC_POLICY": "프로젝트 레지스트리를 공개하면 모든 사람이 모든 저장소에 액세스할 수 있습니다.", + "SECURITY": "배포 보안", + "CONTENT_TRUST_TOGGLE": "콘텐츠 신뢰 활성화", + "CONTENT_TRUST_POLCIY": "확인된 이미지만 배포되도록 허용합니다.", + "PREVENT_VULNERABLE_TOGGLE": "취약한 이미지가 실행되지 않도록 방지합니다.", + "PREVENT_VULNERABLE_1": "Prevent images with vulnerability severity of", + "PREVENT_VULNERABLE_2": "and above from being deployed.", + "SCAN": "취약점 스캔", + "AUTOSCAN_TOGGLE": "푸시 시 이미지 자동 스캔", + "AUTOSCAN_POLICY": "이미지가 프로젝트 레지스트리에 푸시되면 자동으로 이미지를 스캔합니다." + }, + "MEMBER": { + "NEW_USER": "사용자 구성원 추가", + "NEW_MEMBER": "새 구성원", + "MEMBER": "구성원", + "NAME": "이름", + "ROLE": "역할", + "SYS_ADMIN": "시스템 관리자", + "PROJECT_ADMIN": "프로젝트 관리자", + "PROJECT_MAINTAINER": "메인테이너", + "DEVELOPER": "개발자", + "GUEST": "게스트", + "LIMITED_GUEST": "제한된 게스트", + "DELETE": "삭제", + "ITEMS": "아이템", + "ACTIONS": "동작", + "USER": " 사용자", + "USERS": "사용자들", + "EMAIL": "이메일", + "ADD_USER": "사용자 추가", + "NEW_USER_INFO": "역할이 지정된 사용자를 추가하세요.", + "NEW_GROUP": "새 그룹", + "IMPORT_GROUP": "새 그룹 맴버", + "NEW_GROUP_INFO": "기존 사용자 그룹을 추가하거나 LDAP/AD에서 프로젝트 구성원으로 사용자 그룹을 선택합니다.", + "ADD_GROUP_SELECT": "프로젝트 구성원에 기존 사용자 그룹 추가", + "CREATE_GROUP_SELECT": "LDAP에서 프로젝트 구성원으로 그룹 추가", + "LDAP_SEARCH_DN": "LDAP 그룹 DN", + "LDAP_SEARCH_NAME": "이름", + "LDAP_GROUP": "그룹", + "LDAP_GROUPS": "그룹들", + "LDAP_PROPERTY": "속성", + "ACTION": "동작", + "MEMBER_TYPE": "구성원 종류", + "GROUP_TYPE": "그룹", + "USER_TYPE": "사용자", + "USERNAME_IS_REQUIRED": "사용자 이름은 필수항목입니다", + "USERNAME_DOES_NOT_EXISTS": "존재하지 않는 사용자 이름입니다", + "USERNAME_ALREADY_EXISTS": "이미 등록된 사용자 이름입니다", + "UNKNOWN_ERROR": "구성원을 추가하는 중에 알 수 없는 오류가 발생했습니다.", + "FILTER_PLACEHOLDER": "구성원 필터", + "DELETION_TITLE": "프로젝트 구성원 삭제 확인", + "DELETION_SUMMARY": "프로젝트 구성원{{param}}를 정말 삭제하시겠습니까?", + "ADDED_SUCCESS": "구성원를 성공적으로 추가했습니다.", + "DELETED_SUCCESS": "구성원를 성공적으로 삭제했습니다.", + "SWITCHED_SUCCESS": "구성원 역할을 성공적으로 전환했습니다.", + "OF": "of", + "SWITCH_TITLE": "프로젝트 구성원 전환 확인", + "SWITCH_SUMMARY": "프로젝트 구성원 {{param}}을(를) 전환하시겠습니까?", + "SET_ROLE": "역할 설정", + "REMOVE": "제거", + "GROUP_NAME_REQUIRED": "그룹 이름은 필수항목입니다", + "NON_EXISTENT_GROUP": "그룹 이름이 존재하지 않습니다", + "GROUP_ALREADY_ADDED": "이미 프로젝트에 추가된 그룹 이름입니다" + }, + "ROBOT_ACCOUNT": { + "NAME": "이름", + "PERMISSIONS": "권한", + "TOKEN": "시크릿", + "NEW_ROBOT_ACCOUNT": "새 로봇 계정", + "ENABLED_STATE": "활성화된 상태", + "NUMBER_REQUIRED": "필드는 필수이며 0이 아닌 정수여야 합니다.", + "DESCRIPTION": "설명", + "CREATION": "생성 시간", + "EXPIRATION": "만료", + "TOKEN_EXPIRATION": "로봇 토큰 만료(일)", + "ACTION": "동작", + "EDIT": "편집", + "ITEMS": "아이템", + "OF": "of", + "DISABLE_ACCOUNT": "계정 비활성화", + "ENABLE_ACCOUNT": "계정 활성화", + "DELETE": "삭제", + "CREAT_ROBOT_ACCOUNT": "로봇 계정 생성", + "PERMISSIONS_ARTIFACT": "아티팩트", + "PERMISSIONS_HELMCHART": "핼름 차트 (Chart Museum)", + "PUSH": "푸시", + "PULL": "풀(Pull)", + "FILTER_PLACEHOLDER": "로봇 계정 필터", + "ROBOT_NAME": "특수 문자(~#$%)를 포함할 수 없으며 최대 길이는 255자입니다.", + "ACCOUNT_EXISTING": "로봇 계정이 이미 존재합니다.", + "ALERT_TEXT": "이 시크릿을 복사할 수 있는 유일한 기회입니다.다음 기회는 없습니다.", + "CREATED_SUCCESS": "'{{param}}'성공적으로 생성됐습니다.", + "COPY_SUCCESS": "'{{param}}'의 시크릿을 성공적으로 복사했습니다", + "DELETION_TITLE": "로봇 계정 제거 확인", + "DELETION_SUMMARY": "로봇 계정{{param}}을 정말 삭제하시겠습니까?", + "PULL_IS_MUST": "풀(Pull) 권한은 기본적으로 체크되어 있으며 수정할 수 없습니다.", + "EXPORT_TO_FILE": "파일로 내보내기", + "EXPIRES_AT": "~에 만료", + "EXPIRATION_TOOLTIP": "설정하지 않을 경우 시스템 설정에따라 만료 시간이 설정됩니다", + "INVALID_VALUE": "만료 시간 값이 잘못되었습니다", + "NEVER_EXPIRED": "만료되지 않음", + "NAME_PREFIX": "로봇 이름 접두어", + "NAME_PREFIX_REQUIRED": "로봇 이름 접두어는 필수항목입니다", + "UPDATE": "업데이트", + "AUDIT_LOG": "감사 로그", + "PREHEAT_INSTANCE": "인스턴스 예열", + "PROJECT": "프로젝트", + "REPLICATION_POLICY": "복제 정책", + "REPLICATION": "복제", + "REPLICATION_ADAPTER": "복제 어댑터", + "REGISTRY": "레지스트리", + "SCAN_ALL": "모두 스캔", + "SYSTEM_VOLUMES": "시스템 볼륨", + "GARBAGE_COLLECTION": "가비지 컬렉션", + "PURGE_AUDIT": "퍼지 감사", + "JOBSERVICE_MONITOR": "작업 서비스 모니터링", + "TAG_RETENTION": "태그 유지", + "SCANNER": "스캐너", + "LABEL": "라벨", + "EXPORT_CVE": "CVE 내보내기", + "SECURITY_HUB": "보안 허브", + "CATALOG": "카탈로그", + "METADATA": "프로젝트 메타데이터", + "REPOSITORY": "저장소", + "ARTIFACT": "아티팩트", + "SCAN": "스캔", + "TAG": "태그", + "ACCESSORY": "액세서리", + "ARTIFACT_ADDITION": "아티팩트 추가", + "ARTIFACT_LABEL": "아티팩트 라벨", + "PREHEAT_POLICY": "예열 정책", + "IMMUTABLE_TAG": "Immutable Tag", + "LOG": "로그", + "NOTIFICATION_POLICY": "알림 정책", + "QUOTA": "할당량", + "BACK": "뒤로", + "NEXT": "다음", + "FINISH": "마지막", + "BASIC_INFO": "기본 정보", + "SELECT_PERMISSIONS": "권한 선택", + "SELECT_SYSTEM_PERMISSIONS": "시스템 권한 선택", + "SELECT_PROJECT_PERMISSIONS": "프로젝트 권한 선택", + "SYSTEM_PERMISSIONS": "시스템 권한" + }, + "WEBHOOK": { + "EDIT_BUTTON": "편집", + "ENABLED_BUTTON": "활성", + "DISABLED_BUTTON": "비활성", + "TYPE": "웹훅", + "STATUS": "상태", + "CREATED": "생성 됨", + "ENABLED": "활성화 됨", + "DISABLED": "비활성화 됨", + "OF": "of", + "ITEMS": "아이템", + "LAST_TRIGGERED": "마지막 발생", + "EDIT_WEBHOOK": "웹훅 편집", + "ADD_WEBHOOK": "웹훅 추가", + "CREATE_WEBHOOK": "웹훅 시작하기", + "EDIT_WEBHOOK_DESC": "웹훅 알림을 수신하기 위한 엔드포인트 지정", + "CREATE_WEBHOOK_DESC": "웹훅을 시작하려면 웹훅 서버에 액세스하기 위한 엔드포인트와 자격 증명을 제공하세요.", + "VERIFY_REMOTE_CERT_TOOLTIP": "웹훅이 원격 URL의 인증서를 확인해야 하는지 여부를 결정합니다. 원격 URL이 자체 서명된 인증서 또는 신뢰할 수 없는 인증서를 사용하는 경우 이 상자를 선택 취소합니다.", + "ENDPOINT_URL": "엔드포인트 URL", + "URL_IS_REQUIRED": "엔드포인트 URL은 필수입력항목입니다.", + "AUTH_HEADER": "Auth Header", + "VERIFY_REMOTE_CERT": "원격 인증서 확인", + "TEST_ENDPOINT_BUTTON": "엔드포인트 테스트", + "CANCEL_BUTTON": "취소", + "SAVE_BUTTON": "저장", + "TEST_ENDPOINT_SUCCESS": "연결 테스트가 성공적으로 완료되었습니다.", + "TEST_ENDPOINT_FAILURE": "엔드포인트로 Ping 시도가 실패했습니다.", + "ENABLED_WEBHOOK_TITLE": "웹훅 활성화", + "ENABLED_WEBHOOK_SUMMARY": "웹훅{{name}}을 활성화하시겠습니까??", + "DISABLED_WEBHOOK_TITLE": "웹훅 비활성화", + "DISABLED_WEBHOOK_SUMMARY": "웹훅{{name}}을 비활성화 하시겠습니까?", + "DELETE_WEBHOOK_TITLE": "웹훅 삭제", + "DELETE_WEBHOOK_SUMMARY": "웹훅{{names}}을 삭제하시겠습니까?", + "WEBHOOKS": "웹훅", + "NEW_WEBHOOK": "새 웹훅", + "ENABLE": "활성화", + "DISABLE": "비활성화", + "NAME": "이름", + "TARGET": "엔드포인트 URL", + "EVENT_TYPES": "이벤트 종류", + "DESCRIPTION": "설명", + "NO_WEBHOOK": "웹훅 없음", + "LAST_TRIGGER": "마지막 트리거", + "WEBHOOK_NAME": "웹훅 이름", + "NO_TRIGGER": "트리거 없음", + "NAME_REQUIRED": "이름은 필수입니다", + "NOTIFY_TYPE": "알림 유형", + "EVENT_TYPE": "유형 입력", + "EVENT_TYPE_REQUIRED": "하나 이상의 이벤트 유형이 필요합니다.", + "PAYLOAD_FORMAT": "페이로드 형식", + "CLOUD_EVENT": "클라우드이벤트", + "PAYLOAD_DATA": "페이로드 데이터", + "SLACK_RATE_LIMIT": "Slack 속도 제한에 유의하세요." + + }, + "GROUP": { + "GROUP": "그룹", + "GROUPS": "그룹들", + "IMPORT_LDAP_GROUP": "LDAP Group 불러오기", + "IMPORT_HTTP_GROUP": "새 HTTP Group", + "IMPORT_OIDC_GROUP": "새 OIDC Group", + "ADD": "새 그룹", + "EDIT": "편집", + "DELETE": "삭제", + "NAME": "이름", + "TYPE": "종류", + "DN": "DN", + "GROUP_DN": "Ldap Group DN", + "PROPERTY": "속성", + "REG_TIME": "등록 시간", + "ADD_GROUP_SUCCESS": "그룹 추가 성공", + "EDIT_GROUP_SUCCESS": "그룹 편집 성공", + "LDAP_TYPE": "LDAP", + "HTTP_TYPE": "HTTP", + "OIDC_TYPE": "OIDC", + "OF": "of", + "ITEMS": "아이템", + "NEW_MEMBER": "새 구성원", + "NEW_USER_INFO": "지정된 역할을 가진 이 프로젝트의 구성원이 될 그룹을 추가하세요.", + "ROLE": "역할", + "SYS_ADMIN": "시스템 관리자", + "PROJECT_ADMIN": "프로젝트 관리자", + "PROJECT_MAINTAINER": "메인테이너", + "DEVELOPER": "개발자", + "GUEST": "게스트", + "LIMITED_GUEST": "제한된 게스트", + "DELETION_TITLE": "그룹 구성원 삭제 확인", + "DELETION_SUMMARY": "그룹 구성원(들){{param}}을 정말 삭제하시겠습니까?" + }, + "AUDIT_LOG": { + "USERNAME": "사용자 이름", + "REPOSITORY_NAME": "저장소 이름", + "TAGS": "태그", + "OPERATION": "작업", + "OPERATIONS": "작업들", + "TIMESTAMP": "타임스탬프", + "ALL_OPERATIONS": "모든 작업들", + "PULL": "풀(Pull)", + "PUSH": "푸시", + "CREATE": "생성", + "DELETE": "삭제", + "OTHERS": "기타", + "ADVANCED": "Advanced", + "SIMPLE": "Simple", + "ITEMS": "아이템", + "FILTER_PLACEHOLDER": "로그 필터", + "INVALID_DATE": "잘못된 날짜.", + "OF": "of", + "NOT_FOUND": "로그를 찾을 수 없습니다!", + "RESOURCE": "리소스", + "RESOURCE_TYPE": "리소스 종류" + }, + "REPLICATION": { + "PUSH_BASED_ONLY": "푸시 기반 복제에만 해당됩니다", + "YES": "예", + "SECONDS": "초", + "MINUTES": "분", + "HOURS": "시", + "MONTH": "월", + "DAY_MONTH": "해당 월의 일", + "DAY_WEEK": "해당 주의 일", + "CRON_TITLE": "cron '* * * * * *'에 대한 패턴 설명입니다. cron 문자열은 UTC 시간을 기반으로 합니다.", + "FIELD_NAME": "필드 이름", + "MANDATORY": "Mandatory?", + "ALLOWED_VALUES": "허용되는 값", + "ALLOWED_CHARACTERS": "허용되는 특수 문자", + "TOTAL": "총", + "OVERRIDE": "Override", + "ENABLED_RULE": "규칙 활성화", + "OVERRIDE_INFO": "Override", + "OPERATION": "작업", + "CURRENT": "현재", + "FILTER_PLACEHOLDER": "작업 필터", + "STOP_TITLE": "실행 중지 확인", + "BOTH": "both", + "STOP_SUCCESS": "{{param}}를 성공적으로 실행 중지했습니다", + "STOP_SUMMARY": "{{param}} 실행을 중지하시겠습니까?", + "TASK_ID": "작업 아이디", + "RESOURCE_TYPE": "리소스 유형", + "SOURCE": "소스", + "DESTINATION": "목적지", + "POLICY": "정책", + "DURATION": "지속", + "SUCCESS_RATE": "성공률", + "SUCCESS": "성공", + "FAILURE": "실행", + "IN_PROGRESS": "진행 중", + "REPLICATION_RULE": "복제 규칙", + "NEW_REPLICATION_RULE": "새 복제 규칙", + "ENDPOINTS": "엔드포인트", + "FILTER_POLICIES_PLACEHOLDER": "룰 필터", + "FILTER_EXECUTIONS_PLACEHOLDER": "실행 필터", + "DELETION_TITLE": "복제 규칙 삭제 확인", + "DELETION_SUMMARY": "복제 규칙 {{param}}을(를) 삭제하시겠습니까?", + "REPLICATION_TITLE": "규칙 복제 확인", + "REPLICATION_SUMMARY": "{{param}} 규칙을 복제하시겠습니까?", + "DELETION_TITLE_FAILURE": "규칙 삭제를 삭제하지 못했습니다.", + "DELETION_SUMMARY_FAILURE": "대기 중/실행 중/재시도 중 상태입니다", + "REPLICATE_SUMMARY_FAILURE": "대기 중/실행 중 상태입니다", + "FILTER_TARGETS_PLACEHOLDER": "엔드포인트 필터", + "DELETION_TITLE_TARGET": "엔드포인트 삭제 확인", + "DELETION_SUMMARY_TARGET": "엔드포인트{{param}}를 삭제하시겠습니까?", + "ADD_POLICY": "새 복제 규칙", + "EDIT_POLICY": "편집", + "EDIT_POLICY_TITLE": "복제 규칙 편집", + "DELETE_POLICY": "삭제", + "TEST_CONNECTION": "연결 테스트", + "TESTING_CONNECTION": "연결 테스트 중...", + "TEST_CONNECTION_SUCCESS": "연결이 성공적으로 테스트되었습니다.", + "TEST_CONNECTION_FAILURE": "엔트포인트로 Ping을 보내지 못했습니다.", + "ID": "아이디", + "NAME": "이름", + "NAME_IS_REQUIRED": "이름은 필수항목입니다.", + "DESCRIPTION": "설명", + "ENABLE": "활성화", + "DISABLE": "비활성화", + "REPLICATION_MODE": "복제 모드", + "SRC_REGISTRY": "소스 레지스트리", + "DESTINATION_NAMESPACE": "목적지 레지스트리:네임스페이스", + "DESTINATION_NAME_IS_REQUIRED": "엔드포인트 이름은 필수항목입니다.", + "NEW_DESTINATION": "새 엔드포인트", + "DESTINATION_URL": "엔드포인트 URL", + "DESTINATION_URL_IS_REQUIRED": "엔드포인트 URL은 필수항목입니다.", + "DESTINATION_USERNAME": "사용자 이름", + "DESTINATION_PASSWORD": "패스워드", + "ALL_STATUS": "모든 상태", + "ENABLED": "활성화", + "DISABLED": "비활성화", + "LAST_START_TIME": "마지막 시작 시간", + "ACTIVATION": "활성화", + "REPLICATION_EXECUTION": "실행", + "REPLICATION_EXECUTIONS": "실행", + "STOPJOB": "중지", + "ALL": "전체", + "PENDING": "대기 중", + "RUNNING": "실행 중", + "ERROR": "에러", + "RETRYING": "다시 시도 중", + "STOPPED": "중지 됨", + "FINISHED": "완료 됨", + "CANCELED": "취소 됨", + "SIMPLE": "Simple", + "ADVANCED": "Advanced", + "STATUS": "상태", + "REPLICATION_TRIGGER": "트리거", + "CREATION_TIME": "종료 시간", + "UPDATE_TIME": "갱신 시간", + "END_TIME": "종료 시간", + "LOGS": "로그", + "OF": "of", + "ITEMS": "아이템", + "NO_LOGS": "로그 없음", + "TOGGLE_ENABLE_TITLE": "규칙 활성화", + "TOGGLE_DISABLE_TITLE": "규칙 비활성화", + "CREATED_SUCCESS": "복제 규칙을 성공적으로 생성했습니다.", + "UPDATED_SUCCESS": "복제 규칙을 성공적으로 갱신했습니다.", + "DELETED_SUCCESS": "복제 규칙을 성공적으로 삭제했습니다.", + "DELETED_FAILED": "복제 규칙 삭제를 실패했습니다.", + "TOGGLED_SUCCESS": "복제 규칙 상태를 성공적으로 전환했습니다", + "CANNOT_EDIT": "복제 규칙이 활성화된 동안에는 변경할 수 없습니다.", + "INVALID_DATE": "유효하지 않은 날짜입니다.", + "PLACEHOLDER": "복제 규칙을 찾을 수 없습니다!", + "JOB_PLACEHOLDER": "복제 작업을 찾을 수 없습니다!", + "NO_ENDPOINT_INFO": "엔드포인트를 먼저 추가하세요", + "NO_LABEL_INFO": "라벨을 먼저 추가하세요", + "NO_PROJECT_INFO": "이 프로젝트는 존재하지 않습니다", + "SOURCE_RESOURCE_FILTER": "소스 리소스 필터", + "SCHEDULED": "예정", + "MANUAL": "수동", + "EVENT_BASED": "이벤트 기반", + "DAILY": "매일", + "WEEKLY": "매주", + "SETTING": "옵션", + "TRIGGER": "트리거 조건", + "TARGETS": "대상", + "MODE": "모드", + "TRIGGER_MODE": "트리거 모드", + "SOURCE_PROJECT": "소스 프로젝트", + "REPLICATE": "복제", + "DELETE_REMOTE_IMAGES": "로컬에서 삭제되면 원격 리소스 삭제", + "DELETE_ENABLED": "이 정책을 활성화했습니다", + "NEW": "New", + "NAME_TOOLTIP": "복제 규칙 이름은 소문자, 숫자, ._-가 포함된 2자 이상이어야 하며 문자 또는 숫자로 시작해야 합니다.", + "DESTINATION_NAME_TOOLTIP": "대상 이름은 소문자, 숫자, ._-가 포함된 2자 이상이어야 하며 문자 또는 숫자로 시작해야 합니다.", + "ACKNOWLEDGE": "Acknowledge", + "RULE_DISABLED": "필터에 사용된 라벨이 삭제되었기 때문에 이 규칙이 비활성화되었습니다. \n 규칙을 편집하고 필터를 업데이트하여 활성화하십시오.", + "REPLI_MODE": "복제 모드", + "SOURCE_REGISTRY": "소스 레지스트리", + "SOURCE_NAMESPACES": "소스 네임스페이즈", + "DEST_REGISTRY": "목적지 레지스트리", + "DEST_NAMESPACE": "목적지 네임스페이스", + "NAMESPACE_TOOLTIP": "네임스페이스 이름은 소문자, 숫자 및 ._-/가 포함된 2자 이상이어야 하며 문자 또는 숫자로 시작해야 합니다.", + "TAG": "태그", + "LABEL": "라벨", + "RESOURCE": "리소스", + "ENABLE_TITLE": "규칙 활성화", + "ENABLE_SUMMARY": "규칙{{param}}을 활성화하시겠습니까?", + "DISABLE_TITLE": "규칙 비활성화", + "DISABLE_SUMMARY": "규칙{{param}}을 비활성화하시겠습니까?", + "ENABLE_SUCCESS": "규칙을 성공적으로 활성화했습니다", + "ENABLE_FAILED": "규칙 활성화를 실패했습니다", + "DISABLE_SUCCESS": "규칙을 성공적으로 비활성화 했습니다", + "DISABLE_FAILED": "규칙 비활성화를 실패했습니다", + "DES_REPO_FLATTENING": "목적지 저장소 필터링", + "NAMESPACE": "네임스페이스", + "REPO_FLATTENING": "Flattening", + "NO_FLATTING": "No Flatting", + "FLATTEN_LEVEL_1": "Flatten 1 Level", + "FLATTEN_LEVEL_2": "Flatten 2 Levels", + "FLATTEN_LEVEL_3": "Flatten 3 Levels", + "FLATTEN_ALL": "Flatten All Levels", + "FLATTEN_LEVEL_TIP": "이미지를 복사할 때 중첩된 저장소 구조를 줄입니다. 중첩된 저장소 구조가 'a/b/c/d/img'이고 대상 네임스페이스가 'ns'라고 가정하면 각 항목에 해당하는 결과는 다음과 같습니다.", + "FLATTEN_LEVEL_TIP_ALL": "'Flatten All Levels'(Used prior v2.3): 'a/b/c/d/img' -> 'ns/img'", + "FLATTEN_LEVEL_TIP_NO": "'No Flatting': 'a/b/c/d/img' -> 'ns/a/b/c/d/img", + "FLATTEN_LEVEL_TIP_1": "'Flatten 1 Level'(Default): 'a/b/c/d/img' -> 'ns/b/c/d/img'", + "FLATTEN_LEVEL_TIP_2": "'Flatten 2 Levels': 'a/b/c/d/img' -> 'ns/c/d/img'", + "FLATTEN_LEVEL_TIP_3": "'Flatten 3 Levels': 'a/b/c/d/img' -> 'ns/d/img'", + "BANDWIDTH": "대역폭", + "BANDWIDTH_ERROR_TIP": "-1 또는 0보다 큰 정수를 입력하세요.", + "BANDWIDTH_TOOLTIP": "각 실행에 대한 최대 네트워크 대역폭을 설정합니다. 동시 실행 횟수에 주의하시기 바랍니다. 무제한 대역폭을 원하시면 -1을 입력하세요.", + "UNLIMITED": "제한 없음", + "UNREACHABLE_SOURCE_REGISTRY": "소스 레지스트리에 연결하지 못했습니다. 이 규칙을 편집하기 전에 소스 레지스트리를 사용할 수 있는지 확인하세요. {{오류}}", + "CRON_ERROR_TIP": "cron 문자열의 첫 번째 필드는 0이어야 하며 두 번째 필드는 \"*\"일 수 없습니다.", + "COPY_BY_CHUNK": "청크로 복사", + "COPY_BY_CHUNK_TIP": "Blob을 청크로 복사할지 여부를 지정합니다. 청크별로 전송하면 API 요청 수가 늘어날 수 있습니다.", + "TRIGGER_STOP_SUCCESS": "실행 중지가 성공적으로 트리거되었습니다.", + "CRON_STR": "Cron 문자열" + }, + "DESTINATION": { + "NEW_ENDPOINT": "새 엔드포인트", + "PROVIDER": "공급자", + "ENDPOINT": "엔드포인트", + "NAME": "이름", + "NAME_IS_REQUIRED": "엔드포인트 이름은 필수입력항목입니다.", + "URL": "엔드포인트 URL", + "URL_IS_REQUIRED": "엔드포인트 URL 이름은 필수입력항목입니다.", + "AUTHENTICATION": "인증", + "ACCESS_ID": "접근 ID", + "ACCESS_SECRET": "접근 시크릿", + "STATUS": "상태", + "TEST_CONNECTION": "테스트 연결", + "TITLE_EDIT": "엔드포인트 편집", + "TITLE_ADD": "새 레지스트리 엔드포인트", + "EDIT": "편집", + "DELETE": "삭제", + "TESTING_CONNECTION": "테스팅 연결...", + "TEST_CONNECTION_SUCCESS": "연결 테스트가 성공적으로 완료되었습니다.", + "TEST_CONNECTION_FAILURE": "엔드포인트로 Ping이 실패했습니다.", + "CONFLICT_NAME": "엔드포인트이름이 이미 존재합니다.", + "INVALID_NAME": "유효하지 않은 엔트포인트 이름.", + "FAILED_TO_GET_TARGET": "엔드포인트를 가져오는데 실패했습니다.", + "CREATION_TIME": "생성 날짜", + "OF": "of", + "ITEMS": "아이템", + "CREATED_SUCCESS": "엔드포인트를 성공적으로 생성했습니다.", + "UPDATED_SUCCESS": "엔드포인트를 성공적으로 업데이트 했습니다.", + "DELETED_SUCCESS": "엔드포인트를 성공적으로 삭제했습니다.", + "DELETED_FAILED": "엔드포인트 삭제를 실패했습니다.", + "CANNOT_EDIT": "복제 규칙이 활성화된 동안에는 엔드포인트를 변경할 수 없습니다..", + "FAILED_TO_DELETE_TARGET_IN_USED": "사용 중인 엔드포인트를 삭제하지 못했습니다.", + "PLACEHOLDER": "엔드포인트를 찾을 수 없습니다!" + }, + "REPOSITORY": { + "COPY_DIGEST_ID": "Digest 복사", + "DELETE": "삭제", + "NAME": "이름", + "TAGS": "테그", + "PLATFORM": "OS/ARCH", + "ARTIFACT_TOOTIP": "이 OCI 인덱스의 아티팩트 목록을 보려면 클릭하세요.", + "ARTIFACTS_COUNT": "아티팩트", + "PULL_COUNT": "풀(Pull) 수", + "PULL_COMMAND": "풀(Pull) 명령어", + "PULL_TIME": "풀(Pull) 시간", + "PUSH_TIME": "푸시 시간", + "IMMUTABLE": "Immutable", + "MY_REPOSITORY": "나의 저장소", + "PUBLIC_REPOSITORY": "공개 저장소", + "DELETION_TITLE_REPO": "저장소 삭제 확인", + "DELETION_TITLE_REPO_SIGNED": "저장소를 삭제할 수 없습니다", + "DELETION_SUMMARY_REPO_SIGNED": "다음과 같은 서명된 이미지가 존재하므로 '{{repoName}}' 저장소를 삭제할 수 없습니다.\n{{signedImages}} \n저장소를 삭제하기 전에 서명된 이미지를 모두 서명 취소해야 합니다!", + "DELETION_SUMMARY_REPO": "{{repoName}} 저장소를 삭제하시겠습니까?", + "DELETION_TITLE_ARTIFACT": "아티팩트 삭제 확인", + "DELETION_SUMMARY_ARTIFACT": "\n{{param}} 아티팩트를 삭제하시겠습니까? 이 아티팩트를 삭제하면 다이제스트에서 참조하는 모든 태그도 삭제됩니다.", + "DELETION_TITLE_TAG": "태그 삭제 확인", + "DELETION_SUMMARY_TAG": "{{param}} 태그를 삭제하시겠습니까?", + "DELETION_TITLE_TAG_DENIED": "서명된 태그를 삭제할 수 없습니다.", + "DELETION_SUMMARY_TAG_DENIED": "태그를 삭제하려면 먼저 공증인에서 태그를 제거해야 합니다.\nDelete from Notary via this command:\n", + "TAGS_NO_DELETE": "읽기 전용 모드에서는 삭제가 금지됩니다.", + "FILTER_FOR_REPOSITORIES": "저장소 필터", + "TAG": "태그", + "ARTIFACT": "아티팩트", + "ARTIFACTS": "아티팩트들", + "SIZE": "크기", + "VULNERABILITY": "취약점", + "BUILD_HISTORY": "기록 생성", + "SIGNED": "서명됨", + "AUTHOR": "작성자", + "CREATED": "생성 시간", + "DOCKER_VERSION": "도커 버전", + "ARCHITECTURE": "아키텍처", + "OS": "OS", + "SHOW_DETAILS": "자세히 보기", + "REPOSITORIES": "저장소", + "OF": "of", + "ITEMS": "아이템", + "NO_ITEMS": "아이템 없음", + "POP_REPOS": "인기 저장소", + "DELETED_REPO_SUCCESS": "저장소를 성공적으로 삭제했습니다.", + "DELETED_TAG_SUCCESS": "태그를 성공적으로 삭제했습니다.", + "COPY": "복사", + "NOTARY_IS_UNDETERMINED": "이 태그의 서명을 확인할 수 없습니다.", + "PLACEHOLDER": "저장소를 찾을 수 없습니다!", + "INFO": "정보", + "NO_INFO": "이 저장소에 대한 설명이 없습니다. 이 저장소에 추가할 수 있습니다.", + "IMAGE": "이미지", + "LABELS": "라벨", + "ADD_LABEL_TO_IMAGE": "이 이미지에 라벨 추가", + "FILTER_BY_LABEL": "라벨별로 이미지 필터", + "FILTER_ARTIFACT_BY_LABEL": "라벨별로 아티팩트 필터", + "ADD_LABELS": "라벨 추가", + "RETAG": "복사", + "ACTION": "동작", + "DEPLOY": "배포", + "ADDITIONAL_INFO": "추가 정보 추가", + "REPO_NAME": "저장소", + "MARKDOWN": "마크다운 스타일링 지원", + "LAST_MODIFIED": "마지막 수정 시간" + }, + "SUMMARY": { + "QUOTAS": "할당량", + "PROJECT_REPOSITORY": "저장소", + "PROJECT_MEMBER": "구성원", + "PROJECT_QUOTAS": "할당량", + "ARTIFACT_COUNT": "아티팩트 수", + "STORAGE_CONSUMPTION": "저장 장치 소모량", + "ADMIN": "관리자(들)", + "MAINTAINER": "메인테이너(들)", + "DEVELOPER": "개발자(들)", + "GUEST": "게스트(들)", + "LIMITED_GUEST": "제한된 게스트(들)", + "SEE_ALL": "모두 보기" + }, + "ALERT": { + "FORM_CHANGE_CONFIRMATION": "일부 수정사항이 아직 저장되지 않았습니다. 취소하시겠습니까?" + }, + "RESET_PWD": { + "TITLE": "비밀번호 재설정", + "CAPTION": "비밀번호를 재설정하기위해 당신의 이메일을 입력하세요", + "EMAIL": "이메일", + "SUCCESS": "비밀번호 재설정 링크가 포함된 메일이 성공적으로 발송되었습니다. 이 대화 상자를 닫고 사서함을 확인할 수 있습니다.", + "CAPTION2": "새 비밀번호를 입력하세요", + "RESET_OK": "비밀번호를 성공적으로 재설정했습니다. 새 비밀번호로 다시 로그인하기위해 'OK'를 클릭하세요." + }, + "RECENT_LOG": { + "SUB_TITLE": "보기", + "SUB_TITLE_SUFIX": "로그" + }, + "CONFIG": { + "SECURITY": "보안", + "HISTORY": "기록", + "TITLE": "설정", + "AUTH": "인증", + "REPLICATION": "복제", + "LABEL": "라벨", + "REPOSITORY": "저장소", + "REPO_READ_ONLY": "읽기 전용 저장소", + "WEBHOOK_NOTIFICATION_ENABLED": "웹훅 활성화", + "SYSTEM": "시스템 셋팅", + "PROJECT_QUOTAS": "프로젝트 할당량", + "VULNERABILITY": "취약점", + "GC": "가비지 컬랙션", + "CONFIRM_TITLE": "취소 확인", + "CONFIRM_SUMMARY": "일부 변경사항이 저장되지 않았습니다. 삭제하시겠습니까?", + "SAVE_SUCCESS": "구성이 성공적으로 저장되었습니다", + "VERIFY_REMOTE_CERT": "원격 인증서 확인", + "TOKEN_EXPIRATION": "토큰 만료(분)", + "SESSION_TIMEOUT": "세션 타임아웃(분)", + "SESSION_TIMEOUT_INFO": "'Harbor'의 세션 타임아웃을 설정합니다. 기본값은 60분입니다.", + "AUTH_MODE": "인증 모드", + "PRIMARY_AUTH_MODE": "기본 인증 모드", + "PRO_CREATION_RESTRICTION": "프로젝트 생성", + "SELF_REGISTRATION": "셀프 레지스트레이션 허용", + "AUTH_MODE_DB": "데이터베이스", + "AUTH_MODE_LDAP": "LDAP", + "AUTH_MODE_UAA": "UAA", + "AUTH_MODE_HTTP": "Http Auth", + "AUTH_MODE_OIDC": "OIDC", + "SCOPE_BASE": "Base", + "SCOPE_ONE_LEVEL": "OneLevel", + "SCOPE_SUBTREE": "하위항목", + "PRO_CREATION_EVERYONE": "모든 사용자", + "PRO_CREATION_ADMIN": "관리자 전용", + "ROOT_CERT": "레지스트리 루트 인증서", + "ROOT_CERT_LINK": "다운로드", + "REGISTRY_CERTIFICATE": "레지스트리 인증서", + "NO_CHANGE": "변경된 사항이 없으므로 저장을 중단합니다.", + "SKIP_SCANNER_PULL_TIME": "스캔시 이미지 \"마지막 풀(Pull) 시간\" 유지", + "TOOLTIP": { + "SELF_REGISTRATION_ENABLE": "가입을 활성화합니다.", + "SELF_REGISTRATION_DISABLE": "가입을 비활성화합니다.", + "VERIFY_REMOTE_CERT": "이미지 복제가 원격 'Harbor' 레지스트리의 인증서를 확인해야 하는지 여부를 결정합니다. 원격 레지스트리가 자체 서명되거나 신뢰할 수 없는 인증서를 사용하는 경우 이 상자를 선택 취소합니다.", + "AUTH_MODE": "기본적으로 인증 모드는 데이터베이스입니다. 즉, 자격 증명은 로컬 데이터베이스에 저장됩니다. LDAP 서버에 대해 사용자 자격 증명을 확인하려면 이를 LDAP로 설정합니다.", + "PRIMARY_AUTH_MODE": "이 인증 모드는 사용자가 로그인하는 기본 방법이 됩니다. 사용자가 ID 공급자 또는 로컬 DB를 통해 로그인하도록 선택하는 로그인 화면은 자동으로 사용자를 이 ID 공급자로 리디렉션합니다. DB를 통한 로그인은 명시적으로 '/account/sign-in' url로 접속 시 가능합니다.", + "LDAP_SEARCH_DN": "LDAP/AD 서버 검색 권한이 있는 사용자의 DN입니다. LDAP/AD 서버가 익명 검색을 지원하지 않는 경우 이 DN 및 ldap_search_pwd를 구성해야 합니다.", + "LDAP_BASE_DN": "LDAP/AD에서 사용자를 조회하는 기본 DN입니다.", + "LDAP_UID": "사용자를 일치시키기 위해 검색에 사용되는 속성입니다. LDAP/AD에 따라 uid, cn, 이메일, sAMAccountName 또는 기타 속성이 될 수 있습니다.", + "LDAP_SCOPE": "사용자를 검색할 범위입니다.", + "TOKEN_EXPIRATION": "토큰 서비스에서 생성된 토큰의 만료 시간(분)입니다. 기본값은 30분입니다.", + "ROBOT_NAME_PREFIX": "각 로봇(Robot) 이름의 접두사 문자열이며 기본값은 'robot$'입니다.", + "ROBOT_TOKEN_EXPIRATION": "로봇 계정 토큰의 만료 시간(일), 기본값은 30일입니다. 분 단위로 환산한 일 수를 표시하고 내림합니다", + "PRO_CREATION_RESTRICTION": "프로젝트 생성 권한이 있는 사용자를 정의하는 플래그입니다. 기본적으로 누구나 프로젝트를 만들 수 있습니다. 관리자만 프로젝트를 생성할 수 있도록 'Admin Only'로 설정하세요.", + "ROOT_CERT_DOWNLOAD": "레지스트리의 루트 인증서를 다운로드합니다.", + "SCANNING_POLICY": "다양한 요구 사항에 따라 이미지 스캔 정책을 설정합니다. '없음': 활성 정책이 없습니다. 'Daily At': 매일 지정된 시간에 검사를 시작합니다.", + "VERIFY_CERT": "LDAP 서버에서 인증서 확인", + "REPO_TOOLTIP": "이 모드에서는 사용자가 이미지에 어떤 작업도 수행할 수 없습니다.", + "WEBHOOK_TOOLTIP": "이미지 또는 차트 푸시, 가져오기, 삭제, 스캔과 같은 특정 작업이 수행될 때 지정된 엔드포인트에서 콜백을 수신하도록 웹훅를 활성화합니다", + "HOURLY_CRON": "매시간 시작되는 시간에 한 번씩 실행합니다. 0 0 * * * *와 같습니다.", + "WEEKLY_CRON": "일주일에 한 번, 토요일/일 사이 자정에 실행됩니다. 0 0 0 * * 0과 같습니다.", + "DAILY_CRON": "하루에 한 번, 자정에 실행하십시오. 0 0 0 * * *와 같습니다.", + "SKIP_SCANNER_PULL_TIME_TOOLTIP": "취약점 스캐너(예: Trivy)는 이미지를 스캔할 때 이미지 \"마지막 풀 시간\"을 업데이트하지 않습니다." + }, + "LDAP": { + "URL": "LDAP URL", + "SEARCH_DN": "LDAP Search DN", + "SEARCH_PWD": "LDAP 검색 비밀번호", + "BASE_DN": "LDAP Base DN", + "FILTER": "LDAP 필터", + "UID": "LDAP UID", + "SCOPE": "LDAP 범위", + "VERIFY_CERT": "LDAP 확인 인증서", + "LDAP_GROUP_BASE_DN": "LDAP Group Base DN", + "LDAP_GROUP_BASE_DN_INFO": "LDAP/AD에서 그룹을 조회할 기본 DN입니다. LDAP 그룹 관련 기능을 활성화해야 하는 경우 이 필드를 비워둘 수 없습니다.", + "LDAP_GROUP_FILTER": "LDAP 그룹 필터", + "LDAP_GROUP_FILTER_INFO": "LDAP/AD 그룹을 검색하기 위한 필터입니다. OpenLDAP의 경우: objectclass=groupOfNames. Active Directory의 경우: objectclass=group. LDAP 그룹 관련 기능이 필요한 경우 이 필드를 비워둘 수 없습니다.", + "LDAP_GROUP_GID": "LDAP Group GID", + "LDAP_GROUP_GID_INFO": "사용자를 일치시키기 위해 검색에 사용되는 속성으로, LDAP/AD에 따라 uid, cn 또는 기타 속성이 될 수 있습니다. Harbor의 그룹 이름은 기본적으로 이 속성으로 지정됩니다. LDAP 그룹 관련 기능을 활성화해야 하는 경우 이 필드를 비워둘 수 없습니다.", + "LDAP_GROUP_ADMIN_DN": "LDAP 그룹 관리자 DN", + "LDAP_GROUP_ADMIN_DN_INFO": "LDAP 그룹 DN을 지정합니다. 이 그룹의 모든 LDAP 사용자는 'Harbor' 관리자 권한을 갖게 됩니다. 원하지 않으시면 비워두세요.", + "LDAP_GROUP_MEMBERSHIP": "LDAP Group 멤버쉽", + "LDAP_GROUP_MEMBERSHIP_INFO": "속성은 LDAP 그룹의 멤버십을 나타내며 기본값은 memberof이며 일부 LDAP 서버에서는 \"ismemberof\"일 수 있습니다. LDAP 그룹 관련 기능을 활성화해야 하는 경우 이 필드를 비워둘 수 없습니다.", + "GROUP_SCOPE": "LDAP 그룹 검색 범위", + "GROUP_SCOPE_INFO": "그룹을 검색할 범위는 기본적으로 Subtree를 선택합니다." + + }, + "UAA": { + "ENDPOINT": "UAA 엔드포인트", + "CLIENT_ID": "UAA 클라이언트 ID", + "CLIENT_SECRET": "UAA 클라이언트 시크릿", + "VERIFY_CERT": "UAA 확인 인증서" + }, + "HTTP_AUTH": { + "ENDPOINT": "서버 엔드포인트", + "TOKEN_REVIEW": "토큰 리뷰 엔드포인트", + "SKIP_SEARCH": "검색 건너뛰기", + "VERIFY_CERT": "인증서 확인", + "ADMIN_GROUPS": "관리 그룹" + }, + "OIDC": { + "OIDC_PROVIDER": "OIDC 공급자 이름", + "OIDC_REDIREC_URL": "OIDC 공급자의 리디렉션 URI가 다음으로 설정되어 있는지 확인하세요.", + "ENDPOINT": "OIDC 엔드포인트", + "CLIENT_ID": "OIDC 클라이언트 ID", + "CLIENTSECRET": "OIDC 클라이언트 시크릿", + "SCOPE": "OIDC 범위", + "OIDC_VERIFYCERT": "인증서 확인", + "OIDC_AUTOONBOARD": "Automatic onboarding", + "USER_CLAIM": "Username Claim", + "OIDC_SETNAME": "OIDC 사용자 이름 설정", + "OIDC_SETNAMECONTENT": "제3자(OIDC)를 통해 인증할 때 처음으로 'Harbor'사용자 이름을 만들어야 합니다. 이는 'Harbor'내에서 프로젝트, 역할 등과 연결되는 데 사용됩니다.", + "OIDC_USERNAME": "사용자 이름", + "GROUP_CLAIM_NAME": "그룹 클레임 이름", + "GROUP_CLAIM_NAME_INFO": "OIDC 공급자에서 구성한 사용자 지정 그룹 클레임의 이름", + "OIDC_ADMIN_GROUP": "OIDC 관리 그룹", + "OIDC_ADMIN_GROUP_INFO": "OIDC 관리자 그룹 이름을 지정합니다. 이 그룹의 모든 OIDC 사용자는 항구 관리자 권한을 갖습니다. 원하지 않으시면 비워두세요.", + "OIDC_GROUP_FILTER": "OIDC 그룹 필터", + "OIDC_GROUP_FILTER_INFO": "제공된 정규식과 일치하는 OIDC 그룹을 필터링합니다. 모든 그룹과 일치하려면 공백으로 유지하세요." + }, + "SCANNING": { + "STOP_SCAN_ALL_SUCCESS": "트리거 정지 스캔이 모두 성공적으로 완료되었습니다!", + "TRIGGER_SCAN_ALL_SUCCESS": "트리거 스캔이 모두 성공적으로 완료되었습니다!", + "TRIGGER_SCAN_ALL_FAIL": "오류로 인해 전체 검사를 실행하지 못했습니다: {{error}}", + "TITLE": "취약점 스캐닝", + "SCAN_ALL": "모두 스캔", + "SCHEDULE_TO_SCAN_ALL": "모두 검사 예약", + "SCAN_NOW": "즉시 스캔", + "SCAN": "스캔", + "NONE_POLICY": "없음", + "DAILY_POLICY": "매일", + "REFRESH_POLICY": "새로고침 시", + "DB_REFRESH_TIME": "데이터베이스 업데이트 날짜", + "DB_NOT_READY": "취약점 데이터베이스가 완전히 준비되지 않았을 수 있습니다!", + "NEXT_SCAN": "이후 사용 가능", + "STATUS": { + "PENDING": "대기 중", + "RUNNING": "실행 중", + "STOPPED": "중지 됨", + "ERROR": "에러", + "SUCCESS": "성공", + "SCHEDULED": "예정 됨" + }, + "MANUAL": "수동", + "SCHEDULED": "예약" + }, + "TEST_MAIL_SUCCESS": "메일 서버 연결이 확인되었습니다.", + "TEST_LDAP_SUCCESS": "LDAP 서버에 대한 연결이 확인되었습니다.", + "TEST_MAIL_FAILED": "에러로 인해 메일 서버를 확인하지 못했습니다: {{param}}.", + "TEST_LDAP_FAILED": "에러로 인해 LDAP 서버를 확인하지 못했습니다: {{param}}.", + "LEAVING_CONFIRMATION_TITLE": "페이지에서 나가시겠습니까", + "LEAVING_CONFIRMATION_SUMMARY": "변경사항이 아직 저장되지 않았습니다. 현재 페이지에서 나가시겠습니까?", + "TEST_OIDC_SUCCESS": "OIDC 서버 연결이 확인되었습니다." + }, + "PAGE_NOT_FOUND": { + "MAIN_TITLE": "페이지를 찾을 수 없습니다", + "SUB_TITLE": "다음의 메인 페이지로 리디렉션 중입니다.", + "UNIT": "초..." + }, + "ABOUT": { + "VERSION": "버전", + "BUILD": "빌드", + "COPYRIGHT": "프로젝트 'Harbor'는 콘텐츠를 저장, 서명 및 검색하는 신뢰할 수 있는 오픈 소스 클라우드 네이티브 레지스트리 프로젝트입니다. 'Harbor'는 보안, ID, 관리 등 사용자에게 일반적으로 필요한 기능을 추가하여 오픈 소스 Docker 배포를 확장합니다. 'Harbor'는 사용자 관리, 액세스 제어, 활동 모니터링, 인스턴스 간 복제와 같은 고급 기능을 지원합니다. 레지스트리를 빌드 및 실행 환경에 더 가깝게 두면 이미지 전송 효율성도 향상될 수 있습니다.", + "COPYRIGHT_SUFIX": ".", + "TRADEMARK": "VMware는 미국 및 기타 국가에서 VMware, Inc.의 등록 상표 또는 상표입니다. 여기에 언급된 기타 모든 상표 및 이름은 해당 회사의 상표일 수 있습니다.", + "END_USER_LICENSE": "최종 사용자 라이센스 계약", + "OPEN_SOURCE_LICENSE": "오픈소스/서드 파티 라이선스" + }, + "START_PAGE": { + "GETTING_START": "", + "GETTING_START_TITLE": "시작하기" + }, + "TOP_REPO": "인기 저장소", + "STATISTICS": { + "PRO_ITEM": "프로젝트", + "REPO_ITEM": "저장소", + "INDEX_PRIVATE": "비공개", + "INDEX_PUB": "공개", + "INDEX_TOTAL": "합계", + "STORAGE": "저장 장치", + "LIMIT": "제한", + "STORAGE_USED": "저장 장치 사용량" + }, + "SEARCH": { + "IN_PROGRESS": "검색 중...", + "BACK": "뒤로" + }, + "VULNERABILITY": { + "STATE": { + "OTHER_STATUS": "스캔되지 않음", + "QUEUED": "큐에 등록됨", + "ERROR": "로그 보기", + "SCANNING": "스캔 중", + "STOPPED": "스캔 중지 됨" + }, + "GRID": { + "PLACEHOLDER": "스캐닝 결과를 찾을 수 없습니다!", + "COLUMN_ID": "취약점", + "COLUMN_SEVERITY": "심각도", + "COLUMN_PACKAGE": "패키지", + "COLUMN_PACKAGES": "패키지들", + "COLUMN_VERSION": "현재 버전", + "COLUMN_FIXED": "수정된 버전", + "COLUMN_DESCRIPTION": "설명", + "FOOT_ITEMS": "아이템", + "FOOT_OF": "of", + "IN_ALLOW_LIST": "CVE 허용 목록에 나열됨", + "CVSS3": "CVSS3" + }, + "CHART": { + "SCANNING_TIME": "스캔 완료 시간:", + "SCANNING_PERCENT": "스캔 완료 퍼센테이지:", + "SCANNING_PERCENT_EXPLAIN": "스캔 완료 비율은 성공적으로 스캔한 이미지 수 / 이미지 인덱스 내에서 참조된 총 이미지 수로 계산됩니다.", + "TOOLTIPS_TITLE": "{{totalPackages}} {{package}}의 {{totalVulnerability}}가 {{vulnerability}}을 알고있습니다.", + "TOOLTIPS_TITLE_SINGULAR": "{{package}}의 {{totalVulnerability}} 가 {{vulnerability}}를 알고있습니다.", + "TOOLTIPS_TITLE_ZERO": "인식 가능한 취약점이 감지되지 않았습니다." + }, + "SEVERITY": { + "CRITICAL": "심각", + "HIGH": "높은", + "MEDIUM": "중간", + "LOW": "낮음", + "NONE": "없음" + }, + "SINGULAR": "취약점", + "OVERALL_SEVERITY": "취약점 심각도:", + "NO_VULNERABILITY": "취약점 없음", + "PLURAL": "취약점", + "PLACEHOLDER": "취약점 필터", + "PACKAGE": "패키지", + "PACKAGES": "패키지들", + "SCAN_NOW": "스캔", + "SCAN_BY": "{{scanner}로 스캔", + "REPORTED_BY": "{{scanner}}로 보고 됨", + "NO_SCANNER": "스캐너 없음", + "TRIGGER_STOP_SUCCESS": "트리거 중지 스캔이 성공적으로 수행되었습니다", + "STOP_NOW": "스캔 중지" + }, + "PUSH_IMAGE": { + "TITLE": "푸시 명령어", + "DOCKER": "도커", + "PODMAN": "Podman", + "HELM": "헬름", + "CNAB": "CNAB", + "TAG_COMMAND_CHART": "이 프로젝트에 대한 차트 패키징:", + "PUSH_COMMAND_CHART": "이 프로젝트에 차트 푸시:", + "PUSH_COMMAND_CNAB": "CNAB를 이 프로젝트에 푸시:", + "TOOLTIP": "이 프로젝트에 아티팩트를 푸시하기 위한 명령 참조입니다.", + "TAG_COMMAND": "이 프로젝트의 이미지에 태그를 지정:", + "PUSH_COMMAND": "이 프로젝트에 이미지 푸시:", + "COPY_ERROR": "복사에 실패했습니다. 명령 참조를 수동으로 복사해 보세요.", + "COPY_PULL_COMMAND": "풀(PULL) 명령어 복사" + }, + "ARTIFACT": { + "FILTER_FOR_ARTIFACTS": "아티팩트 필터", + "ADDITIONS": "추가사항", + "ANNOTATION": "주석", + "OVERVIEW": "개요", + "IMAGE": "이미지", + "CHART": "차트", + "CNAB": "CNAB", + "WASM": "WASM", + "TAGGED": "태그 됨", + "UNTAGGED": "태그가 지정되지 않음", + "ALL": "모두", + "PLACEHOLDER": "아티팩트를 찾을 수 없음!", + "SCAN_UNSUPPORTED": "지원되지 않음", + "SUMMARY": "요약", + "DEPENDENCIES": "종속성", + "VALUES": "값", + "NAME": "이름", + "REPO": "저장소", + "OF": "of", + "VERSION": "버전", + "NO_README": "이 차트에서는 추가 정보 파일이 제공되지 않습니다.", + "ITEMS": "아이템", + "SHOW_KV": "키-값 쌍", + "SHOW_YAML": "YAML 파일" + }, + "TAG": { + "CREATION_TIME_PREFIX": "생성 날짜", + "CREATOR_PREFIX": "by", + "ANONYMITY": "익명", + "IMAGE_DETAILS": "이미지 상세", + "DOCKER_VERSION": "도커 버전", + "ARCHITECTURE": "아키텍쳐", + "OS": "OS", + "OS_VERSION": "OS 버전", + "HAVE": "have", + "HAS": "has", + "SCAN_COMPLETION_TIME": "스캔 완료", + "IMAGE_VULNERABILITIES": "이미지 취약점", + "LEVEL_VULNERABILITIES": "레벨 취약젖ㅁ", + "PLACEHOLDER": "태그를 찾을 수 없습니다!", + "COPY_ERROR": "복사 실패, 수동으로 복사하세요.", + "FILTER_FOR_TAGS": "필터 태그", + "AUTHOR": "작성자", + "LABELS": "라벨", + "CREATION": "생성 날짜", + "COMMAND": "명령어", + "UPLOADTIME": "업로드 시간", + "NAME": "이름", + "PULL_TIME": "풀(Pull) 시간", + "PUSH_TIME": "푸시 시간", + "OF": "of", + "ITEMS": "아이템", + "ADD_TAG": "태그 추가", + "REMOVE_TAG": "태그 제거", + "NAME_ALREADY_EXISTS": "저장소에 태그가 이미 존재합니다" + }, + "LABEL": { + "LABEL": "라벨", + "DESCRIPTION": "설명", + "CREATION_TIME": "생성 시간", + "NEW_LABEL": "새 라벨", + "EDIT": "편집", + "DELETE": "삭제", + "LABEL_NAME": "라벨 이름", + "COLOR": "색상", + "FILTER_LABEL_PLACEHOLDER": "라벨 필터", + "NO_LABELS": "라벨 없음", + "DELETION_TITLE_TARGET": "라벨 삭제 확인", + "DELETION_SUMMARY_TARGET": "{{param}}를 삭제하시겠습니까?", + "PLACEHOLDER": "라벨을 찾을 수 없습니다!", + "NAME_ALREADY_EXISTS": "라벨 이름이 이미 존재합니다." + }, + "QUOTA": { + "PROJECT": "프로젝트", + "OWNER": "소유자", + "COUNT": "개수", + "STORAGE": "스토리지", + "EDIT": "편집", + "DELETE": "삭제", + "OF": "of", + "PROJECT_QUOTA_DEFAULT_ARTIFACT": "프로젝트당 기본 아티팩트 수", + "PROJECT_QUOTA_DEFAULT_DISK": "프로젝트당 기본 할당량 공간", + "EDIT_PROJECT_QUOTAS": "프로젝트 할당량 편집", + "EDIT_DEFAULT_PROJECT_QUOTAS": "기본 프로젝트 할당량 편집", + "SET_QUOTAS": "{{params}}' 프로젝트에 대한 프로젝트 할당량을 설정하세요", + "SET_DEFAULT_QUOTAS": "새 프로젝트 생성 시 기본 프로젝트 할당량 설정", + "COUNT_QUOTA": "아티팩트 개수", + "COUNT_DEFAULT_QUOTA": "기본 아티팩트 개수", + "STORAGE_QUOTA": "프로젝트 할당량 제한", + "STORAGE_DEFAULT_QUOTA": "기본 저장 공간 할당량", + "SAVE_SUCCESS": "할당량 편집 성공", + "UNLIMITED": "제한 없음", + "INVALID_INPUT": "유효하지 않은 입력", + "PLACEHOLDER": "프로젝트를 찾을 수 없습니다", + "FILTER_PLACEHOLDER": "이름(정확히 일치)으로 검색", + "QUOTA_USED": "사용된 할당량" + }, + "WEEKLY": { + "MONDAY": "월요일", + "TUESDAY": "화요일", + "WEDNESDAY": "수요일", + "THURSDAY": "목요일", + "FRIDAY": "금요일", + "SATURDAY": "토요일", + "SUNDAY": "일요일" + }, + "OPERATION": { + "LOCAL_EVENT": "로컬 이벤트", + "ALL": "모두", + "RUNNING": "실행 중", + "FAILED": "실패", + "STOP_EXECUTIONS": "실행 중지", + "DELETE_PROJECT": "프로젝트 삭제", + "DELETE_REPO": "저장소 삭제", + "DELETE_TAG": "태그 삭제", + "DELETE_USER": "사용자 삭제", + "DELETE_ROBOT": "로봇 삭제", + "DELETE_REGISTRY": "레지스트리 삭제", + "DELETE_REPLICATION": "복제 삭제", + "DELETE_MEMBER": "사용자 구성원 삭제", + "DELETE_GROUP": "그룹 구성원 삭제", + "DELETE_CHART_VERSION": "차트 버전 삭제", + "DELETE_CHART": "차트 삭제", + "SWITCH_ROLE": "역할 변경", + "ADD_GROUP": "그룹 맴버 추가", + "ADD_USER": "유저 맴버 추가", + "DELETE_LABEL": "라벨 삭제", + "REPLICATION": "복제", + "DAY_AGO": " 일 전", + "HOUR_AGO": " 시간 전", + "MINUTE_AGO": " 분 전", + "SECOND_AGO": "1분 미만", + "EVENT_LOG": "이벤트 로그" + }, + "UNKNOWN_ERROR": "알 수 없는 에러가 발생했습니다. 다시 시도해 주세요.", + "UNAUTHORIZED_ERROR": "세션이 유효하지 않거나 만료됐습니다. 로그인 후 다시 시도하세요.", + "REPO_READ_ONLY": "'Harbor'는 읽기 전용 모드로 설정되어 있으며 읽기 전용 모드에서는 저장소 삭제, 아티팩트 태그 그리고 이미지 푸시가 비활성화됩니다", + "FORBIDDEN_ERROR": "작업을 수행하기위한 권한이 없습니다.", + "GENERAL_ERROR": "서비스 호출 수행 중 오류가 발생했습니다: {{param}}.", + "BAD_REQUEST_ERROR": "잘못된 요청으로인해 작업을 수행할 수 없습니다.", + "NOT_FOUND_ERROR": "오브젝트가 존재하지 않기 때문에 요청을 완료할 수 없습니다.", + "CONFLICT_ERROR": "귀하의 제출 내용이 충돌하기 때문에 귀하의 작업을 수행할 수 없습니다.", + "PRECONDITION_FAILED": "전제 조건 요류로 인해 작업을 수행할 수 없습니다.", + "SERVER_ERROR": "내부 서버 오류가 발생하여 귀하의 작업을 수행할 수 없습니다.", + "INCONRRECT_OLD_PWD": "이전 비밀번호가 올바르지 않습니다.", + "UNKNOWN": "n/a", + "STATUS": "상태", + "START_TIME": "시작 시간", + "CREATION_TIME": "생성 시간", + "UPDATE_TIME": "갱신 시간", + "LOGS": "로그", + "PENDING": "대기 중", + "FINISHED": "완료", + "STOPPED": "중지", + "RUNNING": "실행 중", + "ERROR": "에러", + "SCHEDULE": { + "NONE": "없음", + "DAILY": "매일", + "WEEKLY": "매주", + "HOURLY": "매시", + "CUSTOM": "사용자 정의", + "MANUAL": "수동", + "SCHEDULE": "예약", + "CRON": "cron", + "ON": "on", + "AT": "at", + "NOSCHEDULE": "예약내역을 가져오던 중 에러가 발생했습니다" + + }, + "GC": { + "CURRENT_SCHEDULE": "가비지 컬렉션 예약", + "GC_NOW": "가비지 컬렉션 즉시 실행", + "JOB_HISTORY": "가비지 컬렉션 기록", + "JOB_ID": "작업 ID", + "TRIGGER_TYPE": "발생 유형", + "LATEST_JOBS": "마지막 {{param}} 작업", + "MSG_SUCCESS": "가비지 컬렉션 성공", + "MSG_SCHEDULE_SET": "가비지 컬렉션 일정이 추가됐습니다", + "MSG_SCHEDULE_RESET": "가비지 컬렉션 일정이 초기화됐습니다", + "PARAMETERS": "파라미터", + "DELETE_UNTAGGED": "태그가 지정되지 않은 아티팩트에 대한 가비지 수집 허용", + "EXPLAIN": "가비지 컬렉션은 레지스트리 성능에 영향을 미칠 수 있는 커퓨팅 집약적 작업입니다.", + "EXPLAIN_TIME_WINDOW": "지난 2시간(기본 기간) 동안 업로드된 아티팩트는 가비지 컬렉션에서 제외됩니다.", + "DRY_RUN_SUCCESS": "모의 테스트가 성공적으로 실행됐습니다", + "DELETE_DETAIL": "{{blob}} blob(s) and {{manifest}} manifest(s) deleted, {{size}} space freed up", + "DELETE_DETAIL_DRY_RUN": "{{blob}} blob(s) and {{manifest}} manifest(s) could be deleted, {{size}} space could be freed up", + "WORKERS_TOOLTIP": "가비지 컬렉션 작업을 병렬로 실행할 수 있는 작업자 수를 설정합니다. 기본값은 1입니다." + }, + "RETAG": { + "MSG_SUCCESS": "아티팩트를 성공적으로 복사했습니다.", + "TIP_REPO": " 저장소 이름은 경로 구성 요소로 구분됩니다. 저장소 이름의 구성 요소는 하나 이상의 소문자, 영숫자 문자여야 하며 선택적으로 마침표, 대시 또는 밑줄로 구분됩니다. 보다 엄격하게는 정규식 [a-z0-9]+(?:[._-][a-z0-9]+)*와 일치해야 합니다. 저장소 이름에 두 개 이상의 경로 구성 요소가 있는 경우 다음과 같아야 합니다. 슬래시('/')로 구분됩니다. 슬래시를 포함한 저장소 이름의 전체 길이는 256자 미만이어야 합니다.", + "TIP_TAG": "태그는 저장소의 Docker 이미지에 적용되는 라벨입니다. 태그는 저장소의 다양한 이미지를 서로 구별하는 방법입니다. Regex와 일치해야 합니다: (`[\\w][\\w.-]{0,127}`)" + }, + "CVE_ALLOWLIST": { + "DEPLOYMENT_SECURITY": "배포 보안", + "CVE_ALLOWLIST": "CVE 허용 목록", + "SYS_ALLOWLIST_EXPLAIN": "시스템 허용 목록을 사용하면 이미지의 취약점을 계산할 때 이 목록의 취약점을 무시할 수 있습니다.", + "ADD_SYS": "시스템 허용 목록에 CVE 아이디 추가", + "WARNING_SYS": "시스템 CVE 허용 목록이 만료되었습니다. 만료 날짜를 연장하여 허용 목록을 활성화할 수 있습니다.", + "WARNING_PRO": "프로젝트 CVE 허용 목록이 만료되었습니다. 만료 날짜를 연장하여 허용 목록을 활성화할 수 있습니다.", + "ADD": "추가", + "ENTER": "CVE 아이디(들) 입력", + "HELP": "구분 기호: 쉼표 또는 개행 문자", + "NONE": "없음", + "EXPIRES_AT": "~에 만료", + "NEVER_EXPIRES": "만료되지 않음", + "PRO_ALLOWLIST_EXPLAIN": "프로젝트 허용 목록을 사용하면 이 프로젝트에서 이미지를 푸시하고 가져올 때 이 목록의 취약점을 무시할 수 있습니다.", + "PRO_OR_SYS": "시스템 수준에서 구성된 기본 허용 목록을 사용하거나 '프로젝트 허용 목록'을 클릭하여 새 허용 목록을 만들 수 있습니다.", + "MERGE_INTO": "시스템 허용 목록도 추가하려면 '시스템에서 복사'를 클릭하기 전에 개별 CVE ID를 추가하세요.", + "SYS_ALLOWLIST": "시스템 허용 항목", + "PRO_ALLOWLIST": "프로젝트 허용 항목", + "ADD_SYSTEM": "시스템에서 복사" + }, + "TAG_RETENTION": { + "TAG_RETENTION": "태그 보관", + "RETENTION_RULES": "보관 규칙", + "RULE_NAME_1": " 지난 {{number}}일 동안의 아티팩트", + "RULE_NAME_2": " 가장 최근의 활성 {{number}} 아티팩트", + "RULE_NAME_3": " 가장 최근에 푸시된 {{number}} 아티팩트", + "RULE_NAME_4": " 가장 최근에 가져온 {{number}} 아티팩트", + "RULE_NAME_5": " always", + "ADD_RULE": "규칙 추가", + "ADD_RULE_HELP_1": "규칙을 추가하려면 ADD RULE 버튼을 클릭하세요.", + "ADD_RULE_HELP_2": "태그 보관 정책은 하루에 한 번 실행됩니다.", + "RETENTION_RUNS": "보관 실행", + "RUN_NOW": "즉시 실행", + "WHAT_IF_RUN": "모의 테스트", + "ABORT": "ABORT", + "SERIAL": "아이디", + "STATUS": "상태", + "DRY_RUN": "모의 테스트", + "START_TIME": "시작 시간", + "DURATION": "지속 기간", + "DETAILS": "상세", + "REPOSITORY": "저장소", + "EDIT": "편집", + "DISABLE": "비활성화", + "ENABLE": "활성화", + "DELETE": "삭제", + "ADD_TITLE": "태그 보관 규칙 추가", + "ADD_SUBTITLE": "이 프로젝트에 대한 태그 보관 규칙을 지정하세요. 모든 태그 보관 규칙은 독립적으로 계산되며 각 규칙은 선택한 저장소 목록에 적용될 수 있습니다.", + "BY_WHAT": "아티팩트 수 또는 일수별", + "RULE_TEMPLATE_1": "지난 #일 동안의 아티팩트", + "RULE_TEMPLATE_2": "가장 최근의 활성 # 아티팩트", + "RULE_TEMPLATE_3": "가장 최근에 푸시된 아티팩트 #개", + "RULE_TEMPLATE_4": "가장 최근에 가져온 아티팩트 #개", + "RULE_TEMPLATE_5": " 항상", + "ACTION_RETAIN": " 보관", + "UNIT_DAY": "날", + "UNIT_COUNT": "개수", + "NUMBER": "번호", + "IN_REPOSITORIES": "For the repositories", + "REP_SEPARATOR": "쉼표로 구분된 저장소, 저장소* 또는 **를 여러 개 입력하세요.", + "TAGS": "태그", + "UNTAGGED": " 태그되지 않음", + "INCLUDE_UNTAGGED": " 태그가 지정되지 않은 아티팩트", + "MATCHES_TAGS": "태그와 일치", + "MATCHES_EXCEPT_TAGS": "태그를 제외하고 일치", + "TAG_SEPARATOR": "여러 개의 쉼표로 구분된 태그(태그* 또는 **)를 입력하세요. 선택적으로 위의 확인란을 선택하여 '포함' 또는 '제외' 선택기를 적용할 때 태그가 지정되지 않은 모든 아티팩트를 포함합니다.", + "LABELS": "라벨", + "MATCHES_LABELS": "라벨과 일치", + "MATCHES_EXCEPT_LABELS": "라벨을 제외한 일치", + "REP_LABELS": "여러 개의 쉼표로 구분된 라벨을 입력하세요.", + "RETENTION_RUN": "보관 실행", + "RETENTION_RUN_EXPLAIN": "보존 정책을 실행하면 이 프로젝트의 아티팩트에 부정적인 영향을 미칠 수 있으며 영향을 받은 아티팩트 태그가 삭제됩니다. 취소를 누르고 DRY RUN을 사용하여 이 정책의 효과를 시뮬레이션합니다. 그렇지 않으면 RUN을 눌러 계속 진행하세요.", + "RETENTION_RUN_ABORTED": "보존 실행이 중단되었습니다.", + "RETENTION_RUN_ABORTED_EXPLAIN": "이 보존 실행이 중단되었습니다. 이미 삭제된 아티팩트는 되돌릴 수 없습니다. 다른 실행을 시작하여 아티팩트를 계속 삭제할 수 있습니다. 실행을 시뮬레이션하려면 DRY RUN을 사용할 수 있습니다..", + "LOADING": "로딩...", + "NO_EXECUTION": "실행중인 내용을 찾을 수 없습니다!", + "NO_HISTORY": "기록을 찾을 수 없습니다!!", + "DELETION": "삭제", + "EDIT_TITLE": "태그 보관 규칙 편집", + "LOG": "로그", + "EXCLUDES": "제외", + "MATCHES": "일치", + "REPO": " 저장소", + "EXC": " excluding ", + "MAT": " matching ", + "AND": " and", + "WITH": " with ", + "WITHOUT": " without ", + "LOWER_LABELS": " labels", + "WITH_CONDITION": " with", + "LOWER_TAGS": " tags", + "TRIGGER": "Schedule", + "RETAINED": "Retained", + "TOTAL": "전체", + "NONE": " 없음", + "RULE_NAME_6": " 지난 {{number}}일 동안 가져온 아티팩트", + "RULE_NAME_7": " 지난 {{number}}일 동안 푸시된 아티팩트", + "RULE_TEMPLATE_6": " 지난 #일 이내에 가져온 아티팩트", + "RULE_TEMPLATE_7": " 지난 #일 이내에 푸시된 아티팩트", + "SCHEDULE": "예정", + "SCHEDULE_WARNING": "보존 정책을 실행하면 Harbor 프로젝트에서 아티팩트를 삭제하는 되돌릴 수 없는 효과가 발생합니다. 예약하기 전에 모든 정책을 다시 한번 확인하세요.", + "EXISTING_RULE": "기존 규칙", + "ILLEGAL_RULE": "불법적인 규칙", + "INVALID_RULE": "잘못된 규칙", + "COUNT_LARGE": "\"COUNT\" 매개변수가 너무 큽니다.", + "DAYS_LARGE": "\"DAYS\" 매개변수가 너무 큽니다.", + "EXECUTION_TYPE": "실행 유형", + "ACTION": "동작", + "YES": "예", + "NO": "아니요" + }, + "IMMUTABLE_TAG": { + "IMMUTABLE_RULES": "불변성 규칙", + "ADD_RULE": "규칙 추가", + "ADD_RULE_HELP_1": "규칙을 추가하려면 규칙 추가 버튼을 누르세요.", + "EDIT": "편집", + "DISABLE": "비활성화", + "ENABLE": "활성화", + "DELETE": "삭제", + "ADD_TITLE": "태그 불변성 규칙 추가", + "ADD_SUBTITLE": "이 프로젝트에 대한 태그 불변성 규칙을 지정합니다. 참고: 모든 태그 불변성 규칙은 먼저 독립적으로 계산된 다음 통합되어 불변성 태그의 최종 세트를 캡처합니다.", + "IN_REPOSITORIES": "For the repositories", + "REP_SEPARATOR": "쉼표로 구분된 저장소, 저장소* 또는 **를 여러 개 입력하세요.", + "TAGS": "태그", + "TAG_SEPARATOR": "여러 개의 쉼표로 구분된 태그(태그* 또는 **)를 입력하세요.", + "EDIT_TITLE": "태그 불변성 규칙 편집", + "EXC": " excluding ", + "MAT": " matching ", + "AND": " and", + "WITH": " with ", + "WITHOUT": " without ", + "LOWER_LABELS": " labels", + "LOWER_TAGS": " tags", + "NONE": "없음", + "EXISTING_RULE": "Existing rule", + "ACTION": "ACTION" + }, + "SCANNER": { + "DELETION_SUMMARY": "스캐너 {{param}}을 삭제하시겠습니까?", + "SKIP_CERT_VERIFY": "원격 레지스트리가 자체 서명되거나 신뢰할 수 없는 인증서를 사용할 때 인증서 확인을 건너뛰려면 이 상자를 선택하십시오.", + "NAME": "이름", + "NAME_EXISTS": "이미 존재하는 이름입니다", + "NAME_REQUIRED": "이름은 필수 항목입니다", + "NAME_REX": "이름은 2자 이상이어야 하며 소문자, 숫자, ._-가 포함되어야 하며 문자나 숫자로 시작해야 합니다.", + "DESCRIPTION": "설명", + "ENDPOINT": "엔드포인트", + "ENDPOINT_EXISTS": "엔드포인트 URL이 이미 존재합니다", + "ENDPOINT_REQUIRED": "엔드포인트 URL은 필수 항목입니다", + "ILLEGAL_ENDPOINT": "엔드포인트 Url 이 잘못됐습니다", + "AUTH": "권한 부여", + "NONE": "없음", + "BASIC": "기본", + "BEARER": "Bearer", + "API_KEY": "APIKey", + "USERNAME": "사용자 이름", + "USERNAME_REQUIRED": "사용자 이름은 필수 항목입니다", + "PASSWORD": "비밀번호", + "PASSWORD_REQUIRED": "비밀번호는 필수 항목입니다", + "TOKEN": "토큰", + "TOKEN_REQUIRED": "토큰은 필수 항목입니다", + "API_KEY_REQUIRED": "APIKey 는 필수 항목입니다", + "SKIP": "인증서 확인 건너뛰기", + "ADD_SCANNER": "스캐너 추가", + "EDIT_SCANNER": "스캐너 편집", + "TEST_CONNECTION": "TEST CONNECTION", + "ADD_SUCCESS": "성공적으로 추가됐습니다", + "TEST_PASS": "테스트 통과", + "TEST_FAILED": "Ping: registration {{name}}:{{url}} is unreachable", + "UPDATE_SUCCESS": "성공적으로 업데이트됏습니다", + "SCANNER_COLON": "스캐너:", + "NAME_COLON": "이름:", + "VENDOR_COLON": "Vendor:", + "VERSION_COLON": "버전:", + "CAPABILITIES": "기능", + "CONSUMES_MIME_TYPES_COLON": "Mime 유형 사용:", + "PRODUCTS_MIME_TYPES_COLON": "Mime 유형 제공:", + "PROPERTIES": "속성", + "NEW_SCANNER": "새 스캐너", + "SET_AS_DEFAULT": "기본으로 설정", + "HEALTH": "상태", + "DISABLED": "비활성화", + "NO_SCANNER": "스캐너를 찾을 수 없습니다", + "DEFAULT": "기본", + "HEALTHY": "안정적인", + "UNHEALTHY": "위험한", + "SCANNERS": "스캐너들", + "SCANNER": "스캐너", + "EDIT": "편집", + "NOT_AVAILABLE": "사용 불가", + "ADAPTER": "Adapter", + "VENDOR": "Vendor", + "VERSION": "버전", + "SELECT_SCANNER": "스캐너 선택", + "ENABLED": "활성화 됨", + "ENABLE": "활성화", + "DISABLE": "비활성화", + "DELETE_SUCCESS": "성공적으로 살제했습니다", + "TOTAL": "전체", + "FIXABLE": "수정가능", + "DURATION": "지속 기간:", + "OPTIONS": "옵션", + "USE_INNER": "내부 레지스트리 주소 사용", + "USE_INNER_TIP": "옵션을 선택하면 스캐너는 관련 콘텐츠에 액세스하기 위해 내부 레지스트리 주소를 사용해야 합니다.", + "VULNERABILITY_SEVERITY": "취약점 심각도:", + "CONFIRM_DELETION": "스캐너 삭제 확인", + "NO_PROJECT_SCANNER": "스캐너 없음", + "SET_UNHEALTHY_SCANNER": "스캐너 선택:{{name}} 이 위험한 상태입니다", + "SCANNED_BY": "스캔한 사람:", + "IMAGE_SCANNERS": "이미지 스캐너", + "VIEW_DOC": "문서 보기", + "ALL_SCANNERS": "모든 스캐너", + "HELP_INFO_1": "기본 스캐너가 설치되었습니다. 다른 스캐너를 설치하려면 다음을 참조하십시오", + "HELP_INFO_2": "문서.", + "NO_DEFAULT_SCANNER": "기본 스캐너 없음" + }, + "DISTRIBUTION": { + "FILTER_INSTANCE_PLACEHOLDER": "인스턴스 필터", + "FILTER_HISTORIES_PLACEHOLDER": "필터 기록", + "ADD_ACTION": "새 인스턴스", + "PREHEAT_ACTION": "예열", + "EDIT_ACTION": "편집", + "ENABLE_ACTION": "활성화", + "DISABLE_ACTION": "비활성화", + "DELETE_ACTION": "삭제", + "NOT_FOUND": "인스턴스를 찾을 수 없습니다!", + "NAME": "이름", + "ENDPOINT": "엔드포인트", + "STATUS": "상태", + "ENABLED": "활성화", + "SETUP_TIMESTAMP": "타임스탬프 설정", + "PROVIDER": "제공자", + "DELETION_TITLE": "인스턴스 삭제 확인", + "DELETION_SUMMARY": "인스턴스(들) {{param}}를 삭제하시겠습니까?", + "ENABLE_TITLE": "인스턴스(들) 활성화", + "ENABLE_SUMMARY": "인스턴스(들) {{param}}를 활성화하시겠습니까?", + "DISABLE_TITLE": "인스턴스(들) 비활성화", + "DISABLE_SUMMARY": "인스턴스(들){{param}}를 비활성화 하시겠습니까?", + "IMAGE": "이미지", + "START_TIME": "시작 시간", + "FINISH_TIME": "종료 시간", + "INSTANCE": "인스턴스", + "HISTORIES": "기록", + "CREATE_SUCCESS": "인스턴스를 성공적으로 생성했습니다", + "CREATE_FAILED": "인스턴스 생성을 실패했습니다", + "DELETED_SUCCESS": "인스턴스(들)를 성공적으로 삭제했습니다", + "DELETED_FAILED": "인스턴스(들)삭제에 실패했습니다", + "ENABLE_SUCCESS": "인스턴스(들)를 성공적으로 활성화했습니다", + "ENABLE_FAILED": "인스턴스(들) 활성화 실패", + "DISABLE_SUCCESS": "인스턴스(들)를 성공적으로 비활성화 했습니다", + "DISABLE_FAILED": "인스턴스(들) 비활성화를 실패했습니다", + "UPDATE_SUCCESS": "인스턴스(들)를 성공적으로 업데이트했습니다", + "UPDATE_FAILED": "인스턴스(들) 업데이트에 실패했습니다", + "REQUEST_PREHEAT_SUCCESS": "예열을 성공적으로 요청했습니다", + "REQUEST_PREHEAT_FAILED": "예열 요청을 실패했습니다", + "DESCRIPTION": "설명", + "AUTH_MODE": "인증 모드", + "USERNAME": "사용자 이름", + "PASSWORD": "비밀번호", + "TOKEN": "토큰", + "SETUP_NEW_INSTANCE": "새 인스턴스 설치", + "EDIT_INSTANCE": "인스턴스 편집", + "SETUP": { + "NAME_PLACEHOLDER": "인스턴스 이름 입력", + "DESCRIPTION_PLACEHOLDER": "인스턴스 설명 입력", + "ENDPOINT_PLACEHOLDER": "인스턴스 엔드포인트 입력", + "USERNAME_PLACEHOLDER": "사용자 이름 입력", + "PASSWORD_PLACEHOLDER": "비밀번호 입력", + "TOKEN_PLACEHOLDER": "토큰 입력" + }, + "MAINTAINER": "메인테이너(들)", + "SOURCE": "Source", + "VERSION": "버전", + "SET_AS_DEFAULT": "기본으로 설정", + "DELETE_INSTANCE": "인스턴스 삭제", + "ENABLE_INSTANCE": "인스턴스 활성화", + "DISABLE_INSTANCE": "인스턴스 비활성화", + "SET_DEFAULT_SUCCESS": "기본값으로 설정됐습니다", + "SET_DEFAULT_FAILED": "기본값 설정에 실패했습니다", + "UPDATE_INSTANCE": "인스턴스 업데이트", + "CREATE_INSTANCE": "인스턴스 생성" + }, + "P2P_PROVIDER": { + "P2P_PROVIDER": "P2P 예열", + "POLICIES": "정책", + "NEW_POLICY": "새 정책", + "NAME": "이름", + "ENABLED": "활성화", + "PROVIDER": "공급자", + "FILTERS": "필터", + "TRIGGER": "트리거", + "CREATED": "생성 시간", + "DESCRIPTION": "설명", + "NO_POLICY": "정책 없음", + "ENABLED_POLICY_SUMMARY": "정책{{name}}을 활성화하시겠습니까?", + "DISABLED_POLICY_SUMMARY": "정책{{name}}을 비활성화하시겠습니까?", + "ENABLED_POLICY_TITLE": "정책 활성화", + "DISABLED_POLICY_TITLE": "정책 비활성화", + "DELETE_POLICY_SUMMARY": "{{names}}정책을 삭제하시겠습니까?", + "EDIT_POLICY": "P2P 공급자 정책 편집", + "ADD_POLICY": "P2P 공급자 정책 성생", + "NAME_REQUIRED": "이름은 필수 항목입니다", + "PROVIDER_REQUIRED": "공급자는 필수 항목입니다", + "ADDED_SUCCESS": "정책을 성공적으로 추가했습니다", + "UPDATED_SUCCESS": "정책을 성공적으로 업데이트했습니다", + "EXECUTE": "실행", + "EXECUTE_SUCCESSFULLY": "성공적으로 실행됐습니다", + "EXECUTE_TITLE": "정책 실행 확인", + "EXECUTE_SUMMARY": "정책{{param}}을 실행하시겠습니까?", + "STOP_TITLE": "정책 중지 확인", + "STOP_SUMMARY": "정책{{param}}을 중지하시겠습니까?", + "STOP_SUCCESSFULLY": "정책을 성공적으로 중지했습니다", + "STATUS_MSG": "상태 메시지", + "JOB_PLACEHOLDER": "실행된 내용을 찾을 수 없습니다", + "PROVIDER_TYPE": "Vendor", + "ID": "실행 ID", + "NO_PROVIDER": "제공자를 먼저 추가해주세요", + "ARTIFACT": "아티팩트", + "DIGEST": "Digest", + "TYPE": "유형", + "TASKS": "작업", + "TASKS_PLACEHOLDER": "작업을 찾을 수 없습니다", + "SEVERITY_WARNING": "여기의 취약점 설정은 여기의 설정을 재정의하는 관련 프로젝트 구성과 충돌합니다.", + "REPOS": "저장소", + "TAGS": "태그", + "CRITERIA": "기준", + "ONLY_SIGNED": "서명된 이미지만", + "PREHEAT_ON_PUSH": "푸시 시 예열", + "START_TEXT": "No vulnerability severity of", + "EDN_TEXT": "and above", + "LABELS": "라벨", + "SCHEDULE": "예약", + "TEST_FAILED": "테스트 실패", + "MANUAL": "수동", + "SCHEDULED": "예약", + "EVENT_BASED": "이벤트 기반", + "EVENT_BASED_EXPLAIN_LINE1": "다음 이벤트가 발생할 때마다 정책이 평가됩니다:", + "EVENT_BASED_EXPLAIN_LINE2": "아티팩트 푸시됐습니다", + "EVENT_BASED_EXPLAIN_LINE3": "아티팩트 라벨이 지정됐습니다", + "EVENT_BASED_EXPLAIN_LINE4": "아티팩트 스캔됐습니다", + "REPO_REQUIRED": "저장소는 필수 항목입니다", + "TAG_REQUIRED": "태그는 필수 항목입니다", + "DELETE_SUCCESSFULLY": "정책이 성공적으로 삭제됐습니다", + "UPDATED_SUCCESSFULLY": "정책이 성공적으로 업데이트됐습니다", + "EXECUTIONS": "실행", + "TAG_SEPARATOR": "쉼표로 구분된 태그를 여러 개 입력하세요(태그* 또는 **).", + "CONTENT_WARNING": "여기의 콘텐츠 신뢰 설정은 여기의 설정을 재정의하는 관련 프로젝트 구성과 충돌합니다.", + "PREHEAT_EXPLAIN": "예열은 이미지를 p2p 네트워크로 마이그레이션합니다.", + "CRITERIA_EXPLAIN": "구성 탭 아래 '배포 보안' 섹션에 지정된 대로입니다", + "SKIP_CERT_VERIFY": "원격 공급자가 자체 서명되거나 신뢰할 수 없는 인증서를 사용할 때 인증서 확인을 건너뛰려면 이 상자를 선택합니다.", + "NAME_TOOLTIP": "정책 이름은 하나 이상의 대문자, 소문자 또는 숫자 그룹으로 구성됩니다. 그룹은 점, 밑줄 또는 하이픈으로 구분됩니다.", + "NEED_HELP": "먼저 시스템 관리자에게 공급자를 추가해 달라고 요청하세요." + }, + "PAGINATION": { + "PAGE_SIZE": "페이지 크기" + }, + "SYSTEM_ROBOT": { + "READ": "읽기", + "CREATE": "생성", + "ARTIFACT": "아티팩트", + "ADD_ROBOT": "로봇 추가", + "UPDATE_ROBOT": "로봇 갱신", + "UPDATE_ROBOT_SUCCESSFULLY": "로봇이 성공적으로 갱신됐습니다", + "PLACEHOLDER": "새 시크릿 입력", + "SECRET": "시크릿은 8~128자 길이로 대문자 1개, 소문자 1개, 숫자 1개 이상이 포함되어야 합니다.", + "REFRESH_SECRET": "시크릿 새로고침", + "REFRESH_SECRET_SUCCESS": "시크릿이 성공적으로 새로고쳐졌습니다", + "DELETE_ROBOT": "로봇 삭제", + "DELETE_ROBOT_SUCCESS": "로봇(들)이 성공적으로 삭제됐습니다", + "ENABLE_TITLE": "로봇 활성화", + "ENABLE_SUMMARY": "로봇{{param}}을 활성화하시겠습니까?", + "DISABLE_TITLE": "로봇 비활성화", + "DISABLE_SUMMARY": "로봇{{param}}을 비활성화 하시겠습니까?", + "ENABLE_ROBOT_SUCCESSFULLY": "로봇을 성공적으로 활성화했습니다", + "DISABLE_ROBOT_SUCCESSFULLY": "로봇을 성공적으로 비활성화했습니다", + "ROBOT_ACCOUNT": "로봇 계정", + "PROJECTS": "프로젝트", + "ALL_PROJECTS": "모든 프로젝트", + "PERMISSIONS": "권한", + "REFRESH_SECRET_SUMMARY": "로봇 계정 비밀을 자동으로 새로 고치거나 선택적으로 세부 정보를 열어 새 비밀을 수동으로 지정하세요.", + "TOKEN": "시크릿", + "NEW_TOKEN": "새 시크릿", + "REFRESH": "새로고침", + "PROJECTS_MODAL_TITLE": "로봇 계정을 위한 프로젝트", + "PROJECTS_MODAL_SUMMARY": "이 로봇 계정이 다루는 프로젝트가 있습니다.", + "CREATE_ROBOT": "시스템 로봇 계정 생성", + "CREATE_ROBOT_SUMMARY": "시스템 및 특정 프로젝트에 대한 권한을 포함하는 시스템 로봇 계정을 생성합니다", + "EDIT_ROBOT": "시스템 로봇 계정 편집", + "EDIT_ROBOT_SUMMARY": "시스템 및 특정 프로젝트에 대한 권한을 포함하는 시스템 로봇 계정을 편집합니다", + "EXPIRATION_TIME": "만료 시간", + "EXPIRATION_TIME_EXPLAIN": "로봇 계정의 토큰 만료 시간(일 단위, 시작 시점은 생성 시간)입니다. 만료되지 않으려면 \"-1\"을 입력하세요.", + "EXPIRATION_DEFAULT": "일(기본값)", + "EXPIRATION_DAYS": "# 일 지정", + "EXPIRATION_NEVER": "만료 안됨", + "EXPIRATION_REQUIRED": "유효한 만료 시간은 필수 항목입니다", + "COVER_ALL": "모든 프로젝트를 커버", + "COVER_ALL_EXPLAIN": "기존 및 향후 모든 프로젝트에 적용하려면 선택하세요.", + "COVER_ALL_SUMMARY": "현재 및 미래의 모든 프로젝트가 선택되었습니다.", + "RESET_PERMISSION": "모든 프로젝트 권한 재설정", + "PERMISSION_COLUMN": "권한", + "EXPIRES_AT": "만료 시간", + "VIEW_SECRET": "시크릿 새로고침", + "LEGACY": "Legacy", + "CREATE_PROJECT_ROBOT": "로봇 계정 생성", + "CREATE_PROJECT_ROBOT_SUMMARY": "이 프로젝트를 위한 로봇 계정 생성", + "EDIT_PROJECT_ROBOT": "로봇 계정 편집", + "EDIT_PROJECT_ROBOT_SUMMARY": "이 프로젝트를 위한 로봇 계정 편집", + "NOT_FOUND": "로봇을 찾을 수 없습니다!", + "SELECT_ALL": "모두 선택", + "UNSELECT_ALL": "모두 선택하지 않음", + "ROBOT_ACCOUNT_NAV": "로봇 계정", + "COVERED_PROJECTS": "프로젝트", + "CONFIRM_SECRET": "시크릿 확인", + "SECRET_AGAIN": "시크릿을 다시 입력하세요", + "INCONSISTENT": "두 시크릿이 일치하지 않습니다", + "NAME_TOOLTIP": "로봇 이름은 1~255자(영문 소문자, 숫자, ._- 포함)여야 하며 문자나 숫자로 시작해야 합니다.", + "ENABLE_NEW_SECRET": "새을 수동으로 지정하려면 이 옵션을 활성화하세요", + "DELETE": "삭제", + "ARTIFACT_LABEL": "아티팩트 라벨", + "SCAN": "스캔", + "SCANNER_PULL": "스캐너 풀(PUll)", + "SEARCH_BY_NAME": "이름으로 검색(접두사 없이)", + "FINAL_PROJECT_NAME_TIP": "최종 프로젝트 로봇 이름은 접두사, 프로젝트 이름, 더하기 기호 및 현재 입력 값으로 구성됩니다.", + "FINAL_SYSTEM_NAME_TIP": "최종 시스템 로봇 이름은 접두어와 현재 입력 값으로 구성됩니다.", + "PUSH_AND_PULL": "푸시", + "PUSH_PERMISSION_TOOLTIP": "푸시 권한은 단독으로 작동할 수 없으며, 풀 권한과 함께 작동해야 합니다.", + "STOP": "중지", + "LIST": "리스트", + "REPOSITORY": "저장소", + "EXPIRES_IN": "만료 시간", + "EXPIRED": "만료 됨", + "SELECT_ALL_PROJECT": "모든 프로젝트 선택", + "UNSELECT_ALL_PROJECT": "모든 프로젝트 선택 취소" + }, + "ACCESSORY": { + "DELETION_TITLE_ACCESSORY": "액세서리 삭제 확인", + "DELETION_SUMMARY_ACCESSORY": "아티팩트의 액세서리{{param}}를 삭제하시겠습니까?", + "DELETION_SUMMARY_ONE_ACCESSORY": "액세서리{{param}}를 삭제하시겠습니까?", + "DELETE_ACCESSORY": "액세서리 삭제", + "DELETED_SUCCESS": "액세서리를 성공적으로 삭제했습니다", + "DELETED_FAILED": "액세서리 삭제를 실패했습니닼", + "CO_SIGNED": "Cosign에 의해 서명됨", + "NOTARY_SIGNED": "Notary에 의해 서명됨", + "ACCESSORY": "액세서리", + "ACCESSORIES": "액세서리들", + "SUBJECT_ARTIFACT": "서브젝트 아티팩트", + "CO_SIGN": "Cosign", + "NOTARY": "Notation", + "PLACEHOLDER": "액세서리를 찾을 수 없습니다!" + }, + "CLEARANCES": { + "CLEARANCES": "정리", + "AUDIT_LOG": "로그 로테이션", + "LAST_COMPLETED": "마지막 성공", + "NEXT_SCHEDULED_TIME": "다음 예약 시간", + "SCHEDULE_TO_PURGE": "제거 예약", + "KEEP_IN": "기록을 보관하세요", + "KEEP_IN_TOOLTIP": "이 간격으로 기록을 보관하세요", + "KEEP_IN_ERROR": "이 항목의 값은 정수여야 하며 시간 값은 0보다 크고 10000일보다 작아야 합니다.", + "DAYS": "일", + "HOURS": "시간", + "INCLUDED_OPERATIONS": "포함된 작업", + "INCLUDED_OPERATION_TOOLTIP": "선택한 작업에 대한 감사 로그 삭제", + "INCLUDED_OPERATION_ERROR": "작업을 하나 이상 선택하세요.", + "PURGE_NOW": "지금 제거", + "PURGE_NOW_SUCCESS": "제거가 성공적으로 발생됐습니다", + "PURGE_SCHEDULE_RESET": "제거 예약이 초기화됐습니다", + "PURGE_HISTORY": "제거 기록", + "FORWARD_ENDPOINT": "감사 로그를 Syslog 엔트포인트로 전달", + "FORWARD_ENDPOINT_TOOLTIP": "감사 로그를 syslog 엔드포인트로 전달합니다(예: harbor-log:10514)", + "SKIP_DATABASE": "감사 로그 데이터베이스 건너뛰기", + "SKIP_DATABASE_TOOLTIP": "데이터베이스의 감사 로그 로그로 건너뛰기, 감사 로그 전달 엔드포인트가 구성된 경우에만 사용 가능", + "STOP_GC_SUCCESS": "가비지 컬렉션 작업 중지를 성공적으로 발생시켰습니다", + "STOP_PURGE_SUCCESS": "제거 중지를 성공적으로 발생시켰습니다", + "NO_GC_RECORDS": "가비지 컬렉션 기록을 찾을 수 없습니다!", + "NO_PURGE_RECORDS": "제거 기록을 찾을 수 없습니다!" + }, + "CVE_EXPORT": { + "EXPORT_SOME_PROJECTS": "CVE 내보내기", + "ALL_PROJECTS": "모든 프로젝트", + "EXPORT_TITLE": "CVE 내보내기", + "EXPORT_SUBTITLE": "내보내기 조건 설정", + "EXPORT_CVE_FILTER_HELP_TEXT": "여러 개의 쉼표로 구분된 cveId를 입력하세요", + "CVE_IDS": "CVE IDs", + "EXPORT_BUTTON": "내보내기", + "JOB_NAME": "작업 이름", + "JOB_NAME_REQUIRED": "작업 이름은 필수 항목입니다", + "JOB_NAME_EXISTING": "작업 이름이 이미 존재합니다", + "TRIGGER_EXPORT_SUCCESS": "CVE 내보내기가 성공적으로 발생됐습니다!" + }, + "JOB_SERVICE_DASHBOARD": { + "SCHEDULE_PAUSED": "예약(일시 중지)", + "SCHEDULE_BEEN_PAUSED": "{{param}} 가 일시 중지 됐습니다", + "PENDING_JOBS": "대기열에 대기중인 작업", + "OTHERS": "기타", + "STOP_ALL": "모두 중지", + "CONFIRM_STOP_ALL": "모두 중지 확인", + "CONFIRM_STOP_ALL_CONTENT": "대기열의 모든 작업을 중지하시겠습니까?", + "STOP_ALL_SUCCESS": "대기열의 모든 작업을 성공적으로 중지했습니다", + "STOP_BTN": "중지", + "PAUSE_BTN": "일시 중지", + "RESUME_BTN": "재개", + "JOB_TYPE": "작업 유형", + "PENDING_COUNT": "대기 수", + "LATENCY": "지연시간", + "PAUSED": "일시 중지", + "NO_JOB_QUEUE": "대기열을 찾을 수 없습니다", + "CONFIRM_STOPPING_JOBS": "작업 중지 확인", + "CONFIRM_STOPPING_JOBS_CONTENT": "{{param}} 작업을 중지하시겠습니까?", + "CONFIRM_PAUSING_JOBS": "작업 일시 중지 확인", + "CONFIRM_PAUSING_JOBS_CONTENT": "{{param}} 작업을 일시 중지 하시겠습니까?", + "CONFIRM_RESUMING_JOBS": "작업 재개 확인", + "CONFIRM_RESUMING_JOBS_CONTENT": "{{param}} 작업을 재개하시겠습니까?", + "STOP_SUCCESS": "작업을 성공적으로 중지했습니다", + "PAUSE_SUCCESS": "작업을 성공적으로 일시 중지 했습니다", + "RESUME_SUCCESS": "작업을 성공적으로 재개했습니다", + "SCHEDULES": "예약", + "RUNNING_STATUS": "실행 중", + "RESUME_ALL_BTN_TEXT": "모두 재개", + "PAUSE_ALL_BTN_TEXT": "모두 일시 중지", + "CONFIRM_PAUSING_ALL": "모든 작업 일시 중지 확인", + "CONFIRM_PAUSING_ALL_CONTENT": "예정된 모든 작업을 일시 중지 하시겠습니까?", + "CONFIRM_RESUMING_ALL": "모든 작업 재개 확인", + "CONFIRM_RESUMING_ALL_CONTENT": "예정된 모든 작업을 재개하시겠습니까?", + "PAUSE_ALL_SUCCESS": "예정된 모든 작업이 성공적으로 일시 중지 됐습니다", + "RESUME_ALL_SUCCESS": "예정된 모든 작업이 성공적으로 재개됐습니다", + "VENDOR_TYPE": "공급자 분류", + "VENDOR_ID": "공급자 ID", + "NO_SCHEDULE": "에약 내역을 찾을 수 없습니다", + "WORKERS": "작업자", + "FREE_ALL": "모두 해제", + "CONFIRM_FREE_ALL": "모두 해제 확인", + "CONFIRM_FREE_ALL_CONTENT": "모든 작업자를 해제하시겠습니까?", + "CONFIRM_FREE_WORKERS": "작업자 해제 확인", + "CONFIRM_FREE_WORKERS_CONTENT": "작업자{{param}}를 해제하시겠습니까?", + "FREE_WORKER_SUCCESS": "작업자를 성공적으로 해제했습니다", + "FREE_ALL_SUCCESS": "모든 작업자를 성공적으로 해제했습니다", + "WORKER_POOL": "작업자 풀(Pool)", + "WORKER_POOL_ID": "작업자 풀(Pool) ID", + "PID": "Pid", + "START_AT": "시작시간", + "HEARTBEAT_AT": "상태 확인시간", + "CONCURRENCY": "동시성", + "NO_WORKER_POOL": "작업자 풀(pool)을 찾을 수 없습니다", + "FREE": "해제", + "WORKER_ID": "작업자 아이디", + "JOB_ID": "작업 아이디", + "CHECK_IN_AT": "체크인 시간", + "NO_WORKER": "작업자(Worker)를 찾을 수 없습니다", + "JOB_QUEUE": "작업 대기열", + "JOB_SERVICE_DASHBOARD": "작업 서비스 대시보드", + "OPERATION_STOP_ALL_QUEUES": "모든 대기열 중지", + "OPERATION_STOP_SPECIFIED_QUEUES": "특정 대기열 중지", + "OPERATION_PAUSE_SPECIFIED_QUEUES": "특정 대기열 일시중지", + "OPERATION_RESUME_SPECIFIED_QUEUES": "특정 대기열 재시작", + "OPERATION_PAUSE_SCHEDULE": "모든 예약 일시정지", + "OPERATION_RESUME_SCHEDULE": "모든 예약 재개", + "OPERATION_FREE_ALL": "모든 작업자 해제", + "OPERATION_FREE_SPECIFIED_WORKERS": "특정 작업자 해제", + "QUEUE_STOP_BTN_INFO": "중지 — 선택된 대기열의 모든 작업을 중지하고 제거합니다.", + "QUEUE_PAUSE_BTN_INFO": "일시 중지 — 이 유형의 작업 대기열에서 작업 실행을 일시 중지합니다. 대기열이 일시 중지되면 작업이 대기열에 추가될 수 있습니다.", + "QUEUE_RESUME_BTN_INFO": "재개 — 이 유형의 작업 대기열에서 작업 실행을 재개합니다.", + "SCHEDULE_PAUSE_BTN_INFO": "일시 중지 — 실행을 위해 모든 일정을 일시 중지합니다.", + "SCHEDULE_RESUME_BTN_INFO": "재개 — 실행할 모든 일정을 재개합니다.", + "WORKER_FREE_BTN_INFO": "현재 실행 중인 작업을 중지하여 작업자를 해제합니다.", + "CRON": "반복 작업", + "WAITING_TOO_LONG_1": "특정 작업의 실행이 24시간 이상 보류되었습니다. 작업 서비스를 확인해주세요", + "WAITING_TOO_LONG_2": "데쉬보드.", + "WAITING_TOO_LONG_3": "자세한 내용은 다음을 확인해주세요", + "WAITING_TOO_LONG_4": "Wiki." + }, + "CLARITY": { + "OPEN": "열기", + "CLOSE": "보기", + "SHOW": "닫기", + "HIDE": "숨기기", + "EXPAND": "확장하기", + "COLLAPSE": "접기", + "MORE": "더 보기", + "SELECT": "선택", + "SELECT_ALL": "모두 선택", + "PREVIOUS": "이전", + "NEXT": "다음", + "CURRENT": "현재로 이동", + "INFO": "정보", + "SUCCESS": "성공", + "WARNING": "경고", + "DANGER": "에러", + "ROW_ACTION": "사용 가능한 동작", + "PICK_COLUMNS": "열(Columns) 관리", + "SHOW_COLUMNS": "열(Columns) 보기", + "SORT_COLUMNS": "열(Column) 정렬", + "FIRST_PAGE": "첫 페이지", + "LAST_PAGE": "마지막 페이지", + "NEXT_PAGE": "다음 페이지", + "PREVIOUS_PAGE": "이전 페이지", + "CURRENT_PAGE": "현재 페이지", + "TOTAL_PAGE": "전체 페이지", + "FILTER_ITEMS": "항목 필터링", + "MIN_VALUE": "최소 값", + "MAX_VALUE": "최대 값", + "MODAL_CONTENT_START": "모달 콘텐츠의 시작", + "MODAL_CONTENT_END": "모달 콘텐츠 끝", + "SHOW_COLUMNS_MENU_DESCRIPTION": "열(Columns) 보기 혹은 가리기", + "ALL_COLUMNS_SELECTED": "모든 열(Columns) 선택", + "SIGNPOST_TOGGLE": "이정표 토글", + "SIGNPOST_CLOSE": "이정표 닫기", + "LOADING": "로딩", + "DATE_PICKER_DIALOG_LABEL": "날짜 선택", + "DATE_PICKER_TOGGLE_CHOOSE_DATE_LABEL": "날짜 선택", + "DATE_PICKER_TOGGLE_CHANGE_DATE_LABEL": "날짜 변경, {SELECTED_DATE}", + "DATE_PICKER_PREVIOUS_MONTH": "이전 달", + "DATE_PICKER_CURRENT_MONTH": "이번 달", + "DATE_PICKER_NEXT_MONTH": "다음 달", + "DATE_PICKER_PREVIOUS_DECADE": "작년", + "DATE_PICKER_NEXT_DECADE": "내년", + "DATE_PICKER_CURRENT_DECADE": "올해", + "DATE_PICKER_SELECT_MONTH_TEXT": "월을 선택하세요. 현재 월은 {CALENDAR_MONTH}입니다", + "DATE_PICKER_SELECT_YEAR_TEXT": "년을 선택해서요. 올해는 {CALENDAR_YEAR}입니다", + "DATE_PICKER_SELECTED_LABEL": "{FULL_DATE} - 선택 됨" + }, + "BANNER_MESSAGE": { + "BANNER_MESSAGE": "베너 메시지", + "MESSAGE_TYPE": "메시지 유형", + "CLOSABLE": "닫을 수 있음", + "FROM": "From", + "TO": "To", + "SUCCESS": "성공", + "INFO": "정보", + "WARNING": "경고", + "DANGER": "위험", + "ENTER_MESSAGE": "여기에 미시지를 임력하세요" + }, + "SECURITY_HUB": { + "SECURITY_HUB": "보안 허브", + "ARTIFACTS": "아티팩트(들)", + "SCANNED": "스캔 됨", + "NOT_SCANNED": "스캔 안됨", + "TOTAL_VUL": "전체 취약점", + "TOTAL_AND_FIXABLE": "{{totalNum}} total with {{fixableNum}} fixable", + "TOP_5_ARTIFACT": "가장 위험한 아티팩트 상위 5개", + "TOP_5_CVE": "가장 위험한 CVE 상위 5개", + "CVE_ID": "CVE ID", + "VUL": "취약점", + "CVE": "CVEs", + "FILTER_BY": "Filter by", + "OPTION_ALL": "전체", + "OPTION_PROJECT_ID_NAME": "프로젝트 이름", + "SEARCH": "검색", + "REPO_NAME": "저장소 이름", + "TOOLTIP": "CVSS3을 제외한 모든 필터는 정확한 일치만 지원합니다.", + "NO_VUL": "취약점을 찾을 수 없습니다.", + "INVALID_VALUE": "CVSS3 점수의 범위는 0에서 10 사이여야 합니다.", + "PAGE_TITLE_TOOLTIP": "포괄적인 아티팩트 수는 아티팩트 액세서리를 포함한 개별 아티팩트의 누적 합계와 이미지 인덱스 및 CNAB 아티팩트와 관련된 하위 아티팩트로 구성됩니다." + } +} From fa01cc5e48892690580c526d2d5aa36ffa85c72d Mon Sep 17 00:00:00 2001 From: Shengwen YU Date: Mon, 25 Mar 2024 07:26:21 +0100 Subject: [PATCH 042/145] fix: scanner tab change (#20128) fix: routing - tc: open image scanners doc page Signed-off-by: Shengwen Yu --- tests/resources/Harbor-Pages/Vulnerability.robot | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/resources/Harbor-Pages/Vulnerability.robot b/tests/resources/Harbor-Pages/Vulnerability.robot index f5f9d9019..b7fb6d430 100644 --- a/tests/resources/Harbor-Pages/Vulnerability.robot +++ b/tests/resources/Harbor-Pages/Vulnerability.robot @@ -66,6 +66,7 @@ Vulnerability Not Ready Project Hint Switch To Scanners Page Retry Element Click xpath=//clr-main-container//clr-vertical-nav//a[contains(.,'Interrogation')] + Retry Element Click xpath=//app-interrogation-services//a[normalize-space()='Scanners'] Retry Wait Until Page Contains Element ${set_default_scanner} Should Display The Default Trivy Scanner From 2eb5464603a9e1745696eb607b74727e9d3d148d Mon Sep 17 00:00:00 2001 From: Wang Yan Date: Mon, 25 Mar 2024 15:02:39 +0800 Subject: [PATCH 043/145] add type for scanner metadata (#20108) Signed-off-by: wang yan --- api/v2.0/swagger.yaml | 6 ++++++ src/pkg/scan/rest/v1/client_test.go | 2 ++ src/pkg/scan/rest/v1/models.go | 3 +++ src/server/v2.0/handler/model/scanner.go | 1 + 4 files changed, 12 insertions(+) diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index c0f3bf596..8f4ffcdaf 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -8450,6 +8450,12 @@ definitions: ScannerCapability: type: object properties: + type: + type: string + description: | + Specify the type of scanner capability, like vulnerability or sbom + x-omitempty: false + example: "sbom" consumes_mime_types: type: array items: diff --git a/src/pkg/scan/rest/v1/client_test.go b/src/pkg/scan/rest/v1/client_test.go index e3ee8ae49..ee3435066 100644 --- a/src/pkg/scan/rest/v1/client_test.go +++ b/src/pkg/scan/rest/v1/client_test.go @@ -58,6 +58,7 @@ func (suite *ClientTestSuite) TestClientMetadata() { require.NotNil(suite.T(), m) assert.Equal(suite.T(), m.Scanner.Name, "Trivy") + assert.Equal(suite.T(), m.Capabilities[0].Type, "sbom") } // TestClientSubmitScan tests the scan submission of client @@ -119,6 +120,7 @@ func (mh *mockHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { Version: "0.1.0", }, Capabilities: []*ScannerCapability{{ + Type: "sbom", ConsumesMimeTypes: []string{ MimeTypeOCIArtifact, MimeTypeDockerArtifact, diff --git a/src/pkg/scan/rest/v1/models.go b/src/pkg/scan/rest/v1/models.go index 50de0cbf5..d7dce069e 100644 --- a/src/pkg/scan/rest/v1/models.go +++ b/src/pkg/scan/rest/v1/models.go @@ -37,6 +37,7 @@ type Scanner struct { // report MIME types. For example, a scanner capable of analyzing Docker images and producing // a vulnerabilities report recognizable by Harbor web console might be represented with the // following capability: +// - type: vulnerability // - consumes MIME types: // -- application/vnd.oci.image.manifest.v1+json // -- application/vnd.docker.distribution.manifest.v2+json @@ -44,6 +45,8 @@ type Scanner struct { // -- application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0 // -- application/vnd.scanner.adapter.vuln.report.raw type ScannerCapability struct { + // The type of the scanner capability, vulnerability or sbom + Type string `json:"type"` // The set of MIME types of the artifacts supported by the scanner to produce the reports // specified in the "produces_mime_types". A given mime type should only be present in one // capability item. diff --git a/src/server/v2.0/handler/model/scanner.go b/src/server/v2.0/handler/model/scanner.go index 34d7e6b92..bb140937f 100644 --- a/src/server/v2.0/handler/model/scanner.go +++ b/src/server/v2.0/handler/model/scanner.go @@ -74,6 +74,7 @@ func (s *ScannerMetadata) ToSwagger(_ context.Context) *models.ScannerAdapterMet var capabilities []*models.ScannerCapability for _, c := range s.Capabilities { capabilities = append(capabilities, &models.ScannerCapability{ + Type: c.Type, ConsumesMimeTypes: c.ConsumesMimeTypes, ProducesMimeTypes: c.ProducesMimeTypes, }) From 80a9c688fcf63788d8930b6a372df57f6876347d Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Tue, 26 Mar 2024 12:52:17 +0800 Subject: [PATCH 044/145] panic due to mark retention task error (#20161) panic due to mark retention task error fixes #20129 Signed-off-by: stonezdj --- src/controller/replication/execution.go | 7 ++--- src/controller/replication/execution_test.go | 3 +- src/controller/retention/controller.go | 8 ++--- src/controller/systemartifact/execution.go | 7 ++--- .../systemartifact/execution_test.go | 2 +- src/pkg/task/dao/task.go | 27 ++++++++++++----- src/pkg/task/dao/task_test.go | 30 +++++++++++++++++-- src/pkg/task/execution.go | 12 ++++++++ src/testing/pkg/task/execution_manager.go | 14 +++++++++ 9 files changed, 81 insertions(+), 29 deletions(-) diff --git a/src/controller/replication/execution.go b/src/controller/replication/execution.go index f6facc02a..95136d9d8 100644 --- a/src/controller/replication/execution.go +++ b/src/controller/replication/execution.go @@ -154,11 +154,8 @@ func (c *controller) Start(ctx context.Context, policy *replicationmodel.Policy, func (c *controller) markError(ctx context.Context, executionID int64, err error) { logger := log.GetLogger(ctx) // try to stop the execution first in case that some tasks are already created - if err := c.execMgr.StopAndWait(ctx, executionID, 10*time.Second); err != nil { - logger.Errorf("failed to stop the execution %d: %v", executionID, err) - } - if err := c.execMgr.MarkError(ctx, executionID, err.Error()); err != nil { - logger.Errorf("failed to mark error for the execution %d: %v", executionID, err) + if e := c.execMgr.StopAndWaitWithError(ctx, executionID, 10*time.Second, err); e != nil { + logger.Errorf("failed to stop the execution %d: %v", executionID, e) } } diff --git a/src/controller/replication/execution_test.go b/src/controller/replication/execution_test.go index 62b0dd549..de4791f09 100644 --- a/src/controller/replication/execution_test.go +++ b/src/controller/replication/execution_test.go @@ -75,8 +75,7 @@ func (r *replicationTestSuite) TestStart() { // got error when running the replication flow r.execMgr.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil) r.execMgr.On("Get", mock.Anything, mock.Anything).Return(&task.Execution{}, nil) - r.execMgr.On("StopAndWait", mock.Anything, mock.Anything, mock.Anything).Return(nil) - r.execMgr.On("MarkError", mock.Anything, mock.Anything, mock.Anything).Return(nil) + r.execMgr.On("StopAndWaitWithError", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) r.flowCtl.On("Start", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("error")) r.ormCreator.On("Create").Return(nil) id, err = r.ctl.Start(context.Background(), &repctlmodel.Policy{Enabled: true}, nil, task.ExecutionTriggerManual) diff --git a/src/controller/retention/controller.go b/src/controller/retention/controller.go index 4ec607a40..179d8c151 100644 --- a/src/controller/retention/controller.go +++ b/src/controller/retention/controller.go @@ -280,12 +280,8 @@ func (r *defaultController) TriggerRetentionExec(ctx context.Context, policyID i if num, err := r.launcher.Launch(ctx, p, id, dryRun); err != nil { logger.Errorf("failed to launch the retention jobs, err: %v", err) - if err = r.execMgr.StopAndWait(ctx, id, 10*time.Second); err != nil { - logger.Errorf("failed to stop the retention execution %d: %v", id, err) - } - - if err = r.execMgr.MarkError(ctx, id, err.Error()); err != nil { - logger.Errorf("failed to mark error for the retention execution %d: %v", id, err) + if e := r.execMgr.StopAndWaitWithError(ctx, id, 10*time.Second, err); e != nil { + logger.Errorf("failed to stop the retention execution %d: %v", id, e) } } else if num == 0 { // no candidates, mark the execution as done directly diff --git a/src/controller/systemartifact/execution.go b/src/controller/systemartifact/execution.go index c9f6c896d..7a575e076 100644 --- a/src/controller/systemartifact/execution.go +++ b/src/controller/systemartifact/execution.go @@ -119,11 +119,8 @@ func (c *controller) createCleanupTask(ctx context.Context, jobParams job.Parame func (c *controller) markError(ctx context.Context, executionID int64, err error) { // try to stop the execution first in case that some tasks are already created - if err := c.execMgr.StopAndWait(ctx, executionID, 10*time.Second); err != nil { - log.Errorf("failed to stop the execution %d: %v", executionID, err) - } - if err := c.execMgr.MarkError(ctx, executionID, err.Error()); err != nil { - log.Errorf("failed to mark error for the execution %d: %v", executionID, err) + if e := c.execMgr.StopAndWaitWithError(ctx, executionID, 10*time.Second, err); e != nil { + log.Errorf("failed to stop the execution %d: %v", executionID, e) } } diff --git a/src/controller/systemartifact/execution_test.go b/src/controller/systemartifact/execution_test.go index 68ba21191..7656fae37 100644 --- a/src/controller/systemartifact/execution_test.go +++ b/src/controller/systemartifact/execution_test.go @@ -117,7 +117,7 @@ func (suite *SystemArtifactCleanupTestSuite) TestStartCleanupErrorDuringTaskCrea suite.taskMgr.On("Create", ctx, executionID, mock.Anything).Return(taskId, errors.New("test error")).Once() suite.execMgr.On("MarkError", ctx, executionID, mock.Anything).Return(nil).Once() - suite.execMgr.On("StopAndWait", ctx, executionID, mock.Anything).Return(nil).Once() + suite.execMgr.On("StopAndWaitWithError", ctx, executionID, mock.Anything, mock.Anything).Return(nil).Once() err := suite.ctl.Start(ctx, false, "SCHEDULE") suite.Error(err) diff --git a/src/pkg/task/dao/task.go b/src/pkg/task/dao/task.go index de3c71bd2..177738561 100644 --- a/src/pkg/task/dao/task.go +++ b/src/pkg/task/dao/task.go @@ -25,6 +25,8 @@ import ( "github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/orm" "github.com/goharbor/harbor/src/lib/q" + + "github.com/google/uuid" ) // TaskDAO is the data access object interface for task @@ -91,22 +93,33 @@ func (t *taskDAO) List(ctx context.Context, query *q.Query) ([]*Task, error) { return tasks, nil } +func isValidUUID(id string) bool { + if len(id) == 0 { + return false + } + if _, err := uuid.Parse(id); err != nil { + return false + } + return true +} + func (t *taskDAO) ListScanTasksByReportUUID(ctx context.Context, uuid string) ([]*Task, error) { ormer, err := orm.FromContext(ctx) if err != nil { return nil, err } - tasks := []*Task{} - // Due to the limitation of the beego's orm, the SQL cannot be converted by orm framework, - // so we can only execute the query by raw SQL, the SQL filters the task contains the report uuid in the column extra_attrs, - // consider from performance side which can using indexes to speed up queries. - sql := fmt.Sprintf(`SELECT * FROM task WHERE extra_attrs::jsonb->'report_uuids' @> '["%s"]'`, uuid) - _, err = ormer.Raw(sql).QueryRows(&tasks) + if !isValidUUID(uuid) { + return nil, errors.BadRequestError(fmt.Errorf("invalid UUID %v", uuid)) + } + + var tasks []*Task + param := fmt.Sprintf(`{"report_uuids":["%s"]}`, uuid) + sql := `SELECT * FROM task WHERE extra_attrs::jsonb @> cast( ? as jsonb )` + _, err = ormer.Raw(sql, param).QueryRows(&tasks) if err != nil { return nil, err } - return tasks, nil } diff --git a/src/pkg/task/dao/task_test.go b/src/pkg/task/dao/task_test.go index aeca41a1c..58a41e551 100644 --- a/src/pkg/task/dao/task_test.go +++ b/src/pkg/task/dao/task_test.go @@ -113,8 +113,9 @@ func (t *taskDAOTestSuite) TestList() { } func (t *taskDAOTestSuite) TestListScanTasksByReportUUID() { + reportUUID := `7f20b1b9-6117-4a2e-820b-e4cc0401f15e` // should not exist if non set - tasks, err := t.taskDAO.ListScanTasksByReportUUID(t.ctx, "fake-report-uuid") + tasks, err := t.taskDAO.ListScanTasksByReportUUID(t.ctx, reportUUID) t.Require().Nil(err) t.Require().Len(tasks, 0) // create one with report uuid @@ -122,12 +123,12 @@ func (t *taskDAOTestSuite) TestListScanTasksByReportUUID() { ExecutionID: t.executionID, Status: "success", StatusCode: 1, - ExtraAttrs: `{"report_uuids": ["fake-report-uuid"]}`, + ExtraAttrs: fmt.Sprintf(`{"report_uuids": ["%s"]}`, reportUUID), }) t.Require().Nil(err) defer t.taskDAO.Delete(t.ctx, taskID) // should exist as created - tasks, err = t.taskDAO.ListScanTasksByReportUUID(t.ctx, "fake-report-uuid") + tasks, err = t.taskDAO.ListScanTasksByReportUUID(t.ctx, reportUUID) t.Require().Nil(err) t.Require().Len(tasks, 1) t.Equal(taskID, tasks[0].ID) @@ -299,6 +300,29 @@ func (t *taskDAOTestSuite) TestExecutionIDsByVendorAndStatus() { defer t.taskDAO.Delete(t.ctx, tid) } +func TestIsValidUUID(t *testing.T) { + tests := []struct { + name string + uuid string + expected bool + }{ + {"Valid UUID", "7f20b1b9-6117-4a2e-820b-e4cc0401f15f", true}, + {"Invalid UUID - Short", "7f20b1b9-6117-4a2e-820b", false}, + {"Invalid UUID - Long", "7f20b1b9-6117-4a2e-820b-e4cc0401f15f-extra", false}, + {"Invalid UUID - Invalid Characters", "7f20b1b9-6117-4z2e-820b-e4cc0401f15f", false}, + {"Empty String", "", false}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := isValidUUID(test.uuid) + if result != test.expected { + t.Errorf("Expected isValidUUID(%s) to be %t, got %t", test.uuid, test.expected, result) + } + }) + } +} + func TestTaskDAOSuite(t *testing.T) { suite.Run(t, &taskDAOTestSuite{}) } diff --git a/src/pkg/task/execution.go b/src/pkg/task/execution.go index 49fee7f96..e2160cd89 100644 --- a/src/pkg/task/execution.go +++ b/src/pkg/task/execution.go @@ -59,6 +59,8 @@ type ExecutionManager interface { // StopAndWait stops all linked tasks of the specified execution and waits until all tasks are stopped // or get an error StopAndWait(ctx context.Context, id int64, timeout time.Duration) (err error) + // StopAndWaitWithError calls the StopAndWait first, if it doesn't return error, then it call MarkError if the origError is not empty + StopAndWaitWithError(ctx context.Context, id int64, timeout time.Duration, origError error) (err error) // Delete the specified execution and its tasks Delete(ctx context.Context, id int64) (err error) // Delete all executions and tasks of the specific vendor. They can be deleted only when all the executions/tasks @@ -250,6 +252,16 @@ func (e *executionManager) StopAndWait(ctx context.Context, id int64, timeout ti } } +func (e *executionManager) StopAndWaitWithError(ctx context.Context, id int64, timeout time.Duration, origError error) error { + if err := e.StopAndWait(ctx, id, timeout); err != nil { + return err + } + if origError != nil { + return e.MarkError(ctx, id, origError.Error()) + } + return nil +} + func (e *executionManager) Delete(ctx context.Context, id int64) error { tasks, err := e.taskDAO.List(ctx, &q.Query{ Keywords: map[string]interface{}{ diff --git a/src/testing/pkg/task/execution_manager.go b/src/testing/pkg/task/execution_manager.go index 17e912fcc..890aa6d4b 100644 --- a/src/testing/pkg/task/execution_manager.go +++ b/src/testing/pkg/task/execution_manager.go @@ -209,6 +209,20 @@ func (_m *ExecutionManager) StopAndWait(ctx context.Context, id int64, timeout t return r0 } +// StopAndWaitWithError provides a mock function with given fields: ctx, id, timeout, origError +func (_m *ExecutionManager) StopAndWaitWithError(ctx context.Context, id int64, timeout time.Duration, origError error) error { + ret := _m.Called(ctx, id, timeout, origError) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int64, time.Duration, error) error); ok { + r0 = rf(ctx, id, timeout, origError) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // UpdateExtraAttrs provides a mock function with given fields: ctx, id, extraAttrs func (_m *ExecutionManager) UpdateExtraAttrs(ctx context.Context, id int64, extraAttrs map[string]interface{}) error { ret := _m.Called(ctx, id, extraAttrs) From fd81e7c43eb45073ba9ce930c154550e8c52d8e6 Mon Sep 17 00:00:00 2001 From: James Kang <164518010+majorteach@users.noreply.github.com> Date: Tue, 26 Mar 2024 13:53:44 +0800 Subject: [PATCH 045/145] chore: fix function names (#20159) Signed-off-by: majorteach Co-authored-by: Wang Yan --- src/pkg/joblog/manager.go | 2 +- src/server/v2.0/handler/model/webhook_policy.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pkg/joblog/manager.go b/src/pkg/joblog/manager.go index 78ec677ea..991559880 100644 --- a/src/pkg/joblog/manager.go +++ b/src/pkg/joblog/manager.go @@ -56,7 +56,7 @@ func (m *manager) Create(ctx context.Context, jobLog *models.JobLog) (id int64, return m.dao.Create(ctx, jobLog) } -// DeleteJobLogsBefore ... +// DeleteBefore ... func (m *manager) DeleteBefore(ctx context.Context, t time.Time) (id int64, err error) { return m.dao.DeleteBefore(ctx, t) } diff --git a/src/server/v2.0/handler/model/webhook_policy.go b/src/server/v2.0/handler/model/webhook_policy.go index 0443df220..a0cded590 100644 --- a/src/server/v2.0/handler/model/webhook_policy.go +++ b/src/server/v2.0/handler/model/webhook_policy.go @@ -57,7 +57,7 @@ func (n *WebhookPolicy) ToTargets() []*models.WebhookTargetObject { return results } -// NewNotifiactionPolicy ... +// NewWebhookPolicy ... func NewWebhookPolicy(p *model.Policy) *WebhookPolicy { return &WebhookPolicy{ Policy: p, From aa4a142bc16447867487def0d0b6144fac57c196 Mon Sep 17 00:00:00 2001 From: Lichao Xue <68891670+xuelichao@users.noreply.github.com> Date: Tue, 26 Mar 2024 14:36:18 +0800 Subject: [PATCH 046/145] Add two columns to display capability type for scanner (#20111) Signed-off-by: xuelichao Signed-off-by: Lichao Xue <68891670+xuelichao@users.noreply.github.com> Co-authored-by: Wang Yan --- .../scanner/config-scanner.component.html | 30 +++++++++++++++++-- .../scanner/config-scanner.component.scss | 4 +++ .../scanner/config-scanner.component.ts | 11 +++++++ src/portal/src/i18n/lang/de-de-lang.json | 4 +++ src/portal/src/i18n/lang/en-us-lang.json | 4 +++ src/portal/src/i18n/lang/es-es-lang.json | 4 +++ src/portal/src/i18n/lang/fr-fr-lang.json | 4 +++ src/portal/src/i18n/lang/pt-br-lang.json | 4 +++ src/portal/src/i18n/lang/tr-tr-lang.json | 4 +++ src/portal/src/i18n/lang/zh-cn-lang.json | 4 +++ src/portal/src/i18n/lang/zh-tw-lang.json | 4 +++ 11 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.component.html b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.component.html index d60260878..d25f74c39 100644 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.component.html +++ b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.component.html @@ -139,9 +139,21 @@ {{ 'SCANNER.ENDPOINT' | translate }} - {{ 'SCANNER.HEALTH' | translate }} - {{ 'SCANNER.ENABLED' | translate }} - {{ 'SCANNER.AUTH' | translate }} + {{ + 'SCANNER.HEALTH' | translate + }} + {{ + 'SCANNER.ENABLED' | translate + }} + {{ + 'SCANNER.AUTH' | translate + }} + {{ + 'SCANNER.VULNERABILITY' | translate + }} + {{ + 'SCANNER.SBOM' | translate + }} {{ 'SCANNER.DESCRIPTION' | translate }} @@ -198,6 +210,18 @@ {{ scanner.auth ? scanner.auth : 'None' }} + {{ + (supportCapability(scanner, 'vulnerability') + ? 'SCANNER.SUPPORTED' + : 'SCANNER.NOT_SUPPORTED' + ) | translate + }} + {{ + (supportCapability(scanner, 'sbom') + ? 'SCANNER.SUPPORTED' + : 'SCANNER.NOT_SUPPORTED' + ) | translate + }} {{ scanner.description }} type === capabilityType + ) ?? [] + ).length >= 1 + : false; + } + changeStat() { if (this.selectedRow) { let scanner: ScannerRegistrationReq = clone(this.selectedRow); diff --git a/src/portal/src/i18n/lang/de-de-lang.json b/src/portal/src/i18n/lang/de-de-lang.json index 409668644..acf0e8fdb 100644 --- a/src/portal/src/i18n/lang/de-de-lang.json +++ b/src/portal/src/i18n/lang/de-de-lang.json @@ -1433,6 +1433,10 @@ "NAME_REQUIRED": "Name ist erforderlich", "NAME_REX": "Der Name muss mindestens 2 Zeichen lang sein. Mit Kleinbuchstaben, Ziffern und ._-. Er muss mit Buchstaben oder Ziffern beginnen.", "DESCRIPTION": "Beschreibung", + "SBOM": "SBOM", + "VULNERABILITY": "Vulnerability", + "SUPPORTED": "Supported", + "NOT_SUPPORTED": "Not Supported", "ENDPOINT": "Endpunkt", "ENDPOINT_EXISTS": "URL existiert bereits", "ENDPOINT_REQUIRED": "URL ist erforderlich", diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json index 3fdca65f9..243ceee98 100644 --- a/src/portal/src/i18n/lang/en-us-lang.json +++ b/src/portal/src/i18n/lang/en-us-lang.json @@ -1434,6 +1434,10 @@ "NAME_REQUIRED": "Name is required", "NAME_REX": "Name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.", "DESCRIPTION": "Description", + "SBOM": "SBOM", + "VULNERABILITY": "Vulnerability", + "SUPPORTED": "Supported", + "NOT_SUPPORTED": "Not Supported", "ENDPOINT": "Endpoint", "ENDPOINT_EXISTS": "EndpointUrl already exists", "ENDPOINT_REQUIRED": "EndpointUrl is required", diff --git a/src/portal/src/i18n/lang/es-es-lang.json b/src/portal/src/i18n/lang/es-es-lang.json index 7b0055b53..cce410e54 100644 --- a/src/portal/src/i18n/lang/es-es-lang.json +++ b/src/portal/src/i18n/lang/es-es-lang.json @@ -1430,6 +1430,10 @@ "NAME_REQUIRED": "Name is required", "NAME_REX": "Name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.", "DESCRIPTION": "Description", + "SBOM": "SBOM", + "VULNERABILITY": "Vulnerability", + "SUPPORTED": "Supported", + "NOT_SUPPORTED": "Not Supported", "ENDPOINT": "Endpoint", "ENDPOINT_EXISTS": "EndpointUrl already exists", "ENDPOINT_REQUIRED": "EndpointUrl is required", diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json index df74ba9ee..035bb3a05 100644 --- a/src/portal/src/i18n/lang/fr-fr-lang.json +++ b/src/portal/src/i18n/lang/fr-fr-lang.json @@ -1431,6 +1431,10 @@ "NAME_REQUIRED": "Le nom est requis", "NAME_REX": "Le nom doit comporter au moins 2 caractères avec des minuscules, des chiffres et. _- et doit commencer par des caractères ou des chiffres.", "DESCRIPTION": "Description", + "SBOM": "SBOM", + "VULNERABILITY": "Vulnerability", + "SUPPORTED": "Supported", + "NOT_SUPPORTED": "Not Supported", "ENDPOINT": "Endpoint", "ENDPOINT_EXISTS": "L'URL de l'endpoint existe déjà", "ENDPOINT_REQUIRED": "L'URL de l'endpoint est requise", diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json index 7dcdaf6bb..31243b5c3 100644 --- a/src/portal/src/i18n/lang/pt-br-lang.json +++ b/src/portal/src/i18n/lang/pt-br-lang.json @@ -1430,6 +1430,10 @@ "NAME_REQUIRED": "Nome é obrigatório", "NAME_REX": "O nome precisa ter no mínimo 2 caracteres. Pode conter apenas letras minúsculas, numeros, ou símbolos ._- e deve começar por uma letra ou número.", "DESCRIPTION": "Descrição", + "SBOM": "SBOM", + "VULNERABILITY": "Vulnerability", + "SUPPORTED": "Supported", + "NOT_SUPPORTED": "Not Supported", "ENDPOINT": "Endereço", "ENDPOINT_EXISTS": "Endereço já usado por outro examinador", "ENDPOINT_REQUIRED": "Endereço é obrigatório", diff --git a/src/portal/src/i18n/lang/tr-tr-lang.json b/src/portal/src/i18n/lang/tr-tr-lang.json index f4538bb8c..82be0f11f 100644 --- a/src/portal/src/i18n/lang/tr-tr-lang.json +++ b/src/portal/src/i18n/lang/tr-tr-lang.json @@ -1433,6 +1433,10 @@ "NAME_REQUIRED": "Name is required", "NAME_REX": "Name should be at least 2 characters long with lower case characters, numbers and ._- and must be start with characters or numbers.", "DESCRIPTION": "Description", + "SBOM": "SBOM", + "VULNERABILITY": "Vulnerability", + "SUPPORTED": "Supported", + "NOT_SUPPORTED": "Not Supported", "ENDPOINT": "Endpoint", "ENDPOINT_EXISTS": "EndpointUrl already exists", "ENDPOINT_REQUIRED": "EndpointUrl is required", diff --git a/src/portal/src/i18n/lang/zh-cn-lang.json b/src/portal/src/i18n/lang/zh-cn-lang.json index 80b355fef..b738a3fdf 100644 --- a/src/portal/src/i18n/lang/zh-cn-lang.json +++ b/src/portal/src/i18n/lang/zh-cn-lang.json @@ -1429,6 +1429,10 @@ "NAME_REQUIRED": "名称为必填项", "NAME_REX": "名称由小写字符、数字和._-组成且至少2个字符并以字符或者数字开头。", "DESCRIPTION": "描述", + "SBOM": "SBOM", + "VULNERABILITY": "Vulnerability", + "SUPPORTED": "Supported", + "NOT_SUPPORTED": "Not Supported", "ENDPOINT": "地址", "ENDPOINT_EXISTS": "地址已存在", "ENDPOINT_REQUIRED": "地址为必填项", diff --git a/src/portal/src/i18n/lang/zh-tw-lang.json b/src/portal/src/i18n/lang/zh-tw-lang.json index 1f81aa489..4553e3445 100644 --- a/src/portal/src/i18n/lang/zh-tw-lang.json +++ b/src/portal/src/i18n/lang/zh-tw-lang.json @@ -1428,6 +1428,10 @@ "NAME_REQUIRED": "名稱必填", "NAME_REX": "名稱必須由小寫字母、數字和 ._- 組成,至少2個字元,並且必須以字母或數字開頭。", "DESCRIPTION": "描述", + "SBOM": "SBOM", + "VULNERABILITY": "Vulnerability", + "SUPPORTED": "Supported", + "NOT_SUPPORTED": "Not Supported", "ENDPOINT": "端點", "ENDPOINT_EXISTS": "端點 URL 已存在", "ENDPOINT_REQUIRED": "端點 URL 必填", From 06f53368cd7b4465a440985d3acb1dfaaef1aeb4 Mon Sep 17 00:00:00 2001 From: Todd Whiteman Date: Wed, 27 Mar 2024 00:15:14 -0700 Subject: [PATCH 047/145] ScanAll should only log an error when an error occurs (#20087) Signed-off-by: Todd Whiteman Co-authored-by: Wang Yan --- src/controller/scan/base_controller.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/controller/scan/base_controller.go b/src/controller/scan/base_controller.go index 143687516..e79896959 100644 --- a/src/controller/scan/base_controller.go +++ b/src/controller/scan/base_controller.go @@ -379,7 +379,9 @@ func (bc *basicController) ScanAll(ctx context.Context, trigger string, async bo } err = bc.startScanAll(ctx, executionID) - log.Errorf("failed to start scan all, executionID=%d, error: %v", executionID, err) + if err != nil { + log.Errorf("failed to start scan all, executionID=%d, error: %v", executionID, err) + } }(bc.makeCtx()) } else { if err := bc.startScanAll(ctx, executionID); err != nil { From b9659b455bbf51693861004fc7983110b6c27f84 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Mar 2024 07:02:17 +0000 Subject: [PATCH 048/145] Bump express from 4.18.2 to 4.19.2 in /src/portal (#20167) Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2. - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/master/History.md) - [Commits](https://github.com/expressjs/express/compare/4.18.2...4.19.2) --- updated-dependencies: - dependency-name: express dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- src/portal/package-lock.json | 34 +++++++++++++++++----------------- src/portal/package.json | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/portal/package-lock.json b/src/portal/package-lock.json index d39be637c..aa3796d1a 100644 --- a/src/portal/package-lock.json +++ b/src/portal/package-lock.json @@ -53,7 +53,7 @@ "eslint": "^8.41.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", - "express": "^4.17.1", + "express": "^4.19.2", "https-proxy-agent": "^5.0.1", "jasmine-core": "~4.5.0", "jasmine-spec-reporter": "~7.0.0", @@ -6280,13 +6280,13 @@ "dev": true }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dev": true, "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -6294,7 +6294,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -7219,9 +7219,9 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "dev": true, "engines": { "node": ">= 0.6" @@ -9741,17 +9741,17 @@ "dev": true }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dev": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -16336,9 +16336,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, "dependencies": { "bytes": "3.1.2", diff --git a/src/portal/package.json b/src/portal/package.json index 791516e3d..14a48ff88 100644 --- a/src/portal/package.json +++ b/src/portal/package.json @@ -71,7 +71,7 @@ "eslint": "^8.41.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", - "express": "^4.17.1", + "express": "^4.19.2", "https-proxy-agent": "^5.0.1", "jasmine-core": "~4.5.0", "jasmine-spec-reporter": "~7.0.0", From d58172c11218dd569c4366e23a34f72b12378123 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Mar 2024 17:01:37 +0800 Subject: [PATCH 049/145] Bump github.com/tencentcloud/tencentcloud-sdk-go from 1.0.62 to 3.0.233+incompatible in /src (#20035) Bump github.com/tencentcloud/tencentcloud-sdk-go in /src Bumps [github.com/tencentcloud/tencentcloud-sdk-go](https://github.com/tencentcloud/tencentcloud-sdk-go) from 1.0.62 to 3.0.233+incompatible. - [Commits](https://github.com/tencentcloud/tencentcloud-sdk-go/commits) --- updated-dependencies: - dependency-name: github.com/tencentcloud/tencentcloud-sdk-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- src/go.mod | 8 ++++---- src/go.sum | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/go.mod b/src/go.mod index 07ff72277..21f322ed2 100644 --- a/src/go.mod +++ b/src/go.mod @@ -21,12 +21,12 @@ require ( github.com/go-asn1-ber/asn1-ber v1.5.5 github.com/go-ldap/ldap/v3 v3.4.6 github.com/go-openapi/errors v0.21.0 - github.com/go-openapi/loads v0.21.2 + github.com/go-openapi/loads v0.21.2 // indirect github.com/go-openapi/runtime v0.26.2 - github.com/go-openapi/spec v0.20.11 + github.com/go-openapi/spec v0.20.11 // indirect github.com/go-openapi/strfmt v0.22.0 github.com/go-openapi/swag v0.22.7 - github.com/go-openapi/validate v0.22.3 + github.com/go-openapi/validate v0.22.3 // indirect 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 @@ -52,7 +52,7 @@ require ( github.com/robfig/cron/v3 v3.0.1 github.com/spf13/viper v1.8.1 github.com/stretchr/testify v1.8.4 - github.com/tencentcloud/tencentcloud-sdk-go v1.0.62 + github.com/tencentcloud/tencentcloud-sdk-go v3.0.233+incompatible github.com/vmihailenco/msgpack/v5 v5.4.1 github.com/volcengine/volcengine-go-sdk v1.0.97 go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.46.1 diff --git a/src/go.sum b/src/go.sum index b8532ba34..c3735aac1 100644 --- a/src/go.sum +++ b/src/go.sum @@ -611,8 +611,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tencentcloud/tencentcloud-sdk-go v1.0.62 h1:Vnr3IqaafEuQUciG6D6EaeLJm26Mg8sjAfbI4OoeauM= -github.com/tencentcloud/tencentcloud-sdk-go v1.0.62/go.mod h1:asUz5BPXxgoPGaRgZaVm1iGcUAuHyYUo1nXqKa83cvI= +github.com/tencentcloud/tencentcloud-sdk-go v3.0.233+incompatible h1:q+D/Y9jla3afgsIihtyhwyl0c2W+eRWNM9ohVwPiiPw= +github.com/tencentcloud/tencentcloud-sdk-go v3.0.233+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= From 8b8b88d86a530e8a21d9afc69224330c3a8ad9e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Mar 2024 17:46:37 +0800 Subject: [PATCH 050/145] Bump golang.org/x/sync from 0.3.0 to 0.6.0 in /src (#20036) Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.3.0 to 0.6.0. - [Commits](https://github.com/golang/sync/compare/v0.3.0...v0.6.0) --- updated-dependencies: - dependency-name: golang.org/x/sync dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- src/go.mod | 2 +- src/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/go.mod b/src/go.mod index 21f322ed2..d51a68f30 100644 --- a/src/go.mod +++ b/src/go.mod @@ -66,7 +66,7 @@ require ( golang.org/x/crypto v0.17.0 golang.org/x/net v0.17.0 golang.org/x/oauth2 v0.13.0 - golang.org/x/sync v0.3.0 + golang.org/x/sync v0.6.0 golang.org/x/text v0.14.0 golang.org/x/time v0.5.0 gopkg.in/h2non/gock.v1 v1.1.2 diff --git a/src/go.sum b/src/go.sum index c3735aac1..4fab88c93 100644 --- a/src/go.sum +++ b/src/go.sum @@ -780,8 +780,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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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= From 4acde986a9463d9af0e486bfeac8a782978e640f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Mar 2024 18:24:03 +0800 Subject: [PATCH 051/145] Bump github.com/go-jose/go-jose/v3 from 3.0.1 to 3.0.3 in /src (#20104) Bumps [github.com/go-jose/go-jose/v3](https://github.com/go-jose/go-jose) from 3.0.1 to 3.0.3. - [Release notes](https://github.com/go-jose/go-jose/releases) - [Changelog](https://github.com/go-jose/go-jose/blob/v3.0.3/CHANGELOG.md) - [Commits](https://github.com/go-jose/go-jose/compare/v3.0.1...v3.0.3) --- updated-dependencies: - dependency-name: github.com/go-jose/go-jose/v3 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- src/go.mod | 8 ++++---- src/go.sum | 17 ++++++++--------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/go.mod b/src/go.mod index d51a68f30..72d7d023d 100644 --- a/src/go.mod +++ b/src/go.mod @@ -63,7 +63,7 @@ require ( go.opentelemetry.io/otel/sdk v1.21.0 go.opentelemetry.io/otel/trace v1.23.1 go.uber.org/ratelimit v0.2.0 - golang.org/x/crypto v0.17.0 + golang.org/x/crypto v0.19.0 golang.org/x/net v0.17.0 golang.org/x/oauth2 v0.13.0 golang.org/x/sync v0.6.0 @@ -107,7 +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.4.9 // indirect - github.com/go-jose/go-jose/v3 v3.0.1 // indirect + github.com/go-jose/go-jose/v3 v3.0.3 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.21.4 // indirect @@ -171,8 +171,8 @@ require ( go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.19.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/term v0.15.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/term v0.17.0 // indirect google.golang.org/api v0.126.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8 // indirect diff --git a/src/go.sum b/src/go.sum index 4fab88c93..cddaa1104 100644 --- a/src/go.sum +++ b/src/go.sum @@ -172,8 +172,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= -github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= +github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= +github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A= @@ -697,7 +697,6 @@ golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaE golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= @@ -708,8 +707,8 @@ golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -822,16 +821,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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.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.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 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= From 9beede0d8244870a3dd797c00616d183422e64f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Mar 2024 11:02:43 +0000 Subject: [PATCH 052/145] Bump github.com/cloudevents/sdk-go/v2 from 2.14.0 to 2.15.2 in /src (#20099) Bumps [github.com/cloudevents/sdk-go/v2](https://github.com/cloudevents/sdk-go) from 2.14.0 to 2.15.2. - [Release notes](https://github.com/cloudevents/sdk-go/releases) - [Commits](https://github.com/cloudevents/sdk-go/compare/v2.14.0...v2.15.2) --- updated-dependencies: - dependency-name: github.com/cloudevents/sdk-go/v2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- src/go.mod | 2 +- src/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/go.mod b/src/go.mod index 72d7d023d..f303c5557 100644 --- a/src/go.mod +++ b/src/go.mod @@ -13,7 +13,7 @@ require ( github.com/bmatcuk/doublestar v1.3.4 github.com/casbin/casbin v1.9.1 github.com/cenkalti/backoff/v4 v4.2.1 - github.com/cloudevents/sdk-go/v2 v2.14.0 + github.com/cloudevents/sdk-go/v2 v2.15.2 github.com/coreos/go-oidc/v3 v3.9.0 github.com/dghubble/sling v1.1.0 github.com/docker/distribution v2.8.2+incompatible diff --git a/src/go.sum b/src/go.sum index cddaa1104..3b0d91162 100644 --- a/src/go.sum +++ b/src/go.sum @@ -104,8 +104,8 @@ github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL 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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudevents/sdk-go/v2 v2.14.0 h1:Nrob4FwVgi5L4tV9lhjzZcjYqFVyJzsA56CwPaPfv6s= -github.com/cloudevents/sdk-go/v2 v2.14.0/go.mod h1:xDmKfzNjM8gBvjaF8ijFjM1VYOVUEeUfapHMUX1T5To= +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= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= From ba840c20d4cdccb7b7c168814fee7cbe1e5fa02f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Mar 2024 10:49:52 +0800 Subject: [PATCH 053/145] Bump softprops/action-gh-release from 1 to 2 (#20115) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 1 to 2. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/v1...v2) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- .github/workflows/publish_release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index c57863f9f..7ba4df99c 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -68,7 +68,7 @@ jobs: source tools/release/release_utils.sh && generateReleaseNotes ${{ env.CUR_TAG }} ${{ env.PRE_TAG }} ${{ secrets.GITHUB_TOKEN }} $release_notes_path echo "RELEASE_NOTES_PATH=$release_notes_path" >> $GITHUB_ENV - name: RC Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 if: ${{ env.PRERELEASE == 'true' }} with: body_path: ${{ env.RELEASE_NOTES_PATH }} @@ -77,7 +77,7 @@ jobs: ${{ env.OFFLINE_PACKAGE_PATH }}.asc ${{ env.MD5SUM_PATH }} - name: GA Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 if: ${{ env.PRERELEASE == 'false' }} with: body_path: ${{ env.RELEASE_NOTES_PATH }} From ebb8050068d2f0f3548e05e27e663a438bbfe501 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Mar 2024 15:15:28 +0800 Subject: [PATCH 054/145] Bump golang.org/x/net from 0.17.0 to 0.22.0 in /src (#20113) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.17.0 to 0.22.0. - [Commits](https://github.com/golang/net/compare/v0.17.0...v0.22.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: Shengwen YU Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- src/go.mod | 8 ++++---- src/go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/go.mod b/src/go.mod index f303c5557..54ed70e5d 100644 --- a/src/go.mod +++ b/src/go.mod @@ -63,8 +63,8 @@ require ( go.opentelemetry.io/otel/sdk v1.21.0 go.opentelemetry.io/otel/trace v1.23.1 go.uber.org/ratelimit v0.2.0 - golang.org/x/crypto v0.19.0 - golang.org/x/net v0.17.0 + golang.org/x/crypto v0.21.0 + golang.org/x/net v0.22.0 golang.org/x/oauth2 v0.13.0 golang.org/x/sync v0.6.0 golang.org/x/text v0.14.0 @@ -171,8 +171,8 @@ require ( go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.19.0 // indirect - golang.org/x/sys v0.17.0 // indirect - golang.org/x/term v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect google.golang.org/api v0.126.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8 // indirect diff --git a/src/go.sum b/src/go.sum index 3b0d91162..e025a36e2 100644 --- a/src/go.sum +++ b/src/go.sum @@ -707,8 +707,8 @@ golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -762,8 +762,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -821,16 +821,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.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.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.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= 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= From 6a0ee091d828a2c71dceca14f53e594c6b25499f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Mar 2024 16:14:23 +0800 Subject: [PATCH 055/145] Bump github.com/jackc/pgx/v4 from 4.18.1 to 4.18.3 in /src (#20139) Bumps [github.com/jackc/pgx/v4](https://github.com/jackc/pgx) from 4.18.1 to 4.18.3. - [Changelog](https://github.com/jackc/pgx/blob/v4.18.3/CHANGELOG.md) - [Commits](https://github.com/jackc/pgx/compare/v4.18.1...v4.18.3) --- updated-dependencies: - dependency-name: github.com/jackc/pgx/v4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: Shengwen YU Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- src/go.mod | 6 +++--- src/go.sum | 14 ++++++-------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/go.mod b/src/go.mod index 54ed70e5d..b96f7e706 100644 --- a/src/go.mod +++ b/src/go.mod @@ -39,8 +39,8 @@ require ( github.com/gorilla/handlers v1.5.2 github.com/gorilla/mux v1.8.1 github.com/graph-gophers/dataloader v5.0.0+incompatible - github.com/jackc/pgconn v1.14.0 - github.com/jackc/pgx/v4 v4.18.1 + github.com/jackc/pgconn v1.14.3 + github.com/jackc/pgx/v4 v4.18.3 github.com/jpillora/backoff v1.0.0 github.com/ncw/swift v1.0.49 // indirect github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 @@ -129,7 +129,7 @@ require ( github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.3.2 // indirect + github.com/jackc/pgproto3/v2 v2.3.3 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgtype v1.14.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect diff --git a/src/go.sum b/src/go.sum index e025a36e2..ed0e5127a 100644 --- a/src/go.sum +++ b/src/go.sum @@ -354,8 +354,8 @@ github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsU github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q= -github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw= github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= @@ -373,8 +373,8 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvW github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= -github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= @@ -388,12 +388,11 @@ github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08 github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= -github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= +github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -705,7 +704,6 @@ 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.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= From 290b22cf177b79c55c2e405e1c3c80902c96fb63 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Mar 2024 16:51:51 +0800 Subject: [PATCH 056/145] Bump google.golang.org/protobuf from 1.31.0 to 1.33.0 in /src (#20124) Bumps google.golang.org/protobuf from 1.31.0 to 1.33.0. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- src/go.mod | 2 +- src/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/go.mod b/src/go.mod index b96f7e706..1abd297ff 100644 --- a/src/go.mod +++ b/src/go.mod @@ -179,7 +179,7 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/grpc v1.59.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/src/go.sum b/src/go.sum index ed0e5127a..4a205f0e9 100644 --- a/src/go.sum +++ b/src/go.sum @@ -927,8 +927,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.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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= From 6c2cafe7ba53adaff9010847afd006659dde092d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Mar 2024 17:29:09 +0800 Subject: [PATCH 057/145] Bump github.com/docker/docker from 24.0.7+incompatible to 24.0.9+incompatible in /src (#20147) Bump github.com/docker/docker in /src Bumps [github.com/docker/docker](https://github.com/docker/docker) from 24.0.7+incompatible to 24.0.9+incompatible. - [Release notes](https://github.com/docker/docker/releases) - [Commits](https://github.com/docker/docker/compare/v24.0.7...v24.0.9) --- updated-dependencies: - dependency-name: github.com/docker/docker dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- src/go.mod | 2 +- src/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/go.mod b/src/go.mod index 1abd297ff..88caa161f 100644 --- a/src/go.mod +++ b/src/go.mod @@ -102,7 +102,7 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dnaeon/go-vcr v1.2.0 // indirect github.com/docker/cli v24.0.6+incompatible // indirect - github.com/docker/docker v24.0.7+incompatible // indirect + github.com/docker/docker v24.0.9+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect diff --git a/src/go.sum b/src/go.sum index 4a205f0e9..075c8e378 100644 --- a/src/go.sum +++ b/src/go.sum @@ -139,8 +139,8 @@ github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/cli v24.0.6+incompatible h1:fF+XCQCgJjjQNIMjzaSmiKJSCcfcXb3TWTcc7GAneOY= github.com/docker/cli v24.0.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= -github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= +github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= From 7c2158bdf93fc6de8fed8f9ee44ab466b97253c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Mar 2024 18:08:06 +0800 Subject: [PATCH 058/145] Bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp from 1.21.0 to 1.24.0 in /src (#20037) Bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp Bumps [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp](https://github.com/open-telemetry/opentelemetry-go) from 1.21.0 to 1.24.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.21.0...v1.24.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: Shengwen YU Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- src/go.mod | 28 +++++++++++++------------- src/go.sum | 58 ++++++++++++++++++++++++++---------------------------- 2 files changed, 42 insertions(+), 44 deletions(-) diff --git a/src/go.mod b/src/go.mod index 88caa161f..7e439c773 100644 --- a/src/go.mod +++ b/src/go.mod @@ -57,15 +57,15 @@ require ( github.com/volcengine/volcengine-go-sdk v1.0.97 go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.46.1 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 - go.opentelemetry.io/otel v1.23.1 + go.opentelemetry.io/otel v1.24.0 go.opentelemetry.io/otel/exporters/jaeger v1.0.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 - go.opentelemetry.io/otel/sdk v1.21.0 - go.opentelemetry.io/otel/trace v1.23.1 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 + go.opentelemetry.io/otel/sdk v1.24.0 + go.opentelemetry.io/otel/trace v1.24.0 go.uber.org/ratelimit v0.2.0 golang.org/x/crypto v0.21.0 golang.org/x/net v0.22.0 - golang.org/x/oauth2 v0.13.0 + golang.org/x/oauth2 v0.15.0 golang.org/x/sync v0.6.0 golang.org/x/text v0.14.0 golang.org/x/time v0.5.0 @@ -79,7 +79,7 @@ require ( ) require ( - cloud.google.com/go/compute v1.23.0 // indirect + cloud.google.com/go/compute v1.23.3 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect github.com/Azure/azure-sdk-for-go v37.2.0+incompatible // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect @@ -119,7 +119,7 @@ require ( github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/gorilla/securecookie v1.1.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -165,20 +165,20 @@ require ( github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/volcengine/volc-sdk-golang v1.0.23 // indirect go.mongodb.org/mongo-driver v1.13.1 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect - go.opentelemetry.io/otel/metric v1.23.1 // indirect - go.opentelemetry.io/proto/otlp v1.0.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/proto/otlp v1.1.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.19.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/term v0.18.0 // indirect - google.golang.org/api v0.126.0 // indirect + google.golang.org/api v0.149.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect - google.golang.org/grpc v1.59.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/grpc v1.61.1 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.62.0 // indirect diff --git a/src/go.sum b/src/go.sum index 075c8e378..8081c0f92 100644 --- a/src/go.sum +++ b/src/go.sum @@ -5,8 +5,8 @@ cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxK cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= -cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= @@ -244,8 +244,6 @@ github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI github.com/golang-migrate/migrate/v4 v4.16.2 h1:8coYbMKUyInrFk1lfGfRovTLAW7PhWp8qQDT2iKfuoA= github.com/golang-migrate/migrate/v4 v4.16.2/go.mod h1:pfcJX4nPHaVdc5nmdCikFBWtm+UBpiZjRNNsyBbp0/o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= -github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -312,8 +310,8 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+ github.com/graph-gophers/dataloader v5.0.0+incompatible h1:R+yjsbrNq1Mo3aPG+Z/EKYrXrXXUNJHOgbRt+U6jOug= github.com/graph-gophers/dataloader v5.0.0+incompatible/go.mod h1:jk4jk0c5ZISbKaMe8WsVopGB5/15GvGHMdMdPtwlRp4= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= @@ -649,24 +647,24 @@ go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.46 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= go.opentelemetry.io/otel v1.0.0/go.mod h1:AjRVh9A5/5DE7S+mZtTR6t8vpKKryam+0lREnfmS4cg= -go.opentelemetry.io/otel v1.23.1 h1:Za4UzOqJYS+MUczKI320AtqZHZb7EqxO00jAHE0jmQY= -go.opentelemetry.io/otel v1.23.1/go.mod h1:Td0134eafDLcTS4y+zQ26GE8u3dEuRBiBCTUIRHaikA= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel/exporters/jaeger v1.0.0 h1:cLhx8llHw02h5JTqGqaRbYn+QVKHmrzD9vEbKnSPk5U= go.opentelemetry.io/otel/exporters/jaeger v1.0.0/go.mod h1:q10N1AolE1JjqKrFJK2tYw0iZpmX+HBaXBtuCzRnBGQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= -go.opentelemetry.io/otel/metric v1.23.1 h1:PQJmqJ9u2QaJLBOELl1cxIdPcpbwzbkjfEyelTl2rlo= -go.opentelemetry.io/otel/metric v1.23.1/go.mod h1:mpG2QPlAfnK8yNhNJAxDZruU9Y1/HubbC+KyH8FaCWI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/sdk v1.0.0/go.mod h1:PCrDHlSy5x1kjezSdL37PhbFUMjrsLRshJ2zCzeXwbM= -go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= -go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= go.opentelemetry.io/otel/trace v1.0.0/go.mod h1:PXTWqayeFUlJV1YDNhsJYB184+IvAH814St6o6ajzIs= -go.opentelemetry.io/otel/trace v1.23.1 h1:4LrmmEd8AU2rFvU1zegmvqW7+kWarxtNOPyeL6HmYY8= -go.opentelemetry.io/otel/trace v1.23.1/go.mod h1:4IpnpJFwr1mo/6HL8XIPJaE9y0+u1KcVmuW7dwFSVrI= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= +go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -765,8 +763,8 @@ golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= -golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= +golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -900,12 +898,12 @@ google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY= -google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= -google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q= -google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -914,8 +912,8 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= +google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From da3c85be5a700acae54fa21dc08befb75a20b64c Mon Sep 17 00:00:00 2001 From: Taras Katkov Date: Sat, 30 Mar 2024 09:41:50 -0400 Subject: [PATCH 059/145] fix image name extraction (#18992) * Update replication.go It also could be 'library/bitnami/fluentd:1.13.3-debian-10-r0' so we need to split resource to only 2 parts - possible namespace and image name which may include slashes for example - namespace: library, image: bitnami/fluentd:1.13.3-debian-10-r0 Signed-off-by: Taras Katkov * Update replication_test.go Adding namespace and resource extraction tests. Signed-off-by: Taras Katkov * Reformat only Signed-off-by: Taras Katkov --------- Signed-off-by: Taras Katkov --- .../handler/webhook/artifact/replication.go | 4 +++- .../webhook/artifact/replication_test.go | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/controller/event/handler/webhook/artifact/replication.go b/src/controller/event/handler/webhook/artifact/replication.go index fab71978b..56ceb0bb1 100644 --- a/src/controller/event/handler/webhook/artifact/replication.go +++ b/src/controller/event/handler/webhook/artifact/replication.go @@ -216,7 +216,9 @@ func constructReplicationPayload(ctx context.Context, event *event.ReplicationEv func getMetadataFromResource(resource string) (namespace, nameAndTag string) { // Usually resource format likes 'library/busybox:v1', but it could be 'busybox:v1' in docker registry - meta := strings.Split(resource, "/") + // It also could be 'library/bitnami/fluentd:1.13.3-debian-10-r0' so we need to split resource to only 2 parts + // possible namespace and image name which may include slashes for example: bitnami/fluentd:1.13.3-debian-10-r0 + meta := strings.SplitN(resource, "/", 2) if len(meta) == 1 { return "", meta[0] } diff --git a/src/controller/event/handler/webhook/artifact/replication_test.go b/src/controller/event/handler/webhook/artifact/replication_test.go index f920ff25d..8a8d1bca2 100644 --- a/src/controller/event/handler/webhook/artifact/replication_test.go +++ b/src/controller/event/handler/webhook/artifact/replication_test.go @@ -146,3 +146,21 @@ func TestIsLocalRegistry(t *testing.T) { } assert.False(t, isLocalRegistry(reg2)) } + +func TestReplicationHandler_ShortResourceName(t *testing.T) { + namespace, resource := getMetadataFromResource("busybox:v1") + assert.Equal(t, "", namespace) + assert.Equal(t, "busybox:v1", resource) +} + +func TestReplicationHandler_NormalResourceName(t *testing.T) { + namespace, resource := getMetadataFromResource("library/busybox:v1") + assert.Equal(t, "library", namespace) + assert.Equal(t, "busybox:v1", resource) +} + +func TestReplicationHandler_LongResourceName(t *testing.T) { + namespace, resource := getMetadataFromResource("library/bitnami/fluentd:1.13.3-debian-10-r0") + assert.Equal(t, "library", namespace) + assert.Equal(t, "bitnami/fluentd:1.13.3-debian-10-r0", resource) +} From b66d14d9f37329a3b16ba4a42d215d387fd0449e Mon Sep 17 00:00:00 2001 From: guangwu Date: Mon, 1 Apr 2024 10:03:24 +0800 Subject: [PATCH 060/145] fix: typo (#20190) Signed-off-by: guoguangwu --- src/cmd/exporter/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/exporter/main.go b/src/cmd/exporter/main.go index 400d54178..aea7553ce 100644 --- a/src/cmd/exporter/main.go +++ b/src/cmd/exporter/main.go @@ -96,7 +96,7 @@ func main() { ) prometheus.MustRegister(harborExporter) if err := harborExporter.ListenAndServe(); err != nil { - log.Errorf("Error starting Harbor expoter %s", err) + log.Errorf("Error starting Harbor exporter %s", err) os.Exit(1) } } From 680a6a828bbd4e53172c259dc096d94498acb476 Mon Sep 17 00:00:00 2001 From: MinerYang Date: Tue, 2 Apr 2024 10:22:03 +0800 Subject: [PATCH 061/145] bump golang 1.21.8 on main (#20197) bump golang 1.21.8 Signed-off-by: yminer --- .github/workflows/CI.yml | 10 +++++----- .github/workflows/build-package.yml | 2 +- .github/workflows/conformance_test.yml | 2 +- CONTRIBUTING.md | 2 +- Makefile | 2 +- make/photon/registry/Dockerfile.binary | 2 +- make/photon/trivy-adapter/Dockerfile.binary | 2 +- make/photon/trivy-adapter/builder.sh | 2 +- tests/ci/distro_installer.sh | 4 ++-- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 00bc21378..ea867a8ce 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -44,7 +44,7 @@ jobs: - name: Set up Go 1.21 uses: actions/setup-go@v5 with: - go-version: 1.21.5 + go-version: 1.21.8 id: go - uses: actions/checkout@v3 with: @@ -105,7 +105,7 @@ jobs: - name: Set up Go 1.21 uses: actions/setup-go@v5 with: - go-version: 1.21.5 + go-version: 1.21.8 id: go - uses: actions/checkout@v3 with: @@ -160,7 +160,7 @@ jobs: - name: Set up Go 1.21 uses: actions/setup-go@v5 with: - go-version: 1.21.5 + go-version: 1.21.8 id: go - uses: actions/checkout@v3 with: @@ -215,7 +215,7 @@ jobs: - name: Set up Go 1.21 uses: actions/setup-go@v5 with: - go-version: 1.21.5 + go-version: 1.21.8 id: go - uses: actions/checkout@v3 with: @@ -268,7 +268,7 @@ jobs: - name: Set up Go 1.21 uses: actions/setup-go@v5 with: - go-version: 1.21.5 + go-version: 1.21.8 id: go - uses: actions/checkout@v3 with: diff --git a/.github/workflows/build-package.yml b/.github/workflows/build-package.yml index 6d2867a8c..74f0b99ab 100644 --- a/.github/workflows/build-package.yml +++ b/.github/workflows/build-package.yml @@ -26,7 +26,7 @@ jobs: - name: Set up Go 1.21 uses: actions/setup-go@v5 with: - go-version: 1.21.5 + go-version: 1.21.8 id: go - name: Setup Docker uses: docker-practice/actions-setup-docker@master diff --git a/.github/workflows/conformance_test.yml b/.github/workflows/conformance_test.yml index 2e8521250..520f417f4 100644 --- a/.github/workflows/conformance_test.yml +++ b/.github/workflows/conformance_test.yml @@ -28,7 +28,7 @@ jobs: - name: Set up Go 1.21 uses: actions/setup-go@v5 with: - go-version: 1.21.5 + go-version: 1.21.8 id: go - uses: actions/checkout@v3 with: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 107ac61a8..2af5023b8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -164,7 +164,7 @@ Harbor backend is written in [Go](http://golang.org/). If you don't have a Harbo | 2.7 | 1.19.4 | | 2.8 | 1.20.6 | | 2.9 | 1.21.3 | -| 2.10 | 1.21.5 | +| 2.10 | 1.21.8 | Ensure your GOPATH and PATH have been configured in accordance with the Go environment instructions. diff --git a/Makefile b/Makefile index e617ef882..2c10331f0 100644 --- a/Makefile +++ b/Makefile @@ -140,7 +140,7 @@ GOINSTALL=$(GOCMD) install GOTEST=$(GOCMD) test GODEP=$(GOTEST) -i GOFMT=gofmt -w -GOBUILDIMAGE=golang:1.21.5 +GOBUILDIMAGE=golang:1.21.8 GOBUILDPATHINCONTAINER=/harbor # go build diff --git a/make/photon/registry/Dockerfile.binary b/make/photon/registry/Dockerfile.binary index be98146a6..c18fb9e09 100644 --- a/make/photon/registry/Dockerfile.binary +++ b/make/photon/registry/Dockerfile.binary @@ -1,4 +1,4 @@ -FROM golang:1.21.5 +FROM golang:1.21.8 ENV DISTRIBUTION_DIR /go/src/github.com/docker/distribution ENV BUILDTAGS include_oss include_gcs diff --git a/make/photon/trivy-adapter/Dockerfile.binary b/make/photon/trivy-adapter/Dockerfile.binary index 94d71551a..65bc9e9a5 100644 --- a/make/photon/trivy-adapter/Dockerfile.binary +++ b/make/photon/trivy-adapter/Dockerfile.binary @@ -1,4 +1,4 @@ -FROM golang:1.21.5 +FROM golang:1.21.8 ADD . /go/src/github.com/aquasecurity/harbor-scanner-trivy/ WORKDIR /go/src/github.com/aquasecurity/harbor-scanner-trivy/ diff --git a/make/photon/trivy-adapter/builder.sh b/make/photon/trivy-adapter/builder.sh index b696b01bb..debed09a5 100755 --- a/make/photon/trivy-adapter/builder.sh +++ b/make/photon/trivy-adapter/builder.sh @@ -19,7 +19,7 @@ TEMP=$(mktemp -d ${TMPDIR-/tmp}/trivy-adapter.XXXXXX) git clone https://github.com/aquasecurity/harbor-scanner-trivy.git $TEMP cd $TEMP; git checkout $VERSION; cd - -echo "Building Trivy adapter binary based on golang:1.21.5..." +echo "Building Trivy adapter binary based on golang:1.21.8..." cp Dockerfile.binary $TEMP docker build -f $TEMP/Dockerfile.binary -t trivy-adapter-golang $TEMP diff --git a/tests/ci/distro_installer.sh b/tests/ci/distro_installer.sh index b946b461a..ae10a5f85 100755 --- a/tests/ci/distro_installer.sh +++ b/tests/ci/distro_installer.sh @@ -3,5 +3,5 @@ set -x set -e -sudo make package_online GOBUILDTAGS="include_oss include_gcs" VERSIONTAG=dev-gitaction PKGVERSIONTAG=dev-gitaction UIVERSIONTAG=dev-gitaction GOBUILDIMAGE=golang:1.21.5 COMPILETAG=compile_golangimage TRIVYFLAG=true HTTPPROXY= PULL_BASE_FROM_DOCKERHUB=false -sudo make package_offline GOBUILDTAGS="include_oss include_gcs" VERSIONTAG=dev-gitaction PKGVERSIONTAG=dev-gitaction UIVERSIONTAG=dev-gitaction GOBUILDIMAGE=golang:1.21.5 COMPILETAG=compile_golangimage TRIVYFLAG=true HTTPPROXY= PULL_BASE_FROM_DOCKERHUB=false +sudo make package_online GOBUILDTAGS="include_oss include_gcs" VERSIONTAG=dev-gitaction PKGVERSIONTAG=dev-gitaction UIVERSIONTAG=dev-gitaction GOBUILDIMAGE=golang:1.21.8 COMPILETAG=compile_golangimage TRIVYFLAG=true HTTPPROXY= PULL_BASE_FROM_DOCKERHUB=false +sudo make package_offline GOBUILDTAGS="include_oss include_gcs" VERSIONTAG=dev-gitaction PKGVERSIONTAG=dev-gitaction UIVERSIONTAG=dev-gitaction GOBUILDIMAGE=golang:1.21.8 COMPILETAG=compile_golangimage TRIVYFLAG=true HTTPPROXY= PULL_BASE_FROM_DOCKERHUB=false From cea47c7db38f60d2e83343f933a0c1806eb2d583 Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Tue, 2 Apr 2024 18:11:27 +0800 Subject: [PATCH 062/145] Add accessory type for sbom (#20208) Signed-off-by: stonezdj Co-authored-by: stonezdj --- src/core/main.go | 1 + src/pkg/accessory/model/accessory.go | 3 + src/pkg/accessory/model/sbom/sbom.go | 46 ++++++++++++ src/pkg/accessory/model/sbom/sbom_test.go | 87 +++++++++++++++++++++++ src/server/middleware/subject/subject.go | 5 ++ 5 files changed, 142 insertions(+) create mode 100644 src/pkg/accessory/model/sbom/sbom.go create mode 100644 src/pkg/accessory/model/sbom/sbom_test.go diff --git a/src/core/main.go b/src/core/main.go index b660ea012..50e6a4566 100644 --- a/src/core/main.go +++ b/src/core/main.go @@ -60,6 +60,7 @@ import ( _ "github.com/goharbor/harbor/src/pkg/accessory/model/cosign" _ "github.com/goharbor/harbor/src/pkg/accessory/model/notation" _ "github.com/goharbor/harbor/src/pkg/accessory/model/nydus" + _ "github.com/goharbor/harbor/src/pkg/accessory/model/sbom" _ "github.com/goharbor/harbor/src/pkg/accessory/model/subject" "github.com/goharbor/harbor/src/pkg/audit" dbCfg "github.com/goharbor/harbor/src/pkg/config/db" diff --git a/src/pkg/accessory/model/accessory.go b/src/pkg/accessory/model/accessory.go index 4d6052532..5bd276c8e 100644 --- a/src/pkg/accessory/model/accessory.go +++ b/src/pkg/accessory/model/accessory.go @@ -76,6 +76,9 @@ const ( // TypeSubject ... TypeSubject = "subject.accessory" + + // TypeHarborSBOM identifies harbor.sbom + TypeHarborSBOM = "harbor.sbom" ) // AccessoryData ... diff --git a/src/pkg/accessory/model/sbom/sbom.go b/src/pkg/accessory/model/sbom/sbom.go new file mode 100644 index 000000000..3e5a5642a --- /dev/null +++ b/src/pkg/accessory/model/sbom/sbom.go @@ -0,0 +1,46 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sbom + +import ( + "github.com/goharbor/harbor/src/pkg/accessory/model" + "github.com/goharbor/harbor/src/pkg/accessory/model/base" +) + +// HarborSBOM is the sbom accessory for harbor +type HarborSBOM struct { + base.Default +} + +// Kind gives the reference type of accessory. +func (c *HarborSBOM) Kind() string { + return model.RefHard +} + +// IsHard ... +func (c *HarborSBOM) IsHard() bool { + return true +} + +// New returns sbom accessory +func New(data model.AccessoryData) model.Accessory { + return &HarborSBOM{base.Default{ + Data: data, + }} +} + +func init() { + model.Register(model.TypeHarborSBOM, New) +} diff --git a/src/pkg/accessory/model/sbom/sbom_test.go b/src/pkg/accessory/model/sbom/sbom_test.go new file mode 100644 index 000000000..92f9bda27 --- /dev/null +++ b/src/pkg/accessory/model/sbom/sbom_test.go @@ -0,0 +1,87 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sbom + +import ( + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/goharbor/harbor/src/pkg/accessory/model" + htesting "github.com/goharbor/harbor/src/testing" +) + +type SBOMTestSuite struct { + htesting.Suite + accessory model.Accessory + digest string + subDigest string +} + +func (suite *SBOMTestSuite) SetupSuite() { + suite.digest = suite.DigestString() + suite.subDigest = suite.DigestString() + suite.accessory, _ = model.New(model.TypeHarborSBOM, + model.AccessoryData{ + ArtifactID: 1, + SubArtifactDigest: suite.subDigest, + Size: 4321, + Digest: suite.digest, + }) +} + +func (suite *SBOMTestSuite) TestGetID() { + suite.Equal(int64(0), suite.accessory.GetData().ID) +} + +func (suite *SBOMTestSuite) TestGetArtID() { + suite.Equal(int64(1), suite.accessory.GetData().ArtifactID) +} + +func (suite *SBOMTestSuite) TestSubGetArtID() { + suite.Equal(suite.subDigest, suite.accessory.GetData().SubArtifactDigest) +} + +func (suite *SBOMTestSuite) TestSubGetSize() { + suite.Equal(int64(4321), suite.accessory.GetData().Size) +} + +func (suite *SBOMTestSuite) TestSubGetDigest() { + suite.Equal(suite.digest, suite.accessory.GetData().Digest) +} + +func (suite *SBOMTestSuite) TestSubGetType() { + suite.Equal(model.TypeHarborSBOM, suite.accessory.GetData().Type) +} + +func (suite *SBOMTestSuite) TestSubGetRefType() { + suite.Equal(model.RefHard, suite.accessory.Kind()) +} + +func (suite *SBOMTestSuite) TestIsSoft() { + suite.False(suite.accessory.IsSoft()) +} + +func (suite *SBOMTestSuite) TestIsHard() { + suite.True(suite.accessory.IsHard()) +} + +func (suite *SBOMTestSuite) TestDisplay() { + suite.False(suite.accessory.Display()) +} + +func TestSBOMTestSuite(t *testing.T) { + suite.Run(t, new(SBOMTestSuite)) +} diff --git a/src/server/middleware/subject/subject.go b/src/server/middleware/subject/subject.go index c4b86863e..4c1c47315 100644 --- a/src/server/middleware/subject/subject.go +++ b/src/server/middleware/subject/subject.go @@ -41,6 +41,9 @@ var ( // annotation of nydus image layerAnnotationNydusBootstrap = "containerd.io/snapshot/nydus-bootstrap" + + // media type of harbor sbom + mediaTypeHarborSBOM = "application/vnd.goharbor.harbor.sbom.v1" ) /* @@ -149,6 +152,8 @@ func Middleware() func(http.Handler) http.Handler { } case mediaTypeNotationLayer: accData.Type = model.TypeNotationSignature + case mediaTypeHarborSBOM: + accData.Type = model.TypeHarborSBOM } if subjectArt != nil { accData.SubArtifactID = subjectArt.ID From 9778176ff1ee7135c91d8d90ab47942a32bfdadc Mon Sep 17 00:00:00 2001 From: guangwu Date: Wed, 3 Apr 2024 11:45:59 +0800 Subject: [PATCH 063/145] fix: close file (#20189) Signed-off-by: guoguangwu Co-authored-by: Wang Yan --- src/pkg/scan/export/digest_calculator.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pkg/scan/export/digest_calculator.go b/src/pkg/scan/export/digest_calculator.go index 5c3d50d22..a5f46f2be 100644 --- a/src/pkg/scan/export/digest_calculator.go +++ b/src/pkg/scan/export/digest_calculator.go @@ -35,6 +35,7 @@ func (calc *SHA256ArtifactDigestCalculator) Calculate(fileName string) (digest.D if err != nil { return "", err } + defer file.Close() hash := sha256.New() if _, err := io.Copy(hash, file); err != nil { return "", err From b6366e03e90570269d520825d9d996d572450c44 Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Wed, 3 Apr 2024 15:59:15 +0800 Subject: [PATCH 064/145] Update GenAccessoryArt API to generate valid accessory for SBOM (#20214) Signed-off-by: stonezdj Co-authored-by: stonezdj --- src/pkg/scan/util.go | 10 ++++++---- src/pkg/scan/util_test.go | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/pkg/scan/util.go b/src/pkg/scan/util.go index e7b65b1e0..ac19d92a0 100644 --- a/src/pkg/scan/util.go +++ b/src/pkg/scan/util.go @@ -34,8 +34,10 @@ import ( v1sq "github.com/goharbor/harbor/src/pkg/scan/rest/v1" ) +// Insecure ... type Insecure bool +// RemoteOptions ... func (i Insecure) RemoteOptions() []remote.Option { tr := http.DefaultTransport.(*http.Transport).Clone() tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: bool(i)} @@ -76,14 +78,14 @@ func GenAccessoryArt(sq v1sq.ScanRequest, accData []byte, accAnnotations map[str // https://github.com/google/go-containerregistry/issues/1832 accArt = mutate.MediaType(accArt, ocispec.MediaTypeImageManifest) accArt = mutate.ConfigMediaType(accArt, types.MediaType(mediaType)) - accArt = mutate.Subject(accArt, *accSubArt).(v1.Image) accArt = mutate.Annotations(accArt, accAnnotations).(v1.Image) + accArt = mutate.Subject(accArt, *accSubArt).(v1.Image) - digest, err := accArt.Digest() + dgst, err := accArt.Digest() if err != nil { return "", err } - accRef, err := name.ParseReference(fmt.Sprintf("%s/%s@%s", sq.Registry.URL, sq.Artifact.Repository, digest.String())) + accRef, err := name.ParseReference(fmt.Sprintf("%s/%s@%s", sq.Registry.URL, sq.Artifact.Repository, dgst.String())) if err != nil { return "", err } @@ -91,5 +93,5 @@ func GenAccessoryArt(sq v1sq.ScanRequest, accData []byte, accAnnotations map[str if err := remote.Write(accRef, accArt, opts...); err != nil { return "", err } - return digest.String(), nil + return dgst.String(), nil } diff --git a/src/pkg/scan/util_test.go b/src/pkg/scan/util_test.go index f293f026a..519d6d68a 100644 --- a/src/pkg/scan/util_test.go +++ b/src/pkg/scan/util_test.go @@ -60,5 +60,5 @@ func TestGenAccessoryArt(t *testing.T) { } s, err := GenAccessoryArt(sq, []byte(`{"name": "harborAccTest", "version": "1.0"}`), annotations, "application/vnd.goharbor.harbor.main.v1", r) assert.Nil(t, err) - assert.Equal(t, "sha256:8de6104b79deca0253ff8667692f03e34753494c77ec81f631b45aad69223c18", s) + assert.Equal(t, "sha256:a39c6456d3cd1d87b7ee5706f67133d7a6d27a2dbc9ed66d50e504ff8920efc3", s) } From dd76fe47ce4cdd0de471ce2c025e3c007c6f05b3 Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Wed, 3 Apr 2024 16:38:09 +0800 Subject: [PATCH 065/145] Add SBOM scan REST API (#20215) Update swagger API for generate SBOM Signed-off-by: stonezdj Co-authored-by: stonezdj --- api/v2.0/swagger.yaml | 65 ++++++++++++++++++++++++++++++++- src/controller/scan/options.go | 17 +++++++++ src/server/v2.0/handler/scan.go | 7 ++++ 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index 8f4ffcdaf..abf048914 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -991,6 +991,12 @@ paths: type: boolean required: false default: false + - name: with_sbom_overview + in: query + description: Specify whether the SBOM overview is included in returning artifacts, when this option is true, the SBOM overview will be included in the response + type: boolean + required: false + default: false - name: with_signature in: query description: Specify whether the signature is included inside the tags of the returning artifacts. Only works when setting "with_tag=true" @@ -1096,6 +1102,12 @@ paths: type: boolean required: false default: false + - name: with_sbom_overview + in: query + description: Specify whether the SBOM overview is included in returning artifact, when this option is true, the SBOM overview will be included in the response + type: boolean + required: false + default: false - name: with_accessory in: query description: Specify whether the accessories are included of the returning artifacts. @@ -1164,6 +1176,11 @@ paths: - $ref: '#/parameters/projectName' - $ref: '#/parameters/repositoryName' - $ref: '#/parameters/reference' + - name: scan_request_type + in: body + required: false + schema: + $ref: '#/definitions/ScanRequestType' responses: '202': $ref: '#/responses/202' @@ -1432,7 +1449,7 @@ paths: in: path description: The type of addition. type: string - enum: [build_history, values.yaml, readme.md, dependencies] + enum: [build_history, values.yaml, readme.md, dependencies, sbom] required: true responses: '200': @@ -6592,6 +6609,9 @@ definitions: scan_overview: $ref: '#/definitions/ScanOverview' description: The overview of the scan result. + sbom_overview: + $ref: '#/definitions/SBOMOverview' + description: The overview of the generating SBOM progress accessories: type: array items: @@ -6738,11 +6758,47 @@ definitions: type: string description: Version of the scanner adapter example: "v0.9.1" + ScanRequestType: + type: object + properties: + scan_type: + type: string + description: 'The scan type for the scan request. Two options are currently supported, vulnerability and sbom' + enum: [vulnerability, sbom] ScanOverview: type: object description: 'The scan overview attached in the metadata of tag' additionalProperties: $ref: '#/definitions/NativeReportSummary' + SBOMOverview: + type: object + description: 'The generate SBOM overview information' + properties: + start_time: + type: string + format: date-time + description: 'The start time of the generating sbom report task' + example: '2006-01-02T14:04:05Z' + end_time: + type: string + format: date-time + description: 'The end time of the generating sbom report task' + example: '2006-01-02T15:04:05Z' + scan_status: + type: string + description: 'The status of the generating SBOM task' + sbom_digest: + type: string + description: 'The digest of the generated SBOM accessory' + report_id: + type: string + description: 'id of the native scan report' + example: '5f62c830-f996-11e9-957f-0242c0a89008' + duration: + type: integer + format: int64 + description: 'Time in seconds required to create the report' + example: 300 NativeReportSummary: type: object description: 'The summary for the native report' @@ -8368,7 +8424,12 @@ definitions: default: "" description: Indicate the healthy of the registration example: "healthy" - + capabilities: + type: object + description: Indicates the capabilities of the scanner, e.g. support_vulnerability or support_sbom. + additionalProperties: True + example: {"support_vulnerability": true, "support_sbom": true} + ScannerRegistrationReq: type: object required: diff --git a/src/controller/scan/options.go b/src/controller/scan/options.go index a87644833..f62e2205e 100644 --- a/src/controller/scan/options.go +++ b/src/controller/scan/options.go @@ -18,6 +18,15 @@ package scan type Options struct { ExecutionID int64 // The execution id to scan artifact Tag string // The tag of the artifact to scan + ScanType string // The scan type could be sbom or vulnerability +} + +// GetScanType returns the scan type. for backward compatibility, the default type is vulnerability. +func (o *Options) GetScanType() string { + if len(o.ScanType) == 0 { + o.ScanType = "vulnerability" + } + return o.ScanType } // Option represents an option item by func template. @@ -44,3 +53,11 @@ func WithTag(tag string) Option { return nil } } + +// WithScanType set the scanType +func WithScanType(scanType string) Option { + return func(options *Options) error { + options.ScanType = scanType + return nil + } +} diff --git a/src/server/v2.0/handler/scan.go b/src/server/v2.0/handler/scan.go index 8691246e5..e58e12a84 100644 --- a/src/server/v2.0/handler/scan.go +++ b/src/server/v2.0/handler/scan.go @@ -82,6 +82,9 @@ func (s *scanAPI) ScanArtifact(ctx context.Context, params operation.ScanArtifac if !distribution.IsDigest(params.Reference) { options = append(options, scan.WithTag(params.Reference)) } + if params.ScanRequestType != nil && validScanType(params.ScanRequestType.ScanType) { + options = append(options, scan.WithScanType(params.ScanRequestType.ScanType)) + } if err := s.scanCtl.Scan(ctx, artifact, options...); err != nil { return s.SendError(ctx, err) @@ -112,3 +115,7 @@ func (s *scanAPI) GetReportLog(ctx context.Context, params operation.GetReportLo return operation.NewGetReportLogOK().WithPayload(string(bytes)) } + +func validScanType(scanType string) bool { + return scanType == "sbom" || scanType == "vulnerability" +} From 7b8a322a88e8a7a19679ef3590768cb9652addc2 Mon Sep 17 00:00:00 2001 From: Prima Adi Pradana Date: Fri, 5 Apr 2024 22:29:07 +0700 Subject: [PATCH 066/145] delete membership=0 in getProjectsByName (#20153) delete membership=0 in getProjectsByName but lets getProjects still using membership=1 for reserve if getProjectsByName not found any Signed-off-by: prima --- src/pkg/reg/adapter/gitlab/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pkg/reg/adapter/gitlab/client.go b/src/pkg/reg/adapter/gitlab/client.go index af268da62..607884efb 100644 --- a/src/pkg/reg/adapter/gitlab/client.go +++ b/src/pkg/reg/adapter/gitlab/client.go @@ -83,7 +83,7 @@ func (c *Client) getProjects() ([]*Project, error) { func (c *Client) getProjectsByName(name string) ([]*Project, error) { var projects []*Project - urlAPI := fmt.Sprintf("%s/api/v4/projects?search=%s&membership=true&search_namespaces=true&per_page=50", c.url, name) + urlAPI := fmt.Sprintf("%s/api/v4/projects?search=%s&search_namespaces=true&per_page=50", c.url, name) if err := c.GetAndIteratePagination(urlAPI, &projects); err != nil { return nil, err } From c12064df4ec945e0eaa48cd76b045761c5478938 Mon Sep 17 00:00:00 2001 From: Shengwen YU Date: Sun, 7 Apr 2024 13:47:49 +0800 Subject: [PATCH 067/145] feat: add api test case for quota sorting (#20209) Signed-off-by: Shengwen Yu --- .../apitests/python/library/quota_sorting.py | 21 +++++ tests/apitests/python/test_quota_sorting.py | 86 +++++++++++++++++++ tests/robot-cases/Group0-BAT/API_DB.robot | 4 + 3 files changed, 111 insertions(+) create mode 100644 tests/apitests/python/library/quota_sorting.py create mode 100644 tests/apitests/python/test_quota_sorting.py diff --git a/tests/apitests/python/library/quota_sorting.py b/tests/apitests/python/library/quota_sorting.py new file mode 100644 index 000000000..c8d7cf1fc --- /dev/null +++ b/tests/apitests/python/library/quota_sorting.py @@ -0,0 +1,21 @@ +import base +from v2_swagger_client.rest import ApiException + +class QuotaSorting(base.Base): + def __init__(self): + super(QuotaSorting,self).__init__(api_type="quota") + + def list_quotas_with_sorting(self, expect_status_code=200, **kwargs): + params = {} + if "sort" in kwargs: + params["sort"] = kwargs["sort"] + if "reference" in kwargs: + params["reference"] = kwargs["reference"] + + try: + resp_data, status_code, _ = self._get_client(**kwargs).list_quotas_with_http_info(**params) + except ApiException as e: + raise Exception(r"Error out with exception. Exception status: {}; exception reason: {}; exception body: {}", e.status, e.reason, e.body) + base._assert_status_code(expect_status_code, status_code) + + return resp_data diff --git a/tests/apitests/python/test_quota_sorting.py b/tests/apitests/python/test_quota_sorting.py new file mode 100644 index 000000000..1cbc5ee37 --- /dev/null +++ b/tests/apitests/python/test_quota_sorting.py @@ -0,0 +1,86 @@ +from __future__ import absolute_import + +import unittest + +from testutils import harbor_server, suppress_urllib3_warning +from testutils import TEARDOWN +from testutils import ADMIN_CLIENT +from library.project import Project +from library.user import User +from library.repository import Repository +from library.repository import push_self_build_image_to_project +from library.quota_sorting import QuotaSorting + +class TestQuotaSorting(unittest.TestCase): + @suppress_urllib3_warning + def setUp(self): + self.project = Project() + self.user = User() + self.repo = Repository() + self.quota_sorting = QuotaSorting() + + @unittest.skipIf(TEARDOWN == False, "Test data won't be erased.") + def tearDown(self): + #1. Delete repository(RA) by user(UA); + self.repo.delete_repository(TestQuotaSorting.project_1_name, TestQuotaSorting.repo_name_1.split('/')[1], **TestQuotaSorting.user_global_client) + self.repo.delete_repository(TestQuotaSorting.project_2_name, TestQuotaSorting.repo_name_2.split('/')[1], **TestQuotaSorting.user_global_client) + + #2. Delete project(PA); + self.project.delete_project(TestQuotaSorting.project_1_id, **TestQuotaSorting.user_global_client) + self.project.delete_project(TestQuotaSorting.project_2_id, **TestQuotaSorting.user_global_client) + + #3. Delete user(UA); + self.user.delete_user(TestQuotaSorting.user_001_id, **ADMIN_CLIENT) + + def testQuotaSorting(self): + """ + Test case: + Quota Sorting + Test step and expected result: + 1. Create a new user(UA); + 2. Create two new private projects(PA) by user(UA); + 3. Push two images to projects(PA) respectively by user(UA) + 4. Get quota list with sort=used.storage, the used storage should be in ascending order + 5. Get quota list with sort=-used.storage, the used storage should be in descending order + Tear down: + 1. Delete repository(RA) by user(UA); + 2. Delete project(PA); + 3. Delete user(UA); + """ + url = ADMIN_CLIENT["endpoint"] + user_001_password = "Aa123456" + global_admin_client = dict(endpoint=ADMIN_CLIENT["endpoint"], username=ADMIN_CLIENT["username"], passwor=ADMIN_CLIENT["password"], reference="project") + + #1. Create user-001 + TestQuotaSorting.user_001_id, user_001_name = self.user.create_user(user_password=user_001_password, **ADMIN_CLIENT) + TestQuotaSorting.user_global_client = dict(endpoint=url, username=user_001_name, password=user_001_password) + + #2. Create private project_1 and private project_2 + TestQuotaSorting.project_1_id, TestQuotaSorting.project_1_name = self.project.create_project(metadata={"public": "false"}, **TestQuotaSorting.user_global_client) + TestQuotaSorting.project_2_id, TestQuotaSorting.project_2_name = self.project.create_project(metadata={"public": "false"}, **TestQuotaSorting.user_global_client) + + #3. Push images to project_1 and project_2 respectively + image1 = "alpine" + tag1 = "2.6" + TestQuotaSorting.repo_name_1, _ = push_self_build_image_to_project(TestQuotaSorting.project_1_name, harbor_server, user_001_name, user_001_password, image1, tag1) + image2 = "photon" + tag2 = "2.0" + TestQuotaSorting.repo_name_2, _ = push_self_build_image_to_project(TestQuotaSorting.project_2_name, harbor_server, user_001_name, user_001_password, image2, tag2) + + #4. Check whether quota list is in ascending order + global_admin_client["sort"] = "used.storage" + res_ascending = self.quota_sorting.list_quotas_with_sorting(expect_status_code=200, **global_admin_client) + self.assertTrue(len(res_ascending) >= 2) + for idx in range(1, len(res_ascending)): + self.assertTrue(res_ascending[idx - 1].to_dict()["used"]["storage"] <= res_ascending[idx].to_dict()["used"]["storage"]) + + #5. Check whether quota list is in descending order + global_admin_client["sort"] = "-used.storage" + res_descending = self.quota_sorting.list_quotas_with_sorting(expect_status_code=200, **global_admin_client) + self.assertTrue(len(res_descending) >= 2) + for idx in range(1, len(res_descending)): + self.assertTrue(res_descending[idx - 1].to_dict()["used"]["storage"] >= res_descending[idx].to_dict()["used"]["storage"]) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/robot-cases/Group0-BAT/API_DB.robot b/tests/robot-cases/Group0-BAT/API_DB.robot index 8a46efcd2..179d85ebb 100644 --- a/tests/robot-cases/Group0-BAT/API_DB.robot +++ b/tests/robot-cases/Group0-BAT/API_DB.robot @@ -20,6 +20,10 @@ Test Case - Garbage Collection [Tags] gc Harbor API Test ./tests/apitests/python/test_garbage_collection.py +Test Case - Quota Sorting + [Tags] quota_sorting + Harbor API Test ./tests/apitests/python/test_quota_sorting.py + Test Case - Add Private Project Member and Check User Can See It [Tags] private_member Harbor API Test ./tests/apitests/python/test_add_member_to_private_project.py From c8370faeeb16f27992632886334c6f39eefc91bd Mon Sep 17 00:00:00 2001 From: Shengwen YU Date: Mon, 8 Apr 2024 12:57:38 +0800 Subject: [PATCH 068/145] fix: test robot account permission (#20240) Signed-off-by: Shengwen Yu --- api/v2.0/swagger.yaml | 2 ++ tests/apitests/python/test_project_permission.py | 12 +++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index abf048914..8c23e29f3 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -1243,6 +1243,8 @@ paths: description: Successfully get scan log file schema: type: string + '400': + $ref: '#/responses/400' '401': $ref: '#/responses/401' '403': diff --git a/tests/apitests/python/test_project_permission.py b/tests/apitests/python/test_project_permission.py index 988d09a00..1962c48e3 100644 --- a/tests/apitests/python/test_project_permission.py +++ b/tests/apitests/python/test_project_permission.py @@ -91,7 +91,7 @@ delete_artifact = Permission("{}/projects/{}/repositories/target_repo/artifacts/ # 6. Resource scan actions: ['read', 'create', 'stop'] create_scan = Permission("{}/projects/{}/repositories/{}/artifacts/{}/scan".format(harbor_base_url, project_name, source_artifact_name, source_artifact_tag), "POST", 202) stop_scan = Permission("{}/projects/{}/repositories/{}/artifacts/{}/scan/stop".format(harbor_base_url, project_name, source_artifact_name, source_artifact_tag), "POST", 202) -read_scan = Permission("{}/projects/{}/repositories/{}/artifacts/{}/scan/0/log".format(harbor_base_url, project_name, source_artifact_name, source_artifact_tag), "get", 404) +read_scan = Permission("{}/projects/{}/repositories/{}/artifacts/{}/scan/83be44fd-1234-5678-b49f-4b6d6e8f5730/log".format(harbor_base_url, project_name, source_artifact_name, source_artifact_tag), "get", 404) # 7. Resource tag actions: ['list', 'create', 'delete'] tag_payload = { "name": "test-{}".format(int(random.randint(1000, 9999))) } @@ -249,16 +249,22 @@ resource_permissions = { "log": [list_log], "notification-policy": [create_webhook, list_webhook, read_webhook, update_webhook, list_webhook_executions, list_webhook_executions_tasks, read_webhook_executions_tasks, list_webhook_events, delete_webhook] } -resource_permissions["all"] = [item for sublist in resource_permissions.values() for item in sublist] def main(): + global resources # Declare resources as a global variable + + if str(resources) == "all": + resources = ','.join(str(key) for key in resource_permissions.keys()) + for resource in resources.split(","): for permission in resource_permissions[resource]: print("=================================================") print("call: {} {}".format(permission.method, permission.url)) print("payload: {}".format(json.dumps(permission.payload))) - print("response: {}".format(permission.call().text)) + resp = permission.call() + print("response: {}".format(resp.text)) + print("response status code: {}".format(resp.status_code)) print("=================================================\n") From 389a8c49f460488ddbbf2dcd5ba20fe6a6f0bd4e Mon Sep 17 00:00:00 2001 From: MinerYang Date: Mon, 8 Apr 2024 14:25:19 +0800 Subject: [PATCH 069/145] update artifact_type column alteration (#20239) update column if is null Signed-off-by: yminer Co-authored-by: Wang Yan --- make/migrations/postgresql/0140_2.11.0_schema.up.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/make/migrations/postgresql/0140_2.11.0_schema.up.sql b/make/migrations/postgresql/0140_2.11.0_schema.up.sql index b43f6072f..fd6fb16ed 100644 --- a/make/migrations/postgresql/0140_2.11.0_schema.up.sql +++ b/make/migrations/postgresql/0140_2.11.0_schema.up.sql @@ -20,12 +20,12 @@ table artifact: /* Add new column artifact_type for artifact table to work with oci-spec v1.1.0 list referrer api */ -ALTER TABLE artifact ADD COLUMN artifact_type varchar(255); +ALTER TABLE artifact ADD COLUMN IF NOT EXISTS artifact_type varchar(255); /* set value for artifact_type then set column artifact_type as not null */ -UPDATE artifact SET artifact_type = media_type; +UPDATE artifact SET artifact_type = media_type WHERE artifact_type IS NULL; ALTER TABLE artifact ALTER COLUMN artifact_type SET NOT NULL; \ No newline at end of file From 96ba34a93c3d5d8bd80c9585958ff0cc3811ba57 Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Tue, 9 Apr 2024 10:24:57 +0800 Subject: [PATCH 070/145] Allow empty path in redirect_url (#20238) fixes #20226 Signed-off-by: stonezdj Co-authored-by: stonezdj --- src/common/utils/utils.go | 6 +++--- src/common/utils/utils_test.go | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/common/utils/utils.go b/src/common/utils/utils.go index 07597492e..9a7a1f07c 100644 --- a/src/common/utils/utils.go +++ b/src/common/utils/utils.go @@ -313,11 +313,11 @@ func ValidateCronString(cron string) error { // sort.Slice(input, func(i, j int) bool { // return MostMatchSorter(input[i].GroupName, input[j].GroupName, matchWord) // }) +// // a is the field to be used for sorting, b is the other field, matchWord is the word to be matched // the return value is true if a is less than b // for example, search with "user", input is {"harbor_user", "user", "users, "admin_user"} // it returns with this order {"user", "users", "admin_user", "harbor_user"} - func MostMatchSorter(a, b string, matchWord string) bool { // exact match always first if a == matchWord { @@ -333,7 +333,7 @@ func MostMatchSorter(a, b string, matchWord string) bool { return len(a) < len(b) } -// IsLocalPath checks if path is local +// IsLocalPath checks if path is local, includes the empty path func IsLocalPath(path string) bool { - return strings.HasPrefix(path, "/") && !strings.HasPrefix(path, "//") + return len(path) == 0 || (strings.HasPrefix(path, "/") && !strings.HasPrefix(path, "//")) } diff --git a/src/common/utils/utils_test.go b/src/common/utils/utils_test.go index 8849e1f0c..4e1ab2ef3 100644 --- a/src/common/utils/utils_test.go +++ b/src/common/utils/utils_test.go @@ -501,6 +501,7 @@ func TestIsLocalPath(t *testing.T) { {"other_site1", args{"//www.myexample.com"}, false}, {"other_site2", args{"https://www.myexample.com"}, false}, {"other_site", args{"http://www.myexample.com"}, false}, + {"empty_path", args{""}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From ff1a5056d799217675fdde5c08fa0ac34894fccc Mon Sep 17 00:00:00 2001 From: guangwu Date: Tue, 9 Apr 2024 14:27:46 +0800 Subject: [PATCH 071/145] fix: close blob io ReadCloser (#20225) Signed-off-by: guoguangwu Co-authored-by: Wang Yan --- src/controller/artifact/annotation/v1alpha1.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/controller/artifact/annotation/v1alpha1.go b/src/controller/artifact/annotation/v1alpha1.go index 15c464ab2..6ca4605e1 100644 --- a/src/controller/artifact/annotation/v1alpha1.go +++ b/src/controller/artifact/annotation/v1alpha1.go @@ -92,6 +92,7 @@ func parseV1alpha1Icon(artifact *artifact.Artifact, manifest *v1.Manifest, reg r if err != nil { return err } + defer icon.Close() // check the size of the size <= 1MB data, err := io.ReadAll(io.LimitReader(icon, 1<<20)) if err != nil { From be648ea47f0e987f3b48235d6231a9248e81d166 Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Tue, 9 Apr 2024 16:05:30 +0800 Subject: [PATCH 072/145] Refactor scan job service make it easy to add new scan type (#20177) Signed-off-by: stonezdj Signed-off-by: stonezdj(Daojun Zhang) Co-authored-by: stonezdj --- src/controller/scan/base_controller.go | 23 +++---- src/controller/scan/base_controller_test.go | 1 + src/controller/scan/options.go | 4 +- src/core/main.go | 1 + src/jobservice/main.go | 1 + src/pkg/scan/handler.go | 44 +++++++++++++ src/pkg/scan/job.go | 70 ++++++++++---------- src/pkg/scan/job_test.go | 71 ++++++++++++++++++++- src/pkg/scan/rest/v1/models.go | 19 ++++++ src/pkg/scan/util.go | 4 +- src/pkg/scan/util_test.go | 11 ++-- src/pkg/scan/vuln/report.go | 3 + src/pkg/scan/vulnerability/vul.go | 57 +++++++++++++++++ src/pkg/scan/vulnerability/vul_test.go | 52 +++++++++++++++ 14 files changed, 303 insertions(+), 58 deletions(-) create mode 100644 src/pkg/scan/handler.go create mode 100644 src/pkg/scan/vulnerability/vul.go create mode 100644 src/pkg/scan/vulnerability/vul_test.go diff --git a/src/controller/scan/base_controller.go b/src/controller/scan/base_controller.go index e79896959..be2b371d4 100644 --- a/src/controller/scan/base_controller.go +++ b/src/controller/scan/base_controller.go @@ -25,7 +25,6 @@ import ( "github.com/google/uuid" - "github.com/goharbor/harbor/src/common/rbac" ar "github.com/goharbor/harbor/src/controller/artifact" "github.com/goharbor/harbor/src/controller/event/operator" "github.com/goharbor/harbor/src/controller/robot" @@ -91,6 +90,7 @@ type launchScanJobParam struct { Artifact *ar.Artifact Tag string Reports []*scan.Report + Type string } // basicController is default implementation of api.Controller interface @@ -287,6 +287,7 @@ func (bc *basicController) Scan(ctx context.Context, artifact *ar.Artifact, opti Artifact: art, Tag: tag, Reports: reports, + Type: opts.GetScanType(), }) } } @@ -912,7 +913,7 @@ func (bc *basicController) GetVulnerable(ctx context.Context, artifact *ar.Artif } // makeRobotAccount creates a robot account based on the arguments for scanning. -func (bc *basicController) makeRobotAccount(ctx context.Context, projectID int64, repository string, registration *scanner.Registration) (*robot.Robot, error) { +func (bc *basicController) makeRobotAccount(ctx context.Context, projectID int64, repository string, registration *scanner.Registration, permission []*types.Policy) (*robot.Robot, error) { // Use uuid as name to avoid duplicated entries. UUID, err := bc.uuid() if err != nil { @@ -934,16 +935,7 @@ func (bc *basicController) makeRobotAccount(ctx context.Context, projectID int64 { Kind: "project", Namespace: projectName, - Access: []*types.Policy{ - { - Resource: rbac.ResourceRepository, - Action: rbac.ActionPull, - }, - { - Resource: rbac.ResourceRepository, - Action: rbac.ActionScannerPull, - }, - }, + Access: permission, }, }, } @@ -980,7 +972,12 @@ func (bc *basicController) launchScanJob(ctx context.Context, param *launchScanJ return errors.Wrap(err, "scan controller: launch scan job") } - robot, err := bc.makeRobotAccount(ctx, param.Artifact.ProjectID, param.Artifact.RepositoryName, param.Registration) + // Get Scanner handler by scan type to separate the scan logic for different scan types + handler := sca.GetScanHandler(param.Type) + if handler == nil { + return fmt.Errorf("failed to get scan handler, type is %v", param.Type) + } + robot, err := bc.makeRobotAccount(ctx, param.Artifact.ProjectID, param.Artifact.RepositoryName, param.Registration, handler.RequiredPermissions()) if err != nil { return errors.Wrap(err, "scan controller: launch scan job") } diff --git a/src/controller/scan/base_controller_test.go b/src/controller/scan/base_controller_test.go index 2858b7090..ced106577 100644 --- a/src/controller/scan/base_controller_test.go +++ b/src/controller/scan/base_controller_test.go @@ -45,6 +45,7 @@ import ( "github.com/goharbor/harbor/src/pkg/scan/dao/scanner" v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" "github.com/goharbor/harbor/src/pkg/scan/vuln" + _ "github.com/goharbor/harbor/src/pkg/scan/vulnerability" "github.com/goharbor/harbor/src/pkg/task" artifacttesting "github.com/goharbor/harbor/src/testing/controller/artifact" robottesting "github.com/goharbor/harbor/src/testing/controller/robot" diff --git a/src/controller/scan/options.go b/src/controller/scan/options.go index f62e2205e..82e4e3d3e 100644 --- a/src/controller/scan/options.go +++ b/src/controller/scan/options.go @@ -14,6 +14,8 @@ package scan +import v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" + // Options keep the settings/configurations for scanning. type Options struct { ExecutionID int64 // The execution id to scan artifact @@ -24,7 +26,7 @@ type Options struct { // GetScanType returns the scan type. for backward compatibility, the default type is vulnerability. func (o *Options) GetScanType() string { if len(o.ScanType) == 0 { - o.ScanType = "vulnerability" + o.ScanType = v1.ScanTypeVulnerability } return o.ScanType } diff --git a/src/core/main.go b/src/core/main.go index 50e6a4566..cb2676135 100644 --- a/src/core/main.go +++ b/src/core/main.go @@ -70,6 +70,7 @@ import ( "github.com/goharbor/harbor/src/pkg/oidc" "github.com/goharbor/harbor/src/pkg/scan" "github.com/goharbor/harbor/src/pkg/scan/dao/scanner" + _ "github.com/goharbor/harbor/src/pkg/scan/vulnerability" pkguser "github.com/goharbor/harbor/src/pkg/user" "github.com/goharbor/harbor/src/pkg/version" "github.com/goharbor/harbor/src/server" diff --git a/src/jobservice/main.go b/src/jobservice/main.go index 42d531546..f288efea7 100644 --- a/src/jobservice/main.go +++ b/src/jobservice/main.go @@ -36,6 +36,7 @@ import ( _ "github.com/goharbor/harbor/src/pkg/accessory/model/subject" _ "github.com/goharbor/harbor/src/pkg/config/inmemory" _ "github.com/goharbor/harbor/src/pkg/config/rest" + _ "github.com/goharbor/harbor/src/pkg/scan/vulnerability" ) func main() { diff --git a/src/pkg/scan/handler.go b/src/pkg/scan/handler.go new file mode 100644 index 000000000..7ddba595d --- /dev/null +++ b/src/pkg/scan/handler.go @@ -0,0 +1,44 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package scan + +import ( + "time" + + "github.com/goharbor/harbor/src/jobservice/job" + "github.com/goharbor/harbor/src/pkg/permission/types" + "github.com/goharbor/harbor/src/pkg/robot/model" + "github.com/goharbor/harbor/src/pkg/scan/dao/scan" + v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" +) + +var handlerRegistry = map[string]Handler{} + +// RegisterScanHanlder register scanner handler +func RegisterScanHanlder(requestType string, handler Handler) { + handlerRegistry[requestType] = handler +} + +// GetScanHandler get the handler +func GetScanHandler(requestType string) Handler { + return handlerRegistry[requestType] +} + +// Handler handler for scan job, it could be implement by different scan type, such as vulnerability, sbom +type Handler interface { + RequiredPermissions() []*types.Policy + // PostScan defines the operation after scan + PostScan(ctx job.Context, sr *v1.ScanRequest, rp *scan.Report, rawReport string, startTime time.Time, robot *model.Robot) (string, error) +} diff --git a/src/pkg/scan/job.go b/src/pkg/scan/job.go index b1ad1905d..c21b48cb2 100644 --- a/src/pkg/scan/job.go +++ b/src/pkg/scan/job.go @@ -16,6 +16,7 @@ package scan import ( "bytes" + "context" "encoding/base64" "encoding/json" "fmt" @@ -34,8 +35,8 @@ import ( "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/pkg/robot/model" + "github.com/goharbor/harbor/src/pkg/scan/dao/scan" "github.com/goharbor/harbor/src/pkg/scan/dao/scanner" - "github.com/goharbor/harbor/src/pkg/scan/postprocessors" "github.com/goharbor/harbor/src/pkg/scan/report" v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" ) @@ -145,6 +146,7 @@ func (j *Job) Validate(params job.Parameters) error { func (j *Job) Run(ctx job.Context, params job.Parameters) error { // Get logger myLogger := ctx.GetLogger() + startTime := time.Now() // shouldStop checks if the job should be stopped shouldStop := func() bool { @@ -160,6 +162,11 @@ func (j *Job) Run(ctx job.Context, params job.Parameters) error { r, _ := extractRegistration(params) req, _ := ExtractScanReq(params) mimeTypes, _ := extractMimeTypes(params) + scanType := v1.ScanTypeVulnerability + if len(req.RequestType) > 0 { + scanType = req.RequestType[0].Type + } + handler := GetScanHandler(scanType) // Print related infos to log printJSONParameter(JobParamRegistration, removeRegistrationAuthInfo(r), myLogger) @@ -235,30 +242,19 @@ func (j *Job) Run(ctx job.Context, params job.Parameters) error { } myLogger.Debugf("check scan report for mime %s at %s", m, t.Format("2006/01/02 15:04:05")) - - rawReport, err := client.GetScanReport(resp.ID, m) + rawReport, err := fetchScanReportFromScanner(client, resp.ID, m) if err != nil { // Not ready yet if notReadyErr, ok := err.(*v1.ReportNotReadyError); ok { // Reset to the new check interval tm.Reset(time.Duration(notReadyErr.RetryAfter) * time.Second) myLogger.Infof("Report with mime type %s is not ready yet, retry after %d seconds", m, notReadyErr.RetryAfter) - continue } - - errs[i] = errors.Wrap(err, fmt.Sprintf("check scan report with mime type %s", m)) + errs[i] = errors.Wrap(err, fmt.Sprintf("scan job: fetch scan report, mimetype %v", m)) return } - - // Make sure the data is aligned with the v1 spec. - if _, err = report.ResolveData(m, []byte(rawReport)); err != nil { - errs[i] = errors.Wrap(err, "scan job: resolve report data") - return - } - rawReports[i] = rawReport - return case <-ctx.SystemContext().Done(): // Terminated by system @@ -292,33 +288,19 @@ func (j *Job) Run(ctx job.Context, params job.Parameters) error { // Log error to the job log if err != nil { myLogger.Error(err) - return err } for i, mimeType := range mimeTypes { - reports, err := report.Mgr.GetBy(ctx.SystemContext(), req.Artifact.Digest, r.UUID, []string{mimeType}) + rp, err := getReportPlaceholder(ctx.SystemContext(), req.Artifact.Digest, r.UUID, mimeType, myLogger) if err != nil { - myLogger.Error("Failed to get report for artifact %s of mimetype %s, error %v", req.Artifact.Digest, mimeType, err) - return err } + myLogger.Debugf("Converting report ID %s to the new V2 schema", rp.UUID) - if len(reports) == 0 { - myLogger.Error("No report found for artifact %s of mimetype %s, error %v", req.Artifact.Digest, mimeType, err) - - return errors.NotFoundError(nil).WithMessage("no report found to update data") - } - - rp := reports[0] - - logger.Debugf("Converting report ID %s to the new V2 schema", rp.UUID) - - // use a new ormer here to use the short db connection - _, reportData, err := postprocessors.Converter.ToRelationalSchema(ctx.SystemContext(), rp.UUID, rp.RegistrationUUID, rp.Digest, rawReports[i]) + reportData, err := handler.PostScan(ctx, req, rp, rawReports[i], startTime, robotAccount) if err != nil { myLogger.Errorf("Failed to convert vulnerability data to new schema for report %s, error %v", rp.UUID, err) - return err } @@ -328,7 +310,6 @@ func (j *Job) Run(ctx job.Context, params job.Parameters) error { // would be redundant if err := report.Mgr.UpdateReportData(ctx.SystemContext(), rp.UUID, reportData); err != nil { myLogger.Errorf("Failed to update report data for report %s, error %v", rp.UUID, err) - return err } @@ -338,6 +319,31 @@ func (j *Job) Run(ctx job.Context, params job.Parameters) error { return nil } +func getReportPlaceholder(ctx context.Context, digest string, reportUUID string, mimeType string, logger logger.Interface) (*scan.Report, error) { + reports, err := report.Mgr.GetBy(ctx, digest, reportUUID, []string{mimeType}) + if err != nil { + logger.Error("Failed to get report for artifact %s of mimetype %s, error %v", digest, mimeType, err) + return nil, err + } + if len(reports) == 0 { + logger.Errorf("No report found for artifact %s of mimetype %s, error %v", digest, mimeType, err) + return nil, errors.NotFoundError(nil).WithMessage("no report found to update data") + } + return reports[0], nil +} + +func fetchScanReportFromScanner(client v1.Client, requestID string, m string) (rawReport string, err error) { + rawReport, err = client.GetScanReport(requestID, m) + if err != nil { + return "", err + } + // Make sure the data is aligned with the v1 spec. + if _, err = report.ResolveData(m, []byte(rawReport)); err != nil { + return "", err + } + return rawReport, nil +} + // ExtractScanReq extracts the scan request from the job parameters. func ExtractScanReq(params job.Parameters) (*v1.ScanRequest, error) { v, ok := params[JobParameterRequest] diff --git a/src/pkg/scan/job_test.go b/src/pkg/scan/job_test.go index d29c35fde..92571285d 100644 --- a/src/pkg/scan/job_test.go +++ b/src/pkg/scan/job_test.go @@ -19,15 +19,19 @@ import ( "testing" "time" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/goharbor/harbor/src/controller/robot" "github.com/goharbor/harbor/src/jobservice/job" "github.com/goharbor/harbor/src/pkg/robot/model" + "github.com/goharbor/harbor/src/pkg/scan/dao/scan" "github.com/goharbor/harbor/src/pkg/scan/dao/scanner" + "github.com/goharbor/harbor/src/pkg/scan/report" v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" "github.com/goharbor/harbor/src/pkg/scan/vuln" + htesting "github.com/goharbor/harbor/src/testing" mockjobservice "github.com/goharbor/harbor/src/testing/jobservice" mocktesting "github.com/goharbor/harbor/src/testing/mock" v1testing "github.com/goharbor/harbor/src/testing/pkg/scan/rest/v1" @@ -35,10 +39,11 @@ import ( // JobTestSuite is a test suite to test the scan job. type JobTestSuite struct { - suite.Suite + htesting.Suite defaultClientPool v1.ClientPool mcp *v1testing.ClientPool + reportIDs []string } // TestJob is the entry of JobTestSuite. @@ -48,6 +53,7 @@ func TestJob(t *testing.T) { // SetupSuite sets up test env for JobTestSuite. func (suite *JobTestSuite) SetupSuite() { + suite.Suite.SetupSuite() mcp := &v1testing.ClientPool{} suite.defaultClientPool = v1.DefaultClientPool v1.DefaultClientPool = mcp @@ -55,9 +61,12 @@ func (suite *JobTestSuite) SetupSuite() { suite.mcp = mcp } -// TeraDownSuite clears test env for TeraDownSuite. -func (suite *JobTestSuite) TeraDownSuite() { +// TearDownSuite clears test env for TearDownSuite. +func (suite *JobTestSuite) TearDownSuite() { v1.DefaultClientPool = suite.defaultClientPool + for _, id := range suite.reportIDs { + _ = report.Mgr.Delete(suite.Context(), id) + } } // TestJob tests the scan job @@ -151,3 +160,59 @@ func (suite *JobTestSuite) TestJob() { err = j.Run(ctx, jp) require.NoError(suite.T(), err) } + +func (suite *JobTestSuite) TestgetReportPlaceholder() { + dgst := "sha256:mydigest" + uuid := `7f20b1b9-6117-4a2e-820b-e4cc0401f15e` + scannerUUID := `7f20b1b9-6117-4a2e-820b-e4cc0401f15f` + rpt := &scan.Report{ + UUID: uuid, + RegistrationUUID: scannerUUID, + Digest: dgst, + MimeType: v1.MimeTypeDockerArtifact, + } + ctx := suite.Context() + rptID, err := report.Mgr.Create(ctx, rpt) + suite.reportIDs = append(suite.reportIDs, rptID) + require.NoError(suite.T(), err) + jobLogger := &mockjobservice.MockJobLogger{} + report, err := getReportPlaceholder(ctx, dgst, scannerUUID, v1.MimeTypeDockerArtifact, jobLogger) + require.NoError(suite.T(), err) + require.NotNil(suite.T(), report) +} + +func (suite *JobTestSuite) TestfetchScanReportFromScanner() { + vulnRpt := &vuln.Report{ + GeneratedAt: time.Now().UTC().String(), + Scanner: &v1.Scanner{ + Name: "Trivy", + Vendor: "Harbor", + Version: "0.1.0", + }, + Severity: vuln.High, + } + rptContent, err := json.Marshal(vulnRpt) + require.NoError(suite.T(), err) + rawContent := string(rptContent) + ctx := suite.Context() + dgst := "sha256:mydigest" + uuid := `7f20b1b9-6117-4a2e-820b-e4cc0401f15a` + scannerUUID := `7f20b1b9-6117-4a2e-820b-e4cc0401f15b` + rpt := &scan.Report{ + UUID: uuid, + RegistrationUUID: scannerUUID, + Digest: dgst, + MimeType: v1.MimeTypeDockerArtifact, + Report: rawContent, + } + + ctx = suite.Context() + rptID, err := report.Mgr.Create(ctx, rpt) + suite.reportIDs = append(suite.reportIDs, rptID) + require.NoError(suite.T(), err) + client := &v1testing.Client{} + client.On("GetScanReport", mock.Anything, v1.MimeTypeGenericVulnerabilityReport).Return(rawContent, nil) + rawRept, err := fetchScanReportFromScanner(client, "abc", v1.MimeTypeGenericVulnerabilityReport) + require.NoError(suite.T(), err) + require.Equal(suite.T(), rawContent, rawRept) +} diff --git a/src/pkg/scan/rest/v1/models.go b/src/pkg/scan/rest/v1/models.go index d7dce069e..fc48717fb 100644 --- a/src/pkg/scan/rest/v1/models.go +++ b/src/pkg/scan/rest/v1/models.go @@ -21,6 +21,13 @@ import ( "github.com/goharbor/harbor/src/lib/errors" ) +const ( + // ScanTypeVulnerability the scan type for vulnerability + ScanTypeVulnerability = "vulnerability" + // ScanTypeSbom the scan type for sbom + ScanTypeSbom = "sbom" +) + // Scanner represents metadata of a Scanner Adapter which allow Harbor to lookup a scanner capable of // scanning a given Artifact stored in its registry and making sure that it can interpret a // returned result. @@ -173,6 +180,18 @@ type ScanRequest struct { Registry *Registry `json:"registry"` // Artifact to be scanned. Artifact *Artifact `json:"artifact"` + // RequestType + RequestType []*ScanType `json:"enabled_capabilities"` +} + +// ScanType represent the type of the scan request +type ScanType struct { + // Type sets the type of the scan, it could be sbom or vulnerability, default is vulnerability + Type string `json:"type"` + // ProducesMimeTypes defines scanreport should be + ProducesMimeTypes []string `json:"produces_mime_types"` + // Parameters extra parameters + Parameters map[string]interface{} `json:"parameters"` } // FromJSON parses ScanRequest from json data diff --git a/src/pkg/scan/util.go b/src/pkg/scan/util.go index ac19d92a0..efec3eb61 100644 --- a/src/pkg/scan/util.go +++ b/src/pkg/scan/util.go @@ -30,7 +30,7 @@ import ( "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/goharbor/harbor/src/controller/robot" + "github.com/goharbor/harbor/src/pkg/robot/model" v1sq "github.com/goharbor/harbor/src/pkg/scan/rest/v1" ) @@ -49,7 +49,7 @@ type referrer struct { } // GenAccessoryArt composes the accessory oci object and push it back to harbor core as an accessory of the scanned artifact. -func GenAccessoryArt(sq v1sq.ScanRequest, accData []byte, accAnnotations map[string]string, mediaType string, robot robot.Robot) (string, error) { +func GenAccessoryArt(sq v1sq.ScanRequest, accData []byte, accAnnotations map[string]string, mediaType string, robot *model.Robot) (string, error) { accArt, err := mutate.Append(empty.Image, mutate.Addendum{ Layer: static.NewLayer(accData, ocispec.MediaTypeImageLayer), History: v1.History{ diff --git a/src/pkg/scan/util_test.go b/src/pkg/scan/util_test.go index 519d6d68a..b53fad834 100644 --- a/src/pkg/scan/util_test.go +++ b/src/pkg/scan/util_test.go @@ -22,8 +22,7 @@ import ( "github.com/google/go-containerregistry/pkg/registry" "github.com/stretchr/testify/assert" - "github.com/goharbor/harbor/src/controller/robot" - rm "github.com/goharbor/harbor/src/pkg/robot/model" + "github.com/goharbor/harbor/src/pkg/robot/model" v1sq "github.com/goharbor/harbor/src/pkg/scan/rest/v1" ) @@ -47,11 +46,9 @@ func TestGenAccessoryArt(t *testing.T) { Digest: "sha256:d37ada95d47ad12224c205a938129df7a3e52345828b4fa27b03a98825d1e2e7", }, } - r := robot.Robot{ - Robot: rm.Robot{ - Name: "admin", - Secret: "Harbor12345", - }, + r := &model.Robot{ + Name: "admin", + Secret: "Harbor12345", } annotations := map[string]string{ diff --git a/src/pkg/scan/vuln/report.go b/src/pkg/scan/vuln/report.go index 0f24737b9..fbc119f64 100644 --- a/src/pkg/scan/vuln/report.go +++ b/src/pkg/scan/vuln/report.go @@ -33,6 +33,9 @@ type Report struct { Vulnerabilities []*VulnerabilityItem `json:"vulnerabilities"` vulnerabilityItemList *VulnerabilityItemList + + // SBOM sbom content + SBOM map[string]interface{} `json:"sbom,omitempty"` } // GetVulnerabilityItemList returns VulnerabilityItemList from the Vulnerabilities of report diff --git a/src/pkg/scan/vulnerability/vul.go b/src/pkg/scan/vulnerability/vul.go new file mode 100644 index 000000000..804659c09 --- /dev/null +++ b/src/pkg/scan/vulnerability/vul.go @@ -0,0 +1,57 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vulnerability + +import ( + "time" + + "github.com/goharbor/harbor/src/common/rbac" + "github.com/goharbor/harbor/src/jobservice/job" + "github.com/goharbor/harbor/src/pkg/permission/types" + "github.com/goharbor/harbor/src/pkg/robot/model" + scanJob "github.com/goharbor/harbor/src/pkg/scan" + "github.com/goharbor/harbor/src/pkg/scan/dao/scan" + "github.com/goharbor/harbor/src/pkg/scan/postprocessors" + v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" +) + +func init() { + scanJob.RegisterScanHanlder(v1.ScanTypeVulnerability, &ScanHandler{}) +} + +// ScanHandler defines the handler for scan vulnerability +type ScanHandler struct { +} + +// RequiredPermissions defines the permission used by the scan robot account +func (v *ScanHandler) RequiredPermissions() []*types.Policy { + return []*types.Policy{ + { + Resource: rbac.ResourceRepository, + Action: rbac.ActionPull, + }, + { + Resource: rbac.ResourceRepository, + Action: rbac.ActionScannerPull, + }, + } +} + +// PostScan ... +func (v *ScanHandler) PostScan(ctx job.Context, _ *v1.ScanRequest, origRp *scan.Report, rawReport string, _ time.Time, _ *model.Robot) (string, error) { + // use a new ormer here to use the short db connection + _, refreshedReport, err := postprocessors.Converter.ToRelationalSchema(ctx.SystemContext(), origRp.UUID, origRp.RegistrationUUID, origRp.Digest, rawReport) + return refreshedReport, err +} diff --git a/src/pkg/scan/vulnerability/vul_test.go b/src/pkg/scan/vulnerability/vul_test.go new file mode 100644 index 000000000..50d84287e --- /dev/null +++ b/src/pkg/scan/vulnerability/vul_test.go @@ -0,0 +1,52 @@ +package vulnerability + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "github.com/goharbor/harbor/src/common/rbac" + "github.com/goharbor/harbor/src/pkg/permission/types" + "github.com/goharbor/harbor/src/pkg/robot/model" + "github.com/goharbor/harbor/src/pkg/scan/dao/scan" + "github.com/goharbor/harbor/src/pkg/scan/postprocessors" + v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" + "github.com/goharbor/harbor/src/testing/jobservice" + postprocessorstesting "github.com/goharbor/harbor/src/testing/pkg/scan/postprocessors" +) + +func TestRequiredPermissions(t *testing.T) { + v := &ScanHandler{} + expected := []*types.Policy{ + { + Resource: rbac.ResourceRepository, + Action: rbac.ActionPull, + }, + { + Resource: rbac.ResourceRepository, + Action: rbac.ActionScannerPull, + }, + } + + result := v.RequiredPermissions() + + assert.Equal(t, expected, result, "RequiredPermissions should return correct permissions") +} + +func TestPostScan(t *testing.T) { + v := &ScanHandler{} + ctx := &jobservice.MockJobContext{} + artifact := &v1.Artifact{} + origRp := &scan.Report{} + rawReport := "" + + mocker := &postprocessorstesting.ScanReportV1ToV2Converter{} + mocker.On("ToRelationalSchema", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, "original report", nil) + postprocessors.Converter = mocker + sr := &v1.ScanRequest{Artifact: artifact} + refreshedReport, err := v.PostScan(ctx, sr, origRp, rawReport, time.Now(), &model.Robot{}) + assert.Equal(t, "", refreshedReport, "PostScan should return the refreshed report") + assert.Nil(t, err, "PostScan should not return an error") +} From 461a5fa50da856f5d5f940e64bfbc685607d05fb Mon Sep 17 00:00:00 2001 From: Wang Yan Date: Tue, 9 Apr 2024 16:07:47 +0800 Subject: [PATCH 073/145] add stop sbom scanning API (#20200) * add stop sbom scanning API 1. [UI] support to stop sbom scanning #20200 2. add type for stop scanning api, make it able to support both vulnerability and sbom. 3. refactor the db query to support multiple extra attributes. Signed-off-by: wang yan Signed-off-by: xuelichao Co-authored-by: xuelichao --- api/v2.0/swagger.yaml | 14 + src/controller/scan/base_controller.go | 5 +- src/controller/scan/base_controller_test.go | 8 +- src/controller/scan/controller.go | 3 +- src/pkg/task/dao/execution.go | 120 ++++--- src/pkg/task/dao/execution_test.go | 32 +- .../artifact/artifact-additions/models.ts | 1 + .../artifact-list-page.service.spec.ts | 93 ++++- .../artifact-list-page.service.ts | 77 ++++- .../artifact-list-tab.component.html | 62 +++- .../artifact-list-tab.component.scss | 4 + .../artifact-list-tab.component.spec.ts | 133 ++++++- .../artifact-list-tab.component.ts | 147 +++++++- .../repository/artifact/artifact.module.ts | 4 + .../artifact/sbom-scanning/sbom-overview.ts | 39 +++ .../sbom-scanning/sbom-scan-component.html | 38 ++ .../sbom-scanning/sbom-scan.component.spec.ts | 245 +++++++++++++ .../sbom-scanning/sbom-scan.component.ts | 327 ++++++++++++++++++ .../sbom-tip-histogram.component.html | 78 +++++ .../sbom-tip-histogram.component.scss | 60 ++++ .../sbom-tip-histogram.component.spec.ts | 103 ++++++ .../sbom-tip-histogram.component.ts | 111 ++++++ .../artifact/sbom-scanning/scanning.scss | 172 +++++++++ .../result-bar-chart.component.spec.ts | 157 +++++++++ .../result-bar-chart.component.ts | 10 +- .../vulnerability-scanning/scanning.scss | 2 +- .../services/event-service/event.service.ts | 3 + ...rtifact-detail-routing-resolver.service.ts | 3 +- .../src/app/shared/entities/shared.const.ts | 5 + .../src/app/shared/services/interface.ts | 10 + .../app/shared/services/permission-static.ts | 7 + src/portal/src/app/shared/units/utils.ts | 15 + src/portal/src/i18n/lang/de-de-lang.json | 37 ++ src/portal/src/i18n/lang/en-us-lang.json | 37 ++ src/portal/src/i18n/lang/es-es-lang.json | 37 ++ src/portal/src/i18n/lang/fr-fr-lang.json | 37 ++ src/portal/src/i18n/lang/ko-kr-lang.json | 37 ++ src/portal/src/i18n/lang/pt-br-lang.json | 37 ++ src/portal/src/i18n/lang/tr-tr-lang.json | 37 ++ src/portal/src/i18n/lang/zh-cn-lang.json | 37 ++ src/portal/src/i18n/lang/zh-tw-lang.json | 37 ++ src/server/v2.0/handler/scan.go | 2 +- src/server/v2.0/handler/scan_test.go | 9 +- src/testing/controller/scan/controller.go | 10 +- tests/apitests/python/library/scan_stop.py | 4 +- tests/ci/api_run.sh | 2 +- 46 files changed, 2357 insertions(+), 91 deletions(-) create mode 100644 src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-overview.ts create mode 100644 src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan-component.html create mode 100644 src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan.component.spec.ts create mode 100644 src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan.component.ts create mode 100644 src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.html create mode 100644 src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.scss create mode 100644 src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.spec.ts create mode 100644 src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.ts create mode 100644 src/portal/src/app/base/project/repository/artifact/sbom-scanning/scanning.scss diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index 8c23e29f3..ce4210ec8 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -1206,6 +1206,12 @@ paths: - $ref: '#/parameters/projectName' - $ref: '#/parameters/repositoryName' - $ref: '#/parameters/reference' + - name: scanType + in: body + required: true + schema: + $ref: '#/definitions/ScanType' + description: 'The scan type: Vulnerabilities, SBOM' responses: '202': $ref: '#/responses/202' @@ -9980,3 +9986,11 @@ definitions: items: type: string description: Links of the vulnerability + + ScanType: + type: object + properties: + scan_type: + type: string + description: 'The scan type for the scan request. Two options are currently supported, vulnerability and sbom' + enum: [ vulnerability, sbom ] \ No newline at end of file diff --git a/src/controller/scan/base_controller.go b/src/controller/scan/base_controller.go index be2b371d4..a57d1f21b 100644 --- a/src/controller/scan/base_controller.go +++ b/src/controller/scan/base_controller.go @@ -340,15 +340,16 @@ func (bc *basicController) Scan(ctx context.Context, artifact *ar.Artifact, opti } // Stop scan job of a given artifact -func (bc *basicController) Stop(ctx context.Context, artifact *ar.Artifact) error { +func (bc *basicController) Stop(ctx context.Context, artifact *ar.Artifact, capType string) error { if artifact == nil { return errors.New("nil artifact to stop scan") } - query := q.New(q.KeyWords{"extra_attrs.artifact.digest": artifact.Digest}) + query := q.New(q.KeyWords{"vendor_type": job.ImageScanJobVendorType, "extra_attrs.artifact.digest": artifact.Digest, "extra_attrs.enabled_capabilities.type": capType}) executions, err := bc.execMgr.List(ctx, query) if err != nil { return err } + if len(executions) == 0 { message := fmt.Sprintf("no scan job for artifact digest=%v", artifact.Digest) return errors.BadRequestError(nil).WithMessage(message) diff --git a/src/controller/scan/base_controller_test.go b/src/controller/scan/base_controller_test.go index ced106577..9d9f2c334 100644 --- a/src/controller/scan/base_controller_test.go +++ b/src/controller/scan/base_controller_test.go @@ -381,7 +381,7 @@ func (suite *ControllerTestSuite) TestScanControllerScan() { func (suite *ControllerTestSuite) TestScanControllerStop() { { // artifact not provieded - suite.Require().Error(suite.c.Stop(context.TODO(), nil)) + suite.Require().Error(suite.c.Stop(context.TODO(), nil, "vulnerability")) } { @@ -393,7 +393,7 @@ func (suite *ControllerTestSuite) TestScanControllerStop() { ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{}) - suite.Require().NoError(suite.c.Stop(ctx, suite.artifact)) + suite.Require().NoError(suite.c.Stop(ctx, suite.artifact, "vulnerability")) } { @@ -403,7 +403,7 @@ func (suite *ControllerTestSuite) TestScanControllerStop() { ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{}) - suite.Require().Error(suite.c.Stop(ctx, suite.artifact)) + suite.Require().Error(suite.c.Stop(ctx, suite.artifact, "vulnerability")) } { @@ -412,7 +412,7 @@ func (suite *ControllerTestSuite) TestScanControllerStop() { ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{}) - suite.Require().Error(suite.c.Stop(ctx, suite.artifact)) + suite.Require().Error(suite.c.Stop(ctx, suite.artifact, "vulnerability")) } } diff --git a/src/controller/scan/controller.go b/src/controller/scan/controller.go index b890ff7d3..625e8f86f 100644 --- a/src/controller/scan/controller.go +++ b/src/controller/scan/controller.go @@ -55,10 +55,11 @@ type Controller interface { // Arguments: // ctx context.Context : the context for this method // artifact *artifact.Artifact : the artifact whose scan job to be stopped + // capType string : the capability type of the scanner, vulnerability or SBOM. // // Returns: // error : non nil error if any errors occurred - Stop(ctx context.Context, artifact *artifact.Artifact) error + Stop(ctx context.Context, artifact *artifact.Artifact, capType string) error // GetReport gets the reports for the given artifact identified by the digest // diff --git a/src/pkg/task/dao/execution.go b/src/pkg/task/dao/execution.go index cd26e792e..8cd66e75a 100644 --- a/src/pkg/task/dao/execution.go +++ b/src/pkg/task/dao/execution.go @@ -343,6 +343,12 @@ func (e *executionDAO) refreshStatus(ctx context.Context, id int64) (bool, strin return status != execution.Status, status, false, err } +type jsonbStru struct { + keyPrefix string + key string + value interface{} +} + func (e *executionDAO) querySetter(ctx context.Context, query *q.Query) (orm.QuerySeter, error) { qs, err := orm.QuerySetter(ctx, &Execution{}, query) if err != nil { @@ -352,39 +358,32 @@ func (e *executionDAO) querySetter(ctx context.Context, query *q.Query) (orm.Que // append the filter for "extra attrs" if query != nil && len(query.Keywords) > 0 { var ( - key string - keyPrefix string - value interface{} + jsonbStrus []jsonbStru + args []interface{} ) - for key, value = range query.Keywords { - if strings.HasPrefix(key, "ExtraAttrs.") { - keyPrefix = "ExtraAttrs." - break + + for key, value := range query.Keywords { + if strings.HasPrefix(key, "ExtraAttrs.") && key != "ExtraAttrs." { + jsonbStrus = append(jsonbStrus, jsonbStru{ + keyPrefix: "ExtraAttrs.", + key: key, + value: value, + }) } - if strings.HasPrefix(key, "extra_attrs.") { - keyPrefix = "extra_attrs." - break + if strings.HasPrefix(key, "extra_attrs.") && key != "extra_attrs." { + jsonbStrus = append(jsonbStrus, jsonbStru{ + keyPrefix: "extra_attrs.", + key: key, + value: value, + }) } } - if len(keyPrefix) == 0 || keyPrefix == key { + if len(jsonbStrus) == 0 { return qs, nil } - // key with keyPrefix supports multi-level query operator on PostgreSQL JSON data - // examples: - // key = extra_attrs.id, - // ==> sql = "select id from execution where extra_attrs->>?=?", args = {id, value} - // key = extra_attrs.artifact.digest - // ==> sql = "select id from execution where extra_attrs->?->>?=?", args = {artifact, id, value} - // key = extra_attrs.a.b.c - // ==> sql = "select id from execution where extra_attrs->?->?->>?=?", args = {a, b, c, value} - keys := strings.Split(strings.TrimPrefix(key, keyPrefix), ".") - var args []interface{} - for _, item := range keys { - args = append(args, item) - } - args = append(args, value) - inClause, err := orm.CreateInClause(ctx, buildInClauseSQLForExtraAttrs(keys), args...) + idSQL, args := buildInClauseSQLForExtraAttrs(jsonbStrus) + inClause, err := orm.CreateInClause(ctx, idSQL, args...) if err != nil { return nil, err } @@ -395,23 +394,60 @@ func (e *executionDAO) querySetter(ctx context.Context, query *q.Query) (orm.Que } // Param keys is strings.Split() after trim "extra_attrs."/"ExtraAttrs." prefix -func buildInClauseSQLForExtraAttrs(keys []string) string { - switch len(keys) { - case 0: - // won't fall into this case, as the if condition on "keyPrefix == key" - // act as a place holder to ensure "default" is equivalent to "len(keys) >= 2" - return "" - case 1: - return "select id from execution where extra_attrs->>?=?" - default: - // len(keys) >= 2 - elements := make([]string, len(keys)-1) - for i := range elements { - elements[i] = "?" - } - s := strings.Join(elements, "->") - return fmt.Sprintf("select id from execution where extra_attrs->%s->>?=?", s) +// key with keyPrefix supports multi-level query operator on PostgreSQL JSON data +// examples: +// key = extra_attrs.id, +// +// ==> sql = "select id from execution where extra_attrs->>?=?", args = {id, value} +// +// key = extra_attrs.artifact.digest +// +// ==> sql = "select id from execution where extra_attrs->?->>?=?", args = {artifact, id, value} +// +// key = extra_attrs.a.b.c +// +// ==> sql = "select id from execution where extra_attrs->?->?->>?=?", args = {a, b, c, value} +func buildInClauseSQLForExtraAttrs(jsonbStrus []jsonbStru) (string, []interface{}) { + if len(jsonbStrus) == 0 { + return "", nil } + + var cond string + var args []interface{} + sql := "select id from execution where" + + for i, jsonbStr := range jsonbStrus { + if jsonbStr.key == "" || jsonbStr.value == "" { + return "", nil + } + keys := strings.Split(strings.TrimPrefix(jsonbStr.key, jsonbStr.keyPrefix), ".") + if len(keys) == 1 { + if i == 0 { + cond += "extra_attrs->>?=?" + } else { + cond += " and extra_attrs->>?=?" + } + } + if len(keys) >= 2 { + elements := make([]string, len(keys)-1) + for i := range elements { + elements[i] = "?" + } + s := strings.Join(elements, "->") + if i == 0 { + cond += fmt.Sprintf("extra_attrs->%s->>?=?", s) + } else { + cond += fmt.Sprintf(" and extra_attrs->%s->>?=?", s) + } + } + + for _, item := range keys { + args = append(args, item) + } + args = append(args, jsonbStr.value) + } + + return fmt.Sprintf("%s %s", sql, cond), args } func buildExecStatusOutdateKey(id int64, vendor string) string { diff --git a/src/pkg/task/dao/execution_test.go b/src/pkg/task/dao/execution_test.go index 732286eab..edf711d84 100644 --- a/src/pkg/task/dao/execution_test.go +++ b/src/pkg/task/dao/execution_test.go @@ -395,22 +395,36 @@ func TestExecutionDAOSuite(t *testing.T) { } func Test_buildInClauseSQLForExtraAttrs(t *testing.T) { - type args struct { - keys []string - } tests := []struct { name string - args args + args []jsonbStru want string }{ - {"extra_attrs.", args{[]string{}}, ""}, - {"extra_attrs.id", args{[]string{"id"}}, "select id from execution where extra_attrs->>?=?"}, - {"extra_attrs.artifact.digest", args{[]string{"artifact", "digest"}}, "select id from execution where extra_attrs->?->>?=?"}, - {"extra_attrs.a.b.c", args{[]string{"a", "b", "c"}}, "select id from execution where extra_attrs->?->?->>?=?"}, + {"extra_attrs.", []jsonbStru{}, ""}, + {"extra_attrs.", []jsonbStru{{}}, ""}, + {"extra_attrs.id", []jsonbStru{{ + keyPrefix: "extra_attrs.", + key: "extra_attrs.id", + value: "1", + }}, "select id from execution where extra_attrs->>?=?"}, + {"extra_attrs.artifact.digest", []jsonbStru{{ + keyPrefix: "extra_attrs.", + key: "extra_attrs.artifact.digest", + value: "sha256:1234", + }}, "select id from execution where extra_attrs->?->>?=?"}, + {"extra_attrs.a.b.c", []jsonbStru{{ + keyPrefix: "extra_attrs.", + key: "extra_attrs.a.b.c", + value: "test_value_1", + }, { + keyPrefix: "extra_attrs.", + key: "extra_attrs.d.e", + value: "test_value_2", + }}, "select id from execution where extra_attrs->?->?->>?=? and extra_attrs->?->>?=?"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := buildInClauseSQLForExtraAttrs(tt.args.keys); got != tt.want { + if got, _ := buildInClauseSQLForExtraAttrs(tt.args); got != tt.want { t.Errorf("buildInClauseSQLForExtraAttrs() = %v, want %v", got, tt.want) } }) diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/models.ts b/src/portal/src/app/base/project/repository/artifact/artifact-additions/models.ts index 84bf9bf09..dc7ec0a45 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-additions/models.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/models.ts @@ -18,4 +18,5 @@ export enum ADDITIONS { SUMMARY = 'readme.md', VALUES = 'values.yaml', DEPENDENCIES = 'dependencies', + SBOMS = 'sboms', } diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list-page.service.spec.ts b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list-page.service.spec.ts index 6085c7cd4..2d01b1f34 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list-page.service.spec.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list-page.service.spec.ts @@ -1,12 +1,56 @@ import { inject, TestBed } from '@angular/core/testing'; import { ArtifactListPageService } from './artifact-list-page.service'; import { SharedTestingModule } from '../../../../../shared/shared.module'; +import { + ScanningResultService, + UserPermissionService, +} from 'src/app/shared/services'; +import { of } from 'rxjs'; +import { ClrLoadingState } from '@clr/angular'; describe('ArtifactListPageService', () => { + const FakedScanningResultService = { + getProjectScanner: () => + of({ + access_credential: '', + adapter: 'Trivy', + auth: '', + capabilities: { + support_sbom: true, + support_vulnerability: true, + }, + create_time: '2024-03-06T09:29:43.789Z', + description: 'The Trivy scanner adapter', + disabled: false, + health: 'healthy', + is_default: true, + name: 'Trivy', + skip_certVerify: false, + update_time: '2024-03-06T09:29:43.789Z', + url: 'http://trivy-adapter:8080', + use_internal_addr: true, + uuid: '10c68b62-db9c-11ee-9c72-0242ac130009', + vendor: 'Aqua Security', + version: 'v0.47.0', + }), + }; + const FakedUserPermissionService = { + hasProjectPermissions: () => of([true, true, true, true, true]), + }; beforeEach(() => { TestBed.configureTestingModule({ imports: [SharedTestingModule], - providers: [ArtifactListPageService], + providers: [ + ArtifactListPageService, + { + provide: ScanningResultService, + useValue: FakedScanningResultService, + }, + { + provide: UserPermissionService, + useValue: FakedUserPermissionService, + }, + ], }); }); @@ -16,4 +60,51 @@ describe('ArtifactListPageService', () => { expect(service).toBeTruthy(); } )); + it('Test ArtifactListPageService Permissions validation ', inject( + [ArtifactListPageService], + (service: ArtifactListPageService) => { + service.init(3); + expect(service.hasSbomPermission()).toBeTruthy(); + expect(service.hasAddLabelImagePermission()).toBeTruthy(); + expect(service.hasRetagImagePermission()).toBeTruthy(); + expect(service.hasDeleteImagePermission()).toBeTruthy(); + expect(service.hasScanImagePermission()).toBeTruthy(); + expect(service.hasScannerSupportVulnerability()).toBeTruthy(); + expect(service.hasScannerSupportSBOM()).toBeTruthy(); + } + )); + it('Test ArtifactListPageService updateStates', inject( + [ArtifactListPageService], + (service: ArtifactListPageService) => { + service.init(3); + expect(service.hasEnabledScanner()).toBeTruthy(); + expect(service.getScanBtnState()).toBe(ClrLoadingState.SUCCESS); + expect(service.getSbomBtnState()).toBe(ClrLoadingState.SUCCESS); + service.updateStates( + false, + ClrLoadingState.ERROR, + ClrLoadingState.ERROR + ); + expect(service.hasEnabledScanner()).toBeFalsy(); + expect(service.getScanBtnState()).toBe(ClrLoadingState.ERROR); + expect(service.getSbomBtnState()).toBe(ClrLoadingState.ERROR); + } + )); + it('Test ArtifactListPageService updateCapabilities ', inject( + [ArtifactListPageService], + (service: ArtifactListPageService) => { + service.updateCapabilities({ + support_vulnerability: true, + support_sbom: true, + }); + expect(service.hasScannerSupportVulnerability()).toBeTruthy(); + expect(service.hasScannerSupportSBOM()).toBeTruthy(); + service.updateCapabilities({ + support_vulnerability: false, + support_sbom: false, + }); + expect(service.hasScannerSupportVulnerability()).toBeFalsy(); + expect(service.hasScannerSupportSBOM()).toBeFalsy(); + } + )); }); diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list-page.service.ts b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list-page.service.ts index 701d4c453..a858938dd 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list-page.service.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list-page.service.ts @@ -10,11 +10,15 @@ import { ErrorHandler } from '../../../../../shared/units/error-handler'; @Injectable() export class ArtifactListPageService { private _scanBtnState: ClrLoadingState; + private _sbomBtnState: ClrLoadingState; private _hasEnabledScanner: boolean = false; + private _hasScannerSupportVulnerability: boolean = false; + private _hasScannerSupportSBOM: boolean = false; private _hasAddLabelImagePermission: boolean = false; private _hasRetagImagePermission: boolean = false; private _hasDeleteImagePermission: boolean = false; private _hasScanImagePermission: boolean = false; + private _hasSbomPermission: boolean = false; constructor( private scanningService: ScanningResultService, @@ -26,6 +30,10 @@ export class ArtifactListPageService { return this._scanBtnState; } + getSbomBtnState(): ClrLoadingState { + return this._sbomBtnState; + } + hasEnabledScanner(): boolean { return this._hasEnabledScanner; } @@ -46,14 +54,53 @@ export class ArtifactListPageService { return this._hasScanImagePermission; } + hasSbomPermission(): boolean { + return this._hasSbomPermission; + } + + hasScannerSupportVulnerability(): boolean { + return this._hasScannerSupportVulnerability; + } + + hasScannerSupportSBOM(): boolean { + return this._hasScannerSupportSBOM; + } + init(projectId: number) { this._getProjectScanner(projectId); this._getPermissionRule(projectId); } + updateStates( + enabledScanner: boolean, + scanState?: ClrLoadingState, + sbomState?: ClrLoadingState + ) { + if (scanState) { + this._scanBtnState = scanState; + } + if (sbomState) { + this._sbomBtnState = sbomState; + } + this._hasEnabledScanner = enabledScanner; + } + + updateCapabilities(capabilities?: any) { + if (capabilities) { + if (capabilities?.support_vulnerability !== undefined) { + this._hasScannerSupportVulnerability = + capabilities.support_vulnerability; + } + if (capabilities?.support_sbom !== undefined) { + this._hasScannerSupportSBOM = capabilities.support_sbom; + } + } + } + private _getProjectScanner(projectId: number): void { this._hasEnabledScanner = false; this._scanBtnState = ClrLoadingState.LOADING; + this._sbomBtnState = ClrLoadingState.LOADING; this.scanningService.getProjectScanner(projectId).subscribe( response => { if ( @@ -62,14 +109,28 @@ export class ArtifactListPageService { !response.disabled && response.health === 'healthy' ) { - this._scanBtnState = ClrLoadingState.SUCCESS; - this._hasEnabledScanner = true; + this.updateStates( + true, + ClrLoadingState.SUCCESS, + ClrLoadingState.SUCCESS + ); + if (response?.capabilities) { + this.updateCapabilities(response?.capabilities); + } } else { - this._scanBtnState = ClrLoadingState.ERROR; + this.updateStates( + false, + ClrLoadingState.ERROR, + ClrLoadingState.ERROR + ); } }, error => { - this._scanBtnState = ClrLoadingState.ERROR; + this.updateStates( + false, + ClrLoadingState.ERROR, + ClrLoadingState.ERROR + ); } ); } @@ -94,6 +155,11 @@ export class ArtifactListPageService { action: USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.VALUE .CREATE, }, + { + resource: USERSTATICPERMISSION.REPOSITORY_TAG_SBOM_JOB.KEY, + action: USERSTATICPERMISSION.REPOSITORY_TAG_SBOM_JOB.VALUE + .CREATE, + }, ]; this.userPermissionService .hasProjectPermissions(projectId, permissions) @@ -103,6 +169,9 @@ export class ArtifactListPageService { this._hasRetagImagePermission = results[1]; this._hasDeleteImagePermission = results[2]; this._hasScanImagePermission = results[3]; + this._hasSbomPermission = results?.[4] ?? false; + // TODO need to remove the static code + this._hasSbomPermission = true; }, error => this.errorHandlerService.error(error) ); diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.html b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.html index 6258f7a7d..1001da144 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.html +++ b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.html @@ -42,6 +42,34 @@   {{ 'VULNERABILITY.STOP_NOW' | translate }} + + - + + {{ 'REPOSITORY.SBOM' | translate }} + + + + {{ 'ARTIFACT.ANNOTATION' | translate }} - + {{ 'REPOSITORY.LABELS' | translate }} - + {{ 'REPOSITORY.PUSH_TIME' | translate }} - + {{ 'REPOSITORY.PULL_TIME' | translate }} @@ -389,6 +423,26 @@
+ +
+ + {{ 'ARTIFACT.SBOM_UNSUPPORTED' | translate }} + + + +
+
diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.scss b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.scss index b90fad677..23c95498d 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.scss +++ b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.scss @@ -161,6 +161,10 @@ width: 11rem !important; } +.sbom-column { + width: 6rem !important; +} + .annotations-column { width: 5rem !important; } diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.spec.ts b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.spec.ts index 9eef1097e..27010f065 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.spec.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.spec.ts @@ -13,7 +13,7 @@ import { ScanningResultDefaultService, ScanningResultService, } from '../../../../../../../shared/services'; -import { ArtifactFront as Artifact } from '../../../artifact'; +import { ArtifactFront as Artifact, ArtifactFront } from '../../../artifact'; import { ErrorHandler } from '../../../../../../../shared/units/error-handler'; import { OperationService } from '../../../../../../../shared/components/operation/operation.service'; import { ArtifactService as NewArtifactService } from '../../../../../../../../../ng-swagger-gen/services/artifact.service'; @@ -24,6 +24,10 @@ import { ArtifactListPageService } from '../../artifact-list-page.service'; import { ClrLoadingState } from '@clr/angular'; import { Accessory } from 'ng-swagger-gen/models/accessory'; import { ArtifactModule } from '../../../artifact.module'; +import { + SBOM_SCAN_STATUS, + VULNERABILITY_SCAN_STATUS, +} from 'src/app/shared/units/utils'; describe('ArtifactListTabComponent', () => { let comp: ArtifactListTabComponent; @@ -171,6 +175,16 @@ describe('ArtifactListTabComponent', () => { pull_time: '0001-01-01T00:00:00Z', }, ]; + const mockAccessory = { + id: 1, + artifact_id: 2, + subject_artifact_id: 3, + subject_artifact_digest: 'fakeDigest', + subject_artifact_repo: 'test', + size: 120, + digest: 'fakeDigest', + type: 'test', + }; const mockErrorHandler = { error: () => {}, }; @@ -236,9 +250,18 @@ describe('ArtifactListTabComponent', () => { getScanBtnState(): ClrLoadingState { return ClrLoadingState.DEFAULT; }, + getSbomBtnState(): ClrLoadingState { + return ClrLoadingState.DEFAULT; + }, hasEnabledScanner(): boolean { return true; }, + hasSbomPermission(): boolean { + return true; + }, + hasScannerSupportSBOM(): boolean { + return true; + }, hasAddLabelImagePermission(): boolean { return true; }, @@ -353,6 +376,27 @@ describe('ArtifactListTabComponent', () => { fixture.nativeElement.querySelector('.confirmation-title') ).toBeTruthy(); }); + it('Generate SBOM button should be disabled', async () => { + await fixture.whenStable(); + comp.selectedRow = [mockArtifacts[1]]; + await stepOpenAction(fixture, comp); + const generatedButton = + fixture.nativeElement.querySelector('#generate-sbom-btn'); + fixture.detectChanges(); + await fixture.whenStable(); + expect(generatedButton.disabled).toBeTruthy(); + }); + it('Stop SBOM button should be disabled', async () => { + await fixture.whenStable(); + comp.selectedRow = [mockArtifacts[1]]; + await stepOpenAction(fixture, comp); + const stopButton = + fixture.nativeElement.querySelector('#stop-sbom-btn'); + fixture.detectChanges(); + await fixture.whenStable().then(() => { + expect(stopButton.disabled).toBeTruthy(); + }); + }); it('the length of hide array should equal to the number of column', async () => { comp.loading = false; fixture.detectChanges(); @@ -360,6 +404,93 @@ describe('ArtifactListTabComponent', () => { const cols = fixture.nativeElement.querySelectorAll('.datagrid-column'); expect(cols.length).toEqual(comp.hiddenArray.length); }); + + it('Test isEllipsisActive', async () => { + fixture = TestBed.createComponent(ArtifactListTabComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + await fixture.whenStable().then(() => { + expect( + comp.isEllipsisActive(document.createElement('span')) + ).toBeFalsy(); + }); + }); + it('Test deleteAccessory', async () => { + fixture = TestBed.createComponent(ArtifactListTabComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + comp.deleteAccessory(mockAccessory); + fixture.detectChanges(); + await fixture.whenStable().then(() => { + expect( + fixture.nativeElement.querySelector('.confirmation-content') + ).toBeTruthy(); + }); + }); + it('Test scanNow', async () => { + fixture = TestBed.createComponent(ArtifactListTabComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + comp.selectedRow = mockArtifacts.slice(0, 1); + comp.scanNow(); + expect(comp.onScanArtifactsLength).toBe(1); + }); + it('Test stopNow', async () => { + fixture = TestBed.createComponent(ArtifactListTabComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + comp.selectedRow = mockArtifacts.slice(0, 1); + comp.stopNow(); + expect(comp.onStopScanArtifactsLength).toBe(1); + }); + it('Test stopSbom', async () => { + fixture = TestBed.createComponent(ArtifactListTabComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + comp.selectedRow = mockArtifacts.slice(0, 1); + comp.stopSbom(); + expect(comp.onStopSbomArtifactsLength).toBe(1); + }); + it('Test tagsString and isRunningState and canStopSbom and canStopScan', async () => { + fixture = TestBed.createComponent(ArtifactListTabComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + await fixture.whenStable(); + expect(comp.tagsString([])).toBeNull(); + expect( + comp.isRunningState(VULNERABILITY_SCAN_STATUS.RUNNING) + ).toBeTruthy(); + expect( + comp.isRunningState(VULNERABILITY_SCAN_STATUS.ERROR) + ).toBeFalsy(); + expect(comp.canStopSbom()).toBeFalsy(); + expect(comp.canStopScan()).toBeFalsy(); + }); + it('Test status and handleScanOverview', async () => { + fixture = TestBed.createComponent(ArtifactListTabComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + await fixture.whenStable(); + expect(comp.scanStatus(mockArtifacts[0])).toBe( + VULNERABILITY_SCAN_STATUS.ERROR + ); + expect(comp.sbomStatus(null)).toBe(SBOM_SCAN_STATUS.NOT_GENERATED_SBOM); + expect(comp.sbomStatus(mockArtifacts[0])).toBe( + SBOM_SCAN_STATUS.NOT_GENERATED_SBOM + ); + expect(comp.handleScanOverview(mockArtifacts[0])).not.toBeNull(); + }); + it('Test utils', async () => { + fixture = TestBed.createComponent(ArtifactListTabComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + await fixture.whenStable(); + expect(comp.selectedRowHasSbom()).toBeFalsy(); + expect(comp.selectedRowHasVul()).toBeFalsy(); + expect(comp.canScanNow()).toBeFalsy(); + expect(comp.hasEnabledSbom()).toBeTruthy(); + expect(comp.canAddLabel()).toBeFalsy(); + }); }); async function stepOpenAction(fixture, comp) { diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.ts b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.ts index 5d4ffad46..4675031dc 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.ts @@ -37,6 +37,7 @@ import { setHiddenArrayToLocalStorage, setPageSizeToLocalStorage, VULNERABILITY_SCAN_STATUS, + SBOM_SCAN_STATUS, } from '../../../../../../../shared/units/utils'; import { ErrorHandler } from '../../../../../../../shared/units/error-handler'; import { ArtifactService } from '../../../artifact.service'; @@ -76,7 +77,7 @@ import { EventService, HarborEvent, } from '../../../../../../../services/event-service/event.service'; -import { AppConfigService } from 'src/app/services/app-config.service'; +import { AppConfigService } from '../../../../../../../services/app-config.service'; import { ArtifactListPageService } from '../../artifact-list-page.service'; import { ACCESSORY_PAGE_SIZE } from './sub-accessories/sub-accessories.component'; import { Accessory } from 'ng-swagger-gen/models/accessory'; @@ -141,28 +142,60 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { get hasScanImagePermission(): boolean { return this.artifactListPageService.hasScanImagePermission(); } + get hasSbomPermission(): boolean { + return this.artifactListPageService.hasSbomPermission(); + } get hasEnabledScanner(): boolean { return this.artifactListPageService.hasEnabledScanner(); } + get hasScannerSupportVulnerability(): boolean { + return this.artifactListPageService.hasScannerSupportVulnerability(); + } + get hasScannerSupportSBOM(): boolean { + return this.artifactListPageService.hasScannerSupportSBOM(); + } get scanBtnState(): ClrLoadingState { return this.artifactListPageService.getScanBtnState(); } + get generateSbomBtnState(): ClrLoadingState { + return this.artifactListPageService.getSbomBtnState(); + } onSendingScanCommand: boolean; onSendingStopScanCommand: boolean = false; onStopScanArtifactsLength: number = 0; scanStoppedArtifactLength: number = 0; + + onSendingSbomCommand: boolean; + onSendingStopSbomCommand: boolean = false; + onStopSbomArtifactsLength: number = 0; + sbomStoppedArtifactLength: number = 0; + artifactDigest: string; depth: string; // could Pagination filter filters: string[]; scanFinishedArtifactLength: number = 0; onScanArtifactsLength: number = 0; + sbomFinishedArtifactLength: number = 0; + onSbomArtifactsLength: number = 0; stopBtnState: ClrLoadingState = ClrLoadingState.DEFAULT; updateArtifactSub: Subscription; hiddenArray: boolean[] = getHiddenArrayFromLocalStorage( PageSizeMapKeys.ARTIFACT_LIST_TAB_COMPONENT, - [false, false, false, false, false, false, true, false, false, false] + [ + false, + false, + false, + false, + false, + false, + true, + true, + false, + false, + false, + ] ); deleteAccessorySub: Subscription; copyDigestSub: Subscription; @@ -203,7 +236,8 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { } } ngOnInit() { - this.registryUrl = this.appConfigService.getConfig().registry_url; + const appConfig = this.appConfigService.getConfig(); + this.registryUrl = appConfig.registry_url; this.initRouterData(); if (!this.updateArtifactSub) { this.updateArtifactSub = this.eventService.subscribe( @@ -250,7 +284,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { this.copyDigestSub.unsubscribe(); this.copyDigestSub = null; } - this.datagrid['columnsService']?.columns?.forEach((item, index) => { + this.datagrid?.['columnsService']?.columns?.forEach((item, index) => { if (this.depth) { this.hiddenArray[index] = !!item?._value?.hidden; } else { @@ -326,6 +360,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { withImmutableStatus: true, withLabel: true, withScanOverview: true, + // withSbomOverview: true, withTag: false, XAcceptVulnerabilities: DEFAULT_SUPPORTED_MIME_TYPES, withAccessory: false, @@ -350,6 +385,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { withImmutableStatus: true, withLabel: true, withScanOverview: true, + // withSbomOverview: true, withTag: false, XAcceptVulnerabilities: DEFAULT_SUPPORTED_MIME_TYPES, @@ -381,7 +417,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { }); this.getArtifactTagsAsync(this.artifactList); this.getAccessoriesAsync(this.artifactList); - this.checkCosignAsync(this.artifactList); + this.checkCosignAndSbomAsync(this.artifactList); this.getIconsFromBackEnd(); }, error => { @@ -420,7 +456,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { this.artifactList = res.body; this.getArtifactTagsAsync(this.artifactList); this.getAccessoriesAsync(this.artifactList); - this.checkCosignAsync(this.artifactList); + this.checkCosignAndSbomAsync(this.artifactList); this.getIconsFromBackEnd(); }, error => { @@ -519,6 +555,14 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { return formatSize(tagSize); } + hasEnabledSbom(): boolean { + return ( + this.hasScannerSupportSBOM && + this.hasEnabledScanner && + this.hasSbomPermission + ); + } + retag() { if (this.selectedRow && this.selectedRow.length && !this.depth) { this.copyArtifactComponent.retag(this.selectedRow[0].digest); @@ -714,6 +758,17 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { } } + // Get sbom status + sbomStatus(artifact: Artifact): string { + if (artifact) { + let so = (artifact).sbom_overview; + if (so && so.scan_status) { + return so.scan_status; + } + } + return SBOM_SCAN_STATUS.NOT_GENERATED_SBOM; + } + // Get vulnerability scanning status scanStatus(artifact: Artifact): string { if (artifact) { @@ -771,6 +826,10 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { ); } + selectedRowHasSbom(): boolean { + return !!(this.selectedRow && this.selectedRow[0]); + } + hasVul(artifact: Artifact): boolean { return !!( artifact && @@ -779,6 +838,14 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { ); } + hasSbom(artifact: Artifact): boolean { + return !!( + artifact && + artifact.addition_links && + artifact.addition_links[ADDITIONS.SBOMS] + ); + } + submitFinish(e: boolean) { this.scanFinishedArtifactLength += 1; // all selected scan action has started @@ -794,9 +861,27 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { this.onSendingScanCommand = e; } } + + submitSbomFinish(e: boolean) { + this.sbomFinishedArtifactLength += 1; + // all selected scan action has started + if (this.sbomFinishedArtifactLength === this.onSbomArtifactsLength) { + this.onSendingSbomCommand = e; + } + } + + submitSbomStopFinish(e: boolean) { + this.sbomStoppedArtifactLength += 1; + // all selected scan action has stopped + if (this.sbomStoppedArtifactLength === this.onStopSbomArtifactsLength) { + this.onSendingSbomCommand = e; + } + } + handleScanOverview(scanOverview: any): any { if (scanOverview) { - return Object.values(scanOverview)[0]; + const keys = Object.keys(scanOverview) ?? []; + return keys.length > 0 ? scanOverview[keys[0]] : null; } return null; } @@ -857,6 +942,11 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { } } + // when finished, remove it from selectedRow + sbomFinished(artifact: Artifact) { + this.scanFinished(artifact); + } + getIconsFromBackEnd() { if (this.artifactList && this.artifactList.length) { this.artifactService.getIconsFromBackEnd(this.artifactList); @@ -929,11 +1019,18 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { }); } } - checkCosignAsync(artifacts: ArtifactFront[]) { + + checkCosignAndSbomAsync(artifacts: ArtifactFront[]) { if (artifacts) { if (artifacts.length) { artifacts.forEach(item => { item.signed = CHECKING; + // const sbomOverview = item?.sbom_overview; + // item.sbomDigest = sbomOverview?.sbom_digest; + // let queryTypes = `${AccessoryType.COSIGN} ${AccessoryType.NOTATION}`; + // if (!item.sbomDigest) { + // queryTypes = `${queryTypes} ${AccessoryType.SBOM}`; + // } this.newArtifactService .listAccessories({ projectName: this.projectName, @@ -979,6 +1076,24 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { return false; } + // return true if all selected rows are in "running" state + canStopSbom(): boolean { + if (this.onSendingStopSbomCommand) { + return false; + } + if (this.selectedRow && this.selectedRow.length) { + let flag: boolean = true; + this.selectedRow.forEach(item => { + const st: string = this.sbomStatus(item); + if (!this.isRunningState(st)) { + flag = false; + } + }); + return flag; + } + return false; + } + isRunningState(state: string): boolean { return ( state === VULNERABILITY_SCAN_STATUS.RUNNING || @@ -1001,6 +1116,22 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { }); } } + + stopSbom() { + if (this.selectedRow && this.selectedRow.length) { + this.sbomStoppedArtifactLength = 0; + this.onStopSbomArtifactsLength = this.selectedRow.length; + this.onSendingStopSbomCommand = true; + this.selectedRow.forEach((data: any) => { + let digest = data.digest; + this.eventService.publish( + HarborEvent.STOP_SBOM_ARTIFACT, + this.repoName + '/' + digest + ); + }); + } + } + tagsString(tags: Tag[]): string { if (tags?.length) { const arr: string[] = []; diff --git a/src/portal/src/app/base/project/repository/artifact/artifact.module.ts b/src/portal/src/app/base/project/repository/artifact/artifact.module.ts index bd4f49961..4ff6da2d4 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact.module.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact.module.ts @@ -15,6 +15,7 @@ import { ArtifactVulnerabilitiesComponent } from './artifact-additions/artifact- import { ArtifactDefaultService, ArtifactService } from './artifact.service'; import { ArtifactDetailRoutingResolverService } from '../../../../services/routing-resolvers/artifact-detail-routing-resolver.service'; import { ResultBarChartComponent } from './vulnerability-scanning/result-bar-chart.component'; +import { ResultSbomComponent } from './sbom-scanning/sbom-scan.component'; import { ResultTipHistogramComponent } from './vulnerability-scanning/result-tip-histogram/result-tip-histogram.component'; import { HistogramChartComponent } from './vulnerability-scanning/histogram-chart/histogram-chart.component'; import { ArtifactInfoComponent } from './artifact-list-page/artifact-list/artifact-info/artifact-info.component'; @@ -24,6 +25,7 @@ import { CopyArtifactComponent } from './artifact-list-page/artifact-list/artifa import { CopyDigestComponent } from './artifact-list-page/artifact-list/artifact-list-tab/copy-digest/copy-digest.component'; import { ArtifactFilterComponent } from './artifact-list-page/artifact-list/artifact-list-tab/artifact-filter/artifact-filter.component'; import { PullCommandComponent } from './artifact-list-page/artifact-list/artifact-list-tab/pull-command/pull-command.component'; +import { SbomTipHistogramComponent } from './sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component'; const routes: Routes = [ { @@ -80,6 +82,8 @@ const routes: Routes = [ BuildHistoryComponent, ArtifactVulnerabilitiesComponent, ResultBarChartComponent, + ResultSbomComponent, + SbomTipHistogramComponent, ResultTipHistogramComponent, HistogramChartComponent, ArtifactInfoComponent, diff --git a/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-overview.ts b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-overview.ts new file mode 100644 index 000000000..753a32a20 --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-overview.ts @@ -0,0 +1,39 @@ +/* tslint:disable */ + +import { Scanner } from 'ng-swagger-gen/models'; + +/** + * The generate SBOM overview information + */ +export interface SBOMOverview { + /** + * id of the native sbom report + */ + report_id?: string; + + /** + * The start time of the scan process that generating report + */ + start_time?: string; + + /** + * The end time of the scan process that generating report + */ + end_time?: string; + + /** + * The status of the generate SBOM process + */ + scan_status?: string; + + /** + * The digest of the generated SBOM accessory + */ + sbom_digest?: string; + + /** + * The seconds spent for generating the report + */ + duration?: number; + scanner?: Scanner; +} diff --git a/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan-component.html b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan-component.html new file mode 100644 index 000000000..1b537edd5 --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan-component.html @@ -0,0 +1,38 @@ +
+
+ {{ + 'SBOM.STATE.QUEUED' | translate + }} +
+ +
+
{{ 'SBOM.STATE.SCANNING' | translate }}
+
+
+
+ +
+
+ {{ + 'SBOM.STATE.STOPPED' | translate + }} +
+
+ {{ + 'SBOM.STATE.OTHER_STATUS' | translate + }} +
+
diff --git a/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan.component.spec.ts b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan.component.spec.ts new file mode 100644 index 000000000..af74ddde8 --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan.component.spec.ts @@ -0,0 +1,245 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ResultSbomComponent } from './sbom-scan.component'; +import { + ScanningResultDefaultService, + ScanningResultService, +} from '../../../../../shared/services'; +import { SBOM_SCAN_STATUS } from '../../../../../shared/units/utils'; +import { SharedTestingModule } from '../../../../../shared/shared.module'; +import { SbomTipHistogramComponent } from './sbom-tip-histogram/sbom-tip-histogram.component'; +import { SBOMOverview } from './sbom-overview'; +import { of, timer } from 'rxjs'; +import { ArtifactService, ScanService } from 'ng-swagger-gen/services'; +import { Artifact } from 'ng-swagger-gen/models'; + +describe('ResultSbomComponent (inline template)', () => { + let component: ResultSbomComponent; + let fixture: ComponentFixture; + let mockData: SBOMOverview = { + scan_status: SBOM_SCAN_STATUS.SUCCESS, + end_time: new Date().toUTCString(), + }; + const mockedSbomDigest = + 'sha256:052240e8190b7057439d2bee1dffb9b37c8800e5c1af349f667635ae1debf8f3'; + const mockedSbomOverview = { + report_id: '12345', + scan_status: 'Error', + scanner: { + name: 'Trivy', + vendor: 'vm', + version: 'v1.2', + }, + }; + const mockedCloneSbomOverview = { + report_id: '12346', + scan_status: 'Pending', + scanner: { + name: 'Trivy', + vendor: 'vm', + version: 'v1.2', + }, + }; + const FakedScanService = { + scanArtifact: () => of({}), + stopScanArtifact: () => of({}), + }; + const FakedArtifactService = { + getArtifact: () => + of({ + accessories: null, + addition_links: { + build_history: { + absolute: false, + href: '/api/v2.0/projects/xuel/repositories/ui%252Fserver%252Fconfig-dev/artifacts/sha256:052240e8190b7057439d2bee1dffb9b37c8800e5c1af349f667635ae1debf8f3/additions/build_history', + }, + vulnerabilities: { + absolute: false, + href: '/api/v2.0/projects/xuel/repositories/ui%252Fserver%252Fconfig-dev/artifacts/sha256:052240e8190b7057439d2bee1dffb9b37c8800e5c1af349f667635ae1debf8f3/additions/vulnerabilities', + }, + }, + digest: 'sha256:052240e8190b7057439d2bee1dffb9b37c8800e5c1af349f667635ae1debf8f3', + extra_attrs: { + architecture: 'amd64', + author: '', + config: { + Env: [ + 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', + ], + WorkingDir: '/', + }, + created: '2024-01-10T10:05:33.2702206Z', + os: 'linux', + }, + icon: 'sha256:0048162a053eef4d4ce3fe7518615bef084403614f8bca43b40ae2e762e11e06', + id: 3, + labels: null, + manifest_media_type: + 'application/vnd.docker.distribution.manifest.v2+json', + media_type: 'application/vnd.docker.container.image.v1+json', + project_id: 3, + pull_time: '2024-04-02T01:50:58.332Z', + push_time: '2024-03-06T09:47:08.163Z', + references: null, + repository_id: 2, + sbom_overview: { + duration: 2, + end_time: '2024-04-02T01:50:59.406Z', + sbom_digest: + 'sha256:8cca43ea666e0e7990c2433e3b185313e6ba303cc7a3124bb767823c79fb74a6', + scan_status: 'Success', + start_time: '2024-04-02T01:50:57.176Z', + }, + size: 3957, + tags: null, + type: 'IMAGE', + }), + }; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SharedTestingModule], + declarations: [ResultSbomComponent, SbomTipHistogramComponent], + providers: [ + { + provide: ScanningResultService, + useValue: ScanningResultDefaultService, + }, + { + provide: ScanService, + useValue: FakedScanService, + }, + { + provide: ArtifactService, + useValue: FakedArtifactService, + }, + ], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ResultSbomComponent); + component = fixture.componentInstance; + component.repoName = 'mockRepo'; + component.artifactDigest = mockedSbomDigest; + component.sbomDigest = mockedSbomDigest; + component.sbomOverview = mockData; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); + it('should show "scan stopped" if status is STOPPED', () => { + component.sbomOverview.scan_status = SBOM_SCAN_STATUS.STOPPED; + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + let el: HTMLElement = fixture.nativeElement.querySelector('span'); + expect(el).toBeTruthy(); + expect(el.textContent).toEqual('SBOM.STATE.STOPPED'); + }); + }); + + it('should show progress if status is SCANNING', () => { + component.sbomOverview.scan_status = SBOM_SCAN_STATUS.RUNNING; + fixture.detectChanges(); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + + let el: HTMLElement = + fixture.nativeElement.querySelector('.progress'); + expect(el).toBeTruthy(); + }); + }); + + it('should show QUEUED if status is QUEUED', () => { + component.sbomOverview.scan_status = SBOM_SCAN_STATUS.PENDING; + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + + let el: HTMLElement = + fixture.nativeElement.querySelector('.bar-state'); + expect(el).toBeTruthy(); + let el2: HTMLElement = el.querySelector('span'); + expect(el2).toBeTruthy(); + expect(el2.textContent).toEqual('SBOM.STATE.QUEUED'); + }); + }); + + it('should show summary bar chart if status is COMPLETED', () => { + component.sbomOverview.scan_status = SBOM_SCAN_STATUS.SUCCESS; + fixture.detectChanges(); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + let el: HTMLElement = fixture.nativeElement.querySelector('a'); + expect(el).not.toBeNull(); + }); + }); + it('Test ResultSbomComponent getScanner', () => { + fixture.detectChanges(); + expect(component.getScanner()).toBeUndefined(); + component.sbomOverview = mockedSbomOverview; + expect(component.getScanner()).toBe(mockedSbomOverview.scanner); + component.projectName = 'test'; + component.repoName = 'ui'; + component.artifactDigest = 'dg'; + expect(component.viewLog()).toBe( + '/api/v2.0/projects/test/repositories/ui/artifacts/dg/scan/12345/log' + ); + component.copyValue(mockedCloneSbomOverview); + expect(component.sbomOverview.report_id).toBe( + mockedCloneSbomOverview.report_id + ); + }); + it('Test ResultSbomComponent status', () => { + component.sbomOverview = mockedSbomOverview; + fixture.detectChanges(); + expect(component.status).toBe(SBOM_SCAN_STATUS.ERROR); + expect(component.completed).toBeFalsy(); + expect(component.queued).toBeFalsy(); + expect(component.generating).toBeFalsy(); + expect(component.stopped).toBeFalsy(); + expect(component.otherStatus).toBeFalsy(); + expect(component.error).toBeTruthy(); + }); + it('Test ResultSbomComponent ngOnDestroy', () => { + component.stateCheckTimer = timer(0, 10000).subscribe(() => {}); + component.ngOnDestroy(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(component.stateCheckTimer).toBeNull(); + expect(component.generateSbomSubscription).toBeNull(); + expect(component.stopSubscription).toBeNull(); + }); + }); + it('Test ResultSbomComponent generateSbom', () => { + fixture.detectChanges(); + component.generateSbom(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(component.onSubmitting).toBeFalse(); + }); + }); + it('Test ResultSbomComponent stopSbom', () => { + fixture.detectChanges(); + component.stopSbom(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(component.onStopping).toBeFalse(); + }); + }); + it('Test ResultSbomComponent getSbomOverview', () => { + fixture.detectChanges(); + component.getSbomOverview(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(component.stateCheckTimer).toBeUndefined(); + }); + }); +}); diff --git a/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan.component.ts b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan.component.ts new file mode 100644 index 000000000..4cfced2f8 --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan.component.ts @@ -0,0 +1,327 @@ +import { + Component, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output, +} from '@angular/core'; +import { Subscription, timer } from 'rxjs'; +import { finalize } from 'rxjs/operators'; +import { ScannerVo } from '../../../../../shared/services'; +import { ErrorHandler } from '../../../../../shared/units/error-handler'; +import { + clone, + CURRENT_BASE_HREF, + dbEncodeURIComponent, + DEFAULT_SUPPORTED_MIME_TYPES, + SBOM_SCAN_STATUS, +} from '../../../../../shared/units/utils'; +import { ArtifactService } from '../../../../../../../ng-swagger-gen/services/artifact.service'; +import { Artifact } from '../../../../../../../ng-swagger-gen/models/artifact'; +import { + EventService, + HarborEvent, +} from '../../../../../services/event-service/event.service'; +import { ScanService } from '../../../../../../../ng-swagger-gen/services/scan.service'; +import { ScanType } from 'ng-swagger-gen/models'; +import { ScanTypes } from '../../../../../shared/entities/shared.const'; +import { SBOMOverview } from './sbom-overview'; +const STATE_CHECK_INTERVAL: number = 3000; // 3s +const RETRY_TIMES: number = 3; + +@Component({ + selector: 'hbr-sbom-bar', + templateUrl: './sbom-scan-component.html', + styleUrls: ['./scanning.scss'], +}) +export class ResultSbomComponent implements OnInit, OnDestroy { + @Input() inputScanner: ScannerVo; + @Input() repoName: string = ''; + @Input() projectName: string = ''; + @Input() projectId: string = ''; + @Input() artifactDigest: string = ''; + @Input() sbomDigest: string = ''; + @Input() sbomOverview: SBOMOverview; + onSubmitting: boolean = false; + onStopping: boolean = false; + retryCounter: number = 0; + stateCheckTimer: Subscription; + generateSbomSubscription: Subscription; + stopSubscription: Subscription; + timerHandler: any; + @Output() + submitFinish: EventEmitter = new EventEmitter(); + // if sending stop scan request is finished, emit to farther component + @Output() + submitStopFinish: EventEmitter = new EventEmitter(); + @Output() + scanFinished: EventEmitter = new EventEmitter(); + + constructor( + private artifactService: ArtifactService, + private scanService: ScanService, + private errorHandler: ErrorHandler, + private eventService: EventService + ) {} + + ngOnInit(): void { + if ( + (this.status === SBOM_SCAN_STATUS.RUNNING || + this.status === SBOM_SCAN_STATUS.PENDING) && + !this.stateCheckTimer + ) { + // Avoid duplicated subscribing + this.stateCheckTimer = timer(0, STATE_CHECK_INTERVAL).subscribe( + () => { + this.getSbomOverview(); + } + ); + } + if (!this.generateSbomSubscription) { + this.generateSbomSubscription = this.eventService.subscribe( + HarborEvent.START_GENERATE_SBOM, + (artifactDigest: string) => { + let myFullTag: string = + this.repoName + '/' + this.artifactDigest; + if (myFullTag === artifactDigest) { + this.generateSbom(); + } + } + ); + } + if (!this.stopSubscription) { + this.stopSubscription = this.eventService.subscribe( + HarborEvent.STOP_SBOM_ARTIFACT, + (artifactDigest: string) => { + let myFullTag: string = + this.repoName + '/' + this.artifactDigest; + if (myFullTag === artifactDigest) { + this.stopSbom(); + } + } + ); + } + } + + ngOnDestroy(): void { + if (this.stateCheckTimer) { + this.stateCheckTimer.unsubscribe(); + this.stateCheckTimer = null; + } + if (this.generateSbomSubscription) { + this.generateSbomSubscription.unsubscribe(); + this.generateSbomSubscription = null; + } + if (this.stopSubscription) { + this.stopSubscription.unsubscribe(); + this.stopSubscription = null; + } + } + + // Get vulnerability scanning status + public get status(): string { + if (this.sbomOverview && this.sbomOverview.scan_status) { + return this.sbomOverview.scan_status; + } + return SBOM_SCAN_STATUS.NOT_GENERATED_SBOM; + } + + public get completed(): boolean { + return this.status === SBOM_SCAN_STATUS.SUCCESS; + } + + public get error(): boolean { + return this.status === SBOM_SCAN_STATUS.ERROR; + } + + public get queued(): boolean { + return this.status === SBOM_SCAN_STATUS.PENDING; + } + + public get generating(): boolean { + return this.status === SBOM_SCAN_STATUS.RUNNING; + } + + public get stopped(): boolean { + return this.status === SBOM_SCAN_STATUS.STOPPED; + } + + public get otherStatus(): boolean { + return !( + this.completed || + this.error || + this.queued || + this.generating || + this.stopped + ); + } + + generateSbom(): void { + if (this.onSubmitting) { + // Avoid duplicated submitting + console.error('duplicated submit'); + return; + } + + if (!this.repoName || !this.artifactDigest) { + console.error('bad repository or tag'); + return; + } + + this.onSubmitting = true; + + this.scanService + .scanArtifact({ + projectName: this.projectName, + reference: this.artifactDigest, + repositoryName: dbEncodeURIComponent(this.repoName), + // scanType: { + // scan_type: ScanTypes.SBOM, + // }, + }) + .pipe(finalize(() => this.submitFinish.emit(false))) + .subscribe( + () => { + this.onSubmitting = false; + // Forcely change status to queued after successful submitting + this.sbomOverview = { + scan_status: SBOM_SCAN_STATUS.PENDING, + }; + // Start check status util the job is done + if (!this.stateCheckTimer) { + // Avoid duplicated subscribing + this.stateCheckTimer = timer( + STATE_CHECK_INTERVAL, + STATE_CHECK_INTERVAL + ).subscribe(() => { + this.getSbomOverview(); + }); + } + }, + error => { + this.onSubmitting = false; + if (error && error.error && error.error.code === 409) { + console.error(error.error.message); + } else { + this.errorHandler.error(error); + } + } + ); + } + + getSbomOverview(): void { + if (!this.repoName || !this.artifactDigest) { + return; + } + this.artifactService + .getArtifact({ + projectName: this.projectName, + repositoryName: dbEncodeURIComponent(this.repoName), + reference: this.artifactDigest, + // withSbomOverview: true, + XAcceptVulnerabilities: DEFAULT_SUPPORTED_MIME_TYPES, + }) + .subscribe( + (artifact: Artifact) => { + // To keep the same summary reference, use value copy. + // if (artifact.sbom_overview) { + // this.copyValue(artifact.sbom_overview); + // } + if (!this.queued && !this.generating) { + // Scanning should be done + if (this.stateCheckTimer) { + this.stateCheckTimer.unsubscribe(); + this.stateCheckTimer = null; + } + this.scanFinished.emit(artifact); + } + this.eventService.publish( + HarborEvent.UPDATE_SBOM_INFO, + artifact + ); + }, + error => { + this.errorHandler.error(error); + this.retryCounter++; + if (this.retryCounter >= RETRY_TIMES) { + // Stop timer + if (this.stateCheckTimer) { + this.stateCheckTimer.unsubscribe(); + this.stateCheckTimer = null; + } + this.retryCounter = 0; + } + } + ); + } + + copyValue(newVal: SBOMOverview): void { + if (!this.sbomOverview || !newVal || !newVal.scan_status) { + return; + } + this.sbomOverview = clone(newVal); + } + + viewLog(): string { + return `${CURRENT_BASE_HREF}/projects/${ + this.projectName + }/repositories/${dbEncodeURIComponent(this.repoName)}/artifacts/${ + this.artifactDigest + }/scan/${this.sbomOverview.report_id}/log`; + } + + getScanner(): ScannerVo { + if (this.sbomOverview && this.sbomOverview.scanner) { + return this.sbomOverview.scanner; + } + return this.inputScanner; + } + + stopSbom() { + if (this.onStopping) { + // Avoid duplicated stopping command + console.error('duplicated stopping command for SBOM generation'); + return; + } + if (!this.repoName || !this.artifactDigest) { + console.error('bad repository or artifact'); + return; + } + this.onStopping = true; + + this.scanService + .stopScanArtifact({ + projectName: this.projectName, + reference: this.artifactDigest, + repositoryName: dbEncodeURIComponent(this.repoName), + scanType: { + scan_type: ScanTypes.SBOM, + }, + }) + .pipe( + finalize(() => { + this.submitStopFinish.emit(false); + this.onStopping = false; + }) + ) + .subscribe( + () => { + // Start check status util the job is done + if (!this.stateCheckTimer) { + // Avoid duplicated subscribing + this.stateCheckTimer = timer( + STATE_CHECK_INTERVAL, + STATE_CHECK_INTERVAL + ).subscribe(() => { + this.getSbomOverview(); + }); + } + this.errorHandler.info('SBOM.TRIGGER_STOP_SUCCESS'); + }, + error => { + this.errorHandler.error(error); + } + ); + } +} diff --git a/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.html b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.html new file mode 100644 index 000000000..0bb28d622 --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.html @@ -0,0 +1,78 @@ +
+ +
+ +
+ {{ 'SBOM.NO_SBOM' | translate }} +
+
+ +
+
+
+ {{ 'SBOM.PACKAGES' | translate }} +
+
+ + {{ 'SBOM.NO_SBOM' | translate }} + +
+
+
+ {{ + 'SCANNER.SCANNED_BY' | translate + }} + {{ getScannerInfo() }} +
+
+ {{ + 'SCANNER.DURATION' | translate + }} + {{ duration() }} +
+
+ {{ 'SBOM.CHART.SCANNING_TIME' | translate }} + + {{ completeTimestamp | harborDatetime : 'short' }} +
+
+
+ +
+ +
+ +
+ {{ 'SBOM.CHART.SCANNING_PERCENT' | translate }} + + {{ completePercent }} +
+
+ {{ + 'SBOM.CHART.SCANNING_PERCENT_EXPLAIN' | translate + }} +
+
+
+
diff --git a/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.scss b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.scss new file mode 100644 index 000000000..973fa7e1a --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.scss @@ -0,0 +1,60 @@ +.tip-wrapper { + display: flex; + align-items: center; + color: #fff; + text-align: center; + font-size: 10px; + height: 15px; + line-height: 15px; +} + +.bar-scanning-time { + margin-top: 12px; +} + +.bar-tooltip-font-larger { + span { + font-size: 16px; + vertical-align: middle + } +} + +hr { + border-bottom: 0; + border-color: #aaa; + margin: 6px -10px; +} + +.font-weight-600 { + font-weight: 600; +} + +.margin-left-5 { + margin-left: 5px; +} + +.width-215 { + width: 215px; +} + +.width-150 { + width: 150px; +} + +.level-border>div{ + display: inline-flex; + align-items: center; + justify-items: center; + border-radius: 50%; + height: 26px; + width: 26px; + line-height: 26px; +} + +.tip-block { + position: relative; +} + +.margin-right-5 { + margin-right: 5px; +} diff --git a/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.spec.ts b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.spec.ts new file mode 100644 index 000000000..f130dad4f --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.spec.ts @@ -0,0 +1,103 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ClarityModule } from '@clr/angular'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { ActivatedRoute, Router } from '@angular/router'; +import { SbomTipHistogramComponent } from './sbom-tip-histogram.component'; +import { of } from 'rxjs'; +import { Project } from '../../../../../../../app/base/project/project'; +import { Artifact } from 'ng-swagger-gen/models'; + +describe('SbomTipHistogramComponent', () => { + let component: SbomTipHistogramComponent; + let fixture: ComponentFixture; + const mockRouter = { + navigate: () => {}, + }; + const mockedArtifact: Artifact = { + id: 123, + type: 'IMAGE', + }; + const mockedScanner = { + name: 'Trivy', + vendor: 'vm', + version: 'v1.2', + }; + const mockActivatedRoute = { + RouterparamMap: of({ get: key => 'value' }), + snapshot: { + params: { + repo: 'test', + digest: 'ABC', + subscribe: () => { + return of(null); + }, + }, + parent: { + params: { + id: 1, + }, + }, + data: { + artifactResolver: [mockedArtifact, new Project()], + }, + }, + data: of({ + projectResolver: { + ismember: true, + role_name: 'maintainer', + }, + }), + }; + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + BrowserAnimationsModule, + ClarityModule, + TranslateModule.forRoot(), + ], + providers: [ + TranslateService, + { provide: Router, useValue: mockRouter }, + { provide: ActivatedRoute, useValue: mockActivatedRoute }, + ], + declarations: [SbomTipHistogramComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SbomTipHistogramComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('Test SbomTipHistogramComponent basic functions', () => { + fixture.whenStable().then(() => { + expect(component).toBeTruthy(); + expect(component.isLimitedSuccess()).toBeFalsy(); + expect(component.noSbom).toBeTruthy(); + expect(component.isThemeLight()).toBeFalsy(); + expect(component.duration()).toBe('0'); + expect(component.completePercent).toBe('0%'); + }); + }); + + it('Test SbomTipHistogramComponent completeTimestamp', () => { + fixture.whenStable().then(() => { + component.sbomSummary.end_time = new Date('2024-04-08 00:01:02'); + expect(component.completeTimestamp).toBe( + component.sbomSummary.end_time + ); + }); + }); + + it('Test SbomTipHistogramComponent getScannerInfo', () => { + fixture.whenStable().then(() => { + expect(component.getScannerInfo()).toBe(''); + component.scanner = mockedScanner; + expect(component.getScannerInfo()).toBe( + `${mockedScanner.name}@${mockedScanner.version}` + ); + }); + }); +}); diff --git a/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.ts b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.ts new file mode 100644 index 000000000..2e948442b --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.ts @@ -0,0 +1,111 @@ +import { Component, Input } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ScannerVo, SbomSummary } from '../../../../../../shared/services'; +import { SBOM_SCAN_STATUS } from '../../../../../../shared/units/utils'; +import { + UN_LOGGED_PARAM, + YES, +} from '../../../../../../account/sign-in/sign-in.service'; +import { HAS_STYLE_MODE, StyleMode } from '../../../../../../services/theme'; +import { ScanTypes } from '../../../../../../shared/entities/shared.const'; + +const MIN = 60; +const MIN_STR = 'min '; +const SEC_STR = 'sec'; +const SUCCESS_PCT: number = 100; + +@Component({ + selector: 'hbr-sbom-tip-histogram', + templateUrl: './sbom-tip-histogram.component.html', + styleUrls: ['./sbom-tip-histogram.component.scss'], +}) +export class SbomTipHistogramComponent { + @Input() scanner: ScannerVo; + @Input() sbomSummary: SbomSummary = { + scan_status: SBOM_SCAN_STATUS.NOT_GENERATED_SBOM, + }; + @Input() artifactDigest: string = ''; + @Input() sbomDigest: string = ''; + constructor( + private translate: TranslateService, + private activatedRoute: ActivatedRoute, + private router: Router + ) {} + + duration(): string { + if (this.sbomSummary && this.sbomSummary.duration) { + let str = ''; + const min = Math.floor(this.sbomSummary.duration / MIN); + if (min) { + str += min + ' ' + MIN_STR; + } + const sec = this.sbomSummary.duration % MIN; + if (sec) { + str += sec + ' ' + SEC_STR; + } + return str; + } + return '0'; + } + + public get completePercent(): string { + return this.sbomSummary.scan_status === SBOM_SCAN_STATUS.SUCCESS + ? `100%` + : '0%'; + } + isLimitedSuccess(): boolean { + return ( + this.sbomSummary && this.sbomSummary.complete_percent < SUCCESS_PCT + ); + } + get completeTimestamp(): Date { + return this.sbomSummary && this.sbomSummary.end_time + ? this.sbomSummary.end_time + : new Date(); + } + + get noSbom(): boolean { + return ( + this.sbomSummary.scan_status === SBOM_SCAN_STATUS.NOT_GENERATED_SBOM + ); + } + + isThemeLight() { + return localStorage.getItem(HAS_STYLE_MODE) === StyleMode.LIGHT; + } + + getScannerInfo(): string { + if (this.scanner) { + if (this.scanner.name && this.scanner.version) { + return `${this.scanner.name}@${this.scanner.version}`; + } + if (this.scanner.name && !this.scanner.version) { + return `${this.scanner.name}`; + } + } + return ''; + } + + goIntoArtifactSbomSummaryPage(): void { + const relativeRouterLink: string[] = ['artifacts', this.artifactDigest]; + if (this.activatedRoute.snapshot.queryParams[UN_LOGGED_PARAM] === YES) { + this.router.navigate(relativeRouterLink, { + relativeTo: this.activatedRoute, + queryParams: { + [UN_LOGGED_PARAM]: YES, + sbomDigest: this.sbomDigest ?? '', + tab: ScanTypes.SBOM, + }, + }); + } else { + this.router.navigate(relativeRouterLink, { + relativeTo: this.activatedRoute, + queryParams: { + sbomDigest: this.sbomDigest ?? '', + tab: ScanTypes.SBOM, + }, + }); + } + } +} diff --git a/src/portal/src/app/base/project/repository/artifact/sbom-scanning/scanning.scss b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/scanning.scss new file mode 100644 index 000000000..248361e33 --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/scanning.scss @@ -0,0 +1,172 @@ +.bar-wrapper { + width: 210px; +} + +.bar-state { + .unknow-text { + margin-left: -5px; + } + + .label { + width: 50%; + } +} + +.bar-state-chart { + .loop-height { + height: 2px; + } +} + +.bar-state-error { + position: relative; +} + +.error-text { + position: relative; + top: 1px; + margin-left: -5px; + cursor: pointer; +} + +.scanning-button { + height: 24px; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + top: -12px; + position: relative; +} + +.tip-wrapper { + display: inline-block; + height: 10px; + max-width: 120px; +} + + +.bar-tooltip-font-title { + font-weight: 600; +} + +.bar-summary { + margin-top: 12px; + text-align: left; +} + +.bar-scanning-time { + margin-top: 12px; +} + +.bar-summary-item { + margin-top: 3px; + margin-bottom: 3px; +} + +.bar-summary-item span:nth-child(1){ + width: 30px; + text-align: center; + display: inline-block; +} + +.bar-summary-item span:nth-child(2){ + width: 28px; + display: inline-block; +} + +.option-right { + padding-right: 16px; +} + +.refresh-btn { + cursor: pointer; +} + +.refresh-btn:hover { + color: #007CBB; +} + +.tip-icon-medium { + color: orange; +} + +.tip-icon-low { + color: yellow; +} + +.font-color-green{ + color:green; +} +/* stylelint-disable */ +.bar-tooltip-font-larger span{ + font-size:16px; + vertical-align:middle +} + +hr{ + border-bottom: 0; + border-color: #aaa; + margin: 6px -10px; +} + +.font-weight-600{ + font-weight:600; +} + +.rightPos{ + position: absolute; + z-index: 100; + right: 35px; + margin-top: 4px; +} + +.result-row { + position: relative; +} + +.help-icon { + margin-left: 3px; +} + +.mt-3px { + margin-top: 5px; +} + +.label-critical { + background:#ff4d2e; + color:#000; +} + +.label-danger { + background:#ff8f3d!important; + color:#000!important; +} + +.label-medium { + background-color: #ffce66; + color:#000; +} + +.label-low { + background: #fff1ad; + color:#000; +} + +.label-none { + background-color: #2ec0ff; + color:#000; +} + +.no-border { + border: none; +} + +hbr-vulnerability-bar { + .label,.not-scan { + width: 90%; + } +} + +.stopped { + border-color: #cccc15; +} diff --git a/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-bar-chart.component.spec.ts b/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-bar-chart.component.spec.ts index 132549186..9f840d3ec 100644 --- a/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-bar-chart.component.spec.ts +++ b/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-bar-chart.component.spec.ts @@ -11,14 +11,43 @@ import { import { VULNERABILITY_SCAN_STATUS } from '../../../../../shared/units/utils'; import { NativeReportSummary } from '../../../../../../../ng-swagger-gen/models/native-report-summary'; import { SharedTestingModule } from '../../../../../shared/shared.module'; +import { of, timer } from 'rxjs'; +import { ArtifactService, ScanService } from 'ng-swagger-gen/services'; describe('ResultBarChartComponent (inline template)', () => { let component: ResultBarChartComponent; let fixture: ComponentFixture; + const mockedSbomDigest = + 'sha256:052240e8190b7057439d2bee1dffb9b37c8800e5c1af349f667635ae1debf8f3'; let mockData: NativeReportSummary = { + report_id: '12345', scan_status: VULNERABILITY_SCAN_STATUS.SUCCESS, severity: 'High', end_time: new Date().toUTCString(), + scanner: { + name: 'Trivy', + vendor: 'vm', + version: 'v1.2', + }, + summary: { + total: 124, + fixable: 50, + summary: { + High: 5, + Low: 5, + }, + }, + }; + let mockCloneData: NativeReportSummary = { + report_id: '123456', + scan_status: VULNERABILITY_SCAN_STATUS.SUCCESS, + severity: 'High', + end_time: new Date().toUTCString(), + scanner: { + name: 'Trivy', + vendor: 'vm', + version: 'v1.3', + }, summary: { total: 124, fixable: 50, @@ -29,6 +58,59 @@ describe('ResultBarChartComponent (inline template)', () => { }, }; + const FakedScanService = { + scanArtifact: () => of({}), + stopScanArtifact: () => of({}), + }; + const FakedArtifactService = { + getArtifact: () => + of({ + accessories: null, + addition_links: { + build_history: { + absolute: false, + href: '/api/v2.0/projects/xuel/repositories/ui%252Fserver%252Fconfig-dev/artifacts/sha256:052240e8190b7057439d2bee1dffb9b37c8800e5c1af349f667635ae1debf8f3/additions/build_history', + }, + vulnerabilities: { + absolute: false, + href: '/api/v2.0/projects/xuel/repositories/ui%252Fserver%252Fconfig-dev/artifacts/sha256:052240e8190b7057439d2bee1dffb9b37c8800e5c1af349f667635ae1debf8f3/additions/vulnerabilities', + }, + }, + digest: 'sha256:052240e8190b7057439d2bee1dffb9b37c8800e5c1af349f667635ae1debf8f3', + extra_attrs: { + architecture: 'amd64', + author: '', + config: { + Env: [ + 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', + ], + WorkingDir: '/', + }, + created: '2024-01-10T10:05:33.2702206Z', + os: 'linux', + }, + icon: 'sha256:0048162a053eef4d4ce3fe7518615bef084403614f8bca43b40ae2e762e11e06', + id: 3, + labels: null, + manifest_media_type: + 'application/vnd.docker.distribution.manifest.v2+json', + media_type: 'application/vnd.docker.container.image.v1+json', + project_id: 3, + pull_time: '2024-04-02T01:50:58.332Z', + push_time: '2024-03-06T09:47:08.163Z', + references: null, + repository_id: 2, + scan_overview: { + duration: 2, + end_time: '2024-04-02T01:50:59.406Z', + scan_status: 'Success', + start_time: '2024-04-02T01:50:57.176Z', + }, + size: 3957, + tags: null, + type: 'IMAGE', + }), + }; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [SharedTestingModule], @@ -43,6 +125,14 @@ describe('ResultBarChartComponent (inline template)', () => { useValue: ScanningResultDefaultService, }, { provide: JobLogService, useValue: JobLogDefaultService }, + { + provide: ScanService, + useValue: FakedScanService, + }, + { + provide: ArtifactService, + useValue: FakedArtifactService, + }, ], }).compileComponents(); }); @@ -52,6 +142,8 @@ describe('ResultBarChartComponent (inline template)', () => { component = fixture.componentInstance; component.artifactDigest = 'mockTag'; component.summary = mockData; + component.repoName = 'mockRepo'; + component.artifactDigest = mockedSbomDigest; fixture.detectChanges(); }); @@ -109,4 +201,69 @@ describe('ResultBarChartComponent (inline template)', () => { expect(el).not.toBeNull(); }); }); + it('Test ResultBarChartComponent getScanner', () => { + fixture.detectChanges(); + component.summary = mockData; + expect(component.getScanner()).toBe(mockData.scanner); + component.projectName = 'test'; + component.repoName = 'ui'; + component.artifactDigest = 'dg'; + expect(component.viewLog()).toBe( + '/api/v2.0/projects/test/repositories/ui/artifacts/dg/scan/12345/log' + ); + component.copyValue(mockCloneData); + expect(component.summary.report_id).toBe(mockCloneData.report_id); + }); + it('Test ResultBarChartComponent status', () => { + fixture.detectChanges(); + component.summary.scan_status = VULNERABILITY_SCAN_STATUS.SUCCESS; + expect(component.status).toBe(VULNERABILITY_SCAN_STATUS.SUCCESS); + expect(component.completed).toBeTruthy(); + expect(component.queued).toBeFalsy(); + expect(component.scanning).toBeFalsy(); + expect(component.stopped).toBeFalsy(); + expect(component.otherStatus).toBeFalsy(); + expect(component.error).toBeFalsy(); + }); + it('Test ResultBarChartComponent ngOnDestroy', () => { + component.stateCheckTimer = timer(0, 10000).subscribe(() => {}); + component.ngOnDestroy(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(component.stateCheckTimer).toBeNull(); + expect(component.scanSubscription).toBeNull(); + expect(component.stopSubscription).toBeNull(); + }); + }); + it('Test ResultBarChartComponent scanNow', () => { + fixture.detectChanges(); + component.scanNow(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(component.onSubmitting).toBeFalse(); + }); + }); + it('Test ResultBarChartComponent stopScan', () => { + fixture.detectChanges(); + component.stopScan(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(component.onStopping).toBeFalse(); + expect(component.stateCheckTimer).toBeNull(); + }); + }); + it('Test ResultBarChartComponent getSummary', () => { + fixture.detectChanges(); + // component.summary.scan_status = VULNERABILITY_SCAN_STATUS.SUCCESS; + component.getSummary(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(component.summary.scan_status).toBe( + VULNERABILITY_SCAN_STATUS.SUCCESS + ); + }); + }); }); diff --git a/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-bar-chart.component.ts b/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-bar-chart.component.ts index 69568d550..ceb8e25b2 100644 --- a/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-bar-chart.component.ts +++ b/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-bar-chart.component.ts @@ -25,6 +25,8 @@ import { HarborEvent, } from '../../../../../services/event-service/event.service'; import { ScanService } from '../../../../../../../ng-swagger-gen/services/scan.service'; +import { ScanType } from 'ng-swagger-gen/models'; +import { ScanTypes } from '../../../../../shared/entities/shared.const'; const STATE_CHECK_INTERVAL: number = 3000; // 3s const RETRY_TIMES: number = 3; @@ -110,7 +112,7 @@ export class ResultBarChartComponent implements OnInit, OnDestroy { this.scanSubscription.unsubscribe(); this.scanSubscription = null; } - if (!this.stopSubscription) { + if (this.stopSubscription) { this.stopSubscription.unsubscribe(); this.stopSubscription = null; } @@ -171,6 +173,9 @@ export class ResultBarChartComponent implements OnInit, OnDestroy { projectName: this.projectName, reference: this.artifactDigest, repositoryName: dbEncodeURIComponent(this.repoName), + // scanType: { + // scan_type: ScanTypes.VULNERABILITY, + // }, }) .pipe(finalize(() => this.submitFinish.emit(false))) .subscribe( @@ -286,6 +291,9 @@ export class ResultBarChartComponent implements OnInit, OnDestroy { projectName: this.projectName, reference: this.artifactDigest, repositoryName: dbEncodeURIComponent(this.repoName), + scanType: { + scan_type: ScanTypes.VULNERABILITY, + }, }) .pipe( finalize(() => { diff --git a/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/scanning.scss b/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/scanning.scss index 5d1f98335..248361e33 100644 --- a/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/scanning.scss +++ b/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/scanning.scss @@ -8,7 +8,7 @@ } .label { - width: 90%; + width: 50%; } } diff --git a/src/portal/src/app/services/event-service/event.service.ts b/src/portal/src/app/services/event-service/event.service.ts index 88d5e28bd..31032f52d 100644 --- a/src/portal/src/app/services/event-service/event.service.ts +++ b/src/portal/src/app/services/event-service/event.service.ts @@ -76,8 +76,11 @@ export enum HarborEvent { SCROLL_TO_POSITION = 'scrollToPosition', REFRESH_PROJECT_INFO = 'refreshProjectInfo', START_SCAN_ARTIFACT = 'startScanArtifact', + START_GENERATE_SBOM = 'startGenerateSbom', STOP_SCAN_ARTIFACT = 'stopScanArtifact', + STOP_SBOM_ARTIFACT = 'stopSbomArtifact', UPDATE_VULNERABILITY_INFO = 'UpdateVulnerabilityInfo', + UPDATE_SBOM_INFO = 'UpdateSbomInfo', REFRESH_EXPORT_JOBS = 'refreshExportJobs', DELETE_ACCESSORY = 'deleteAccessory', COPY_DIGEST = 'copyDigest', diff --git a/src/portal/src/app/services/routing-resolvers/artifact-detail-routing-resolver.service.ts b/src/portal/src/app/services/routing-resolvers/artifact-detail-routing-resolver.service.ts index 77701f2a4..5aa9f8939 100644 --- a/src/portal/src/app/services/routing-resolvers/artifact-detail-routing-resolver.service.ts +++ b/src/portal/src/app/services/routing-resolvers/artifact-detail-routing-resolver.service.ts @@ -18,7 +18,7 @@ import { ActivatedRouteSnapshot, } from '@angular/router'; import { forkJoin, Observable, of } from 'rxjs'; -import { map, catchError, mergeMap } from 'rxjs/operators'; +import { catchError, mergeMap } from 'rxjs/operators'; import { Artifact } from '../../../../ng-swagger-gen/models/artifact'; import { ArtifactService } from '../../../../ng-swagger-gen/services/artifact.service'; import { Project } from '../../base/project/project'; @@ -51,6 +51,7 @@ export class ArtifactDetailRoutingResolverService { projectName: project.name, withLabel: true, withScanOverview: true, + // withSbomOverview: true, withTag: false, withImmutableStatus: true, }), diff --git a/src/portal/src/app/shared/entities/shared.const.ts b/src/portal/src/app/shared/entities/shared.const.ts index be9bdab5f..573cda40b 100644 --- a/src/portal/src/app/shared/entities/shared.const.ts +++ b/src/portal/src/app/shared/entities/shared.const.ts @@ -382,3 +382,8 @@ export const stringsForClarity: Partial = { datepickerSelectYearText: 'CLARITY.DATE_PICKER_SELECT_YEAR_TEXT', datepickerSelectedLabel: 'CLARITY.DATE_PICKER_SELECTED_LABEL', }; + +export enum ScanTypes { + SBOM = 'sbom', + VULNERABILITY = 'vulnerability', +} diff --git a/src/portal/src/app/shared/services/interface.ts b/src/portal/src/app/shared/services/interface.ts index 5d641c99a..1afbc36af 100644 --- a/src/portal/src/app/shared/services/interface.ts +++ b/src/portal/src/app/shared/services/interface.ts @@ -211,6 +211,16 @@ export interface VulnerabilitySummary { scanner?: ScannerVo; complete_percent?: number; } +export interface SbomSummary { + report_id?: string; + sbom_digest?: string; + scan_status?: string; + duration?: number; + start_time?: Date; + end_time?: Date; + scanner?: ScannerVo; + complete_percent?: number; +} export interface ScannerVo { name?: string; vendor?: string; diff --git a/src/portal/src/app/shared/services/permission-static.ts b/src/portal/src/app/shared/services/permission-static.ts index 125b15f94..fc15a0303 100644 --- a/src/portal/src/app/shared/services/permission-static.ts +++ b/src/portal/src/app/shared/services/permission-static.ts @@ -105,6 +105,13 @@ export const USERSTATICPERMISSION = { READ: 'read', }, }, + REPOSITORY_TAG_SBOM_JOB: { + KEY: 'sbom', + VALUE: { + CREATE: 'create', + READ: 'read', + }, + }, REPOSITORY_ARTIFACT_LABEL: { KEY: 'artifact-label', VALUE: { diff --git a/src/portal/src/app/shared/units/utils.ts b/src/portal/src/app/shared/units/utils.ts index ad38954ac..4efa2eadb 100644 --- a/src/portal/src/app/shared/units/utils.ts +++ b/src/portal/src/app/shared/units/utils.ts @@ -275,6 +275,21 @@ export const VULNERABILITY_SCAN_STATUS = { SUCCESS: 'Success', SCHEDULED: 'Scheduled', }; + +/** + * The state of sbom generation + */ +export const SBOM_SCAN_STATUS = { + // front-end status + NOT_GENERATED_SBOM: 'Not generated SBOM', + // back-end status + PENDING: 'Pending', + RUNNING: 'Running', + ERROR: 'Error', + STOPPED: 'Stopped', + SUCCESS: 'Success', + SCHEDULED: 'Scheduled', +}; /** * The severity of vulnerability scanning */ diff --git a/src/portal/src/i18n/lang/de-de-lang.json b/src/portal/src/i18n/lang/de-de-lang.json index acf0e8fdb..cae7b7af0 100644 --- a/src/portal/src/i18n/lang/de-de-lang.json +++ b/src/portal/src/i18n/lang/de-de-lang.json @@ -777,6 +777,7 @@ "ARTIFACTS": "Artefakte", "SIZE": "Größe", "VULNERABILITY": "Schwachstellen", + "SBOM": "SBOM", "BUILD_HISTORY": "Build History", "SIGNED": "Signiert", "AUTHOR": "Autor", @@ -1027,6 +1028,41 @@ "IN_PROGRESS": "Suche...", "BACK": "Zurück" }, + "SBOM": { + "CHART": { + "SCANNING_TIME": "Scan completed time:", + "SCANNING_PERCENT": "Scan progress:", + "SCANNING_PERCENT_EXPLAIN": "The scan completion progress is calculated as # of successfully scanned images / total number of images referenced within the image index.", + "TOOLTIPS_TITLE": "{{totalSbom}} of {{totalPackages}} {{package}} have known {{sbom}}.", + "TOOLTIPS_TITLE_SINGULAR": "{{totalSbom}} of {{totalPackages}} {{package}} has known {{sbom}}.", + "TOOLTIPS_TITLE_ZERO": "No recognizable SBOM detected" + }, + "GRID": { + "PLACEHOLDER": "No scan results found.", + "COLUMN_PACKAGE": "Package", + "COLUMN_PACKAGES": "Packages", + "COLUMN_VERSION": "Current version", + "COLUMN_LICENSE": "License", + "COLUMN_DESCRIPTION": "Description", + "FOOT_ITEMS": "Items", + "FOOT_OF": "of" + }, + "STATE": { + "OTHER_STATUS": "Not Generated", + "QUEUED": "Queued", + "ERROR": "View Log", + "SCANNING": "Generating", + "STOPPED": "SBOM scan stopped" + }, + "NO_SBOM": "No SBOM", + "PACKAGES": "SBOM", + "REPORTED_BY": "Reported by {{scanner}}", + "GENERATE": "Create SBOM", + "DOWNLOAD": "Download SBOM", + "Details": "SBOM details", + "STOP": "Stop SBOM", + "TRIGGER_STOP_SUCCESS": "Trigger stopping SBOM generation successfully" + }, "VULNERABILITY": { "STATE": { "OTHER_STATUS": "Nicht gescannt", @@ -1107,6 +1143,7 @@ "ALL": "Alle", "PLACEHOLDER": "Keine Artefakte gefunden!", "SCAN_UNSUPPORTED": "Nicht unterstützt", + "SBOM_UNSUPPORTED": "Unsupported", "SUMMARY": "Zusammenfassung", "DEPENDENCIES": "Dependencies", "VALUES": "Values", diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json index 243ceee98..93b802401 100644 --- a/src/portal/src/i18n/lang/en-us-lang.json +++ b/src/portal/src/i18n/lang/en-us-lang.json @@ -778,6 +778,7 @@ "ARTIFACTS": "Artifacts", "SIZE": "Size", "VULNERABILITY": "Vulnerabilities", + "SBOM": "SBOM", "BUILD_HISTORY": "Build History", "SIGNED": "Signed", "AUTHOR": "Author", @@ -1028,6 +1029,41 @@ "IN_PROGRESS": "Search...", "BACK": "Back" }, + "SBOM": { + "CHART": { + "SCANNING_TIME": "Scan completed time:", + "SCANNING_PERCENT": "Scan progress:", + "SCANNING_PERCENT_EXPLAIN": "The scan completion progress is calculated as # of successfully scanned images / total number of images referenced within the image index.", + "TOOLTIPS_TITLE": "{{totalSbom}} of {{totalPackages}} {{package}} have known {{sbom}}.", + "TOOLTIPS_TITLE_SINGULAR": "{{totalSbom}} of {{totalPackages}} {{package}} has known {{sbom}}.", + "TOOLTIPS_TITLE_ZERO": "No recognizable SBOM detected" + }, + "GRID": { + "PLACEHOLDER": "No scan results found.", + "COLUMN_PACKAGE": "Package", + "COLUMN_PACKAGES": "Packages", + "COLUMN_VERSION": "Current version", + "COLUMN_LICENSE": "License", + "COLUMN_DESCRIPTION": "Description", + "FOOT_ITEMS": "Items", + "FOOT_OF": "of" + }, + "STATE": { + "OTHER_STATUS": "Not Generated", + "QUEUED": "Queued", + "ERROR": "View Log", + "SCANNING": "Generating", + "STOPPED": "SBOM scan stopped" + }, + "NO_SBOM": "No SBOM", + "PACKAGES": "SBOM", + "REPORTED_BY": "Reported by {{scanner}}", + "GENERATE": "Create SBOM", + "DOWNLOAD": "Download SBOM", + "Details": "SBOM details", + "STOP": "Stop SBOM", + "TRIGGER_STOP_SUCCESS": "Trigger stopping SBOM generation successfully" + }, "VULNERABILITY": { "STATE": { "OTHER_STATUS": "Not Scanned", @@ -1108,6 +1144,7 @@ "ALL": "All", "PLACEHOLDER": "We couldn't find any artifacts!", "SCAN_UNSUPPORTED": "Unsupported", + "SBOM_UNSUPPORTED": "Unsupported", "SUMMARY": "Summary", "DEPENDENCIES": "Dependencies", "VALUES": "Values", diff --git a/src/portal/src/i18n/lang/es-es-lang.json b/src/portal/src/i18n/lang/es-es-lang.json index cce410e54..11dc5b0b5 100644 --- a/src/portal/src/i18n/lang/es-es-lang.json +++ b/src/portal/src/i18n/lang/es-es-lang.json @@ -778,6 +778,7 @@ "ARTIFACTS": "Artifacts", "SIZE": "Size", "VULNERABILITY": "Vulnerabilities", + "SBOM": "SBOM", "BUILD_HISTORY": "Construir Historia", "SIGNED": "Firmada", "AUTHOR": "Autor", @@ -1026,6 +1027,41 @@ "IN_PROGRESS": "Buscar...", "BACK": "Volver" }, + "SBOM": { + "CHART": { + "SCANNING_TIME": "Scan completed time:", + "SCANNING_PERCENT": "Scan progress:", + "SCANNING_PERCENT_EXPLAIN": "The scan completion progress is calculated as # of successfully scanned images / total number of images referenced within the image index.", + "TOOLTIPS_TITLE": "{{totalSbom}} of {{totalPackages}} {{package}} have known {{sbom}}.", + "TOOLTIPS_TITLE_SINGULAR": "{{totalSbom}} of {{totalPackages}} {{package}} has known {{sbom}}.", + "TOOLTIPS_TITLE_ZERO": "No recognizable SBOM detected" + }, + "GRID": { + "PLACEHOLDER": "No scan results found.", + "COLUMN_PACKAGE": "Package", + "COLUMN_PACKAGES": "Packages", + "COLUMN_VERSION": "Current version", + "COLUMN_LICENSE": "License", + "COLUMN_DESCRIPTION": "Description", + "FOOT_ITEMS": "Items", + "FOOT_OF": "of" + }, + "STATE": { + "OTHER_STATUS": "Not Generated", + "QUEUED": "Queued", + "ERROR": "View Log", + "SCANNING": "Generating", + "STOPPED": "SBOM scan stopped" + }, + "NO_SBOM": "No SBOM", + "PACKAGES": "SBOM", + "REPORTED_BY": "Reported by {{scanner}}", + "GENERATE": "Create SBOM", + "DOWNLOAD": "Download SBOM", + "Details": "SBOM details", + "STOP": "Stop SBOM", + "TRIGGER_STOP_SUCCESS": "Trigger stopping SBOM generation successfully" + }, "VULNERABILITY": { "STATE": { "OTHER_STATUS": "Not Scanned", @@ -1106,6 +1142,7 @@ "ALL": "All", "PLACEHOLDER": "We couldn't find any artifacts!", "SCAN_UNSUPPORTED": "Unsupported", + "SBOM_UNSUPPORTED": "Unsupported", "SUMMARY": "Summary", "DEPENDENCIES": "Dependencies", "VALUES": "Values", diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json index 035bb3a05..f09c6ba66 100644 --- a/src/portal/src/i18n/lang/fr-fr-lang.json +++ b/src/portal/src/i18n/lang/fr-fr-lang.json @@ -777,6 +777,7 @@ "ARTIFACTS": "Artefacts", "SIZE": "Taille", "VULNERABILITY": "Vulnérabilité", + "SBOM": "SBOM", "BUILD_HISTORY": "Historique de construction", "SIGNED": "Signé", "AUTHOR": "Auteur", @@ -1026,6 +1027,41 @@ "IN_PROGRESS": "Recherche...", "BACK": "Retour" }, + "SBOM": { + "CHART": { + "SCANNING_TIME": "Scan completed time:", + "SCANNING_PERCENT": "Scan progress:", + "SCANNING_PERCENT_EXPLAIN": "The scan completion progress is calculated as # of successfully scanned images / total number of images referenced within the image index.", + "TOOLTIPS_TITLE": "{{totalSbom}} of {{totalPackages}} {{package}} have known {{sbom}}.", + "TOOLTIPS_TITLE_SINGULAR": "{{totalSbom}} of {{totalPackages}} {{package}} has known {{sbom}}.", + "TOOLTIPS_TITLE_ZERO": "No recognizable SBOM detected" + }, + "GRID": { + "PLACEHOLDER": "No scan results found.", + "COLUMN_PACKAGE": "Package", + "COLUMN_PACKAGES": "Packages", + "COLUMN_VERSION": "Current version", + "COLUMN_LICENSE": "License", + "COLUMN_DESCRIPTION": "Description", + "FOOT_ITEMS": "Items", + "FOOT_OF": "of" + }, + "STATE": { + "OTHER_STATUS": "Not Generated", + "QUEUED": "Queued", + "ERROR": "View Log", + "SCANNING": "Generating", + "STOPPED": "SBOM scan stopped" + }, + "NO_SBOM": "No SBOM", + "PACKAGES": "SBOM", + "REPORTED_BY": "Reported by {{scanner}}", + "GENERATE": "Create SBOM", + "DOWNLOAD": "Download SBOM", + "Details": "SBOM details", + "STOP": "Stop SBOM", + "TRIGGER_STOP_SUCCESS": "Trigger stopping SBOM generation successfully" + }, "VULNERABILITY": { "STATE": { "OTHER_STATUS": "Non analysé", @@ -1106,6 +1142,7 @@ "ALL": "Tous", "PLACEHOLDER": "Nous n'avons trouvé aucun artefact !", "SCAN_UNSUPPORTED": "Non supporté", + "SBOM_UNSUPPORTED": "Unsupported", "SUMMARY": "Résumé", "DEPENDENCIES": "Dépendances", "VALUES": "Valeurs", diff --git a/src/portal/src/i18n/lang/ko-kr-lang.json b/src/portal/src/i18n/lang/ko-kr-lang.json index 20de6aa90..49f16e841 100644 --- a/src/portal/src/i18n/lang/ko-kr-lang.json +++ b/src/portal/src/i18n/lang/ko-kr-lang.json @@ -775,6 +775,7 @@ "ARTIFACTS": "아티팩트들", "SIZE": "크기", "VULNERABILITY": "취약점", + "SBOM": "SBOM", "BUILD_HISTORY": "기록 생성", "SIGNED": "서명됨", "AUTHOR": "작성자", @@ -1025,6 +1026,41 @@ "IN_PROGRESS": "검색 중...", "BACK": "뒤로" }, + "SBOM": { + "CHART": { + "SCANNING_TIME": "Scan completed time:", + "SCANNING_PERCENT": "Scan progress:", + "SCANNING_PERCENT_EXPLAIN": "The scan completion progress is calculated as # of successfully scanned images / total number of images referenced within the image index.", + "TOOLTIPS_TITLE": "{{totalSbom}} of {{totalPackages}} {{package}} have known {{sbom}}.", + "TOOLTIPS_TITLE_SINGULAR": "{{totalSbom}} of {{totalPackages}} {{package}} has known {{sbom}}.", + "TOOLTIPS_TITLE_ZERO": "No recognizable SBOM detected" + }, + "GRID": { + "PLACEHOLDER": "No scan results found.", + "COLUMN_PACKAGE": "Package", + "COLUMN_PACKAGES": "Packages", + "COLUMN_VERSION": "Current version", + "COLUMN_LICENSE": "License", + "COLUMN_DESCRIPTION": "Description", + "FOOT_ITEMS": "Items", + "FOOT_OF": "of" + }, + "STATE": { + "OTHER_STATUS": "Not Generated", + "QUEUED": "Queued", + "ERROR": "View Log", + "SCANNING": "Generating", + "STOPPED": "SBOM scan stopped" + }, + "NO_SBOM": "No SBOM", + "PACKAGES": "SBOM", + "REPORTED_BY": "Reported by {{scanner}}", + "GENERATE": "Create SBOM", + "DOWNLOAD": "Download SBOM", + "Details": "SBOM details", + "STOP": "Stop SBOM", + "TRIGGER_STOP_SUCCESS": "Trigger stopping SBOM generation successfully" + }, "VULNERABILITY": { "STATE": { "OTHER_STATUS": "스캔되지 않음", @@ -1105,6 +1141,7 @@ "ALL": "모두", "PLACEHOLDER": "아티팩트를 찾을 수 없음!", "SCAN_UNSUPPORTED": "지원되지 않음", + "SBOM_UNSUPPORTED": "Unsupported", "SUMMARY": "요약", "DEPENDENCIES": "종속성", "VALUES": "값", diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json index 31243b5c3..2ba78f122 100644 --- a/src/portal/src/i18n/lang/pt-br-lang.json +++ b/src/portal/src/i18n/lang/pt-br-lang.json @@ -776,6 +776,7 @@ "ARTIFACTS": "Artefatos", "SIZE": "Tamanho", "VULNERABILITY": "Vulnerabilidade", + "SBOM": "SBOM", "SIGNED": "Assinada", "AUTHOR": "Autor", "CREATED": "Data de criação", @@ -1024,6 +1025,41 @@ "IN_PROGRESS": "Buscando...", "BACK": "Voltar" }, + "SBOM": { + "CHART": { + "SCANNING_TIME": "Scan completed time:", + "SCANNING_PERCENT": "Scan progress:", + "SCANNING_PERCENT_EXPLAIN": "The scan completion progress is calculated as # of successfully scanned images / total number of images referenced within the image index.", + "TOOLTIPS_TITLE": "{{totalSbom}} of {{totalPackages}} {{package}} have known {{sbom}}.", + "TOOLTIPS_TITLE_SINGULAR": "{{totalSbom}} of {{totalPackages}} {{package}} has known {{sbom}}.", + "TOOLTIPS_TITLE_ZERO": "No recognizable SBOM detected" + }, + "GRID": { + "PLACEHOLDER": "No scan results found.", + "COLUMN_PACKAGE": "Package", + "COLUMN_PACKAGES": "Packages", + "COLUMN_VERSION": "Current version", + "COLUMN_LICENSE": "License", + "COLUMN_DESCRIPTION": "Description", + "FOOT_ITEMS": "Items", + "FOOT_OF": "of" + }, + "STATE": { + "OTHER_STATUS": "Not Generated", + "QUEUED": "Queued", + "ERROR": "View Log", + "SCANNING": "Generating", + "STOPPED": "SBOM scan stopped" + }, + "NO_SBOM": "No SBOM", + "PACKAGES": "SBOM", + "REPORTED_BY": "Reported by {{scanner}}", + "GENERATE": "Create SBOM", + "DOWNLOAD": "Download SBOM", + "Details": "SBOM details", + "STOP": "Stop SBOM", + "TRIGGER_STOP_SUCCESS": "Trigger stopping SBOM generation successfully" + }, "VULNERABILITY": { "STATE": { "OTHER_STATUS": "Não analisado", @@ -1104,6 +1140,7 @@ "ALL": "Todos", "PLACEHOLDER": "Nenhum artefato encontrado!", "SCAN_UNSUPPORTED": "Não pode ser examinada", + "SBOM_UNSUPPORTED": "Unsupported", "SUMMARY": "Resumo", "DEPENDENCIES": "Dependências", "VALUES": "Valores", diff --git a/src/portal/src/i18n/lang/tr-tr-lang.json b/src/portal/src/i18n/lang/tr-tr-lang.json index 82be0f11f..0dddab935 100644 --- a/src/portal/src/i18n/lang/tr-tr-lang.json +++ b/src/portal/src/i18n/lang/tr-tr-lang.json @@ -777,6 +777,7 @@ "ARTIFACTS": "Artifacts", "SIZE": "Boyut", "VULNERABILITY": "Güvenlik Açığı", + "SBOM": "SBOM", "BUILD_HISTORY": "Geçmişi Oluştur", "SIGNED": "İmzalanmış", "AUTHOR": "Yazar", @@ -1027,6 +1028,41 @@ "IN_PROGRESS": "Ara...", "BACK": "Geri" }, + "SBOM": { + "CHART": { + "SCANNING_TIME": "Scan completed time:", + "SCANNING_PERCENT": "Scan progress:", + "SCANNING_PERCENT_EXPLAIN": "The scan completion progress is calculated as # of successfully scanned images / total number of images referenced within the image index.", + "TOOLTIPS_TITLE": "{{totalSbom}} of {{totalPackages}} {{package}} have known {{sbom}}.", + "TOOLTIPS_TITLE_SINGULAR": "{{totalSbom}} of {{totalPackages}} {{package}} has known {{sbom}}.", + "TOOLTIPS_TITLE_ZERO": "No recognizable SBOM detected" + }, + "GRID": { + "PLACEHOLDER": "No scan results found.", + "COLUMN_PACKAGE": "Package", + "COLUMN_PACKAGES": "Packages", + "COLUMN_VERSION": "Current version", + "COLUMN_LICENSE": "License", + "COLUMN_DESCRIPTION": "Description", + "FOOT_ITEMS": "Items", + "FOOT_OF": "of" + }, + "STATE": { + "OTHER_STATUS": "Not Generated", + "QUEUED": "Queued", + "ERROR": "View Log", + "SCANNING": "Generating", + "STOPPED": "SBOM scan stopped" + }, + "NO_SBOM": "No SBOM", + "PACKAGES": "SBOM", + "REPORTED_BY": "Reported by {{scanner}}", + "GENERATE": "Create SBOM", + "DOWNLOAD": "Download SBOM", + "Details": "SBOM details", + "STOP": "Stop SBOM", + "TRIGGER_STOP_SUCCESS": "Trigger stopping SBOM generation successfully" + }, "VULNERABILITY": { "STATE": { "OTHER_STATUS": "Taranmadı", @@ -1107,6 +1143,7 @@ "ALL": "All", "PLACEHOLDER": "We couldn't find any artifacts!", "SCAN_UNSUPPORTED": "Unsupported", + "SBOM_UNSUPPORTED": "Unsupported", "SUMMARY": "Özet", "DEPENDENCIES": "Bağımlılıklar", "VALUES": "Değerler", diff --git a/src/portal/src/i18n/lang/zh-cn-lang.json b/src/portal/src/i18n/lang/zh-cn-lang.json index b738a3fdf..bb2a58578 100644 --- a/src/portal/src/i18n/lang/zh-cn-lang.json +++ b/src/portal/src/i18n/lang/zh-cn-lang.json @@ -776,6 +776,7 @@ "ARTIFACTS": "Artifacts", "SIZE": "大小", "VULNERABILITY": "漏洞", + "SBOM": "SBOM", "BUILD_HISTORY": "构建历史", "SIGNED": "已签名", "AUTHOR": "作者", @@ -1025,6 +1026,41 @@ "IN_PROGRESS": "搜索中...", "BACK": "返回" }, + "SBOM": { + "CHART": { + "SCANNING_TIME": "Scan completed time:", + "SCANNING_PERCENT": "Scan progress:", + "SCANNING_PERCENT_EXPLAIN": "The scan completion progress is calculated as # of successfully scanned images / total number of images referenced within the image index.", + "TOOLTIPS_TITLE": "{{totalSbom}} of {{totalPackages}} {{package}} have known {{sbom}}.", + "TOOLTIPS_TITLE_SINGULAR": "{{totalSbom}} of {{totalPackages}} {{package}} has known {{sbom}}.", + "TOOLTIPS_TITLE_ZERO": "No recognizable SBOM detected" + }, + "GRID": { + "PLACEHOLDER": "No scan results found.", + "COLUMN_PACKAGE": "Package", + "COLUMN_PACKAGES": "Packages", + "COLUMN_VERSION": "Current version", + "COLUMN_LICENSE": "License", + "COLUMN_DESCRIPTION": "Description", + "FOOT_ITEMS": "Items", + "FOOT_OF": "of" + }, + "STATE": { + "OTHER_STATUS": "Not Generated", + "QUEUED": "Queued", + "ERROR": "View Log", + "SCANNING": "Generating", + "STOPPED": "SBOM scan stopped" + }, + "NO_SBOM": "No SBOM", + "PACKAGES": "SBOM", + "REPORTED_BY": "Reported by {{scanner}}", + "GENERATE": "Create SBOM", + "DOWNLOAD": "Download SBOM", + "Details": "SBOM details", + "STOP": "Stop SBOM", + "TRIGGER_STOP_SUCCESS": "Trigger stopping SBOM generation successfully" + }, "VULNERABILITY": { "STATE": { "OTHER_STATUS": "未扫描", @@ -1105,6 +1141,7 @@ "ALL": "所有", "PLACEHOLDER": "未发现任何 artifacts!", "SCAN_UNSUPPORTED": "不支持扫描", + "SBOM_UNSUPPORTED": "Unsupported", "SUMMARY": "概要", "DEPENDENCIES": "依赖", "VALUES": "取值", diff --git a/src/portal/src/i18n/lang/zh-tw-lang.json b/src/portal/src/i18n/lang/zh-tw-lang.json index 4553e3445..bc1732775 100644 --- a/src/portal/src/i18n/lang/zh-tw-lang.json +++ b/src/portal/src/i18n/lang/zh-tw-lang.json @@ -776,6 +776,7 @@ "ARTIFACTS": "Artifacts", "SIZE": "大小", "VULNERABILITY": "弱點", + "SBOM": "SBOM", "BUILD_HISTORY": "建置歷史", "SIGNED": "已簽署", "AUTHOR": "作者", @@ -1024,6 +1025,41 @@ "IN_PROGRESS": "搜尋中...", "BACK": "返回" }, + "SBOM": { + "CHART": { + "SCANNING_TIME": "Scan completed time:", + "SCANNING_PERCENT": "Scan progress:", + "SCANNING_PERCENT_EXPLAIN": "The scan completion progress is calculated as # of successfully scanned images / total number of images referenced within the image index.", + "TOOLTIPS_TITLE": "{{totalSbom}} of {{totalPackages}} {{package}} have known {{sbom}}.", + "TOOLTIPS_TITLE_SINGULAR": "{{totalSbom}} of {{totalPackages}} {{package}} has known {{sbom}}.", + "TOOLTIPS_TITLE_ZERO": "No recognizable SBOM detected" + }, + "GRID": { + "PLACEHOLDER": "No scan results found.", + "COLUMN_PACKAGE": "Package", + "COLUMN_PACKAGES": "Packages", + "COLUMN_VERSION": "Current version", + "COLUMN_LICENSE": "License", + "COLUMN_DESCRIPTION": "Description", + "FOOT_ITEMS": "Items", + "FOOT_OF": "of" + }, + "STATE": { + "OTHER_STATUS": "Not Generated", + "QUEUED": "Queued", + "ERROR": "View Log", + "SCANNING": "Generating", + "STOPPED": "SBOM scan stopped" + }, + "NO_SBOM": "No SBOM", + "PACKAGES": "SBOM", + "REPORTED_BY": "Reported by {{scanner}}", + "GENERATE": "Create SBOM", + "DOWNLOAD": "Download SBOM", + "Details": "SBOM details", + "STOP": "Stop SBOM", + "TRIGGER_STOP_SUCCESS": "Trigger stopping SBOM generation successfully" +}, "VULNERABILITY": { "STATE": { "OTHER_STATUS": "未掃描", @@ -1104,6 +1140,7 @@ "ALL": "全部", "PLACEHOLDER": "未發現任何 artifacts!", "SCAN_UNSUPPORTED": "不支援掃描", + "SBOM_UNSUPPORTED": "Unsupported", "SUMMARY": "摘要", "DEPENDENCIES": "相依性", "VALUES": "值", diff --git a/src/server/v2.0/handler/scan.go b/src/server/v2.0/handler/scan.go index e58e12a84..cca0092e8 100644 --- a/src/server/v2.0/handler/scan.go +++ b/src/server/v2.0/handler/scan.go @@ -60,7 +60,7 @@ func (s *scanAPI) StopScanArtifact(ctx context.Context, params operation.StopSca return s.SendError(ctx, err) } - if err := s.scanCtl.Stop(ctx, curArtifact); err != nil { + if err := s.scanCtl.Stop(ctx, curArtifact, params.ScanType.ScanType); err != nil { return s.SendError(ctx, err) } diff --git a/src/server/v2.0/handler/scan_test.go b/src/server/v2.0/handler/scan_test.go index f073b83e9..e0977b80c 100644 --- a/src/server/v2.0/handler/scan_test.go +++ b/src/server/v2.0/handler/scan_test.go @@ -16,6 +16,7 @@ package handler import ( "fmt" + "github.com/goharbor/harbor/src/server/v2.0/models" "testing" "github.com/stretchr/testify/suite" @@ -67,12 +68,12 @@ func (suite *ScanTestSuite) TestStopScan() { suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times) url := "/projects/library/repositories/nginx/artifacts/sha256:e4f0474a75c510f40b37b6b7dc2516241ffa8bde5a442bde3d372c9519c84d90/scan/stop" - + body := models.ScanType{ScanType: "sbom"} { // failed to get artifact by reference mock.OnAnything(suite.artifactCtl, "GetByReference").Return(&artifact.Artifact{}, fmt.Errorf("failed to get artifact by reference")).Once() - res, err := suite.Post(url, nil) + res, err := suite.PostJSON(url, body) suite.NoError(err) suite.Equal(500, res.StatusCode) } @@ -82,7 +83,7 @@ func (suite *ScanTestSuite) TestStopScan() { mock.OnAnything(suite.artifactCtl, "GetByReference").Return(nil, nil).Once() mock.OnAnything(suite.scanCtl, "Stop").Return(fmt.Errorf("nil artifact to stop scan")).Once() - res, err := suite.Post(url, nil) + res, err := suite.PostJSON(url, body) suite.NoError(err) suite.Equal(500, res.StatusCode) } @@ -92,7 +93,7 @@ func (suite *ScanTestSuite) TestStopScan() { mock.OnAnything(suite.artifactCtl, "GetByReference").Return(&artifact.Artifact{}, nil).Once() mock.OnAnything(suite.scanCtl, "Stop").Return(nil).Once() - res, err := suite.Post(url, nil) + res, err := suite.PostJSON(url, body) suite.NoError(err) suite.Equal(202, res.StatusCode) } diff --git a/src/testing/controller/scan/controller.go b/src/testing/controller/scan/controller.go index 2efdd8091..bd8001380 100644 --- a/src/testing/controller/scan/controller.go +++ b/src/testing/controller/scan/controller.go @@ -191,13 +191,13 @@ func (_m *Controller) ScanAll(ctx context.Context, trigger string, async bool) ( return r0, r1 } -// Stop provides a mock function with given fields: ctx, _a1 -func (_m *Controller) Stop(ctx context.Context, _a1 *artifact.Artifact) error { - ret := _m.Called(ctx, _a1) +// Stop provides a mock function with given fields: ctx, _a1, capType +func (_m *Controller) Stop(ctx context.Context, _a1 *artifact.Artifact, capType string) error { + ret := _m.Called(ctx, _a1, capType) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact) error); ok { - r0 = rf(ctx, _a1) + if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, string) error); ok { + r0 = rf(ctx, _a1, capType) } else { r0 = ret.Error(0) } diff --git a/tests/apitests/python/library/scan_stop.py b/tests/apitests/python/library/scan_stop.py index 9f4bfd85c..e002239b2 100644 --- a/tests/apitests/python/library/scan_stop.py +++ b/tests/apitests/python/library/scan_stop.py @@ -11,7 +11,9 @@ class StopScan(base.Base, object): def stop_scan_artifact(self, project_name, repo_name, reference, expect_status_code = 202, expect_response_body = None, **kwargs): try: - data, status_code, _ = self._get_client(**kwargs).stop_scan_artifact_with_http_info(project_name, repo_name, reference) + scanType = v2_swagger_client.ScanType() + scanType.scan_type = "vulnerability" + data, status_code, _ = self._get_client(**kwargs).stop_scan_artifact_with_http_info(project_name, repo_name, reference, scanType) except ApiException as e: base._assert_status_code(expect_status_code, e.status) if expect_response_body is not None: diff --git a/tests/ci/api_run.sh b/tests/ci/api_run.sh index 83f60ffd8..26fdd5f81 100755 --- a/tests/ci/api_run.sh +++ b/tests/ci/api_run.sh @@ -21,7 +21,7 @@ set +e docker ps # run db auth api cases if [ "$1" = 'DB' ]; then - docker run -i --privileged -v $DIR/../../:/drone -v $DIR/../:/ca -w /drone $E2E_IMAGE robot --exclude proxy_cache -v DOCKER_USER:${DOCKER_USER} -v DOCKER_PWD:${DOCKER_PWD} -v ip:$2 -v ip1: -v http_get_ca:false -v HARBOR_PASSWORD:Harbor12345 /drone/tests/robot-cases/Group1-Nightly/Setup.robot /drone/tests/robot-cases/Group0-BAT/API_DB.robot + docker run -i --privileged -v $DIR/../../:/drone -v $DIR/../:/ca -w /drone $E2E_IMAGE robot --exclude proxy_cache --exclude stop_scan -v DOCKER_USER:${DOCKER_USER} -v DOCKER_PWD:${DOCKER_PWD} -v ip:$2 -v ip1: -v http_get_ca:false -v HARBOR_PASSWORD:Harbor12345 /drone/tests/robot-cases/Group1-Nightly/Setup.robot /drone/tests/robot-cases/Group0-BAT/API_DB.robot elif [ "$1" = 'PROXY_CACHE' ]; then docker run -i --privileged -v $DIR/../../:/drone -v $DIR/../:/ca -w /drone $E2E_IMAGE robot --include setup --include proxy_cache -v DOCKER_USER:${DOCKER_USER} -v DOCKER_PWD:${DOCKER_PWD} -v ip:$2 -v ip1: -v http_get_ca:false -v HARBOR_PASSWORD:Harbor12345 /drone/tests/robot-cases/Group1-Nightly/Setup.robot /drone/tests/robot-cases/Group0-BAT/API_DB.robot elif [ "$1" = 'LDAP' ]; then From 03d9575d845594e81ed29c5a868a3acf844df0b2 Mon Sep 17 00:00:00 2001 From: MinerYang Date: Tue, 9 Apr 2024 16:50:46 +0800 Subject: [PATCH 074/145] update referrer manifest descriptor size (#20207) cache manifest when first time pull if cacheEnabled Signed-off-by: yminer --- src/server/registry/referrers.go | 59 ++++++++++-- src/server/registry/referrers_test.go | 129 +++++++++++++++++++++++++- 2 files changed, 177 insertions(+), 11 deletions(-) diff --git a/src/server/registry/referrers.go b/src/server/registry/referrers.go index ee715faba..00504dfd4 100644 --- a/src/server/registry/referrers.go +++ b/src/server/registry/referrers.go @@ -22,11 +22,16 @@ import ( "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/goharbor/harbor/src/lib/cache" + "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/errors" lib_http "github.com/goharbor/harbor/src/lib/http" + "github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/q" "github.com/goharbor/harbor/src/pkg/accessory" "github.com/goharbor/harbor/src/pkg/artifact" + "github.com/goharbor/harbor/src/pkg/cached/manifest/redis" + "github.com/goharbor/harbor/src/pkg/registry" "github.com/goharbor/harbor/src/server/router" "github.com/goharbor/harbor/src/server/v2.0/handler" ) @@ -38,12 +43,16 @@ func newReferrersHandler() http.Handler { return &referrersHandler{ artifactManager: artifact.NewManager(), accessoryManager: accessory.NewManager(), + registryClient: registry.Cli, + maniCacheManager: redis.NewManager(), } } type referrersHandler struct { artifactManager artifact.Manager accessoryManager accessory.Manager + registryClient registry.Client + maniCacheManager redis.CachedManager } func (r *referrersHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { @@ -75,18 +84,56 @@ func (r *referrersHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { lib_http.SendError(w, err) return } - // Build index manifest from accessories mfs := make([]ocispec.Descriptor, 0) for _, acc := range accs { - accArt, err := r.artifactManager.GetByDigest(ctx, repository, acc.GetData().Digest) + accArtDigest := acc.GetData().Digest + accArt, err := r.artifactManager.GetByDigest(ctx, repository, accArtDigest) if err != nil { lib_http.SendError(w, err) return } - mf := ocispec.Descriptor{ + // whether get manifest from cache + fromCache := false + // whether need write manifest to cache + writeCache := false + var maniContent []byte + + // pull manifest, will try to pull from cache first + // and write to cache when pull manifest from registry at first time + if config.CacheEnabled() { + maniContent, err = r.maniCacheManager.Get(req.Context(), accArtDigest) + if err == nil { + fromCache = true + } else { + log.Debugf("failed to get manifest %s from cache, will fallback to registry, error: %v", accArtDigest, err) + if errors.As(err, &cache.ErrNotFound) { + writeCache = true + } + } + } + if !fromCache { + mani, _, err := r.registryClient.PullManifest(accArt.RepositoryName, accArtDigest) + if err != nil { + lib_http.SendError(w, err) + return + } + _, maniContent, err = mani.Payload() + if err != nil { + lib_http.SendError(w, err) + return + } + // write manifest to cache when first time pulling + if writeCache { + err = r.maniCacheManager.Save(req.Context(), accArtDigest, maniContent) + if err != nil { + log.Warningf("failed to save accArt manifest %s to cache, error: %v", accArtDigest, err) + } + } + } + desc := ocispec.Descriptor{ MediaType: accArt.ManifestMediaType, - Size: accArt.Size, + Size: int64(len(maniContent)), Digest: digest.Digest(accArt.Digest), Annotations: accArt.Annotations, ArtifactType: accArt.ArtifactType, @@ -94,10 +141,10 @@ func (r *referrersHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { // filter use accArt.ArtifactType as artifactType if at != "" { if accArt.ArtifactType == at { - mfs = append(mfs, mf) + mfs = append(mfs, desc) } } else { - mfs = append(mfs, mf) + mfs = append(mfs, desc) } } diff --git a/src/server/registry/referrers_test.go b/src/server/registry/referrers_test.go index f8d8abc22..5eab49ee7 100644 --- a/src/server/registry/referrers_test.go +++ b/src/server/registry/referrers_test.go @@ -3,20 +3,50 @@ package registry import ( "context" "encoding/json" + "fmt" "net/http" "net/http/httptest" "testing" beegocontext "github.com/beego/beego/v2/server/web/context" ocispec "github.com/opencontainers/image-spec/specs-go/v1" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/stretchr/testify/assert" + "github.com/goharbor/harbor/src/lib/cache" + "github.com/goharbor/harbor/src/lib/config" accessorymodel "github.com/goharbor/harbor/src/pkg/accessory/model" basemodel "github.com/goharbor/harbor/src/pkg/accessory/model/base" "github.com/goharbor/harbor/src/pkg/artifact" + "github.com/goharbor/harbor/src/pkg/distribution" "github.com/goharbor/harbor/src/server/router" "github.com/goharbor/harbor/src/testing/mock" accessorytesting "github.com/goharbor/harbor/src/testing/pkg/accessory" arttesting "github.com/goharbor/harbor/src/testing/pkg/artifact" + testmanifest "github.com/goharbor/harbor/src/testing/pkg/cached/manifest/redis" + regtesting "github.com/goharbor/harbor/src/testing/pkg/registry" +) + +var ( + OCIManifest = `{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.example.sbom", + "digest": "sha256:5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", + "size": 123 + }, + "layers": [ + { + "mediaType": "application/vnd.example.data.v1.tar+gzip", + "digest": "sha256:e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317", + "size": 1234 + } + ], + "annotations": { + "name": "test-image" + } + }` ) func TestReferrersHandlerOK(t *testing.T) { @@ -35,10 +65,10 @@ func TestReferrersHandlerOK(t *testing.T) { artifactMock.On("GetByDigest", mock.Anything, mock.Anything, mock.Anything). Return(&artifact.Artifact{ - Digest: digestVal, + Digest: "sha256:4911bb745e19a6b5513755f3d033f10ef10c34b40edc631809e28be8a7c005f6", ManifestMediaType: "application/vnd.oci.image.manifest.v1+json", MediaType: "application/vnd.example.sbom", - ArtifactType: "application/vnd.example+type", + ArtifactType: "application/vnd.example.sbom", Size: 1000, Annotations: map[string]string{ "name": "test-image", @@ -56,13 +86,23 @@ func TestReferrersHandlerOK(t *testing.T) { SubArtifactDigest: digestVal, SubArtifactRepo: "goharbor", Type: accessorymodel.TypeCosignSignature, + Digest: "sha256:4911bb745e19a6b5513755f3d033f10ef10c34b40edc631809e28be8a7c005f6", }, }, }, nil) + manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(OCIManifest)) + if err != nil { + t.Fatal(err) + } + regCliMock := ®testing.Client{} + config.DefaultMgr().Set(context.TODO(), "cache_enabled", false) + mock.OnAnything(regCliMock, "PullManifest").Return(manifest, "", nil) + handler := &referrersHandler{ artifactManager: artifactMock, accessoryManager: accessoryMock, + registryClient: regCliMock, } handler.ServeHTTP(rec, req) @@ -72,10 +112,89 @@ func TestReferrersHandlerOK(t *testing.T) { t.Errorf("Expected status code %d, but got %d", http.StatusOK, rec.Code) } index := &ocispec.Index{} - json.Unmarshal([]byte(rec.Body.String()), index) - if index.Manifests[0].ArtifactType != "application/vnd.example+type" { - t.Errorf("Expected response body %s, but got %s", "application/vnd.example+type", rec.Body.String()) + json.Unmarshal(rec.Body.Bytes(), index) + if index.Manifests[0].ArtifactType != "application/vnd.example.sbom" { + t.Errorf("Expected response body %s, but got %s", "application/vnd.example.sbom", rec.Body.String()) } + _, content, _ := manifest.Payload() + assert.Equal(t, int64(len(content)), index.Manifests[0].Size) +} + +func TestReferrersHandlerSavetoCache(t *testing.T) { + rec := httptest.NewRecorder() + digestVal := "sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b" + req, err := http.NewRequest("GET", "/v2/test/repository/referrers/sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b", nil) + if err != nil { + t.Fatal(err) + } + input := &beegocontext.BeegoInput{} + input.SetParam(":reference", digestVal) + *req = *(req.WithContext(context.WithValue(req.Context(), router.ContextKeyInput{}, input))) + + artifactMock := &arttesting.Manager{} + accessoryMock := &accessorytesting.Manager{} + + artifactMock.On("GetByDigest", mock.Anything, mock.Anything, mock.Anything). + Return(&artifact.Artifact{ + Digest: "sha256:4911bb745e19a6b5513755f3d033f10ef10c34b40edc631809e28be8a7c005f6", + ManifestMediaType: "application/vnd.oci.image.manifest.v1+json", + MediaType: "application/vnd.example.sbom", + ArtifactType: "application/vnd.example.sbom", + Size: 1000, + Annotations: map[string]string{ + "name": "test-image", + }, + }, nil) + + accessoryMock.On("Count", mock.Anything, mock.Anything). + Return(int64(1), nil) + accessoryMock.On("List", mock.Anything, mock.Anything). + Return([]accessorymodel.Accessory{ + &basemodel.Default{ + Data: accessorymodel.AccessoryData{ + ID: 1, + ArtifactID: 2, + SubArtifactDigest: digestVal, + SubArtifactRepo: "goharbor", + Type: accessorymodel.TypeCosignSignature, + Digest: "sha256:4911bb745e19a6b5513755f3d033f10ef10c34b40edc631809e28be8a7c005f6", + }, + }, + }, nil) + + manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(OCIManifest)) + if err != nil { + t.Fatal(err) + } + + // cache_enabled pull from cahce + config.DefaultMgr().Set(context.TODO(), "cache_enabled", true) + cacheManagerMock := &testmanifest.CachedManager{} + mock.OnAnything(cacheManagerMock, "Get").Return(nil, fmt.Errorf("unable to do stuff: %w", cache.ErrNotFound)) + regCliMock := ®testing.Client{} + mock.OnAnything(regCliMock, "PullManifest").Return(manifest, "", nil) + mock.OnAnything(cacheManagerMock, "Save").Return(nil) + + handler := &referrersHandler{ + artifactManager: artifactMock, + accessoryManager: accessoryMock, + registryClient: regCliMock, + maniCacheManager: cacheManagerMock, + } + + handler.ServeHTTP(rec, req) + + // check that the response has the expected status code (200 OK) + if rec.Code != http.StatusOK { + t.Errorf("Expected status code %d, but got %d", http.StatusOK, rec.Code) + } + index := &ocispec.Index{} + json.Unmarshal(rec.Body.Bytes(), index) + if index.Manifests[0].ArtifactType != "application/vnd.example.sbom" { + t.Errorf("Expected response body %s, but got %s", "application/vnd.example.sbom", rec.Body.String()) + } + _, content, _ := manifest.Payload() + assert.Equal(t, int64(len(content)), index.Manifests[0].Size) } func TestReferrersHandlerEmpty(t *testing.T) { From 2e7db335b3d082aa184f24203c284e21a4e00734 Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Tue, 9 Apr 2024 17:30:53 +0800 Subject: [PATCH 075/145] Add auto generate SBOM on push feature (#20250) Signed-off-by: stonezdj Co-authored-by: stonezdj Co-authored-by: Wang Yan --- .../event/handler/internal/artifact.go | 5 ++++ src/controller/event/handler/internal/util.go | 19 ++++++++++++ .../event/handler/internal/util_test.go | 30 +++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/src/controller/event/handler/internal/artifact.go b/src/controller/event/handler/internal/artifact.go index fb212ac87..9218db95a 100644 --- a/src/controller/event/handler/internal/artifact.go +++ b/src/controller/event/handler/internal/artifact.go @@ -258,6 +258,11 @@ func (a *ArtifactEventHandler) onPush(ctx context.Context, event *event.Artifact if err := autoScan(ctx, &artifact.Artifact{Artifact: *event.Artifact}, event.Tags...); err != nil { log.Errorf("scan artifact %s@%s failed, error: %v", event.Artifact.RepositoryName, event.Artifact.Digest, err) } + + log.Debugf("auto generate sbom is triggered for artifact event %+v", event) + if err := autoGenSBOM(ctx, &artifact.Artifact{Artifact: *event.Artifact}); err != nil { + log.Errorf("generate sbom for artifact %s@%s failed, error: %v", event.Artifact.RepositoryName, event.Artifact.Digest, err) + } }() return nil diff --git a/src/controller/event/handler/internal/util.go b/src/controller/event/handler/internal/util.go index c7cf51243..cc10e09ca 100644 --- a/src/controller/event/handler/internal/util.go +++ b/src/controller/event/handler/internal/util.go @@ -20,6 +20,7 @@ import ( "github.com/goharbor/harbor/src/controller/artifact" "github.com/goharbor/harbor/src/controller/project" "github.com/goharbor/harbor/src/controller/scan" + "github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/orm" ) @@ -43,3 +44,21 @@ func autoScan(ctx context.Context, a *artifact.Artifact, tags ...string) error { return scan.DefaultController.Scan(ctx, a, options...) })(orm.SetTransactionOpNameToContext(ctx, "tx-auto-scan")) } + +func autoGenSBOM(ctx context.Context, a *artifact.Artifact) error { + proj, err := project.Ctl.Get(ctx, a.ProjectID) + if err != nil { + return err + } + if !proj.AutoSBOMGen() { + return nil + } + // transaction here to work with the image index + return orm.WithTransaction(func(ctx context.Context) error { + options := []scan.Option{} + // TODO: extract the sbom scan type to a constant + options = append(options, scan.WithScanType("sbom")) + log.Debugf("sbom scan controller artifact %+v, options %+v", a, options) + return scan.DefaultController.Scan(ctx, a, options...) + })(orm.SetTransactionOpNameToContext(ctx, "tx-auto-gen-sbom")) +} diff --git a/src/controller/event/handler/internal/util_test.go b/src/controller/event/handler/internal/util_test.go index 615fad9f6..4a48378a4 100644 --- a/src/controller/event/handler/internal/util_test.go +++ b/src/controller/event/handler/internal/util_test.go @@ -95,6 +95,36 @@ func (suite *AutoScanTestSuite) TestAutoScan() { suite.Nil(autoScan(ctx, art)) } +func (suite *AutoScanTestSuite) TestAutoScanSBOM() { + mock.OnAnything(suite.projectController, "Get").Return(&proModels.Project{ + Metadata: map[string]string{ + proModels.ProMetaAutoSBOMGen: "true", + }, + }, nil) + + mock.OnAnything(suite.scanController, "Scan").Return(nil) + + ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{}) + art := &artifact.Artifact{} + + suite.Nil(autoGenSBOM(ctx, art)) +} + +func (suite *AutoScanTestSuite) TestAutoScanSBOMFalse() { + mock.OnAnything(suite.projectController, "Get").Return(&proModels.Project{ + Metadata: map[string]string{ + proModels.ProMetaAutoSBOMGen: "false", + }, + }, nil) + + mock.OnAnything(suite.scanController, "Scan").Return(nil) + + ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{}) + art := &artifact.Artifact{} + + suite.Nil(autoGenSBOM(ctx, art)) +} + func (suite *AutoScanTestSuite) TestAutoScanFailed() { mock.OnAnything(suite.projectController, "Get").Return(&proModels.Project{ Metadata: map[string]string{ From 2bb5166c80c6e315646ad77042d5f6637743d165 Mon Sep 17 00:00:00 2001 From: MinerYang Date: Wed, 10 Apr 2024 13:46:00 +0800 Subject: [PATCH 076/145] adopt cosign with oci-spec 1.1 (#20245) Signed-off-by: yminer add comment for cosign middlware --- src/server/middleware/cosign/cosign.go | 32 ++++++++++++++++++++++++ src/server/middleware/subject/subject.go | 5 ++++ 2 files changed, 37 insertions(+) diff --git a/src/server/middleware/cosign/cosign.go b/src/server/middleware/cosign/cosign.go index 13021cb10..53fcdc7ed 100644 --- a/src/server/middleware/cosign/cosign.go +++ b/src/server/middleware/cosign/cosign.go @@ -65,6 +65,38 @@ var ( } ] } +*/ +// cosign adopt oci-spec 1.1 will have request and manifest like below +// It will skip this middleware since not using cosignRe for subject artifact reference +// use Subject Middleware indtead +/* +PUT /v2/library/goharbor/harbor-db/manifests/sha256:aabea2bdd5a6fb79c13837b88c7b158f4aa57a621194ee21959d0b520eda412f +{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.dev.cosign.artifact.sig.v1+json", + "size": 233, + "digest": "sha256:c025e9532dbc880534be96dbbb86a6bf63a272faced7f07bb8b4ceb45ca938d1" + }, + "layers": [ + { + "mediaType": "application/vnd.dev.cosign.simplesigning.v1+json", + "size": 257, + "digest": "sha256:38d07d81bf1d026da6420295113115d999ad6da90073b5e67147f978626423e6", + "annotations": { + "dev.cosignproject.cosign/signature": "MEUCIDOQc6I4MSd4/s8Bc8S7LXHCOnm4MGimpQdeCInLzM0VAiEAhWWYxmwEmYrFJ8xYNE3ow7PS4zeGe1R4RUbXRIawKJ4=", + "dev.sigstore.cosign/bundle": "{\"SignedEntryTimestamp\":\"MEUCIC5DSFQx3nZhPFquF4NAdfetjqLR6qAa9i04cEtAg7VjAiEAzG2DUxqH+MdFSPih/EL/Vvsn3L1xCJUlOmRZeUYZaG0=\",\"Payload\":{\"body\":\"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiIzOGQwN2Q4MWJmMWQwMjZkYTY0MjAyOTUxMTMxMTVkOTk5YWQ2ZGE5MDA3M2I1ZTY3MTQ3Zjk3ODYyNjQyM2U2In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJRE9RYzZJNE1TZDQvczhCYzhTN0xYSENPbm00TUdpbXBRZGVDSW5Mek0wVkFpRUFoV1dZeG13RW1ZckZKOHhZTkUzb3c3UFM0emVHZTFSNFJVYlhSSWF3S0o0PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCUVZVSk1TVU1nUzBWWkxTMHRMUzBLVFVacmQwVjNXVWhMYjFwSmVtb3dRMEZSV1VsTGIxcEplbW93UkVGUlkwUlJaMEZGWVVoSk1DOTZiWEpIYW1VNE9FeFVTM0ZDU2tvNWJXZDNhWEprWkFwaVJrZGpNQzlRYWtWUUwxbFJNelJwZFZweWJGVnRhMGx3ZDBocFdVTmxSV3M0YWpoWE5rSnBaV3BxTHk5WmVVRnZZaXN5VTFCTGRqUkJQVDBLTFMwdExTMUZUa1FnVUZWQ1RFbERJRXRGV1MwdExTMHRDZz09In19fX0=\",\"integratedTime\":1712651102,\"logIndex\":84313668,\"logID\":\"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d\"}}" + } + } + ], + "subject": { + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "size": 2621, + "digest": "sha256:e50f88df1b11f94627e35bed9f34214392363508a2b07146d0a94516da97e4c0" + } +} + */ func SignatureMiddleware() func(http.Handler) http.Handler { return middleware.AfterResponse(func(w http.ResponseWriter, r *http.Request, statusCode int) error { diff --git a/src/server/middleware/subject/subject.go b/src/server/middleware/subject/subject.go index 4c1c47315..7995703e2 100644 --- a/src/server/middleware/subject/subject.go +++ b/src/server/middleware/subject/subject.go @@ -39,6 +39,9 @@ var ( // the media type of notation signature layer mediaTypeNotationLayer = "application/vnd.cncf.notary.signature" + // cosign media type in config layer, which would support in oci-spec1.1 + mediaTypeCosignConfig = "application/vnd.dev.cosign.artifact.sig.v1+json" + // annotation of nydus image layerAnnotationNydusBootstrap = "containerd.io/snapshot/nydus-bootstrap" @@ -152,6 +155,8 @@ func Middleware() func(http.Handler) http.Handler { } case mediaTypeNotationLayer: accData.Type = model.TypeNotationSignature + case mediaTypeCosignConfig: + accData.Type = model.TypeCosignSignature case mediaTypeHarborSBOM: accData.Type = model.TypeHarborSBOM } From a858fb4f4d90a31a50e728a40a89f29d184a6d55 Mon Sep 17 00:00:00 2001 From: tostt Date: Wed, 10 Apr 2024 09:17:30 +0200 Subject: [PATCH 077/145] Updated internationalisation : fr-fr (#20179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update french translation Signed-off-by: tostt * More updates french language Signed-off-by: tostt * Corr. spelling Signed-off-by: tostt * Update src/portal/src/i18n/lang/fr-fr-lang.json Co-authored-by: Florian Blampey Signed-off-by: tostt * Update src/portal/src/i18n/lang/fr-fr-lang.json Co-authored-by: Florian Blampey Signed-off-by: tostt * Update src/portal/src/i18n/lang/fr-fr-lang.json Co-authored-by: Thomas Coudert Signed-off-by: tostt * Update src/portal/src/i18n/lang/fr-fr-lang.json Co-authored-by: Thomas Coudert Signed-off-by: tostt * Update src/portal/src/i18n/lang/fr-fr-lang.json Co-authored-by: Thomas Coudert Signed-off-by: tostt * Update src/portal/src/i18n/lang/fr-fr-lang.json Co-authored-by: Thomas Coudert Signed-off-by: tostt * Update src/portal/src/i18n/lang/fr-fr-lang.json Co-authored-by: Thomas Coudert Signed-off-by: tostt * Update src/portal/src/i18n/lang/fr-fr-lang.json Co-authored-by: Thomas Coudert Signed-off-by: tostt * Update fr-fr-lang.json : further changes following thcdrt's review Signed-off-by: tostt * Update src/portal/src/i18n/lang/fr-fr-lang.json Co-authored-by: Thomas Coudert Signed-off-by: tostt * Update fr-fr-lang.json: translate Expand to Déplier Signed-off-by: tostt * Update fr-fr-lang.json: Remove duplicate portion of text Signed-off-by: tostt --------- Signed-off-by: tostt Co-authored-by: Vadim Bauer Co-authored-by: Florian Blampey Co-authored-by: Thomas Coudert Co-authored-by: Wang Yan --- src/portal/src/i18n/lang/fr-fr-lang.json | 140 +++++++++++------------ 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json index f09c6ba66..bf45e5c53 100644 --- a/src/portal/src/i18n/lang/fr-fr-lang.json +++ b/src/portal/src/i18n/lang/fr-fr-lang.json @@ -5,7 +5,7 @@ "VIC": "vSphere Integrated Containers", "MGMT": "Management", "REG": "Registre", - "HARBOR_SWAGGER": "Harbor Swagger", + "HARBOR_SWAGGER": "Swagger Harbor", "THEME_DARK_TEXT": "SOMBRE", "THEME_LIGHT_TEXT": "CLAIR" }, @@ -28,7 +28,7 @@ "DELETE": "Supprimer", "LOG_IN": "S'identifier", "LOG_IN_OIDC": "Connexion via fournisseur OIDC", - "LOG_IN_OIDC_WITH_PROVIDER_NAME": "LOGIN WITH {{providerName}}", + "LOG_IN_OIDC_WITH_PROVIDER_NAME": "S'IDENTIFIER AVEC {{providerName}}", "SIGN_UP_LINK": "Ouvrir un compte", "SIGN_UP": "S'inscrire", "CONFIRM": "Confirmer", @@ -130,7 +130,7 @@ "ADMIN_RENAME_TIP": "Cliquez sur le bouton pour changer le nom d'utilisateur en \"admin@harbor.local\". Cette opération ne peut pas être annulée.", "RENAME_SUCCESS": "Renommage effectué !", "RENAME_CONFIRM_INFO": "Attention, changer le nom d'utilisateur pour \"admin@harbor.local\" ne peut pas être annulé.", - "CLI_PASSWORD": "CLI secret", + "CLI_PASSWORD": "Secret CLI", "CLI_PASSWORD_TIP": "Le secret CLI peut être utilisé comme mot de passe pour le client Docker ou Helm. Lorsque le mode d'authentification est OIDC, nous recommandons fortement d'utiliser des comptes robots, car les secrets CLI dépendent de la validité du jeton ID et nécessitent que l'utilisateur se connecte régulièrement à l'interface utilisateur pour rafraîchir le jeton.", "COPY_SUCCESS": "Copie effectuée", "COPY_ERROR": "Copie échouée", @@ -248,12 +248,12 @@ "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.", "OF": "sur", "COUNT_QUOTA": "Quota de nombre", - "STORAGE_QUOTA": "Project quota limits", + "STORAGE_QUOTA": "Quota du projet", "COUNT_QUOTA_TIP": "Entrez un entier entre '1' et '100000000', ou '-1' pour un quota illimité", "STORAGE_QUOTA_TIP": "La limite haute du quota de stockage n'accepte que des valeurs entières, au maximum '1024TB'. Entrez '-1' pour un quota illimité", - "QUOTA_UNLIMIT_TIP": "The maximum logical space that can be used by the project. Pour un quota illimité, entrez '-1'.", + "QUOTA_UNLIMIT_TIP": "Espace maximum logique pouvant être utilisé par le projet. Pour un quota illimité, entrez '-1'.", "TYPE": "Type", - "PROXY_CACHE": "Proxy Cache", + "PROXY_CACHE": "Cache proxy", "PROXY_CACHE_TOOLTIP": "Activez cette option pour permettre à ce projet d'agir comme un cache de pull pour un espace de noms particulier dans un registre cible. Harbor ne peut agir en tant que proxy que pour les registres DockerHub et Harbor.", "ENDPOINT": "Endpoint", "PROXY_CACHE_ENDPOINT": "Endpoint du Proxy Cache", @@ -287,9 +287,9 @@ "SCAN": "Analyse des vulnérabilités", "AUTOSCAN_TOGGLE": "Analyse automatique des images lors de l'envoi", "AUTOSCAN_POLICY": "Analyser automatiquement les images lorsqu'elles sont envoyées au projet du registre.", - "SBOM": "SBOM generation", - "AUTOSBOM_TOGGLE": "Automatically generate SBOM on push", - "AUTOSBOM_POLICY": "Automatically generate SBOM when the images are pushed to the project registry." + "SBOM": "Génération de SBOM", + "AUTOSBOM_TOGGLE": "Générer automatiquement un SBOM au push", + "AUTOSBOM_POLICY": "Générer automatiquement un SBOM lorsque les images sont poussées sur le registre." }, "MEMBER": { "NEW_USER": "Ajouter un nouveau membre", @@ -339,7 +339,7 @@ "SWITCH_TITLE": "Confirmez le changement de membres projet", "SWITCH_SUMMARY": "Voulez-vous changer les membres projet {{param}}?", "SET_ROLE": "Définir Role", - "REMOVE": "Remove", + "REMOVE": "Retirer", "GROUP_NAME_REQUIRED": "Le nom du groupe est requis", "NON_EXISTENT_GROUP": "Ce groupe n'existe pas", "GROUP_ALREADY_ADDED": "Ce groupe a déjà été ajouté au projet" @@ -383,46 +383,46 @@ "NEVER_EXPIRED": "Ne jamais expirer", "NAME_PREFIX": "Préfixe du nom du compte robot", "NAME_PREFIX_REQUIRED": "Le préfixe du nom du compte robot est obligatoire", - "UPDATE": "Update", - "AUDIT_LOG": "Audit Log", - "PREHEAT_INSTANCE": "Preheat Instance", - "PROJECT": "Project", - "REPLICATION_POLICY": "Replication Policy", - "REPLICATION": "Replication", - "REPLICATION_ADAPTER": "Replication Adapter", - "REGISTRY": "Registry", - "SCAN_ALL": "Scan All", - "SYSTEM_VOLUMES": "System Volumes", - "GARBAGE_COLLECTION": "Garbage Collection", - "PURGE_AUDIT": "Purge Audit", + "UPDATE": "Mettre à jour", + "AUDIT_LOG": "Log d'audit", + "PREHEAT_INSTANCE": "Préchauffer l'instance", + "PROJECT": "Projet", + "REPLICATION_POLICY": "Politique de réplication", + "REPLICATION": "Réplication", + "REPLICATION_ADAPTER": "Adaptateur de réplication", + "REGISTRY": "Registre", + "SCAN_ALL": "Scanner tout", + "SYSTEM_VOLUMES": "Volumes système", + "GARBAGE_COLLECTION": "Purge", + "PURGE_AUDIT": "Purger l'audit", "JOBSERVICE_MONITOR": "Job Service Monitor", - "TAG_RETENTION": "Tag Retention", + "TAG_RETENTION": "Rétention des tags", "SCANNER": "Scanner", "LABEL": "Label", - "EXPORT_CVE": "Export CVE", - "SECURITY_HUB": "Security Hub", - "CATALOG": "Catalog", - "METADATA": "Project Metadata", - "REPOSITORY": "Repository", - "ARTIFACT": "Artifact", + "EXPORT_CVE": "Exporter les CVE", + "SECURITY_HUB": "Centre de sécurité", + "CATALOG": "Catalogue", + "METADATA": "Métadonnées du projet", + "REPOSITORY": "Dépôt", + "ARTIFACT": "Artefact", "SCAN": "Scan", "TAG": "Tag", - "ACCESSORY": "Accessory", - "ARTIFACT_ADDITION": "Artifact Addition", - "ARTIFACT_LABEL": "Artifact Label", - "PREHEAT_POLICY": "Preheat Policy", - "IMMUTABLE_TAG": "Immutable Tag", + "ACCESSORY": "Accessoire", + "ARTIFACT_ADDITION": "Artefact Addition", + "ARTIFACT_LABEL": "Label d'artefact", + "PREHEAT_POLICY": "Politique de préchauffage", + "IMMUTABLE_TAG": "Tag immutable", "LOG": "Log", - "NOTIFICATION_POLICY": "Notification Policy", + "NOTIFICATION_POLICY": "Politique de notification", "QUOTA": "Quota", - "BACK": "Back", - "NEXT": "Next", - "FINISH": "Finish", - "BASIC_INFO": "Basic Information", - "SELECT_PERMISSIONS": "Select Permissions", - "SELECT_SYSTEM_PERMISSIONS": "Select System Permissions", - "SELECT_PROJECT_PERMISSIONS": "Select Project Permissions", - "SYSTEM_PERMISSIONS": "System Permissions" + "BACK": "Retour", + "NEXT": "Suivant", + "FINISH": "Finir", + "BASIC_INFO": "Informations de base", + "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" }, "WEBHOOK": { "EDIT_BUTTON": "Éditer", @@ -536,7 +536,7 @@ "RESOURCE_TYPE": "Type de ressource" }, "REPLICATION": { - "PUSH_BASED_ONLY": "Only for the push-based replication", + "PUSH_BASED_ONLY": "Uniquement pour la réplication de type push", "YES": "Oui", "SECONDS": "Secondes", "MINUTES": "Minutes", @@ -645,7 +645,7 @@ "CANNOT_EDIT": "La règle de réplication ne peut pas être modifiée lorsqu'elle est activée.", "INVALID_DATE": "Date non valide.", "PLACEHOLDER": "Nous n'avons trouvé aucune règle de réplication !", - "JOB_PLACEHOLDER": "Nous n'avons trouvé aucun travail de réplication !", + "JOB_PLACEHOLDER": "Nous n'avons trouvé aucune tâche de réplication !", "NO_ENDPOINT_INFO": "Ajoutez d'abord un endpoint", "NO_LABEL_INFO": "Ajoutez d'abord un label", "NO_PROJECT_INFO": "Ce projet n'existe pas", @@ -705,11 +705,11 @@ "BANDWIDTH_TOOLTIP": "Set the maximum network bandwidth for each replication worker. Please pay attention to the number of concurrent executions (max. {{max_job_workers}}). For unlimited bandwidth, please enter -1.", "UNLIMITED": "Illimitée", "UNREACHABLE_SOURCE_REGISTRY": "Échec de connexion au registre source. Veuillez vérifier que le registre source est disponible avant d'éditer cette règle: {{error}}", - "CRON_ERROR_TIP": "The 1st field of the cron string must be 0 and the 2nd filed can not be \"*\"", + "CRON_ERROR_TIP": "Le 1er champ de la chaîne cron doit être 0 et le 2ème champ ne peut pas être \"*\"", "COPY_BY_CHUNK": "Copier par morceaux", "COPY_BY_CHUNK_TIP": "Spécifie si le blob doit être copié par morceaux. Transférer par morceaux peut augmenter le nombre de requêtes faites à l'API.", "TRIGGER_STOP_SUCCESS": "Déclenchement avec succès de l'arrêt d'exécution", - "CRON_STR": "Cron String" + "CRON_STR": "Chaîne cron" }, "DESTINATION": { "NEW_ENDPOINT": "Nouveau Endpoint", @@ -938,7 +938,7 @@ "TOKEN_REVIEW": "Endpoint de revue de token", "SKIP_SEARCH": "Passer la recherche", "VERIFY_CERT": "Vérifier le certificat", - "ADMIN_GROUPS": "Admin Groups" + "ADMIN_GROUPS": "Groupes admin" }, "OIDC": { "OIDC_PROVIDER": "Fournisseur OIDC", @@ -1079,7 +1079,7 @@ "COLUMN_VERSION": "Version Actuelle", "COLUMN_FIXED": "Réparé dans la version", "COLUMN_DESCRIPTION": "Description", - "FOOT_ITEMS": "Items", + "FOOT_ITEMS": "Entrées", "FOOT_OF": "sur", "IN_ALLOW_LIST": "Présent dans la liste blanche CVE", "CVSS3": "CVSS3" @@ -1126,7 +1126,7 @@ "TAG_COMMAND": "Taguer une image pour ce projet :", "PUSH_COMMAND": "Push une image dans ce projet :", "COPY_ERROR": "Copie échouée, veuillez essayer de copier manuellement les commandes de référence.", - "COPY_PULL_COMMAND": "COPY PULL COMMAND" + "COPY_PULL_COMMAND": "COMMANDE COPY PULL" }, "ARTIFACT": { "FILTER_FOR_ARTIFACTS": "Filtrer les artefacts", @@ -1181,7 +1181,7 @@ "PULL_TIME": "Date/Heure de pull", "PUSH_TIME": "Date/Heure de push", "OF": "sur", - "ITEMS": "items", + "ITEMS": "entrées", "ADD_TAG": "AJOUTER TAG", "REMOVE_TAG": "SUPPRIMER TAG", "NAME_ALREADY_EXISTS": "Ce tag existe déjà dans ce dépôt" @@ -1211,14 +1211,14 @@ "DELETE": "Supprimer", "OF": "sur", "PROJECT_QUOTA_DEFAULT_ARTIFACT": "Nombre par défaut d'artefacts par projet", - "PROJECT_QUOTA_DEFAULT_DISK": "Default quota space per project", + "PROJECT_QUOTA_DEFAULT_DISK": "Quota d'espace par défaut par projet", "EDIT_PROJECT_QUOTAS": "Éditer les quotas projet", "EDIT_DEFAULT_PROJECT_QUOTAS": "Éditer les quotas projet par défaut", "SET_QUOTAS": "Configurer les quotas pour le projet '{{params}}'", "SET_DEFAULT_QUOTAS": "Configurer les quotas projet par défaut lors de la création de nouveaux projets", "COUNT_QUOTA": "Quota de nombre", "COUNT_DEFAULT_QUOTA": "Quota de nombre par défaut", - "STORAGE_QUOTA": "Project quota limits", + "STORAGE_QUOTA": "Limites des quotas de projets", "STORAGE_DEFAULT_QUOTA": "Quota de stockage par défaut", "SAVE_SUCCESS": "Edition de quota effectuée", "UNLIMITED": "Illimité", @@ -1469,9 +1469,9 @@ "NAME_REX": "Le nom doit comporter au moins 2 caractères avec des minuscules, des chiffres et. _- et doit commencer par des caractères ou des chiffres.", "DESCRIPTION": "Description", "SBOM": "SBOM", - "VULNERABILITY": "Vulnerability", - "SUPPORTED": "Supported", - "NOT_SUPPORTED": "Not Supported", + "VULNERABILITY": "Vulnérabilité", + "SUPPORTED": "Supporté", + "NOT_SUPPORTED": "Non Supporté", "ENDPOINT": "Endpoint", "ENDPOINT_EXISTS": "L'URL de l'endpoint existe déjà", "ENDPOINT_REQUIRED": "L'URL de l'endpoint est requise", @@ -1676,7 +1676,7 @@ "PREHEAT_EXPLAIN": "Le préchauffage migrera l'image vers le réseau p2p", "CRITERIA_EXPLAIN": "Comme spécifié dans la section 'Sécurité de déploiement' dans l'onglet Configuration", "SKIP_CERT_VERIFY": "Cochez cette case pour ignorer la vérification du certificat lorsque le fournisseur distant utilise un certificat auto-signé ou non approuvé.", - "NAME_TOOLTIP": "Policy name consists of one or more groups of uppercase letter, lowercase letter or number; and groups are separated by a dot, underscore, or hyphen.", + "NAME_TOOLTIP": "Le nom de la politique consiste en un ou plusieurs groupes de lettres (majuscules ou minuscules) ou de chiffres ; les groupes sont séparés par un point, un trait de soulignement ou un trait d'union.", "NEED_HELP": "Veuillez d'abord demander à votre administrateur système d'ajouter un fournisseur" }, "PAGINATION": { @@ -1712,9 +1712,9 @@ "PROJECTS_MODAL_TITLE": "Projets pour le compte robot", "PROJECTS_MODAL_SUMMARY": "Voici les projets couverts par ce compte robot.", "CREATE_ROBOT": "Créer un compte robot Système", - "CREATE_ROBOT_SUMMARY": "Create a system Robot Account that will cover permissions for the system as well as for specific projects", + "CREATE_ROBOT_SUMMARY": "Créer un compte système Robot qui couvrira les autorisations pour le système ainsi que pour des projets spécifiques", "EDIT_ROBOT": "Éditer un compte robot Système", - "EDIT_ROBOT_SUMMARY": "Edit a system Robot Account that will cover permissions for the system as well as for specific projects", + "EDIT_ROBOT_SUMMARY": "Éditer un compte système Robot qui couvrira les autorisations pour le système ainsi que pour des projets spécifiques", "EXPIRATION_TIME": "Date/Heure d'Expiration", "EXPIRATION_TIME_EXPLAIN": "L'heure d'expiration (en jours, le point de départ est l'heure de création) du jeton du compte robot. Pour ne jamais expirer, entrer \"-1\".", "EXPIRATION_DEFAULT": "jours (défaut)", @@ -1724,7 +1724,7 @@ "COVER_ALL": "Couvrir tous les projets", "COVER_ALL_EXPLAIN": "Cocher pour appliquer à tous les projets existants et futurs", "COVER_ALL_SUMMARY": "\"Tous les projets existants et futurs\" sélectionné.", - "RESET_PERMISSION": "RESET ALL PROJECT PERMISSIONS", + "RESET_PERMISSION": "REINITIALISER TOUTES LES PERMISSIONS PROJET", "PERMISSION_COLUMN": "Permissions", "EXPIRES_AT": "Expire à", "VIEW_SECRET": "Actualiser le secret", @@ -1757,8 +1757,8 @@ "REPOSITORY": "Dépôt", "EXPIRES_IN": "Expire dans", "EXPIRED": "Expiré", - "SELECT_ALL_PROJECT": "SELECT ALL PROJECTS", - "UNSELECT_ALL_PROJECT": "UNSELECT ALL PROJECTS" + "SELECT_ALL_PROJECT": "SELECTIONNER TOUS LES PROJETS", + "UNSELECT_ALL_PROJECT": "DESELECTIONNER TOUS LES PROJETS" }, "ACCESSORY": { "DELETION_TITLE_ACCESSORY": "Confirmer la suppression de l'accessoire", @@ -1809,7 +1809,7 @@ "EXPORT_TITLE": "Export de CVEs", "EXPORT_SUBTITLE": "Définir les conditions d'exportation", "EXPORT_CVE_FILTER_HELP_TEXT": "Entrer plusieurs cveIDs séparés par des virgules", - "CVE_IDS": "CVE IDs", + "CVE_IDS": "IDs CVE", "EXPORT_BUTTON": "Exporter", "JOB_NAME": "Nom de la tâche", "JOB_NAME_REQUIRED": "Le nom de la tâche est requis", @@ -1892,9 +1892,9 @@ "SCHEDULE_RESUME_BTN_INFO": "REPRENDRE — Reprend les files d'attente de tâches à exécuter.", "WORKER_FREE_BTN_INFO": "Arrête les tâches en cours pour libérer le worker", "CRON": "Cron", - "WAITING_TOO_LONG_1": "Certain jobs have been pending for execution for over 24 hours. Please check the job service ", - "WAITING_TOO_LONG_2": "dashboard.", - "WAITING_TOO_LONG_3": "For more details, please refer to the ", + "WAITING_TOO_LONG_1": "Certaines tâches sont en attente d'exécution depuis plus de 24 heures. Veuillez vérifier le ", + "WAITING_TOO_LONG_2": "tableau de bord.", + "WAITING_TOO_LONG_3": "Pour plus de détails, veuillez consulter le ", "WAITING_TOO_LONG_4": "Wiki." }, "CLARITY": { @@ -1902,8 +1902,8 @@ "CLOSE": "Fermer", "SHOW": "Afficher", "HIDE": "Cacher", - "EXPAND": "Etendre", - "COLLAPSE": "Collapse", + "EXPAND": "Déplier", + "COLLAPSE": "Replier", "MORE": "Plus", "SELECT": "Sélectionner", "SELECT_ALL": "Tout sélectionner", @@ -1960,7 +1960,7 @@ "ENTER_MESSAGE": "Entrer votre message ici" }, "SECURITY_HUB": { - "SECURITY_HUB": "Tableau de bord de sécurité", + "SECURITY_HUB": "Centre de sécurité", "ARTIFACTS": "artefact(s)", "SCANNED": "scannés", "NOT_SCANNED": "non scannés", @@ -1968,7 +1968,7 @@ "TOTAL_AND_FIXABLE": "{{totalNum}} total dont {{fixableNum}} corrigeables", "TOP_5_ARTIFACT": "Top 5 des Artefacts les plus Dangereux", "TOP_5_CVE": "Top 5 des CVEs les plus Dangereuses", - "CVE_ID": "CVE ID", + "CVE_ID": "ID CVE", "VUL": "Vulnérabilités", "CVE": "CVEs", "FILTER_BY": "Filtrer par", From 89995075a7f00d906f22be3f70b0474d82237d62 Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Wed, 10 Apr 2024 20:39:25 +0800 Subject: [PATCH 078/145] Update swagger API to display SBOM content in addition API (#20234) complete task #20066 Signed-off-by: stonezdj Co-authored-by: stonezdj --- .../artifact/processor/chart/chart.go | 2 +- .../artifact/processor/sbom/sbom.go | 89 ++++++++++ .../artifact/processor/sbom/sbom_test.go | 166 ++++++++++++++++++ 3 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 src/controller/artifact/processor/sbom/sbom.go create mode 100644 src/controller/artifact/processor/sbom/sbom_test.go diff --git a/src/controller/artifact/processor/chart/chart.go b/src/controller/artifact/processor/chart/chart.go index 059d47bbf..e7df72b56 100644 --- a/src/controller/artifact/processor/chart/chart.go +++ b/src/controller/artifact/processor/chart/chart.go @@ -85,11 +85,11 @@ func (p *processor) AbstractAddition(_ context.Context, artifact *artifact.Artif if err != nil { return nil, err } + defer blob.Close() content, err := io.ReadAll(blob) if err != nil { return nil, err } - blob.Close() chartDetails, err := p.chartOperator.GetDetails(content) if err != nil { return nil, err diff --git a/src/controller/artifact/processor/sbom/sbom.go b/src/controller/artifact/processor/sbom/sbom.go new file mode 100644 index 000000000..ec0222fb9 --- /dev/null +++ b/src/controller/artifact/processor/sbom/sbom.go @@ -0,0 +1,89 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sbom + +import ( + "context" + "encoding/json" + "io" + + v1 "github.com/opencontainers/image-spec/specs-go/v1" + + "github.com/goharbor/harbor/src/controller/artifact/processor" + "github.com/goharbor/harbor/src/controller/artifact/processor/base" + "github.com/goharbor/harbor/src/lib/errors" + "github.com/goharbor/harbor/src/lib/log" + "github.com/goharbor/harbor/src/pkg/artifact" +) + +const ( + // processorArtifactTypeSBOM is the artifact type for SBOM, it's scope is only used in the processor + processorArtifactTypeSBOM = "SBOM" + // processorMediaType is the media type for SBOM, it's scope is only used to register the processor + processorMediaType = "application/vnd.goharbor.harbor.sbom.v1" +) + +func init() { + pc := &Processor{} + pc.ManifestProcessor = base.NewManifestProcessor() + if err := processor.Register(pc, processorMediaType); err != nil { + log.Errorf("failed to register processor for media type %s: %v", processorMediaType, err) + return + } +} + +// Processor is the processor for SBOM +type Processor struct { + *base.ManifestProcessor +} + +// AbstractAddition returns the addition for SBOM +func (m *Processor) AbstractAddition(_ context.Context, art *artifact.Artifact, _ string) (*processor.Addition, error) { + man, _, err := m.RegCli.PullManifest(art.RepositoryName, art.Digest) + if err != nil { + return nil, errors.Wrap(err, "failed to pull manifest") + } + _, payload, err := man.Payload() + if err != nil { + return nil, errors.Wrap(err, "failed to get payload") + } + manifest := &v1.Manifest{} + if err := json.Unmarshal(payload, manifest); err != nil { + return nil, err + } + // SBOM artifact should only have one layer + if len(manifest.Layers) != 1 { + return nil, errors.New(nil).WithCode(errors.NotFoundCode).WithMessage("The sbom is not found") + } + layerDgst := manifest.Layers[0].Digest.String() + _, blob, err := m.RegCli.PullBlob(art.RepositoryName, layerDgst) + if err != nil { + return nil, errors.Wrap(err, "failed to pull the blob") + } + defer blob.Close() + content, err := io.ReadAll(blob) + if err != nil { + return nil, err + } + return &processor.Addition{ + Content: content, + ContentType: processorMediaType, + }, nil +} + +// GetArtifactType the artifact type is used to display the artifact type in the UI +func (m *Processor) GetArtifactType(_ context.Context, _ *artifact.Artifact) string { + return processorArtifactTypeSBOM +} diff --git a/src/controller/artifact/processor/sbom/sbom_test.go b/src/controller/artifact/processor/sbom/sbom_test.go new file mode 100644 index 000000000..6128c550f --- /dev/null +++ b/src/controller/artifact/processor/sbom/sbom_test.go @@ -0,0 +1,166 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sbom + +import ( + "context" + "fmt" + "io" + "strings" + "testing" + + "github.com/docker/distribution" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" + + "github.com/goharbor/harbor/src/controller/artifact/processor/base" + "github.com/goharbor/harbor/src/lib/errors" + "github.com/goharbor/harbor/src/pkg/artifact" + "github.com/goharbor/harbor/src/testing/pkg/registry" +) + +type SBOMProcessorTestSuite struct { + suite.Suite + processor *Processor + regCli *registry.Client +} + +func (suite *SBOMProcessorTestSuite) SetupSuite() { + suite.regCli = ®istry.Client{} + suite.processor = &Processor{ + &base.ManifestProcessor{ + RegCli: suite.regCli, + }, + } +} + +func (suite *SBOMProcessorTestSuite) TearDownSuite() { +} + +func (suite *SBOMProcessorTestSuite) TestAbstractAdditionNormal() { + manContent := `{ + "schemaVersion": 2, + "config": { + "mediaType": "application/vnd.oci.image.config.v1+json", + "digest": "sha256:e91b9dfcbbb3b88bac94726f276b89de46e4460b55f6e6d6f876e666b150ec5b", + "size": 498 + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 32654, + "digest": "sha256:abc" + }] +}` + sbomContent := "this is a sbom content" + reader := strings.NewReader(sbomContent) + blobReader := io.NopCloser(reader) + mani, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(manContent)) + suite.Require().NoError(err) + suite.regCli.On("PullManifest", mock.Anything, mock.Anything).Return(mani, "sha256:123", nil).Once() + suite.regCli.On("PullBlob", mock.Anything, mock.Anything).Return(int64(123), blobReader, nil).Once() + addition, err := suite.processor.AbstractAddition(context.Background(), &artifact.Artifact{RepositoryName: "repo", Digest: "digest"}, "sbom") + suite.Nil(err) + suite.Equal(sbomContent, string(addition.Content)) +} + +func (suite *SBOMProcessorTestSuite) TestAbstractAdditionMultiLayer() { + manContent := `{ + "schemaVersion": 2, + "config": { + "mediaType": "application/vnd.oci.image.config.v1+json", + "digest": "sha256:e91b9dfcbbb3b88bac94726f276b89de46e4460b55f6e6d6f876e666b150ec5b", + "size": 498 + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 32654, + "digest": "sha256:abc" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 843, + "digest": "sha256:def" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 531, + "digest": "sha256:123" + } + ] +}` + mani, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(manContent)) + suite.Require().NoError(err) + suite.regCli.On("PullManifest", mock.Anything, mock.Anything).Return(mani, "sha256:123", nil).Once() + _, err = suite.processor.AbstractAddition(context.Background(), &artifact.Artifact{RepositoryName: "repo", Digest: "digest"}, "sbom") + suite.NotNil(err) +} + +func (suite *SBOMProcessorTestSuite) TestAbstractAdditionPullBlobError() { + manContent := `{ + "schemaVersion": 2, + "config": { + "mediaType": "application/vnd.oci.image.config.v1+json", + "digest": "sha256:e91b9dfcbbb3b88bac94726f276b89de46e4460b55f6e6d6f876e666b150ec5b", + "size": 498 + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 32654, + "digest": "sha256:abc" + } + ] +}` + mani, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(manContent)) + suite.Require().NoError(err) + suite.regCli.On("PullManifest", mock.Anything, mock.Anything).Return(mani, "sha256:123", nil).Once() + suite.regCli.On("PullBlob", mock.Anything, mock.Anything).Return(int64(123), nil, errors.NotFoundError(fmt.Errorf("not found"))).Once() + addition, err := suite.processor.AbstractAddition(context.Background(), &artifact.Artifact{RepositoryName: "repo", Digest: "digest"}, "sbom") + suite.NotNil(err) + suite.Nil(addition) +} +func (suite *SBOMProcessorTestSuite) TestAbstractAdditionNoSBOMLayer() { + manContent := `{ + "schemaVersion": 2, + "config": { + "mediaType": "application/vnd.oci.image.config.v1+json", + "digest": "sha256:e91b9dfcbbb3b88bac94726f276b89de46e4460b55f6e6d6f876e666b150ec5b", + "size": 498 + } +}` + mani, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(manContent)) + suite.Require().NoError(err) + suite.regCli.On("PullManifest", mock.Anything, mock.Anything).Return(mani, "sha256:123", nil).Once() + _, err = suite.processor.AbstractAddition(context.Background(), &artifact.Artifact{RepositoryName: "repo", Digest: "digest"}, "sbom") + suite.NotNil(err) +} + +func (suite *SBOMProcessorTestSuite) TestAbstractAdditionPullManifestError() { + suite.regCli.On("PullManifest", mock.Anything, mock.Anything).Return(nil, "sha256:123", errors.NotFoundError(fmt.Errorf("not found"))).Once() + _, err := suite.processor.AbstractAddition(context.Background(), &artifact.Artifact{RepositoryName: "repo", Digest: "digest"}, "sbom") + suite.NotNil(err) + +} + +func (suite *SBOMProcessorTestSuite) TestGetArtifactType() { + suite.Equal(processorArtifactTypeSBOM, suite.processor.GetArtifactType(context.Background(), &artifact.Artifact{})) +} + +func TestSBOMProcessorTestSuite(t *testing.T) { + suite.Run(t, &SBOMProcessorTestSuite{}) +} From 5d7c668028ac11b64152259736b6251c49f26120 Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Wed, 10 Apr 2024 22:47:45 +0800 Subject: [PATCH 079/145] Support list artifact with_sbom_overview option (#20244) Signed-off-by: stonezdj Co-authored-by: stonezdj --- src/controller/scan/base_controller.go | 38 +++++++++++++- src/controller/scan/base_controller_test.go | 37 +++++++++++++- src/pkg/scan/rest/v1/models.go | 22 +++++--- src/pkg/scan/rest/v1/models_test.go | 15 ++++++ src/pkg/scan/rest/v1/spec.go | 5 ++ src/server/v2.0/handler/artifact.go | 7 +-- .../handler/assembler/{vul.go => report.go} | 51 +++++++++++++------ .../assembler/{vul_test.go => report_test.go} | 45 ++++++++++++---- src/server/v2.0/handler/model/artifact.go | 14 +++++ src/server/v2.0/handler/model/option.go | 47 +++++++++++++++++ src/server/v2.0/handler/model/option_test.go | 33 ++++++++++++ 11 files changed, 277 insertions(+), 37 deletions(-) create mode 100644 src/pkg/scan/rest/v1/models_test.go rename src/server/v2.0/handler/assembler/{vul.go => report.go} (55%) rename src/server/v2.0/handler/assembler/{vul_test.go => report_test.go} (61%) create mode 100644 src/server/v2.0/handler/model/option.go create mode 100644 src/server/v2.0/handler/model/option_test.go diff --git a/src/controller/scan/base_controller.go b/src/controller/scan/base_controller.go index a57d1f21b..a1627af30 100644 --- a/src/controller/scan/base_controller.go +++ b/src/controller/scan/base_controller.go @@ -17,6 +17,7 @@ package scan import ( "bytes" "context" + "encoding/json" "fmt" "reflect" "strings" @@ -674,12 +675,23 @@ func (bc *basicController) GetReport(ctx context.Context, artifact *ar.Artifact, return reports, nil } +func isSBOMMimeTypes(mimeTypes []string) bool { + for _, mimeType := range mimeTypes { + if mimeType == v1.MimeTypeSBOMReport { + return true + } + } + return false +} + // GetSummary ... func (bc *basicController) GetSummary(ctx context.Context, artifact *ar.Artifact, mimeTypes []string) (map[string]interface{}, error) { if artifact == nil { return nil, errors.New("no way to get report summaries for nil artifact") } - + if isSBOMMimeTypes(mimeTypes) { + return bc.GetSBOMSummary(ctx, artifact, mimeTypes) + } // Get reports first rps, err := bc.GetReport(ctx, artifact, mimeTypes) if err != nil { @@ -708,6 +720,30 @@ func (bc *basicController) GetSummary(ctx context.Context, artifact *ar.Artifact return summaries, nil } +func (bc *basicController) GetSBOMSummary(ctx context.Context, art *ar.Artifact, mimeTypes []string) (map[string]interface{}, error) { + if art == nil { + return nil, errors.New("no way to get report summaries for nil artifact") + } + r, err := bc.sc.GetRegistrationByProject(ctx, art.ProjectID) + if err != nil { + return nil, errors.Wrap(err, "scan controller: get sbom summary") + } + reports, err := bc.manager.GetBy(ctx, art.Digest, r.UUID, mimeTypes) + if err != nil { + return nil, err + } + if len(reports) == 0 { + return map[string]interface{}{}, nil + } + reportContent := reports[0].Report + if len(reportContent) == 0 { + log.Warning("no content for current report") + } + result := map[string]interface{}{} + err = json.Unmarshal([]byte(reportContent), &result) + return result, err +} + // GetScanLog ... func (bc *basicController) GetScanLog(ctx context.Context, artifact *ar.Artifact, uuid string) ([]byte, error) { if len(uuid) == 0 { diff --git a/src/controller/scan/base_controller_test.go b/src/controller/scan/base_controller_test.go index 9d9f2c334..9d485f16c 100644 --- a/src/controller/scan/base_controller_test.go +++ b/src/controller/scan/base_controller_test.go @@ -78,7 +78,7 @@ type ControllerTestSuite struct { taskMgr *tasktesting.Manager reportMgr *reporttesting.Manager ar artifact.Controller - c Controller + c *basicController reportConverter *postprocessorstesting.ScanReportV1ToV2Converter cache *mockcache.Cache } @@ -180,7 +180,19 @@ func (suite *ControllerTestSuite) SetupSuite() { }, } + sbomReport := []*scan.Report{ + { + ID: 12, + UUID: "rp-uuid-002", + Digest: "digest-code", + RegistrationUUID: "uuid001", + MimeType: "application/vnd.scanner.adapter.sbom.report.harbor+json; version=1.0", + Status: "Success", + Report: `{"sbom_digest": "sha256:1234567890", "scan_status": "Success", "duration": 3, "start_time": "2021-09-01T00:00:00Z", "end_time": "2021-09-01T00:00:03Z"}`, + }, + } mgr.On("GetBy", mock.Anything, suite.artifact.Digest, suite.registration.UUID, []string{v1.MimeTypeNativeReport}).Return(reports, nil) + mgr.On("GetBy", mock.Anything, suite.artifact.Digest, suite.registration.UUID, []string{v1.MimeTypeSBOMReport}).Return(sbomReport, nil) mgr.On("Get", mock.Anything, "rp-uuid-001").Return(reports[0], nil) mgr.On("UpdateReportData", "rp-uuid-001", suite.rawReport, (int64)(10000)).Return(nil) mgr.On("UpdateStatus", "the-uuid-123", "Success", (int64)(10000)).Return(nil) @@ -620,3 +632,26 @@ func (suite *ControllerTestSuite) makeExtraAttrs(artifactID int64, reportUUIDs . return extraAttrs } + +func (suite *ControllerTestSuite) TestGenerateSBOMSummary() { + sum, err := suite.c.GetSBOMSummary(context.TODO(), suite.artifact, []string{v1.MimeTypeSBOMReport}) + suite.Nil(err) + suite.NotNil(sum) + status := sum["scan_status"] + suite.NotNil(status) + dgst := sum["sbom_digest"] + suite.NotNil(dgst) + suite.Equal("Success", status) + suite.Equal("sha256:1234567890", dgst) +} + +func TestIsSBOMMimeTypes(t *testing.T) { + // Test with a slice containing the SBOM mime type + assert.True(t, isSBOMMimeTypes([]string{v1.MimeTypeSBOMReport})) + + // Test with a slice not containing the SBOM mime type + assert.False(t, isSBOMMimeTypes([]string{"application/vnd.oci.image.manifest.v1+json"})) + + // Test with an empty slice + assert.False(t, isSBOMMimeTypes([]string{})) +} diff --git a/src/pkg/scan/rest/v1/models.go b/src/pkg/scan/rest/v1/models.go index fc48717fb..c31edb93b 100644 --- a/src/pkg/scan/rest/v1/models.go +++ b/src/pkg/scan/rest/v1/models.go @@ -21,12 +21,11 @@ import ( "github.com/goharbor/harbor/src/lib/errors" ) -const ( - // ScanTypeVulnerability the scan type for vulnerability - ScanTypeVulnerability = "vulnerability" - // ScanTypeSbom the scan type for sbom - ScanTypeSbom = "sbom" -) +var supportedMimeTypes = []string{ + MimeTypeNativeReport, + MimeTypeGenericVulnerabilityReport, + MimeTypeSBOMReport, +} // Scanner represents metadata of a Scanner Adapter which allow Harbor to lookup a scanner capable of // scanning a given Artifact stored in its registry and making sure that it can interpret a @@ -105,7 +104,7 @@ func (md *ScannerAdapterMetadata) Validate() error { // either of v1.MimeTypeNativeReport OR v1.MimeTypeGenericVulnerabilityReport is required found = false for _, pm := range ca.ProducesMimeTypes { - if pm == MimeTypeNativeReport || pm == MimeTypeGenericVulnerabilityReport { + if isSupportedMimeType(pm) { found = true break } @@ -119,6 +118,15 @@ func (md *ScannerAdapterMetadata) Validate() error { return nil } +func isSupportedMimeType(mimeType string) bool { + for _, mt := range supportedMimeTypes { + if mt == mimeType { + return true + } + } + return false +} + // HasCapability returns true when mine type of the artifact support by the scanner func (md *ScannerAdapterMetadata) HasCapability(mimeType string) bool { for _, capability := range md.Capabilities { diff --git a/src/pkg/scan/rest/v1/models_test.go b/src/pkg/scan/rest/v1/models_test.go new file mode 100644 index 000000000..e96aa0178 --- /dev/null +++ b/src/pkg/scan/rest/v1/models_test.go @@ -0,0 +1,15 @@ +package v1 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsSupportedMimeType(t *testing.T) { + // Test with a supported mime type + assert.True(t, isSupportedMimeType(MimeTypeSBOMReport), "isSupportedMimeType should return true for supported mime types") + + // Test with an unsupported mime type + assert.False(t, isSupportedMimeType("unsupported/mime-type"), "isSupportedMimeType should return false for unsupported mime types") +} diff --git a/src/pkg/scan/rest/v1/spec.go b/src/pkg/scan/rest/v1/spec.go index bb46d1c1b..a867e7167 100644 --- a/src/pkg/scan/rest/v1/spec.go +++ b/src/pkg/scan/rest/v1/spec.go @@ -39,9 +39,14 @@ const ( MimeTypeScanRequest = "application/vnd.scanner.adapter.scan.request+json; version=1.0" // MimeTypeScanResponse defines the mime type for scan response MimeTypeScanResponse = "application/vnd.scanner.adapter.scan.response+json; version=1.0" + // MimeTypeSBOMReport + MimeTypeSBOMReport = "application/vnd.security.sbom.report+json; version=1.0" // MimeTypeGenericVulnerabilityReport defines the MIME type for the generic report with enhanced information MimeTypeGenericVulnerabilityReport = "application/vnd.security.vulnerability.report; version=1.1" + ScanTypeVulnerability = "vulnerability" + ScanTypeSbom = "sbom" + apiPrefix = "/api/v1" ) diff --git a/src/server/v2.0/handler/artifact.go b/src/server/v2.0/handler/artifact.go index 84d78cc5d..dfc457047 100644 --- a/src/server/v2.0/handler/artifact.go +++ b/src/server/v2.0/handler/artifact.go @@ -107,8 +107,8 @@ func (a *artifactAPI) ListArtifacts(ctx context.Context, params operation.ListAr if err != nil { return a.SendError(ctx, err) } - - assembler := assembler.NewVulAssembler(lib.BoolValue(params.WithScanOverview), parseScanReportMimeTypes(params.XAcceptVulnerabilities)) + overviewOpts := model.NewOverviewOptions(model.WithSBOM(lib.BoolValue(params.WithSbomOverview)), model.WithVuln(lib.BoolValue(params.WithScanOverview))) + assembler := assembler.NewScanReportAssembler(overviewOpts, parseScanReportMimeTypes(params.XAcceptVulnerabilities)) var artifacts []*models.Artifact for _, art := range arts { artifact := &model.Artifact{} @@ -138,8 +138,9 @@ func (a *artifactAPI) GetArtifact(ctx context.Context, params operation.GetArtif } art := &model.Artifact{} art.Artifact = *artifact + overviewOpts := model.NewOverviewOptions(model.WithSBOM(lib.BoolValue(params.WithSbomOverview)), model.WithVuln(lib.BoolValue(params.WithScanOverview))) - err = assembler.NewVulAssembler(lib.BoolValue(params.WithScanOverview), parseScanReportMimeTypes(params.XAcceptVulnerabilities)).WithArtifacts(art).Assemble(ctx) + err = assembler.NewScanReportAssembler(overviewOpts, parseScanReportMimeTypes(params.XAcceptVulnerabilities)).WithArtifacts(art).Assemble(ctx) if err != nil { log.Warningf("failed to assemble vulnerabilities with artifact, error: %v", err) } diff --git a/src/server/v2.0/handler/assembler/vul.go b/src/server/v2.0/handler/assembler/report.go similarity index 55% rename from src/server/v2.0/handler/assembler/vul.go rename to src/server/v2.0/handler/assembler/report.go index 055baab35..e4f9657ea 100644 --- a/src/server/v2.0/handler/assembler/vul.go +++ b/src/server/v2.0/handler/assembler/report.go @@ -20,43 +20,48 @@ import ( "github.com/goharbor/harbor/src/controller/scan" "github.com/goharbor/harbor/src/lib" "github.com/goharbor/harbor/src/lib/log" + v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" "github.com/goharbor/harbor/src/server/v2.0/handler/model" ) const ( vulnerabilitiesAddition = "vulnerabilities" + startTime = "start_time" + endTime = "end_time" + scanStatus = "scan_status" + sbomDigest = "sbom_digest" + duration = "duration" ) -// NewVulAssembler returns vul assembler -func NewVulAssembler(withScanOverview bool, mimeTypes []string) *VulAssembler { - return &VulAssembler{ - scanChecker: scan.NewChecker(), - scanCtl: scan.DefaultController, - - withScanOverview: withScanOverview, - mimeTypes: mimeTypes, +// NewScanReportAssembler returns vul assembler +func NewScanReportAssembler(option *model.OverviewOptions, mimeTypes []string) *ScanReportAssembler { + return &ScanReportAssembler{ + overviewOption: option, + scanChecker: scan.NewChecker(), + scanCtl: scan.DefaultController, + mimeTypes: mimeTypes, } } -// VulAssembler vul assembler -type VulAssembler struct { +// ScanReportAssembler vul assembler +type ScanReportAssembler struct { scanChecker scan.Checker scanCtl scan.Controller - artifacts []*model.Artifact - withScanOverview bool - mimeTypes []string + artifacts []*model.Artifact + mimeTypes []string + overviewOption *model.OverviewOptions } // WithArtifacts set artifacts for the assembler -func (assembler *VulAssembler) WithArtifacts(artifacts ...*model.Artifact) *VulAssembler { +func (assembler *ScanReportAssembler) WithArtifacts(artifacts ...*model.Artifact) *ScanReportAssembler { assembler.artifacts = artifacts return assembler } // Assemble assemble vul for the artifacts -func (assembler *VulAssembler) Assemble(ctx context.Context) error { +func (assembler *ScanReportAssembler) Assemble(ctx context.Context) error { version := lib.GetAPIVersion(ctx) for _, artifact := range assembler.artifacts { @@ -72,7 +77,7 @@ func (assembler *VulAssembler) Assemble(ctx context.Context) error { artifact.SetAdditionLink(vulnerabilitiesAddition, version) - if assembler.withScanOverview { + if assembler.overviewOption.WithVuln { for _, mimeType := range assembler.mimeTypes { overview, err := assembler.scanCtl.GetSummary(ctx, &artifact.Artifact, []string{mimeType}) if err != nil { @@ -83,6 +88,20 @@ func (assembler *VulAssembler) Assemble(ctx context.Context) error { } } } + if assembler.overviewOption.WithSBOM { + overview, err := assembler.scanCtl.GetSummary(ctx, &artifact.Artifact, []string{v1.MimeTypeSBOMReport}) + if err != nil { + log.Warningf("get scan summary of artifact %s@%s for %s failed, error:%v", artifact.RepositoryName, artifact.Digest, v1.MimeTypeSBOMReport, err) + } else if len(overview) > 0 { + artifact.SBOMOverView = map[string]interface{}{ + startTime: overview[startTime], + endTime: overview[endTime], + scanStatus: overview[scanStatus], + sbomDigest: overview[sbomDigest], + duration: overview[duration], + } + } + } } return nil diff --git a/src/server/v2.0/handler/assembler/vul_test.go b/src/server/v2.0/handler/assembler/report_test.go similarity index 61% rename from src/server/v2.0/handler/assembler/vul_test.go rename to src/server/v2.0/handler/assembler/report_test.go index 202720afa..6079a1d60 100644 --- a/src/server/v2.0/handler/assembler/vul_test.go +++ b/src/server/v2.0/handler/assembler/report_test.go @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/suite" + v1sq "github.com/goharbor/harbor/src/pkg/scan/rest/v1" "github.com/goharbor/harbor/src/server/v2.0/handler/model" "github.com/goharbor/harbor/src/testing/controller/scan" "github.com/goharbor/harbor/src/testing/mock" @@ -33,11 +34,11 @@ func (suite *VulAssemblerTestSuite) TestScannable() { checker := &scan.Checker{} scanCtl := &scan.Controller{} - assembler := VulAssembler{ - scanChecker: checker, - scanCtl: scanCtl, - withScanOverview: true, - mimeTypes: []string{"mimeType"}, + assembler := ScanReportAssembler{ + scanChecker: checker, + scanCtl: scanCtl, + overviewOption: model.NewOverviewOptions(model.WithVuln(true)), + mimeTypes: []string{"mimeType"}, } mock.OnAnything(checker, "IsScannable").Return(true, nil) @@ -56,10 +57,10 @@ func (suite *VulAssemblerTestSuite) TestNotScannable() { checker := &scan.Checker{} scanCtl := &scan.Controller{} - assembler := VulAssembler{ - scanChecker: checker, - scanCtl: scanCtl, - withScanOverview: true, + assembler := ScanReportAssembler{ + scanChecker: checker, + scanCtl: scanCtl, + overviewOption: model.NewOverviewOptions(model.WithVuln(true)), } mock.OnAnything(checker, "IsScannable").Return(false, nil) @@ -74,6 +75,32 @@ func (suite *VulAssemblerTestSuite) TestNotScannable() { scanCtl.AssertNotCalled(suite.T(), "GetSummary") } +func (suite *VulAssemblerTestSuite) TestAssembleSBOMOverview() { + checker := &scan.Checker{} + scanCtl := &scan.Controller{} + + assembler := ScanReportAssembler{ + scanChecker: checker, + scanCtl: scanCtl, + overviewOption: model.NewOverviewOptions(model.WithSBOM(true)), + mimeTypes: []string{v1sq.MimeTypeSBOMReport}, + } + + mock.OnAnything(checker, "IsScannable").Return(true, nil) + overview := map[string]interface{}{ + "sbom_digest": "sha256:123456", + "scan_status": "Success", + } + mock.OnAnything(scanCtl, "GetSummary").Return(overview, nil) + + var artifact model.Artifact + err := assembler.WithArtifacts(&artifact).Assemble(context.TODO()) + suite.Nil(err) + suite.Equal(artifact.SBOMOverView["sbom_digest"], "sha256:123456") + suite.Equal(artifact.SBOMOverView["scan_status"], "Success") + +} + func TestVulAssemblerTestSuite(t *testing.T) { suite.Run(t, &VulAssemblerTestSuite{}) } diff --git a/src/server/v2.0/handler/model/artifact.go b/src/server/v2.0/handler/model/artifact.go index 3627d9ced..f931c0abf 100644 --- a/src/server/v2.0/handler/model/artifact.go +++ b/src/server/v2.0/handler/model/artifact.go @@ -28,7 +28,9 @@ import ( // Artifact model type Artifact struct { artifact.Artifact + // TODO: rename to VulOverview ScanOverview map[string]interface{} `json:"scan_overview"` + SBOMOverView map[string]interface{} `json:"sbom_overview"` } // ToSwagger converts the artifact to the swagger model @@ -84,6 +86,18 @@ func (a *Artifact) ToSwagger() *models.Artifact { art.ScanOverview[key] = summary } } + if len(a.SBOMOverView) > 0 { + js, err := json.Marshal(a.SBOMOverView) + if err != nil { + log.Warningf("convert sbom summary failed, error: %v", err) + } + sbomOverview := &models.SBOMOverview{} + err = json.Unmarshal(js, sbomOverview) + if err != nil { + log.Warningf("failed to get sbom summary: error: %v", err) + } + art.SbomOverview = sbomOverview + } return art } diff --git a/src/server/v2.0/handler/model/option.go b/src/server/v2.0/handler/model/option.go new file mode 100644 index 000000000..06aab9943 --- /dev/null +++ b/src/server/v2.0/handler/model/option.go @@ -0,0 +1,47 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +// OverviewOptions define the option to query overview info +type OverviewOptions struct { + WithVuln bool + WithSBOM bool +} + +// Option define the func to build options +type Option func(*OverviewOptions) + +// NewOverviewOptions create a new OverviewOptions +func NewOverviewOptions(options ...Option) *OverviewOptions { + opts := &OverviewOptions{} + for _, f := range options { + f(opts) + } + return opts +} + +// WithVuln set the option to query vulnerability info +func WithVuln(enable bool) Option { + return func(o *OverviewOptions) { + o.WithVuln = enable + } +} + +// WithSBOM set the option to query SBOM info +func WithSBOM(enable bool) Option { + return func(o *OverviewOptions) { + o.WithSBOM = enable + } +} diff --git a/src/server/v2.0/handler/model/option_test.go b/src/server/v2.0/handler/model/option_test.go new file mode 100644 index 000000000..1b1bf2f37 --- /dev/null +++ b/src/server/v2.0/handler/model/option_test.go @@ -0,0 +1,33 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestOverviewOptions(t *testing.T) { + // Test NewOverviewOptions with WithVuln and WithSBOM + opts := NewOverviewOptions(WithVuln(true), WithSBOM(true)) + assert.True(t, opts.WithVuln) + assert.True(t, opts.WithSBOM) + + // Test NewOverviewOptions with WithVuln and WithSBOM set to false + opts = NewOverviewOptions(WithVuln(false), WithSBOM(false)) + assert.False(t, opts.WithVuln) + assert.False(t, opts.WithSBOM) +} From 4c9e84cae1a92341829e1cff24b8f66e81cb943a Mon Sep 17 00:00:00 2001 From: Shengwen YU Date: Thu, 11 Apr 2024 10:05:05 +0800 Subject: [PATCH 080/145] fix: update e2e test engine images (#20223) Signed-off-by: Shengwen Yu --- tests/test-engine-image/Dockerfile.common | 12 ++++++------ tests/test-engine-image/Dockerfile.ui_test | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/test-engine-image/Dockerfile.common b/tests/test-engine-image/Dockerfile.common index 713ad234f..b5151ccf1 100644 --- a/tests/test-engine-image/Dockerfile.common +++ b/tests/test-engine-image/Dockerfile.common @@ -22,15 +22,15 @@ RUN apt-get update && apt-get install -y software-properties-common && \ RUN pwd && mkdir /tool/binary && \ # Install CONTAINERD - CONTAINERD_VERSION=1.7.8 && \ + CONTAINERD_VERSION=1.7.14 && \ wget https://github.com/containerd/containerd/releases/download/v$CONTAINERD_VERSION/containerd-$CONTAINERD_VERSION-linux-amd64.tar.gz && \ tar zxvf containerd-$CONTAINERD_VERSION-linux-amd64.tar.gz && \ cd bin && cp -f containerd ctr /tool/binary/ && \ # docker compose - curl -L "https://github.com/docker/compose/releases/download/v2.23.0/docker-compose-$(uname -s)-$(uname -m)" -o /tool/binary/docker-compose && \ + curl -L "https://github.com/docker/compose/releases/download/v2.26.1/docker-compose-$(uname -s)-$(uname -m)" -o /tool/binary/docker-compose && \ chmod +x /tool/binary/docker-compose && \ # Install helm - HELM_VERSION=3.13.1 && wget https://get.helm.sh/helm-v$HELM_VERSION-linux-amd64.tar.gz && \ + HELM_VERSION=3.14.3 && wget https://get.helm.sh/helm-v$HELM_VERSION-linux-amd64.tar.gz && \ tar zxvf helm-v$HELM_VERSION-linux-amd64.tar.gz && \ ls || pwd && \ mv linux-amd64/helm /tool/binary/helm && \ @@ -54,13 +54,13 @@ RUN pwd && mkdir /tool/binary && \ WASM_TO_OCI_VERSION=0.1.2 && wget https://github.com/engineerd/wasm-to-oci/releases/download/v${WASM_TO_OCI_VERSION}/linux-amd64-wasm-to-oci && \ chmod +x linux-amd64-wasm-to-oci && mv linux-amd64-wasm-to-oci /tool/binary/wasm-to-oci && \ # Install imgpkg - IMGPKG_VERSION=0.39.0 && wget https://github.com/vmware-tanzu/carvel-imgpkg/releases/download/v$IMGPKG_VERSION/imgpkg-linux-amd64 && \ + IMGPKG_VERSION=0.41.1 && wget https://github.com/vmware-tanzu/carvel-imgpkg/releases/download/v$IMGPKG_VERSION/imgpkg-linux-amd64 && \ mv imgpkg-linux-amd64 /tool/binary/imgpkg && chmod +x /tool/binary/imgpkg && \ # Install cosign - COSIGN_VERSION=2.2.0 && wget https://github.com/sigstore/cosign/releases/download/v$COSIGN_VERSION/cosign-linux-amd64 && \ + COSIGN_VERSION=2.2.3 && wget https://github.com/sigstore/cosign/releases/download/v$COSIGN_VERSION/cosign-linux-amd64 && \ mv cosign-linux-amd64 /tool/binary/cosign && chmod +x /tool/binary/cosign && \ # # Install notation - NOTATION_VERSION=1.0.0 && wget https://github.com/notaryproject/notation/releases/download/v$NOTATION_VERSION/notation_${NOTATION_VERSION}_linux_amd64.tar.gz && \ + NOTATION_VERSION=1.1.0 && wget https://github.com/notaryproject/notation/releases/download/v$NOTATION_VERSION/notation_${NOTATION_VERSION}_linux_amd64.tar.gz && \ tar zxvf notation_${NOTATION_VERSION}_linux_amd64.tar.gz && \ mv notation /tool/binary/notation && chmod +x /tool/binary/notation && \ pwd diff --git a/tests/test-engine-image/Dockerfile.ui_test b/tests/test-engine-image/Dockerfile.ui_test index 89f5535e0..9a391089e 100644 --- a/tests/test-engine-image/Dockerfile.ui_test +++ b/tests/test-engine-image/Dockerfile.ui_test @@ -41,8 +41,8 @@ RUN pip3 install --upgrade pip pyasn1 google-apitools==0.5.31 gsutil \ requests dbbot robotframework-seleniumlibrary robotframework-pabot \ robotframework-JSONLibrary hurry.filesize --upgrade && \ apt-get clean all -# Upgrade chromedriver version to 119.0.6045.105 -RUN wget -N https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/119.0.6045.105/linux64/chromedriver-linux64.zip && \ +# Upgrade chromedriver version to 123.0.6312.86 +RUN wget -N https://storage.googleapis.com/chrome-for-testing-public/123.0.6312.86/linux64/chromedriver-linux64.zip && \ unzip -j chromedriver-linux64.zip && \ chmod +x chromedriver && \ mv -f chromedriver /usr/local/share/chromedriver && \ @@ -51,7 +51,7 @@ RUN wget -N https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/119.0.60 RUN pwd && ls && \ # Install docker - DOCKER_VERSION=24.0.2 && wget https://download.docker.com/linux/static/stable/x86_64/docker-$DOCKER_VERSION.tgz && \ + DOCKER_VERSION=26.0.0 && wget https://download.docker.com/linux/static/stable/x86_64/docker-$DOCKER_VERSION.tgz && \ tar --strip-components=1 -xvzf docker-$DOCKER_VERSION.tgz -C /usr/bin && \ rm docker-$DOCKER_VERSION.tgz From 643e84cdfeedf4a0840efb25b080c9c950ef27a0 Mon Sep 17 00:00:00 2001 From: Shengwen YU Date: Thu, 11 Apr 2024 10:47:07 +0800 Subject: [PATCH 081/145] feat: expose `trivy.timeout` to configure the duration to wait for scan completion (#20257) Signed-off-by: Shengwen Yu --- make/harbor.yml.tmpl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/make/harbor.yml.tmpl b/make/harbor.yml.tmpl index 72c9dff44..22e421691 100644 --- a/make/harbor.yml.tmpl +++ b/make/harbor.yml.tmpl @@ -115,6 +115,11 @@ trivy: # # insecure The flag to skip verifying registry certificate insecure: false + # + # timeout The duration to wait for scan completion. + # There is upper bound of 30 minutes defined in scan job. So if this `timeout` is larger than 30m0s, it will also timeout at 30m0s. + timeout: 5m0s + # # github_token The GitHub access token to download Trivy DB # # Anonymous downloads from GitHub are subject to the limit of 60 requests per hour. Normally such rate limit is enough From e9d2f50669a2b4ae7022c4d20d079ba3e6cec957 Mon Sep 17 00:00:00 2001 From: MinerYang Date: Thu, 11 Apr 2024 11:37:59 +0800 Subject: [PATCH 082/145] update mockery to v2.42.2 (#20258) Signed-off-by: yminer --- Makefile | 2 +- .../flow/mock_adapter_factory_test.go | 10 ++- .../replication/flow/mock_adapter_test.go | 70 ++++++++++++++++++- .../replication/mock_flow_controller_test.go | 6 +- src/jobservice/mgt/mock_manager.go | 22 +++++- src/jobservice/period/mock_scheduler.go | 10 ++- src/lib/cache/mock_cache_test.go | 26 ++++++- src/pkg/scheduler/mock_dao_test.go | 30 +++++++- src/pkg/task/mock_execution_dao_test.go | 38 +++++++++- src/pkg/task/mock_jobservice_client_test.go | 22 +++++- src/pkg/task/mock_sweep_manager_test.go | 14 +++- src/pkg/task/mock_task_dao_test.go | 50 ++++++++++++- src/pkg/task/mock_task_manager_test.go | 50 ++++++++++++- src/testing/common/security/context.go | 26 ++++++- src/testing/controller/artifact/controller.go | 50 ++++++++++++- src/testing/controller/blob/controller.go | 70 ++++++++++++++++++- src/testing/controller/config/controller.go | 22 +++++- .../jobservice/scheduler_controller.go | 26 ++++++- src/testing/controller/project/controller.go | 38 +++++++++- .../controller/proxy/remote_interface.go | 18 ++++- src/testing/controller/purge/controller.go | 10 ++- src/testing/controller/quota/controller.go | 42 ++++++++++- .../controller/replication/controller.go | 62 +++++++++++++++- .../controller/repository/controller.go | 34 ++++++++- .../controller/retention/controller.go | 58 ++++++++++++++- src/testing/controller/robot/controller.go | 26 ++++++- src/testing/controller/scan/checker.go | 6 +- src/testing/controller/scan/controller.go | 38 +++++++++- .../controller/scandataexport/controller.go | 22 +++++- src/testing/controller/scanner/controller.go | 50 ++++++++++++- .../controller/securityhub/controller.go | 14 +++- .../controller/systemartifact/controller.go | 6 +- src/testing/controller/task/controller.go | 22 +++++- .../controller/task/execution_controller.go | 22 +++++- src/testing/controller/user/controller.go | 58 ++++++++++++++- src/testing/controller/webhook/controller.go | 58 ++++++++++++++- src/testing/lib/cache/cache.go | 26 ++++++- src/testing/lib/cache/iterator.go | 10 ++- src/testing/lib/config/manager.go | 34 ++++++++- src/testing/lib/orm/creator.go | 6 +- src/testing/pkg/accessory/dao/dao.go | 30 +++++++- src/testing/pkg/accessory/manager.go | 34 ++++++++- src/testing/pkg/accessory/model/accessory.go | 22 +++++- src/testing/pkg/allowlist/dao/dao.go | 10 ++- src/testing/pkg/allowlist/manager.go | 22 +++++- src/testing/pkg/artifact/manager.go | 42 ++++++++++- src/testing/pkg/audit/dao/dao.go | 30 +++++++- src/testing/pkg/audit/manager.go | 30 +++++++- src/testing/pkg/blob/manager.go | 62 +++++++++++++++- .../cached/manifest/redis/cached_manager.go | 34 ++++++++- src/testing/pkg/immutable/dao/dao.go | 30 +++++++- src/testing/pkg/joblog/dao/dao.go | 14 +++- src/testing/pkg/joblog/manager.go | 14 +++- .../jobmonitor/job_service_monitor_client.go | 14 +++- src/testing/pkg/jobmonitor/pool_manager.go | 6 +- src/testing/pkg/jobmonitor/queue_manager.go | 6 +- src/testing/pkg/jobmonitor/redis_client.go | 18 ++++- src/testing/pkg/jobmonitor/worker_manager.go | 6 +- src/testing/pkg/label/dao/dao.go | 42 ++++++++++- src/testing/pkg/label/manager.go | 46 +++++++++++- src/testing/pkg/ldap/manager.go | 18 ++++- src/testing/pkg/member/fake_member_manager.go | 42 ++++++++++- .../pkg/notification/policy/dao/dao.go | 26 ++++++- .../pkg/notification/policy/manager.go | 30 +++++++- src/testing/pkg/oidc/dao/meta_dao.go | 22 +++++- src/testing/pkg/oidc/meta_manager.go | 26 ++++++- src/testing/pkg/project/manager.go | 30 +++++++- src/testing/pkg/project/metadata/manager.go | 22 +++++- src/testing/pkg/queuestatus/manager.go | 22 +++++- src/testing/pkg/quota/driver/driver.go | 22 +++++- src/testing/pkg/quota/manager.go | 30 +++++++- src/testing/pkg/rbac/dao/dao.go | 34 ++++++++- src/testing/pkg/rbac/manager.go | 34 ++++++++- src/testing/pkg/reg/adapter/adapter.go | 14 +++- src/testing/pkg/reg/dao/dao.go | 26 ++++++- src/testing/pkg/reg/manager.go | 38 +++++++++- .../pkg/registry/fake_registry_client.go | 66 ++++++++++++++++- src/testing/pkg/replication/dao/dao.go | 26 ++++++- src/testing/pkg/replication/manager.go | 26 ++++++- src/testing/pkg/repository/dao/dao.go | 34 ++++++++- src/testing/pkg/repository/manager.go | 38 +++++++++- src/testing/pkg/robot/dao/dao.go | 30 +++++++- src/testing/pkg/robot/manager.go | 30 +++++++- .../scan/export/artifact_digest_calculator.go | 6 +- .../pkg/scan/export/filter_processor.go | 14 +++- src/testing/pkg/scan/export/manager.go | 6 +- src/testing/pkg/scan/report/manager.go | 30 +++++++- src/testing/pkg/scan/rest/v1/client.go | 14 +++- src/testing/pkg/scan/rest/v1/client_pool.go | 6 +- .../pkg/scan/rest/v1/request_resolver.go | 2 +- .../pkg/scan/rest/v1/response_handler.go | 6 +- src/testing/pkg/scan/scanner/manager.go | 38 +++++++++- src/testing/pkg/scheduler/scheduler.go | 26 ++++++- src/testing/pkg/securityhub/manager.go | 30 +++++++- .../pkg/systemartifact/cleanup/selector.go | 10 ++- src/testing/pkg/systemartifact/dao/dao.go | 22 +++++- src/testing/pkg/systemartifact/manager.go | 34 ++++++++- src/testing/pkg/task/execution_manager.go | 50 ++++++++++++- src/testing/pkg/task/manager.go | 50 ++++++++++++- src/testing/pkg/user/dao/dao.go | 22 +++++- src/testing/pkg/user/manager.go | 54 +++++++++++++- .../pkg/usergroup/fake_usergroup_manager.go | 34 ++++++++- 102 files changed, 2754 insertions(+), 102 deletions(-) diff --git a/Makefile b/Makefile index 2c10331f0..16a532a52 100644 --- a/Makefile +++ b/Makefile @@ -312,7 +312,7 @@ gen_apis: lint_apis MOCKERY_IMAGENAME=$(IMAGENAMESPACE)/mockery -MOCKERY_VERSION=v2.35.4 +MOCKERY_VERSION=v2.42.2 MOCKERY=$(RUNCONTAINER) ${MOCKERY_IMAGENAME}:${MOCKERY_VERSION} MOCKERY_IMAGE_BUILD_CMD=${DOCKERBUILD} -f ${TOOLSPATH}/mockery/Dockerfile --build-arg GOLANG=${GOBUILDIMAGE} --build-arg MOCKERY_VERSION=${MOCKERY_VERSION} -t ${MOCKERY_IMAGENAME}:$(MOCKERY_VERSION) . diff --git a/src/controller/replication/flow/mock_adapter_factory_test.go b/src/controller/replication/flow/mock_adapter_factory_test.go index e1d069b07..e11e8baca 100644 --- a/src/controller/replication/flow/mock_adapter_factory_test.go +++ b/src/controller/replication/flow/mock_adapter_factory_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package flow @@ -18,6 +18,10 @@ type mockFactory struct { func (_m *mockFactory) AdapterPattern() *model.AdapterPattern { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for AdapterPattern") + } + var r0 *model.AdapterPattern if rf, ok := ret.Get(0).(func() *model.AdapterPattern); ok { r0 = rf() @@ -34,6 +38,10 @@ func (_m *mockFactory) AdapterPattern() *model.AdapterPattern { func (_m *mockFactory) Create(_a0 *model.Registry) (adapter.Adapter, error) { ret := _m.Called(_a0) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 adapter.Adapter var r1 error if rf, ok := ret.Get(0).(func(*model.Registry) (adapter.Adapter, error)); ok { diff --git a/src/controller/replication/flow/mock_adapter_test.go b/src/controller/replication/flow/mock_adapter_test.go index f5fd79158..331b98e2f 100644 --- a/src/controller/replication/flow/mock_adapter_test.go +++ b/src/controller/replication/flow/mock_adapter_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package flow @@ -21,6 +21,10 @@ type mockAdapter struct { func (_m *mockAdapter) BlobExist(repository string, digest string) (bool, error) { ret := _m.Called(repository, digest) + if len(ret) == 0 { + panic("no return value specified for BlobExist") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(string, string) (bool, error)); ok { @@ -45,6 +49,10 @@ func (_m *mockAdapter) BlobExist(repository string, digest string) (bool, error) func (_m *mockAdapter) CanBeMount(digest string) (bool, string, error) { ret := _m.Called(digest) + if len(ret) == 0 { + panic("no return value specified for CanBeMount") + } + var r0 bool var r1 string var r2 error @@ -76,6 +84,10 @@ func (_m *mockAdapter) CanBeMount(digest string) (bool, string, error) { func (_m *mockAdapter) DeleteManifest(repository string, reference string) error { ret := _m.Called(repository, reference) + if len(ret) == 0 { + panic("no return value specified for DeleteManifest") + } + var r0 error if rf, ok := ret.Get(0).(func(string, string) error); ok { r0 = rf(repository, reference) @@ -90,6 +102,10 @@ func (_m *mockAdapter) DeleteManifest(repository string, reference string) error func (_m *mockAdapter) DeleteTag(repository string, tag string) error { ret := _m.Called(repository, tag) + if len(ret) == 0 { + panic("no return value specified for DeleteTag") + } + var r0 error if rf, ok := ret.Get(0).(func(string, string) error); ok { r0 = rf(repository, tag) @@ -104,6 +120,10 @@ func (_m *mockAdapter) DeleteTag(repository string, tag string) error { func (_m *mockAdapter) FetchArtifacts(filters []*model.Filter) ([]*model.Resource, error) { ret := _m.Called(filters) + if len(ret) == 0 { + panic("no return value specified for FetchArtifacts") + } + var r0 []*model.Resource var r1 error if rf, ok := ret.Get(0).(func([]*model.Filter) ([]*model.Resource, error)); ok { @@ -130,6 +150,10 @@ func (_m *mockAdapter) FetchArtifacts(filters []*model.Filter) ([]*model.Resourc func (_m *mockAdapter) HealthCheck() (string, error) { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for HealthCheck") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func() (string, error)); ok { @@ -154,6 +178,10 @@ func (_m *mockAdapter) HealthCheck() (string, error) { func (_m *mockAdapter) Info() (*model.RegistryInfo, error) { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Info") + } + var r0 *model.RegistryInfo var r1 error if rf, ok := ret.Get(0).(func() (*model.RegistryInfo, error)); ok { @@ -180,6 +208,10 @@ func (_m *mockAdapter) Info() (*model.RegistryInfo, error) { func (_m *mockAdapter) ListTags(repository string) ([]string, error) { ret := _m.Called(repository) + if len(ret) == 0 { + panic("no return value specified for ListTags") + } + var r0 []string var r1 error if rf, ok := ret.Get(0).(func(string) ([]string, error)); ok { @@ -206,6 +238,10 @@ func (_m *mockAdapter) ListTags(repository string) ([]string, error) { func (_m *mockAdapter) ManifestExist(repository string, reference string) (bool, *distribution.Descriptor, error) { ret := _m.Called(repository, reference) + if len(ret) == 0 { + panic("no return value specified for ManifestExist") + } + var r0 bool var r1 *distribution.Descriptor var r2 error @@ -239,6 +275,10 @@ func (_m *mockAdapter) ManifestExist(repository string, reference string) (bool, func (_m *mockAdapter) MountBlob(srcRepository string, digest string, dstRepository string) error { ret := _m.Called(srcRepository, digest, dstRepository) + if len(ret) == 0 { + panic("no return value specified for MountBlob") + } + var r0 error if rf, ok := ret.Get(0).(func(string, string, string) error); ok { r0 = rf(srcRepository, digest, dstRepository) @@ -253,6 +293,10 @@ func (_m *mockAdapter) MountBlob(srcRepository string, digest string, dstReposit func (_m *mockAdapter) PrepareForPush(_a0 []*model.Resource) error { ret := _m.Called(_a0) + if len(ret) == 0 { + panic("no return value specified for PrepareForPush") + } + var r0 error if rf, ok := ret.Get(0).(func([]*model.Resource) error); ok { r0 = rf(_a0) @@ -267,6 +311,10 @@ func (_m *mockAdapter) PrepareForPush(_a0 []*model.Resource) error { func (_m *mockAdapter) PullBlob(repository string, digest string) (int64, io.ReadCloser, error) { ret := _m.Called(repository, digest) + if len(ret) == 0 { + panic("no return value specified for PullBlob") + } + var r0 int64 var r1 io.ReadCloser var r2 error @@ -300,6 +348,10 @@ func (_m *mockAdapter) PullBlob(repository string, digest string) (int64, io.Rea func (_m *mockAdapter) PullBlobChunk(repository string, digest string, blobSize int64, start int64, end int64) (int64, io.ReadCloser, error) { ret := _m.Called(repository, digest, blobSize, start, end) + if len(ret) == 0 { + panic("no return value specified for PullBlobChunk") + } + var r0 int64 var r1 io.ReadCloser var r2 error @@ -340,6 +392,10 @@ func (_m *mockAdapter) PullManifest(repository string, reference string, acceptt _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for PullManifest") + } + var r0 distribution.Manifest var r1 string var r2 error @@ -373,6 +429,10 @@ func (_m *mockAdapter) PullManifest(repository string, reference string, acceptt func (_m *mockAdapter) PushBlob(repository string, digest string, size int64, blob io.Reader) error { ret := _m.Called(repository, digest, size, blob) + if len(ret) == 0 { + panic("no return value specified for PushBlob") + } + var r0 error if rf, ok := ret.Get(0).(func(string, string, int64, io.Reader) error); ok { r0 = rf(repository, digest, size, blob) @@ -387,6 +447,10 @@ func (_m *mockAdapter) PushBlob(repository string, digest string, size int64, bl func (_m *mockAdapter) PushBlobChunk(repository string, digest string, size int64, chunk io.Reader, start int64, end int64, location string) (string, int64, error) { ret := _m.Called(repository, digest, size, chunk, start, end, location) + if len(ret) == 0 { + panic("no return value specified for PushBlobChunk") + } + var r0 string var r1 int64 var r2 error @@ -418,6 +482,10 @@ func (_m *mockAdapter) PushBlobChunk(repository string, digest string, size int6 func (_m *mockAdapter) PushManifest(repository string, reference string, mediaType string, payload []byte) (string, error) { ret := _m.Called(repository, reference, mediaType, payload) + if len(ret) == 0 { + panic("no return value specified for PushManifest") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(string, string, string, []byte) (string, error)); ok { diff --git a/src/controller/replication/mock_flow_controller_test.go b/src/controller/replication/mock_flow_controller_test.go index cd0728a5c..a3d6ec854 100644 --- a/src/controller/replication/mock_flow_controller_test.go +++ b/src/controller/replication/mock_flow_controller_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package replication @@ -21,6 +21,10 @@ type flowController struct { func (_m *flowController) Start(ctx context.Context, executionID int64, policy *model.Policy, resource *regmodel.Resource) error { ret := _m.Called(ctx, executionID, policy, resource) + if len(ret) == 0 { + panic("no return value specified for Start") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, *model.Policy, *regmodel.Resource) error); ok { r0 = rf(ctx, executionID, policy, resource) diff --git a/src/jobservice/mgt/mock_manager.go b/src/jobservice/mgt/mock_manager.go index 12a12231e..8f1ccd3a2 100644 --- a/src/jobservice/mgt/mock_manager.go +++ b/src/jobservice/mgt/mock_manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package mgt @@ -18,6 +18,10 @@ type MockManager struct { func (_m *MockManager) GetJob(jobID string) (*job.Stats, error) { ret := _m.Called(jobID) + if len(ret) == 0 { + panic("no return value specified for GetJob") + } + var r0 *job.Stats var r1 error if rf, ok := ret.Get(0).(func(string) (*job.Stats, error)); ok { @@ -44,6 +48,10 @@ func (_m *MockManager) GetJob(jobID string) (*job.Stats, error) { func (_m *MockManager) GetJobs(q *query.Parameter) ([]*job.Stats, int64, error) { ret := _m.Called(q) + if len(ret) == 0 { + panic("no return value specified for GetJobs") + } + var r0 []*job.Stats var r1 int64 var r2 error @@ -77,6 +85,10 @@ func (_m *MockManager) GetJobs(q *query.Parameter) ([]*job.Stats, int64, error) func (_m *MockManager) GetPeriodicExecution(pID string, q *query.Parameter) ([]*job.Stats, int64, error) { ret := _m.Called(pID, q) + if len(ret) == 0 { + panic("no return value specified for GetPeriodicExecution") + } + var r0 []*job.Stats var r1 int64 var r2 error @@ -110,6 +122,10 @@ func (_m *MockManager) GetPeriodicExecution(pID string, q *query.Parameter) ([]* func (_m *MockManager) GetScheduledJobs(q *query.Parameter) ([]*job.Stats, int64, error) { ret := _m.Called(q) + if len(ret) == 0 { + panic("no return value specified for GetScheduledJobs") + } + var r0 []*job.Stats var r1 int64 var r2 error @@ -143,6 +159,10 @@ func (_m *MockManager) GetScheduledJobs(q *query.Parameter) ([]*job.Stats, int64 func (_m *MockManager) SaveJob(_a0 *job.Stats) error { ret := _m.Called(_a0) + if len(ret) == 0 { + panic("no return value specified for SaveJob") + } + var r0 error if rf, ok := ret.Get(0).(func(*job.Stats) error); ok { r0 = rf(_a0) diff --git a/src/jobservice/period/mock_scheduler.go b/src/jobservice/period/mock_scheduler.go index 9a8bb611f..abbf494b7 100644 --- a/src/jobservice/period/mock_scheduler.go +++ b/src/jobservice/period/mock_scheduler.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package period @@ -13,6 +13,10 @@ type MockScheduler struct { func (_m *MockScheduler) Schedule(policy *Policy) (int64, error) { ret := _m.Called(policy) + if len(ret) == 0 { + panic("no return value specified for Schedule") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(*Policy) (int64, error)); ok { @@ -42,6 +46,10 @@ func (_m *MockScheduler) Start() { func (_m *MockScheduler) UnSchedule(policyID string) error { ret := _m.Called(policyID) + if len(ret) == 0 { + panic("no return value specified for UnSchedule") + } + var r0 error if rf, ok := ret.Get(0).(func(string) error); ok { r0 = rf(policyID) diff --git a/src/lib/cache/mock_cache_test.go b/src/lib/cache/mock_cache_test.go index 02616848a..62af8f2bb 100644 --- a/src/lib/cache/mock_cache_test.go +++ b/src/lib/cache/mock_cache_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package cache @@ -18,6 +18,10 @@ type mockCache struct { func (_m *mockCache) Contains(ctx context.Context, key string) bool { ret := _m.Called(ctx, key) + if len(ret) == 0 { + panic("no return value specified for Contains") + } + var r0 bool if rf, ok := ret.Get(0).(func(context.Context, string) bool); ok { r0 = rf(ctx, key) @@ -32,6 +36,10 @@ func (_m *mockCache) Contains(ctx context.Context, key string) bool { func (_m *mockCache) Delete(ctx context.Context, key string) error { ret := _m.Called(ctx, key) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, key) @@ -46,6 +54,10 @@ func (_m *mockCache) Delete(ctx context.Context, key string) error { func (_m *mockCache) Fetch(ctx context.Context, key string, value interface{}) error { ret := _m.Called(ctx, key, value) + if len(ret) == 0 { + panic("no return value specified for Fetch") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, interface{}) error); ok { r0 = rf(ctx, key, value) @@ -60,6 +72,10 @@ func (_m *mockCache) Fetch(ctx context.Context, key string, value interface{}) e func (_m *mockCache) Ping(ctx context.Context) error { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for Ping") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context) error); ok { r0 = rf(ctx) @@ -81,6 +97,10 @@ func (_m *mockCache) Save(ctx context.Context, key string, value interface{}, ex _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Save") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, interface{}, ...time.Duration) error); ok { r0 = rf(ctx, key, value, expiration...) @@ -95,6 +115,10 @@ func (_m *mockCache) Save(ctx context.Context, key string, value interface{}, ex func (_m *mockCache) Scan(ctx context.Context, match string) (Iterator, error) { ret := _m.Called(ctx, match) + if len(ret) == 0 { + panic("no return value specified for Scan") + } + var r0 Iterator var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (Iterator, error)); ok { diff --git a/src/pkg/scheduler/mock_dao_test.go b/src/pkg/scheduler/mock_dao_test.go index 69783d30b..8fc6ea3c5 100644 --- a/src/pkg/scheduler/mock_dao_test.go +++ b/src/pkg/scheduler/mock_dao_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package scheduler @@ -18,6 +18,10 @@ type mockDAO struct { func (_m *mockDAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -42,6 +46,10 @@ func (_m *mockDAO) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *mockDAO) Create(ctx context.Context, s *schedule) (int64, error) { ret := _m.Called(ctx, s) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *schedule) (int64, error)); ok { @@ -66,6 +74,10 @@ func (_m *mockDAO) Create(ctx context.Context, s *schedule) (int64, error) { func (_m *mockDAO) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -80,6 +92,10 @@ func (_m *mockDAO) Delete(ctx context.Context, id int64) error { func (_m *mockDAO) Get(ctx context.Context, id int64) (*schedule, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *schedule var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*schedule, error)); ok { @@ -106,6 +122,10 @@ func (_m *mockDAO) Get(ctx context.Context, id int64) (*schedule, error) { func (_m *mockDAO) List(ctx context.Context, query *q.Query) ([]*schedule, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*schedule var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*schedule, error)); ok { @@ -139,6 +159,10 @@ func (_m *mockDAO) Update(ctx context.Context, s *schedule, props ...string) err _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *schedule, ...string) error); ok { r0 = rf(ctx, s, props...) @@ -153,6 +177,10 @@ func (_m *mockDAO) Update(ctx context.Context, s *schedule, props ...string) err func (_m *mockDAO) UpdateRevision(ctx context.Context, id int64, revision int64) (int64, error) { ret := _m.Called(ctx, id, revision) + if len(ret) == 0 { + panic("no return value specified for UpdateRevision") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, int64) (int64, error)); ok { diff --git a/src/pkg/task/mock_execution_dao_test.go b/src/pkg/task/mock_execution_dao_test.go index 0f8051169..f26a035a6 100644 --- a/src/pkg/task/mock_execution_dao_test.go +++ b/src/pkg/task/mock_execution_dao_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package task @@ -20,6 +20,10 @@ type mockExecutionDAO struct { func (_m *mockExecutionDAO) AsyncRefreshStatus(ctx context.Context, id int64, vendor string) error { ret := _m.Called(ctx, id, vendor) + if len(ret) == 0 { + panic("no return value specified for AsyncRefreshStatus") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, string) error); ok { r0 = rf(ctx, id, vendor) @@ -34,6 +38,10 @@ func (_m *mockExecutionDAO) AsyncRefreshStatus(ctx context.Context, id int64, ve func (_m *mockExecutionDAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -58,6 +66,10 @@ func (_m *mockExecutionDAO) Count(ctx context.Context, query *q.Query) (int64, e func (_m *mockExecutionDAO) Create(ctx context.Context, execution *dao.Execution) (int64, error) { ret := _m.Called(ctx, execution) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *dao.Execution) (int64, error)); ok { @@ -82,6 +94,10 @@ func (_m *mockExecutionDAO) Create(ctx context.Context, execution *dao.Execution func (_m *mockExecutionDAO) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -96,6 +112,10 @@ func (_m *mockExecutionDAO) Delete(ctx context.Context, id int64) error { func (_m *mockExecutionDAO) Get(ctx context.Context, id int64) (*dao.Execution, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *dao.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*dao.Execution, error)); ok { @@ -122,6 +142,10 @@ func (_m *mockExecutionDAO) Get(ctx context.Context, id int64) (*dao.Execution, func (_m *mockExecutionDAO) GetMetrics(ctx context.Context, id int64) (*dao.Metrics, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for GetMetrics") + } + var r0 *dao.Metrics var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*dao.Metrics, error)); ok { @@ -148,6 +172,10 @@ func (_m *mockExecutionDAO) GetMetrics(ctx context.Context, id int64) (*dao.Metr func (_m *mockExecutionDAO) List(ctx context.Context, query *q.Query) ([]*dao.Execution, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*dao.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*dao.Execution, error)); ok { @@ -174,6 +202,10 @@ func (_m *mockExecutionDAO) List(ctx context.Context, query *q.Query) ([]*dao.Ex func (_m *mockExecutionDAO) RefreshStatus(ctx context.Context, id int64) (bool, string, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for RefreshStatus") + } + var r0 bool var r1 string var r2 error @@ -212,6 +244,10 @@ func (_m *mockExecutionDAO) Update(ctx context.Context, execution *dao.Execution _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *dao.Execution, ...string) error); ok { r0 = rf(ctx, execution, props...) diff --git a/src/pkg/task/mock_jobservice_client_test.go b/src/pkg/task/mock_jobservice_client_test.go index 4aee354ea..0d9ecfeca 100644 --- a/src/pkg/task/mock_jobservice_client_test.go +++ b/src/pkg/task/mock_jobservice_client_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package task @@ -18,6 +18,10 @@ type mockJobserviceClient struct { func (_m *mockJobserviceClient) GetExecutions(uuid string) ([]job.Stats, error) { ret := _m.Called(uuid) + if len(ret) == 0 { + panic("no return value specified for GetExecutions") + } + var r0 []job.Stats var r1 error if rf, ok := ret.Get(0).(func(string) ([]job.Stats, error)); ok { @@ -44,6 +48,10 @@ func (_m *mockJobserviceClient) GetExecutions(uuid string) ([]job.Stats, error) func (_m *mockJobserviceClient) GetJobLog(uuid string) ([]byte, error) { ret := _m.Called(uuid) + if len(ret) == 0 { + panic("no return value specified for GetJobLog") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(string) ([]byte, error)); ok { @@ -70,6 +78,10 @@ func (_m *mockJobserviceClient) GetJobLog(uuid string) ([]byte, error) { func (_m *mockJobserviceClient) GetJobServiceConfig() (*job.Config, error) { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for GetJobServiceConfig") + } + var r0 *job.Config var r1 error if rf, ok := ret.Get(0).(func() (*job.Config, error)); ok { @@ -96,6 +108,10 @@ func (_m *mockJobserviceClient) GetJobServiceConfig() (*job.Config, error) { func (_m *mockJobserviceClient) PostAction(uuid string, action string) error { ret := _m.Called(uuid, action) + if len(ret) == 0 { + panic("no return value specified for PostAction") + } + var r0 error if rf, ok := ret.Get(0).(func(string, string) error); ok { r0 = rf(uuid, action) @@ -110,6 +126,10 @@ func (_m *mockJobserviceClient) PostAction(uuid string, action string) error { func (_m *mockJobserviceClient) SubmitJob(_a0 *models.JobData) (string, error) { ret := _m.Called(_a0) + if len(ret) == 0 { + panic("no return value specified for SubmitJob") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(*models.JobData) (string, error)); ok { diff --git a/src/pkg/task/mock_sweep_manager_test.go b/src/pkg/task/mock_sweep_manager_test.go index 4ca1a88d1..735c34304 100644 --- a/src/pkg/task/mock_sweep_manager_test.go +++ b/src/pkg/task/mock_sweep_manager_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package task @@ -17,6 +17,10 @@ type mockSweepManager struct { func (_m *mockSweepManager) Clean(ctx context.Context, execID []int64) error { ret := _m.Called(ctx, execID) + if len(ret) == 0 { + panic("no return value specified for Clean") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, []int64) error); ok { r0 = rf(ctx, execID) @@ -31,6 +35,10 @@ func (_m *mockSweepManager) Clean(ctx context.Context, execID []int64) error { func (_m *mockSweepManager) FixDanglingStateExecution(ctx context.Context) error { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for FixDanglingStateExecution") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context) error); ok { r0 = rf(ctx) @@ -45,6 +53,10 @@ func (_m *mockSweepManager) FixDanglingStateExecution(ctx context.Context) error func (_m *mockSweepManager) ListCandidates(ctx context.Context, vendorType string, retainCnt int64) ([]int64, error) { ret := _m.Called(ctx, vendorType, retainCnt) + if len(ret) == 0 { + panic("no return value specified for ListCandidates") + } + var r0 []int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64) ([]int64, error)); ok { diff --git a/src/pkg/task/mock_task_dao_test.go b/src/pkg/task/mock_task_dao_test.go index 49ae17e9f..357353bfa 100644 --- a/src/pkg/task/mock_task_dao_test.go +++ b/src/pkg/task/mock_task_dao_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package task @@ -22,6 +22,10 @@ type mockTaskDAO struct { func (_m *mockTaskDAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -46,6 +50,10 @@ func (_m *mockTaskDAO) Count(ctx context.Context, query *q.Query) (int64, error) func (_m *mockTaskDAO) Create(ctx context.Context, _a1 *dao.Task) (int64, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *dao.Task) (int64, error)); ok { @@ -70,6 +78,10 @@ func (_m *mockTaskDAO) Create(ctx context.Context, _a1 *dao.Task) (int64, error) func (_m *mockTaskDAO) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -84,6 +96,10 @@ func (_m *mockTaskDAO) Delete(ctx context.Context, id int64) error { func (_m *mockTaskDAO) ExecutionIDsByVendorAndStatus(ctx context.Context, vendorType string, status string) ([]int64, error) { ret := _m.Called(ctx, vendorType, status) + if len(ret) == 0 { + panic("no return value specified for ExecutionIDsByVendorAndStatus") + } + var r0 []int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]int64, error)); ok { @@ -110,6 +126,10 @@ func (_m *mockTaskDAO) ExecutionIDsByVendorAndStatus(ctx context.Context, vendor func (_m *mockTaskDAO) Get(ctx context.Context, id int64) (*dao.Task, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *dao.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*dao.Task, error)); ok { @@ -136,6 +156,10 @@ func (_m *mockTaskDAO) Get(ctx context.Context, id int64) (*dao.Task, error) { func (_m *mockTaskDAO) GetMaxEndTime(ctx context.Context, executionID int64) (time.Time, error) { ret := _m.Called(ctx, executionID) + if len(ret) == 0 { + panic("no return value specified for GetMaxEndTime") + } + var r0 time.Time var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (time.Time, error)); ok { @@ -160,6 +184,10 @@ func (_m *mockTaskDAO) GetMaxEndTime(ctx context.Context, executionID int64) (ti func (_m *mockTaskDAO) List(ctx context.Context, query *q.Query) ([]*dao.Task, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*dao.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*dao.Task, error)); ok { @@ -186,6 +214,10 @@ func (_m *mockTaskDAO) List(ctx context.Context, query *q.Query) ([]*dao.Task, e func (_m *mockTaskDAO) ListScanTasksByReportUUID(ctx context.Context, uuid string) ([]*dao.Task, error) { ret := _m.Called(ctx, uuid) + if len(ret) == 0 { + panic("no return value specified for ListScanTasksByReportUUID") + } + var r0 []*dao.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]*dao.Task, error)); ok { @@ -212,6 +244,10 @@ func (_m *mockTaskDAO) ListScanTasksByReportUUID(ctx context.Context, uuid strin func (_m *mockTaskDAO) ListStatusCount(ctx context.Context, executionID int64) ([]*dao.StatusCount, error) { ret := _m.Called(ctx, executionID) + if len(ret) == 0 { + panic("no return value specified for ListStatusCount") + } + var r0 []*dao.StatusCount var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) ([]*dao.StatusCount, error)); ok { @@ -245,6 +281,10 @@ func (_m *mockTaskDAO) Update(ctx context.Context, _a1 *dao.Task, props ...strin _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *dao.Task, ...string) error); ok { r0 = rf(ctx, _a1, props...) @@ -259,6 +299,10 @@ func (_m *mockTaskDAO) Update(ctx context.Context, _a1 *dao.Task, props ...strin func (_m *mockTaskDAO) UpdateStatus(ctx context.Context, id int64, status string, statusRevision int64) error { ret := _m.Called(ctx, id, status, statusRevision) + if len(ret) == 0 { + panic("no return value specified for UpdateStatus") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, string, int64) error); ok { r0 = rf(ctx, id, status, statusRevision) @@ -273,6 +317,10 @@ func (_m *mockTaskDAO) UpdateStatus(ctx context.Context, id int64, status string func (_m *mockTaskDAO) UpdateStatusInBatch(ctx context.Context, jobIDs []string, status string, batchSize int) error { ret := _m.Called(ctx, jobIDs, status, batchSize) + if len(ret) == 0 { + panic("no return value specified for UpdateStatusInBatch") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, []string, string, int) error); ok { r0 = rf(ctx, jobIDs, status, batchSize) diff --git a/src/pkg/task/mock_task_manager_test.go b/src/pkg/task/mock_task_manager_test.go index 6e740ab0f..bca9c411a 100644 --- a/src/pkg/task/mock_task_manager_test.go +++ b/src/pkg/task/mock_task_manager_test.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package task @@ -18,6 +18,10 @@ type mockTaskManager struct { func (_m *mockTaskManager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -49,6 +53,10 @@ func (_m *mockTaskManager) Create(ctx context.Context, executionID int64, job *J _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, *Job, ...map[string]interface{}) (int64, error)); ok { @@ -73,6 +81,10 @@ func (_m *mockTaskManager) Create(ctx context.Context, executionID int64, job *J func (_m *mockTaskManager) ExecutionIDsByVendorAndStatus(ctx context.Context, vendorType string, status string) ([]int64, error) { ret := _m.Called(ctx, vendorType, status) + if len(ret) == 0 { + panic("no return value specified for ExecutionIDsByVendorAndStatus") + } + var r0 []int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]int64, error)); ok { @@ -99,6 +111,10 @@ func (_m *mockTaskManager) ExecutionIDsByVendorAndStatus(ctx context.Context, ve func (_m *mockTaskManager) Get(ctx context.Context, id int64) (*Task, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*Task, error)); ok { @@ -125,6 +141,10 @@ func (_m *mockTaskManager) Get(ctx context.Context, id int64) (*Task, error) { func (_m *mockTaskManager) GetLog(ctx context.Context, id int64) ([]byte, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for GetLog") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) ([]byte, error)); ok { @@ -151,6 +171,10 @@ func (_m *mockTaskManager) GetLog(ctx context.Context, id int64) ([]byte, error) func (_m *mockTaskManager) GetLogByJobID(ctx context.Context, jobID string) ([]byte, error) { ret := _m.Called(ctx, jobID) + if len(ret) == 0 { + panic("no return value specified for GetLogByJobID") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]byte, error)); ok { @@ -177,6 +201,10 @@ func (_m *mockTaskManager) GetLogByJobID(ctx context.Context, jobID string) ([]b func (_m *mockTaskManager) List(ctx context.Context, query *q.Query) ([]*Task, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*Task, error)); ok { @@ -203,6 +231,10 @@ func (_m *mockTaskManager) List(ctx context.Context, query *q.Query) ([]*Task, e func (_m *mockTaskManager) ListScanTasksByReportUUID(ctx context.Context, uuid string) ([]*Task, error) { ret := _m.Called(ctx, uuid) + if len(ret) == 0 { + panic("no return value specified for ListScanTasksByReportUUID") + } + var r0 []*Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]*Task, error)); ok { @@ -229,6 +261,10 @@ func (_m *mockTaskManager) ListScanTasksByReportUUID(ctx context.Context, uuid s func (_m *mockTaskManager) Stop(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Stop") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -250,6 +286,10 @@ func (_m *mockTaskManager) Update(ctx context.Context, task *Task, props ...stri _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *Task, ...string) error); ok { r0 = rf(ctx, task, props...) @@ -264,6 +304,10 @@ func (_m *mockTaskManager) Update(ctx context.Context, task *Task, props ...stri func (_m *mockTaskManager) UpdateExtraAttrs(ctx context.Context, id int64, extraAttrs map[string]interface{}) error { ret := _m.Called(ctx, id, extraAttrs) + if len(ret) == 0 { + panic("no return value specified for UpdateExtraAttrs") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, map[string]interface{}) error); ok { r0 = rf(ctx, id, extraAttrs) @@ -278,6 +322,10 @@ func (_m *mockTaskManager) UpdateExtraAttrs(ctx context.Context, id int64, extra func (_m *mockTaskManager) UpdateStatusInBatch(ctx context.Context, jobIDs []string, status string, batchSize int) error { ret := _m.Called(ctx, jobIDs, status, batchSize) + if len(ret) == 0 { + panic("no return value specified for UpdateStatusInBatch") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, []string, string, int) error); ok { r0 = rf(ctx, jobIDs, status, batchSize) diff --git a/src/testing/common/security/context.go b/src/testing/common/security/context.go index 59bc1dfe0..9f6c80f6b 100644 --- a/src/testing/common/security/context.go +++ b/src/testing/common/security/context.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package security @@ -19,6 +19,10 @@ type Context struct { func (_m *Context) Can(ctx context.Context, action types.Action, resource types.Resource) bool { ret := _m.Called(ctx, action, resource) + if len(ret) == 0 { + panic("no return value specified for Can") + } + var r0 bool if rf, ok := ret.Get(0).(func(context.Context, types.Action, types.Resource) bool); ok { r0 = rf(ctx, action, resource) @@ -33,6 +37,10 @@ func (_m *Context) Can(ctx context.Context, action types.Action, resource types. func (_m *Context) GetUsername() string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for GetUsername") + } + var r0 string if rf, ok := ret.Get(0).(func() string); ok { r0 = rf() @@ -47,6 +55,10 @@ func (_m *Context) GetUsername() string { func (_m *Context) IsAuthenticated() bool { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for IsAuthenticated") + } + var r0 bool if rf, ok := ret.Get(0).(func() bool); ok { r0 = rf() @@ -61,6 +73,10 @@ func (_m *Context) IsAuthenticated() bool { func (_m *Context) IsSolutionUser() bool { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for IsSolutionUser") + } + var r0 bool if rf, ok := ret.Get(0).(func() bool); ok { r0 = rf() @@ -75,6 +91,10 @@ func (_m *Context) IsSolutionUser() bool { func (_m *Context) IsSysAdmin() bool { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for IsSysAdmin") + } + var r0 bool if rf, ok := ret.Get(0).(func() bool); ok { r0 = rf() @@ -89,6 +109,10 @@ func (_m *Context) IsSysAdmin() bool { func (_m *Context) Name() string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Name") + } + var r0 string if rf, ok := ret.Get(0).(func() string); ok { r0 = rf() diff --git a/src/testing/controller/artifact/controller.go b/src/testing/controller/artifact/controller.go index 96e23c6ea..7af5d556f 100644 --- a/src/testing/controller/artifact/controller.go +++ b/src/testing/controller/artifact/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package artifact @@ -25,6 +25,10 @@ type Controller struct { func (_m *Controller) AddLabel(ctx context.Context, artifactID int64, labelID int64) error { ret := _m.Called(ctx, artifactID, labelID) + if len(ret) == 0 { + panic("no return value specified for AddLabel") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok { r0 = rf(ctx, artifactID, labelID) @@ -39,6 +43,10 @@ func (_m *Controller) AddLabel(ctx context.Context, artifactID int64, labelID in func (_m *Controller) Copy(ctx context.Context, srcRepo string, reference string, dstRepo string) (int64, error) { ret := _m.Called(ctx, srcRepo, reference, dstRepo) + if len(ret) == 0 { + panic("no return value specified for Copy") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string) (int64, error)); ok { @@ -63,6 +71,10 @@ func (_m *Controller) Copy(ctx context.Context, srcRepo string, reference string func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -87,6 +99,10 @@ func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) func (_m *Controller) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -101,6 +117,10 @@ func (_m *Controller) Delete(ctx context.Context, id int64) error { func (_m *Controller) Ensure(ctx context.Context, repository string, digest string, option *artifact.ArtOption) (bool, int64, error) { ret := _m.Called(ctx, repository, digest, option) + if len(ret) == 0 { + panic("no return value specified for Ensure") + } + var r0 bool var r1 int64 var r2 error @@ -132,6 +152,10 @@ func (_m *Controller) Ensure(ctx context.Context, repository string, digest stri func (_m *Controller) Get(ctx context.Context, id int64, option *artifact.Option) (*artifact.Artifact, error) { ret := _m.Called(ctx, id, option) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *artifact.Artifact var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, *artifact.Option) (*artifact.Artifact, error)); ok { @@ -158,6 +182,10 @@ func (_m *Controller) Get(ctx context.Context, id int64, option *artifact.Option func (_m *Controller) GetAddition(ctx context.Context, artifactID int64, additionType string) (*processor.Addition, error) { ret := _m.Called(ctx, artifactID, additionType) + if len(ret) == 0 { + panic("no return value specified for GetAddition") + } + var r0 *processor.Addition var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, string) (*processor.Addition, error)); ok { @@ -184,6 +212,10 @@ func (_m *Controller) GetAddition(ctx context.Context, artifactID int64, additio func (_m *Controller) GetByReference(ctx context.Context, repository string, reference string, option *artifact.Option) (*artifact.Artifact, error) { ret := _m.Called(ctx, repository, reference, option) + if len(ret) == 0 { + panic("no return value specified for GetByReference") + } + var r0 *artifact.Artifact var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, *artifact.Option) (*artifact.Artifact, error)); ok { @@ -210,6 +242,10 @@ func (_m *Controller) GetByReference(ctx context.Context, repository string, ref func (_m *Controller) List(ctx context.Context, query *q.Query, option *artifact.Option) ([]*artifact.Artifact, error) { ret := _m.Called(ctx, query, option) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*artifact.Artifact var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query, *artifact.Option) ([]*artifact.Artifact, error)); ok { @@ -236,6 +272,10 @@ func (_m *Controller) List(ctx context.Context, query *q.Query, option *artifact func (_m *Controller) RemoveLabel(ctx context.Context, artifactID int64, labelID int64) error { ret := _m.Called(ctx, artifactID, labelID) + if len(ret) == 0 { + panic("no return value specified for RemoveLabel") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok { r0 = rf(ctx, artifactID, labelID) @@ -250,6 +290,10 @@ func (_m *Controller) RemoveLabel(ctx context.Context, artifactID int64, labelID func (_m *Controller) UpdatePullTime(ctx context.Context, artifactID int64, tagID int64, _a3 time.Time) error { ret := _m.Called(ctx, artifactID, tagID, _a3) + if len(ret) == 0 { + panic("no return value specified for UpdatePullTime") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, int64, time.Time) error); ok { r0 = rf(ctx, artifactID, tagID, _a3) @@ -264,6 +308,10 @@ func (_m *Controller) UpdatePullTime(ctx context.Context, artifactID int64, tagI func (_m *Controller) Walk(ctx context.Context, root *artifact.Artifact, walkFn func(*artifact.Artifact) error, option *artifact.Option) error { ret := _m.Called(ctx, root, walkFn, option) + if len(ret) == 0 { + panic("no return value specified for Walk") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, func(*artifact.Artifact) error, *artifact.Option) error); ok { r0 = rf(ctx, root, walkFn, option) diff --git a/src/testing/controller/blob/controller.go b/src/testing/controller/blob/controller.go index fa212af25..3081f5e25 100644 --- a/src/testing/controller/blob/controller.go +++ b/src/testing/controller/blob/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package blob @@ -25,6 +25,10 @@ type Controller struct { func (_m *Controller) AssociateWithArtifact(ctx context.Context, blobDigests []string, artifactDigest string) error { ret := _m.Called(ctx, blobDigests, artifactDigest) + if len(ret) == 0 { + panic("no return value specified for AssociateWithArtifact") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, []string, string) error); ok { r0 = rf(ctx, blobDigests, artifactDigest) @@ -39,6 +43,10 @@ func (_m *Controller) AssociateWithArtifact(ctx context.Context, blobDigests []s func (_m *Controller) AssociateWithProjectByDigest(ctx context.Context, blobDigest string, projectID int64) error { ret := _m.Called(ctx, blobDigest, projectID) + if len(ret) == 0 { + panic("no return value specified for AssociateWithProjectByDigest") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, int64) error); ok { r0 = rf(ctx, blobDigest, projectID) @@ -53,6 +61,10 @@ func (_m *Controller) AssociateWithProjectByDigest(ctx context.Context, blobDige func (_m *Controller) AssociateWithProjectByID(ctx context.Context, blobID int64, projectID int64) error { ret := _m.Called(ctx, blobID, projectID) + if len(ret) == 0 { + panic("no return value specified for AssociateWithProjectByID") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok { r0 = rf(ctx, blobID, projectID) @@ -67,6 +79,10 @@ func (_m *Controller) AssociateWithProjectByID(ctx context.Context, blobID int64 func (_m *Controller) CalculateTotalSize(ctx context.Context, excludeForeign bool) (int64, error) { ret := _m.Called(ctx, excludeForeign) + if len(ret) == 0 { + panic("no return value specified for CalculateTotalSize") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, bool) (int64, error)); ok { @@ -91,6 +107,10 @@ func (_m *Controller) CalculateTotalSize(ctx context.Context, excludeForeign boo func (_m *Controller) CalculateTotalSizeByProject(ctx context.Context, projectID int64, excludeForeign bool) (int64, error) { ret := _m.Called(ctx, projectID, excludeForeign) + if len(ret) == 0 { + panic("no return value specified for CalculateTotalSizeByProject") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, bool) (int64, error)); ok { @@ -115,6 +135,10 @@ func (_m *Controller) CalculateTotalSizeByProject(ctx context.Context, projectID func (_m *Controller) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -129,6 +153,10 @@ func (_m *Controller) Delete(ctx context.Context, id int64) error { func (_m *Controller) Ensure(ctx context.Context, digest string, contentType string, size int64) (int64, error) { ret := _m.Called(ctx, digest, contentType, size) + if len(ret) == 0 { + panic("no return value specified for Ensure") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) (int64, error)); ok { @@ -160,6 +188,10 @@ func (_m *Controller) Exist(ctx context.Context, digest string, options ...blob. _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Exist") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, ...blob.Option) (bool, error)); ok { @@ -184,6 +216,10 @@ func (_m *Controller) Exist(ctx context.Context, digest string, options ...blob. func (_m *Controller) Fail(ctx context.Context, _a1 *models.Blob) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Fail") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.Blob) error); ok { r0 = rf(ctx, _a1) @@ -198,6 +234,10 @@ func (_m *Controller) Fail(ctx context.Context, _a1 *models.Blob) error { func (_m *Controller) FindMissingAssociationsForProject(ctx context.Context, projectID int64, blobs []*models.Blob) ([]*models.Blob, error) { ret := _m.Called(ctx, projectID, blobs) + if len(ret) == 0 { + panic("no return value specified for FindMissingAssociationsForProject") + } + var r0 []*models.Blob var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, []*models.Blob) ([]*models.Blob, error)); ok { @@ -231,6 +271,10 @@ func (_m *Controller) Get(ctx context.Context, digest string, options ...blob.Op _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *models.Blob var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, ...blob.Option) (*models.Blob, error)); ok { @@ -257,6 +301,10 @@ func (_m *Controller) Get(ctx context.Context, digest string, options ...blob.Op func (_m *Controller) GetAcceptedBlobSize(ctx context.Context, sessionID string) (int64, error) { ret := _m.Called(ctx, sessionID) + if len(ret) == 0 { + panic("no return value specified for GetAcceptedBlobSize") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (int64, error)); ok { @@ -281,6 +329,10 @@ func (_m *Controller) GetAcceptedBlobSize(ctx context.Context, sessionID string) func (_m *Controller) List(ctx context.Context, query *q.Query) ([]*models.Blob, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*models.Blob var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*models.Blob, error)); ok { @@ -307,6 +359,10 @@ func (_m *Controller) List(ctx context.Context, query *q.Query) ([]*models.Blob, func (_m *Controller) SetAcceptedBlobSize(ctx context.Context, sessionID string, size int64) error { ret := _m.Called(ctx, sessionID, size) + if len(ret) == 0 { + panic("no return value specified for SetAcceptedBlobSize") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, int64) error); ok { r0 = rf(ctx, sessionID, size) @@ -321,6 +377,10 @@ func (_m *Controller) SetAcceptedBlobSize(ctx context.Context, sessionID string, func (_m *Controller) Sync(ctx context.Context, references []distribution.Descriptor) error { ret := _m.Called(ctx, references) + if len(ret) == 0 { + panic("no return value specified for Sync") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, []distribution.Descriptor) error); ok { r0 = rf(ctx, references) @@ -335,6 +395,10 @@ func (_m *Controller) Sync(ctx context.Context, references []distribution.Descri func (_m *Controller) Touch(ctx context.Context, _a1 *models.Blob) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Touch") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.Blob) error); ok { r0 = rf(ctx, _a1) @@ -349,6 +413,10 @@ func (_m *Controller) Touch(ctx context.Context, _a1 *models.Blob) error { func (_m *Controller) Update(ctx context.Context, _a1 *models.Blob) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.Blob) error); ok { r0 = rf(ctx, _a1) diff --git a/src/testing/controller/config/controller.go b/src/testing/controller/config/controller.go index 8fd145413..c82468518 100644 --- a/src/testing/controller/config/controller.go +++ b/src/testing/controller/config/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package config @@ -18,6 +18,10 @@ type Controller struct { func (_m *Controller) AllConfigs(ctx context.Context) (map[string]interface{}, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for AllConfigs") + } + var r0 map[string]interface{} var r1 error if rf, ok := ret.Get(0).(func(context.Context) (map[string]interface{}, error)); ok { @@ -44,6 +48,10 @@ func (_m *Controller) AllConfigs(ctx context.Context) (map[string]interface{}, e func (_m *Controller) ConvertForGet(ctx context.Context, cfg map[string]interface{}, internal bool) (map[string]*models.Value, error) { ret := _m.Called(ctx, cfg, internal) + if len(ret) == 0 { + panic("no return value specified for ConvertForGet") + } + var r0 map[string]*models.Value var r1 error if rf, ok := ret.Get(0).(func(context.Context, map[string]interface{}, bool) (map[string]*models.Value, error)); ok { @@ -70,6 +78,10 @@ func (_m *Controller) ConvertForGet(ctx context.Context, cfg map[string]interfac func (_m *Controller) OverwriteConfig(ctx context.Context) error { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for OverwriteConfig") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context) error); ok { r0 = rf(ctx) @@ -84,6 +96,10 @@ func (_m *Controller) OverwriteConfig(ctx context.Context) error { func (_m *Controller) UpdateUserConfigs(ctx context.Context, conf map[string]interface{}) error { ret := _m.Called(ctx, conf) + if len(ret) == 0 { + panic("no return value specified for UpdateUserConfigs") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, map[string]interface{}) error); ok { r0 = rf(ctx, conf) @@ -98,6 +114,10 @@ func (_m *Controller) UpdateUserConfigs(ctx context.Context, conf map[string]int func (_m *Controller) UserConfigs(ctx context.Context) (map[string]*models.Value, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for UserConfigs") + } + var r0 map[string]*models.Value var r1 error if rf, ok := ret.Get(0).(func(context.Context) (map[string]*models.Value, error)); ok { diff --git a/src/testing/controller/jobservice/scheduler_controller.go b/src/testing/controller/jobservice/scheduler_controller.go index 806214fd2..4f5a09328 100644 --- a/src/testing/controller/jobservice/scheduler_controller.go +++ b/src/testing/controller/jobservice/scheduler_controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package jobservice @@ -21,6 +21,10 @@ type SchedulerController struct { func (_m *SchedulerController) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -45,6 +49,10 @@ func (_m *SchedulerController) Count(ctx context.Context, query *q.Query) (int64 func (_m *SchedulerController) Create(ctx context.Context, vendorType string, cronType string, cron string, callbackFuncName string, policy interface{}, extrasParam map[string]interface{}) (int64, error) { ret := _m.Called(ctx, vendorType, cronType, cron, callbackFuncName, policy, extrasParam) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, interface{}, map[string]interface{}) (int64, error)); ok { @@ -69,6 +77,10 @@ func (_m *SchedulerController) Create(ctx context.Context, vendorType string, cr func (_m *SchedulerController) Delete(ctx context.Context, vendorType string) error { ret := _m.Called(ctx, vendorType) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, vendorType) @@ -83,6 +95,10 @@ func (_m *SchedulerController) Delete(ctx context.Context, vendorType string) er func (_m *SchedulerController) Get(ctx context.Context, vendorType string) (*scheduler.Schedule, error) { ret := _m.Called(ctx, vendorType) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *scheduler.Schedule var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*scheduler.Schedule, error)); ok { @@ -109,6 +125,10 @@ func (_m *SchedulerController) Get(ctx context.Context, vendorType string) (*sch func (_m *SchedulerController) List(ctx context.Context, query *q.Query) ([]*scheduler.Schedule, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*scheduler.Schedule var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*scheduler.Schedule, error)); ok { @@ -135,6 +155,10 @@ func (_m *SchedulerController) List(ctx context.Context, query *q.Query) ([]*sch func (_m *SchedulerController) Paused(ctx context.Context) (bool, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for Paused") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context) (bool, error)); ok { diff --git a/src/testing/controller/project/controller.go b/src/testing/controller/project/controller.go index 69f3563e6..bcb9331b2 100644 --- a/src/testing/controller/project/controller.go +++ b/src/testing/controller/project/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package project @@ -25,6 +25,10 @@ type Controller struct { func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -49,6 +53,10 @@ func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) func (_m *Controller) Create(ctx context.Context, _a1 *models.Project) (int64, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *models.Project) (int64, error)); ok { @@ -73,6 +81,10 @@ func (_m *Controller) Create(ctx context.Context, _a1 *models.Project) (int64, e func (_m *Controller) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -87,6 +99,10 @@ func (_m *Controller) Delete(ctx context.Context, id int64) error { func (_m *Controller) Exists(ctx context.Context, projectIDOrName interface{}) (bool, error) { ret := _m.Called(ctx, projectIDOrName) + if len(ret) == 0 { + panic("no return value specified for Exists") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, interface{}) (bool, error)); ok { @@ -118,6 +134,10 @@ func (_m *Controller) Get(ctx context.Context, projectIDOrName interface{}, opti _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *models.Project var r1 error if rf, ok := ret.Get(0).(func(context.Context, interface{}, ...project.Option) (*models.Project, error)); ok { @@ -151,6 +171,10 @@ func (_m *Controller) GetByName(ctx context.Context, projectName string, options _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for GetByName") + } + var r0 *models.Project var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, ...project.Option) (*models.Project, error)); ok { @@ -184,6 +208,10 @@ func (_m *Controller) List(ctx context.Context, query *q.Query, options ...proje _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*models.Project var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query, ...project.Option) ([]*models.Project, error)); ok { @@ -210,6 +238,10 @@ func (_m *Controller) List(ctx context.Context, query *q.Query, options ...proje func (_m *Controller) ListRoles(ctx context.Context, projectID int64, u *commonmodels.User) ([]int, error) { ret := _m.Called(ctx, projectID, u) + if len(ret) == 0 { + panic("no return value specified for ListRoles") + } + var r0 []int var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, *commonmodels.User) ([]int, error)); ok { @@ -236,6 +268,10 @@ func (_m *Controller) ListRoles(ctx context.Context, projectID int64, u *commonm func (_m *Controller) Update(ctx context.Context, _a1 *models.Project) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.Project) error); ok { r0 = rf(ctx, _a1) diff --git a/src/testing/controller/proxy/remote_interface.go b/src/testing/controller/proxy/remote_interface.go index eb9361280..48b52a094 100644 --- a/src/testing/controller/proxy/remote_interface.go +++ b/src/testing/controller/proxy/remote_interface.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package proxy @@ -19,6 +19,10 @@ type RemoteInterface struct { func (_m *RemoteInterface) BlobReader(repo string, dig string) (int64, io.ReadCloser, error) { ret := _m.Called(repo, dig) + if len(ret) == 0 { + panic("no return value specified for BlobReader") + } + var r0 int64 var r1 io.ReadCloser var r2 error @@ -52,6 +56,10 @@ func (_m *RemoteInterface) BlobReader(repo string, dig string) (int64, io.ReadCl func (_m *RemoteInterface) ListTags(repo string) ([]string, error) { ret := _m.Called(repo) + if len(ret) == 0 { + panic("no return value specified for ListTags") + } + var r0 []string var r1 error if rf, ok := ret.Get(0).(func(string) ([]string, error)); ok { @@ -78,6 +86,10 @@ func (_m *RemoteInterface) ListTags(repo string) ([]string, error) { func (_m *RemoteInterface) Manifest(repo string, ref string) (distribution.Manifest, string, error) { ret := _m.Called(repo, ref) + if len(ret) == 0 { + panic("no return value specified for Manifest") + } + var r0 distribution.Manifest var r1 string var r2 error @@ -111,6 +123,10 @@ func (_m *RemoteInterface) Manifest(repo string, ref string) (distribution.Manif func (_m *RemoteInterface) ManifestExist(repo string, ref string) (bool, *distribution.Descriptor, error) { ret := _m.Called(repo, ref) + if len(ret) == 0 { + panic("no return value specified for ManifestExist") + } + var r0 bool var r1 *distribution.Descriptor var r2 error diff --git a/src/testing/controller/purge/controller.go b/src/testing/controller/purge/controller.go index 3771e28e8..7c3e27fed 100644 --- a/src/testing/controller/purge/controller.go +++ b/src/testing/controller/purge/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package purge @@ -18,6 +18,10 @@ type Controller struct { func (_m *Controller) Start(ctx context.Context, policy purge.JobPolicy, trigger string) (int64, error) { ret := _m.Called(ctx, policy, trigger) + if len(ret) == 0 { + panic("no return value specified for Start") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, purge.JobPolicy, string) (int64, error)); ok { @@ -42,6 +46,10 @@ func (_m *Controller) Start(ctx context.Context, policy purge.JobPolicy, trigger func (_m *Controller) Stop(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Stop") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) diff --git a/src/testing/controller/quota/controller.go b/src/testing/controller/quota/controller.go index 23b10a091..7053fb4d7 100644 --- a/src/testing/controller/quota/controller.go +++ b/src/testing/controller/quota/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package quota @@ -24,6 +24,10 @@ type Controller struct { func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -55,6 +59,10 @@ func (_m *Controller) Create(ctx context.Context, reference string, referenceID _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, types.ResourceList, ...types.ResourceList) (int64, error)); ok { @@ -79,6 +87,10 @@ func (_m *Controller) Create(ctx context.Context, reference string, referenceID func (_m *Controller) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -100,6 +112,10 @@ func (_m *Controller) Get(ctx context.Context, id int64, options ...quota.Option _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *models.Quota var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, ...quota.Option) (*models.Quota, error)); ok { @@ -133,6 +149,10 @@ func (_m *Controller) GetByRef(ctx context.Context, reference string, referenceI _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for GetByRef") + } + var r0 *models.Quota var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, ...quota.Option) (*models.Quota, error)); ok { @@ -159,6 +179,10 @@ func (_m *Controller) GetByRef(ctx context.Context, reference string, referenceI func (_m *Controller) IsEnabled(ctx context.Context, reference string, referenceID string) (bool, error) { ret := _m.Called(ctx, reference, referenceID) + if len(ret) == 0 { + panic("no return value specified for IsEnabled") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (bool, error)); ok { @@ -190,6 +214,10 @@ func (_m *Controller) List(ctx context.Context, query *q.Query, options ...quota _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*models.Quota var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query, ...quota.Option) ([]*models.Quota, error)); ok { @@ -223,6 +251,10 @@ func (_m *Controller) Refresh(ctx context.Context, reference string, referenceID _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Refresh") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, ...quota.Option) error); ok { r0 = rf(ctx, reference, referenceID, options...) @@ -237,6 +269,10 @@ func (_m *Controller) Refresh(ctx context.Context, reference string, referenceID func (_m *Controller) Request(ctx context.Context, reference string, referenceID string, resources types.ResourceList, f func() error) error { ret := _m.Called(ctx, reference, referenceID, resources, f) + if len(ret) == 0 { + panic("no return value specified for Request") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, types.ResourceList, func() error) error); ok { r0 = rf(ctx, reference, referenceID, resources, f) @@ -251,6 +287,10 @@ func (_m *Controller) Request(ctx context.Context, reference string, referenceID func (_m *Controller) Update(ctx context.Context, _a1 *models.Quota) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.Quota) error); ok { r0 = rf(ctx, _a1) diff --git a/src/testing/controller/replication/controller.go b/src/testing/controller/replication/controller.go index 76138da96..02a2afbb5 100644 --- a/src/testing/controller/replication/controller.go +++ b/src/testing/controller/replication/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package replication @@ -24,6 +24,10 @@ type Controller struct { func (_m *Controller) CreatePolicy(ctx context.Context, policy *model.Policy) (int64, error) { ret := _m.Called(ctx, policy) + if len(ret) == 0 { + panic("no return value specified for CreatePolicy") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy) (int64, error)); ok { @@ -48,6 +52,10 @@ func (_m *Controller) CreatePolicy(ctx context.Context, policy *model.Policy) (i func (_m *Controller) DeletePolicy(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for DeletePolicy") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -62,6 +70,10 @@ func (_m *Controller) DeletePolicy(ctx context.Context, id int64) error { func (_m *Controller) ExecutionCount(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ExecutionCount") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -86,6 +98,10 @@ func (_m *Controller) ExecutionCount(ctx context.Context, query *q.Query) (int64 func (_m *Controller) GetExecution(ctx context.Context, executionID int64) (*replication.Execution, error) { ret := _m.Called(ctx, executionID) + if len(ret) == 0 { + panic("no return value specified for GetExecution") + } + var r0 *replication.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*replication.Execution, error)); ok { @@ -112,6 +128,10 @@ func (_m *Controller) GetExecution(ctx context.Context, executionID int64) (*rep func (_m *Controller) GetPolicy(ctx context.Context, id int64) (*model.Policy, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for GetPolicy") + } + var r0 *model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.Policy, error)); ok { @@ -138,6 +158,10 @@ func (_m *Controller) GetPolicy(ctx context.Context, id int64) (*model.Policy, e func (_m *Controller) GetTask(ctx context.Context, taskID int64) (*replication.Task, error) { ret := _m.Called(ctx, taskID) + if len(ret) == 0 { + panic("no return value specified for GetTask") + } + var r0 *replication.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*replication.Task, error)); ok { @@ -164,6 +188,10 @@ func (_m *Controller) GetTask(ctx context.Context, taskID int64) (*replication.T func (_m *Controller) GetTaskLog(ctx context.Context, taskID int64) ([]byte, error) { ret := _m.Called(ctx, taskID) + if len(ret) == 0 { + panic("no return value specified for GetTaskLog") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) ([]byte, error)); ok { @@ -190,6 +218,10 @@ func (_m *Controller) GetTaskLog(ctx context.Context, taskID int64) ([]byte, err func (_m *Controller) ListExecutions(ctx context.Context, query *q.Query) ([]*replication.Execution, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListExecutions") + } + var r0 []*replication.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*replication.Execution, error)); ok { @@ -216,6 +248,10 @@ func (_m *Controller) ListExecutions(ctx context.Context, query *q.Query) ([]*re func (_m *Controller) ListPolicies(ctx context.Context, query *q.Query) ([]*model.Policy, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListPolicies") + } + var r0 []*model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.Policy, error)); ok { @@ -242,6 +278,10 @@ func (_m *Controller) ListPolicies(ctx context.Context, query *q.Query) ([]*mode func (_m *Controller) ListTasks(ctx context.Context, query *q.Query) ([]*replication.Task, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListTasks") + } + var r0 []*replication.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*replication.Task, error)); ok { @@ -268,6 +308,10 @@ func (_m *Controller) ListTasks(ctx context.Context, query *q.Query) ([]*replica func (_m *Controller) PolicyCount(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for PolicyCount") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -292,6 +336,10 @@ func (_m *Controller) PolicyCount(ctx context.Context, query *q.Query) (int64, e func (_m *Controller) Start(ctx context.Context, policy *model.Policy, resource *regmodel.Resource, trigger string) (int64, error) { ret := _m.Called(ctx, policy, resource, trigger) + if len(ret) == 0 { + panic("no return value specified for Start") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy, *regmodel.Resource, string) (int64, error)); ok { @@ -316,6 +364,10 @@ func (_m *Controller) Start(ctx context.Context, policy *model.Policy, resource func (_m *Controller) Stop(ctx context.Context, executionID int64) error { ret := _m.Called(ctx, executionID) + if len(ret) == 0 { + panic("no return value specified for Stop") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, executionID) @@ -330,6 +382,10 @@ func (_m *Controller) Stop(ctx context.Context, executionID int64) error { func (_m *Controller) TaskCount(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for TaskCount") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -361,6 +417,10 @@ func (_m *Controller) UpdatePolicy(ctx context.Context, policy *model.Policy, pr _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for UpdatePolicy") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy, ...string) error); ok { r0 = rf(ctx, policy, props...) diff --git a/src/testing/controller/repository/controller.go b/src/testing/controller/repository/controller.go index 92896a00c..bdc109770 100644 --- a/src/testing/controller/repository/controller.go +++ b/src/testing/controller/repository/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package repository @@ -20,6 +20,10 @@ type Controller struct { func (_m *Controller) AddPullCount(ctx context.Context, id int64, count uint64) error { ret := _m.Called(ctx, id, count) + if len(ret) == 0 { + panic("no return value specified for AddPullCount") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, uint64) error); ok { r0 = rf(ctx, id, count) @@ -34,6 +38,10 @@ func (_m *Controller) AddPullCount(ctx context.Context, id int64, count uint64) func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -58,6 +66,10 @@ func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) func (_m *Controller) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -72,6 +84,10 @@ func (_m *Controller) Delete(ctx context.Context, id int64) error { func (_m *Controller) Ensure(ctx context.Context, name string) (bool, int64, error) { ret := _m.Called(ctx, name) + if len(ret) == 0 { + panic("no return value specified for Ensure") + } + var r0 bool var r1 int64 var r2 error @@ -103,6 +119,10 @@ func (_m *Controller) Ensure(ctx context.Context, name string) (bool, int64, err func (_m *Controller) Get(ctx context.Context, id int64) (*model.RepoRecord, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.RepoRecord var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.RepoRecord, error)); ok { @@ -129,6 +149,10 @@ func (_m *Controller) Get(ctx context.Context, id int64) (*model.RepoRecord, err func (_m *Controller) GetByName(ctx context.Context, name string) (*model.RepoRecord, error) { ret := _m.Called(ctx, name) + if len(ret) == 0 { + panic("no return value specified for GetByName") + } + var r0 *model.RepoRecord var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*model.RepoRecord, error)); ok { @@ -155,6 +179,10 @@ func (_m *Controller) GetByName(ctx context.Context, name string) (*model.RepoRe func (_m *Controller) List(ctx context.Context, query *q.Query) ([]*model.RepoRecord, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.RepoRecord var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.RepoRecord, error)); ok { @@ -188,6 +216,10 @@ func (_m *Controller) Update(ctx context.Context, _a1 *model.RepoRecord, propert _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.RepoRecord, ...string) error); ok { r0 = rf(ctx, _a1, properties...) diff --git a/src/testing/controller/retention/controller.go b/src/testing/controller/retention/controller.go index d27ce8d0a..8eb3c9889 100644 --- a/src/testing/controller/retention/controller.go +++ b/src/testing/controller/retention/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package retention @@ -22,6 +22,10 @@ type Controller struct { func (_m *Controller) CreateRetention(ctx context.Context, p *policy.Metadata) (int64, error) { ret := _m.Called(ctx, p) + if len(ret) == 0 { + panic("no return value specified for CreateRetention") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *policy.Metadata) (int64, error)); ok { @@ -46,6 +50,10 @@ func (_m *Controller) CreateRetention(ctx context.Context, p *policy.Metadata) ( func (_m *Controller) DeleteRetention(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for DeleteRetention") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -60,6 +68,10 @@ func (_m *Controller) DeleteRetention(ctx context.Context, id int64) error { func (_m *Controller) DeleteRetentionByProject(ctx context.Context, projectID int64) error { ret := _m.Called(ctx, projectID) + if len(ret) == 0 { + panic("no return value specified for DeleteRetentionByProject") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, projectID) @@ -74,6 +86,10 @@ func (_m *Controller) DeleteRetentionByProject(ctx context.Context, projectID in func (_m *Controller) GetRetention(ctx context.Context, id int64) (*policy.Metadata, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for GetRetention") + } + var r0 *policy.Metadata var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*policy.Metadata, error)); ok { @@ -100,6 +116,10 @@ func (_m *Controller) GetRetention(ctx context.Context, id int64) (*policy.Metad func (_m *Controller) GetRetentionExec(ctx context.Context, eid int64) (*pkgretention.Execution, error) { ret := _m.Called(ctx, eid) + if len(ret) == 0 { + panic("no return value specified for GetRetentionExec") + } + var r0 *pkgretention.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*pkgretention.Execution, error)); ok { @@ -126,6 +146,10 @@ func (_m *Controller) GetRetentionExec(ctx context.Context, eid int64) (*pkgrete func (_m *Controller) GetRetentionExecTask(ctx context.Context, taskID int64) (*pkgretention.Task, error) { ret := _m.Called(ctx, taskID) + if len(ret) == 0 { + panic("no return value specified for GetRetentionExecTask") + } + var r0 *pkgretention.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*pkgretention.Task, error)); ok { @@ -152,6 +176,10 @@ func (_m *Controller) GetRetentionExecTask(ctx context.Context, taskID int64) (* func (_m *Controller) GetRetentionExecTaskLog(ctx context.Context, taskID int64) ([]byte, error) { ret := _m.Called(ctx, taskID) + if len(ret) == 0 { + panic("no return value specified for GetRetentionExecTaskLog") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) ([]byte, error)); ok { @@ -178,6 +206,10 @@ func (_m *Controller) GetRetentionExecTaskLog(ctx context.Context, taskID int64) func (_m *Controller) GetTotalOfRetentionExecTasks(ctx context.Context, executionID int64) (int64, error) { ret := _m.Called(ctx, executionID) + if len(ret) == 0 { + panic("no return value specified for GetTotalOfRetentionExecTasks") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (int64, error)); ok { @@ -202,6 +234,10 @@ func (_m *Controller) GetTotalOfRetentionExecTasks(ctx context.Context, executio func (_m *Controller) GetTotalOfRetentionExecs(ctx context.Context, policyID int64) (int64, error) { ret := _m.Called(ctx, policyID) + if len(ret) == 0 { + panic("no return value specified for GetTotalOfRetentionExecs") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (int64, error)); ok { @@ -226,6 +262,10 @@ func (_m *Controller) GetTotalOfRetentionExecs(ctx context.Context, policyID int func (_m *Controller) ListRetentionExecTasks(ctx context.Context, executionID int64, query *q.Query) ([]*pkgretention.Task, error) { ret := _m.Called(ctx, executionID, query) + if len(ret) == 0 { + panic("no return value specified for ListRetentionExecTasks") + } + var r0 []*pkgretention.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, *q.Query) ([]*pkgretention.Task, error)); ok { @@ -252,6 +292,10 @@ func (_m *Controller) ListRetentionExecTasks(ctx context.Context, executionID in func (_m *Controller) ListRetentionExecs(ctx context.Context, policyID int64, query *q.Query) ([]*pkgretention.Execution, error) { ret := _m.Called(ctx, policyID, query) + if len(ret) == 0 { + panic("no return value specified for ListRetentionExecs") + } + var r0 []*pkgretention.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, *q.Query) ([]*pkgretention.Execution, error)); ok { @@ -278,6 +322,10 @@ func (_m *Controller) ListRetentionExecs(ctx context.Context, policyID int64, qu func (_m *Controller) OperateRetentionExec(ctx context.Context, eid int64, action string) error { ret := _m.Called(ctx, eid, action) + if len(ret) == 0 { + panic("no return value specified for OperateRetentionExec") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, string) error); ok { r0 = rf(ctx, eid, action) @@ -292,6 +340,10 @@ func (_m *Controller) OperateRetentionExec(ctx context.Context, eid int64, actio func (_m *Controller) TriggerRetentionExec(ctx context.Context, policyID int64, trigger string, dryRun bool) (int64, error) { ret := _m.Called(ctx, policyID, trigger, dryRun) + if len(ret) == 0 { + panic("no return value specified for TriggerRetentionExec") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, string, bool) (int64, error)); ok { @@ -316,6 +368,10 @@ func (_m *Controller) TriggerRetentionExec(ctx context.Context, policyID int64, func (_m *Controller) UpdateRetention(ctx context.Context, p *policy.Metadata) error { ret := _m.Called(ctx, p) + if len(ret) == 0 { + panic("no return value specified for UpdateRetention") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *policy.Metadata) error); ok { r0 = rf(ctx, p) diff --git a/src/testing/controller/robot/controller.go b/src/testing/controller/robot/controller.go index ab54afe59..03c6af483 100644 --- a/src/testing/controller/robot/controller.go +++ b/src/testing/controller/robot/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package robot @@ -20,6 +20,10 @@ type Controller struct { func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) func (_m *Controller) Create(ctx context.Context, r *robot.Robot) (int64, string, error) { ret := _m.Called(ctx, r) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 string var r2 error @@ -75,6 +83,10 @@ func (_m *Controller) Create(ctx context.Context, r *robot.Robot) (int64, string func (_m *Controller) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -89,6 +101,10 @@ func (_m *Controller) Delete(ctx context.Context, id int64) error { func (_m *Controller) Get(ctx context.Context, id int64, option *robot.Option) (*robot.Robot, error) { ret := _m.Called(ctx, id, option) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *robot.Robot var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, *robot.Option) (*robot.Robot, error)); ok { @@ -115,6 +131,10 @@ func (_m *Controller) Get(ctx context.Context, id int64, option *robot.Option) ( func (_m *Controller) List(ctx context.Context, query *q.Query, option *robot.Option) ([]*robot.Robot, error) { ret := _m.Called(ctx, query, option) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*robot.Robot var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query, *robot.Option) ([]*robot.Robot, error)); ok { @@ -141,6 +161,10 @@ func (_m *Controller) List(ctx context.Context, query *q.Query, option *robot.Op func (_m *Controller) Update(ctx context.Context, r *robot.Robot, option *robot.Option) error { ret := _m.Called(ctx, r, option) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *robot.Robot, *robot.Option) error); ok { r0 = rf(ctx, r, option) diff --git a/src/testing/controller/scan/checker.go b/src/testing/controller/scan/checker.go index 71f160185..9c2e4b674 100644 --- a/src/testing/controller/scan/checker.go +++ b/src/testing/controller/scan/checker.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package scan @@ -19,6 +19,10 @@ type Checker struct { func (_m *Checker) IsScannable(ctx context.Context, _a1 *artifact.Artifact) (bool, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for IsScannable") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact) (bool, error)); ok { diff --git a/src/testing/controller/scan/controller.go b/src/testing/controller/scan/controller.go index bd8001380..5ec8091a4 100644 --- a/src/testing/controller/scan/controller.go +++ b/src/testing/controller/scan/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package scan @@ -32,6 +32,10 @@ func (_m *Controller) DeleteReports(ctx context.Context, digests ...string) erro _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for DeleteReports") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, ...string) error); ok { r0 = rf(ctx, digests...) @@ -46,6 +50,10 @@ func (_m *Controller) DeleteReports(ctx context.Context, digests ...string) erro func (_m *Controller) GetReport(ctx context.Context, _a1 *artifact.Artifact, mimeTypes []string) ([]*daoscan.Report, error) { ret := _m.Called(ctx, _a1, mimeTypes) + if len(ret) == 0 { + panic("no return value specified for GetReport") + } + var r0 []*daoscan.Report var r1 error if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, []string) ([]*daoscan.Report, error)); ok { @@ -72,6 +80,10 @@ func (_m *Controller) GetReport(ctx context.Context, _a1 *artifact.Artifact, mim func (_m *Controller) GetScanLog(ctx context.Context, art *artifact.Artifact, uuid string) ([]byte, error) { ret := _m.Called(ctx, art, uuid) + if len(ret) == 0 { + panic("no return value specified for GetScanLog") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, string) ([]byte, error)); ok { @@ -98,6 +110,10 @@ func (_m *Controller) GetScanLog(ctx context.Context, art *artifact.Artifact, uu func (_m *Controller) GetSummary(ctx context.Context, _a1 *artifact.Artifact, mimeTypes []string) (map[string]interface{}, error) { ret := _m.Called(ctx, _a1, mimeTypes) + if len(ret) == 0 { + panic("no return value specified for GetSummary") + } + var r0 map[string]interface{} var r1 error if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, []string) (map[string]interface{}, error)); ok { @@ -124,6 +140,10 @@ func (_m *Controller) GetSummary(ctx context.Context, _a1 *artifact.Artifact, mi func (_m *Controller) GetVulnerable(ctx context.Context, _a1 *artifact.Artifact, allowlist models.CVESet, allowlistIsExpired bool) (*scan.Vulnerable, error) { ret := _m.Called(ctx, _a1, allowlist, allowlistIsExpired) + if len(ret) == 0 { + panic("no return value specified for GetVulnerable") + } + var r0 *scan.Vulnerable var r1 error if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, models.CVESet, bool) (*scan.Vulnerable, error)); ok { @@ -157,6 +177,10 @@ func (_m *Controller) Scan(ctx context.Context, _a1 *artifact.Artifact, options _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Scan") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, ...scan.Option) error); ok { r0 = rf(ctx, _a1, options...) @@ -171,6 +195,10 @@ func (_m *Controller) Scan(ctx context.Context, _a1 *artifact.Artifact, options func (_m *Controller) ScanAll(ctx context.Context, trigger string, async bool) (int64, error) { ret := _m.Called(ctx, trigger, async) + if len(ret) == 0 { + panic("no return value specified for ScanAll") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, bool) (int64, error)); ok { @@ -195,6 +223,10 @@ func (_m *Controller) ScanAll(ctx context.Context, trigger string, async bool) ( func (_m *Controller) Stop(ctx context.Context, _a1 *artifact.Artifact, capType string) error { ret := _m.Called(ctx, _a1, capType) + if len(ret) == 0 { + panic("no return value specified for Stop") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, string) error); ok { r0 = rf(ctx, _a1, capType) @@ -209,6 +241,10 @@ func (_m *Controller) Stop(ctx context.Context, _a1 *artifact.Artifact, capType func (_m *Controller) StopScanAll(ctx context.Context, executionID int64, async bool) error { ret := _m.Called(ctx, executionID, async) + if len(ret) == 0 { + panic("no return value specified for StopScanAll") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, bool) error); ok { r0 = rf(ctx, executionID, async) diff --git a/src/testing/controller/scandataexport/controller.go b/src/testing/controller/scandataexport/controller.go index 08b93c74b..b75de2fc8 100644 --- a/src/testing/controller/scandataexport/controller.go +++ b/src/testing/controller/scandataexport/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package scandataexport @@ -20,6 +20,10 @@ type Controller struct { func (_m *Controller) DeleteExecution(ctx context.Context, executionID int64) error { ret := _m.Called(ctx, executionID) + if len(ret) == 0 { + panic("no return value specified for DeleteExecution") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, executionID) @@ -34,6 +38,10 @@ func (_m *Controller) DeleteExecution(ctx context.Context, executionID int64) er func (_m *Controller) GetExecution(ctx context.Context, executionID int64) (*export.Execution, error) { ret := _m.Called(ctx, executionID) + if len(ret) == 0 { + panic("no return value specified for GetExecution") + } + var r0 *export.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*export.Execution, error)); ok { @@ -60,6 +68,10 @@ func (_m *Controller) GetExecution(ctx context.Context, executionID int64) (*exp func (_m *Controller) GetTask(ctx context.Context, executionID int64) (*task.Task, error) { ret := _m.Called(ctx, executionID) + if len(ret) == 0 { + panic("no return value specified for GetTask") + } + var r0 *task.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*task.Task, error)); ok { @@ -86,6 +98,10 @@ func (_m *Controller) GetTask(ctx context.Context, executionID int64) (*task.Tas func (_m *Controller) ListExecutions(ctx context.Context, userName string) ([]*export.Execution, error) { ret := _m.Called(ctx, userName) + if len(ret) == 0 { + panic("no return value specified for ListExecutions") + } + var r0 []*export.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]*export.Execution, error)); ok { @@ -112,6 +128,10 @@ func (_m *Controller) ListExecutions(ctx context.Context, userName string) ([]*e func (_m *Controller) Start(ctx context.Context, criteria export.Request) (int64, error) { ret := _m.Called(ctx, criteria) + if len(ret) == 0 { + panic("no return value specified for Start") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, export.Request) (int64, error)); ok { diff --git a/src/testing/controller/scanner/controller.go b/src/testing/controller/scanner/controller.go index b07a82f5d..2f7f9777a 100644 --- a/src/testing/controller/scanner/controller.go +++ b/src/testing/controller/scanner/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package scanner @@ -24,6 +24,10 @@ type Controller struct { func (_m *Controller) CreateRegistration(ctx context.Context, registration *scanner.Registration) (string, error) { ret := _m.Called(ctx, registration) + if len(ret) == 0 { + panic("no return value specified for CreateRegistration") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(context.Context, *scanner.Registration) (string, error)); ok { @@ -48,6 +52,10 @@ func (_m *Controller) CreateRegistration(ctx context.Context, registration *scan func (_m *Controller) DeleteRegistration(ctx context.Context, registrationUUID string) (*scanner.Registration, error) { ret := _m.Called(ctx, registrationUUID) + if len(ret) == 0 { + panic("no return value specified for DeleteRegistration") + } + var r0 *scanner.Registration var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*scanner.Registration, error)); ok { @@ -74,6 +82,10 @@ func (_m *Controller) DeleteRegistration(ctx context.Context, registrationUUID s func (_m *Controller) GetMetadata(ctx context.Context, registrationUUID string) (*v1.ScannerAdapterMetadata, error) { ret := _m.Called(ctx, registrationUUID) + if len(ret) == 0 { + panic("no return value specified for GetMetadata") + } + var r0 *v1.ScannerAdapterMetadata var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*v1.ScannerAdapterMetadata, error)); ok { @@ -100,6 +112,10 @@ func (_m *Controller) GetMetadata(ctx context.Context, registrationUUID string) func (_m *Controller) GetRegistration(ctx context.Context, registrationUUID string) (*scanner.Registration, error) { ret := _m.Called(ctx, registrationUUID) + if len(ret) == 0 { + panic("no return value specified for GetRegistration") + } + var r0 *scanner.Registration var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*scanner.Registration, error)); ok { @@ -133,6 +149,10 @@ func (_m *Controller) GetRegistrationByProject(ctx context.Context, projectID in _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for GetRegistrationByProject") + } + var r0 *scanner.Registration var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, ...controllerscanner.Option) (*scanner.Registration, error)); ok { @@ -159,6 +179,10 @@ func (_m *Controller) GetRegistrationByProject(ctx context.Context, projectID in func (_m *Controller) GetTotalOfRegistrations(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for GetTotalOfRegistrations") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -183,6 +207,10 @@ func (_m *Controller) GetTotalOfRegistrations(ctx context.Context, query *q.Quer func (_m *Controller) ListRegistrations(ctx context.Context, query *q.Query) ([]*scanner.Registration, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListRegistrations") + } + var r0 []*scanner.Registration var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*scanner.Registration, error)); ok { @@ -209,6 +237,10 @@ func (_m *Controller) ListRegistrations(ctx context.Context, query *q.Query) ([] func (_m *Controller) Ping(ctx context.Context, registration *scanner.Registration) (*v1.ScannerAdapterMetadata, error) { ret := _m.Called(ctx, registration) + if len(ret) == 0 { + panic("no return value specified for Ping") + } + var r0 *v1.ScannerAdapterMetadata var r1 error if rf, ok := ret.Get(0).(func(context.Context, *scanner.Registration) (*v1.ScannerAdapterMetadata, error)); ok { @@ -235,6 +267,10 @@ func (_m *Controller) Ping(ctx context.Context, registration *scanner.Registrati func (_m *Controller) RegistrationExists(ctx context.Context, registrationUUID string) bool { ret := _m.Called(ctx, registrationUUID) + if len(ret) == 0 { + panic("no return value specified for RegistrationExists") + } + var r0 bool if rf, ok := ret.Get(0).(func(context.Context, string) bool); ok { r0 = rf(ctx, registrationUUID) @@ -249,6 +285,10 @@ func (_m *Controller) RegistrationExists(ctx context.Context, registrationUUID s func (_m *Controller) SetDefaultRegistration(ctx context.Context, registrationUUID string) error { ret := _m.Called(ctx, registrationUUID) + if len(ret) == 0 { + panic("no return value specified for SetDefaultRegistration") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, registrationUUID) @@ -263,6 +303,10 @@ func (_m *Controller) SetDefaultRegistration(ctx context.Context, registrationUU func (_m *Controller) SetRegistrationByProject(ctx context.Context, projectID int64, scannerID string) error { ret := _m.Called(ctx, projectID, scannerID) + if len(ret) == 0 { + panic("no return value specified for SetRegistrationByProject") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, string) error); ok { r0 = rf(ctx, projectID, scannerID) @@ -277,6 +321,10 @@ func (_m *Controller) SetRegistrationByProject(ctx context.Context, projectID in func (_m *Controller) UpdateRegistration(ctx context.Context, registration *scanner.Registration) error { ret := _m.Called(ctx, registration) + if len(ret) == 0 { + panic("no return value specified for UpdateRegistration") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *scanner.Registration) error); ok { r0 = rf(ctx, registration) diff --git a/src/testing/controller/securityhub/controller.go b/src/testing/controller/securityhub/controller.go index 55449b37e..5382fb2b1 100644 --- a/src/testing/controller/securityhub/controller.go +++ b/src/testing/controller/securityhub/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package securityhub @@ -22,6 +22,10 @@ type Controller struct { func (_m *Controller) CountVuls(ctx context.Context, scannerUUID string, projectID int64, tuneCount bool, query *q.Query) (int64, error) { ret := _m.Called(ctx, scannerUUID, projectID, tuneCount, query) + if len(ret) == 0 { + panic("no return value specified for CountVuls") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64, bool, *q.Query) (int64, error)); ok { @@ -46,6 +50,10 @@ func (_m *Controller) CountVuls(ctx context.Context, scannerUUID string, project func (_m *Controller) ListVuls(ctx context.Context, scannerUUID string, projectID int64, withTag bool, query *q.Query) ([]*model.VulnerabilityItem, error) { ret := _m.Called(ctx, scannerUUID, projectID, withTag, query) + if len(ret) == 0 { + panic("no return value specified for ListVuls") + } + var r0 []*model.VulnerabilityItem var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64, bool, *q.Query) ([]*model.VulnerabilityItem, error)); ok { @@ -79,6 +87,10 @@ func (_m *Controller) SecuritySummary(ctx context.Context, projectID int64, opti _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for SecuritySummary") + } + var r0 *model.Summary var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, ...securityhub.Option) (*model.Summary, error)); ok { diff --git a/src/testing/controller/systemartifact/controller.go b/src/testing/controller/systemartifact/controller.go index 73c4824b4..52354277a 100644 --- a/src/testing/controller/systemartifact/controller.go +++ b/src/testing/controller/systemartifact/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package systemartifact @@ -17,6 +17,10 @@ type Controller struct { func (_m *Controller) Start(ctx context.Context, async bool, trigger string) error { ret := _m.Called(ctx, async, trigger) + if len(ret) == 0 { + panic("no return value specified for Start") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, bool, string) error); ok { r0 = rf(ctx, async, trigger) diff --git a/src/testing/controller/task/controller.go b/src/testing/controller/task/controller.go index 256b9774a..4e542513d 100644 --- a/src/testing/controller/task/controller.go +++ b/src/testing/controller/task/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package task @@ -20,6 +20,10 @@ type Controller struct { func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) func (_m *Controller) Get(ctx context.Context, id int64) (*pkgtask.Task, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *pkgtask.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*pkgtask.Task, error)); ok { @@ -70,6 +78,10 @@ func (_m *Controller) Get(ctx context.Context, id int64) (*pkgtask.Task, error) func (_m *Controller) GetLog(ctx context.Context, id int64) ([]byte, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for GetLog") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) ([]byte, error)); ok { @@ -96,6 +108,10 @@ func (_m *Controller) GetLog(ctx context.Context, id int64) ([]byte, error) { func (_m *Controller) List(ctx context.Context, query *q.Query) ([]*pkgtask.Task, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*pkgtask.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*pkgtask.Task, error)); ok { @@ -122,6 +138,10 @@ func (_m *Controller) List(ctx context.Context, query *q.Query) ([]*pkgtask.Task func (_m *Controller) Stop(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Stop") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) diff --git a/src/testing/controller/task/execution_controller.go b/src/testing/controller/task/execution_controller.go index 22580b6f3..5501b14de 100644 --- a/src/testing/controller/task/execution_controller.go +++ b/src/testing/controller/task/execution_controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package task @@ -20,6 +20,10 @@ type ExecutionController struct { func (_m *ExecutionController) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *ExecutionController) Count(ctx context.Context, query *q.Query) (int64 func (_m *ExecutionController) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -58,6 +66,10 @@ func (_m *ExecutionController) Delete(ctx context.Context, id int64) error { func (_m *ExecutionController) Get(ctx context.Context, id int64) (*pkgtask.Execution, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *pkgtask.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*pkgtask.Execution, error)); ok { @@ -84,6 +96,10 @@ func (_m *ExecutionController) Get(ctx context.Context, id int64) (*pkgtask.Exec func (_m *ExecutionController) List(ctx context.Context, query *q.Query) ([]*pkgtask.Execution, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*pkgtask.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*pkgtask.Execution, error)); ok { @@ -110,6 +126,10 @@ func (_m *ExecutionController) List(ctx context.Context, query *q.Query) ([]*pkg func (_m *ExecutionController) Stop(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Stop") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) diff --git a/src/testing/controller/user/controller.go b/src/testing/controller/user/controller.go index 17f7ea5ef..5976018d4 100644 --- a/src/testing/controller/user/controller.go +++ b/src/testing/controller/user/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package user @@ -24,6 +24,10 @@ type Controller struct { func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -48,6 +52,10 @@ func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) func (_m *Controller) Create(ctx context.Context, u *models.User) (int, error) { ret := _m.Called(ctx, u) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int var r1 error if rf, ok := ret.Get(0).(func(context.Context, *models.User) (int, error)); ok { @@ -72,6 +80,10 @@ func (_m *Controller) Create(ctx context.Context, u *models.User) (int, error) { func (_m *Controller) Delete(ctx context.Context, id int) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int) error); ok { r0 = rf(ctx, id) @@ -86,6 +98,10 @@ func (_m *Controller) Delete(ctx context.Context, id int) error { func (_m *Controller) Get(ctx context.Context, id int, opt *user.Option) (*models.User, error) { ret := _m.Called(ctx, id, opt) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *models.User var r1 error if rf, ok := ret.Get(0).(func(context.Context, int, *user.Option) (*models.User, error)); ok { @@ -112,6 +128,10 @@ func (_m *Controller) Get(ctx context.Context, id int, opt *user.Option) (*model func (_m *Controller) GetByName(ctx context.Context, username string) (*models.User, error) { ret := _m.Called(ctx, username) + if len(ret) == 0 { + panic("no return value specified for GetByName") + } + var r0 *models.User var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*models.User, error)); ok { @@ -138,6 +158,10 @@ func (_m *Controller) GetByName(ctx context.Context, username string) (*models.U func (_m *Controller) GetBySubIss(ctx context.Context, sub string, iss string) (*models.User, error) { ret := _m.Called(ctx, sub, iss) + if len(ret) == 0 { + panic("no return value specified for GetBySubIss") + } + var r0 *models.User var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (*models.User, error)); ok { @@ -171,6 +195,10 @@ func (_m *Controller) List(ctx context.Context, query *q.Query, options ...userm _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*models.User var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query, ...usermodels.Option) ([]*models.User, error)); ok { @@ -197,6 +225,10 @@ func (_m *Controller) List(ctx context.Context, query *q.Query, options ...userm func (_m *Controller) OnboardOIDCUser(ctx context.Context, u *models.User) error { ret := _m.Called(ctx, u) + if len(ret) == 0 { + panic("no return value specified for OnboardOIDCUser") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.User) error); ok { r0 = rf(ctx, u) @@ -211,6 +243,10 @@ func (_m *Controller) OnboardOIDCUser(ctx context.Context, u *models.User) error func (_m *Controller) SetCliSecret(ctx context.Context, id int, secret string) error { ret := _m.Called(ctx, id, secret) + if len(ret) == 0 { + panic("no return value specified for SetCliSecret") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int, string) error); ok { r0 = rf(ctx, id, secret) @@ -225,6 +261,10 @@ func (_m *Controller) SetCliSecret(ctx context.Context, id int, secret string) e func (_m *Controller) SetSysAdmin(ctx context.Context, id int, adminFlag bool) error { ret := _m.Called(ctx, id, adminFlag) + if len(ret) == 0 { + panic("no return value specified for SetSysAdmin") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int, bool) error); ok { r0 = rf(ctx, id, adminFlag) @@ -246,6 +286,10 @@ func (_m *Controller) UpdateOIDCMeta(ctx context.Context, ou *models.OIDCUser, c _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for UpdateOIDCMeta") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.OIDCUser, ...string) error); ok { r0 = rf(ctx, ou, cols...) @@ -260,6 +304,10 @@ func (_m *Controller) UpdateOIDCMeta(ctx context.Context, ou *models.OIDCUser, c func (_m *Controller) UpdatePassword(ctx context.Context, id int, password string) error { ret := _m.Called(ctx, id, password) + if len(ret) == 0 { + panic("no return value specified for UpdatePassword") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int, string) error); ok { r0 = rf(ctx, id, password) @@ -281,6 +329,10 @@ func (_m *Controller) UpdateProfile(ctx context.Context, u *models.User, cols .. _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for UpdateProfile") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.User, ...string) error); ok { r0 = rf(ctx, u, cols...) @@ -295,6 +347,10 @@ func (_m *Controller) UpdateProfile(ctx context.Context, u *models.User, cols .. func (_m *Controller) VerifyPassword(ctx context.Context, usernameOrEmail string, password string) (bool, error) { ret := _m.Called(ctx, usernameOrEmail, password) + if len(ret) == 0 { + panic("no return value specified for VerifyPassword") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (bool, error)); ok { diff --git a/src/testing/controller/webhook/controller.go b/src/testing/controller/webhook/controller.go index a956dc7f1..c50d44850 100644 --- a/src/testing/controller/webhook/controller.go +++ b/src/testing/controller/webhook/controller.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package webhook @@ -24,6 +24,10 @@ type Controller struct { func (_m *Controller) CountExecutions(ctx context.Context, policyID int64, query *q.Query) (int64, error) { ret := _m.Called(ctx, policyID, query) + if len(ret) == 0 { + panic("no return value specified for CountExecutions") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, *q.Query) (int64, error)); ok { @@ -48,6 +52,10 @@ func (_m *Controller) CountExecutions(ctx context.Context, policyID int64, query func (_m *Controller) CountPolicies(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for CountPolicies") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -72,6 +80,10 @@ func (_m *Controller) CountPolicies(ctx context.Context, query *q.Query) (int64, func (_m *Controller) CountTasks(ctx context.Context, execID int64, query *q.Query) (int64, error) { ret := _m.Called(ctx, execID, query) + if len(ret) == 0 { + panic("no return value specified for CountTasks") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, *q.Query) (int64, error)); ok { @@ -96,6 +108,10 @@ func (_m *Controller) CountTasks(ctx context.Context, execID int64, query *q.Que func (_m *Controller) CreatePolicy(ctx context.Context, policy *model.Policy) (int64, error) { ret := _m.Called(ctx, policy) + if len(ret) == 0 { + panic("no return value specified for CreatePolicy") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy) (int64, error)); ok { @@ -120,6 +136,10 @@ func (_m *Controller) CreatePolicy(ctx context.Context, policy *model.Policy) (i func (_m *Controller) DeletePolicy(ctx context.Context, policyID int64) error { ret := _m.Called(ctx, policyID) + if len(ret) == 0 { + panic("no return value specified for DeletePolicy") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, policyID) @@ -134,6 +154,10 @@ func (_m *Controller) DeletePolicy(ctx context.Context, policyID int64) error { func (_m *Controller) GetLastTriggerTime(ctx context.Context, eventType string, policyID int64) (time.Time, error) { ret := _m.Called(ctx, eventType, policyID) + if len(ret) == 0 { + panic("no return value specified for GetLastTriggerTime") + } + var r0 time.Time var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64) (time.Time, error)); ok { @@ -158,6 +182,10 @@ func (_m *Controller) GetLastTriggerTime(ctx context.Context, eventType string, func (_m *Controller) GetPolicy(ctx context.Context, id int64) (*model.Policy, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for GetPolicy") + } + var r0 *model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.Policy, error)); ok { @@ -184,6 +212,10 @@ func (_m *Controller) GetPolicy(ctx context.Context, id int64) (*model.Policy, e func (_m *Controller) GetRelatedPolices(ctx context.Context, projectID int64, eventType string) ([]*model.Policy, error) { ret := _m.Called(ctx, projectID, eventType) + if len(ret) == 0 { + panic("no return value specified for GetRelatedPolices") + } + var r0 []*model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, string) ([]*model.Policy, error)); ok { @@ -210,6 +242,10 @@ func (_m *Controller) GetRelatedPolices(ctx context.Context, projectID int64, ev func (_m *Controller) GetTask(ctx context.Context, taskID int64) (*task.Task, error) { ret := _m.Called(ctx, taskID) + if len(ret) == 0 { + panic("no return value specified for GetTask") + } + var r0 *task.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*task.Task, error)); ok { @@ -236,6 +272,10 @@ func (_m *Controller) GetTask(ctx context.Context, taskID int64) (*task.Task, er func (_m *Controller) GetTaskLog(ctx context.Context, taskID int64) ([]byte, error) { ret := _m.Called(ctx, taskID) + if len(ret) == 0 { + panic("no return value specified for GetTaskLog") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) ([]byte, error)); ok { @@ -262,6 +302,10 @@ func (_m *Controller) GetTaskLog(ctx context.Context, taskID int64) ([]byte, err func (_m *Controller) ListExecutions(ctx context.Context, policyID int64, query *q.Query) ([]*task.Execution, error) { ret := _m.Called(ctx, policyID, query) + if len(ret) == 0 { + panic("no return value specified for ListExecutions") + } + var r0 []*task.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, *q.Query) ([]*task.Execution, error)); ok { @@ -288,6 +332,10 @@ func (_m *Controller) ListExecutions(ctx context.Context, policyID int64, query func (_m *Controller) ListPolicies(ctx context.Context, query *q.Query) ([]*model.Policy, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListPolicies") + } + var r0 []*model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.Policy, error)); ok { @@ -314,6 +362,10 @@ func (_m *Controller) ListPolicies(ctx context.Context, query *q.Query) ([]*mode func (_m *Controller) ListTasks(ctx context.Context, execID int64, query *q.Query) ([]*task.Task, error) { ret := _m.Called(ctx, execID, query) + if len(ret) == 0 { + panic("no return value specified for ListTasks") + } + var r0 []*task.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, *q.Query) ([]*task.Task, error)); ok { @@ -340,6 +392,10 @@ func (_m *Controller) ListTasks(ctx context.Context, execID int64, query *q.Quer func (_m *Controller) UpdatePolicy(ctx context.Context, policy *model.Policy) error { ret := _m.Called(ctx, policy) + if len(ret) == 0 { + panic("no return value specified for UpdatePolicy") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy) error); ok { r0 = rf(ctx, policy) diff --git a/src/testing/lib/cache/cache.go b/src/testing/lib/cache/cache.go index e28a1040a..e4e719d9b 100644 --- a/src/testing/lib/cache/cache.go +++ b/src/testing/lib/cache/cache.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package cache @@ -21,6 +21,10 @@ type Cache struct { func (_m *Cache) Contains(ctx context.Context, key string) bool { ret := _m.Called(ctx, key) + if len(ret) == 0 { + panic("no return value specified for Contains") + } + var r0 bool if rf, ok := ret.Get(0).(func(context.Context, string) bool); ok { r0 = rf(ctx, key) @@ -35,6 +39,10 @@ func (_m *Cache) Contains(ctx context.Context, key string) bool { func (_m *Cache) Delete(ctx context.Context, key string) error { ret := _m.Called(ctx, key) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, key) @@ -49,6 +57,10 @@ func (_m *Cache) Delete(ctx context.Context, key string) error { func (_m *Cache) Fetch(ctx context.Context, key string, value interface{}) error { ret := _m.Called(ctx, key, value) + if len(ret) == 0 { + panic("no return value specified for Fetch") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, interface{}) error); ok { r0 = rf(ctx, key, value) @@ -63,6 +75,10 @@ func (_m *Cache) Fetch(ctx context.Context, key string, value interface{}) error func (_m *Cache) Ping(ctx context.Context) error { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for Ping") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context) error); ok { r0 = rf(ctx) @@ -84,6 +100,10 @@ func (_m *Cache) Save(ctx context.Context, key string, value interface{}, expira _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Save") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, interface{}, ...time.Duration) error); ok { r0 = rf(ctx, key, value, expiration...) @@ -98,6 +118,10 @@ func (_m *Cache) Save(ctx context.Context, key string, value interface{}, expira func (_m *Cache) Scan(ctx context.Context, match string) (cache.Iterator, error) { ret := _m.Called(ctx, match) + if len(ret) == 0 { + panic("no return value specified for Scan") + } + var r0 cache.Iterator var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (cache.Iterator, error)); ok { diff --git a/src/testing/lib/cache/iterator.go b/src/testing/lib/cache/iterator.go index ec7dc6f08..7a80fb382 100644 --- a/src/testing/lib/cache/iterator.go +++ b/src/testing/lib/cache/iterator.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package cache @@ -17,6 +17,10 @@ type Iterator struct { func (_m *Iterator) Next(ctx context.Context) bool { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for Next") + } + var r0 bool if rf, ok := ret.Get(0).(func(context.Context) bool); ok { r0 = rf(ctx) @@ -31,6 +35,10 @@ func (_m *Iterator) Next(ctx context.Context) bool { func (_m *Iterator) Val() string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Val") + } + var r0 string if rf, ok := ret.Get(0).(func() string); ok { r0 = rf() diff --git a/src/testing/lib/config/manager.go b/src/testing/lib/config/manager.go index f04086964..ff6456b5d 100644 --- a/src/testing/lib/config/manager.go +++ b/src/testing/lib/config/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package config @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) Get(ctx context.Context, key string) *metadata.ConfigureValue { ret := _m.Called(ctx, key) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *metadata.ConfigureValue if rf, ok := ret.Get(0).(func(context.Context, string) *metadata.ConfigureValue); ok { r0 = rf(ctx, key) @@ -36,6 +40,10 @@ func (_m *Manager) Get(ctx context.Context, key string) *metadata.ConfigureValue func (_m *Manager) GetAll(ctx context.Context) map[string]interface{} { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for GetAll") + } + var r0 map[string]interface{} if rf, ok := ret.Get(0).(func(context.Context) map[string]interface{}); ok { r0 = rf(ctx) @@ -52,6 +60,10 @@ func (_m *Manager) GetAll(ctx context.Context) map[string]interface{} { func (_m *Manager) GetDatabaseCfg() *models.Database { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for GetDatabaseCfg") + } + var r0 *models.Database if rf, ok := ret.Get(0).(func() *models.Database); ok { r0 = rf() @@ -68,6 +80,10 @@ func (_m *Manager) GetDatabaseCfg() *models.Database { func (_m *Manager) GetUserCfgs(ctx context.Context) map[string]interface{} { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for GetUserCfgs") + } + var r0 map[string]interface{} if rf, ok := ret.Get(0).(func(context.Context) map[string]interface{}); ok { r0 = rf(ctx) @@ -84,6 +100,10 @@ func (_m *Manager) GetUserCfgs(ctx context.Context) map[string]interface{} { func (_m *Manager) Load(ctx context.Context) error { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for Load") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context) error); ok { r0 = rf(ctx) @@ -98,6 +118,10 @@ func (_m *Manager) Load(ctx context.Context) error { func (_m *Manager) Save(ctx context.Context) error { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for Save") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context) error); ok { r0 = rf(ctx) @@ -117,6 +141,10 @@ func (_m *Manager) Set(ctx context.Context, key string, value interface{}) { func (_m *Manager) UpdateConfig(ctx context.Context, cfgs map[string]interface{}) error { ret := _m.Called(ctx, cfgs) + if len(ret) == 0 { + panic("no return value specified for UpdateConfig") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, map[string]interface{}) error); ok { r0 = rf(ctx, cfgs) @@ -131,6 +159,10 @@ func (_m *Manager) UpdateConfig(ctx context.Context, cfgs map[string]interface{} func (_m *Manager) ValidateCfg(ctx context.Context, cfgs map[string]interface{}) error { ret := _m.Called(ctx, cfgs) + if len(ret) == 0 { + panic("no return value specified for ValidateCfg") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, map[string]interface{}) error); ok { r0 = rf(ctx, cfgs) diff --git a/src/testing/lib/orm/creator.go b/src/testing/lib/orm/creator.go index 7844ecaf2..d10e5f918 100644 --- a/src/testing/lib/orm/creator.go +++ b/src/testing/lib/orm/creator.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package orm @@ -16,6 +16,10 @@ type Creator struct { func (_m *Creator) Create() orm.Ormer { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 orm.Ormer if rf, ok := ret.Get(0).(func() orm.Ormer); ok { r0 = rf() diff --git a/src/testing/pkg/accessory/dao/dao.go b/src/testing/pkg/accessory/dao/dao.go index 886ee4f14..d4165cf9b 100644 --- a/src/testing/pkg/accessory/dao/dao.go +++ b/src/testing/pkg/accessory/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -20,6 +20,10 @@ type DAO struct { func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *DAO) Create(ctx context.Context, accessory *dao.Accessory) (int64, error) { ret := _m.Called(ctx, accessory) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *dao.Accessory) (int64, error)); ok { @@ -68,6 +76,10 @@ func (_m *DAO) Create(ctx context.Context, accessory *dao.Accessory) (int64, err func (_m *DAO) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -82,6 +94,10 @@ func (_m *DAO) Delete(ctx context.Context, id int64) error { func (_m *DAO) DeleteAccessories(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for DeleteAccessories") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -106,6 +122,10 @@ func (_m *DAO) DeleteAccessories(ctx context.Context, query *q.Query) (int64, er func (_m *DAO) Get(ctx context.Context, id int64) (*dao.Accessory, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *dao.Accessory var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*dao.Accessory, error)); ok { @@ -132,6 +152,10 @@ func (_m *DAO) Get(ctx context.Context, id int64) (*dao.Accessory, error) { func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*dao.Accessory, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*dao.Accessory var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*dao.Accessory, error)); ok { @@ -158,6 +182,10 @@ func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*dao.Accessory, erro func (_m *DAO) Update(ctx context.Context, accessory *dao.Accessory) error { ret := _m.Called(ctx, accessory) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *dao.Accessory) error); ok { r0 = rf(ctx, accessory) diff --git a/src/testing/pkg/accessory/manager.go b/src/testing/pkg/accessory/manager.go index 9a3312a32..1245311d8 100644 --- a/src/testing/pkg/accessory/manager.go +++ b/src/testing/pkg/accessory/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package accessory @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *Manager) Create(ctx context.Context, _a1 model.AccessoryData) (int64, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, model.AccessoryData) (int64, error)); ok { @@ -68,6 +76,10 @@ func (_m *Manager) Create(ctx context.Context, _a1 model.AccessoryData) (int64, func (_m *Manager) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -82,6 +94,10 @@ func (_m *Manager) Delete(ctx context.Context, id int64) error { func (_m *Manager) DeleteAccessories(ctx context.Context, _a1 *q.Query) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for DeleteAccessories") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) error); ok { r0 = rf(ctx, _a1) @@ -96,6 +112,10 @@ func (_m *Manager) DeleteAccessories(ctx context.Context, _a1 *q.Query) error { func (_m *Manager) Ensure(ctx context.Context, subArtDigest string, subArtRepo string, subArtID int64, artifactID int64, size int64, digest string, accType string) error { ret := _m.Called(ctx, subArtDigest, subArtRepo, subArtID, artifactID, size, digest, accType) + if len(ret) == 0 { + panic("no return value specified for Ensure") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, int64, int64, int64, string, string) error); ok { r0 = rf(ctx, subArtDigest, subArtRepo, subArtID, artifactID, size, digest, accType) @@ -110,6 +130,10 @@ func (_m *Manager) Ensure(ctx context.Context, subArtDigest string, subArtRepo s func (_m *Manager) Get(ctx context.Context, id int64) (model.Accessory, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 model.Accessory var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (model.Accessory, error)); ok { @@ -136,6 +160,10 @@ func (_m *Manager) Get(ctx context.Context, id int64) (model.Accessory, error) { func (_m *Manager) List(ctx context.Context, query *q.Query) ([]model.Accessory, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []model.Accessory var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]model.Accessory, error)); ok { @@ -162,6 +190,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]model.Accessory, func (_m *Manager) Update(ctx context.Context, _a1 model.AccessoryData) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, model.AccessoryData) error); ok { r0 = rf(ctx, _a1) diff --git a/src/testing/pkg/accessory/model/accessory.go b/src/testing/pkg/accessory/model/accessory.go index 04d4dad65..a33371896 100644 --- a/src/testing/pkg/accessory/model/accessory.go +++ b/src/testing/pkg/accessory/model/accessory.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package model @@ -16,6 +16,10 @@ type Accessory struct { func (_m *Accessory) Display() bool { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Display") + } + var r0 bool if rf, ok := ret.Get(0).(func() bool); ok { r0 = rf() @@ -30,6 +34,10 @@ func (_m *Accessory) Display() bool { func (_m *Accessory) GetData() model.AccessoryData { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for GetData") + } + var r0 model.AccessoryData if rf, ok := ret.Get(0).(func() model.AccessoryData); ok { r0 = rf() @@ -44,6 +52,10 @@ func (_m *Accessory) GetData() model.AccessoryData { func (_m *Accessory) IsHard() bool { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for IsHard") + } + var r0 bool if rf, ok := ret.Get(0).(func() bool); ok { r0 = rf() @@ -58,6 +70,10 @@ func (_m *Accessory) IsHard() bool { func (_m *Accessory) IsSoft() bool { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for IsSoft") + } + var r0 bool if rf, ok := ret.Get(0).(func() bool); ok { r0 = rf() @@ -72,6 +88,10 @@ func (_m *Accessory) IsSoft() bool { func (_m *Accessory) Kind() string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Kind") + } + var r0 string if rf, ok := ret.Get(0).(func() string); ok { r0 = rf() diff --git a/src/testing/pkg/allowlist/dao/dao.go b/src/testing/pkg/allowlist/dao/dao.go index d818c5e57..a8ebfec10 100644 --- a/src/testing/pkg/allowlist/dao/dao.go +++ b/src/testing/pkg/allowlist/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -19,6 +19,10 @@ type DAO struct { func (_m *DAO) QueryByProjectID(ctx context.Context, pid int64) (*models.CVEAllowlist, error) { ret := _m.Called(ctx, pid) + if len(ret) == 0 { + panic("no return value specified for QueryByProjectID") + } + var r0 *models.CVEAllowlist var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*models.CVEAllowlist, error)); ok { @@ -45,6 +49,10 @@ func (_m *DAO) QueryByProjectID(ctx context.Context, pid int64) (*models.CVEAllo func (_m *DAO) Set(ctx context.Context, l models.CVEAllowlist) (int64, error) { ret := _m.Called(ctx, l) + if len(ret) == 0 { + panic("no return value specified for Set") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, models.CVEAllowlist) (int64, error)); ok { diff --git a/src/testing/pkg/allowlist/manager.go b/src/testing/pkg/allowlist/manager.go index a3c7dc30e..ecb1b4115 100644 --- a/src/testing/pkg/allowlist/manager.go +++ b/src/testing/pkg/allowlist/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package robot @@ -18,6 +18,10 @@ type Manager struct { func (_m *Manager) CreateEmpty(ctx context.Context, projectID int64) error { ret := _m.Called(ctx, projectID) + if len(ret) == 0 { + panic("no return value specified for CreateEmpty") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, projectID) @@ -32,6 +36,10 @@ func (_m *Manager) CreateEmpty(ctx context.Context, projectID int64) error { func (_m *Manager) Get(ctx context.Context, projectID int64) (*models.CVEAllowlist, error) { ret := _m.Called(ctx, projectID) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *models.CVEAllowlist var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*models.CVEAllowlist, error)); ok { @@ -58,6 +66,10 @@ func (_m *Manager) Get(ctx context.Context, projectID int64) (*models.CVEAllowli func (_m *Manager) GetSys(ctx context.Context) (*models.CVEAllowlist, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for GetSys") + } + var r0 *models.CVEAllowlist var r1 error if rf, ok := ret.Get(0).(func(context.Context) (*models.CVEAllowlist, error)); ok { @@ -84,6 +96,10 @@ func (_m *Manager) GetSys(ctx context.Context) (*models.CVEAllowlist, error) { func (_m *Manager) Set(ctx context.Context, projectID int64, list models.CVEAllowlist) error { ret := _m.Called(ctx, projectID, list) + if len(ret) == 0 { + panic("no return value specified for Set") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, models.CVEAllowlist) error); ok { r0 = rf(ctx, projectID, list) @@ -98,6 +114,10 @@ func (_m *Manager) Set(ctx context.Context, projectID int64, list models.CVEAllo func (_m *Manager) SetSys(ctx context.Context, list models.CVEAllowlist) error { ret := _m.Called(ctx, list) + if len(ret) == 0 { + panic("no return value specified for SetSys") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, models.CVEAllowlist) error); ok { r0 = rf(ctx, list) diff --git a/src/testing/pkg/artifact/manager.go b/src/testing/pkg/artifact/manager.go index fb246aebd..bf19a0b60 100644 --- a/src/testing/pkg/artifact/manager.go +++ b/src/testing/pkg/artifact/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package artifact @@ -23,6 +23,10 @@ type Manager struct { func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -47,6 +51,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *Manager) Create(ctx context.Context, _a1 *artifact.Artifact) (int64, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact) (int64, error)); ok { @@ -71,6 +79,10 @@ func (_m *Manager) Create(ctx context.Context, _a1 *artifact.Artifact) (int64, e func (_m *Manager) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -85,6 +97,10 @@ func (_m *Manager) Delete(ctx context.Context, id int64) error { func (_m *Manager) DeleteReference(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for DeleteReference") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -99,6 +115,10 @@ func (_m *Manager) DeleteReference(ctx context.Context, id int64) error { func (_m *Manager) Get(ctx context.Context, id int64) (*artifact.Artifact, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *artifact.Artifact var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*artifact.Artifact, error)); ok { @@ -125,6 +145,10 @@ func (_m *Manager) Get(ctx context.Context, id int64) (*artifact.Artifact, error func (_m *Manager) GetByDigest(ctx context.Context, repository string, digest string) (*artifact.Artifact, error) { ret := _m.Called(ctx, repository, digest) + if len(ret) == 0 { + panic("no return value specified for GetByDigest") + } + var r0 *artifact.Artifact var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (*artifact.Artifact, error)); ok { @@ -151,6 +175,10 @@ func (_m *Manager) GetByDigest(ctx context.Context, repository string, digest st func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*artifact.Artifact, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*artifact.Artifact var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*artifact.Artifact, error)); ok { @@ -177,6 +205,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*artifact.Artifa func (_m *Manager) ListReferences(ctx context.Context, query *q.Query) ([]*artifact.Reference, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListReferences") + } + var r0 []*artifact.Reference var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*artifact.Reference, error)); ok { @@ -210,6 +242,10 @@ func (_m *Manager) Update(ctx context.Context, _a1 *artifact.Artifact, props ... _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *artifact.Artifact, ...string) error); ok { r0 = rf(ctx, _a1, props...) @@ -224,6 +260,10 @@ func (_m *Manager) Update(ctx context.Context, _a1 *artifact.Artifact, props ... func (_m *Manager) UpdatePullTime(ctx context.Context, id int64, pullTime time.Time) error { ret := _m.Called(ctx, id, pullTime) + if len(ret) == 0 { + panic("no return value specified for UpdatePullTime") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, time.Time) error); ok { r0 = rf(ctx, id, pullTime) diff --git a/src/testing/pkg/audit/dao/dao.go b/src/testing/pkg/audit/dao/dao.go index 97665fdaa..74dd03aaa 100644 --- a/src/testing/pkg/audit/dao/dao.go +++ b/src/testing/pkg/audit/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -21,6 +21,10 @@ type DAO struct { func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -45,6 +49,10 @@ func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *DAO) Create(ctx context.Context, access *model.AuditLog) (int64, error) { ret := _m.Called(ctx, access) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.AuditLog) (int64, error)); ok { @@ -69,6 +77,10 @@ func (_m *DAO) Create(ctx context.Context, access *model.AuditLog) (int64, error func (_m *DAO) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -83,6 +95,10 @@ func (_m *DAO) Delete(ctx context.Context, id int64) error { func (_m *DAO) Get(ctx context.Context, id int64) (*model.AuditLog, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.AuditLog var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.AuditLog, error)); ok { @@ -109,6 +125,10 @@ func (_m *DAO) Get(ctx context.Context, id int64) (*model.AuditLog, error) { func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.AuditLog, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.AuditLog var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.AuditLog, error)); ok { @@ -135,6 +155,10 @@ func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.AuditLog, err func (_m *DAO) Purge(ctx context.Context, retentionHour int, includeOperations []string, dryRun bool) (int64, error) { ret := _m.Called(ctx, retentionHour, includeOperations, dryRun) + if len(ret) == 0 { + panic("no return value specified for Purge") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int, []string, bool) (int64, error)); ok { @@ -159,6 +183,10 @@ func (_m *DAO) Purge(ctx context.Context, retentionHour int, includeOperations [ func (_m *DAO) UpdateUsername(ctx context.Context, username string, usernameReplace string) error { ret := _m.Called(ctx, username, usernameReplace) + if len(ret) == 0 { + panic("no return value specified for UpdateUsername") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { r0 = rf(ctx, username, usernameReplace) diff --git a/src/testing/pkg/audit/manager.go b/src/testing/pkg/audit/manager.go index 1efcd06df..887996c0b 100644 --- a/src/testing/pkg/audit/manager.go +++ b/src/testing/pkg/audit/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package audit @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *Manager) Create(ctx context.Context, _a1 *model.AuditLog) (int64, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.AuditLog) (int64, error)); ok { @@ -68,6 +76,10 @@ func (_m *Manager) Create(ctx context.Context, _a1 *model.AuditLog) (int64, erro func (_m *Manager) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -82,6 +94,10 @@ func (_m *Manager) Delete(ctx context.Context, id int64) error { func (_m *Manager) Get(ctx context.Context, id int64) (*model.AuditLog, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.AuditLog var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.AuditLog, error)); ok { @@ -108,6 +124,10 @@ func (_m *Manager) Get(ctx context.Context, id int64) (*model.AuditLog, error) { func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.AuditLog, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.AuditLog var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.AuditLog, error)); ok { @@ -134,6 +154,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.AuditLog, func (_m *Manager) Purge(ctx context.Context, retentionHour int, includeOperations []string, dryRun bool) (int64, error) { ret := _m.Called(ctx, retentionHour, includeOperations, dryRun) + if len(ret) == 0 { + panic("no return value specified for Purge") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int, []string, bool) (int64, error)); ok { @@ -158,6 +182,10 @@ func (_m *Manager) Purge(ctx context.Context, retentionHour int, includeOperatio func (_m *Manager) UpdateUsername(ctx context.Context, username string, replaceWith string) error { ret := _m.Called(ctx, username, replaceWith) + if len(ret) == 0 { + panic("no return value specified for UpdateUsername") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { r0 = rf(ctx, username, replaceWith) diff --git a/src/testing/pkg/blob/manager.go b/src/testing/pkg/blob/manager.go index 3edba7fbb..8308e4aaf 100644 --- a/src/testing/pkg/blob/manager.go +++ b/src/testing/pkg/blob/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package blob @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) AssociateWithArtifact(ctx context.Context, blobDigest string, artifactDigest string) (int64, error) { ret := _m.Called(ctx, blobDigest, artifactDigest) + if len(ret) == 0 { + panic("no return value specified for AssociateWithArtifact") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Manager) AssociateWithArtifact(ctx context.Context, blobDigest string, func (_m *Manager) AssociateWithProject(ctx context.Context, blobID int64, projectID int64) (int64, error) { ret := _m.Called(ctx, blobID, projectID) + if len(ret) == 0 { + panic("no return value specified for AssociateWithProject") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, int64) (int64, error)); ok { @@ -68,6 +76,10 @@ func (_m *Manager) AssociateWithProject(ctx context.Context, blobID int64, proje func (_m *Manager) CalculateTotalSize(ctx context.Context, excludeForeignLayer bool) (int64, error) { ret := _m.Called(ctx, excludeForeignLayer) + if len(ret) == 0 { + panic("no return value specified for CalculateTotalSize") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, bool) (int64, error)); ok { @@ -92,6 +104,10 @@ func (_m *Manager) CalculateTotalSize(ctx context.Context, excludeForeignLayer b func (_m *Manager) CalculateTotalSizeByProject(ctx context.Context, projectID int64, excludeForeignLayer bool) (int64, error) { ret := _m.Called(ctx, projectID, excludeForeignLayer) + if len(ret) == 0 { + panic("no return value specified for CalculateTotalSizeByProject") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, bool) (int64, error)); ok { @@ -116,6 +132,10 @@ func (_m *Manager) CalculateTotalSizeByProject(ctx context.Context, projectID in func (_m *Manager) CleanupAssociationsForArtifact(ctx context.Context, artifactDigest string) error { ret := _m.Called(ctx, artifactDigest) + if len(ret) == 0 { + panic("no return value specified for CleanupAssociationsForArtifact") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, artifactDigest) @@ -130,6 +150,10 @@ func (_m *Manager) CleanupAssociationsForArtifact(ctx context.Context, artifactD func (_m *Manager) CleanupAssociationsForProject(ctx context.Context, projectID int64, blobs []*models.Blob) error { ret := _m.Called(ctx, projectID, blobs) + if len(ret) == 0 { + panic("no return value specified for CleanupAssociationsForProject") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, []*models.Blob) error); ok { r0 = rf(ctx, projectID, blobs) @@ -144,6 +168,10 @@ func (_m *Manager) CleanupAssociationsForProject(ctx context.Context, projectID func (_m *Manager) Create(ctx context.Context, digest string, contentType string, size int64) (int64, error) { ret := _m.Called(ctx, digest, contentType, size) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) (int64, error)); ok { @@ -168,6 +196,10 @@ func (_m *Manager) Create(ctx context.Context, digest string, contentType string func (_m *Manager) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -182,6 +214,10 @@ func (_m *Manager) Delete(ctx context.Context, id int64) error { func (_m *Manager) FindBlobsShouldUnassociatedWithProject(ctx context.Context, projectID int64, blobs []*models.Blob) ([]*models.Blob, error) { ret := _m.Called(ctx, projectID, blobs) + if len(ret) == 0 { + panic("no return value specified for FindBlobsShouldUnassociatedWithProject") + } + var r0 []*models.Blob var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, []*models.Blob) ([]*models.Blob, error)); ok { @@ -208,6 +244,10 @@ func (_m *Manager) FindBlobsShouldUnassociatedWithProject(ctx context.Context, p func (_m *Manager) Get(ctx context.Context, digest string) (*models.Blob, error) { ret := _m.Called(ctx, digest) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *models.Blob var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*models.Blob, error)); ok { @@ -234,6 +274,10 @@ func (_m *Manager) Get(ctx context.Context, digest string) (*models.Blob, error) func (_m *Manager) GetByArt(ctx context.Context, digest string) ([]*models.Blob, error) { ret := _m.Called(ctx, digest) + if len(ret) == 0 { + panic("no return value specified for GetByArt") + } + var r0 []*models.Blob var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]*models.Blob, error)); ok { @@ -260,6 +304,10 @@ func (_m *Manager) GetByArt(ctx context.Context, digest string) ([]*models.Blob, func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*models.Blob, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*models.Blob var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*models.Blob, error)); ok { @@ -286,6 +334,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*models.Blob, er func (_m *Manager) Update(ctx context.Context, _a1 *models.Blob) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.Blob) error); ok { r0 = rf(ctx, _a1) @@ -300,6 +352,10 @@ func (_m *Manager) Update(ctx context.Context, _a1 *models.Blob) error { func (_m *Manager) UpdateBlobStatus(ctx context.Context, _a1 *models.Blob) (int64, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for UpdateBlobStatus") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *models.Blob) (int64, error)); ok { @@ -324,6 +380,10 @@ func (_m *Manager) UpdateBlobStatus(ctx context.Context, _a1 *models.Blob) (int6 func (_m *Manager) UselessBlobs(ctx context.Context, timeWindowHours int64) ([]*models.Blob, error) { ret := _m.Called(ctx, timeWindowHours) + if len(ret) == 0 { + panic("no return value specified for UselessBlobs") + } + var r0 []*models.Blob var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) ([]*models.Blob, error)); ok { diff --git a/src/testing/pkg/cached/manifest/redis/cached_manager.go b/src/testing/pkg/cached/manifest/redis/cached_manager.go index 6ae99bb96..033e96aca 100644 --- a/src/testing/pkg/cached/manifest/redis/cached_manager.go +++ b/src/testing/pkg/cached/manifest/redis/cached_manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package redis @@ -19,6 +19,10 @@ type CachedManager struct { func (_m *CachedManager) CacheClient(ctx context.Context) cache.Cache { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for CacheClient") + } + var r0 cache.Cache if rf, ok := ret.Get(0).(func(context.Context) cache.Cache); ok { r0 = rf(ctx) @@ -35,6 +39,10 @@ func (_m *CachedManager) CacheClient(ctx context.Context) cache.Cache { func (_m *CachedManager) CountCache(ctx context.Context) (int64, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for CountCache") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context) (int64, error)); ok { @@ -59,6 +67,10 @@ func (_m *CachedManager) CountCache(ctx context.Context) (int64, error) { func (_m *CachedManager) Delete(ctx context.Context, digest string) error { ret := _m.Called(ctx, digest) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, digest) @@ -73,6 +85,10 @@ func (_m *CachedManager) Delete(ctx context.Context, digest string) error { func (_m *CachedManager) DeleteCache(ctx context.Context, key string) error { ret := _m.Called(ctx, key) + if len(ret) == 0 { + panic("no return value specified for DeleteCache") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, key) @@ -87,6 +103,10 @@ func (_m *CachedManager) DeleteCache(ctx context.Context, key string) error { func (_m *CachedManager) FlushAll(ctx context.Context) error { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for FlushAll") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context) error); ok { r0 = rf(ctx) @@ -101,6 +121,10 @@ func (_m *CachedManager) FlushAll(ctx context.Context) error { func (_m *CachedManager) Get(ctx context.Context, digest string) ([]byte, error) { ret := _m.Called(ctx, digest) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]byte, error)); ok { @@ -127,6 +151,10 @@ func (_m *CachedManager) Get(ctx context.Context, digest string) ([]byte, error) func (_m *CachedManager) ResourceType(ctx context.Context) string { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for ResourceType") + } + var r0 string if rf, ok := ret.Get(0).(func(context.Context) string); ok { r0 = rf(ctx) @@ -141,6 +169,10 @@ func (_m *CachedManager) ResourceType(ctx context.Context) string { func (_m *CachedManager) Save(ctx context.Context, digest string, manifest []byte) error { ret := _m.Called(ctx, digest, manifest) + if len(ret) == 0 { + panic("no return value specified for Save") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, []byte) error); ok { r0 = rf(ctx, digest, manifest) diff --git a/src/testing/pkg/immutable/dao/dao.go b/src/testing/pkg/immutable/dao/dao.go index a3e841e6f..8ad5aac0f 100644 --- a/src/testing/pkg/immutable/dao/dao.go +++ b/src/testing/pkg/immutable/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -21,6 +21,10 @@ type DAO struct { func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -45,6 +49,10 @@ func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *DAO) CreateImmutableRule(ctx context.Context, ir *model.ImmutableRule) (int64, error) { ret := _m.Called(ctx, ir) + if len(ret) == 0 { + panic("no return value specified for CreateImmutableRule") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.ImmutableRule) (int64, error)); ok { @@ -69,6 +77,10 @@ func (_m *DAO) CreateImmutableRule(ctx context.Context, ir *model.ImmutableRule) func (_m *DAO) DeleteImmutableRule(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for DeleteImmutableRule") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -83,6 +95,10 @@ func (_m *DAO) DeleteImmutableRule(ctx context.Context, id int64) error { func (_m *DAO) GetImmutableRule(ctx context.Context, id int64) (*model.ImmutableRule, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for GetImmutableRule") + } + var r0 *model.ImmutableRule var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.ImmutableRule, error)); ok { @@ -109,6 +125,10 @@ func (_m *DAO) GetImmutableRule(ctx context.Context, id int64) (*model.Immutable func (_m *DAO) ListImmutableRules(ctx context.Context, query *q.Query) ([]*model.ImmutableRule, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListImmutableRules") + } + var r0 []*model.ImmutableRule var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.ImmutableRule, error)); ok { @@ -135,6 +155,10 @@ func (_m *DAO) ListImmutableRules(ctx context.Context, query *q.Query) ([]*model func (_m *DAO) ToggleImmutableRule(ctx context.Context, id int64, status bool) error { ret := _m.Called(ctx, id, status) + if len(ret) == 0 { + panic("no return value specified for ToggleImmutableRule") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, bool) error); ok { r0 = rf(ctx, id, status) @@ -149,6 +173,10 @@ func (_m *DAO) ToggleImmutableRule(ctx context.Context, id int64, status bool) e func (_m *DAO) UpdateImmutableRule(ctx context.Context, projectID int64, ir *model.ImmutableRule) error { ret := _m.Called(ctx, projectID, ir) + if len(ret) == 0 { + panic("no return value specified for UpdateImmutableRule") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, *model.ImmutableRule) error); ok { r0 = rf(ctx, projectID, ir) diff --git a/src/testing/pkg/joblog/dao/dao.go b/src/testing/pkg/joblog/dao/dao.go index 37fd4b9b0..353d2c168 100644 --- a/src/testing/pkg/joblog/dao/dao.go +++ b/src/testing/pkg/joblog/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -21,6 +21,10 @@ type DAO struct { func (_m *DAO) Create(ctx context.Context, jobLog *models.JobLog) (int64, error) { ret := _m.Called(ctx, jobLog) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *models.JobLog) (int64, error)); ok { @@ -45,6 +49,10 @@ func (_m *DAO) Create(ctx context.Context, jobLog *models.JobLog) (int64, error) func (_m *DAO) DeleteBefore(ctx context.Context, t time.Time) (int64, error) { ret := _m.Called(ctx, t) + if len(ret) == 0 { + panic("no return value specified for DeleteBefore") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, time.Time) (int64, error)); ok { @@ -69,6 +77,10 @@ func (_m *DAO) DeleteBefore(ctx context.Context, t time.Time) (int64, error) { func (_m *DAO) Get(ctx context.Context, uuid string) (*models.JobLog, error) { ret := _m.Called(ctx, uuid) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *models.JobLog var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*models.JobLog, error)); ok { diff --git a/src/testing/pkg/joblog/manager.go b/src/testing/pkg/joblog/manager.go index 103fb0734..d00e413c9 100644 --- a/src/testing/pkg/joblog/manager.go +++ b/src/testing/pkg/joblog/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package joblog @@ -21,6 +21,10 @@ type Manager struct { func (_m *Manager) Create(ctx context.Context, jobLog *models.JobLog) (int64, error) { ret := _m.Called(ctx, jobLog) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *models.JobLog) (int64, error)); ok { @@ -45,6 +49,10 @@ func (_m *Manager) Create(ctx context.Context, jobLog *models.JobLog) (int64, er func (_m *Manager) DeleteBefore(ctx context.Context, t time.Time) (int64, error) { ret := _m.Called(ctx, t) + if len(ret) == 0 { + panic("no return value specified for DeleteBefore") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, time.Time) (int64, error)); ok { @@ -69,6 +77,10 @@ func (_m *Manager) DeleteBefore(ctx context.Context, t time.Time) (int64, error) func (_m *Manager) Get(ctx context.Context, uuid string) (*models.JobLog, error) { ret := _m.Called(ctx, uuid) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *models.JobLog var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*models.JobLog, error)); ok { diff --git a/src/testing/pkg/jobmonitor/job_service_monitor_client.go b/src/testing/pkg/jobmonitor/job_service_monitor_client.go index 569357d4b..d528776fc 100644 --- a/src/testing/pkg/jobmonitor/job_service_monitor_client.go +++ b/src/testing/pkg/jobmonitor/job_service_monitor_client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package jobmonitor @@ -16,6 +16,10 @@ type JobServiceMonitorClient struct { func (_m *JobServiceMonitorClient) Queues() ([]*work.Queue, error) { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Queues") + } + var r0 []*work.Queue var r1 error if rf, ok := ret.Get(0).(func() ([]*work.Queue, error)); ok { @@ -42,6 +46,10 @@ func (_m *JobServiceMonitorClient) Queues() ([]*work.Queue, error) { func (_m *JobServiceMonitorClient) WorkerObservations() ([]*work.WorkerObservation, error) { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for WorkerObservations") + } + var r0 []*work.WorkerObservation var r1 error if rf, ok := ret.Get(0).(func() ([]*work.WorkerObservation, error)); ok { @@ -68,6 +76,10 @@ func (_m *JobServiceMonitorClient) WorkerObservations() ([]*work.WorkerObservati func (_m *JobServiceMonitorClient) WorkerPoolHeartbeats() ([]*work.WorkerPoolHeartbeat, error) { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for WorkerPoolHeartbeats") + } + var r0 []*work.WorkerPoolHeartbeat var r1 error if rf, ok := ret.Get(0).(func() ([]*work.WorkerPoolHeartbeat, error)); ok { diff --git a/src/testing/pkg/jobmonitor/pool_manager.go b/src/testing/pkg/jobmonitor/pool_manager.go index 7b41ac6d8..51dc4fbf9 100644 --- a/src/testing/pkg/jobmonitor/pool_manager.go +++ b/src/testing/pkg/jobmonitor/pool_manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package jobmonitor @@ -18,6 +18,10 @@ type PoolManager struct { func (_m *PoolManager) List(ctx context.Context, monitorClient jobmonitor.JobServiceMonitorClient) ([]*jobmonitor.WorkerPool, error) { ret := _m.Called(ctx, monitorClient) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*jobmonitor.WorkerPool var r1 error if rf, ok := ret.Get(0).(func(context.Context, jobmonitor.JobServiceMonitorClient) ([]*jobmonitor.WorkerPool, error)); ok { diff --git a/src/testing/pkg/jobmonitor/queue_manager.go b/src/testing/pkg/jobmonitor/queue_manager.go index de7862bae..bce10ef38 100644 --- a/src/testing/pkg/jobmonitor/queue_manager.go +++ b/src/testing/pkg/jobmonitor/queue_manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package jobmonitor @@ -18,6 +18,10 @@ type QueueManager struct { func (_m *QueueManager) List(ctx context.Context, monitClient jobmonitor.JobServiceMonitorClient) ([]*jobmonitor.Queue, error) { ret := _m.Called(ctx, monitClient) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*jobmonitor.Queue var r1 error if rf, ok := ret.Get(0).(func(context.Context, jobmonitor.JobServiceMonitorClient) ([]*jobmonitor.Queue, error)); ok { diff --git a/src/testing/pkg/jobmonitor/redis_client.go b/src/testing/pkg/jobmonitor/redis_client.go index bf1c5c4e4..5a7f91837 100644 --- a/src/testing/pkg/jobmonitor/redis_client.go +++ b/src/testing/pkg/jobmonitor/redis_client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package jobmonitor @@ -17,6 +17,10 @@ type RedisClient struct { func (_m *RedisClient) AllJobTypes(ctx context.Context) ([]string, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for AllJobTypes") + } + var r0 []string var r1 error if rf, ok := ret.Get(0).(func(context.Context) ([]string, error)); ok { @@ -43,6 +47,10 @@ func (_m *RedisClient) AllJobTypes(ctx context.Context) ([]string, error) { func (_m *RedisClient) PauseJob(ctx context.Context, jobName string) error { ret := _m.Called(ctx, jobName) + if len(ret) == 0 { + panic("no return value specified for PauseJob") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, jobName) @@ -57,6 +65,10 @@ func (_m *RedisClient) PauseJob(ctx context.Context, jobName string) error { func (_m *RedisClient) StopPendingJobs(ctx context.Context, jobType string) ([]string, error) { ret := _m.Called(ctx, jobType) + if len(ret) == 0 { + panic("no return value specified for StopPendingJobs") + } + var r0 []string var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]string, error)); ok { @@ -83,6 +95,10 @@ func (_m *RedisClient) StopPendingJobs(ctx context.Context, jobType string) ([]s func (_m *RedisClient) UnpauseJob(ctx context.Context, jobName string) error { ret := _m.Called(ctx, jobName) + if len(ret) == 0 { + panic("no return value specified for UnpauseJob") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, jobName) diff --git a/src/testing/pkg/jobmonitor/worker_manager.go b/src/testing/pkg/jobmonitor/worker_manager.go index 35563d24b..d6f136f2a 100644 --- a/src/testing/pkg/jobmonitor/worker_manager.go +++ b/src/testing/pkg/jobmonitor/worker_manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package jobmonitor @@ -18,6 +18,10 @@ type WorkerManager struct { func (_m *WorkerManager) List(ctx context.Context, monitClient jobmonitor.JobServiceMonitorClient, poolID string) ([]*jobmonitor.Worker, error) { ret := _m.Called(ctx, monitClient, poolID) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*jobmonitor.Worker var r1 error if rf, ok := ret.Get(0).(func(context.Context, jobmonitor.JobServiceMonitorClient, string) ([]*jobmonitor.Worker, error)); ok { diff --git a/src/testing/pkg/label/dao/dao.go b/src/testing/pkg/label/dao/dao.go index 71b64de51..63cb4525b 100644 --- a/src/testing/pkg/label/dao/dao.go +++ b/src/testing/pkg/label/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -21,6 +21,10 @@ type DAO struct { func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -45,6 +49,10 @@ func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *DAO) Create(ctx context.Context, label *model.Label) (int64, error) { ret := _m.Called(ctx, label) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Label) (int64, error)); ok { @@ -69,6 +77,10 @@ func (_m *DAO) Create(ctx context.Context, label *model.Label) (int64, error) { func (_m *DAO) CreateReference(ctx context.Context, reference *model.Reference) (int64, error) { ret := _m.Called(ctx, reference) + if len(ret) == 0 { + panic("no return value specified for CreateReference") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Reference) (int64, error)); ok { @@ -93,6 +105,10 @@ func (_m *DAO) CreateReference(ctx context.Context, reference *model.Reference) func (_m *DAO) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -107,6 +123,10 @@ func (_m *DAO) Delete(ctx context.Context, id int64) error { func (_m *DAO) DeleteReference(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for DeleteReference") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -121,6 +141,10 @@ func (_m *DAO) DeleteReference(ctx context.Context, id int64) error { func (_m *DAO) DeleteReferences(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for DeleteReferences") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -145,6 +169,10 @@ func (_m *DAO) DeleteReferences(ctx context.Context, query *q.Query) (int64, err func (_m *DAO) Get(ctx context.Context, id int64) (*model.Label, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.Label var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.Label, error)); ok { @@ -171,6 +199,10 @@ func (_m *DAO) Get(ctx context.Context, id int64) (*model.Label, error) { func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.Label, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.Label var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.Label, error)); ok { @@ -197,6 +229,10 @@ func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.Label, error) func (_m *DAO) ListByArtifact(ctx context.Context, artifactID int64) ([]*model.Label, error) { ret := _m.Called(ctx, artifactID) + if len(ret) == 0 { + panic("no return value specified for ListByArtifact") + } + var r0 []*model.Label var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) ([]*model.Label, error)); ok { @@ -223,6 +259,10 @@ func (_m *DAO) ListByArtifact(ctx context.Context, artifactID int64) ([]*model.L func (_m *DAO) Update(ctx context.Context, label *model.Label) error { ret := _m.Called(ctx, label) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Label) error); ok { r0 = rf(ctx, label) diff --git a/src/testing/pkg/label/manager.go b/src/testing/pkg/label/manager.go index 9fbd4727d..73c722944 100644 --- a/src/testing/pkg/label/manager.go +++ b/src/testing/pkg/label/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package label @@ -21,6 +21,10 @@ type Manager struct { func (_m *Manager) AddTo(ctx context.Context, labelID int64, artifactID int64) error { ret := _m.Called(ctx, labelID, artifactID) + if len(ret) == 0 { + panic("no return value specified for AddTo") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok { r0 = rf(ctx, labelID, artifactID) @@ -35,6 +39,10 @@ func (_m *Manager) AddTo(ctx context.Context, labelID int64, artifactID int64) e func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -59,6 +67,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *Manager) Create(ctx context.Context, _a1 *model.Label) (int64, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Label) (int64, error)); ok { @@ -83,6 +95,10 @@ func (_m *Manager) Create(ctx context.Context, _a1 *model.Label) (int64, error) func (_m *Manager) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -97,6 +113,10 @@ func (_m *Manager) Delete(ctx context.Context, id int64) error { func (_m *Manager) Get(ctx context.Context, id int64) (*model.Label, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.Label var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.Label, error)); ok { @@ -123,6 +143,10 @@ func (_m *Manager) Get(ctx context.Context, id int64) (*model.Label, error) { func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.Label, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.Label var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.Label, error)); ok { @@ -149,6 +173,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.Label, er func (_m *Manager) ListByArtifact(ctx context.Context, artifactID int64) ([]*model.Label, error) { ret := _m.Called(ctx, artifactID) + if len(ret) == 0 { + panic("no return value specified for ListByArtifact") + } + var r0 []*model.Label var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) ([]*model.Label, error)); ok { @@ -175,6 +203,10 @@ func (_m *Manager) ListByArtifact(ctx context.Context, artifactID int64) ([]*mod func (_m *Manager) RemoveAllFrom(ctx context.Context, artifactID int64) error { ret := _m.Called(ctx, artifactID) + if len(ret) == 0 { + panic("no return value specified for RemoveAllFrom") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, artifactID) @@ -189,6 +221,10 @@ func (_m *Manager) RemoveAllFrom(ctx context.Context, artifactID int64) error { func (_m *Manager) RemoveFrom(ctx context.Context, labelID int64, artifactID int64) error { ret := _m.Called(ctx, labelID, artifactID) + if len(ret) == 0 { + panic("no return value specified for RemoveFrom") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok { r0 = rf(ctx, labelID, artifactID) @@ -203,6 +239,10 @@ func (_m *Manager) RemoveFrom(ctx context.Context, labelID int64, artifactID int func (_m *Manager) RemoveFromAllArtifacts(ctx context.Context, labelID int64) error { ret := _m.Called(ctx, labelID) + if len(ret) == 0 { + panic("no return value specified for RemoveFromAllArtifacts") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, labelID) @@ -217,6 +257,10 @@ func (_m *Manager) RemoveFromAllArtifacts(ctx context.Context, labelID int64) er func (_m *Manager) Update(ctx context.Context, _a1 *model.Label) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Label) error); ok { r0 = rf(ctx, _a1) diff --git a/src/testing/pkg/ldap/manager.go b/src/testing/pkg/ldap/manager.go index 4303c79bf..de682ae5d 100644 --- a/src/testing/pkg/ldap/manager.go +++ b/src/testing/pkg/ldap/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package ldap @@ -22,6 +22,10 @@ type Manager struct { func (_m *Manager) ImportUser(ctx context.Context, sess *ldap.Session, ldapImportUsers []string) ([]model.FailedImportUser, error) { ret := _m.Called(ctx, sess, ldapImportUsers) + if len(ret) == 0 { + panic("no return value specified for ImportUser") + } + var r0 []model.FailedImportUser var r1 error if rf, ok := ret.Get(0).(func(context.Context, *ldap.Session, []string) ([]model.FailedImportUser, error)); ok { @@ -48,6 +52,10 @@ func (_m *Manager) ImportUser(ctx context.Context, sess *ldap.Session, ldapImpor func (_m *Manager) Ping(ctx context.Context, cfg models.LdapConf) (bool, error) { ret := _m.Called(ctx, cfg) + if len(ret) == 0 { + panic("no return value specified for Ping") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, models.LdapConf) (bool, error)); ok { @@ -72,6 +80,10 @@ func (_m *Manager) Ping(ctx context.Context, cfg models.LdapConf) (bool, error) func (_m *Manager) SearchGroup(ctx context.Context, sess *ldap.Session, groupName string, groupDN string) ([]model.Group, error) { ret := _m.Called(ctx, sess, groupName, groupDN) + if len(ret) == 0 { + panic("no return value specified for SearchGroup") + } + var r0 []model.Group var r1 error if rf, ok := ret.Get(0).(func(context.Context, *ldap.Session, string, string) ([]model.Group, error)); ok { @@ -98,6 +110,10 @@ func (_m *Manager) SearchGroup(ctx context.Context, sess *ldap.Session, groupNam func (_m *Manager) SearchUser(ctx context.Context, sess *ldap.Session, username string) ([]model.User, error) { ret := _m.Called(ctx, sess, username) + if len(ret) == 0 { + panic("no return value specified for SearchUser") + } + var r0 []model.User var r1 error if rf, ok := ret.Get(0).(func(context.Context, *ldap.Session, string) ([]model.User, error)); ok { diff --git a/src/testing/pkg/member/fake_member_manager.go b/src/testing/pkg/member/fake_member_manager.go index af75d1746..1517a3784 100644 --- a/src/testing/pkg/member/fake_member_manager.go +++ b/src/testing/pkg/member/fake_member_manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package member @@ -21,6 +21,10 @@ type Manager struct { func (_m *Manager) AddProjectMember(ctx context.Context, _a1 models.Member) (int, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for AddProjectMember") + } + var r0 int var r1 error if rf, ok := ret.Get(0).(func(context.Context, models.Member) (int, error)); ok { @@ -45,6 +49,10 @@ func (_m *Manager) AddProjectMember(ctx context.Context, _a1 models.Member) (int func (_m *Manager) Delete(ctx context.Context, projectID int64, memberID int) error { ret := _m.Called(ctx, projectID, memberID) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, int) error); ok { r0 = rf(ctx, projectID, memberID) @@ -59,6 +67,10 @@ func (_m *Manager) Delete(ctx context.Context, projectID int64, memberID int) er func (_m *Manager) DeleteMemberByProjectID(ctx context.Context, projectID int64) error { ret := _m.Called(ctx, projectID) + if len(ret) == 0 { + panic("no return value specified for DeleteMemberByProjectID") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, projectID) @@ -73,6 +85,10 @@ func (_m *Manager) DeleteMemberByProjectID(ctx context.Context, projectID int64) func (_m *Manager) DeleteMemberByUserID(ctx context.Context, uid int) error { ret := _m.Called(ctx, uid) + if len(ret) == 0 { + panic("no return value specified for DeleteMemberByUserID") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int) error); ok { r0 = rf(ctx, uid) @@ -87,6 +103,10 @@ func (_m *Manager) DeleteMemberByUserID(ctx context.Context, uid int) error { func (_m *Manager) Get(ctx context.Context, projectID int64, memberID int) (*models.Member, error) { ret := _m.Called(ctx, projectID, memberID) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *models.Member var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, int) (*models.Member, error)); ok { @@ -120,6 +140,10 @@ func (_m *Manager) GetTotalOfProjectMembers(ctx context.Context, projectID int64 _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for GetTotalOfProjectMembers") + } + var r0 int var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, *q.Query, ...int) (int, error)); ok { @@ -144,6 +168,10 @@ func (_m *Manager) GetTotalOfProjectMembers(ctx context.Context, projectID int64 func (_m *Manager) List(ctx context.Context, queryMember models.Member, query *q.Query) ([]*models.Member, error) { ret := _m.Called(ctx, queryMember, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*models.Member var r1 error if rf, ok := ret.Get(0).(func(context.Context, models.Member, *q.Query) ([]*models.Member, error)); ok { @@ -170,6 +198,10 @@ func (_m *Manager) List(ctx context.Context, queryMember models.Member, query *q func (_m *Manager) ListRoles(ctx context.Context, user *models.User, projectID int64) ([]int, error) { ret := _m.Called(ctx, user, projectID) + if len(ret) == 0 { + panic("no return value specified for ListRoles") + } + var r0 []int var r1 error if rf, ok := ret.Get(0).(func(context.Context, *models.User, int64) ([]int, error)); ok { @@ -196,6 +228,10 @@ func (_m *Manager) ListRoles(ctx context.Context, user *models.User, projectID i func (_m *Manager) SearchMemberByName(ctx context.Context, projectID int64, entityName string) ([]*models.Member, error) { ret := _m.Called(ctx, projectID, entityName) + if len(ret) == 0 { + panic("no return value specified for SearchMemberByName") + } + var r0 []*models.Member var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, string) ([]*models.Member, error)); ok { @@ -222,6 +258,10 @@ func (_m *Manager) SearchMemberByName(ctx context.Context, projectID int64, enti func (_m *Manager) UpdateRole(ctx context.Context, projectID int64, pmID int, role int) error { ret := _m.Called(ctx, projectID, pmID, role) + if len(ret) == 0 { + panic("no return value specified for UpdateRole") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, int, int) error); ok { r0 = rf(ctx, projectID, pmID, role) diff --git a/src/testing/pkg/notification/policy/dao/dao.go b/src/testing/pkg/notification/policy/dao/dao.go index 55d9e9ba4..83181b5a4 100644 --- a/src/testing/pkg/notification/policy/dao/dao.go +++ b/src/testing/pkg/notification/policy/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -21,6 +21,10 @@ type DAO struct { func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -45,6 +49,10 @@ func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *DAO) Create(ctx context.Context, n *model.Policy) (int64, error) { ret := _m.Called(ctx, n) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy) (int64, error)); ok { @@ -69,6 +77,10 @@ func (_m *DAO) Create(ctx context.Context, n *model.Policy) (int64, error) { func (_m *DAO) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -83,6 +95,10 @@ func (_m *DAO) Delete(ctx context.Context, id int64) error { func (_m *DAO) Get(ctx context.Context, id int64) (*model.Policy, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.Policy, error)); ok { @@ -109,6 +125,10 @@ func (_m *DAO) Get(ctx context.Context, id int64) (*model.Policy, error) { func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.Policy, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.Policy, error)); ok { @@ -135,6 +155,10 @@ func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.Policy, error func (_m *DAO) Update(ctx context.Context, n *model.Policy) error { ret := _m.Called(ctx, n) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy) error); ok { r0 = rf(ctx, n) diff --git a/src/testing/pkg/notification/policy/manager.go b/src/testing/pkg/notification/policy/manager.go index 716d91b44..b68aaecb5 100644 --- a/src/testing/pkg/notification/policy/manager.go +++ b/src/testing/pkg/notification/policy/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package policy @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *Manager) Create(ctx context.Context, _a1 *model.Policy) (int64, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy) (int64, error)); ok { @@ -68,6 +76,10 @@ func (_m *Manager) Create(ctx context.Context, _a1 *model.Policy) (int64, error) func (_m *Manager) Delete(ctx context.Context, policyID int64) error { ret := _m.Called(ctx, policyID) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, policyID) @@ -82,6 +94,10 @@ func (_m *Manager) Delete(ctx context.Context, policyID int64) error { func (_m *Manager) Get(ctx context.Context, id int64) (*model.Policy, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.Policy, error)); ok { @@ -108,6 +124,10 @@ func (_m *Manager) Get(ctx context.Context, id int64) (*model.Policy, error) { func (_m *Manager) GetRelatedPolices(ctx context.Context, projectID int64, eventType string) ([]*model.Policy, error) { ret := _m.Called(ctx, projectID, eventType) + if len(ret) == 0 { + panic("no return value specified for GetRelatedPolices") + } + var r0 []*model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, string) ([]*model.Policy, error)); ok { @@ -134,6 +154,10 @@ func (_m *Manager) GetRelatedPolices(ctx context.Context, projectID int64, event func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.Policy, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.Policy, error)); ok { @@ -160,6 +184,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.Policy, e func (_m *Manager) Update(ctx context.Context, _a1 *model.Policy) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy) error); ok { r0 = rf(ctx, _a1) diff --git a/src/testing/pkg/oidc/dao/meta_dao.go b/src/testing/pkg/oidc/dao/meta_dao.go index d66a416e0..c773133ef 100644 --- a/src/testing/pkg/oidc/dao/meta_dao.go +++ b/src/testing/pkg/oidc/dao/meta_dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -21,6 +21,10 @@ type MetaDAO struct { func (_m *MetaDAO) Create(ctx context.Context, oidcUser *models.OIDCUser) (int, error) { ret := _m.Called(ctx, oidcUser) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int var r1 error if rf, ok := ret.Get(0).(func(context.Context, *models.OIDCUser) (int, error)); ok { @@ -45,6 +49,10 @@ func (_m *MetaDAO) Create(ctx context.Context, oidcUser *models.OIDCUser) (int, func (_m *MetaDAO) DeleteByUserID(ctx context.Context, uid int) error { ret := _m.Called(ctx, uid) + if len(ret) == 0 { + panic("no return value specified for DeleteByUserID") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int) error); ok { r0 = rf(ctx, uid) @@ -59,6 +67,10 @@ func (_m *MetaDAO) DeleteByUserID(ctx context.Context, uid int) error { func (_m *MetaDAO) GetByUsername(ctx context.Context, username string) (*models.OIDCUser, error) { ret := _m.Called(ctx, username) + if len(ret) == 0 { + panic("no return value specified for GetByUsername") + } + var r0 *models.OIDCUser var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*models.OIDCUser, error)); ok { @@ -85,6 +97,10 @@ func (_m *MetaDAO) GetByUsername(ctx context.Context, username string) (*models. func (_m *MetaDAO) List(ctx context.Context, query *q.Query) ([]*models.OIDCUser, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*models.OIDCUser var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*models.OIDCUser, error)); ok { @@ -118,6 +134,10 @@ func (_m *MetaDAO) Update(ctx context.Context, oidcUser *models.OIDCUser, props _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.OIDCUser, ...string) error); ok { r0 = rf(ctx, oidcUser, props...) diff --git a/src/testing/pkg/oidc/meta_manager.go b/src/testing/pkg/oidc/meta_manager.go index 676a33623..c9a688a2e 100644 --- a/src/testing/pkg/oidc/meta_manager.go +++ b/src/testing/pkg/oidc/meta_manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package oidc @@ -18,6 +18,10 @@ type MetaManager struct { func (_m *MetaManager) Create(ctx context.Context, oidcUser *models.OIDCUser) (int, error) { ret := _m.Called(ctx, oidcUser) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int var r1 error if rf, ok := ret.Get(0).(func(context.Context, *models.OIDCUser) (int, error)); ok { @@ -42,6 +46,10 @@ func (_m *MetaManager) Create(ctx context.Context, oidcUser *models.OIDCUser) (i func (_m *MetaManager) DeleteByUserID(ctx context.Context, uid int) error { ret := _m.Called(ctx, uid) + if len(ret) == 0 { + panic("no return value specified for DeleteByUserID") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int) error); ok { r0 = rf(ctx, uid) @@ -56,6 +64,10 @@ func (_m *MetaManager) DeleteByUserID(ctx context.Context, uid int) error { func (_m *MetaManager) GetBySubIss(ctx context.Context, sub string, iss string) (*models.OIDCUser, error) { ret := _m.Called(ctx, sub, iss) + if len(ret) == 0 { + panic("no return value specified for GetBySubIss") + } + var r0 *models.OIDCUser var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (*models.OIDCUser, error)); ok { @@ -82,6 +94,10 @@ func (_m *MetaManager) GetBySubIss(ctx context.Context, sub string, iss string) func (_m *MetaManager) GetByUserID(ctx context.Context, uid int) (*models.OIDCUser, error) { ret := _m.Called(ctx, uid) + if len(ret) == 0 { + panic("no return value specified for GetByUserID") + } + var r0 *models.OIDCUser var r1 error if rf, ok := ret.Get(0).(func(context.Context, int) (*models.OIDCUser, error)); ok { @@ -108,6 +124,10 @@ func (_m *MetaManager) GetByUserID(ctx context.Context, uid int) (*models.OIDCUs func (_m *MetaManager) SetCliSecretByUserID(ctx context.Context, uid int, secret string) error { ret := _m.Called(ctx, uid, secret) + if len(ret) == 0 { + panic("no return value specified for SetCliSecretByUserID") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int, string) error); ok { r0 = rf(ctx, uid, secret) @@ -129,6 +149,10 @@ func (_m *MetaManager) Update(ctx context.Context, oidcUser *models.OIDCUser, co _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.OIDCUser, ...string) error); ok { r0 = rf(ctx, oidcUser, cols...) diff --git a/src/testing/pkg/project/manager.go b/src/testing/pkg/project/manager.go index c29bb1c9f..614a5e067 100644 --- a/src/testing/pkg/project/manager.go +++ b/src/testing/pkg/project/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package project @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *Manager) Create(ctx context.Context, _a1 *models.Project) (int64, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *models.Project) (int64, error)); ok { @@ -68,6 +76,10 @@ func (_m *Manager) Create(ctx context.Context, _a1 *models.Project) (int64, erro func (_m *Manager) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -82,6 +94,10 @@ func (_m *Manager) Delete(ctx context.Context, id int64) error { func (_m *Manager) Get(ctx context.Context, idOrName interface{}) (*models.Project, error) { ret := _m.Called(ctx, idOrName) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *models.Project var r1 error if rf, ok := ret.Get(0).(func(context.Context, interface{}) (*models.Project, error)); ok { @@ -108,6 +124,10 @@ func (_m *Manager) Get(ctx context.Context, idOrName interface{}) (*models.Proje func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*models.Project, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*models.Project var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*models.Project, error)); ok { @@ -134,6 +154,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*models.Project, func (_m *Manager) ListAdminRolesOfUser(ctx context.Context, userID int) ([]models.Member, error) { ret := _m.Called(ctx, userID) + if len(ret) == 0 { + panic("no return value specified for ListAdminRolesOfUser") + } + var r0 []models.Member var r1 error if rf, ok := ret.Get(0).(func(context.Context, int) ([]models.Member, error)); ok { @@ -167,6 +191,10 @@ func (_m *Manager) ListRoles(ctx context.Context, projectID int64, userID int, g _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for ListRoles") + } + var r0 []int var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, int, ...int) ([]int, error)); ok { diff --git a/src/testing/pkg/project/metadata/manager.go b/src/testing/pkg/project/metadata/manager.go index 3a6e071c0..7135b3be2 100644 --- a/src/testing/pkg/project/metadata/manager.go +++ b/src/testing/pkg/project/metadata/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package metadata @@ -19,6 +19,10 @@ type Manager struct { func (_m *Manager) Add(ctx context.Context, projectID int64, meta map[string]string) error { ret := _m.Called(ctx, projectID, meta) + if len(ret) == 0 { + panic("no return value specified for Add") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, map[string]string) error); ok { r0 = rf(ctx, projectID, meta) @@ -40,6 +44,10 @@ func (_m *Manager) Delete(ctx context.Context, projectID int64, meta ...string) _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, ...string) error); ok { r0 = rf(ctx, projectID, meta...) @@ -61,6 +69,10 @@ func (_m *Manager) Get(ctx context.Context, projectID int64, meta ...string) (ma _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 map[string]string var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, ...string) (map[string]string, error)); ok { @@ -87,6 +99,10 @@ func (_m *Manager) Get(ctx context.Context, projectID int64, meta ...string) (ma func (_m *Manager) List(ctx context.Context, name string, value string) ([]*models.ProjectMetadata, error) { ret := _m.Called(ctx, name, value) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*models.ProjectMetadata var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]*models.ProjectMetadata, error)); ok { @@ -113,6 +129,10 @@ func (_m *Manager) List(ctx context.Context, name string, value string) ([]*mode func (_m *Manager) Update(ctx context.Context, projectID int64, meta map[string]string) error { ret := _m.Called(ctx, projectID, meta) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, map[string]string) error); ok { r0 = rf(ctx, projectID, meta) diff --git a/src/testing/pkg/queuestatus/manager.go b/src/testing/pkg/queuestatus/manager.go index 836631dfe..924bc4ca8 100644 --- a/src/testing/pkg/queuestatus/manager.go +++ b/src/testing/pkg/queuestatus/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package queuestatus @@ -18,6 +18,10 @@ type Manager struct { func (_m *Manager) AllJobTypeStatus(ctx context.Context) (map[string]bool, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for AllJobTypeStatus") + } + var r0 map[string]bool var r1 error if rf, ok := ret.Get(0).(func(context.Context) (map[string]bool, error)); ok { @@ -44,6 +48,10 @@ func (_m *Manager) AllJobTypeStatus(ctx context.Context) (map[string]bool, error func (_m *Manager) CreateOrUpdate(ctx context.Context, status *model.JobQueueStatus) (int64, error) { ret := _m.Called(ctx, status) + if len(ret) == 0 { + panic("no return value specified for CreateOrUpdate") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.JobQueueStatus) (int64, error)); ok { @@ -68,6 +76,10 @@ func (_m *Manager) CreateOrUpdate(ctx context.Context, status *model.JobQueueSta func (_m *Manager) Get(ctx context.Context, jobType string) (*model.JobQueueStatus, error) { ret := _m.Called(ctx, jobType) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.JobQueueStatus var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*model.JobQueueStatus, error)); ok { @@ -94,6 +106,10 @@ func (_m *Manager) Get(ctx context.Context, jobType string) (*model.JobQueueStat func (_m *Manager) List(ctx context.Context) ([]*model.JobQueueStatus, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.JobQueueStatus var r1 error if rf, ok := ret.Get(0).(func(context.Context) ([]*model.JobQueueStatus, error)); ok { @@ -120,6 +136,10 @@ func (_m *Manager) List(ctx context.Context) ([]*model.JobQueueStatus, error) { func (_m *Manager) UpdateStatus(ctx context.Context, jobType string, paused bool) error { ret := _m.Called(ctx, jobType, paused) + if len(ret) == 0 { + panic("no return value specified for UpdateStatus") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, bool) error); ok { r0 = rf(ctx, jobType, paused) diff --git a/src/testing/pkg/quota/driver/driver.go b/src/testing/pkg/quota/driver/driver.go index 377caf2b0..2ef8b7c37 100644 --- a/src/testing/pkg/quota/driver/driver.go +++ b/src/testing/pkg/quota/driver/driver.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package driver @@ -20,6 +20,10 @@ type Driver struct { func (_m *Driver) CalculateUsage(ctx context.Context, key string) (types.ResourceList, error) { ret := _m.Called(ctx, key) + if len(ret) == 0 { + panic("no return value specified for CalculateUsage") + } + var r0 types.ResourceList var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (types.ResourceList, error)); ok { @@ -46,6 +50,10 @@ func (_m *Driver) CalculateUsage(ctx context.Context, key string) (types.Resourc func (_m *Driver) Enabled(ctx context.Context, key string) (bool, error) { ret := _m.Called(ctx, key) + if len(ret) == 0 { + panic("no return value specified for Enabled") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (bool, error)); ok { @@ -70,6 +78,10 @@ func (_m *Driver) Enabled(ctx context.Context, key string) (bool, error) { func (_m *Driver) HardLimits(ctx context.Context) types.ResourceList { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for HardLimits") + } + var r0 types.ResourceList if rf, ok := ret.Get(0).(func(context.Context) types.ResourceList); ok { r0 = rf(ctx) @@ -86,6 +98,10 @@ func (_m *Driver) HardLimits(ctx context.Context) types.ResourceList { func (_m *Driver) Load(ctx context.Context, key string) (driver.RefObject, error) { ret := _m.Called(ctx, key) + if len(ret) == 0 { + panic("no return value specified for Load") + } + var r0 driver.RefObject var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (driver.RefObject, error)); ok { @@ -112,6 +128,10 @@ func (_m *Driver) Load(ctx context.Context, key string) (driver.RefObject, error func (_m *Driver) Validate(hardLimits types.ResourceList) error { ret := _m.Called(hardLimits) + if len(ret) == 0 { + panic("no return value specified for Validate") + } + var r0 error if rf, ok := ret.Get(0).(func(types.ResourceList) error); ok { r0 = rf(hardLimits) diff --git a/src/testing/pkg/quota/manager.go b/src/testing/pkg/quota/manager.go index b5feaaec6..4d570f3ed 100644 --- a/src/testing/pkg/quota/manager.go +++ b/src/testing/pkg/quota/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package quota @@ -22,6 +22,10 @@ type Manager struct { func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -53,6 +57,10 @@ func (_m *Manager) Create(ctx context.Context, reference string, referenceID str _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, types.ResourceList, ...types.ResourceList) (int64, error)); ok { @@ -77,6 +85,10 @@ func (_m *Manager) Create(ctx context.Context, reference string, referenceID str func (_m *Manager) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -91,6 +103,10 @@ func (_m *Manager) Delete(ctx context.Context, id int64) error { func (_m *Manager) Get(ctx context.Context, id int64) (*models.Quota, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *models.Quota var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*models.Quota, error)); ok { @@ -117,6 +133,10 @@ func (_m *Manager) Get(ctx context.Context, id int64) (*models.Quota, error) { func (_m *Manager) GetByRef(ctx context.Context, reference string, referenceID string) (*models.Quota, error) { ret := _m.Called(ctx, reference, referenceID) + if len(ret) == 0 { + panic("no return value specified for GetByRef") + } + var r0 *models.Quota var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (*models.Quota, error)); ok { @@ -143,6 +163,10 @@ func (_m *Manager) GetByRef(ctx context.Context, reference string, referenceID s func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*models.Quota, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*models.Quota var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*models.Quota, error)); ok { @@ -169,6 +193,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*models.Quota, e func (_m *Manager) Update(ctx context.Context, _a1 *models.Quota) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.Quota) error); ok { r0 = rf(ctx, _a1) diff --git a/src/testing/pkg/rbac/dao/dao.go b/src/testing/pkg/rbac/dao/dao.go index 7f2138499..91a2c351c 100644 --- a/src/testing/pkg/rbac/dao/dao.go +++ b/src/testing/pkg/rbac/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -21,6 +21,10 @@ type DAO struct { func (_m *DAO) CreatePermission(ctx context.Context, rp *model.RolePermission) (int64, error) { ret := _m.Called(ctx, rp) + if len(ret) == 0 { + panic("no return value specified for CreatePermission") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.RolePermission) (int64, error)); ok { @@ -45,6 +49,10 @@ func (_m *DAO) CreatePermission(ctx context.Context, rp *model.RolePermission) ( func (_m *DAO) CreateRbacPolicy(ctx context.Context, pp *model.PermissionPolicy) (int64, error) { ret := _m.Called(ctx, pp) + if len(ret) == 0 { + panic("no return value specified for CreateRbacPolicy") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.PermissionPolicy) (int64, error)); ok { @@ -69,6 +77,10 @@ func (_m *DAO) CreateRbacPolicy(ctx context.Context, pp *model.PermissionPolicy) func (_m *DAO) DeletePermission(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for DeletePermission") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -83,6 +95,10 @@ func (_m *DAO) DeletePermission(ctx context.Context, id int64) error { func (_m *DAO) DeletePermissionsByRole(ctx context.Context, roleType string, roleID int64) error { ret := _m.Called(ctx, roleType, roleID) + if len(ret) == 0 { + panic("no return value specified for DeletePermissionsByRole") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, int64) error); ok { r0 = rf(ctx, roleType, roleID) @@ -97,6 +113,10 @@ func (_m *DAO) DeletePermissionsByRole(ctx context.Context, roleType string, rol func (_m *DAO) DeleteRbacPolicy(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for DeleteRbacPolicy") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -111,6 +131,10 @@ func (_m *DAO) DeleteRbacPolicy(ctx context.Context, id int64) error { func (_m *DAO) GetPermissionsByRole(ctx context.Context, roleType string, roleID int64) ([]*model.UniversalRolePermission, error) { ret := _m.Called(ctx, roleType, roleID) + if len(ret) == 0 { + panic("no return value specified for GetPermissionsByRole") + } + var r0 []*model.UniversalRolePermission var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64) ([]*model.UniversalRolePermission, error)); ok { @@ -137,6 +161,10 @@ func (_m *DAO) GetPermissionsByRole(ctx context.Context, roleType string, roleID func (_m *DAO) ListPermissions(ctx context.Context, query *q.Query) ([]*model.RolePermission, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListPermissions") + } + var r0 []*model.RolePermission var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.RolePermission, error)); ok { @@ -163,6 +191,10 @@ func (_m *DAO) ListPermissions(ctx context.Context, query *q.Query) ([]*model.Ro func (_m *DAO) ListRbacPolicies(ctx context.Context, query *q.Query) ([]*model.PermissionPolicy, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListRbacPolicies") + } + var r0 []*model.PermissionPolicy var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.PermissionPolicy, error)); ok { diff --git a/src/testing/pkg/rbac/manager.go b/src/testing/pkg/rbac/manager.go index 3de52e1d4..0cc460753 100644 --- a/src/testing/pkg/rbac/manager.go +++ b/src/testing/pkg/rbac/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package rbac @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) CreatePermission(ctx context.Context, rp *model.RolePermission) (int64, error) { ret := _m.Called(ctx, rp) + if len(ret) == 0 { + panic("no return value specified for CreatePermission") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.RolePermission) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Manager) CreatePermission(ctx context.Context, rp *model.RolePermissio func (_m *Manager) CreateRbacPolicy(ctx context.Context, pp *model.PermissionPolicy) (int64, error) { ret := _m.Called(ctx, pp) + if len(ret) == 0 { + panic("no return value specified for CreateRbacPolicy") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.PermissionPolicy) (int64, error)); ok { @@ -68,6 +76,10 @@ func (_m *Manager) CreateRbacPolicy(ctx context.Context, pp *model.PermissionPol func (_m *Manager) DeletePermission(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for DeletePermission") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -82,6 +94,10 @@ func (_m *Manager) DeletePermission(ctx context.Context, id int64) error { func (_m *Manager) DeletePermissionsByRole(ctx context.Context, roleType string, roleID int64) error { ret := _m.Called(ctx, roleType, roleID) + if len(ret) == 0 { + panic("no return value specified for DeletePermissionsByRole") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, int64) error); ok { r0 = rf(ctx, roleType, roleID) @@ -96,6 +112,10 @@ func (_m *Manager) DeletePermissionsByRole(ctx context.Context, roleType string, func (_m *Manager) DeleteRbacPolicy(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for DeleteRbacPolicy") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -110,6 +130,10 @@ func (_m *Manager) DeleteRbacPolicy(ctx context.Context, id int64) error { func (_m *Manager) GetPermissionsByRole(ctx context.Context, roleType string, roleID int64) ([]*model.UniversalRolePermission, error) { ret := _m.Called(ctx, roleType, roleID) + if len(ret) == 0 { + panic("no return value specified for GetPermissionsByRole") + } + var r0 []*model.UniversalRolePermission var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64) ([]*model.UniversalRolePermission, error)); ok { @@ -136,6 +160,10 @@ func (_m *Manager) GetPermissionsByRole(ctx context.Context, roleType string, ro func (_m *Manager) ListPermissions(ctx context.Context, query *q.Query) ([]*model.RolePermission, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListPermissions") + } + var r0 []*model.RolePermission var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.RolePermission, error)); ok { @@ -162,6 +190,10 @@ func (_m *Manager) ListPermissions(ctx context.Context, query *q.Query) ([]*mode func (_m *Manager) ListRbacPolicies(ctx context.Context, query *q.Query) ([]*model.PermissionPolicy, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListRbacPolicies") + } + var r0 []*model.PermissionPolicy var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.PermissionPolicy, error)); ok { diff --git a/src/testing/pkg/reg/adapter/adapter.go b/src/testing/pkg/reg/adapter/adapter.go index 6f8ad39b5..a10a0f2ff 100644 --- a/src/testing/pkg/reg/adapter/adapter.go +++ b/src/testing/pkg/reg/adapter/adapter.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package adapter @@ -16,6 +16,10 @@ type Adapter struct { func (_m *Adapter) HealthCheck() (string, error) { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for HealthCheck") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func() (string, error)); ok { @@ -40,6 +44,10 @@ func (_m *Adapter) HealthCheck() (string, error) { func (_m *Adapter) Info() (*model.RegistryInfo, error) { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Info") + } + var r0 *model.RegistryInfo var r1 error if rf, ok := ret.Get(0).(func() (*model.RegistryInfo, error)); ok { @@ -66,6 +74,10 @@ func (_m *Adapter) Info() (*model.RegistryInfo, error) { func (_m *Adapter) PrepareForPush(_a0 []*model.Resource) error { ret := _m.Called(_a0) + if len(ret) == 0 { + panic("no return value specified for PrepareForPush") + } + var r0 error if rf, ok := ret.Get(0).(func([]*model.Resource) error); ok { r0 = rf(_a0) diff --git a/src/testing/pkg/reg/dao/dao.go b/src/testing/pkg/reg/dao/dao.go index 0f11dd37f..23ae0ecc8 100644 --- a/src/testing/pkg/reg/dao/dao.go +++ b/src/testing/pkg/reg/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -20,6 +20,10 @@ type DAO struct { func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *DAO) Create(ctx context.Context, registry *dao.Registry) (int64, error) { ret := _m.Called(ctx, registry) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *dao.Registry) (int64, error)); ok { @@ -68,6 +76,10 @@ func (_m *DAO) Create(ctx context.Context, registry *dao.Registry) (int64, error func (_m *DAO) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -82,6 +94,10 @@ func (_m *DAO) Delete(ctx context.Context, id int64) error { func (_m *DAO) Get(ctx context.Context, id int64) (*dao.Registry, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *dao.Registry var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*dao.Registry, error)); ok { @@ -108,6 +124,10 @@ func (_m *DAO) Get(ctx context.Context, id int64) (*dao.Registry, error) { func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*dao.Registry, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*dao.Registry var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*dao.Registry, error)); ok { @@ -141,6 +161,10 @@ func (_m *DAO) Update(ctx context.Context, registry *dao.Registry, props ...stri _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *dao.Registry, ...string) error); ok { r0 = rf(ctx, registry, props...) diff --git a/src/testing/pkg/reg/manager.go b/src/testing/pkg/reg/manager.go index 291af3927..c25e542d1 100644 --- a/src/testing/pkg/reg/manager.go +++ b/src/testing/pkg/reg/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package manager @@ -23,6 +23,10 @@ type Manager struct { func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -47,6 +51,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *Manager) Create(ctx context.Context, registry *model.Registry) (int64, error) { ret := _m.Called(ctx, registry) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Registry) (int64, error)); ok { @@ -71,6 +79,10 @@ func (_m *Manager) Create(ctx context.Context, registry *model.Registry) (int64, func (_m *Manager) CreateAdapter(ctx context.Context, registry *model.Registry) (adapter.Adapter, error) { ret := _m.Called(ctx, registry) + if len(ret) == 0 { + panic("no return value specified for CreateAdapter") + } + var r0 adapter.Adapter var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Registry) (adapter.Adapter, error)); ok { @@ -97,6 +109,10 @@ func (_m *Manager) CreateAdapter(ctx context.Context, registry *model.Registry) func (_m *Manager) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -111,6 +127,10 @@ func (_m *Manager) Delete(ctx context.Context, id int64) error { func (_m *Manager) Get(ctx context.Context, id int64) (*model.Registry, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.Registry var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.Registry, error)); ok { @@ -137,6 +157,10 @@ func (_m *Manager) Get(ctx context.Context, id int64) (*model.Registry, error) { func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.Registry, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.Registry var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.Registry, error)); ok { @@ -163,6 +187,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.Registry, func (_m *Manager) ListRegistryProviderInfos(ctx context.Context) (map[string]*model.AdapterPattern, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for ListRegistryProviderInfos") + } + var r0 map[string]*model.AdapterPattern var r1 error if rf, ok := ret.Get(0).(func(context.Context) (map[string]*model.AdapterPattern, error)); ok { @@ -189,6 +217,10 @@ func (_m *Manager) ListRegistryProviderInfos(ctx context.Context) (map[string]*m func (_m *Manager) ListRegistryProviderTypes(ctx context.Context) ([]string, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for ListRegistryProviderTypes") + } + var r0 []string var r1 error if rf, ok := ret.Get(0).(func(context.Context) ([]string, error)); ok { @@ -222,6 +254,10 @@ func (_m *Manager) Update(ctx context.Context, registry *model.Registry, props . _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Registry, ...string) error); ok { r0 = rf(ctx, registry, props...) diff --git a/src/testing/pkg/registry/fake_registry_client.go b/src/testing/pkg/registry/fake_registry_client.go index adb9c3446..72a6decf2 100644 --- a/src/testing/pkg/registry/fake_registry_client.go +++ b/src/testing/pkg/registry/fake_registry_client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package registry @@ -21,6 +21,10 @@ type Client struct { func (_m *Client) BlobExist(repository string, digest string) (bool, error) { ret := _m.Called(repository, digest) + if len(ret) == 0 { + panic("no return value specified for BlobExist") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(string, string) (bool, error)); ok { @@ -45,6 +49,10 @@ func (_m *Client) BlobExist(repository string, digest string) (bool, error) { func (_m *Client) Catalog() ([]string, error) { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Catalog") + } + var r0 []string var r1 error if rf, ok := ret.Get(0).(func() ([]string, error)); ok { @@ -71,6 +79,10 @@ func (_m *Client) Catalog() ([]string, error) { func (_m *Client) Copy(srcRepository string, srcReference string, dstRepository string, dstReference string, override bool) error { ret := _m.Called(srcRepository, srcReference, dstRepository, dstReference, override) + if len(ret) == 0 { + panic("no return value specified for Copy") + } + var r0 error if rf, ok := ret.Get(0).(func(string, string, string, string, bool) error); ok { r0 = rf(srcRepository, srcReference, dstRepository, dstReference, override) @@ -85,6 +97,10 @@ func (_m *Client) Copy(srcRepository string, srcReference string, dstRepository func (_m *Client) DeleteBlob(repository string, digest string) error { ret := _m.Called(repository, digest) + if len(ret) == 0 { + panic("no return value specified for DeleteBlob") + } + var r0 error if rf, ok := ret.Get(0).(func(string, string) error); ok { r0 = rf(repository, digest) @@ -99,6 +115,10 @@ func (_m *Client) DeleteBlob(repository string, digest string) error { func (_m *Client) DeleteManifest(repository string, reference string) error { ret := _m.Called(repository, reference) + if len(ret) == 0 { + panic("no return value specified for DeleteManifest") + } + var r0 error if rf, ok := ret.Get(0).(func(string, string) error); ok { r0 = rf(repository, reference) @@ -113,6 +133,10 @@ func (_m *Client) DeleteManifest(repository string, reference string) error { func (_m *Client) Do(req *http.Request) (*http.Response, error) { ret := _m.Called(req) + if len(ret) == 0 { + panic("no return value specified for Do") + } + var r0 *http.Response var r1 error if rf, ok := ret.Get(0).(func(*http.Request) (*http.Response, error)); ok { @@ -139,6 +163,10 @@ func (_m *Client) Do(req *http.Request) (*http.Response, error) { func (_m *Client) ListTags(repository string) ([]string, error) { ret := _m.Called(repository) + if len(ret) == 0 { + panic("no return value specified for ListTags") + } + var r0 []string var r1 error if rf, ok := ret.Get(0).(func(string) ([]string, error)); ok { @@ -165,6 +193,10 @@ func (_m *Client) ListTags(repository string) ([]string, error) { func (_m *Client) ManifestExist(repository string, reference string) (bool, *distribution.Descriptor, error) { ret := _m.Called(repository, reference) + if len(ret) == 0 { + panic("no return value specified for ManifestExist") + } + var r0 bool var r1 *distribution.Descriptor var r2 error @@ -198,6 +230,10 @@ func (_m *Client) ManifestExist(repository string, reference string) (bool, *dis func (_m *Client) MountBlob(srcRepository string, digest string, dstRepository string) error { ret := _m.Called(srcRepository, digest, dstRepository) + if len(ret) == 0 { + panic("no return value specified for MountBlob") + } + var r0 error if rf, ok := ret.Get(0).(func(string, string, string) error); ok { r0 = rf(srcRepository, digest, dstRepository) @@ -212,6 +248,10 @@ func (_m *Client) MountBlob(srcRepository string, digest string, dstRepository s func (_m *Client) Ping() error { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Ping") + } + var r0 error if rf, ok := ret.Get(0).(func() error); ok { r0 = rf() @@ -226,6 +266,10 @@ func (_m *Client) Ping() error { func (_m *Client) PullBlob(repository string, digest string) (int64, io.ReadCloser, error) { ret := _m.Called(repository, digest) + if len(ret) == 0 { + panic("no return value specified for PullBlob") + } + var r0 int64 var r1 io.ReadCloser var r2 error @@ -259,6 +303,10 @@ func (_m *Client) PullBlob(repository string, digest string) (int64, io.ReadClos func (_m *Client) PullBlobChunk(repository string, digest string, blobSize int64, start int64, end int64) (int64, io.ReadCloser, error) { ret := _m.Called(repository, digest, blobSize, start, end) + if len(ret) == 0 { + panic("no return value specified for PullBlobChunk") + } + var r0 int64 var r1 io.ReadCloser var r2 error @@ -299,6 +347,10 @@ func (_m *Client) PullManifest(repository string, reference string, acceptedMedi _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for PullManifest") + } + var r0 distribution.Manifest var r1 string var r2 error @@ -332,6 +384,10 @@ func (_m *Client) PullManifest(repository string, reference string, acceptedMedi func (_m *Client) PushBlob(repository string, digest string, size int64, blob io.Reader) error { ret := _m.Called(repository, digest, size, blob) + if len(ret) == 0 { + panic("no return value specified for PushBlob") + } + var r0 error if rf, ok := ret.Get(0).(func(string, string, int64, io.Reader) error); ok { r0 = rf(repository, digest, size, blob) @@ -346,6 +402,10 @@ func (_m *Client) PushBlob(repository string, digest string, size int64, blob io func (_m *Client) PushBlobChunk(repository string, digest string, blobSize int64, chunk io.Reader, start int64, end int64, location string) (string, int64, error) { ret := _m.Called(repository, digest, blobSize, chunk, start, end, location) + if len(ret) == 0 { + panic("no return value specified for PushBlobChunk") + } + var r0 string var r1 int64 var r2 error @@ -377,6 +437,10 @@ func (_m *Client) PushBlobChunk(repository string, digest string, blobSize int64 func (_m *Client) PushManifest(repository string, reference string, mediaType string, payload []byte) (string, error) { ret := _m.Called(repository, reference, mediaType, payload) + if len(ret) == 0 { + panic("no return value specified for PushManifest") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(string, string, string, []byte) (string, error)); ok { diff --git a/src/testing/pkg/replication/dao/dao.go b/src/testing/pkg/replication/dao/dao.go index 0a0ed964b..29c9a2be4 100644 --- a/src/testing/pkg/replication/dao/dao.go +++ b/src/testing/pkg/replication/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -21,6 +21,10 @@ type DAO struct { func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -45,6 +49,10 @@ func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *DAO) Create(ctx context.Context, policy *model.Policy) (int64, error) { ret := _m.Called(ctx, policy) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy) (int64, error)); ok { @@ -69,6 +77,10 @@ func (_m *DAO) Create(ctx context.Context, policy *model.Policy) (int64, error) func (_m *DAO) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -83,6 +95,10 @@ func (_m *DAO) Delete(ctx context.Context, id int64) error { func (_m *DAO) Get(ctx context.Context, id int64) (*model.Policy, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.Policy, error)); ok { @@ -109,6 +125,10 @@ func (_m *DAO) Get(ctx context.Context, id int64) (*model.Policy, error) { func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.Policy, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.Policy, error)); ok { @@ -142,6 +162,10 @@ func (_m *DAO) Update(ctx context.Context, policy *model.Policy, props ...string _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy, ...string) error); ok { r0 = rf(ctx, policy, props...) diff --git a/src/testing/pkg/replication/manager.go b/src/testing/pkg/replication/manager.go index 9289c4f8c..652331740 100644 --- a/src/testing/pkg/replication/manager.go +++ b/src/testing/pkg/replication/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package manager @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *Manager) Create(ctx context.Context, policy *model.Policy) (int64, error) { ret := _m.Called(ctx, policy) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy) (int64, error)); ok { @@ -68,6 +76,10 @@ func (_m *Manager) Create(ctx context.Context, policy *model.Policy) (int64, err func (_m *Manager) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -82,6 +94,10 @@ func (_m *Manager) Delete(ctx context.Context, id int64) error { func (_m *Manager) Get(ctx context.Context, id int64) (*model.Policy, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.Policy, error)); ok { @@ -108,6 +124,10 @@ func (_m *Manager) Get(ctx context.Context, id int64) (*model.Policy, error) { func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.Policy, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.Policy var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.Policy, error)); ok { @@ -141,6 +161,10 @@ func (_m *Manager) Update(ctx context.Context, policy *model.Policy, props ...st _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Policy, ...string) error); ok { r0 = rf(ctx, policy, props...) diff --git a/src/testing/pkg/repository/dao/dao.go b/src/testing/pkg/repository/dao/dao.go index 7e5563eed..59094a22c 100644 --- a/src/testing/pkg/repository/dao/dao.go +++ b/src/testing/pkg/repository/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -21,6 +21,10 @@ type DAO struct { func (_m *DAO) AddPullCount(ctx context.Context, id int64, count uint64) error { ret := _m.Called(ctx, id, count) + if len(ret) == 0 { + panic("no return value specified for AddPullCount") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, uint64) error); ok { r0 = rf(ctx, id, count) @@ -35,6 +39,10 @@ func (_m *DAO) AddPullCount(ctx context.Context, id int64, count uint64) error { func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -59,6 +67,10 @@ func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *DAO) Create(ctx context.Context, repository *model.RepoRecord) (int64, error) { ret := _m.Called(ctx, repository) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.RepoRecord) (int64, error)); ok { @@ -83,6 +95,10 @@ func (_m *DAO) Create(ctx context.Context, repository *model.RepoRecord) (int64, func (_m *DAO) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -97,6 +113,10 @@ func (_m *DAO) Delete(ctx context.Context, id int64) error { func (_m *DAO) Get(ctx context.Context, id int64) (*model.RepoRecord, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.RepoRecord var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.RepoRecord, error)); ok { @@ -123,6 +143,10 @@ func (_m *DAO) Get(ctx context.Context, id int64) (*model.RepoRecord, error) { func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.RepoRecord, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.RepoRecord var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.RepoRecord, error)); ok { @@ -149,6 +173,10 @@ func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.RepoRecord, e func (_m *DAO) NonEmptyRepos(ctx context.Context) ([]*model.RepoRecord, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for NonEmptyRepos") + } + var r0 []*model.RepoRecord var r1 error if rf, ok := ret.Get(0).(func(context.Context) ([]*model.RepoRecord, error)); ok { @@ -182,6 +210,10 @@ func (_m *DAO) Update(ctx context.Context, repository *model.RepoRecord, props . _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.RepoRecord, ...string) error); ok { r0 = rf(ctx, repository, props...) diff --git a/src/testing/pkg/repository/manager.go b/src/testing/pkg/repository/manager.go index f0de300a5..db6df7bde 100644 --- a/src/testing/pkg/repository/manager.go +++ b/src/testing/pkg/repository/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package repository @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) AddPullCount(ctx context.Context, id int64, count uint64) error { ret := _m.Called(ctx, id, count) + if len(ret) == 0 { + panic("no return value specified for AddPullCount") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, uint64) error); ok { r0 = rf(ctx, id, count) @@ -34,6 +38,10 @@ func (_m *Manager) AddPullCount(ctx context.Context, id int64, count uint64) err func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -58,6 +66,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *Manager) Create(ctx context.Context, _a1 *model.RepoRecord) (int64, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.RepoRecord) (int64, error)); ok { @@ -82,6 +94,10 @@ func (_m *Manager) Create(ctx context.Context, _a1 *model.RepoRecord) (int64, er func (_m *Manager) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -96,6 +112,10 @@ func (_m *Manager) Delete(ctx context.Context, id int64) error { func (_m *Manager) Get(ctx context.Context, id int64) (*model.RepoRecord, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.RepoRecord var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.RepoRecord, error)); ok { @@ -122,6 +142,10 @@ func (_m *Manager) Get(ctx context.Context, id int64) (*model.RepoRecord, error) func (_m *Manager) GetByName(ctx context.Context, name string) (*model.RepoRecord, error) { ret := _m.Called(ctx, name) + if len(ret) == 0 { + panic("no return value specified for GetByName") + } + var r0 *model.RepoRecord var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*model.RepoRecord, error)); ok { @@ -148,6 +172,10 @@ func (_m *Manager) GetByName(ctx context.Context, name string) (*model.RepoRecor func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.RepoRecord, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.RepoRecord var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.RepoRecord, error)); ok { @@ -174,6 +202,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.RepoRecor func (_m *Manager) NonEmptyRepos(ctx context.Context) ([]*model.RepoRecord, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for NonEmptyRepos") + } + var r0 []*model.RepoRecord var r1 error if rf, ok := ret.Get(0).(func(context.Context) ([]*model.RepoRecord, error)); ok { @@ -207,6 +239,10 @@ func (_m *Manager) Update(ctx context.Context, _a1 *model.RepoRecord, props ...s _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.RepoRecord, ...string) error); ok { r0 = rf(ctx, _a1, props...) diff --git a/src/testing/pkg/robot/dao/dao.go b/src/testing/pkg/robot/dao/dao.go index d3255265f..f1160128f 100644 --- a/src/testing/pkg/robot/dao/dao.go +++ b/src/testing/pkg/robot/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -21,6 +21,10 @@ type DAO struct { func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -45,6 +49,10 @@ func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *DAO) Create(ctx context.Context, r *model.Robot) (int64, error) { ret := _m.Called(ctx, r) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Robot) (int64, error)); ok { @@ -69,6 +77,10 @@ func (_m *DAO) Create(ctx context.Context, r *model.Robot) (int64, error) { func (_m *DAO) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -83,6 +95,10 @@ func (_m *DAO) Delete(ctx context.Context, id int64) error { func (_m *DAO) DeleteByProjectID(ctx context.Context, projectID int64) error { ret := _m.Called(ctx, projectID) + if len(ret) == 0 { + panic("no return value specified for DeleteByProjectID") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, projectID) @@ -97,6 +113,10 @@ func (_m *DAO) DeleteByProjectID(ctx context.Context, projectID int64) error { func (_m *DAO) Get(ctx context.Context, id int64) (*model.Robot, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.Robot var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.Robot, error)); ok { @@ -123,6 +143,10 @@ func (_m *DAO) Get(ctx context.Context, id int64) (*model.Robot, error) { func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.Robot, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.Robot var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.Robot, error)); ok { @@ -156,6 +180,10 @@ func (_m *DAO) Update(ctx context.Context, r *model.Robot, props ...string) erro _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Robot, ...string) error); ok { r0 = rf(ctx, r, props...) diff --git a/src/testing/pkg/robot/manager.go b/src/testing/pkg/robot/manager.go index 56be7000c..12101d09e 100644 --- a/src/testing/pkg/robot/manager.go +++ b/src/testing/pkg/robot/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package robot @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *Manager) Create(ctx context.Context, m *model.Robot) (int64, error) { ret := _m.Called(ctx, m) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Robot) (int64, error)); ok { @@ -68,6 +76,10 @@ func (_m *Manager) Create(ctx context.Context, m *model.Robot) (int64, error) { func (_m *Manager) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -82,6 +94,10 @@ func (_m *Manager) Delete(ctx context.Context, id int64) error { func (_m *Manager) DeleteByProjectID(ctx context.Context, projectID int64) error { ret := _m.Called(ctx, projectID) + if len(ret) == 0 { + panic("no return value specified for DeleteByProjectID") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, projectID) @@ -96,6 +112,10 @@ func (_m *Manager) DeleteByProjectID(ctx context.Context, projectID int64) error func (_m *Manager) Get(ctx context.Context, id int64) (*model.Robot, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.Robot var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*model.Robot, error)); ok { @@ -122,6 +142,10 @@ func (_m *Manager) Get(ctx context.Context, id int64) (*model.Robot, error) { func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.Robot, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.Robot var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.Robot, error)); ok { @@ -155,6 +179,10 @@ func (_m *Manager) Update(ctx context.Context, m *model.Robot, props ...string) _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Robot, ...string) error); ok { r0 = rf(ctx, m, props...) diff --git a/src/testing/pkg/scan/export/artifact_digest_calculator.go b/src/testing/pkg/scan/export/artifact_digest_calculator.go index 0a3436672..150c11cfd 100644 --- a/src/testing/pkg/scan/export/artifact_digest_calculator.go +++ b/src/testing/pkg/scan/export/artifact_digest_calculator.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package export @@ -17,6 +17,10 @@ type ArtifactDigestCalculator struct { func (_m *ArtifactDigestCalculator) Calculate(fileName string) (digest.Digest, error) { ret := _m.Called(fileName) + if len(ret) == 0 { + panic("no return value specified for Calculate") + } + var r0 digest.Digest var r1 error if rf, ok := ret.Get(0).(func(string) (digest.Digest, error)); ok { diff --git a/src/testing/pkg/scan/export/filter_processor.go b/src/testing/pkg/scan/export/filter_processor.go index fac0dbfff..4087eba62 100644 --- a/src/testing/pkg/scan/export/filter_processor.go +++ b/src/testing/pkg/scan/export/filter_processor.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package export @@ -19,6 +19,10 @@ type FilterProcessor struct { func (_m *FilterProcessor) ProcessLabelFilter(ctx context.Context, labelIDs []int64, arts []*artifact.Artifact) ([]*artifact.Artifact, error) { ret := _m.Called(ctx, labelIDs, arts) + if len(ret) == 0 { + panic("no return value specified for ProcessLabelFilter") + } + var r0 []*artifact.Artifact var r1 error if rf, ok := ret.Get(0).(func(context.Context, []int64, []*artifact.Artifact) ([]*artifact.Artifact, error)); ok { @@ -45,6 +49,10 @@ func (_m *FilterProcessor) ProcessLabelFilter(ctx context.Context, labelIDs []in func (_m *FilterProcessor) ProcessRepositoryFilter(ctx context.Context, filter string, projectIds []int64) ([]int64, error) { ret := _m.Called(ctx, filter, projectIds) + if len(ret) == 0 { + panic("no return value specified for ProcessRepositoryFilter") + } + var r0 []int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, []int64) ([]int64, error)); ok { @@ -71,6 +79,10 @@ func (_m *FilterProcessor) ProcessRepositoryFilter(ctx context.Context, filter s func (_m *FilterProcessor) ProcessTagFilter(ctx context.Context, filter string, repositoryIds []int64) ([]*artifact.Artifact, error) { ret := _m.Called(ctx, filter, repositoryIds) + if len(ret) == 0 { + panic("no return value specified for ProcessTagFilter") + } + var r0 []*artifact.Artifact var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, []int64) ([]*artifact.Artifact, error)); ok { diff --git a/src/testing/pkg/scan/export/manager.go b/src/testing/pkg/scan/export/manager.go index 3261f9b50..158ed47ad 100644 --- a/src/testing/pkg/scan/export/manager.go +++ b/src/testing/pkg/scan/export/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package export @@ -18,6 +18,10 @@ type Manager struct { func (_m *Manager) Fetch(ctx context.Context, params export.Params) ([]export.Data, error) { ret := _m.Called(ctx, params) + if len(ret) == 0 { + panic("no return value specified for Fetch") + } + var r0 []export.Data var r1 error if rf, ok := ret.Get(0).(func(context.Context, export.Params) ([]export.Data, error)); ok { diff --git a/src/testing/pkg/scan/report/manager.go b/src/testing/pkg/scan/report/manager.go index 768933506..4a50114f4 100644 --- a/src/testing/pkg/scan/report/manager.go +++ b/src/testing/pkg/scan/report/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package report @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) Create(ctx context.Context, r *scan.Report) (string, error) { ret := _m.Called(ctx, r) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(context.Context, *scan.Report) (string, error)); ok { @@ -44,6 +48,10 @@ func (_m *Manager) Create(ctx context.Context, r *scan.Report) (string, error) { func (_m *Manager) Delete(ctx context.Context, uuid string) error { ret := _m.Called(ctx, uuid) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, uuid) @@ -65,6 +73,10 @@ func (_m *Manager) DeleteByDigests(ctx context.Context, digests ...string) error _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for DeleteByDigests") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, ...string) error); ok { r0 = rf(ctx, digests...) @@ -79,6 +91,10 @@ func (_m *Manager) DeleteByDigests(ctx context.Context, digests ...string) error func (_m *Manager) GetBy(ctx context.Context, digest string, registrationUUID string, mimeTypes []string) ([]*scan.Report, error) { ret := _m.Called(ctx, digest, registrationUUID, mimeTypes) + if len(ret) == 0 { + panic("no return value specified for GetBy") + } + var r0 []*scan.Report var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, []string) ([]*scan.Report, error)); ok { @@ -105,6 +121,10 @@ func (_m *Manager) GetBy(ctx context.Context, digest string, registrationUUID st func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*scan.Report, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*scan.Report var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*scan.Report, error)); ok { @@ -138,6 +158,10 @@ func (_m *Manager) Update(ctx context.Context, r *scan.Report, cols ...string) e _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *scan.Report, ...string) error); ok { r0 = rf(ctx, r, cols...) @@ -152,6 +176,10 @@ func (_m *Manager) Update(ctx context.Context, r *scan.Report, cols ...string) e func (_m *Manager) UpdateReportData(ctx context.Context, uuid string, _a2 string) error { ret := _m.Called(ctx, uuid, _a2) + if len(ret) == 0 { + panic("no return value specified for UpdateReportData") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { r0 = rf(ctx, uuid, _a2) diff --git a/src/testing/pkg/scan/rest/v1/client.go b/src/testing/pkg/scan/rest/v1/client.go index 1f17ff8e6..b17c21e71 100644 --- a/src/testing/pkg/scan/rest/v1/client.go +++ b/src/testing/pkg/scan/rest/v1/client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package v1 @@ -16,6 +16,10 @@ type Client struct { func (_m *Client) GetMetadata() (*v1.ScannerAdapterMetadata, error) { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for GetMetadata") + } + var r0 *v1.ScannerAdapterMetadata var r1 error if rf, ok := ret.Get(0).(func() (*v1.ScannerAdapterMetadata, error)); ok { @@ -42,6 +46,10 @@ func (_m *Client) GetMetadata() (*v1.ScannerAdapterMetadata, error) { func (_m *Client) GetScanReport(scanRequestID string, reportMIMEType string) (string, error) { ret := _m.Called(scanRequestID, reportMIMEType) + if len(ret) == 0 { + panic("no return value specified for GetScanReport") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(string, string) (string, error)); ok { @@ -66,6 +74,10 @@ func (_m *Client) GetScanReport(scanRequestID string, reportMIMEType string) (st func (_m *Client) SubmitScan(req *v1.ScanRequest) (*v1.ScanResponse, error) { ret := _m.Called(req) + if len(ret) == 0 { + panic("no return value specified for SubmitScan") + } + var r0 *v1.ScanResponse var r1 error if rf, ok := ret.Get(0).(func(*v1.ScanRequest) (*v1.ScanResponse, error)); ok { diff --git a/src/testing/pkg/scan/rest/v1/client_pool.go b/src/testing/pkg/scan/rest/v1/client_pool.go index f662ebd5e..6533473a8 100644 --- a/src/testing/pkg/scan/rest/v1/client_pool.go +++ b/src/testing/pkg/scan/rest/v1/client_pool.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package v1 @@ -16,6 +16,10 @@ type ClientPool struct { func (_m *ClientPool) Get(url string, authType string, accessCredential string, skipCertVerify bool) (v1.Client, error) { ret := _m.Called(url, authType, accessCredential, skipCertVerify) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 v1.Client var r1 error if rf, ok := ret.Get(0).(func(string, string, string, bool) (v1.Client, error)); ok { diff --git a/src/testing/pkg/scan/rest/v1/request_resolver.go b/src/testing/pkg/scan/rest/v1/request_resolver.go index 0f8ad927e..f5e67b78e 100644 --- a/src/testing/pkg/scan/rest/v1/request_resolver.go +++ b/src/testing/pkg/scan/rest/v1/request_resolver.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package v1 diff --git a/src/testing/pkg/scan/rest/v1/response_handler.go b/src/testing/pkg/scan/rest/v1/response_handler.go index 632f6c336..690934146 100644 --- a/src/testing/pkg/scan/rest/v1/response_handler.go +++ b/src/testing/pkg/scan/rest/v1/response_handler.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package v1 @@ -17,6 +17,10 @@ type responseHandler struct { func (_m *responseHandler) Execute(code int, resp *http.Response) ([]byte, error) { ret := _m.Called(code, resp) + if len(ret) == 0 { + panic("no return value specified for Execute") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(int, *http.Response) ([]byte, error)); ok { diff --git a/src/testing/pkg/scan/scanner/manager.go b/src/testing/pkg/scan/scanner/manager.go index aaadd9f7d..08890ace3 100644 --- a/src/testing/pkg/scan/scanner/manager.go +++ b/src/testing/pkg/scan/scanner/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package scanner @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *Manager) Create(ctx context.Context, registration *daoscanner.Registration) (string, error) { ret := _m.Called(ctx, registration) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(context.Context, *daoscanner.Registration) (string, error)); ok { @@ -68,6 +76,10 @@ func (_m *Manager) Create(ctx context.Context, registration *daoscanner.Registra func (_m *Manager) DefaultScannerUUID(ctx context.Context) (string, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for DefaultScannerUUID") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(context.Context) (string, error)); ok { @@ -92,6 +104,10 @@ func (_m *Manager) DefaultScannerUUID(ctx context.Context) (string, error) { func (_m *Manager) Delete(ctx context.Context, registrationUUID string) error { ret := _m.Called(ctx, registrationUUID) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, registrationUUID) @@ -106,6 +122,10 @@ func (_m *Manager) Delete(ctx context.Context, registrationUUID string) error { func (_m *Manager) Get(ctx context.Context, registrationUUID string) (*daoscanner.Registration, error) { ret := _m.Called(ctx, registrationUUID) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *daoscanner.Registration var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*daoscanner.Registration, error)); ok { @@ -132,6 +152,10 @@ func (_m *Manager) Get(ctx context.Context, registrationUUID string) (*daoscanne func (_m *Manager) GetDefault(ctx context.Context) (*daoscanner.Registration, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for GetDefault") + } + var r0 *daoscanner.Registration var r1 error if rf, ok := ret.Get(0).(func(context.Context) (*daoscanner.Registration, error)); ok { @@ -158,6 +182,10 @@ func (_m *Manager) GetDefault(ctx context.Context) (*daoscanner.Registration, er func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*daoscanner.Registration, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*daoscanner.Registration var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*daoscanner.Registration, error)); ok { @@ -184,6 +212,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*daoscanner.Regi func (_m *Manager) SetAsDefault(ctx context.Context, registrationUUID string) error { ret := _m.Called(ctx, registrationUUID) + if len(ret) == 0 { + panic("no return value specified for SetAsDefault") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, registrationUUID) @@ -198,6 +230,10 @@ func (_m *Manager) SetAsDefault(ctx context.Context, registrationUUID string) er func (_m *Manager) Update(ctx context.Context, registration *daoscanner.Registration) error { ret := _m.Called(ctx, registration) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *daoscanner.Registration) error); ok { r0 = rf(ctx, registration) diff --git a/src/testing/pkg/scheduler/scheduler.go b/src/testing/pkg/scheduler/scheduler.go index 437c7ca19..0550dcdf4 100644 --- a/src/testing/pkg/scheduler/scheduler.go +++ b/src/testing/pkg/scheduler/scheduler.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package scheduler @@ -20,6 +20,10 @@ type Scheduler struct { func (_m *Scheduler) CountSchedules(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for CountSchedules") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Scheduler) CountSchedules(ctx context.Context, query *q.Query) (int64, func (_m *Scheduler) GetSchedule(ctx context.Context, id int64) (*scheduler.Schedule, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for GetSchedule") + } + var r0 *scheduler.Schedule var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*scheduler.Schedule, error)); ok { @@ -70,6 +78,10 @@ func (_m *Scheduler) GetSchedule(ctx context.Context, id int64) (*scheduler.Sche func (_m *Scheduler) ListSchedules(ctx context.Context, query *q.Query) ([]*scheduler.Schedule, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListSchedules") + } + var r0 []*scheduler.Schedule var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*scheduler.Schedule, error)); ok { @@ -96,6 +108,10 @@ func (_m *Scheduler) ListSchedules(ctx context.Context, query *q.Query) ([]*sche func (_m *Scheduler) Schedule(ctx context.Context, vendorType string, vendorID int64, cronType string, cron string, callbackFuncName string, callbackFuncParams interface{}, extraAttrs map[string]interface{}) (int64, error) { ret := _m.Called(ctx, vendorType, vendorID, cronType, cron, callbackFuncName, callbackFuncParams, extraAttrs) + if len(ret) == 0 { + panic("no return value specified for Schedule") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64, string, string, string, interface{}, map[string]interface{}) (int64, error)); ok { @@ -120,6 +136,10 @@ func (_m *Scheduler) Schedule(ctx context.Context, vendorType string, vendorID i func (_m *Scheduler) UnScheduleByID(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for UnScheduleByID") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -134,6 +154,10 @@ func (_m *Scheduler) UnScheduleByID(ctx context.Context, id int64) error { func (_m *Scheduler) UnScheduleByVendor(ctx context.Context, vendorType string, vendorID int64) error { ret := _m.Called(ctx, vendorType, vendorID) + if len(ret) == 0 { + panic("no return value specified for UnScheduleByVendor") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, int64) error); ok { r0 = rf(ctx, vendorType, vendorID) diff --git a/src/testing/pkg/securityhub/manager.go b/src/testing/pkg/securityhub/manager.go index 9b47578c0..5050fb40d 100644 --- a/src/testing/pkg/securityhub/manager.go +++ b/src/testing/pkg/securityhub/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package securityhub @@ -22,6 +22,10 @@ type Manager struct { func (_m *Manager) DangerousArtifacts(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) ([]*model.DangerousArtifact, error) { ret := _m.Called(ctx, scannerUUID, projectID, query) + if len(ret) == 0 { + panic("no return value specified for DangerousArtifacts") + } + var r0 []*model.DangerousArtifact var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64, *q.Query) ([]*model.DangerousArtifact, error)); ok { @@ -48,6 +52,10 @@ func (_m *Manager) DangerousArtifacts(ctx context.Context, scannerUUID string, p func (_m *Manager) DangerousCVEs(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) ([]*scan.VulnerabilityRecord, error) { ret := _m.Called(ctx, scannerUUID, projectID, query) + if len(ret) == 0 { + panic("no return value specified for DangerousCVEs") + } + var r0 []*scan.VulnerabilityRecord var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64, *q.Query) ([]*scan.VulnerabilityRecord, error)); ok { @@ -74,6 +82,10 @@ func (_m *Manager) DangerousCVEs(ctx context.Context, scannerUUID string, projec func (_m *Manager) ListVuls(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) ([]*model.VulnerabilityItem, error) { ret := _m.Called(ctx, scannerUUID, projectID, query) + if len(ret) == 0 { + panic("no return value specified for ListVuls") + } + var r0 []*model.VulnerabilityItem var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64, *q.Query) ([]*model.VulnerabilityItem, error)); ok { @@ -100,6 +112,10 @@ func (_m *Manager) ListVuls(ctx context.Context, scannerUUID string, projectID i func (_m *Manager) ScannedArtifactsCount(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) (int64, error) { ret := _m.Called(ctx, scannerUUID, projectID, query) + if len(ret) == 0 { + panic("no return value specified for ScannedArtifactsCount") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64, *q.Query) (int64, error)); ok { @@ -124,6 +140,10 @@ func (_m *Manager) ScannedArtifactsCount(ctx context.Context, scannerUUID string func (_m *Manager) Summary(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) (*model.Summary, error) { ret := _m.Called(ctx, scannerUUID, projectID, query) + if len(ret) == 0 { + panic("no return value specified for Summary") + } + var r0 *model.Summary var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64, *q.Query) (*model.Summary, error)); ok { @@ -150,6 +170,10 @@ func (_m *Manager) Summary(ctx context.Context, scannerUUID string, projectID in func (_m *Manager) TotalArtifactsCount(ctx context.Context, projectID int64) (int64, error) { ret := _m.Called(ctx, projectID) + if len(ret) == 0 { + panic("no return value specified for TotalArtifactsCount") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (int64, error)); ok { @@ -174,6 +198,10 @@ func (_m *Manager) TotalArtifactsCount(ctx context.Context, projectID int64) (in func (_m *Manager) TotalVuls(ctx context.Context, scannerUUID string, projectID int64, tuneCount bool, query *q.Query) (int64, error) { ret := _m.Called(ctx, scannerUUID, projectID, tuneCount, query) + if len(ret) == 0 { + panic("no return value specified for TotalVuls") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64, bool, *q.Query) (int64, error)); ok { diff --git a/src/testing/pkg/systemartifact/cleanup/selector.go b/src/testing/pkg/systemartifact/cleanup/selector.go index 5ca86ca76..68b3f8f20 100644 --- a/src/testing/pkg/systemartifact/cleanup/selector.go +++ b/src/testing/pkg/systemartifact/cleanup/selector.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package cleanup @@ -20,6 +20,10 @@ type Selector struct { func (_m *Selector) List(ctx context.Context) ([]*model.SystemArtifact, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.SystemArtifact var r1 error if rf, ok := ret.Get(0).(func(context.Context) ([]*model.SystemArtifact, error)); ok { @@ -46,6 +50,10 @@ func (_m *Selector) List(ctx context.Context) ([]*model.SystemArtifact, error) { func (_m *Selector) ListWithFilters(ctx context.Context, query *q.Query) ([]*model.SystemArtifact, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for ListWithFilters") + } + var r0 []*model.SystemArtifact var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.SystemArtifact, error)); ok { diff --git a/src/testing/pkg/systemartifact/dao/dao.go b/src/testing/pkg/systemartifact/dao/dao.go index 3222028dd..1d49abae3 100644 --- a/src/testing/pkg/systemartifact/dao/dao.go +++ b/src/testing/pkg/systemartifact/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -21,6 +21,10 @@ type DAO struct { func (_m *DAO) Create(ctx context.Context, systemArtifact *model.SystemArtifact) (int64, error) { ret := _m.Called(ctx, systemArtifact) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.SystemArtifact) (int64, error)); ok { @@ -45,6 +49,10 @@ func (_m *DAO) Create(ctx context.Context, systemArtifact *model.SystemArtifact) func (_m *DAO) Delete(ctx context.Context, vendor string, repository string, digest string) error { ret := _m.Called(ctx, vendor, repository, digest) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { r0 = rf(ctx, vendor, repository, digest) @@ -59,6 +67,10 @@ func (_m *DAO) Delete(ctx context.Context, vendor string, repository string, dig func (_m *DAO) Get(ctx context.Context, vendor string, repository string, digest string) (*model.SystemArtifact, error) { ret := _m.Called(ctx, vendor, repository, digest) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.SystemArtifact var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string) (*model.SystemArtifact, error)); ok { @@ -85,6 +97,10 @@ func (_m *DAO) Get(ctx context.Context, vendor string, repository string, digest func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.SystemArtifact, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.SystemArtifact var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.SystemArtifact, error)); ok { @@ -111,6 +127,10 @@ func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*model.SystemArtifac func (_m *DAO) Size(ctx context.Context) (int64, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for Size") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context) (int64, error)); ok { diff --git a/src/testing/pkg/systemartifact/manager.go b/src/testing/pkg/systemartifact/manager.go index 392d2803b..1af03bee8 100644 --- a/src/testing/pkg/systemartifact/manager.go +++ b/src/testing/pkg/systemartifact/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package systemartifact @@ -22,6 +22,10 @@ type Manager struct { func (_m *Manager) Cleanup(ctx context.Context) (int64, int64, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for Cleanup") + } + var r0 int64 var r1 int64 var r2 error @@ -53,6 +57,10 @@ func (_m *Manager) Cleanup(ctx context.Context) (int64, int64, error) { func (_m *Manager) Create(ctx context.Context, artifactRecord *model.SystemArtifact, reader io.Reader) (int64, error) { ret := _m.Called(ctx, artifactRecord, reader) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.SystemArtifact, io.Reader) (int64, error)); ok { @@ -77,6 +85,10 @@ func (_m *Manager) Create(ctx context.Context, artifactRecord *model.SystemArtif func (_m *Manager) Delete(ctx context.Context, vendor string, repository string, digest string) error { ret := _m.Called(ctx, vendor, repository, digest) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { r0 = rf(ctx, vendor, repository, digest) @@ -91,6 +103,10 @@ func (_m *Manager) Delete(ctx context.Context, vendor string, repository string, func (_m *Manager) Exists(ctx context.Context, vendor string, repository string, digest string) (bool, error) { ret := _m.Called(ctx, vendor, repository, digest) + if len(ret) == 0 { + panic("no return value specified for Exists") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string) (bool, error)); ok { @@ -115,6 +131,10 @@ func (_m *Manager) Exists(ctx context.Context, vendor string, repository string, func (_m *Manager) GetCleanupCriteria(vendor string, artifactType string) systemartifact.Selector { ret := _m.Called(vendor, artifactType) + if len(ret) == 0 { + panic("no return value specified for GetCleanupCriteria") + } + var r0 systemartifact.Selector if rf, ok := ret.Get(0).(func(string, string) systemartifact.Selector); ok { r0 = rf(vendor, artifactType) @@ -131,6 +151,10 @@ func (_m *Manager) GetCleanupCriteria(vendor string, artifactType string) system func (_m *Manager) GetStorageSize(ctx context.Context) (int64, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for GetStorageSize") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context) (int64, error)); ok { @@ -155,6 +179,10 @@ func (_m *Manager) GetStorageSize(ctx context.Context) (int64, error) { func (_m *Manager) GetSystemArtifactProjectNames() []string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for GetSystemArtifactProjectNames") + } + var r0 []string if rf, ok := ret.Get(0).(func() []string); ok { r0 = rf() @@ -171,6 +199,10 @@ func (_m *Manager) GetSystemArtifactProjectNames() []string { func (_m *Manager) Read(ctx context.Context, vendor string, repository string, digest string) (io.ReadCloser, error) { ret := _m.Called(ctx, vendor, repository, digest) + if len(ret) == 0 { + panic("no return value specified for Read") + } + var r0 io.ReadCloser var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string) (io.ReadCloser, error)); ok { diff --git a/src/testing/pkg/task/execution_manager.go b/src/testing/pkg/task/execution_manager.go index 890aa6d4b..7960eea18 100644 --- a/src/testing/pkg/task/execution_manager.go +++ b/src/testing/pkg/task/execution_manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package task @@ -22,6 +22,10 @@ type ExecutionManager struct { func (_m *ExecutionManager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -53,6 +57,10 @@ func (_m *ExecutionManager) Create(ctx context.Context, vendorType string, vendo _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int64, string, ...map[string]interface{}) (int64, error)); ok { @@ -77,6 +85,10 @@ func (_m *ExecutionManager) Create(ctx context.Context, vendorType string, vendo func (_m *ExecutionManager) Delete(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -91,6 +103,10 @@ func (_m *ExecutionManager) Delete(ctx context.Context, id int64) error { func (_m *ExecutionManager) DeleteByVendor(ctx context.Context, vendorType string, vendorID int64) error { ret := _m.Called(ctx, vendorType, vendorID) + if len(ret) == 0 { + panic("no return value specified for DeleteByVendor") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, int64) error); ok { r0 = rf(ctx, vendorType, vendorID) @@ -105,6 +121,10 @@ func (_m *ExecutionManager) DeleteByVendor(ctx context.Context, vendorType strin func (_m *ExecutionManager) Get(ctx context.Context, id int64) (*task.Execution, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *task.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*task.Execution, error)); ok { @@ -131,6 +151,10 @@ func (_m *ExecutionManager) Get(ctx context.Context, id int64) (*task.Execution, func (_m *ExecutionManager) List(ctx context.Context, query *q.Query) ([]*task.Execution, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*task.Execution var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*task.Execution, error)); ok { @@ -157,6 +181,10 @@ func (_m *ExecutionManager) List(ctx context.Context, query *q.Query) ([]*task.E func (_m *ExecutionManager) MarkDone(ctx context.Context, id int64, message string) error { ret := _m.Called(ctx, id, message) + if len(ret) == 0 { + panic("no return value specified for MarkDone") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, string) error); ok { r0 = rf(ctx, id, message) @@ -171,6 +199,10 @@ func (_m *ExecutionManager) MarkDone(ctx context.Context, id int64, message stri func (_m *ExecutionManager) MarkError(ctx context.Context, id int64, message string) error { ret := _m.Called(ctx, id, message) + if len(ret) == 0 { + panic("no return value specified for MarkError") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, string) error); ok { r0 = rf(ctx, id, message) @@ -185,6 +217,10 @@ func (_m *ExecutionManager) MarkError(ctx context.Context, id int64, message str func (_m *ExecutionManager) Stop(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Stop") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -199,6 +235,10 @@ func (_m *ExecutionManager) Stop(ctx context.Context, id int64) error { func (_m *ExecutionManager) StopAndWait(ctx context.Context, id int64, timeout time.Duration) error { ret := _m.Called(ctx, id, timeout) + if len(ret) == 0 { + panic("no return value specified for StopAndWait") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, time.Duration) error); ok { r0 = rf(ctx, id, timeout) @@ -213,6 +253,10 @@ func (_m *ExecutionManager) StopAndWait(ctx context.Context, id int64, timeout t func (_m *ExecutionManager) StopAndWaitWithError(ctx context.Context, id int64, timeout time.Duration, origError error) error { ret := _m.Called(ctx, id, timeout, origError) + if len(ret) == 0 { + panic("no return value specified for StopAndWaitWithError") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, time.Duration, error) error); ok { r0 = rf(ctx, id, timeout, origError) @@ -227,6 +271,10 @@ func (_m *ExecutionManager) StopAndWaitWithError(ctx context.Context, id int64, func (_m *ExecutionManager) UpdateExtraAttrs(ctx context.Context, id int64, extraAttrs map[string]interface{}) error { ret := _m.Called(ctx, id, extraAttrs) + if len(ret) == 0 { + panic("no return value specified for UpdateExtraAttrs") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, map[string]interface{}) error); ok { r0 = rf(ctx, id, extraAttrs) diff --git a/src/testing/pkg/task/manager.go b/src/testing/pkg/task/manager.go index 9e8dbbd16..d1172aa69 100644 --- a/src/testing/pkg/task/manager.go +++ b/src/testing/pkg/task/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package task @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -51,6 +55,10 @@ func (_m *Manager) Create(ctx context.Context, executionID int64, job *task.Job, _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, *task.Job, ...map[string]interface{}) (int64, error)); ok { @@ -75,6 +83,10 @@ func (_m *Manager) Create(ctx context.Context, executionID int64, job *task.Job, func (_m *Manager) ExecutionIDsByVendorAndStatus(ctx context.Context, vendorType string, status string) ([]int64, error) { ret := _m.Called(ctx, vendorType, status) + if len(ret) == 0 { + panic("no return value specified for ExecutionIDsByVendorAndStatus") + } + var r0 []int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]int64, error)); ok { @@ -101,6 +113,10 @@ func (_m *Manager) ExecutionIDsByVendorAndStatus(ctx context.Context, vendorType func (_m *Manager) Get(ctx context.Context, id int64) (*task.Task, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *task.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*task.Task, error)); ok { @@ -127,6 +143,10 @@ func (_m *Manager) Get(ctx context.Context, id int64) (*task.Task, error) { func (_m *Manager) GetLog(ctx context.Context, id int64) ([]byte, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for GetLog") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) ([]byte, error)); ok { @@ -153,6 +173,10 @@ func (_m *Manager) GetLog(ctx context.Context, id int64) ([]byte, error) { func (_m *Manager) GetLogByJobID(ctx context.Context, jobID string) ([]byte, error) { ret := _m.Called(ctx, jobID) + if len(ret) == 0 { + panic("no return value specified for GetLogByJobID") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]byte, error)); ok { @@ -179,6 +203,10 @@ func (_m *Manager) GetLogByJobID(ctx context.Context, jobID string) ([]byte, err func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*task.Task, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*task.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*task.Task, error)); ok { @@ -205,6 +233,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*task.Task, erro func (_m *Manager) ListScanTasksByReportUUID(ctx context.Context, uuid string) ([]*task.Task, error) { ret := _m.Called(ctx, uuid) + if len(ret) == 0 { + panic("no return value specified for ListScanTasksByReportUUID") + } + var r0 []*task.Task var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]*task.Task, error)); ok { @@ -231,6 +263,10 @@ func (_m *Manager) ListScanTasksByReportUUID(ctx context.Context, uuid string) ( func (_m *Manager) Stop(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Stop") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -252,6 +288,10 @@ func (_m *Manager) Update(ctx context.Context, _a1 *task.Task, props ...string) _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *task.Task, ...string) error); ok { r0 = rf(ctx, _a1, props...) @@ -266,6 +306,10 @@ func (_m *Manager) Update(ctx context.Context, _a1 *task.Task, props ...string) func (_m *Manager) UpdateExtraAttrs(ctx context.Context, id int64, extraAttrs map[string]interface{}) error { ret := _m.Called(ctx, id, extraAttrs) + if len(ret) == 0 { + panic("no return value specified for UpdateExtraAttrs") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, map[string]interface{}) error); ok { r0 = rf(ctx, id, extraAttrs) @@ -280,6 +324,10 @@ func (_m *Manager) UpdateExtraAttrs(ctx context.Context, id int64, extraAttrs ma func (_m *Manager) UpdateStatusInBatch(ctx context.Context, jobIDs []string, status string, batchSize int) error { ret := _m.Called(ctx, jobIDs, status, batchSize) + if len(ret) == 0 { + panic("no return value specified for UpdateStatusInBatch") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, []string, string, int) error); ok { r0 = rf(ctx, jobIDs, status, batchSize) diff --git a/src/testing/pkg/user/dao/dao.go b/src/testing/pkg/user/dao/dao.go index 89bcb213a..f46ec54d5 100644 --- a/src/testing/pkg/user/dao/dao.go +++ b/src/testing/pkg/user/dao/dao.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package dao @@ -21,6 +21,10 @@ type DAO struct { func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -45,6 +49,10 @@ func (_m *DAO) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *DAO) Create(ctx context.Context, user *models.User) (int, error) { ret := _m.Called(ctx, user) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int var r1 error if rf, ok := ret.Get(0).(func(context.Context, *models.User) (int, error)); ok { @@ -69,6 +77,10 @@ func (_m *DAO) Create(ctx context.Context, user *models.User) (int, error) { func (_m *DAO) Delete(ctx context.Context, userID int) error { ret := _m.Called(ctx, userID) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int) error); ok { r0 = rf(ctx, userID) @@ -83,6 +95,10 @@ func (_m *DAO) Delete(ctx context.Context, userID int) error { func (_m *DAO) List(ctx context.Context, query *q.Query) ([]*models.User, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*models.User var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*models.User, error)); ok { @@ -116,6 +132,10 @@ func (_m *DAO) Update(ctx context.Context, user *models.User, props ...string) e _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.User, ...string) error); ok { r0 = rf(ctx, user, props...) diff --git a/src/testing/pkg/user/manager.go b/src/testing/pkg/user/manager.go index 83b4f9d77..cee323805 100644 --- a/src/testing/pkg/user/manager.go +++ b/src/testing/pkg/user/manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package user @@ -30,6 +30,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query, options ...models. _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query, ...models.Option) (int64, error)); ok { @@ -54,6 +58,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query, options ...models. func (_m *Manager) Create(ctx context.Context, _a1 *commonmodels.User) (int, error) { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int var r1 error if rf, ok := ret.Get(0).(func(context.Context, *commonmodels.User) (int, error)); ok { @@ -78,6 +86,10 @@ func (_m *Manager) Create(ctx context.Context, _a1 *commonmodels.User) (int, err func (_m *Manager) Delete(ctx context.Context, id int) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int) error); ok { r0 = rf(ctx, id) @@ -92,6 +104,10 @@ func (_m *Manager) Delete(ctx context.Context, id int) error { func (_m *Manager) DeleteGDPR(ctx context.Context, id int) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for DeleteGDPR") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int) error); ok { r0 = rf(ctx, id) @@ -106,6 +122,10 @@ func (_m *Manager) DeleteGDPR(ctx context.Context, id int) error { func (_m *Manager) GenerateCheckSum(in string) string { ret := _m.Called(in) + if len(ret) == 0 { + panic("no return value specified for GenerateCheckSum") + } + var r0 string if rf, ok := ret.Get(0).(func(string) string); ok { r0 = rf(in) @@ -120,6 +140,10 @@ func (_m *Manager) GenerateCheckSum(in string) string { func (_m *Manager) Get(ctx context.Context, id int) (*commonmodels.User, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *commonmodels.User var r1 error if rf, ok := ret.Get(0).(func(context.Context, int) (*commonmodels.User, error)); ok { @@ -146,6 +170,10 @@ func (_m *Manager) Get(ctx context.Context, id int) (*commonmodels.User, error) func (_m *Manager) GetByName(ctx context.Context, username string) (*commonmodels.User, error) { ret := _m.Called(ctx, username) + if len(ret) == 0 { + panic("no return value specified for GetByName") + } + var r0 *commonmodels.User var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*commonmodels.User, error)); ok { @@ -179,6 +207,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query, options ...models.O _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 commonmodels.Users var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query, ...models.Option) (commonmodels.Users, error)); ok { @@ -205,6 +237,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query, options ...models.O func (_m *Manager) MatchLocalPassword(ctx context.Context, username string, password string) (*commonmodels.User, error) { ret := _m.Called(ctx, username, password) + if len(ret) == 0 { + panic("no return value specified for MatchLocalPassword") + } + var r0 *commonmodels.User var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (*commonmodels.User, error)); ok { @@ -231,6 +267,10 @@ func (_m *Manager) MatchLocalPassword(ctx context.Context, username string, pass func (_m *Manager) Onboard(ctx context.Context, _a1 *commonmodels.User) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Onboard") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *commonmodels.User) error); ok { r0 = rf(ctx, _a1) @@ -245,6 +285,10 @@ func (_m *Manager) Onboard(ctx context.Context, _a1 *commonmodels.User) error { func (_m *Manager) SetSysAdminFlag(ctx context.Context, id int, admin bool) error { ret := _m.Called(ctx, id, admin) + if len(ret) == 0 { + panic("no return value specified for SetSysAdminFlag") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int, bool) error); ok { r0 = rf(ctx, id, admin) @@ -259,6 +303,10 @@ func (_m *Manager) SetSysAdminFlag(ctx context.Context, id int, admin bool) erro func (_m *Manager) UpdatePassword(ctx context.Context, id int, newPassword string) error { ret := _m.Called(ctx, id, newPassword) + if len(ret) == 0 { + panic("no return value specified for UpdatePassword") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int, string) error); ok { r0 = rf(ctx, id, newPassword) @@ -280,6 +328,10 @@ func (_m *Manager) UpdateProfile(ctx context.Context, _a1 *commonmodels.User, co _ca = append(_ca, _va...) ret := _m.Called(_ca...) + if len(ret) == 0 { + panic("no return value specified for UpdateProfile") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *commonmodels.User, ...string) error); ok { r0 = rf(ctx, _a1, col...) diff --git a/src/testing/pkg/usergroup/fake_usergroup_manager.go b/src/testing/pkg/usergroup/fake_usergroup_manager.go index 8d2b83a63..8f1bf2d8f 100644 --- a/src/testing/pkg/usergroup/fake_usergroup_manager.go +++ b/src/testing/pkg/usergroup/fake_usergroup_manager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.35.4. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package usergroup @@ -20,6 +20,10 @@ type Manager struct { func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) (int64, error)); ok { @@ -44,6 +48,10 @@ func (_m *Manager) Count(ctx context.Context, query *q.Query) (int64, error) { func (_m *Manager) Create(ctx context.Context, userGroup model.UserGroup) (int, error) { ret := _m.Called(ctx, userGroup) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 int var r1 error if rf, ok := ret.Get(0).(func(context.Context, model.UserGroup) (int, error)); ok { @@ -68,6 +76,10 @@ func (_m *Manager) Create(ctx context.Context, userGroup model.UserGroup) (int, func (_m *Manager) Delete(ctx context.Context, id int) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int) error); ok { r0 = rf(ctx, id) @@ -82,6 +94,10 @@ func (_m *Manager) Delete(ctx context.Context, id int) error { func (_m *Manager) Get(ctx context.Context, id int) (*model.UserGroup, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.UserGroup var r1 error if rf, ok := ret.Get(0).(func(context.Context, int) (*model.UserGroup, error)); ok { @@ -108,6 +124,10 @@ func (_m *Manager) Get(ctx context.Context, id int) (*model.UserGroup, error) { func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.UserGroup, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*model.UserGroup var r1 error if rf, ok := ret.Get(0).(func(context.Context, *q.Query) ([]*model.UserGroup, error)); ok { @@ -134,6 +154,10 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*model.UserGroup func (_m *Manager) Onboard(ctx context.Context, g *model.UserGroup) error { ret := _m.Called(ctx, g) + if len(ret) == 0 { + panic("no return value specified for Onboard") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.UserGroup) error); ok { r0 = rf(ctx, g) @@ -148,6 +172,10 @@ func (_m *Manager) Onboard(ctx context.Context, g *model.UserGroup) error { func (_m *Manager) Populate(ctx context.Context, userGroups []model.UserGroup) ([]int, error) { ret := _m.Called(ctx, userGroups) + if len(ret) == 0 { + panic("no return value specified for Populate") + } + var r0 []int var r1 error if rf, ok := ret.Get(0).(func(context.Context, []model.UserGroup) ([]int, error)); ok { @@ -174,6 +202,10 @@ func (_m *Manager) Populate(ctx context.Context, userGroups []model.UserGroup) ( func (_m *Manager) UpdateName(ctx context.Context, id int, groupName string) error { ret := _m.Called(ctx, id, groupName) + if len(ret) == 0 { + panic("no return value specified for UpdateName") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int, string) error); ok { r0 = rf(ctx, id, groupName) From 7e8032b14441f0dbd6e8b601620c3d7f3379f144 Mon Sep 17 00:00:00 2001 From: MinerYang Date: Fri, 12 Apr 2024 13:46:29 +0800 Subject: [PATCH 083/145] bump golang to 1.22.2 (#20256) Signed-off-by: yminer replace go get to go install update go.mod --- .github/workflows/CI.yml | 20 +++++++++---------- .github/workflows/build-package.yml | 4 ++-- .github/workflows/codeql-analysis.yml | 3 +++ .github/workflows/conformance_test.yml | 2 +- CONTRIBUTING.md | 3 ++- Makefile | 2 +- make/photon/registry/Dockerfile.binary | 2 +- make/photon/trivy-adapter/Dockerfile.binary | 2 +- make/photon/trivy-adapter/builder.sh | 2 +- src/go.mod | 2 +- tests/ci/distro_installer.sh | 4 ++-- tests/ci/ut_install.sh | 22 ++++++++++----------- 12 files changed, 36 insertions(+), 32 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index ea867a8ce..94ec2377b 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -41,10 +41,10 @@ jobs: - ubuntu-latest timeout-minutes: 100 steps: - - name: Set up Go 1.21 + - name: Set up Go 1.22 uses: actions/setup-go@v5 with: - go-version: 1.21.8 + go-version: 1.22.2 id: go - uses: actions/checkout@v3 with: @@ -102,10 +102,10 @@ jobs: - ubuntu-latest timeout-minutes: 100 steps: - - name: Set up Go 1.21 + - name: Set up Go 1.22 uses: actions/setup-go@v5 with: - go-version: 1.21.8 + go-version: 1.22.2 id: go - uses: actions/checkout@v3 with: @@ -157,10 +157,10 @@ jobs: - ubuntu-latest timeout-minutes: 100 steps: - - name: Set up Go 1.21 + - name: Set up Go 1.22 uses: actions/setup-go@v5 with: - go-version: 1.21.8 + go-version: 1.22.2 id: go - uses: actions/checkout@v3 with: @@ -212,10 +212,10 @@ jobs: - ubuntu-latest timeout-minutes: 100 steps: - - name: Set up Go 1.21 + - name: Set up Go 1.22 uses: actions/setup-go@v5 with: - go-version: 1.21.8 + go-version: 1.22.2 id: go - uses: actions/checkout@v3 with: @@ -265,10 +265,10 @@ jobs: - ubuntu-latest timeout-minutes: 100 steps: - - name: Set up Go 1.21 + - name: Set up Go 1.22 uses: actions/setup-go@v5 with: - go-version: 1.21.8 + go-version: 1.22.2 id: go - uses: actions/checkout@v3 with: diff --git a/.github/workflows/build-package.yml b/.github/workflows/build-package.yml index 74f0b99ab..6bd029b70 100644 --- a/.github/workflows/build-package.yml +++ b/.github/workflows/build-package.yml @@ -23,10 +23,10 @@ jobs: with: version: '430.0.0' - run: gcloud info - - name: Set up Go 1.21 + - name: Set up Go 1.22 uses: actions/setup-go@v5 with: - go-version: 1.21.8 + go-version: 1.22.2 id: go - name: Setup Docker uses: docker-practice/actions-setup-docker@master diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 9dfb26473..f97fcebcd 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -47,5 +47,8 @@ jobs: # make bootstrap # make release + # to make sure autobuild success, specifify golang version in go.mod + # https://github.com/github/codeql/issues/15647#issuecomment-2003768106 + - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/conformance_test.yml b/.github/workflows/conformance_test.yml index 520f417f4..b1183ca80 100644 --- a/.github/workflows/conformance_test.yml +++ b/.github/workflows/conformance_test.yml @@ -28,7 +28,7 @@ jobs: - name: Set up Go 1.21 uses: actions/setup-go@v5 with: - go-version: 1.21.8 + go-version: 1.22.2 id: go - uses: actions/checkout@v3 with: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2af5023b8..a7cda9d19 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -164,7 +164,8 @@ Harbor backend is written in [Go](http://golang.org/). If you don't have a Harbo | 2.7 | 1.19.4 | | 2.8 | 1.20.6 | | 2.9 | 1.21.3 | -| 2.10 | 1.21.8 | +| 2.10 | 1.21.8 | +| 2.11 | 1.22.2 | Ensure your GOPATH and PATH have been configured in accordance with the Go environment instructions. diff --git a/Makefile b/Makefile index 16a532a52..e9dd6f7dd 100644 --- a/Makefile +++ b/Makefile @@ -140,7 +140,7 @@ GOINSTALL=$(GOCMD) install GOTEST=$(GOCMD) test GODEP=$(GOTEST) -i GOFMT=gofmt -w -GOBUILDIMAGE=golang:1.21.8 +GOBUILDIMAGE=golang:1.22.2 GOBUILDPATHINCONTAINER=/harbor # go build diff --git a/make/photon/registry/Dockerfile.binary b/make/photon/registry/Dockerfile.binary index c18fb9e09..e5efd1013 100644 --- a/make/photon/registry/Dockerfile.binary +++ b/make/photon/registry/Dockerfile.binary @@ -1,4 +1,4 @@ -FROM golang:1.21.8 +FROM golang:1.22.2 ENV DISTRIBUTION_DIR /go/src/github.com/docker/distribution ENV BUILDTAGS include_oss include_gcs diff --git a/make/photon/trivy-adapter/Dockerfile.binary b/make/photon/trivy-adapter/Dockerfile.binary index 65bc9e9a5..fad616100 100644 --- a/make/photon/trivy-adapter/Dockerfile.binary +++ b/make/photon/trivy-adapter/Dockerfile.binary @@ -1,4 +1,4 @@ -FROM golang:1.21.8 +FROM golang:1.22.2 ADD . /go/src/github.com/aquasecurity/harbor-scanner-trivy/ WORKDIR /go/src/github.com/aquasecurity/harbor-scanner-trivy/ diff --git a/make/photon/trivy-adapter/builder.sh b/make/photon/trivy-adapter/builder.sh index debed09a5..e9eadf182 100755 --- a/make/photon/trivy-adapter/builder.sh +++ b/make/photon/trivy-adapter/builder.sh @@ -19,7 +19,7 @@ TEMP=$(mktemp -d ${TMPDIR-/tmp}/trivy-adapter.XXXXXX) git clone https://github.com/aquasecurity/harbor-scanner-trivy.git $TEMP cd $TEMP; git checkout $VERSION; cd - -echo "Building Trivy adapter binary based on golang:1.21.8..." +echo "Building Trivy adapter binary based on golang:1.22.2..." cp Dockerfile.binary $TEMP docker build -f $TEMP/Dockerfile.binary -t trivy-adapter-golang $TEMP diff --git a/src/go.mod b/src/go.mod index 7e439c773..00a328798 100644 --- a/src/go.mod +++ b/src/go.mod @@ -1,6 +1,6 @@ module github.com/goharbor/harbor/src -go 1.21 +go 1.22.2 require ( github.com/FZambia/sentinel v1.1.0 diff --git a/tests/ci/distro_installer.sh b/tests/ci/distro_installer.sh index ae10a5f85..af89f4471 100755 --- a/tests/ci/distro_installer.sh +++ b/tests/ci/distro_installer.sh @@ -3,5 +3,5 @@ set -x set -e -sudo make package_online GOBUILDTAGS="include_oss include_gcs" VERSIONTAG=dev-gitaction PKGVERSIONTAG=dev-gitaction UIVERSIONTAG=dev-gitaction GOBUILDIMAGE=golang:1.21.8 COMPILETAG=compile_golangimage TRIVYFLAG=true HTTPPROXY= PULL_BASE_FROM_DOCKERHUB=false -sudo make package_offline GOBUILDTAGS="include_oss include_gcs" VERSIONTAG=dev-gitaction PKGVERSIONTAG=dev-gitaction UIVERSIONTAG=dev-gitaction GOBUILDIMAGE=golang:1.21.8 COMPILETAG=compile_golangimage TRIVYFLAG=true HTTPPROXY= PULL_BASE_FROM_DOCKERHUB=false +sudo make package_online GOBUILDTAGS="include_oss include_gcs" VERSIONTAG=dev-gitaction PKGVERSIONTAG=dev-gitaction UIVERSIONTAG=dev-gitaction GOBUILDIMAGE=golang:1.22.2 COMPILETAG=compile_golangimage TRIVYFLAG=true HTTPPROXY= PULL_BASE_FROM_DOCKERHUB=false +sudo make package_offline GOBUILDTAGS="include_oss include_gcs" VERSIONTAG=dev-gitaction PKGVERSIONTAG=dev-gitaction UIVERSIONTAG=dev-gitaction GOBUILDIMAGE=golang:1.22.2 COMPILETAG=compile_golangimage TRIVYFLAG=true HTTPPROXY= PULL_BASE_FROM_DOCKERHUB=false diff --git a/tests/ci/ut_install.sh b/tests/ci/ut_install.sh index 38cfa8cf2..6702bb3aa 100755 --- a/tests/ci/ut_install.sh +++ b/tests/ci/ut_install.sh @@ -5,19 +5,19 @@ set -e sudo apt-get update && sudo apt-get install -y libldap2-dev sudo go env -w GO111MODULE=auto -go get github.com/docker/distribution -go get github.com/docker/libtrust -go get golang.org/x/lint/golint -go get github.com/GeertJohan/fgt -go get github.com/dghubble/sling -set +e -go get github.com/stretchr/testify -go get golang.org/x/tools/cmd/cover -go get github.com/mattn/goveralls -go get -u github.com/client9/misspell/cmd/misspell +pwd +# cd ./src +# go get github.com/docker/distribution@latest +# go get github.com/docker/libtrust@latest +# set +e +# go get github.com/stretchr/testify@v1.8.4 +go install golang.org/x/tools/cmd/cover@latest +go install github.com/mattn/goveralls@latest +go install github.com/client9/misspell/cmd/misspell@latest set -e +# cd ../ # binary will be $(go env GOPATH)/bin/golangci-lint -# go install/go get installation aren't guaranteed to work. We recommend using binary installation. +# go get installation aren't guaranteed to work. We recommend using binary installation. curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.55.2 sudo service postgresql stop || echo no postgresql need to be stopped sleep 2 From 7465a29919da8ed4fa132bdbc217806369e3bf38 Mon Sep 17 00:00:00 2001 From: MinerYang Date: Fri, 12 Apr 2024 20:12:46 +0800 Subject: [PATCH 084/145] add SBOM icon (#20270) Signed-off-by: yminer --- icons/sbom.png | Bin 0 -> 120331 bytes src/controller/icon/controller.go | 4 ++++ src/lib/icon/const.go | 1 + src/pkg/accessory/manager.go | 1 + 4 files changed, 6 insertions(+) create mode 100644 icons/sbom.png diff --git a/icons/sbom.png b/icons/sbom.png new file mode 100644 index 0000000000000000000000000000000000000000..8903142fb226fa6db1fde21590e1d63d84761371 GIT binary patch literal 120331 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%0D^gbM@X^j5vA`EBmtv*XLH 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 Date: Mon, 15 Apr 2024 16:47:55 +0800 Subject: [PATCH 085/145] Bump k8s.io/api from 0.29.0 to 0.29.3 in /src (#20205) Bumps [k8s.io/api](https://github.com/kubernetes/api) from 0.29.0 to 0.29.3. - [Commits](https://github.com/kubernetes/api/compare/v0.29.0...v0.29.3) --- updated-dependencies: - dependency-name: k8s.io/api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- src/go.mod | 6 +++--- src/go.sum | 19 +++++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/go.mod b/src/go.mod index 00a328798..fe3c17e60 100644 --- a/src/go.mod +++ b/src/go.mod @@ -72,8 +72,8 @@ require ( gopkg.in/h2non/gock.v1 v1.1.2 gopkg.in/yaml.v2 v2.4.0 helm.sh/helm/v3 v3.14.2 - k8s.io/api v0.29.0 - k8s.io/apimachinery v0.29.0 + k8s.io/api v0.29.3 + k8s.io/apimachinery v0.29.3 k8s.io/client-go v0.29.0 sigs.k8s.io/yaml v1.4.0 ) @@ -115,7 +115,7 @@ require ( github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.4.2 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/gorilla/securecookie v1.1.1 // indirect diff --git a/src/go.sum b/src/go.sum index 8081c0f92..b9cb6294f 100644 --- a/src/go.sum +++ b/src/go.sum @@ -260,8 +260,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v1.8.8 h1:f6cXq6RRfiyrOJEV7p3JhLDlmawGBVBBP1MggY8Mo4E= github.com/gomodule/redigo v1.8.8/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= @@ -703,6 +703,7 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -817,6 +818,7 @@ 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.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -825,6 +827,7 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX 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.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -872,8 +875,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= -golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= +golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -968,10 +971,10 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/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.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A= -k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA= -k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o= -k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis= +k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw= +k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80= +k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU= +k8s.io/apimachinery v0.29.3/go.mod h1:hx/S4V2PNW4OMg3WizRrHutyB5la0iCUbZym+W0EQIU= k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8= k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38= k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= From b8392968ac93a759b7ef7076421350fea15b0f59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Apr 2024 18:18:53 +0800 Subject: [PATCH 086/145] Bump github.com/coreos/go-oidc/v3 from 3.9.0 to 3.10.0 in /src (#20202) Bumps [github.com/coreos/go-oidc/v3](https://github.com/coreos/go-oidc) from 3.9.0 to 3.10.0. - [Release notes](https://github.com/coreos/go-oidc/releases) - [Commits](https://github.com/coreos/go-oidc/compare/v3.9.0...v3.10.0) --- updated-dependencies: - dependency-name: github.com/coreos/go-oidc/v3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- src/go.mod | 4 ++-- src/go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/go.mod b/src/go.mod index fe3c17e60..9d09b5d5e 100644 --- a/src/go.mod +++ b/src/go.mod @@ -14,7 +14,7 @@ require ( github.com/casbin/casbin v1.9.1 github.com/cenkalti/backoff/v4 v4.2.1 github.com/cloudevents/sdk-go/v2 v2.15.2 - github.com/coreos/go-oidc/v3 v3.9.0 + github.com/coreos/go-oidc/v3 v3.10.0 github.com/dghubble/sling v1.1.0 github.com/docker/distribution v2.8.2+incompatible github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 @@ -107,7 +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.4.9 // indirect - github.com/go-jose/go-jose/v3 v3.0.3 // indirect + github.com/go-jose/go-jose/v4 v4.0.1 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.21.4 // indirect diff --git a/src/go.sum b/src/go.sum index b9cb6294f..03e8e2d96 100644 --- a/src/go.sum +++ b/src/go.sum @@ -112,8 +112,8 @@ github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= -github.com/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo= -github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4= +github.com/coreos/go-oidc/v3 v3.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoEaJU= +github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -172,8 +172,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= -github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U= +github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A= From 79dbebd48d8df70d9d55c432ea96005451a933ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Apr 2024 19:04:22 +0800 Subject: [PATCH 087/145] Bump golang.org/x/oauth2 from 0.15.0 to 0.19.0 in /src (#20247) Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.15.0 to 0.19.0. - [Commits](https://github.com/golang/oauth2/compare/v0.15.0...v0.19.0) --- updated-dependencies: - dependency-name: golang.org/x/oauth2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU --- src/go.mod | 3 +-- src/go.sum | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/go.mod b/src/go.mod index 9d09b5d5e..e25f95586 100644 --- a/src/go.mod +++ b/src/go.mod @@ -65,7 +65,7 @@ require ( go.uber.org/ratelimit v0.2.0 golang.org/x/crypto v0.21.0 golang.org/x/net v0.22.0 - golang.org/x/oauth2 v0.15.0 + golang.org/x/oauth2 v0.19.0 golang.org/x/sync v0.6.0 golang.org/x/text v0.14.0 golang.org/x/time v0.5.0 @@ -174,7 +174,6 @@ require ( golang.org/x/sys v0.18.0 // indirect golang.org/x/term v0.18.0 // indirect google.golang.org/api v0.149.0 // indirect - google.golang.org/appengine v1.6.8 // indirect google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect diff --git a/src/go.sum b/src/go.sum index 03e8e2d96..3d452134c 100644 --- a/src/go.sum +++ b/src/go.sum @@ -764,8 +764,8 @@ golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= -golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= +golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= +golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -888,8 +888,6 @@ google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8 h1:Cpp2P6TPjujNoC5M2KHY6g7wfyLYfIWRZaSdIKfDasA= google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= From a2507dc3fc7d0335123ff896bf0bd8b0b2274389 Mon Sep 17 00:00:00 2001 From: Iceber Gu Date: Mon, 15 Apr 2024 20:37:59 +0800 Subject: [PATCH 088/145] Sending signals by closing the channel (#17917) Signed-off-by: Iceber Gu --- src/cmd/migrate-patch/main.go | 7 +++++-- src/core/main.go | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/cmd/migrate-patch/main.go b/src/cmd/migrate-patch/main.go index cb224ec5d..eb728b3a1 100644 --- a/src/cmd/migrate-patch/main.go +++ b/src/cmd/migrate-patch/main.go @@ -48,20 +48,23 @@ func main() { log.Fatalf("Failed to connect to Database, error: %v\n", err) } defer db.Close() - c := make(chan struct{}, 1) + + c := make(chan struct{}) go func() { + defer close(c) + err := db.Ping() for ; err != nil; err = db.Ping() { log.Println("Failed to Ping DB, sleep for 1 second.") time.Sleep(1 * time.Second) } - c <- struct{}{} }() select { case <-c: case <-time.After(30 * time.Second): log.Fatal("Failed to connect DB after 30 seconds, time out. \n") } + row := db.QueryRow(pgSQLCheckColStmt) var tblCount, colCount int if err := row.Scan(&tblCount, &colCount); err != nil { diff --git a/src/core/main.go b/src/core/main.go index cb2676135..ebc786d7e 100644 --- a/src/core/main.go +++ b/src/core/main.go @@ -104,14 +104,14 @@ func gracefulShutdown(closing, done chan struct{}, shutdowns ...func()) { signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) log.Infof("capture system signal %s, to close \"closing\" channel", <-signals) close(closing) - shutdownChan := make(chan struct{}, 1) + shutdownChan := make(chan struct{}) go func() { + defer close(shutdownChan) for _, s := range shutdowns { s() } <-done log.Infof("Goroutines exited normally") - shutdownChan <- struct{}{} }() select { case <-shutdownChan: From 938c80451359cce5058d5d096824eb0b125406e6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Apr 2024 10:11:19 +0800 Subject: [PATCH 089/145] Bump go.uber.org/ratelimit from 0.2.0 to 0.3.1 in /src (#20204) Bumps [go.uber.org/ratelimit](https://github.com/uber-go/ratelimit) from 0.2.0 to 0.3.1. - [Changelog](https://github.com/uber-go/ratelimit/blob/main/CHANGELOG.md) - [Commits](https://github.com/uber-go/ratelimit/compare/v0.2.0...v0.3.1) --- updated-dependencies: - dependency-name: go.uber.org/ratelimit dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shengwen YU Co-authored-by: Wang Yan --- src/go.mod | 4 ++-- src/go.sum | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/go.mod b/src/go.mod index e25f95586..9960d620e 100644 --- a/src/go.mod +++ b/src/go.mod @@ -62,7 +62,7 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 go.opentelemetry.io/otel/sdk v1.24.0 go.opentelemetry.io/otel/trace v1.24.0 - go.uber.org/ratelimit v0.2.0 + go.uber.org/ratelimit v0.3.1 golang.org/x/crypto v0.21.0 golang.org/x/net v0.22.0 golang.org/x/oauth2 v0.19.0 @@ -93,7 +93,7 @@ require ( github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Unknwon/goconfig v0.0.0-20160216183935-5f601ca6ef4d // indirect - github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // 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/containerd/stargz-snapshotter/estargz v0.14.3 // indirect diff --git a/src/go.sum b/src/go.sum index 3d452134c..077b14bcf 100644 --- a/src/go.sum +++ b/src/go.sum @@ -68,8 +68,6 @@ github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1L github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190726115642-cd293c93fd97 h1:bNE5ID4C3YOkROfvBjXJUG53gyb+8az3TQN02LqnGBk= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190726115642-cd293c93fd97/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ= github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= -github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI= -github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -85,8 +83,9 @@ github.com/beego/beego/v2 v2.0.6 h1:21Aqz3+RzUE1yP9a5xdU6LK54n9Z7NLEJtR4PE7NrPQ= github.com/beego/beego/v2 v2.0.6/go.mod h1:CH2/JIaB4ceGYVQlYqTAFft4pVk/ol1ZkakUrUvAyns= github.com/beego/i18n v0.0.0-20140604031826-e87155e8f0c0 h1:fQaDnUQvBXHHQdGBu9hz8nPznB4BeiPQokvmQVjmNEw= github.com/beego/i18n v0.0.0-20140604031826-e87155e8f0c0/go.mod h1:KLeFCpAMq2+50NkXC8iiJxLLiiTfTqrGtKEVm+2fk7s= -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -678,8 +677,8 @@ go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+ go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA= -go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg= +go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0= +go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= From 91efec1e2a4b1d434aa09d9472da9c1e292f0ab3 Mon Sep 17 00:00:00 2001 From: Shengwen YU Date: Tue, 16 Apr 2024 11:11:59 +0800 Subject: [PATCH 090/145] fix: update the image reference format for audit log when pulling image (#20278) Signed-off-by: Shengwen Yu --- src/controller/event/topic.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controller/event/topic.go b/src/controller/event/topic.go index 3514bb9dc..d099a8dbb 100644 --- a/src/controller/event/topic.go +++ b/src/controller/event/topic.go @@ -188,7 +188,7 @@ func (p *PullArtifactEvent) ResolveToAuditLog() (*model.AuditLog, error) { ResourceType: "artifact"} if len(p.Tags) == 0 { - auditLog.Resource = fmt.Sprintf("%s:%s", + auditLog.Resource = fmt.Sprintf("%s@%s", p.Artifact.RepositoryName, p.Artifact.Digest) } else { auditLog.Resource = fmt.Sprintf("%s:%s", From 550bf1d7509860e9628dcf52bb5e83e6d552269c Mon Sep 17 00:00:00 2001 From: Wang Yan Date: Tue, 16 Apr 2024 16:49:52 +0800 Subject: [PATCH 091/145] fix issue 20269 (#20274) By default, use the nvd score as the primary score, and if it is unavailable, fallback to the redhat score. fix #20269 Signed-off-by: wang yan --- .../scan/postprocessors/report_converters.go | 27 ++++++++++--------- .../postprocessors/report_converters_test.go | 2 ++ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/pkg/scan/postprocessors/report_converters.go b/src/pkg/scan/postprocessors/report_converters.go index 538b75ce7..0a91f41ad 100644 --- a/src/pkg/scan/postprocessors/report_converters.go +++ b/src/pkg/scan/postprocessors/report_converters.go @@ -354,25 +354,28 @@ func (c *nativeToRelationalSchemaConverter) updateReport(ctx context.Context, vu return report.Mgr.Update(ctx, r, "CriticalCnt", "HighCnt", "MediumCnt", "LowCnt", "NoneCnt", "UnknownCnt", "FixableCnt") } -// CVSS ... -type CVSS struct { - NVD Nvd `json:"nvd"` +// CVS ... +type CVS struct { + CVSS map[string]map[string]interface{} `json:"CVSS"` } -// Nvd ... -type Nvd struct { - V3Score float64 `json:"V3Score"` -} - -func parseScoreFromVendorAttribute(ctx context.Context, vendorAttribute string) (NvdV3Score float64) { - var data map[string]CVSS +func parseScoreFromVendorAttribute(ctx context.Context, vendorAttribute string) float64 { + var data CVS err := json.Unmarshal([]byte(vendorAttribute), &data) if err != nil { log.G(ctx).Errorf("failed to parse vendor_attribute, error %v", err) return 0 } - if cvss, ok := data["CVSS"]; ok { - return cvss.NVD.V3Score + + // set the nvd as the first priority, if it's unavailable, return the first V3Score available. + if val, ok := data.CVSS["nvd"]["V3Score"]; ok { + return val.(float64) + } + + for vendor := range data.CVSS { + if val, ok := data.CVSS[vendor]["V3Score"]; ok { + return val.(float64) + } } return 0 } diff --git a/src/pkg/scan/postprocessors/report_converters_test.go b/src/pkg/scan/postprocessors/report_converters_test.go index 057113a47..32b7660fa 100644 --- a/src/pkg/scan/postprocessors/report_converters_test.go +++ b/src/pkg/scan/postprocessors/report_converters_test.go @@ -578,6 +578,8 @@ func Test_parseScoreFromVendorAttribute(t *testing.T) { {"both", args{`{"CVSS":{"nvd":{"V3Score":5.5,"V3Vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H"},"redhat":{"V3Score":6.2,"V3Vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"}}}`}, 5.5}, {"both2", args{`{"CVSS":{"nvd":{"V2Score":7.2,"V2Vector":"AV:L/AC:L/Au:N/C:C/I:C/A:C","V3Score":7.8,"V3Vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"},"redhat":{"V3Score":7.8,"V3Vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"}}}`}, 7.8}, {"none", args{`{"CVSS":{"nvd":{"V2Score":7.2,"V2Vector":"AV:L/AC:L/Au:N/C:C/I:C/A:C","V3Vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"},"redhat":{"V3Vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"}}}`}, 0}, + {"redhatonly", args{`{"CVSS":{"redhat":{"V3Score":8.8, "V3Vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"}}}`}, 8.8}, + {"nvdnov3butredhat", args{`{"CVSS":{"nvd":{"V2Score":7.2,"V2Vector":"AV:L/AC:L/Au:N/C:C/I:C/A:C"},"redhat":{"V3Score":7.8,"V3Vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"}}}`}, 7.8}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 67c03ddc4f0a2d10b876a3d71f07886ee934f2a6 Mon Sep 17 00:00:00 2001 From: Shengwen YU Date: Tue, 16 Apr 2024 18:37:16 +0800 Subject: [PATCH 092/145] fix: update TRIVYVERSION=v0.50.1 && TRIVYADAPTERVERSION=v0.31.0 (#20285) Signed-off-by: Shengwen Yu --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index e9dd6f7dd..7b86db516 100644 --- a/Makefile +++ b/Makefile @@ -104,8 +104,8 @@ PREPARE_VERSION_NAME=versions #versions REGISTRYVERSION=v2.8.3-patch-redis -TRIVYVERSION=v0.49.1 -TRIVYADAPTERVERSION=v0.30.22 +TRIVYVERSION=v0.50.1 +TRIVYADAPTERVERSION=v0.31.0 # version of registry for pulling the source code REGISTRY_SRC_TAG=v2.8.3 From 654aa8edcfad6007b1d15ac323c37bc2c140418c Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Tue, 16 Apr 2024 21:34:19 +0800 Subject: [PATCH 093/145] Add generate SBOM feature (#20251) * Add SBOM scan feature Add scan handler for sbom Delete previous sbom accessory before the job service Signed-off-by: stonezdj * fix issue Signed-off-by: stonezdj --------- Signed-off-by: stonezdj Signed-off-by: stonezdj Co-authored-by: stonezdj --- api/v2.0/swagger.yaml | 15 +- src/common/rbac/const.go | 5 + src/common/rbac/project/rbac_role.go | 9 + src/controller/artifact/controller.go | 2 + .../artifact/processor/sbom/sbom.go | 6 +- .../artifact/processor/sbom/sbom_test.go | 2 +- src/controller/scan/base_controller.go | 70 +++++++- src/controller/scan/base_controller_test.go | 32 +++- src/controller/scanner/base_controller.go | 23 ++- src/core/main.go | 1 + src/jobservice/main.go | 1 + src/pkg/scan/dao/scanner/model.go | 20 ++- src/pkg/scan/handler.go | 7 + src/pkg/scan/job.go | 29 +++- src/pkg/scan/job_test.go | 5 +- src/pkg/scan/rest/v1/client.go | 13 +- src/pkg/scan/rest/v1/client_test.go | 6 +- src/pkg/scan/rest/v1/models.go | 19 ++ src/pkg/scan/sbom/model/summary.go | 43 +++++ src/pkg/scan/sbom/sbom.go | 162 ++++++++++++++++++ src/pkg/scan/sbom/sbom_test.go | 139 +++++++++++++++ src/pkg/scan/vulnerability/vul.go | 15 ++ src/pkg/scan/vulnerability/vul_test.go | 64 +++++++ src/server/v2.0/handler/model/scanner.go | 1 + src/server/v2.0/handler/project.go | 8 +- src/server/v2.0/handler/project_test.go | 14 +- src/server/v2.0/handler/scan.go | 37 ++-- src/testing/pkg/scan/rest/v1/client.go | 18 +- 28 files changed, 697 insertions(+), 69 deletions(-) create mode 100644 src/pkg/scan/sbom/model/summary.go create mode 100644 src/pkg/scan/sbom/sbom.go create mode 100644 src/pkg/scan/sbom/sbom_test.go diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index ce4210ec8..0f5c18e73 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -996,7 +996,7 @@ paths: description: Specify whether the SBOM overview is included in returning artifacts, when this option is true, the SBOM overview will be included in the response type: boolean required: false - default: false + default: false - name: with_signature in: query description: Specify whether the signature is included inside the tags of the returning artifacts. Only works when setting "with_tag=true" @@ -1179,7 +1179,7 @@ paths: - name: scan_request_type in: body required: false - schema: + schema: $ref: '#/definitions/ScanRequestType' responses: '202': @@ -6769,7 +6769,7 @@ definitions: ScanRequestType: type: object properties: - scan_type: + scan_type: type: string description: 'The scan type for the scan request. Two options are currently supported, vulnerability and sbom' enum: [vulnerability, sbom] @@ -6797,12 +6797,12 @@ definitions: description: 'The status of the generating SBOM task' sbom_digest: type: string - description: 'The digest of the generated SBOM accessory' + description: 'The digest of the generated SBOM accessory' report_id: type: string description: 'id of the native scan report' - example: '5f62c830-f996-11e9-957f-0242c0a89008' - duration: + example: '5f62c830-f996-11e9-957f-0242c0a89008' + duration: type: integer format: int64 description: 'Time in seconds required to create the report' @@ -8437,7 +8437,7 @@ definitions: description: Indicates the capabilities of the scanner, e.g. support_vulnerability or support_sbom. additionalProperties: True example: {"support_vulnerability": true, "support_sbom": true} - + ScannerRegistrationReq: type: object required: @@ -9986,7 +9986,6 @@ definitions: items: type: string description: Links of the vulnerability - ScanType: type: object properties: diff --git a/src/common/rbac/const.go b/src/common/rbac/const.go index ff49ec3fd..a783e71d4 100644 --- a/src/common/rbac/const.go +++ b/src/common/rbac/const.go @@ -51,6 +51,7 @@ const ( ResourceRobot = Resource("robot") ResourceNotificationPolicy = Resource("notification-policy") ResourceScan = Resource("scan") + ResourceSBOM = Resource("sbom") ResourceScanner = Resource("scanner") ResourceArtifact = Resource("artifact") ResourceTag = Resource("tag") @@ -182,6 +183,10 @@ var ( {Resource: ResourceScan, Action: ActionRead}, {Resource: ResourceScan, Action: ActionStop}, + {Resource: ResourceSBOM, Action: ActionCreate}, + {Resource: ResourceSBOM, Action: ActionStop}, + {Resource: ResourceSBOM, Action: ActionRead}, + {Resource: ResourceTag, Action: ActionCreate}, {Resource: ResourceTag, Action: ActionList}, {Resource: ResourceTag, Action: ActionDelete}, diff --git a/src/common/rbac/project/rbac_role.go b/src/common/rbac/project/rbac_role.go index fa618b982..5ef773e9a 100644 --- a/src/common/rbac/project/rbac_role.go +++ b/src/common/rbac/project/rbac_role.go @@ -86,6 +86,9 @@ var ( {Resource: rbac.ResourceScan, Action: rbac.ActionCreate}, {Resource: rbac.ResourceScan, Action: rbac.ActionRead}, {Resource: rbac.ResourceScan, Action: rbac.ActionStop}, + {Resource: rbac.ResourceSBOM, Action: rbac.ActionCreate}, + {Resource: rbac.ResourceSBOM, Action: rbac.ActionStop}, + {Resource: rbac.ResourceSBOM, Action: rbac.ActionRead}, {Resource: rbac.ResourceScanner, Action: rbac.ActionRead}, {Resource: rbac.ResourceScanner, Action: rbac.ActionCreate}, @@ -169,6 +172,9 @@ var ( {Resource: rbac.ResourceScan, Action: rbac.ActionCreate}, {Resource: rbac.ResourceScan, Action: rbac.ActionRead}, {Resource: rbac.ResourceScan, Action: rbac.ActionStop}, + {Resource: rbac.ResourceSBOM, Action: rbac.ActionCreate}, + {Resource: rbac.ResourceSBOM, Action: rbac.ActionStop}, + {Resource: rbac.ResourceSBOM, Action: rbac.ActionRead}, {Resource: rbac.ResourceScanner, Action: rbac.ActionRead}, @@ -223,6 +229,7 @@ var ( {Resource: rbac.ResourceRobot, Action: rbac.ActionList}, {Resource: rbac.ResourceScan, Action: rbac.ActionRead}, + {Resource: rbac.ResourceSBOM, Action: rbac.ActionRead}, {Resource: rbac.ResourceScanner, Action: rbac.ActionRead}, @@ -267,6 +274,7 @@ var ( {Resource: rbac.ResourceRobot, Action: rbac.ActionList}, {Resource: rbac.ResourceScan, Action: rbac.ActionRead}, + {Resource: rbac.ResourceSBOM, Action: rbac.ActionRead}, {Resource: rbac.ResourceScanner, Action: rbac.ActionRead}, @@ -290,6 +298,7 @@ var ( {Resource: rbac.ResourceConfiguration, Action: rbac.ActionRead}, {Resource: rbac.ResourceScan, Action: rbac.ActionRead}, + {Resource: rbac.ResourceSBOM, Action: rbac.ActionRead}, {Resource: rbac.ResourceScanner, Action: rbac.ActionRead}, diff --git a/src/controller/artifact/controller.go b/src/controller/artifact/controller.go index cc100211f..34ea29077 100644 --- a/src/controller/artifact/controller.go +++ b/src/controller/artifact/controller.go @@ -29,6 +29,7 @@ import ( "github.com/goharbor/harbor/src/controller/artifact/processor/chart" "github.com/goharbor/harbor/src/controller/artifact/processor/cnab" "github.com/goharbor/harbor/src/controller/artifact/processor/image" + "github.com/goharbor/harbor/src/controller/artifact/processor/sbom" "github.com/goharbor/harbor/src/controller/artifact/processor/wasm" "github.com/goharbor/harbor/src/controller/event/metadata" "github.com/goharbor/harbor/src/controller/tag" @@ -73,6 +74,7 @@ var ( chart.ArtifactTypeChart: icon.DigestOfIconChart, cnab.ArtifactTypeCNAB: icon.DigestOfIconCNAB, wasm.ArtifactTypeWASM: icon.DigestOfIconWASM, + sbom.ArtifactTypeSBOM: icon.DigestOfIconAccSBOM, } ) diff --git a/src/controller/artifact/processor/sbom/sbom.go b/src/controller/artifact/processor/sbom/sbom.go index ec0222fb9..4eb11f4bd 100644 --- a/src/controller/artifact/processor/sbom/sbom.go +++ b/src/controller/artifact/processor/sbom/sbom.go @@ -29,8 +29,8 @@ import ( ) const ( - // processorArtifactTypeSBOM is the artifact type for SBOM, it's scope is only used in the processor - processorArtifactTypeSBOM = "SBOM" + // ArtifactTypeSBOM is the artifact type for SBOM, it's scope is only used in the processor + ArtifactTypeSBOM = "SBOM" // processorMediaType is the media type for SBOM, it's scope is only used to register the processor processorMediaType = "application/vnd.goharbor.harbor.sbom.v1" ) @@ -85,5 +85,5 @@ func (m *Processor) AbstractAddition(_ context.Context, art *artifact.Artifact, // GetArtifactType the artifact type is used to display the artifact type in the UI func (m *Processor) GetArtifactType(_ context.Context, _ *artifact.Artifact) string { - return processorArtifactTypeSBOM + return ArtifactTypeSBOM } diff --git a/src/controller/artifact/processor/sbom/sbom_test.go b/src/controller/artifact/processor/sbom/sbom_test.go index 6128c550f..33889591a 100644 --- a/src/controller/artifact/processor/sbom/sbom_test.go +++ b/src/controller/artifact/processor/sbom/sbom_test.go @@ -158,7 +158,7 @@ func (suite *SBOMProcessorTestSuite) TestAbstractAdditionPullManifestError() { } func (suite *SBOMProcessorTestSuite) TestGetArtifactType() { - suite.Equal(processorArtifactTypeSBOM, suite.processor.GetArtifactType(context.Background(), &artifact.Artifact{})) + suite.Equal(ArtifactTypeSBOM, suite.processor.GetArtifactType(context.Background(), &artifact.Artifact{})) } func TestSBOMProcessorTestSuite(t *testing.T) { diff --git a/src/controller/scan/base_controller.go b/src/controller/scan/base_controller.go index a1627af30..be168098a 100644 --- a/src/controller/scan/base_controller.go +++ b/src/controller/scan/base_controller.go @@ -49,8 +49,10 @@ import ( "github.com/goharbor/harbor/src/pkg/scan/postprocessors" "github.com/goharbor/harbor/src/pkg/scan/report" v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" + sbomModel "github.com/goharbor/harbor/src/pkg/scan/sbom/model" "github.com/goharbor/harbor/src/pkg/scan/vuln" "github.com/goharbor/harbor/src/pkg/task" + "github.com/goharbor/harbor/src/testing/controller/artifact" ) var ( @@ -108,6 +110,8 @@ type basicController struct { rc robot.Controller // Tag controller tagCtl tag.Controller + // Artifact controller + artCtl artifact.Controller // UUID generator uuid uuidGenerator // Configuration getter func @@ -259,7 +263,7 @@ func (bc *basicController) Scan(ctx context.Context, artifact *ar.Artifact, opti launchScanJobParams []*launchScanJobParam ) for _, art := range artifacts { - reports, err := bc.makeReportPlaceholder(ctx, r, art) + reports, err := bc.makeReportPlaceholder(ctx, r, art, opts) if err != nil { if errors.IsConflictErr(err) { errs = append(errs, err) @@ -326,7 +330,7 @@ func (bc *basicController) Scan(ctx context.Context, artifact *ar.Artifact, opti for _, launchScanJobParam := range launchScanJobParams { launchScanJobParam.ExecutionID = opts.ExecutionID - if err := bc.launchScanJob(ctx, launchScanJobParam); err != nil { + if err := bc.launchScanJob(ctx, launchScanJobParam, opts); err != nil { log.G(ctx).Warningf("scan artifact %s@%s failed, error: %v", artifact.RepositoryName, artifact.Digest, err) errs = append(errs, err) } @@ -546,13 +550,15 @@ func (bc *basicController) startScanAll(ctx context.Context, executionID int64) return nil } -func (bc *basicController) makeReportPlaceholder(ctx context.Context, r *scanner.Registration, art *ar.Artifact) ([]*scan.Report, error) { - mimeTypes := r.GetProducesMimeTypes(art.ManifestMediaType) - +func (bc *basicController) makeReportPlaceholder(ctx context.Context, r *scanner.Registration, art *ar.Artifact, opts *Options) ([]*scan.Report, error) { + mimeTypes := r.GetProducesMimeTypes(art.ManifestMediaType, opts.GetScanType()) oldReports, err := bc.manager.GetBy(bc.cloneCtx(ctx), art.Digest, r.UUID, mimeTypes) if err != nil { return nil, err } + if err := bc.deleteArtifactAccessories(ctx, oldReports); err != nil { + return nil, err + } if err := bc.assembleReports(ctx, oldReports...); err != nil { return nil, err @@ -574,7 +580,7 @@ func (bc *basicController) makeReportPlaceholder(ctx context.Context, r *scanner var reports []*scan.Report - for _, pm := range r.GetProducesMimeTypes(art.ManifestMediaType) { + for _, pm := range r.GetProducesMimeTypes(art.ManifestMediaType, opts.GetScanType()) { report := &scan.Report{ Digest: art.Digest, RegistrationUUID: r.UUID, @@ -991,7 +997,7 @@ func (bc *basicController) makeRobotAccount(ctx context.Context, projectID int64 } // launchScanJob launches a job to run scan -func (bc *basicController) launchScanJob(ctx context.Context, param *launchScanJobParam) error { +func (bc *basicController) launchScanJob(ctx context.Context, param *launchScanJobParam, opts *Options) error { // don't launch scan job for the artifact which is not supported by the scanner if !hasCapability(param.Registration, param.Artifact) { return nil @@ -1032,6 +1038,11 @@ func (bc *basicController) launchScanJob(ctx context.Context, param *launchScanJ MimeType: param.Artifact.ManifestMediaType, Size: param.Artifact.Size, }, + RequestType: []*v1.ScanType{ + { + Type: opts.GetScanType(), + }, + }, } rJSON, err := param.Registration.ToJSON() @@ -1265,3 +1276,48 @@ func parseOptions(options ...Option) (*Options, error) { return ops, nil } + +// deleteArtifactAccessories delete the accessory in reports, only delete sbom accessory +func (bc *basicController) deleteArtifactAccessories(ctx context.Context, reports []*scan.Report) error { + for _, rpt := range reports { + if rpt.MimeType != v1.MimeTypeSBOMReport { + continue + } + if err := bc.deleteArtifactAccessory(ctx, rpt.Report); err != nil { + return err + } + } + return nil +} + +// deleteArtifactAccessory check if current report has accessory info, if there is, delete it +func (bc *basicController) deleteArtifactAccessory(ctx context.Context, report string) error { + if len(report) == 0 { + return nil + } + sbomSummary := sbomModel.Summary{} + if err := json.Unmarshal([]byte(report), &sbomSummary); err != nil { + // it could be a non sbom report, just skip + log.Debugf("fail to unmarshal %v, skip to delete sbom report", err) + return nil + } + repo, dgst := sbomSummary.SBOMAccArt() + if len(repo) == 0 || len(dgst) == 0 { + return nil + } + art, err := bc.ar.GetByReference(ctx, repo, dgst, nil) + if err != nil { + if errors.IsNotFoundErr(err) { + return nil + } + return err + } + if art == nil { + return nil + } + err = bc.ar.Delete(ctx, art.ID) + if errors.IsNotFoundErr(err) { + return nil + } + return err +} diff --git a/src/controller/scan/base_controller_test.go b/src/controller/scan/base_controller_test.go index 9d485f16c..521325d79 100644 --- a/src/controller/scan/base_controller_test.go +++ b/src/controller/scan/base_controller_test.go @@ -108,6 +108,7 @@ func (suite *ControllerTestSuite) SetupSuite() { Version: "0.1.0", }, Capabilities: []*v1.ScannerCapability{{ + Type: v1.ScanTypeVulnerability, ConsumesMimeTypes: []string{ v1.MimeTypeOCIArtifact, v1.MimeTypeDockerArtifact, @@ -115,7 +116,17 @@ func (suite *ControllerTestSuite) SetupSuite() { ProducesMimeTypes: []string{ v1.MimeTypeNativeReport, }, - }}, + }, + { + Type: v1.ScanTypeSbom, + ConsumesMimeTypes: []string{ + v1.MimeTypeOCIArtifact, + }, + ProducesMimeTypes: []string{ + v1.MimeTypeSBOMReport, + }, + }, + }, Properties: v1.ScannerProperties{ "extra": "testing", }, @@ -655,3 +666,22 @@ func TestIsSBOMMimeTypes(t *testing.T) { // Test with an empty slice assert.False(t, isSBOMMimeTypes([]string{})) } + +func (suite *ControllerTestSuite) TestDeleteArtifactAccessories() { + // artifact not provided + suite.Nil(suite.c.deleteArtifactAccessories(context.TODO(), nil)) + + // artifact is provided + art := &artifact.Artifact{Artifact: art.Artifact{ID: 1, ProjectID: 1, RepositoryName: "library/photon"}} + mock.OnAnything(suite.ar, "GetByReference").Return(art, nil).Once() + mock.OnAnything(suite.ar, "Delete").Return(nil).Once() + reportContent := `{"sbom_digest":"sha256:12345", "scan_status":"Success", "duration":3, "sbom_repository":"library/photon"}` + emptyReportContent := `` + reports := []*scan.Report{ + {Report: reportContent}, + {Report: emptyReportContent}, + } + ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{}) + suite.NoError(suite.c.deleteArtifactAccessories(ctx, reports)) + +} diff --git a/src/controller/scanner/base_controller.go b/src/controller/scanner/base_controller.go index 2028da355..068424a1e 100644 --- a/src/controller/scanner/base_controller.go +++ b/src/controller/scanner/base_controller.go @@ -79,7 +79,11 @@ func (bc *basicController) ListRegistrations(ctx context.Context, query *q.Query if err != nil { return nil, errors.Wrap(err, "api controller: list registrations") } - + for _, r := range l { + if err := bc.appendCap(ctx, r); err != nil { + return nil, err + } + } return l, nil } @@ -122,10 +126,25 @@ func (bc *basicController) GetRegistration(ctx context.Context, registrationUUID if err != nil { return nil, errors.Wrap(err, "api controller: get registration") } - + if r == nil { + return nil, nil + } + if err := bc.appendCap(ctx, r); err != nil { + return nil, err + } return r, nil } +func (bc *basicController) appendCap(ctx context.Context, r *scanner.Registration) error { + mt, err := bc.Ping(ctx, r) + if err != nil { + logger.Errorf("Get registration error: %s", err) + return err + } + r.Capabilities = mt.ConvertCapability() + return nil +} + // RegistrationExists ... func (bc *basicController) RegistrationExists(ctx context.Context, registrationUUID string) bool { registration, err := bc.manager.Get(ctx, registrationUUID) diff --git a/src/core/main.go b/src/core/main.go index ebc786d7e..f0bc96564 100644 --- a/src/core/main.go +++ b/src/core/main.go @@ -70,6 +70,7 @@ import ( "github.com/goharbor/harbor/src/pkg/oidc" "github.com/goharbor/harbor/src/pkg/scan" "github.com/goharbor/harbor/src/pkg/scan/dao/scanner" + _ "github.com/goharbor/harbor/src/pkg/scan/sbom" _ "github.com/goharbor/harbor/src/pkg/scan/vulnerability" pkguser "github.com/goharbor/harbor/src/pkg/user" "github.com/goharbor/harbor/src/pkg/version" diff --git a/src/jobservice/main.go b/src/jobservice/main.go index f288efea7..e00dd4b20 100644 --- a/src/jobservice/main.go +++ b/src/jobservice/main.go @@ -36,6 +36,7 @@ import ( _ "github.com/goharbor/harbor/src/pkg/accessory/model/subject" _ "github.com/goharbor/harbor/src/pkg/config/inmemory" _ "github.com/goharbor/harbor/src/pkg/config/rest" + _ "github.com/goharbor/harbor/src/pkg/scan/sbom" _ "github.com/goharbor/harbor/src/pkg/scan/vulnerability" ) diff --git a/src/pkg/scan/dao/scanner/model.go b/src/pkg/scan/dao/scanner/model.go index dbdcf8b1d..cc418e624 100644 --- a/src/pkg/scan/dao/scanner/model.go +++ b/src/pkg/scan/dao/scanner/model.go @@ -66,8 +66,9 @@ type Registration struct { Metadata *v1.ScannerAdapterMetadata `orm:"-" json:"-"` // Timestamps - CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` - UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` + UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"` + Capabilities map[string]interface{} `orm:"-" json:"capabilities,omitempty"` } // TableName for Endpoint @@ -151,15 +152,20 @@ func (r *Registration) HasCapability(manifestMimeType string) bool { } // GetProducesMimeTypes returns produces mime types for the artifact -func (r *Registration) GetProducesMimeTypes(mimeType string) []string { +func (r *Registration) GetProducesMimeTypes(mimeType string, scanType string) []string { if r.Metadata == nil { return nil } - for _, capability := range r.Metadata.Capabilities { - for _, mt := range capability.ConsumesMimeTypes { - if mt == mimeType { - return capability.ProducesMimeTypes + capType := capability.Type + if len(capType) == 0 { + capType = v1.ScanTypeVulnerability + } + if scanType == capType { + for _, mt := range capability.ConsumesMimeTypes { + if mt == mimeType { + return capability.ProducesMimeTypes + } } } } diff --git a/src/pkg/scan/handler.go b/src/pkg/scan/handler.go index 7ddba595d..f402107c7 100644 --- a/src/pkg/scan/handler.go +++ b/src/pkg/scan/handler.go @@ -38,7 +38,14 @@ func GetScanHandler(requestType string) Handler { // Handler handler for scan job, it could be implement by different scan type, such as vulnerability, sbom type Handler interface { + // RequestProducesMineTypes returns the produces mime types + RequestProducesMineTypes() []string + // RequiredPermissions defines the permission used by the scan robot account RequiredPermissions() []*types.Policy + // RequestParameters defines the parameters for scan request + RequestParameters() map[string]interface{} + // ReportURLParameter defines the parameters for scan report + ReportURLParameter(sr *v1.ScanRequest) (string, error) // PostScan defines the operation after scan PostScan(ctx job.Context, sr *v1.ScanRequest, rp *scan.Report, rawReport string, startTime time.Time, robot *model.Robot) (string, error) } diff --git a/src/pkg/scan/job.go b/src/pkg/scan/job.go index c21b48cb2..171e0c307 100644 --- a/src/pkg/scan/job.go +++ b/src/pkg/scan/job.go @@ -242,7 +242,13 @@ func (j *Job) Run(ctx job.Context, params job.Parameters) error { } myLogger.Debugf("check scan report for mime %s at %s", m, t.Format("2006/01/02 15:04:05")) - rawReport, err := fetchScanReportFromScanner(client, resp.ID, m) + + reportURLParameter, err := handler.ReportURLParameter(req) + if err != nil { + errs[i] = errors.Wrap(err, "scan job: get report url") + return + } + rawReport, err := fetchScanReportFromScanner(client, resp.ID, m, reportURLParameter) if err != nil { // Not ready yet if notReadyErr, ok := err.(*v1.ReportNotReadyError); ok { @@ -332,13 +338,13 @@ func getReportPlaceholder(ctx context.Context, digest string, reportUUID string, return reports[0], nil } -func fetchScanReportFromScanner(client v1.Client, requestID string, m string) (rawReport string, err error) { - rawReport, err = client.GetScanReport(requestID, m) +func fetchScanReportFromScanner(client v1.Client, requestID string, mimType string, urlParameter string) (rawReport string, err error) { + rawReport, err = client.GetScanReport(requestID, mimType, urlParameter) if err != nil { return "", err } // Make sure the data is aligned with the v1 spec. - if _, err = report.ResolveData(m, []byte(rawReport)); err != nil { + if _, err = report.ResolveData(mimType, []byte(rawReport)); err != nil { return "", err } return rawReport, nil @@ -367,7 +373,20 @@ func ExtractScanReq(params job.Parameters) (*v1.ScanRequest, error) { if err := req.Validate(); err != nil { return nil, err } - + reqType := v1.ScanTypeVulnerability + // attach the request with ProducesMimeTypes and Parameters + if len(req.RequestType) > 0 { + // current only support requestType with one element for each request + if len(req.RequestType[0].Type) > 0 { + reqType = req.RequestType[0].Type + } + handler := GetScanHandler(reqType) + if handler == nil { + return nil, errors.Errorf("failed to get scan handler, request type %v", reqType) + } + req.RequestType[0].ProducesMimeTypes = handler.RequestProducesMineTypes() + req.RequestType[0].Parameters = handler.RequestParameters() + } return req, nil } diff --git a/src/pkg/scan/job_test.go b/src/pkg/scan/job_test.go index 92571285d..ff00dd20f 100644 --- a/src/pkg/scan/job_test.go +++ b/src/pkg/scan/job_test.go @@ -211,8 +211,9 @@ func (suite *JobTestSuite) TestfetchScanReportFromScanner() { suite.reportIDs = append(suite.reportIDs, rptID) require.NoError(suite.T(), err) client := &v1testing.Client{} - client.On("GetScanReport", mock.Anything, v1.MimeTypeGenericVulnerabilityReport).Return(rawContent, nil) - rawRept, err := fetchScanReportFromScanner(client, "abc", v1.MimeTypeGenericVulnerabilityReport) + client.On("GetScanReport", mock.Anything, v1.MimeTypeGenericVulnerabilityReport, mock.Anything).Return(rawContent, nil) + parameters := "sbom_media_type=application/spdx+json" + rawRept, err := fetchScanReportFromScanner(client, "abc", v1.MimeTypeGenericVulnerabilityReport, parameters) require.NoError(suite.T(), err) require.Equal(suite.T(), rawContent, rawRept) } diff --git a/src/pkg/scan/rest/v1/client.go b/src/pkg/scan/rest/v1/client.go index 251ccef1f..a5ae04075 100644 --- a/src/pkg/scan/rest/v1/client.go +++ b/src/pkg/scan/rest/v1/client.go @@ -68,7 +68,7 @@ type Client interface { // Returns: // string : the scan report of the given artifact // error : non nil error if any errors occurred - GetScanReport(scanRequestID, reportMIMEType string) (string, error) + GetScanReport(scanRequestID, reportMIMEType string, urlParameter string) (string, error) } // basicClient is default implementation of the Client interface @@ -97,7 +97,7 @@ func NewClient(url, authType, accessCredential string, skipCertVerify bool) (Cli httpClient: &http.Client{ Timeout: time.Second * 5, Transport: transport, - CheckRedirect: func(req *http.Request, via []*http.Request) error { + CheckRedirect: func(_ *http.Request, _ []*http.Request) error { return http.ErrUseLastResponse }, }, @@ -167,7 +167,7 @@ func (c *basicClient) SubmitScan(req *ScanRequest) (*ScanResponse, error) { } // GetScanReport ... -func (c *basicClient) GetScanReport(scanRequestID, reportMIMEType string) (string, error) { +func (c *basicClient) GetScanReport(scanRequestID, reportMIMEType string, urlParameter string) (string, error) { if len(scanRequestID) == 0 { return "", errors.New("empty scan request ID") } @@ -177,8 +177,11 @@ func (c *basicClient) GetScanReport(scanRequestID, reportMIMEType string) (strin } def := c.spec.GetScanReport(scanRequestID, reportMIMEType) - - req, err := http.NewRequest(http.MethodGet, def.URL, nil) + reportURL := def.URL + if len(urlParameter) > 0 { + reportURL = fmt.Sprintf("%s?%s", def.URL, urlParameter) + } + req, err := http.NewRequest(http.MethodGet, reportURL, nil) if err != nil { return "", errors.Wrap(err, "v1 client: get scan report") } diff --git a/src/pkg/scan/rest/v1/client_test.go b/src/pkg/scan/rest/v1/client_test.go index ee3435066..5893514d6 100644 --- a/src/pkg/scan/rest/v1/client_test.go +++ b/src/pkg/scan/rest/v1/client_test.go @@ -72,7 +72,7 @@ func (suite *ClientTestSuite) TestClientSubmitScan() { // TestClientGetScanReportError tests getting report failed func (suite *ClientTestSuite) TestClientGetScanReportError() { - _, err := suite.client.GetScanReport("id1", MimeTypeNativeReport) + _, err := suite.client.GetScanReport("id1", MimeTypeNativeReport, "") require.Error(suite.T(), err) assert.Condition(suite.T(), func() (success bool) { success = strings.Index(err.Error(), "error") != -1 @@ -82,14 +82,14 @@ func (suite *ClientTestSuite) TestClientGetScanReportError() { // TestClientGetScanReport tests getting report func (suite *ClientTestSuite) TestClientGetScanReport() { - res, err := suite.client.GetScanReport("id2", MimeTypeNativeReport) + res, err := suite.client.GetScanReport("id2", MimeTypeNativeReport, "") require.NoError(suite.T(), err) require.NotEmpty(suite.T(), res) } // TestClientGetScanReportNotReady tests the case that the report is not ready func (suite *ClientTestSuite) TestClientGetScanReportNotReady() { - _, err := suite.client.GetScanReport("id3", MimeTypeNativeReport) + _, err := suite.client.GetScanReport("id3", MimeTypeNativeReport, "") require.Error(suite.T(), err) require.Condition(suite.T(), func() (success bool) { _, success = err.(*ReportNotReadyError) diff --git a/src/pkg/scan/rest/v1/models.go b/src/pkg/scan/rest/v1/models.go index c31edb93b..21352c749 100644 --- a/src/pkg/scan/rest/v1/models.go +++ b/src/pkg/scan/rest/v1/models.go @@ -21,6 +21,11 @@ import ( "github.com/goharbor/harbor/src/lib/errors" ) +const ( + supportVulnerability = "support_vulnerability" + supportSBOM = "support_sbom" +) + var supportedMimeTypes = []string{ MimeTypeNativeReport, MimeTypeGenericVulnerabilityReport, @@ -153,6 +158,20 @@ func (md *ScannerAdapterMetadata) GetCapability(mimeType string) *ScannerCapabil return nil } +// ConvertCapability converts the capability to map, used in get scanner API +func (md *ScannerAdapterMetadata) ConvertCapability() map[string]interface{} { + capabilities := make(map[string]interface{}) + for _, c := range md.Capabilities { + if c.Type == ScanTypeVulnerability { + capabilities[supportVulnerability] = true + } + if c.Type == ScanTypeSbom { + capabilities[supportSBOM] = true + } + } + return capabilities +} + // Artifact represents an artifact stored in Registry. type Artifact struct { // ID of the namespace (project). It will not be sent to scanner adapter. diff --git a/src/pkg/scan/sbom/model/summary.go b/src/pkg/scan/sbom/model/summary.go new file mode 100644 index 000000000..46c870f97 --- /dev/null +++ b/src/pkg/scan/sbom/model/summary.go @@ -0,0 +1,43 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +const ( + // SBOMRepository ... + SBOMRepository = "sbom_repository" + // SBOMDigest ... + SBOMDigest = "sbom_digest" + // StartTime ... + StartTime = "start_time" + // EndTime ... + EndTime = "end_time" + // Duration ... + Duration = "duration" + // ScanStatus ... + ScanStatus = "scan_status" +) + +// Summary includes the sbom summary information +type Summary map[string]interface{} + +// SBOMAccArt returns the repository and digest of the SBOM +func (s Summary) SBOMAccArt() (repo, digest string) { + if repo, ok := s[SBOMRepository].(string); ok { + if digest, ok := s[SBOMDigest].(string); ok { + return repo, digest + } + } + return "", "" +} diff --git a/src/pkg/scan/sbom/sbom.go b/src/pkg/scan/sbom/sbom.go new file mode 100644 index 000000000..bbf405571 --- /dev/null +++ b/src/pkg/scan/sbom/sbom.go @@ -0,0 +1,162 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sbom + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "strings" + "time" + + "github.com/goharbor/harbor/src/common" + "github.com/goharbor/harbor/src/lib/config" + scanModel "github.com/goharbor/harbor/src/pkg/scan/dao/scan" + sbom "github.com/goharbor/harbor/src/pkg/scan/sbom/model" + + "github.com/goharbor/harbor/src/common/rbac" + "github.com/goharbor/harbor/src/jobservice/job" + "github.com/goharbor/harbor/src/pkg/permission/types" + "github.com/goharbor/harbor/src/pkg/robot/model" + "github.com/goharbor/harbor/src/pkg/scan" + + v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" + "github.com/goharbor/harbor/src/pkg/scan/vuln" +) + +const ( + sbomMimeType = "application/vnd.goharbor.harbor.sbom.v1" + sbomMediaTypeSpdx = "application/spdx+json" +) + +func init() { + scan.RegisterScanHanlder(v1.ScanTypeSbom, &scanHandler{GenAccessoryFunc: scan.GenAccessoryArt, RegistryServer: registryFQDN}) +} + +// ScanHandler defines the Handler to generate sbom +type scanHandler struct { + GenAccessoryFunc func(scanRep v1.ScanRequest, sbomContent []byte, labels map[string]string, mediaType string, robot *model.Robot) (string, error) + RegistryServer func(ctx context.Context) string +} + +// RequestProducesMineTypes defines the mine types produced by the scan handler +func (v *scanHandler) RequestProducesMineTypes() []string { + return []string{v1.MimeTypeSBOMReport} +} + +// RequestParameters defines the parameters for scan request +func (v *scanHandler) RequestParameters() map[string]interface{} { + return map[string]interface{}{"sbom_media_types": []string{sbomMediaTypeSpdx}} +} + +// ReportURLParameter defines the parameters for scan report url +func (v *scanHandler) ReportURLParameter(_ *v1.ScanRequest) (string, error) { + return fmt.Sprintf("sbom_media_type=%s", url.QueryEscape(sbomMediaTypeSpdx)), nil +} + +// RequiredPermissions defines the permission used by the scan robot account +func (v *scanHandler) RequiredPermissions() []*types.Policy { + return []*types.Policy{ + { + Resource: rbac.ResourceRepository, + Action: rbac.ActionPull, + }, + { + Resource: rbac.ResourceRepository, + Action: rbac.ActionScannerPull, + }, + { + Resource: rbac.ResourceRepository, + Action: rbac.ActionPush, + }, + } +} + +// PostScan defines task specific operations after the scan is complete +func (v *scanHandler) PostScan(ctx job.Context, sr *v1.ScanRequest, _ *scanModel.Report, rawReport string, startTime time.Time, robot *model.Robot) (string, error) { + sbomContent, err := retrieveSBOMContent(rawReport) + if err != nil { + return "", err + } + scanReq := v1.ScanRequest{ + Registry: sr.Registry, + Artifact: sr.Artifact, + } + // the registry server url is core by default, need to replace it with real registry server url + scanReq.Registry.URL = v.RegistryServer(ctx.SystemContext()) + if len(scanReq.Registry.URL) == 0 { + return "", fmt.Errorf("empty registry server") + } + myLogger := ctx.GetLogger() + myLogger.Debugf("Pushing accessory artifact to %s/%s", scanReq.Registry.URL, scanReq.Artifact.Repository) + dgst, err := v.GenAccessoryFunc(scanReq, sbomContent, v.annotations(), sbomMimeType, robot) + if err != nil { + myLogger.Errorf("error when create accessory from image %v", err) + return "", err + } + return v.generateReport(startTime, sr.Artifact.Repository, dgst, "Success") +} + +// annotations defines the annotations for the accessory artifact +func (v *scanHandler) annotations() map[string]string { + return map[string]string{ + "created-by": "Harbor", + "org.opencontainers.artifact.created": time.Now().Format(time.RFC3339), + "org.opencontainers.artifact.description": "SPDX JSON SBOM", + } +} + +func (v *scanHandler) generateReport(startTime time.Time, repository, digest, status string) (string, error) { + summary := sbom.Summary{} + endTime := time.Now() + summary[sbom.StartTime] = startTime + summary[sbom.EndTime] = endTime + summary[sbom.Duration] = int64(endTime.Sub(startTime).Seconds()) + summary[sbom.SBOMRepository] = repository + summary[sbom.SBOMDigest] = digest + summary[sbom.ScanStatus] = status + rep, err := json.Marshal(summary) + if err != nil { + return "", err + } + return string(rep), nil +} + +// extract server name from config, and remove the protocol prefix +func registryFQDN(ctx context.Context) string { + cfgMgr, ok := config.FromContext(ctx) + if ok { + extURL := cfgMgr.Get(context.Background(), common.ExtEndpoint).GetString() + server := strings.TrimPrefix(extURL, "https://") + server = strings.TrimPrefix(server, "http://") + return server + } + return "" +} + +// retrieveSBOMContent retrieves the "sbom" field from the raw report +func retrieveSBOMContent(rawReport string) ([]byte, error) { + rpt := vuln.Report{} + err := json.Unmarshal([]byte(rawReport), &rpt) + if err != nil { + return nil, err + } + sbomContent, err := json.Marshal(rpt.SBOM) + if err != nil { + return nil, err + } + return sbomContent, nil +} diff --git a/src/pkg/scan/sbom/sbom_test.go b/src/pkg/scan/sbom/sbom_test.go new file mode 100644 index 000000000..cf56b3bbb --- /dev/null +++ b/src/pkg/scan/sbom/sbom_test.go @@ -0,0 +1,139 @@ +package sbom + +import ( + "context" + "reflect" + "testing" + "time" + + "github.com/goharbor/harbor/src/common/rbac" + "github.com/goharbor/harbor/src/pkg/permission/types" + "github.com/goharbor/harbor/src/pkg/robot/model" + v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" + "github.com/goharbor/harbor/src/testing/jobservice" + + "github.com/stretchr/testify/suite" +) + +func Test_scanHandler_ReportURLParameter(t *testing.T) { + type args struct { + in0 *v1.ScanRequest + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + {"normal test", args{&v1.ScanRequest{}}, "sbom_media_type=application%2Fspdx%2Bjson", false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &scanHandler{} + got, err := v.ReportURLParameter(tt.args.in0) + if (err != nil) != tt.wantErr { + t.Errorf("ReportURLParameter() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ReportURLParameter() got = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_scanHandler_RequiredPermissions(t *testing.T) { + tests := []struct { + name string + want []*types.Policy + }{ + {"normal test", []*types.Policy{ + { + Resource: rbac.ResourceRepository, + Action: rbac.ActionPull, + }, + { + Resource: rbac.ResourceRepository, + Action: rbac.ActionScannerPull, + }, + { + Resource: rbac.ResourceRepository, + Action: rbac.ActionPush, + }, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &scanHandler{} + if got := v.RequiredPermissions(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("RequiredPermissions() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_scanHandler_RequestProducesMineTypes(t *testing.T) { + tests := []struct { + name string + want []string + }{ + {"normal test", []string{v1.MimeTypeSBOMReport}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &scanHandler{} + if got := v.RequestProducesMineTypes(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("RequestProducesMineTypes() = %v, want %v", got, tt.want) + } + }) + } +} + +func mockGetRegistry(ctx context.Context) string { + return "myharbor.example.com" +} + +func mockGenAccessory(scanRep v1.ScanRequest, sbomContent []byte, labels map[string]string, mediaType string, robot *model.Robot) (string, error) { + return "sha256:1234567890", nil +} + +type ExampleTestSuite struct { + handler *scanHandler + suite.Suite +} + +func (suite *ExampleTestSuite) SetupSuite() { + suite.handler = &scanHandler{ + GenAccessoryFunc: mockGenAccessory, + RegistryServer: mockGetRegistry, + } +} + +func (suite *ExampleTestSuite) TearDownSuite() { +} + +func (suite *ExampleTestSuite) TestPostScan() { + req := &v1.ScanRequest{ + Registry: &v1.Registry{ + URL: "myregistry.example.com", + }, + Artifact: &v1.Artifact{ + Repository: "library/nosql", + }, + } + robot := &model.Robot{ + Name: "robot", + Secret: "mysecret", + } + startTime := time.Now() + rawReport := `{"sbom": { "key": "value" }}` + ctx := &jobservice.MockJobContext{} + ctx.On("GetLogger").Return(&jobservice.MockJobLogger{}) + accessory, err := suite.handler.PostScan(ctx, req, nil, rawReport, startTime, robot) + suite.Require().NoError(err) + suite.Require().NotEmpty(accessory) +} + +func TestExampleTestSuite(t *testing.T) { + suite.Run(t, &ExampleTestSuite{}) +} diff --git a/src/pkg/scan/vulnerability/vul.go b/src/pkg/scan/vulnerability/vul.go index 804659c09..2e9194c4a 100644 --- a/src/pkg/scan/vulnerability/vul.go +++ b/src/pkg/scan/vulnerability/vul.go @@ -35,6 +35,16 @@ func init() { type ScanHandler struct { } +// RequestProducesMineTypes returns the produces mime types +func (v *ScanHandler) RequestProducesMineTypes() []string { + return []string{v1.MimeTypeGenericVulnerabilityReport} +} + +// RequestParameters defines the parameters for scan request +func (v *ScanHandler) RequestParameters() map[string]interface{} { + return nil +} + // RequiredPermissions defines the permission used by the scan robot account func (v *ScanHandler) RequiredPermissions() []*types.Policy { return []*types.Policy{ @@ -49,6 +59,11 @@ func (v *ScanHandler) RequiredPermissions() []*types.Policy { } } +// ReportURLParameter vulnerability doesn't require any scan report parameters +func (v *ScanHandler) ReportURLParameter(_ *v1.ScanRequest) (string, error) { + return "", nil +} + // PostScan ... func (v *ScanHandler) PostScan(ctx job.Context, _ *v1.ScanRequest, origRp *scan.Report, rawReport string, _ time.Time, _ *model.Robot) (string, error) { // use a new ormer here to use the short db connection diff --git a/src/pkg/scan/vulnerability/vul_test.go b/src/pkg/scan/vulnerability/vul_test.go index 50d84287e..003e15a0d 100644 --- a/src/pkg/scan/vulnerability/vul_test.go +++ b/src/pkg/scan/vulnerability/vul_test.go @@ -1,6 +1,7 @@ package vulnerability import ( + "fmt" "testing" "time" @@ -50,3 +51,66 @@ func TestPostScan(t *testing.T) { assert.Equal(t, "", refreshedReport, "PostScan should return the refreshed report") assert.Nil(t, err, "PostScan should not return an error") } + +func TestScanHandler_RequiredPermissions(t *testing.T) { + tests := []struct { + name string + want []*types.Policy + }{ + {"normal", []*types.Policy{ + { + Resource: rbac.ResourceRepository, + Action: rbac.ActionPull, + }, + { + Resource: rbac.ResourceRepository, + Action: rbac.ActionScannerPull, + }, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &ScanHandler{} + assert.Equalf(t, tt.want, v.RequiredPermissions(), "RequiredPermissions()") + }) + } +} + +func TestScanHandler_ReportURLParameter(t *testing.T) { + type args struct { + in0 *v1.ScanRequest + } + tests := []struct { + name string + args args + want string + wantErr assert.ErrorAssertionFunc + }{ + {"normal", args{&v1.ScanRequest{}}, "", assert.NoError}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &ScanHandler{} + got, err := v.ReportURLParameter(tt.args.in0) + if !tt.wantErr(t, err, fmt.Sprintf("ReportURLParameter(%v)", tt.args.in0)) { + return + } + assert.Equalf(t, tt.want, got, "ReportURLParameter(%v)", tt.args.in0) + }) + } +} + +func TestScanHandler_RequestProducesMineTypes(t *testing.T) { + tests := []struct { + name string + want []string + }{ + {"normal", []string{v1.MimeTypeGenericVulnerabilityReport}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &ScanHandler{} + assert.Equalf(t, tt.want, v.RequestProducesMineTypes(), "RequestProducesMineTypes()") + }) + } +} diff --git a/src/server/v2.0/handler/model/scanner.go b/src/server/v2.0/handler/model/scanner.go index bb140937f..a2fd45ce9 100644 --- a/src/server/v2.0/handler/model/scanner.go +++ b/src/server/v2.0/handler/model/scanner.go @@ -52,6 +52,7 @@ func (s *ScannerRegistration) ToSwagger(_ context.Context) *models.ScannerRegist Vendor: s.Vendor, Version: s.Version, Health: s.Health, + Capabilities: s.Capabilities, } } diff --git a/src/server/v2.0/handler/project.go b/src/server/v2.0/handler/project.go index 692a26d6f..f9848345f 100644 --- a/src/server/v2.0/handler/project.go +++ b/src/server/v2.0/handler/project.go @@ -594,7 +594,13 @@ func (a *projectAPI) GetScannerOfProject(ctx context.Context, params operation.G if err != nil { return a.SendError(ctx, err) } - + if scanner != nil { + metadata, err := a.scannerCtl.GetMetadata(ctx, scanner.UUID) + if err != nil { + return a.SendError(ctx, err) + } + scanner.Capabilities = metadata.ConvertCapability() + } return operation.NewGetScannerOfProjectOK().WithPayload(model.NewScannerRegistration(scanner).ToSwagger(ctx)) } diff --git a/src/server/v2.0/handler/project_test.go b/src/server/v2.0/handler/project_test.go index 21ba79a0a..9289829b8 100644 --- a/src/server/v2.0/handler/project_test.go +++ b/src/server/v2.0/handler/project_test.go @@ -22,6 +22,7 @@ import ( "github.com/goharbor/harbor/src/pkg/project/models" "github.com/goharbor/harbor/src/pkg/scan/dao/scanner" + v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" "github.com/goharbor/harbor/src/server/v2.0/restapi" projecttesting "github.com/goharbor/harbor/src/testing/controller/project" scannertesting "github.com/goharbor/harbor/src/testing/controller/scanner" @@ -36,6 +37,7 @@ type ProjectTestSuite struct { scannerCtl *scannertesting.Controller project *models.Project reg *scanner.Registration + metadata *v1.ScannerAdapterMetadata } func (suite *ProjectTestSuite) SetupSuite() { @@ -59,7 +61,12 @@ func (suite *ProjectTestSuite) SetupSuite() { scannerCtl: suite.scannerCtl, }, } - + suite.metadata = &v1.ScannerAdapterMetadata{ + Capabilities: []*v1.ScannerCapability{ + {Type: "vulnerability", ProducesMimeTypes: []string{v1.MimeTypeScanResponse}}, + {Type: "sbom", ProducesMimeTypes: []string{v1.MimeTypeSBOMReport}}, + }, + } suite.Suite.SetupSuite() } @@ -81,7 +88,7 @@ func (suite *ProjectTestSuite) TestGetScannerOfProject() { // scanner not found mock.OnAnything(suite.projectCtl, "Get").Return(suite.project, nil).Once() mock.OnAnything(suite.scannerCtl, "GetRegistrationByProject").Return(nil, nil).Once() - + mock.OnAnything(suite.scannerCtl, "GetMetadata").Return(suite.metadata, nil).Once() res, err := suite.Get("/projects/1/scanner") suite.NoError(err) suite.Equal(200, res.StatusCode) @@ -90,7 +97,7 @@ func (suite *ProjectTestSuite) TestGetScannerOfProject() { { mock.OnAnything(suite.projectCtl, "Get").Return(suite.project, nil).Once() mock.OnAnything(suite.scannerCtl, "GetRegistrationByProject").Return(suite.reg, nil).Once() - + mock.OnAnything(suite.scannerCtl, "GetMetadata").Return(suite.metadata, nil).Once() var scanner scanner.Registration res, err := suite.GetJSON("/projects/1/scanner", &scanner) suite.NoError(err) @@ -101,6 +108,7 @@ func (suite *ProjectTestSuite) TestGetScannerOfProject() { { mock.OnAnything(projectCtlMock, "GetByName").Return(suite.project, nil).Once() mock.OnAnything(suite.projectCtl, "Get").Return(suite.project, nil).Once() + mock.OnAnything(suite.scannerCtl, "GetMetadata").Return(suite.metadata, nil).Once() mock.OnAnything(suite.scannerCtl, "GetRegistrationByProject").Return(suite.reg, nil).Once() var scanner scanner.Registration diff --git a/src/server/v2.0/handler/scan.go b/src/server/v2.0/handler/scan.go index cca0092e8..80b70131f 100644 --- a/src/server/v2.0/handler/scan.go +++ b/src/server/v2.0/handler/scan.go @@ -25,6 +25,7 @@ import ( "github.com/goharbor/harbor/src/controller/scan" "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/pkg/distribution" + v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/scan" ) @@ -50,7 +51,15 @@ func (s *scanAPI) Prepare(ctx context.Context, _ string, params interface{}) mid } func (s *scanAPI) StopScanArtifact(ctx context.Context, params operation.StopScanArtifactParams) middleware.Responder { - if err := s.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionStop, rbac.ResourceScan); err != nil { + scanType := v1.ScanTypeVulnerability + if params.ScanType != nil && validScanType(params.ScanType.ScanType) { + scanType = params.ScanType.ScanType + } + res := rbac.ResourceScan + if scanType == v1.ScanTypeSbom { + res = rbac.ResourceSBOM + } + if err := s.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionStop, res); err != nil { return s.SendError(ctx, err) } @@ -68,22 +77,26 @@ func (s *scanAPI) StopScanArtifact(ctx context.Context, params operation.StopSca } func (s *scanAPI) ScanArtifact(ctx context.Context, params operation.ScanArtifactParams) middleware.Responder { - if err := s.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionCreate, rbac.ResourceScan); err != nil { - return s.SendError(ctx, err) - } - - repository := fmt.Sprintf("%s/%s", params.ProjectName, params.RepositoryName) - artifact, err := s.artCtl.GetByReference(ctx, repository, params.Reference, nil) - if err != nil { - return s.SendError(ctx, err) - } - + scanType := v1.ScanTypeVulnerability options := []scan.Option{} if !distribution.IsDigest(params.Reference) { options = append(options, scan.WithTag(params.Reference)) } if params.ScanRequestType != nil && validScanType(params.ScanRequestType.ScanType) { - options = append(options, scan.WithScanType(params.ScanRequestType.ScanType)) + scanType = params.ScanRequestType.ScanType + options = append(options, scan.WithScanType(scanType)) + } + res := rbac.ResourceScan + if scanType == v1.ScanTypeSbom { + res = rbac.ResourceSBOM + } + if err := s.RequireProjectAccess(ctx, params.ProjectName, rbac.ActionCreate, res); err != nil { + return s.SendError(ctx, err) + } + repository := fmt.Sprintf("%s/%s", params.ProjectName, params.RepositoryName) + artifact, err := s.artCtl.GetByReference(ctx, repository, params.Reference, nil) + if err != nil { + return s.SendError(ctx, err) } if err := s.scanCtl.Scan(ctx, artifact, options...); err != nil { diff --git a/src/testing/pkg/scan/rest/v1/client.go b/src/testing/pkg/scan/rest/v1/client.go index b17c21e71..1d15012c7 100644 --- a/src/testing/pkg/scan/rest/v1/client.go +++ b/src/testing/pkg/scan/rest/v1/client.go @@ -42,9 +42,9 @@ func (_m *Client) GetMetadata() (*v1.ScannerAdapterMetadata, error) { return r0, r1 } -// GetScanReport provides a mock function with given fields: scanRequestID, reportMIMEType -func (_m *Client) GetScanReport(scanRequestID string, reportMIMEType string) (string, error) { - ret := _m.Called(scanRequestID, reportMIMEType) +// GetScanReport provides a mock function with given fields: scanRequestID, reportMIMEType, urlParameter +func (_m *Client) GetScanReport(scanRequestID string, reportMIMEType string, urlParameter string) (string, error) { + ret := _m.Called(scanRequestID, reportMIMEType, urlParameter) if len(ret) == 0 { panic("no return value specified for GetScanReport") @@ -52,17 +52,17 @@ func (_m *Client) GetScanReport(scanRequestID string, reportMIMEType string) (st var r0 string var r1 error - if rf, ok := ret.Get(0).(func(string, string) (string, error)); ok { - return rf(scanRequestID, reportMIMEType) + if rf, ok := ret.Get(0).(func(string, string, string) (string, error)); ok { + return rf(scanRequestID, reportMIMEType, urlParameter) } - if rf, ok := ret.Get(0).(func(string, string) string); ok { - r0 = rf(scanRequestID, reportMIMEType) + if rf, ok := ret.Get(0).(func(string, string, string) string); ok { + r0 = rf(scanRequestID, reportMIMEType, urlParameter) } else { r0 = ret.Get(0).(string) } - if rf, ok := ret.Get(1).(func(string, string) error); ok { - r1 = rf(scanRequestID, reportMIMEType) + if rf, ok := ret.Get(1).(func(string, string, string) error); ok { + r1 = rf(scanRequestID, reportMIMEType, urlParameter) } else { r1 = ret.Error(1) } From 6709c789fb5a949a25c26c5f1a4e6086bc165ba8 Mon Sep 17 00:00:00 2001 From: Shengwen YU Date: Wed, 17 Apr 2024 15:52:58 +0800 Subject: [PATCH 094/145] feat: add test case for customizing OIDC provider name (#20287) Signed-off-by: Shengwen Yu --- tests/robot-cases/Group1-Nightly/OIDC.robot | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/robot-cases/Group1-Nightly/OIDC.robot b/tests/robot-cases/Group1-Nightly/OIDC.robot index f4b11079b..a29d1064b 100644 --- a/tests/robot-cases/Group1-Nightly/OIDC.robot +++ b/tests/robot-cases/Group1-Nightly/OIDC.robot @@ -26,6 +26,18 @@ Test Case - Get Harbor Version #Just get harbor version and log it Get Harbor Version +Test Case - Update OIDC Provider Name + [Tags] oidc_provider_name + Init Chrome Driver + Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} is_oidc=${true} + # Set OIDC Provider Name to TestDex + Switch To Configuration Authentication + Retry Text Input //input[@id='oidcName'] TestDex + Retry Element Click ${config_auth_save_button_xpath} + Logout Harbor + Retry Wait Until Page Contains Element //span[normalize-space()='LOGIN WITH TestDex'] + Close Browser + Test Case - OIDC User Sign In #Sign in with all 9 users is for user population, other test cases might use these users. Sign In Harbor With OIDC User ${HARBOR_URL} From fb2e0042d029c0eb9f2f753b2fad287e6c8a04e8 Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Wed, 17 Apr 2024 17:52:50 +0800 Subject: [PATCH 095/145] Rename scan request type (#20288) Signed-off-by: stonezdj --- api/v2.0/swagger.yaml | 11 ++--------- src/controller/scan/base_controller.go | 3 ++- src/server/v2.0/handler/scan.go | 4 ++-- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index 0f5c18e73..5f0457790 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -1176,11 +1176,11 @@ paths: - $ref: '#/parameters/projectName' - $ref: '#/parameters/repositoryName' - $ref: '#/parameters/reference' - - name: scan_request_type + - name: scanType in: body required: false schema: - $ref: '#/definitions/ScanRequestType' + $ref: '#/definitions/ScanType' responses: '202': $ref: '#/responses/202' @@ -6766,13 +6766,6 @@ definitions: type: string description: Version of the scanner adapter example: "v0.9.1" - ScanRequestType: - type: object - properties: - scan_type: - type: string - description: 'The scan type for the scan request. Two options are currently supported, vulnerability and sbom' - enum: [vulnerability, sbom] ScanOverview: type: object description: 'The scan overview attached in the metadata of tag' diff --git a/src/controller/scan/base_controller.go b/src/controller/scan/base_controller.go index be168098a..25f0fc791 100644 --- a/src/controller/scan/base_controller.go +++ b/src/controller/scan/base_controller.go @@ -742,10 +742,11 @@ func (bc *basicController) GetSBOMSummary(ctx context.Context, art *ar.Artifact, return map[string]interface{}{}, nil } reportContent := reports[0].Report + result := map[string]interface{}{} if len(reportContent) == 0 { log.Warning("no content for current report") + return result, nil } - result := map[string]interface{}{} err = json.Unmarshal([]byte(reportContent), &result) return result, err } diff --git a/src/server/v2.0/handler/scan.go b/src/server/v2.0/handler/scan.go index 80b70131f..a22eaaaed 100644 --- a/src/server/v2.0/handler/scan.go +++ b/src/server/v2.0/handler/scan.go @@ -82,8 +82,8 @@ func (s *scanAPI) ScanArtifact(ctx context.Context, params operation.ScanArtifac if !distribution.IsDigest(params.Reference) { options = append(options, scan.WithTag(params.Reference)) } - if params.ScanRequestType != nil && validScanType(params.ScanRequestType.ScanType) { - scanType = params.ScanRequestType.ScanType + if params.ScanType != nil && validScanType(params.ScanType.ScanType) { + scanType = params.ScanType.ScanType options = append(options, scan.WithScanType(scanType)) } res := rbac.ResourceScan From 2ea7d09412e25a838e217e2c4d52ee4d0f51ec1f Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Wed, 17 Apr 2024 22:51:11 +0800 Subject: [PATCH 096/145] skip to log scan sbom accessory for sbom accessory (#20290) Avoid to log the generate SBOM failure message when the artifact is SBOM in webhook event Signed-off-by: stonezdj --- src/controller/event/handler/internal/util.go | 6 +++--- src/controller/event/handler/internal/util_test.go | 6 ++---- src/controller/scan/base_controller.go | 13 ++++++++----- src/controller/scan/options.go | 9 +++++++++ 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/controller/event/handler/internal/util.go b/src/controller/event/handler/internal/util.go index cc10e09ca..51085a6f1 100644 --- a/src/controller/event/handler/internal/util.go +++ b/src/controller/event/handler/internal/util.go @@ -22,6 +22,7 @@ import ( "github.com/goharbor/harbor/src/controller/scan" "github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/orm" + v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" ) // autoScan scan artifact when the project of the artifact enable auto scan @@ -38,7 +39,7 @@ func autoScan(ctx context.Context, a *artifact.Artifact, tags ...string) error { return orm.WithTransaction(func(ctx context.Context) error { options := []scan.Option{} if len(tags) > 0 { - options = append(options, scan.WithTag(tags[0])) + options = append(options, scan.WithTag(tags[0]), scan.WithFromEvent(true)) } return scan.DefaultController.Scan(ctx, a, options...) @@ -56,8 +57,7 @@ func autoGenSBOM(ctx context.Context, a *artifact.Artifact) error { // transaction here to work with the image index return orm.WithTransaction(func(ctx context.Context) error { options := []scan.Option{} - // TODO: extract the sbom scan type to a constant - options = append(options, scan.WithScanType("sbom")) + options = append(options, scan.WithScanType(v1.ScanTypeSbom), scan.WithFromEvent(true)) log.Debugf("sbom scan controller artifact %+v, options %+v", a, options) return scan.DefaultController.Scan(ctx, a, options...) })(orm.SetTransactionOpNameToContext(ctx, "tx-auto-gen-sbom")) diff --git a/src/controller/event/handler/internal/util_test.go b/src/controller/event/handler/internal/util_test.go index 4a48378a4..158bcc2fe 100644 --- a/src/controller/event/handler/internal/util_test.go +++ b/src/controller/event/handler/internal/util_test.go @@ -101,9 +101,7 @@ func (suite *AutoScanTestSuite) TestAutoScanSBOM() { proModels.ProMetaAutoSBOMGen: "true", }, }, nil) - - mock.OnAnything(suite.scanController, "Scan").Return(nil) - + suite.scanController.On("Scan", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{}) art := &artifact.Artifact{} @@ -117,7 +115,7 @@ func (suite *AutoScanTestSuite) TestAutoScanSBOMFalse() { }, }, nil) - mock.OnAnything(suite.scanController, "Scan").Return(nil) + suite.scanController.On("Scan", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{}) art := &artifact.Artifact{} diff --git a/src/controller/scan/base_controller.go b/src/controller/scan/base_controller.go index 25f0fc791..52eb4eefe 100644 --- a/src/controller/scan/base_controller.go +++ b/src/controller/scan/base_controller.go @@ -247,17 +247,20 @@ func (bc *basicController) Scan(ctx context.Context, artifact *ar.Artifact, opti if err != nil { return err } - - if !scannable { - return errors.BadRequestError(nil).WithMessage("the configured scanner %s does not support scanning artifact with mime type %s", r.Name, artifact.ManifestMediaType) - } - // Parse options opts, err := parseOptions(options...) if err != nil { return errors.Wrap(err, "scan controller: scan") } + if !scannable { + if opts.FromEvent { + // skip to return err for event related scan + return nil + } + return errors.BadRequestError(nil).WithMessage("the configured scanner %s does not support scanning artifact with mime type %s", r.Name, artifact.ManifestMediaType) + } + var ( errs []error launchScanJobParams []*launchScanJobParam diff --git a/src/controller/scan/options.go b/src/controller/scan/options.go index 82e4e3d3e..c751ee100 100644 --- a/src/controller/scan/options.go +++ b/src/controller/scan/options.go @@ -21,6 +21,7 @@ type Options struct { ExecutionID int64 // The execution id to scan artifact Tag string // The tag of the artifact to scan ScanType string // The scan type could be sbom or vulnerability + FromEvent bool // indicate the current call from event or not } // GetScanType returns the scan type. for backward compatibility, the default type is vulnerability. @@ -63,3 +64,11 @@ func WithScanType(scanType string) Option { return nil } } + +// WithFromEvent set the caller's source +func WithFromEvent(fromEvent bool) Option { + return func(options *Options) error { + options.FromEvent = fromEvent + return nil + } +} From 4fd11ce072d650217b95c5e2ac84f3b8e5bbec8b Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Thu, 18 Apr 2024 15:26:03 +0900 Subject: [PATCH 097/145] refactor: update controller.go (#20297) minor fix Signed-off-by: Ikko Eltociear Ashimine Co-authored-by: MinerYang --- src/controller/scanner/controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controller/scanner/controller.go b/src/controller/scanner/controller.go index 9df9a6d36..d2ae007dd 100644 --- a/src/controller/scanner/controller.go +++ b/src/controller/scanner/controller.go @@ -113,7 +113,7 @@ type Controller interface { // Arguments: // ctx context.Context : the context.Context for this method // projectID int64 : the ID of the given project - // scannerID string : the UUID of the the scanner + // scannerID string : the UUID of the scanner // // Returns: // error : non nil error if any errors occurred From e8907a47ab6301298a2b77042b1408edf24a17c5 Mon Sep 17 00:00:00 2001 From: Lichao Xue <68891670+xuelichao@users.noreply.github.com> Date: Thu, 18 Apr 2024 16:22:11 +0800 Subject: [PATCH 098/145] SBOM UI feature implementation (#19946) * draft: sbom UI feature implementation Signed-off-by: xuelichao * refactor based on swagger yaml changes Signed-off-by: xuelichao * update scan type for scan and stop sbom request Signed-off-by: xuelichao --------- Signed-off-by: xuelichao --- .../create-edit-rule.component.spec.ts | 9 + .../artifact-additions.component.html | 117 ++++++--- .../artifact-additions.component.ts | 45 +++- .../artifact-sbom.component.html | 92 +++++++ .../artifact-sbom.component.scss | 74 ++++++ .../artifact-sbom.component.spec.ts | 194 ++++++++++++++ .../artifact-sbom/artifact-sbom.component.ts | 248 ++++++++++++++++++ .../artifact-list-page.service.ts | 43 +-- .../artifact-list-tab.component.html | 84 ++++-- .../artifact-list-tab.component.spec.ts | 13 +- .../artifact-list-tab.component.ts | 92 +++++-- .../artifact/artifact-summary.component.html | 2 + .../artifact-summary.component.spec.ts | 6 + .../artifact/artifact-summary.component.ts | 10 +- .../repository/artifact/artifact.module.ts | 2 + .../project/repository/artifact/artifact.ts | 195 ++++++++++++++ .../sbom-scanning/sbom-scan.component.spec.ts | 25 +- .../sbom-scanning/sbom-scan.component.ts | 23 +- .../sbom-tip-histogram.component.ts | 6 +- .../result-bar-chart.component.spec.ts | 2 +- .../result-bar-chart.component.ts | 6 +- ...rtifact-detail-routing-resolver.service.ts | 2 +- src/portal/src/app/shared/shared.module.ts | 17 ++ src/portal/src/app/shared/units/utils.ts | 33 +++ src/portal/src/i18n/lang/de-de-lang.json | 5 +- src/portal/src/i18n/lang/en-us-lang.json | 5 +- src/portal/src/i18n/lang/es-es-lang.json | 5 +- src/portal/src/i18n/lang/fr-fr-lang.json | 7 +- src/portal/src/i18n/lang/ko-kr-lang.json | 7 +- src/portal/src/i18n/lang/pt-br-lang.json | 7 +- src/portal/src/i18n/lang/tr-tr-lang.json | 5 +- src/portal/src/i18n/lang/zh-cn-lang.json | 5 +- src/portal/src/i18n/lang/zh-tw-lang.json | 3 +- 33 files changed, 1222 insertions(+), 167 deletions(-) create mode 100644 src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.html create mode 100644 src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.scss create mode 100644 src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.spec.ts create mode 100644 src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.ts diff --git a/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.spec.ts b/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.spec.ts index 3bdf95fa5..19e47f9bc 100644 --- a/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.spec.ts +++ b/src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.spec.ts @@ -281,4 +281,13 @@ describe('CreateEditRuleComponent (inline template)', () => { expect(ruleNameInput).toBeTruthy(); expect(ruleNameInput.value.trim()).toEqual('sync_01'); })); + + it('List all Registries Response', fakeAsync(() => { + fixture.detectChanges(); + comp.ngOnInit(); + comp.getAllRegistries(); + fixture.whenStable(); + tick(5000); + expect(comp.targetList.length).toBe(4); + })); }); diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.html b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.html index 20d2950b8..99208d3f4 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.html +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.html @@ -1,62 +1,105 @@

{{ 'ARTIFACT.ADDITIONS' | translate }}

- + - - - - + + + + + + + + + + + + + - - - - + + + + + - - - - + + + + + - - - - + + + + + - - - - + + + + +
diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.ts b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.ts index b31ef5df4..45994ac8e 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.ts @@ -1,15 +1,23 @@ -import { Component, Input } from '@angular/core'; +import { + AfterViewChecked, + ChangeDetectorRef, + Component, + Input, + OnInit, + ViewChild, +} from '@angular/core'; import { ADDITIONS } from './models'; import { AdditionLinks } from '../../../../../../../ng-swagger-gen/models/addition-links'; import { AdditionLink } from '../../../../../../../ng-swagger-gen/models/addition-link'; import { Artifact } from '../../../../../../../ng-swagger-gen/models/artifact'; +import { ClrTabs } from '@clr/angular'; @Component({ selector: 'artifact-additions', templateUrl: './artifact-additions.component.html', styleUrls: ['./artifact-additions.component.scss'], }) -export class ArtifactAdditionsComponent { +export class ArtifactAdditionsComponent implements AfterViewChecked, OnInit { @Input() artifact: Artifact; @Input() additionLinks: AdditionLinks; @Input() projectName: string; @@ -19,7 +27,28 @@ export class ArtifactAdditionsComponent { repoName: string; @Input() digest: string; - constructor() {} + @Input() + sbomDigest: string; + @Input() + tab: string; + + @Input() currentTabLinkId: string = 'vulnerability'; + activeTab: string = null; + + @ViewChild('additionsTab') tabs: ClrTabs; + constructor(private ref: ChangeDetectorRef) {} + + ngOnInit(): void { + this.activeTab = this.tab; + } + + ngAfterViewChecked() { + if (this.activeTab) { + this.currentTabLinkId = this.activeTab; + this.activeTab = null; + } + this.ref.detectChanges(); + } getVulnerability(): AdditionLink { if ( @@ -30,6 +59,12 @@ export class ArtifactAdditionsComponent { } return null; } + getSbom(): AdditionLink { + if (this.additionLinks && this.additionLinks[ADDITIONS.SBOMS]) { + return this.additionLinks[ADDITIONS.SBOMS]; + } + return {}; + } getBuildHistory(): AdditionLink { if (this.additionLinks && this.additionLinks[ADDITIONS.BUILD_HISTORY]) { return this.additionLinks[ADDITIONS.BUILD_HISTORY]; @@ -54,4 +89,8 @@ export class ArtifactAdditionsComponent { } return null; } + + actionTab(tab: string): void { + this.currentTabLinkId = tab; + } } diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.html b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.html new file mode 100644 index 000000000..c7b9cf8a6 --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.html @@ -0,0 +1,92 @@ +
+
+
+
+ +
+
+
+
+ + +
+
+ +
+
+
+ {{ + 'SBOM.GRID.COLUMN_PACKAGE' | translate + }} + {{ + 'SBOM.GRID.COLUMN_VERSION' | translate + }} + {{ + 'SBOM.GRID.COLUMN_LICENSE' | translate + }} + + {{ + 'SBOM.STATE.OTHER_STATUS' | translate + }} + + {{ 'SBOM.CHART.TOOLTIPS_TITLE_ZERO' | translate }} + + + + {{ + res.name ?? '' + }} + {{ + res.versionInfo ?? '' + }} + {{ res.licenseConcluded ?? '' }} + + + +
+ {{ + 'SBOM.REPORTED_BY' + | translate + : { + scanner: getScannerInfo( + artifact?.sbom_overview?.scanner + ) + } + }} +
+ + {{ + 'PAGINATION.PAGE_SIZE' | translate + }} + {{ pagination.firstItem + 1 }} - + {{ pagination.lastItem + 1 }} + {{ 'SBOM.GRID.FOOT_OF' | translate }} + {{ artifactSbomPackages().length }} + {{ 'SBOM.GRID.FOOT_ITEMS' | translate }} + +
+
+
+
diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.scss b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.scss new file mode 100644 index 000000000..9ec2fa919 --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.scss @@ -0,0 +1,74 @@ +.result-row { + position: relative; +} +/* stylelint-disable */ +.rightPos{ + position: absolute; + z-index: 100; + right: 0; + margin-top: 1.25rem; +} + +.option-right { + padding-right: 16px; + margin-top: 5px; +} + +.center { + align-items: center; +} + +.label-critical { + background:#ff4d2e; + color:#000; +} + +.label-danger { + background:#ff8f3d!important; + color:#000!important; +} + +.label-medium { + background-color: #ffce66; + color:#000; +} + +.label-low { + background: #fff1ad; + color:#000; +} + +.label-none { + background-color: #2ec0ff; + color:#000; +} + +.no-border { + border: none; +} + +.ml-05 { + margin-left: 0.5rem; +} + +.report { + text-align: left; +} + +.mt-5px { + margin-top: 5px; +} + +.label { + min-width: 3rem; +} + +.package-medium { + max-width: 25rem; + text-overflow: ellipsis; +} + +.version-medium { + min-width: 22rem; + text-overflow: ellipsis; +} diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.spec.ts b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.spec.ts new file mode 100644 index 000000000..e3978ad39 --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.spec.ts @@ -0,0 +1,194 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ArtifactSbomComponent } from './artifact-sbom.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ClarityModule } from '@clr/angular'; +import { of } from 'rxjs'; +import { + TranslateFakeLoader, + TranslateLoader, + TranslateModule, +} from '@ngx-translate/core'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { UserPermissionService } from '../../../../../../shared/services'; +import { AdditionLink } from '../../../../../../../../ng-swagger-gen/models/addition-link'; +import { ErrorHandler } from '../../../../../../shared/units/error-handler'; +import { SessionService } from '../../../../../../shared/services/session.service'; +import { SessionUser } from '../../../../../../shared/entities/session-user'; +import { AppConfigService } from 'src/app/services/app-config.service'; +import { ArtifactSbomPackageItem } from '../../artifact'; +import { ArtifactService } from 'ng-swagger-gen/services'; +import { ArtifactListPageService } from '../../artifact-list-page/artifact-list-page.service'; + +describe('ArtifactSbomComponent', () => { + let component: ArtifactSbomComponent; + let fixture: ComponentFixture; + const artifactSbomPackages: ArtifactSbomPackageItem[] = [ + { + name: 'alpine-baselayout', + SPDXID: 'SPDXRef-Package-5b53573c19a59415', + versionInfo: '3.2.0-r18', + supplier: 'NOASSERTION', + downloadLocation: 'NONE', + checksums: [ + { + algorithm: 'SHA1', + checksumValue: '132992eab020986b3b5d886a77212889680467a0', + }, + ], + sourceInfo: 'built package from: alpine-baselayout 3.2.0-r18', + licenseConcluded: 'GPL-2.0-only', + licenseDeclared: 'GPL-2.0-only', + copyrightText: '', + externalRefs: [ + { + referenceCategory: 'PACKAGE-MANAGER', + referenceType: 'purl', + referenceLocator: + 'pkg:apk/alpine/alpine-baselayout@3.2.0-r18?arch=x86_64\u0026distro=3.15.5', + }, + ], + attributionTexts: [ + 'PkgID: alpine-baselayout@3.2.0-r18', + 'LayerDiffID: sha256:ad543cd673bd9de2bac48599da992506dcc37a183179302ea934853aaa92cb84', + ], + primaryPackagePurpose: 'LIBRARY', + }, + { + name: 'alpine-keys', + SPDXID: 'SPDXRef-Package-7e5952f7a76e9643', + versionInfo: '2.4-r1', + supplier: 'NOASSERTION', + downloadLocation: 'NONE', + checksums: [ + { + algorithm: 'SHA1', + checksumValue: '903176b2d2a8ddefd1ba6940f19ad17c2c1d4aff', + }, + ], + sourceInfo: 'built package from: alpine-keys 2.4-r1', + licenseConcluded: 'MIT', + licenseDeclared: 'MIT', + copyrightText: '', + externalRefs: [ + { + referenceCategory: 'PACKAGE-MANAGER', + referenceType: 'purl', + referenceLocator: + 'pkg:apk/alpine/alpine-keys@2.4-r1?arch=x86_64\u0026distro=3.15.5', + }, + ], + attributionTexts: [ + 'PkgID: alpine-keys@2.4-r1', + 'LayerDiffID: sha256:ad543cd673bd9de2bac48599da992506dcc37a183179302ea934853aaa92cb84', + ], + primaryPackagePurpose: 'LIBRARY', + }, + ]; + const artifactSbomJson = { + spdxVersion: 'SPDX-2.3', + dataLicense: 'CC0-1.0', + SPDXID: 'SPDXRef-DOCUMENT', + name: 'alpine:3.15.5', + documentNamespace: + 'http://aquasecurity.github.io/trivy/container_image/alpine:3.15.5-7ead854c-7340-44c9-bbbf-5403c21cc9b6', + creationInfo: { + licenseListVersion: '', + creators: ['Organization: aquasecurity', 'Tool: trivy-0.47.0'], + created: '2023-11-29T07:06:22Z', + }, + packages: artifactSbomPackages, + }; + const fakedArtifactService = { + getAddition() { + return of(JSON.stringify(artifactSbomJson)); + }, + }; + const fakedUserPermissionService = { + hasProjectPermissions() { + return of(true); + }, + }; + const fakedAppConfigService = { + getConfig() { + return of({ sbom_enabled: true }); + }, + }; + const mockedUser: SessionUser = { + user_id: 1, + username: 'admin', + email: 'harbor@vmware.com', + realname: 'admin', + has_admin_role: true, + comment: 'no comment', + }; + const fakedSessionService = { + getCurrentUser() { + return mockedUser; + }, + }; + const mockedSbomDigest = + 'sha256:51a41cec9de9d62ee60e206f5a8a615a028a65653e45539990867417cb486285'; + const mockedArtifactListPageService = { + hasScannerSupportSBOM(): boolean { + return true; + }, + init() {}, + }; + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + BrowserAnimationsModule, + ClarityModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateFakeLoader, + }, + }), + ], + declarations: [ArtifactSbomComponent], + providers: [ + ErrorHandler, + { provide: AppConfigService, useValue: fakedAppConfigService }, + { provide: ArtifactService, useValue: fakedArtifactService }, + { + provide: UserPermissionService, + useValue: fakedUserPermissionService, + }, + { provide: SessionService, useValue: fakedSessionService }, + { + provide: ArtifactListPageService, + useValue: mockedArtifactListPageService, + }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ArtifactSbomComponent); + component = fixture.componentInstance; + component.hasSbomPermission = true; + component.hasScannerSupportSBOM = true; + component.sbomDigest = mockedSbomDigest; + component.ngOnInit(); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + it('should get sbom list and render', async () => { + fixture.detectChanges(); + await fixture.whenStable(); + const rows = fixture.nativeElement.getElementsByTagName('clr-dg-row'); + expect(rows.length).toEqual(2); + }); + + it('download button should show the right text', async () => { + fixture.autoDetectChanges(true); + const scanBtn: HTMLButtonElement = + fixture.nativeElement.querySelector('#sbom-btn'); + expect(scanBtn.innerText).toContain('SBOM.DOWNLOAD'); + }); +}); diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.ts b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.ts new file mode 100644 index 000000000..ac352ff0f --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.ts @@ -0,0 +1,248 @@ +import { + AfterViewInit, + Component, + Input, + OnDestroy, + OnInit, +} from '@angular/core'; +import { ClrDatagridStateInterface, ClrLoadingState } from '@clr/angular'; +import { finalize } from 'rxjs/operators'; +import { AdditionLink } from '../../../../../../../../ng-swagger-gen/models/addition-link'; +import { + ScannerVo, + UserPermissionService, + USERSTATICPERMISSION, +} from '../../../../../../shared/services'; +import { ErrorHandler } from '../../../../../../shared/units/error-handler'; +import { + dbEncodeURIComponent, + downloadJson, + getPageSizeFromLocalStorage, + PageSizeMapKeys, + SBOM_SCAN_STATUS, + setPageSizeToLocalStorage, +} from '../../../../../../shared/units/utils'; +import { Subscription } from 'rxjs'; +import { Artifact } from '../../../../../../../../ng-swagger-gen/models/artifact'; +import { SessionService } from '../../../../../../shared/services/session.service'; +import { + EventService, + HarborEvent, +} from '../../../../../../services/event-service/event.service'; +import { severityText } from '../../../../../left-side-nav/interrogation-services/vulnerability-database/security-hub.interface'; +import { AppConfigService } from 'src/app/services/app-config.service'; + +import { + ArtifactSbom, + ArtifactSbomPackageItem, + getArtifactSbom, +} from '../../artifact'; +import { ArtifactService } from 'ng-swagger-gen/services'; +import { ScanTypes } from 'src/app/shared/entities/shared.const'; +import { ArtifactListPageService } from '../../artifact-list-page/artifact-list-page.service'; + +@Component({ + selector: 'hbr-artifact-sbom', + templateUrl: './artifact-sbom.component.html', + styleUrls: ['./artifact-sbom.component.scss'], +}) +export class ArtifactSbomComponent implements OnInit, OnDestroy { + @Input() + projectName: string; + @Input() + projectId: number; + @Input() + repoName: string; + @Input() + sbomDigest: string; + @Input() artifact: Artifact; + + artifactSbom: ArtifactSbom; + loading: boolean = false; + hasScannerSupportSBOM: boolean = false; + downloadSbomBtnState: ClrLoadingState = ClrLoadingState.DEFAULT; + hasSbomPermission: boolean = false; + + hasShowLoading: boolean = false; + sub: Subscription; + hasViewInitWithDelay: boolean = false; + pageSize: number = getPageSizeFromLocalStorage( + PageSizeMapKeys.ARTIFACT_SBOM_COMPONENT, + 25 + ); + readonly severityText = severityText; + constructor( + private errorHandler: ErrorHandler, + private appConfigService: AppConfigService, + private artifactService: ArtifactService, + private artifactListPageService: ArtifactListPageService, + private userPermissionService: UserPermissionService, + private eventService: EventService, + private session: SessionService + ) {} + + ngOnInit() { + this.artifactListPageService.init(this.projectId); + this.getSbom(); + this.getSbomPermission(); + if (!this.sub) { + this.sub = this.eventService.subscribe( + HarborEvent.UPDATE_SBOM_INFO, + (artifact: Artifact) => { + if (artifact?.digest === this.artifact?.digest) { + if (artifact.sbom_overview) { + const sbomDigest = Object.values( + artifact.sbom_overview + )?.[0]?.sbom_digest; + if (sbomDigest) { + this.sbomDigest = sbomDigest; + } + } + this.getSbom(); + } + } + ); + } + setTimeout(() => { + this.hasViewInitWithDelay = true; + }, 0); + } + + ngOnDestroy() { + if (this.sub) { + this.sub.unsubscribe(); + this.sub = null; + } + } + + getSbom() { + if (this.sbomDigest) { + if (!this.hasShowLoading) { + this.loading = true; + this.hasShowLoading = true; + } + const sbomAdditionParams = { + repositoryName: dbEncodeURIComponent(this.repoName), + reference: this.sbomDigest, + projectName: this.projectName, + addition: ScanTypes.SBOM, + }; + this.artifactService + .getAddition(sbomAdditionParams) + .pipe( + finalize(() => { + this.loading = false; + this.hasShowLoading = false; + }) + ) + .subscribe( + res => { + if (res) { + this.artifactSbom = getArtifactSbom( + JSON.parse(res) + ); + } else { + this.loading = false; + this.hasShowLoading = false; + } + }, + error => { + this.errorHandler.error(error); + } + ); + } + } + + getSbomPermission(): void { + const permissions = [ + { + resource: USERSTATICPERMISSION.REPOSITORY_TAG_SBOM_JOB.KEY, + action: USERSTATICPERMISSION.REPOSITORY_TAG_SBOM_JOB.VALUE.READ, + }, + ]; + this.userPermissionService + .hasProjectPermissions(this.projectId, permissions) + .subscribe( + (results: Array) => { + this.hasSbomPermission = results[0]; + // only has label permission + }, + error => this.errorHandler.error(error) + ); + } + + refresh(): void { + this.getSbom(); + } + + hasGeneratedSbom(): boolean { + return this.hasViewInitWithDelay; + } + + isSystemAdmin(): boolean { + const account = this.session.getCurrentUser(); + return account && account.has_admin_role; + } + + getScannerInfo(scanner: ScannerVo): string { + if (scanner) { + if (scanner.name && scanner.version) { + return `${scanner.name}@${scanner.version}`; + } + if (scanner.name && !scanner.version) { + return `${scanner.name}`; + } + } + return ''; + } + + isRunningState(): boolean { + return ( + this.hasViewInitWithDelay && + this.artifact.sbom_overview && + (this.artifact.sbom_overview.scan_status === + SBOM_SCAN_STATUS.PENDING || + this.artifact.sbom_overview.scan_status === + SBOM_SCAN_STATUS.RUNNING) + ); + } + + downloadSbom() { + this.downloadSbomBtnState = ClrLoadingState.LOADING; + if ( + this.artifact?.sbom_overview?.scan_status === + SBOM_SCAN_STATUS.SUCCESS + ) { + downloadJson( + this.artifactSbom.sbomJsonRaw, + `${this.artifactSbom.sbomName}.json` + ); + } + this.downloadSbomBtnState = ClrLoadingState.DEFAULT; + } + + canDownloadSbom(): boolean { + this.hasScannerSupportSBOM = + this.artifactListPageService.hasScannerSupportSBOM(); + return ( + this.hasScannerSupportSBOM && + //this.hasSbomPermission && + this.sbomDigest && + this.downloadSbomBtnState !== ClrLoadingState.LOADING && + this.artifactSbom !== undefined + ); + } + + artifactSbomPackages(): ArtifactSbomPackageItem[] { + return this.artifactSbom?.sbomPackage?.packages ?? []; + } + + load(state: ClrDatagridStateInterface) { + if (state?.page?.size) { + setPageSizeToLocalStorage( + PageSizeMapKeys.ARTIFACT_SBOM_COMPONENT, + state.page.size + ); + } + } +} diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list-page.service.ts b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list-page.service.ts index a858938dd..daf0c0ae5 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list-page.service.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list-page.service.ts @@ -6,6 +6,7 @@ import { USERSTATICPERMISSION, } from '../../../../../shared/services'; import { ErrorHandler } from '../../../../../shared/units/error-handler'; +import { Scanner } from '../../../../left-side-nav/interrogation-services/scanner/scanner'; @Injectable() export class ArtifactListPageService { @@ -19,6 +20,7 @@ export class ArtifactListPageService { private _hasDeleteImagePermission: boolean = false; private _hasScanImagePermission: boolean = false; private _hasSbomPermission: boolean = false; + private _scanner: Scanner = undefined; constructor( private scanningService: ScanningResultService, @@ -26,6 +28,10 @@ export class ArtifactListPageService { private errorHandlerService: ErrorHandler ) {} + getProjectScanner(): Scanner { + return this._scanner; + } + getScanBtnState(): ClrLoadingState { return this._scanBtnState; } @@ -103,29 +109,28 @@ export class ArtifactListPageService { this._sbomBtnState = ClrLoadingState.LOADING; this.scanningService.getProjectScanner(projectId).subscribe( response => { - if ( - response && - '{}' !== JSON.stringify(response) && - !response.disabled && - response.health === 'healthy' - ) { - this.updateStates( - true, - ClrLoadingState.SUCCESS, - ClrLoadingState.SUCCESS - ); - if (response?.capabilities) { - this.updateCapabilities(response?.capabilities); + if (response && '{}' !== JSON.stringify(response)) { + this._scanner = response; + if (!response.disabled && response.health === 'healthy') { + this.updateStates( + true, + ClrLoadingState.SUCCESS, + ClrLoadingState.SUCCESS + ); + if (response?.capabilities) { + this.updateCapabilities(response?.capabilities); + } + } else { + this.updateStates( + false, + ClrLoadingState.ERROR, + ClrLoadingState.ERROR + ); } - } else { - this.updateStates( - false, - ClrLoadingState.ERROR, - ClrLoadingState.ERROR - ); } }, error => { + this._scanner = null; this.updateStates( false, ClrLoadingState.ERROR, diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.html b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.html index 1001da144..d523b67c1 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.html +++ b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.html @@ -24,6 +24,7 @@ canScanNow() && selectedRowHasVul() && hasEnabledScanner && + hasScannerSupportVulnerability && hasScanImagePermission ) " @@ -32,44 +33,26 @@ >  {{ 'VULNERABILITY.SCAN_NOW' | translate }} - - + + + + +
{{ 'REPOSITORY.COPY_DIGEST_ID' | translate }}
+ +
@@ -432,7 +462,7 @@ (submitStopFinish)="submitSbomStopFinish($event)" (scanFinished)="sbomFinished($event)" *ngIf="hasScannerSupportSBOM" - [inputScanner]="artifact?.sbom_overview?.scanner" + [inputScanner]="projectScanner" (submitFinish)="submitSbomFinish($event)" [projectName]="projectName" [projectId]="projectId" diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.spec.ts b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.spec.ts index 27010f065..bb45710a1 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.spec.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.spec.ts @@ -27,11 +27,17 @@ import { ArtifactModule } from '../../../artifact.module'; import { SBOM_SCAN_STATUS, VULNERABILITY_SCAN_STATUS, -} from 'src/app/shared/units/utils'; +} from '../../../../../../../shared/units/utils'; +import { Scanner } from '../../../../../../left-side-nav/interrogation-services/scanner/scanner'; describe('ArtifactListTabComponent', () => { let comp: ArtifactListTabComponent; let fixture: ComponentFixture; + const mockScanner = { + name: 'Trivy', + vendor: 'vm', + version: 'v1.2', + }; const mockActivatedRoute = { snapshot: { params: { @@ -274,6 +280,9 @@ describe('ArtifactListTabComponent', () => { hasScanImagePermission(): boolean { return true; }, + getProjectScanner(): Scanner { + return mockScanner; + }, init() {}, }; beforeEach(async () => { @@ -384,7 +393,7 @@ describe('ArtifactListTabComponent', () => { fixture.nativeElement.querySelector('#generate-sbom-btn'); fixture.detectChanges(); await fixture.whenStable(); - expect(generatedButton.disabled).toBeTruthy(); + expect(generatedButton.disabled).toBeFalsy(); }); it('Stop SBOM button should be disabled', async () => { await fixture.whenStable(); diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.ts b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.ts index 4675031dc..f73b448bb 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.ts @@ -84,6 +84,7 @@ import { Accessory } from 'ng-swagger-gen/models/accessory'; import { Tag } from '../../../../../../../../../ng-swagger-gen/models/tag'; import { CopyArtifactComponent } from './copy-artifact/copy-artifact.component'; import { CopyDigestComponent } from './copy-digest/copy-digest.component'; +import { Scanner } from '../../../../../../left-side-nav/interrogation-services/scanner/scanner'; export const AVAILABLE_TIME = '0001-01-01T00:00:00.000Z'; @@ -160,6 +161,10 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { get generateSbomBtnState(): ClrLoadingState { return this.artifactListPageService.getSbomBtnState(); } + get projectScanner(): Scanner { + return this.artifactListPageService.getProjectScanner(); + } + onSendingScanCommand: boolean; onSendingStopScanCommand: boolean = false; onStopScanArtifactsLength: number = 0; @@ -190,7 +195,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { false, false, false, - true, + false, true, false, false, @@ -269,6 +274,9 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { } ); } + if (this.projectId) { + this.artifactListPageService.init(this.projectId); + } } ngOnDestroy() { @@ -360,7 +368,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { withImmutableStatus: true, withLabel: true, withScanOverview: true, - // withSbomOverview: true, + withSbomOverview: true, withTag: false, XAcceptVulnerabilities: DEFAULT_SUPPORTED_MIME_TYPES, withAccessory: false, @@ -385,7 +393,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { withImmutableStatus: true, withLabel: true, withScanOverview: true, - // withSbomOverview: true, + withSbomOverview: true, withTag: false, XAcceptVulnerabilities: DEFAULT_SUPPORTED_MIME_TYPES, @@ -435,6 +443,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { repositoryName: dbEncodeURIComponent(this.repoName), withLabel: true, withScanOverview: true, + withSbomOverview: true, withTag: false, sort: getSortingString(state), XAcceptVulnerabilities: DEFAULT_SUPPORTED_MIME_TYPES, @@ -749,11 +758,15 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { if (this.activatedRoute.snapshot.queryParams[UN_LOGGED_PARAM] === YES) { this.router.navigate(relativeRouterLink, { relativeTo: this.activatedRoute, - queryParams: { [UN_LOGGED_PARAM]: YES }, + queryParams: { + [UN_LOGGED_PARAM]: YES, + sbomDigest: artifact.sbomDigest ?? '', + }, }); } else { this.router.navigate(relativeRouterLink, { relativeTo: this.activatedRoute, + queryParams: { sbomDigest: artifact.sbomDigest ?? '' }, }); } } @@ -800,6 +813,26 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { } return false; } + // if has running job, return false + canGenerateSbomNow(): boolean { + if (!this.hasSbomPermission) { + return false; + } + if (this.onSendingSbomCommand) { + return false; + } + if (this.selectedRow && this.selectedRow.length) { + let flag: boolean = true; + this.selectedRow.forEach(item => { + const st: string = this.sbomStatus(item); + if (this.isRunningState(st)) { + flag = false; + } + }); + return flag; + } + return false; + } // Trigger scan scanNow(): void { if (!this.selectedRow.length) { @@ -816,6 +849,22 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { ); }); } + // Generate SBOM + generateSbom(): void { + if (!this.selectedRow.length) { + return; + } + this.sbomFinishedArtifactLength = 0; + this.onSbomArtifactsLength = this.selectedRow.length; + this.onSendingSbomCommand = true; + this.selectedRow.forEach((data: any) => { + let digest = data.digest; + this.eventService.publish( + HarborEvent.START_GENERATE_SBOM, + this.repoName + '/' + digest + ); + }); + } selectedRowHasVul(): boolean { return !!( @@ -941,7 +990,6 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { } } } - // when finished, remove it from selectedRow sbomFinished(artifact: Artifact) { this.scanFinished(artifact); @@ -1019,18 +1067,17 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { }); } } - checkCosignAndSbomAsync(artifacts: ArtifactFront[]) { if (artifacts) { if (artifacts.length) { artifacts.forEach(item => { item.signed = CHECKING; - // const sbomOverview = item?.sbom_overview; - // item.sbomDigest = sbomOverview?.sbom_digest; - // let queryTypes = `${AccessoryType.COSIGN} ${AccessoryType.NOTATION}`; - // if (!item.sbomDigest) { - // queryTypes = `${queryTypes} ${AccessoryType.SBOM}`; - // } + const sbomOverview = item?.sbom_overview; + item.sbomDigest = sbomOverview?.sbom_digest; + let queryTypes = `${AccessoryType.COSIGN} ${AccessoryType.NOTATION}`; + if (!item.sbomDigest) { + queryTypes = `${queryTypes} ${AccessoryType.SBOM}`; + } this.newArtifactService .listAccessories({ projectName: this.projectName, @@ -1038,16 +1085,21 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { reference: item.digest, page: 1, pageSize: ACCESSORY_PAGE_SIZE, - q: encodeURIComponent( - `type={${AccessoryType.COSIGN} ${AccessoryType.NOTATION}}` - ), + q: encodeURIComponent(`type={${queryTypes}}`), }) .subscribe({ next: res => { - if (res?.length) { - item.signed = TRUE; - } else { - item.signed = FALSE; + item.signed = res?.filter( + item => item.type !== AccessoryType.SBOM + )?.length + ? TRUE + : FALSE; + if (!item.sbomDigest) { + item.sbomDigest = + res?.filter( + item => + item.type === AccessoryType.SBOM + )?.[0]?.digest ?? null; } }, error: err => { @@ -1075,7 +1127,6 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { } return false; } - // return true if all selected rows are in "running" state canStopSbom(): boolean { if (this.onSendingStopSbomCommand) { @@ -1142,6 +1193,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { } return null; } + deleteAccessory(a: Accessory) { let titleKey: string, summaryKey: string, diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-summary.component.html b/src/portal/src/app/base/project/repository/artifact/artifact-summary.component.html index 436363325..05465eae3 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-summary.component.html +++ b/src/portal/src/app/base/project/repository/artifact/artifact-summary.component.html @@ -59,6 +59,8 @@ [projectId]="projectId" [repoName]="repositoryName" [digest]="artifactDigest" + [sbomDigest]="sbomDigest" + [tab]="activeTab" [additionLinks]="artifact?.addition_links">
diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-summary.component.spec.ts b/src/portal/src/app/base/project/repository/artifact/artifact-summary.component.spec.ts index 9a6a0bf2f..1a7944e83 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-summary.component.spec.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-summary.component.spec.ts @@ -30,6 +30,8 @@ describe('ArtifactSummaryComponent', () => { return undefined; }, }; + const mockedSbomDigest = + 'sha256:51a41cec9de9d62ee60e206f5a8a615a028a65653e45539990867417cb486285'; let component: ArtifactSummaryComponent; let fixture: ComponentFixture; const mockActivatedRoute = { @@ -42,6 +44,9 @@ describe('ArtifactSummaryComponent', () => { return of(null); }, }, + queryParams: { + sbomDigest: mockedSbomDigest, + }, parent: { params: { id: 1, @@ -89,6 +94,7 @@ describe('ArtifactSummaryComponent', () => { component = fixture.componentInstance; component.repositoryName = 'demo'; component.artifactDigest = 'sha: acf4234f'; + component.sbomDigest = mockedSbomDigest; fixture.detectChanges(); }); diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-summary.component.ts b/src/portal/src/app/base/project/repository/artifact/artifact-summary.component.ts index 7aa95189d..de2f96444 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-summary.component.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-summary.component.ts @@ -1,10 +1,7 @@ import { Component, EventEmitter, OnInit, Output } from '@angular/core'; import { Artifact } from '../../../../../../ng-swagger-gen/models/artifact'; -import { ErrorHandler } from '../../../../shared/units/error-handler'; import { Label } from '../../../../../../ng-swagger-gen/models/label'; -import { ProjectService } from '../../../../shared/services'; import { ActivatedRoute, Router } from '@angular/router'; -import { AppConfigService } from '../../../../services/app-config.service'; import { Project } from '../../project'; import { artifactDefault } from './artifact'; import { SafeUrl } from '@angular/platform-browser'; @@ -24,6 +21,8 @@ import { export class ArtifactSummaryComponent implements OnInit { tagId: string; artifactDigest: string; + sbomDigest?: string; + activeTab?: string; repositoryName: string; projectId: string | number; referArtifactNameArray: string[] = []; @@ -37,10 +36,7 @@ export class ArtifactSummaryComponent implements OnInit { loading: boolean = false; constructor( - private projectService: ProjectService, - private errorHandler: ErrorHandler, private route: ActivatedRoute, - private appConfigService: AppConfigService, private router: Router, private frontEndArtifactService: ArtifactService, private event: EventService @@ -100,6 +96,8 @@ export class ArtifactSummaryComponent implements OnInit { this.repositoryName = this.route.snapshot.params['repo']; this.artifactDigest = this.route.snapshot.params['digest']; this.projectId = this.route.snapshot.parent.params['id']; + this.sbomDigest = this.route.snapshot.queryParams['sbomDigest']; + this.activeTab = this.route.snapshot.queryParams['tab']; if (this.repositoryName && this.artifactDigest) { const resolverData = this.route.snapshot.data; if (resolverData) { diff --git a/src/portal/src/app/base/project/repository/artifact/artifact.module.ts b/src/portal/src/app/base/project/repository/artifact/artifact.module.ts index 4ff6da2d4..0272818b8 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact.module.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact.module.ts @@ -12,6 +12,7 @@ import { SummaryComponent } from './artifact-additions/summary/summary.component import { DependenciesComponent } from './artifact-additions/dependencies/dependencies.component'; import { BuildHistoryComponent } from './artifact-additions/build-history/build-history.component'; import { ArtifactVulnerabilitiesComponent } from './artifact-additions/artifact-vulnerabilities/artifact-vulnerabilities.component'; +import { ArtifactSbomComponent } from './artifact-additions/artifact-sbom/artifact-sbom.component'; import { ArtifactDefaultService, ArtifactService } from './artifact.service'; import { ArtifactDetailRoutingResolverService } from '../../../../services/routing-resolvers/artifact-detail-routing-resolver.service'; import { ResultBarChartComponent } from './vulnerability-scanning/result-bar-chart.component'; @@ -80,6 +81,7 @@ const routes: Routes = [ SummaryComponent, DependenciesComponent, BuildHistoryComponent, + ArtifactSbomComponent, ArtifactVulnerabilitiesComponent, ResultBarChartComponent, ResultSbomComponent, diff --git a/src/portal/src/app/base/project/repository/artifact/artifact.ts b/src/portal/src/app/base/project/repository/artifact/artifact.ts index 9a2c379ae..fa3e19c7d 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact.ts @@ -10,6 +10,7 @@ export interface ArtifactFront extends Artifact { annotationsArray?: Array<{ [key: string]: any }>; tagNumber?: number; signed?: string; + sbomDigest?: string; accessoryNumber?: number; } @@ -75,6 +76,7 @@ export enum AccessoryType { COSIGN = 'signature.cosign', NOTATION = 'signature.notation', NYDUS = 'accelerator.nydus', + SBOM = 'harbor.sbom', } export enum ArtifactType { @@ -166,3 +168,196 @@ export enum ClientNames { CHART = 'Helm', CNAB = 'CNAB', } + +export enum ArtifactSbomType { + SPDX = 'SPDX', +} + +export interface ArtifactSbomPackageItem { + name?: string; + versionInfo?: string; + licenseConcluded?: string; + [key: string]: Object; +} + +export interface ArtifactSbomPackage { + packages: ArtifactSbomPackageItem[]; +} + +export interface ArtifactSbom { + sbomType: ArtifactSbomType; + sbomVersion: string; + sbomName?: string; + sbomDataLicense?: string; + sbomId?: string; + sbomDocumentNamespace?: string; + sbomCreated?: string; + sbomPackage?: ArtifactSbomPackage; + sbomJsonRaw?: Object; +} + +export const ArtifactSbomFieldMapper = { + sbomVersion: 'spdxVersion', + sbomName: 'name', + sbomDataLicense: 'dataLicense', + sbomId: 'SPDXID', + sbomDocumentNamespace: 'documentNamespace', + sbomCreated: 'creationInfo.created', + sbomPackage: { + packages: ['name', 'versionInfo', 'licenseConcluded'], + }, +}; + +/** + * Identify the sbomJson contains the two main properties 'spdxVersion' and 'SPDXID'. + * @param sbomJson SBOM JSON report object. + * @returns true or false + * Return true when the sbomJson object contains the attribues 'spdxVersion' and 'SPDXID'. + * else return false. + */ +export function isSpdxSbom(sbomJson?: Object): boolean { + return Object.keys(sbomJson ?? {}).includes(ArtifactSbomFieldMapper.sbomId); +} + +/** + * Update the value to the data object with the field path. + * @param fieldPath field class path eg {a: {b:'test'}}. field path for b is 'a.b' + * @param data The target object to receive the value. + * @param value The value will be set to the data object. + */ +export function updateObjectWithFieldPath( + fieldPath: string, + data: Object, + value: Object +) { + if (fieldPath && data) { + const fields = fieldPath?.split('.'); + let tempData = data; + fields.forEach((field, index) => { + const properties = Object.getOwnPropertyNames(tempData); + if (field !== '__proto__' && field !== 'constructor') { + if (index === fields.length - 1) { + tempData[field] = value; + } else { + if (!properties.includes(field)) { + tempData[field] = {}; + } + tempData = tempData[field]; + } + } + }); + } +} + +/** + * Get value from data object with field path. + * @param fieldPath field class path eg {a: {b:'test'}}. field path for b is 'a.b' + * @param data The data source target object. + * @returns The value read from data object. + */ +export const getValueFromObjectWithFieldPath = ( + fieldPath: string, + data: Object +) => { + let tempObject = data; + if (fieldPath && data) { + const fields = fieldPath?.split('.'); + fields.forEach(field => { + if (tempObject) { + tempObject = tempObject[field] ?? null; + } + }); + } + return tempObject; +}; + +/** + * Get value from source data object with field path. + * @param fieldPathObject The Object that contains the field paths. + * If we have an Object - {a: {b: 'test', c: [{ d: 2, e: 'v'}]}}. + * The field path for b is 'a.b'. + * The field path for c is {'a.c': ['d', 'e']'}. + * @param sourceData The data source target object. + * @returns the value by field class path. + */ +export function readDataFromArtifactSbomJson( + fieldPathObject: Object, + sourceData: Object +): Object { + let result = null; + if (sourceData) { + switch (typeof fieldPathObject) { + case 'string': + result = getValueFromObjectWithFieldPath( + fieldPathObject, + sourceData + ); + break; + case 'object': + if ( + Array.isArray(fieldPathObject) && + Array.isArray(sourceData) + ) { + result = sourceData.map(source => { + let arrayItem = {}; + fieldPathObject.forEach(field => { + updateObjectWithFieldPath( + field, + arrayItem, + readDataFromArtifactSbomJson(field, source) + ); + }); + return arrayItem; + }); + } else { + const fields = Object.getOwnPropertyNames(fieldPathObject); + result = result ? result : {}; + fields.forEach(field => { + if (sourceData[field]) { + updateObjectWithFieldPath( + field, + result, + readDataFromArtifactSbomJson( + fieldPathObject[field], + sourceData[field] + ) + ); + } + }); + } + break; + default: + break; + } + } + return result; +} + +/** + * Convert SBOM Json report to ArtifactSbom + * @param sbomJson SBOM report in Json format + * @returns ArtifactSbom || null + */ +export function getArtifactSbom(sbomJson?: Object): ArtifactSbom { + if (sbomJson) { + if (isSpdxSbom(sbomJson)) { + const artifactSbom = {}; + artifactSbom.sbomJsonRaw = sbomJson; + artifactSbom.sbomType = ArtifactSbomType.SPDX; + // only retrieve the fields defined in ArtifactSbomFieldMapper + const fields = Object.getOwnPropertyNames(ArtifactSbomFieldMapper); + fields.forEach(field => { + updateObjectWithFieldPath( + field, + artifactSbom, + readDataFromArtifactSbomJson( + ArtifactSbomFieldMapper[field], + sbomJson + ) + ); + }); + return artifactSbom; + } + } + return null; +} diff --git a/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan.component.spec.ts b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan.component.spec.ts index af74ddde8..0f8398c59 100644 --- a/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan.component.spec.ts +++ b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan.component.spec.ts @@ -10,7 +10,6 @@ import { SbomTipHistogramComponent } from './sbom-tip-histogram/sbom-tip-histogr import { SBOMOverview } from './sbom-overview'; import { of, timer } from 'rxjs'; import { ArtifactService, ScanService } from 'ng-swagger-gen/services'; -import { Artifact } from 'ng-swagger-gen/models'; describe('ResultSbomComponent (inline template)', () => { let component: ResultSbomComponent; @@ -21,23 +20,18 @@ describe('ResultSbomComponent (inline template)', () => { }; const mockedSbomDigest = 'sha256:052240e8190b7057439d2bee1dffb9b37c8800e5c1af349f667635ae1debf8f3'; + const mockScanner = { + name: 'Trivy', + vendor: 'vm', + version: 'v1.2', + }; const mockedSbomOverview = { report_id: '12345', scan_status: 'Error', - scanner: { - name: 'Trivy', - vendor: 'vm', - version: 'v1.2', - }, }; const mockedCloneSbomOverview = { report_id: '12346', scan_status: 'Pending', - scanner: { - name: 'Trivy', - vendor: 'vm', - version: 'v1.2', - }, }; const FakedScanService = { scanArtifact: () => of({}), @@ -120,6 +114,7 @@ describe('ResultSbomComponent (inline template)', () => { fixture = TestBed.createComponent(ResultSbomComponent); component = fixture.componentInstance; component.repoName = 'mockRepo'; + component.inputScanner = mockScanner; component.artifactDigest = mockedSbomDigest; component.sbomDigest = mockedSbomDigest; component.sbomOverview = mockData; @@ -180,9 +175,11 @@ describe('ResultSbomComponent (inline template)', () => { }); it('Test ResultSbomComponent getScanner', () => { fixture.detectChanges(); + component.inputScanner = undefined; expect(component.getScanner()).toBeUndefined(); + component.inputScanner = mockScanner; component.sbomOverview = mockedSbomOverview; - expect(component.getScanner()).toBe(mockedSbomOverview.scanner); + expect(component.getScanner()).toBe(mockScanner); component.projectName = 'test'; component.repoName = 'ui'; component.artifactDigest = 'dg'; @@ -239,7 +236,9 @@ describe('ResultSbomComponent (inline template)', () => { fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); - expect(component.stateCheckTimer).toBeUndefined(); + expect(component.sbomOverview.scan_status).toBe( + SBOM_SCAN_STATUS.SUCCESS + ); }); }); }); diff --git a/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan.component.ts b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan.component.ts index 4cfced2f8..46829dd4f 100644 --- a/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan.component.ts +++ b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-scan.component.ts @@ -8,7 +8,6 @@ import { } from '@angular/core'; import { Subscription, timer } from 'rxjs'; import { finalize } from 'rxjs/operators'; -import { ScannerVo } from '../../../../../shared/services'; import { ErrorHandler } from '../../../../../shared/units/error-handler'; import { clone, @@ -27,6 +26,7 @@ import { ScanService } from '../../../../../../../ng-swagger-gen/services/scan.s import { ScanType } from 'ng-swagger-gen/models'; import { ScanTypes } from '../../../../../shared/entities/shared.const'; import { SBOMOverview } from './sbom-overview'; +import { Scanner } from '../../../../left-side-nav/interrogation-services/scanner/scanner'; const STATE_CHECK_INTERVAL: number = 3000; // 3s const RETRY_TIMES: number = 3; @@ -36,7 +36,7 @@ const RETRY_TIMES: number = 3; styleUrls: ['./scanning.scss'], }) export class ResultSbomComponent implements OnInit, OnDestroy { - @Input() inputScanner: ScannerVo; + @Input() inputScanner: Scanner; @Input() repoName: string = ''; @Input() projectName: string = ''; @Input() projectId: string = ''; @@ -176,9 +176,9 @@ export class ResultSbomComponent implements OnInit, OnDestroy { projectName: this.projectName, reference: this.artifactDigest, repositoryName: dbEncodeURIComponent(this.repoName), - // scanType: { - // scan_type: ScanTypes.SBOM, - // }, + scanType: { + scan_type: ScanTypes.SBOM, + }, }) .pipe(finalize(() => this.submitFinish.emit(false))) .subscribe( @@ -219,15 +219,15 @@ export class ResultSbomComponent implements OnInit, OnDestroy { projectName: this.projectName, repositoryName: dbEncodeURIComponent(this.repoName), reference: this.artifactDigest, - // withSbomOverview: true, + withSbomOverview: true, XAcceptVulnerabilities: DEFAULT_SUPPORTED_MIME_TYPES, }) .subscribe( (artifact: Artifact) => { // To keep the same summary reference, use value copy. - // if (artifact.sbom_overview) { - // this.copyValue(artifact.sbom_overview); - // } + if (artifact.sbom_overview) { + this.copyValue(artifact.sbom_overview); + } if (!this.queued && !this.generating) { // Scanning should be done if (this.stateCheckTimer) { @@ -271,10 +271,7 @@ export class ResultSbomComponent implements OnInit, OnDestroy { }/scan/${this.sbomOverview.report_id}/log`; } - getScanner(): ScannerVo { - if (this.sbomOverview && this.sbomOverview.scanner) { - return this.sbomOverview.scanner; - } + getScanner(): Scanner { return this.inputScanner; } diff --git a/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.ts b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.ts index 2e948442b..f75050dc1 100644 --- a/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.ts +++ b/src/portal/src/app/base/project/repository/artifact/sbom-scanning/sbom-tip-histogram/sbom-tip-histogram.component.ts @@ -1,7 +1,7 @@ import { Component, Input } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { ScannerVo, SbomSummary } from '../../../../../../shared/services'; +import { SbomSummary } from '../../../../../../shared/services'; import { SBOM_SCAN_STATUS } from '../../../../../../shared/units/utils'; import { UN_LOGGED_PARAM, @@ -9,6 +9,7 @@ import { } from '../../../../../../account/sign-in/sign-in.service'; import { HAS_STYLE_MODE, StyleMode } from '../../../../../../services/theme'; import { ScanTypes } from '../../../../../../shared/entities/shared.const'; +import { Scanner } from '../../../../../left-side-nav/interrogation-services/scanner/scanner'; const MIN = 60; const MIN_STR = 'min '; @@ -21,7 +22,7 @@ const SUCCESS_PCT: number = 100; styleUrls: ['./sbom-tip-histogram.component.scss'], }) export class SbomTipHistogramComponent { - @Input() scanner: ScannerVo; + @Input() scanner: Scanner; @Input() sbomSummary: SbomSummary = { scan_status: SBOM_SCAN_STATUS.NOT_GENERATED_SBOM, }; @@ -54,6 +55,7 @@ export class SbomTipHistogramComponent { ? `100%` : '0%'; } + isLimitedSuccess(): boolean { return ( this.sbomSummary && this.sbomSummary.complete_percent < SUCCESS_PCT diff --git a/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-bar-chart.component.spec.ts b/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-bar-chart.component.spec.ts index 9f840d3ec..66534a82b 100644 --- a/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-bar-chart.component.spec.ts +++ b/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-bar-chart.component.spec.ts @@ -256,8 +256,8 @@ describe('ResultBarChartComponent (inline template)', () => { }); it('Test ResultBarChartComponent getSummary', () => { fixture.detectChanges(); - // component.summary.scan_status = VULNERABILITY_SCAN_STATUS.SUCCESS; component.getSummary(); + component.summary.scan_status = VULNERABILITY_SCAN_STATUS.SUCCESS; fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); diff --git a/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-bar-chart.component.ts b/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-bar-chart.component.ts index ceb8e25b2..ceef85bbe 100644 --- a/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-bar-chart.component.ts +++ b/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-bar-chart.component.ts @@ -173,9 +173,9 @@ export class ResultBarChartComponent implements OnInit, OnDestroy { projectName: this.projectName, reference: this.artifactDigest, repositoryName: dbEncodeURIComponent(this.repoName), - // scanType: { - // scan_type: ScanTypes.VULNERABILITY, - // }, + scanType: { + scan_type: ScanTypes.VULNERABILITY, + }, }) .pipe(finalize(() => this.submitFinish.emit(false))) .subscribe( diff --git a/src/portal/src/app/services/routing-resolvers/artifact-detail-routing-resolver.service.ts b/src/portal/src/app/services/routing-resolvers/artifact-detail-routing-resolver.service.ts index 5aa9f8939..c67761e2a 100644 --- a/src/portal/src/app/services/routing-resolvers/artifact-detail-routing-resolver.service.ts +++ b/src/portal/src/app/services/routing-resolvers/artifact-detail-routing-resolver.service.ts @@ -51,7 +51,7 @@ export class ArtifactDetailRoutingResolverService { projectName: project.name, withLabel: true, withScanOverview: true, - // withSbomOverview: true, + withSbomOverview: true, withTag: false, withImmutableStatus: true, }), diff --git a/src/portal/src/app/shared/shared.module.ts b/src/portal/src/app/shared/shared.module.ts index 5409a12e5..b64995e7a 100644 --- a/src/portal/src/app/shared/shared.module.ts +++ b/src/portal/src/app/shared/shared.module.ts @@ -127,6 +127,23 @@ ClarityIcons.add({ 21.18,0,0,0,4,21.42,21,21,0,0,0,7.71,33.58a1,1,0,0,0,.81.42h19a1,1,0,0,0, .81-.42A21,21,0,0,0,32,21.42,21.18,21.18,0,0,0,29.1,10.49Z"/> `, + sbom: ` + + + + + + + + + + + + +SBOM + + +`, }); @NgModule({ diff --git a/src/portal/src/app/shared/units/utils.ts b/src/portal/src/app/shared/units/utils.ts index 4efa2eadb..1a7a91df7 100644 --- a/src/portal/src/app/shared/units/utils.ts +++ b/src/portal/src/app/shared/units/utils.ts @@ -252,6 +252,18 @@ export const DEFAULT_PAGE_SIZE: number = 15; */ export const DEFAULT_SUPPORTED_MIME_TYPES = 'application/vnd.security.vulnerability.report; version=1.1, application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0'; +/** + * The default supported mime type for SBOM + */ +export const DEFAULT_SBOM_SUPPORTED_MIME_TYPES = + 'application/vnd.security.sbom.report+json; version=1.0'; +/** + * The SBOM supported additional mime types + */ +export const SBOM_SUPPORTED_ADDITIONAL_MIME_TYPES = [ + 'application/spdx+json', + // 'application/vnd.cyclonedx+json', // feature release +]; /** * the property name of vulnerability database updated time @@ -483,6 +495,26 @@ export function downloadFile(fileData) { a.remove(); } +/** + * Download the Json Object as a Json file to local. + * @param data Json Object + * @param filename Json filename + */ +export function downloadJson(data, filename) { + const blob = new Blob([JSON.stringify(data)], { + type: 'application/json;charset=utf-8', + }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + document.body.appendChild(a); + a.setAttribute('style', 'display: none'); + a.href = url; + a.download = filename; + a.click(); + window.URL.revokeObjectURL(url); + a.remove(); +} + export function getChanges( original: any, afterChange: any @@ -1030,6 +1062,7 @@ export enum PageSizeMapKeys { ARTIFACT_LIST_TAB_COMPONENT = 'ArtifactListTabComponent', ARTIFACT_TAGS_COMPONENT = 'ArtifactTagComponent', ARTIFACT_VUL_COMPONENT = 'ArtifactVulnerabilitiesComponent', + ARTIFACT_SBOM_COMPONENT = 'ArtifactSbomComponent', MEMBER_COMPONENT = 'MemberComponent', LABEL_COMPONENT = 'LabelComponent', P2P_POLICY_COMPONENT = 'P2pPolicyComponent', diff --git a/src/portal/src/i18n/lang/de-de-lang.json b/src/portal/src/i18n/lang/de-de-lang.json index cae7b7af0..eb640c0ba 100644 --- a/src/portal/src/i18n/lang/de-de-lang.json +++ b/src/portal/src/i18n/lang/de-de-lang.json @@ -804,6 +804,7 @@ "FILTER_BY_LABEL": "Images nach Label filtern", "FILTER_ARTIFACT_BY_LABEL": "Artefakte nach Label filtern", "ADD_LABELS": "Label hinzufügen", + "STOP": "Stop", "RETAG": "Kopieren", "ACTION": "AKTION", "DEPLOY": "Bereitstellen", @@ -1057,7 +1058,7 @@ "NO_SBOM": "No SBOM", "PACKAGES": "SBOM", "REPORTED_BY": "Reported by {{scanner}}", - "GENERATE": "Create SBOM", + "GENERATE": "Generate SBOM", "DOWNLOAD": "Download SBOM", "Details": "SBOM details", "STOP": "Stop SBOM", @@ -1107,7 +1108,7 @@ "PLACEHOLDER": "Filter Schwachstellen", "PACKAGE": "Paket", "PACKAGES": "Pakete", - "SCAN_NOW": "Scan", + "SCAN_NOW": "Scan vulnerability", "SCAN_BY": "SCAN DURCH {{scanner}}", "REPORTED_BY": "GEMELDET VON {{scanner}}", "NO_SCANNER": "KEIN SCANNER", diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json index 93b802401..27897783a 100644 --- a/src/portal/src/i18n/lang/en-us-lang.json +++ b/src/portal/src/i18n/lang/en-us-lang.json @@ -805,6 +805,7 @@ "FILTER_BY_LABEL": "Filter images by label", "FILTER_ARTIFACT_BY_LABEL": "Filter actifact by label", "ADD_LABELS": "Add Labels", + "STOP": "Stop", "RETAG": "Copy", "ACTION": "ACTION", "DEPLOY": "DEPLOY", @@ -1058,7 +1059,7 @@ "NO_SBOM": "No SBOM", "PACKAGES": "SBOM", "REPORTED_BY": "Reported by {{scanner}}", - "GENERATE": "Create SBOM", + "GENERATE": "Generate SBOM ", "DOWNLOAD": "Download SBOM", "Details": "SBOM details", "STOP": "Stop SBOM", @@ -1108,7 +1109,7 @@ "PLACEHOLDER": "Filter Vulnerabilities", "PACKAGE": "package", "PACKAGES": "packages", - "SCAN_NOW": "Scan", + "SCAN_NOW": "Scan vulnerability ", "SCAN_BY": "SCAN BY {{scanner}}", "REPORTED_BY": "Reported by {{scanner}}", "NO_SCANNER": "NO SCANNER", diff --git a/src/portal/src/i18n/lang/es-es-lang.json b/src/portal/src/i18n/lang/es-es-lang.json index 11dc5b0b5..0605cdeaa 100644 --- a/src/portal/src/i18n/lang/es-es-lang.json +++ b/src/portal/src/i18n/lang/es-es-lang.json @@ -805,6 +805,7 @@ "FILTER_BY_LABEL": "Filter images by label", "FILTER_ARTIFACT_BY_LABEL": "Filter actifact by label", "ADD_LABELS": "Add Labels", + "STOP": "Stop", "RETAG": "Copy", "ACTION": "ACTION", "DEPLOY": "DEPLOY", @@ -1056,7 +1057,7 @@ "NO_SBOM": "No SBOM", "PACKAGES": "SBOM", "REPORTED_BY": "Reported by {{scanner}}", - "GENERATE": "Create SBOM", + "GENERATE": "Generate SBOM", "DOWNLOAD": "Download SBOM", "Details": "SBOM details", "STOP": "Stop SBOM", @@ -1106,7 +1107,7 @@ "PLACEHOLDER": "Filter Vulnerabilities", "PACKAGE": "package", "PACKAGES": "packages", - "SCAN_NOW": "Scan", + "SCAN_NOW": "Scan vulnerability", "SCAN_BY": "SCAN BY {{scanner}}", "REPORTED_BY": "Reported by {{scanner}}", "NO_SCANNER": "NO SCANNER", diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json index bf45e5c53..9e4004300 100644 --- a/src/portal/src/i18n/lang/fr-fr-lang.json +++ b/src/portal/src/i18n/lang/fr-fr-lang.json @@ -804,6 +804,7 @@ "FILTER_BY_LABEL": "Filtrer les images par label", "FILTER_ARTIFACT_BY_LABEL": "Filtrer les artefact par label", "ADD_LABELS": "Ajouter des labels", + "STOP": "Stop", "RETAG": "Copier", "ACTION": "Action", "DEPLOY": "Déployer", @@ -1056,7 +1057,7 @@ "NO_SBOM": "No SBOM", "PACKAGES": "SBOM", "REPORTED_BY": "Reported by {{scanner}}", - "GENERATE": "Create SBOM", + "GENERATE": "Generate SBOM", "DOWNLOAD": "Download SBOM", "Details": "SBOM details", "STOP": "Stop SBOM", @@ -1106,12 +1107,12 @@ "PLACEHOLDER": "Filtrer les vulnérabilités", "PACKAGE": "paquet", "PACKAGES": "paquets", - "SCAN_NOW": "Analyser", + "SCAN_NOW": "Scan vulnerability", "SCAN_BY": "Scan par {{scanner}}", "REPORTED_BY": "Rapporté par {{scanner}}", "NO_SCANNER": "Aucun scanneur", "TRIGGER_STOP_SUCCESS": "Déclenchement avec succès de l'arrêt d'analyse", - "STOP_NOW": "Arrêter l'analyse" + "STOP_NOW": "Stop Scan" }, "PUSH_IMAGE": { "TITLE": "Commande de push", diff --git a/src/portal/src/i18n/lang/ko-kr-lang.json b/src/portal/src/i18n/lang/ko-kr-lang.json index 49f16e841..9ce8e1a17 100644 --- a/src/portal/src/i18n/lang/ko-kr-lang.json +++ b/src/portal/src/i18n/lang/ko-kr-lang.json @@ -802,6 +802,7 @@ "FILTER_BY_LABEL": "라벨별로 이미지 필터", "FILTER_ARTIFACT_BY_LABEL": "라벨별로 아티팩트 필터", "ADD_LABELS": "라벨 추가", + "STOP": "Stop", "RETAG": "복사", "ACTION": "동작", "DEPLOY": "배포", @@ -1055,7 +1056,7 @@ "NO_SBOM": "No SBOM", "PACKAGES": "SBOM", "REPORTED_BY": "Reported by {{scanner}}", - "GENERATE": "Create SBOM", + "GENERATE": "Generate SBOM", "DOWNLOAD": "Download SBOM", "Details": "SBOM details", "STOP": "Stop SBOM", @@ -1105,12 +1106,12 @@ "PLACEHOLDER": "취약점 필터", "PACKAGE": "패키지", "PACKAGES": "패키지들", - "SCAN_NOW": "스캔", + "SCAN_NOW": "Scan vulnerability", "SCAN_BY": "{{scanner}로 스캔", "REPORTED_BY": "{{scanner}}로 보고 됨", "NO_SCANNER": "스캐너 없음", "TRIGGER_STOP_SUCCESS": "트리거 중지 스캔이 성공적으로 수행되었습니다", - "STOP_NOW": "스캔 중지" + "STOP_NOW": "Stop Scan" }, "PUSH_IMAGE": { "TITLE": "푸시 명령어", diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json index 2ba78f122..1e4431b0f 100644 --- a/src/portal/src/i18n/lang/pt-br-lang.json +++ b/src/portal/src/i18n/lang/pt-br-lang.json @@ -803,6 +803,7 @@ "FILTER_BY_LABEL": "Filtrar imagens por marcadores", "FILTER_ARTIFACT_BY_LABEL": "Filtrar por marcador", "ADD_LABELS": "Adicionar Marcadores", + "STOP": "Stop", "RETAG": "Copiar", "ACTION": "AÇÃO", "DEPLOY": "IMPLANTAR", @@ -1054,7 +1055,7 @@ "NO_SBOM": "No SBOM", "PACKAGES": "SBOM", "REPORTED_BY": "Reported by {{scanner}}", - "GENERATE": "Create SBOM", + "GENERATE": "Generate SBOM", "DOWNLOAD": "Download SBOM", "Details": "SBOM details", "STOP": "Stop SBOM", @@ -1104,12 +1105,12 @@ "PLACEHOLDER": "Filtrar", "PACKAGE": "pacote", "PACKAGES": "pacotes", - "SCAN_NOW": "Examinar", + "SCAN_NOW": "Scan vulnerability", "SCAN_BY": "EXAMINAR COM {{scanner}}", "REPORTED_BY": "Encontrado com {{scanner}}", "NO_SCANNER": "NENHUM", "TRIGGER_STOP_SUCCESS": "Exame foi interrompido", - "STOP_NOW": "Interromper" + "STOP_NOW": "Stop Scan" }, "PUSH_IMAGE": { "TITLE": "Comando Push", diff --git a/src/portal/src/i18n/lang/tr-tr-lang.json b/src/portal/src/i18n/lang/tr-tr-lang.json index 0dddab935..3ac8e7c46 100644 --- a/src/portal/src/i18n/lang/tr-tr-lang.json +++ b/src/portal/src/i18n/lang/tr-tr-lang.json @@ -804,6 +804,7 @@ "FILTER_BY_LABEL": "İmajları etikete göre filtrele", "FILTER_ARTIFACT_BY_LABEL": "Filter actifact by label", "ADD_LABELS": "Etiketler Ekle", + "STOP": "Stop", "RETAG": "Copy", "ACTION": "AKSİYON", "DEPLOY": "YÜKLE", @@ -1057,7 +1058,7 @@ "NO_SBOM": "No SBOM", "PACKAGES": "SBOM", "REPORTED_BY": "Reported by {{scanner}}", - "GENERATE": "Create SBOM", + "GENERATE": "Generate SBOM", "DOWNLOAD": "Download SBOM", "Details": "SBOM details", "STOP": "Stop SBOM", @@ -1107,7 +1108,7 @@ "PLACEHOLDER": "Güvenlik Açıklarını Filtrele", "PACKAGE": "paket", "PACKAGES": "paketler", - "SCAN_NOW": "Tara", + "SCAN_NOW": "Scan vulnerability", "SCAN_BY": "SCAN BY {{scanner}}", "REPORTED_BY": "Reported by {{scanner}}", "NO_SCANNER": "NO SCANNER", diff --git a/src/portal/src/i18n/lang/zh-cn-lang.json b/src/portal/src/i18n/lang/zh-cn-lang.json index bb2a58578..c8b0a4258 100644 --- a/src/portal/src/i18n/lang/zh-cn-lang.json +++ b/src/portal/src/i18n/lang/zh-cn-lang.json @@ -801,6 +801,7 @@ "LABELS": "标签", "ADD_LABEL_TO_IMAGE": "添加标签到此镜像", "ADD_LABELS": "添加标签", + "STOP": "Stop", "RETAG": "拷贝", "FILTER_BY_LABEL": "过滤标签", "FILTER_ARTIFACT_BY_LABEL": "通过标签过滤Artifact", @@ -1055,7 +1056,7 @@ "NO_SBOM": "No SBOM", "PACKAGES": "SBOM", "REPORTED_BY": "Reported by {{scanner}}", - "GENERATE": "Create SBOM", + "GENERATE": "Generate SBOM", "DOWNLOAD": "Download SBOM", "Details": "SBOM details", "STOP": "Stop SBOM", @@ -1105,7 +1106,7 @@ "PLACEHOLDER": "过滤漏洞", "PACKAGE": "组件", "PACKAGES": "组件", - "SCAN_NOW": "扫描", + "SCAN_NOW": "Scan vulnerability", "SCAN_BY": "使用 {{scanner}} 进行扫描", "REPORTED_BY": "结果由 {{scanner}} 提供", "NO_SCANNER": "无扫描器", diff --git a/src/portal/src/i18n/lang/zh-tw-lang.json b/src/portal/src/i18n/lang/zh-tw-lang.json index bc1732775..ca8c90c44 100644 --- a/src/portal/src/i18n/lang/zh-tw-lang.json +++ b/src/portal/src/i18n/lang/zh-tw-lang.json @@ -801,6 +801,7 @@ "LABELS": "標籤", "ADD_LABEL_TO_IMAGE": "新增標籤到此映像檔", "ADD_LABELS": "新增標籤", + "STOP": "Stop", "RETAG": "複製", "FILTER_BY_LABEL": "篩選標籤", "FILTER_ARTIFACT_BY_LABEL": "透過標籤篩選 Artifact", @@ -1054,7 +1055,7 @@ "NO_SBOM": "No SBOM", "PACKAGES": "SBOM", "REPORTED_BY": "Reported by {{scanner}}", - "GENERATE": "Create SBOM", + "GENERATE": "Generate SBOM", "DOWNLOAD": "Download SBOM", "Details": "SBOM details", "STOP": "Stop SBOM", From 9c3fc28250b9d7d3956b7395367016ab9c6a79e3 Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Fri, 19 Apr 2024 10:14:28 +0800 Subject: [PATCH 099/145] Allow generate sbom in proxy cache project (#20298) Signed-off-by: stonezdj --- src/server/middleware/repoproxy/proxy.go | 14 +++++++--- src/server/middleware/repoproxy/proxy_test.go | 28 +++++++++++++++++-- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/server/middleware/repoproxy/proxy.go b/src/server/middleware/repoproxy/proxy.go index 5fb44a681..ddb201784 100644 --- a/src/server/middleware/repoproxy/proxy.go +++ b/src/server/middleware/repoproxy/proxy.go @@ -28,6 +28,7 @@ import ( "github.com/goharbor/harbor/src/controller/proxy" "github.com/goharbor/harbor/src/controller/registry" "github.com/goharbor/harbor/src/lib" + "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/errors" httpLib "github.com/goharbor/harbor/src/lib/http" "github.com/goharbor/harbor/src/lib/log" @@ -259,16 +260,21 @@ func setHeaders(w http.ResponseWriter, size int64, mediaType string, dig string) } // isProxySession check if current security context is proxy session -func isProxySession(ctx context.Context) bool { +func isProxySession(ctx context.Context, projectName string) bool { sc, ok := security.FromContext(ctx) if !ok { log.Error("Failed to get security context") return false } - if sc.GetUsername() == proxycachesecret.ProxyCacheService { + username := sc.GetUsername() + if username == proxycachesecret.ProxyCacheService { return true } - return false + // it should include the auto generate SBOM session, so that it could generate SBOM accessory in proxy cache project + robotPrefix := config.RobotPrefix(ctx) + scannerPrefix := config.ScannerRobotPrefix(ctx) + prefix := fmt.Sprintf("%s%s+%s", robotPrefix, projectName, scannerPrefix) + return strings.HasPrefix(username, prefix) } // DisableBlobAndManifestUploadMiddleware disable push artifact to a proxy project with a non-proxy session @@ -281,7 +287,7 @@ func DisableBlobAndManifestUploadMiddleware() func(http.Handler) http.Handler { httpLib.SendError(w, err) return } - if p.IsProxy() && !isProxySession(ctx) { + if p.IsProxy() && !isProxySession(ctx, art.ProjectName) { httpLib.SendError(w, errors.DeniedError( errors.Errorf("can not push artifact to a proxy project: %v", p.Name))) diff --git a/src/server/middleware/repoproxy/proxy_test.go b/src/server/middleware/repoproxy/proxy_test.go index 4b7ee3d75..c47f20db0 100644 --- a/src/server/middleware/repoproxy/proxy_test.go +++ b/src/server/middleware/repoproxy/proxy_test.go @@ -18,7 +18,9 @@ import ( "context" "testing" + "github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/security" + "github.com/goharbor/harbor/src/common/security/local" "github.com/goharbor/harbor/src/common/security/proxycachesecret" securitySecret "github.com/goharbor/harbor/src/common/security/secret" ) @@ -29,6 +31,19 @@ func TestIsProxySession(t *testing.T) { sc2 := proxycachesecret.NewSecurityContext("library/hello-world") proxyCtx := security.NewContext(context.Background(), sc2) + + user := &models.User{ + Username: "robot$library+scanner-8ec3b47a-fd29-11ee-9681-0242c0a87009", + } + userSc := local.NewSecurityContext(user) + scannerCtx := security.NewContext(context.Background(), userSc) + + otherRobot := &models.User{ + Username: "robot$library+test-8ec3b47a-fd29-11ee-9681-0242c0a87009", + } + userSc2 := local.NewSecurityContext(otherRobot) + nonScannerCtx := security.NewContext(context.Background(), userSc2) + cases := []struct { name string in context.Context @@ -44,15 +59,24 @@ func TestIsProxySession(t *testing.T) { in: proxyCtx, want: true, }, + { + name: `robot account`, + in: scannerCtx, + want: true, + }, + { + name: `non scanner robot`, + in: nonScannerCtx, + want: false, + }, } for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { - got := isProxySession(tt.in) + got := isProxySession(tt.in, "library") if got != tt.want { t.Errorf(`(%v) = %v; want "%v"`, tt.in, got, tt.want) } - }) } } From b3dc183f476b20c5e51e49103230ef5151aa477b Mon Sep 17 00:00:00 2001 From: Lichao Xue <68891670+xuelichao@users.noreply.github.com> Date: Fri, 19 Apr 2024 13:01:54 +0800 Subject: [PATCH 100/145] Fixed an issue where the scan stop button can only be clicked once (#20302) Signed-off-by: xuelichao --- .../artifact-list-tab/artifact-list-tab.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.ts b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.ts index f73b448bb..d880f8856 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.ts @@ -907,7 +907,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { this.scanStoppedArtifactLength += 1; // all selected scan action has stopped if (this.scanStoppedArtifactLength === this.onStopScanArtifactsLength) { - this.onSendingScanCommand = e; + this.onSendingStopScanCommand = e; } } @@ -923,7 +923,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { this.sbomStoppedArtifactLength += 1; // all selected scan action has stopped if (this.sbomStoppedArtifactLength === this.onStopSbomArtifactsLength) { - this.onSendingSbomCommand = e; + this.onSendingStopSbomCommand = e; } } From 0d9dc4b4a473e682d1d9de00394865659df7aca8 Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Fri, 19 Apr 2024 15:36:56 +0800 Subject: [PATCH 101/145] Add enableCapabilities to extraAttrs for stop (#20299) Signed-off-by: stonezdj --- src/controller/scan/base_controller.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/controller/scan/base_controller.go b/src/controller/scan/base_controller.go index 52eb4eefe..c1b68947c 100644 --- a/src/controller/scan/base_controller.go +++ b/src/controller/scan/base_controller.go @@ -70,10 +70,11 @@ const ( artfiactKey = "artifact" registrationKey = "registration" - artifactIDKey = "artifact_id" - artifactTagKey = "artifact_tag" - reportUUIDsKey = "report_uuids" - robotIDKey = "robot_id" + artifactIDKey = "artifact_id" + artifactTagKey = "artifact_tag" + reportUUIDsKey = "report_uuids" + robotIDKey = "robot_id" + enabledCapabilities = "enabled_capabilities" ) // uuidGenerator is a func template which is for generating UUID. @@ -317,6 +318,9 @@ func (bc *basicController) Scan(ctx context.Context, artifact *ar.Artifact, opti "id": r.ID, "name": r.Name, }, + enabledCapabilities: map[string]interface{}{ + "type": opts.GetScanType(), + }, } if op := operator.FromContext(ctx); op != "" { extraAttrs["operator"] = op From d7594298310f81053d1d63d42f47d0ffc4053bbb Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Sat, 20 Apr 2024 10:37:30 +0800 Subject: [PATCH 102/145] Set default capability for old scanners (#20306) Signed-off-by: stonezdj Co-authored-by: Wang Yan --- src/pkg/scan/rest/v1/models.go | 12 ++++++++++-- src/pkg/scan/rest/v1/models_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/pkg/scan/rest/v1/models.go b/src/pkg/scan/rest/v1/models.go index 21352c749..9c25c16ea 100644 --- a/src/pkg/scan/rest/v1/models.go +++ b/src/pkg/scan/rest/v1/models.go @@ -161,14 +161,22 @@ func (md *ScannerAdapterMetadata) GetCapability(mimeType string) *ScannerCapabil // ConvertCapability converts the capability to map, used in get scanner API func (md *ScannerAdapterMetadata) ConvertCapability() map[string]interface{} { capabilities := make(map[string]interface{}) + oldScanner := true for _, c := range md.Capabilities { + if len(c.Type) > 0 { + oldScanner = false + } if c.Type == ScanTypeVulnerability { capabilities[supportVulnerability] = true - } - if c.Type == ScanTypeSbom { + } else if c.Type == ScanTypeSbom { capabilities[supportSBOM] = true } } + if oldScanner && len(capabilities) == 0 { + // to compatible with old version scanner, suppose they should always support scan vulnerability when capability is empty + capabilities[supportVulnerability] = true + capabilities[supportSBOM] = false + } return capabilities } diff --git a/src/pkg/scan/rest/v1/models_test.go b/src/pkg/scan/rest/v1/models_test.go index e96aa0178..96590bc4c 100644 --- a/src/pkg/scan/rest/v1/models_test.go +++ b/src/pkg/scan/rest/v1/models_test.go @@ -13,3 +13,29 @@ func TestIsSupportedMimeType(t *testing.T) { // Test with an unsupported mime type assert.False(t, isSupportedMimeType("unsupported/mime-type"), "isSupportedMimeType should return false for unsupported mime types") } + +func TestConvertCapability(t *testing.T) { + md := &ScannerAdapterMetadata{ + Capabilities: []*ScannerCapability{ + {Type: ScanTypeSbom}, + {Type: ScanTypeVulnerability}, + }, + } + result := md.ConvertCapability() + assert.Equal(t, result[supportSBOM], true) + assert.Equal(t, result[supportVulnerability], true) +} + +func TestConvertCapabilityOldScaner(t *testing.T) { + md := &ScannerAdapterMetadata{ + Capabilities: []*ScannerCapability{ + { + ConsumesMimeTypes: []string{"application/vnd.oci.image.manifest.v1+json", "application/vnd.docker.distribution.manifest.v2+json"}, + ProducesMimeTypes: []string{MimeTypeNativeReport}, + }, + }, + } + result := md.ConvertCapability() + assert.Equal(t, result[supportSBOM], false) + assert.Equal(t, result[supportVulnerability], true) +} From e7fce627238addf5e7efae95655cc47571b147d6 Mon Sep 17 00:00:00 2001 From: Lichao Xue <68891670+xuelichao@users.noreply.github.com> Date: Mon, 22 Apr 2024 13:29:48 +0800 Subject: [PATCH 103/145] Wrong values shown for the columns of support_sbom and support_vulnerability in scanner list (#20308) Fix wrong value shown for the columns of support_sbom and support_vulnerability in scanner list Signed-off-by: xuelichao --- .../scanner/config-scanner.component.ts | 8 +-- .../scanner-metadata/scanner-metadata.html | 53 +++++++++++++------ src/portal/src/i18n/lang/de-de-lang.json | 3 +- src/portal/src/i18n/lang/en-us-lang.json | 3 +- src/portal/src/i18n/lang/es-es-lang.json | 3 +- src/portal/src/i18n/lang/fr-fr-lang.json | 3 +- src/portal/src/i18n/lang/ko-kr-lang.json | 3 +- src/portal/src/i18n/lang/pt-br-lang.json | 3 +- src/portal/src/i18n/lang/tr-tr-lang.json | 3 +- src/portal/src/i18n/lang/zh-cn-lang.json | 3 +- src/portal/src/i18n/lang/zh-tw-lang.json | 3 +- 11 files changed, 58 insertions(+), 30 deletions(-) diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.component.ts b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.component.ts index 550aff8bc..b05ce3657 100644 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.component.ts +++ b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.component.ts @@ -180,12 +180,8 @@ export class ConfigurationScannerComponent implements OnInit, OnDestroy { } supportCapability(scanner: Scanner, capabilityType: string): boolean { - return scanner && scanner.metadata && capabilityType - ? ( - scanner?.metadata?.capabilities?.filter( - ({ type }) => type === capabilityType - ) ?? [] - ).length >= 1 + return scanner && scanner.capabilities && capabilityType + ? scanner?.capabilities?.[`support_${capabilityType}`] ?? false : false; } diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/scanner-metadata/scanner-metadata.html b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/scanner-metadata/scanner-metadata.html index eeefb4aca..4da2dd94b 100644 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/scanner-metadata/scanner-metadata.html +++ b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/scanner-metadata/scanner-metadata.html @@ -19,21 +19,44 @@
{{ 'SCANNER.CAPABILITIES' | translate }}
-
- {{ 'SCANNER.CONSUMES_MIME_TYPES_COLON' | translate }} - -
-
- {{ 'SCANNER.PRODUCTS_MIME_TYPES_COLON' | translate }} - +
+ {{ i }}: +
+ {{ 'SCANNER.CONSUMES_MIME_TYPES_COLON' | translate }} + +
+
+ {{ 'SCANNER.PRODUCTS_MIME_TYPES_COLON' | translate }} + +
+
+ {{ 'SCANNER.CAPABILITIES_TYPE' | translate }} + + {{ + (scannerMetadata?.capabilities[i]?.type === 'sbom' + ? 'SCANNER.SBOM' + : scannerMetadata?.capabilities[i]?.type === + 'vulnerability' + ? 'SCANNER.VULNERABILITY' + : scannerMetadata?.capabilities[i]?.type + ) | translate + }} + +
{{ 'SCANNER.PROPERTIES' | translate }}
Date: Mon, 22 Apr 2024 14:36:35 +0800 Subject: [PATCH 104/145] feat: add tc for limited guest of a project to get repository (#20311) Signed-off-by: Shengwen Yu --- .gitignore | 2 + .../test_user_limited_guest_get_repository.py | 62 +++++++++++++++++++ tests/robot-cases/Group0-BAT/API_DB.robot | 4 ++ 3 files changed, 68 insertions(+) create mode 100644 tests/apitests/python/test_user_limited_guest_get_repository.py diff --git a/.gitignore b/.gitignore index ef34ce1bf..f2b08ad4a 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,5 @@ src/server/v2.0/models/ src/server/v2.0/restapi/ .editorconfig +harborclient/ +openapi-generator-cli.jar diff --git a/tests/apitests/python/test_user_limited_guest_get_repository.py b/tests/apitests/python/test_user_limited_guest_get_repository.py new file mode 100644 index 000000000..d29b936fa --- /dev/null +++ b/tests/apitests/python/test_user_limited_guest_get_repository.py @@ -0,0 +1,62 @@ +from __future__ import absolute_import +import unittest + + +from testutils import ADMIN_CLIENT, suppress_urllib3_warning +from testutils import harbor_server +from testutils import admin_user +from testutils import admin_pwd +from testutils import created_project +from testutils import created_user +from testutils import TEARDOWN +from library.repository import push_self_build_image_to_project +from library.repository import Repository + + +class TestLimitedGuestGetRepository(unittest.TestCase): + + + @suppress_urllib3_warning + def setUp(self): + self.repository = Repository() + + @unittest.skipIf(TEARDOWN == False, "Test data won't be erased.") + def tearDown(self): + print("Case completed") + + def testLimitedGuestGetRepository(self): + """ + Test case: + Limited Guest GetRepository + Test step and expected result: + 1. Create a new user(UA) + 2. Create a private project(PA) + 3. Add (UA) as "Limited Guest" to this (PA) + 4. Push an image to project(PA) + 5. Call the "GetRepository" API, it should return 200 status code and project_id should be as expected, and the name should be "ProjectName/ImageName" + 6. Delete repository(RA) + """ + url = ADMIN_CLIENT["endpoint"] + user_001_password = "Aa123456" + # 1. Create a new user(UA) + with created_user(user_001_password) as (user_id, user_name): + #2. Create a new private project(PA) by user(UA); + #3. Add user(UA) as a member of project(PA) with "Limited Guest" role; + with created_project(metadata={"public": "false"}, user_id=user_id, member_role_id=5) as (project_id, project_name): + #4. Push an image to project(PA) by user(UA), then check the project quota usage; + image, tag = "goharbor/alpine", "3.10" + push_self_build_image_to_project(project_name, harbor_server, admin_user, admin_pwd, image, tag) + + #5. Call the "GetRepository" API, it should return 200 status code and the "name" attribute is "ProjectName/ImageName" + USER_CLIENT=dict(endpoint=url, username=user_name, password=user_001_password) + repository_data = self.repository.get_repository(project_name, "goharbor%2Falpine", **USER_CLIENT) + self.assertEqual(repository_data.project_id, project_id) + self.assertEqual(repository_data.name, project_name + "/" + image) + + #6. Delete repository(RA) + self.repository.delete_repository(project_name, "goharbor%2Falpine", **ADMIN_CLIENT) + + +if __name__ == '__main__': + unittest.main() + diff --git a/tests/robot-cases/Group0-BAT/API_DB.robot b/tests/robot-cases/Group0-BAT/API_DB.robot index 179d85ebb..f1a92b720 100644 --- a/tests/robot-cases/Group0-BAT/API_DB.robot +++ b/tests/robot-cases/Group0-BAT/API_DB.robot @@ -210,3 +210,7 @@ Test Case - Banner Message Test Case - User CRUD [Tags] user_crud Harbor API Test ./tests/apitests/python/test_user_crud.py + +Test Case - Limited Guest GetRepository + [Tags] limited_guest_getrepository + Harbor API Test ./tests/apitests/python/test_user_limited_guest_get_repository.py From ea3cd06171c10f20d5b942fd7de40107c26843c2 Mon Sep 17 00:00:00 2001 From: MinerYang Date: Mon, 22 Apr 2024 16:34:08 +0800 Subject: [PATCH 105/145] add prepare migration script for 2.11.0 (#20315) Signed-off-by: yminer correct jaeger agent_host update ip_family part --- make/harbor.yml.tmpl | 2 +- make/photon/prepare/commands/migrate.py | 2 +- make/photon/prepare/migrations/__init__.py | 2 +- .../migrations/version_2_11_0/__init__.py | 21 + .../version_2_11_0/harbor.yml.jinja | 737 ++++++++++++++++++ 5 files changed, 761 insertions(+), 3 deletions(-) create mode 100644 make/photon/prepare/migrations/version_2_11_0/__init__.py create mode 100644 make/photon/prepare/migrations/version_2_11_0/harbor.yml.jinja diff --git a/make/harbor.yml.tmpl b/make/harbor.yml.tmpl index 22e421691..e81abfc43 100644 --- a/make/harbor.yml.tmpl +++ b/make/harbor.yml.tmpl @@ -174,7 +174,7 @@ log: # port: 5140 #This attribute is for migrator to detect the version of the .cfg file, DO NOT MODIFY! -_version: 2.10.0 +_version: 2.11.0 # Uncomment external_database if using external database. # external_database: diff --git a/make/photon/prepare/commands/migrate.py b/make/photon/prepare/commands/migrate.py index 6808aa18f..6ec8d7a28 100644 --- a/make/photon/prepare/commands/migrate.py +++ b/make/photon/prepare/commands/migrate.py @@ -10,7 +10,7 @@ from migrations import accept_versions @click.command() @click.option('-i', '--input', 'input_', required=True, help="The path of original config file") @click.option('-o', '--output', default='', help="the path of output config file") -@click.option('-t', '--target', default='2.10.0', help="target version of input path") +@click.option('-t', '--target', default='2.11.0', help="target version of input path") def migrate(input_, output, target): """ migrate command will migrate config file style to specific version diff --git a/make/photon/prepare/migrations/__init__.py b/make/photon/prepare/migrations/__init__.py index 4ecb468a3..14b50018a 100644 --- a/make/photon/prepare/migrations/__init__.py +++ b/make/photon/prepare/migrations/__init__.py @@ -2,4 +2,4 @@ import os MIGRATION_BASE_DIR = os.path.dirname(__file__) -accept_versions = {'1.9.0', '1.10.0', '2.0.0', '2.1.0', '2.2.0', '2.3.0', '2.4.0', '2.5.0', '2.6.0', '2.7.0', '2.8.0', '2.9.0','2.10.0'} \ No newline at end of file +accept_versions = {'1.9.0', '1.10.0', '2.0.0', '2.1.0', '2.2.0', '2.3.0', '2.4.0', '2.5.0', '2.6.0', '2.7.0', '2.8.0', '2.9.0','2.10.0', '2.11.0'} \ No newline at end of file diff --git a/make/photon/prepare/migrations/version_2_11_0/__init__.py b/make/photon/prepare/migrations/version_2_11_0/__init__.py new file mode 100644 index 000000000..8f2f64cfa --- /dev/null +++ b/make/photon/prepare/migrations/version_2_11_0/__init__.py @@ -0,0 +1,21 @@ +import os +from jinja2 import Environment, FileSystemLoader, StrictUndefined, select_autoescape +from utils.migration import read_conf + +revision = '2.11.0' +down_revisions = ['2.10.0'] + +def migrate(input_cfg, output_cfg): + current_dir = os.path.dirname(__file__) + tpl = Environment( + loader=FileSystemLoader(current_dir), + undefined=StrictUndefined, + trim_blocks=True, + lstrip_blocks=True, + autoescape = select_autoescape() + ).get_template('harbor.yml.jinja') + + config_dict = read_conf(input_cfg) + + with open(output_cfg, 'w') as f: + f.write(tpl.render(**config_dict)) diff --git a/make/photon/prepare/migrations/version_2_11_0/harbor.yml.jinja b/make/photon/prepare/migrations/version_2_11_0/harbor.yml.jinja new file mode 100644 index 000000000..ef0be73db --- /dev/null +++ b/make/photon/prepare/migrations/version_2_11_0/harbor.yml.jinja @@ -0,0 +1,737 @@ +# Configuration file of Harbor + +# The IP address or hostname to access admin UI and registry service. +# DO NOT use localhost or 127.0.0.1, because Harbor needs to be accessed by external clients. +hostname: {{ hostname }} + +# http related config +{% if http is defined %} +http: + # port for http, default is 80. If https enabled, this port will redirect to https port + port: {{ http.port }} +{% else %} +# http: +# # port for http, default is 80. If https enabled, this port will redirect to https port +# port: 80 +{% endif %} + +{% if https is defined %} +# https related config +https: + # https port for harbor, default is 443 + port: {{ https.port }} + # The path of cert and key files for nginx + certificate: {{ https.certificate }} + private_key: {{ https.private_key }} + # enable strong ssl ciphers (default: false) + {% if strong_ssl_ciphers is defined %} + strong_ssl_ciphers: {{ strong_ssl_ciphers | lower }} + {% else %} + strong_ssl_ciphers: false + {% endif %} +{% else %} +# https related config +# https: +# # https port for harbor, default is 443 +# port: 443 +# # The path of cert and key files for nginx +# certificate: /your/certificate/path +# private_key: /your/private/key/path +# enable strong ssl ciphers (default: false) +# strong_ssl_ciphers: false +{% endif %} + +# # Harbor will set ipv4 enabled only by default if this block is not configured +# # Otherwise, please uncomment this block to configure your own ip_family stacks +{% if ip_family is defined %} +ip_family: + # ipv6Enabled set to true if ipv6 is enabled in docker network, currently it affected the nginx related component + {% if ip_family.ipv6 is defined %} + ipv6: + enabled: {{ ip_family.ipv6.enabled | lower }} + {% else %} + ipv6: + enabled: false + {% endif %} + # ipv4Enabled set to true by default, currently it affected the nginx related component + {% if ip_family.ipv4 is defined %} + ipv4: + enabled: {{ ip_family.ipv4.enabled | lower }} + {% else %} + ipv4: + enabled: true + {% endif %} +{% else %} +# ip_family: +# # ipv6Enabled set to true if ipv6 is enabled in docker network, currently it affected the nginx related component +# ipv6: +# enabled: false +# # ipv4Enabled set to true by default, currently it affected the nginx related component +# ipv4: +# enabled: true +{% endif %} + +{% if internal_tls is defined %} +# Uncomment following will enable tls communication between all harbor components +internal_tls: + # set enabled to true means internal tls is enabled + enabled: {{ internal_tls.enabled | lower }} + {% if internal_tls.dir is defined %} + # put your cert and key files on dir + dir: {{ internal_tls.dir }} + {% endif %} +{% else %} +# internal_tls: +# # set enabled to true means internal tls is enabled +# enabled: true +# # put your cert and key files on dir +# dir: /etc/harbor/tls/internal +{% endif %} + +# Uncomment external_url if you want to enable external proxy +# And when it enabled the hostname will no longer used +{% if external_url is defined %} +external_url: {{ external_url }} +{% else %} +# external_url: https://reg.mydomain.com:8433 +{% endif %} + +# The initial password of Harbor admin +# It only works in first time to install harbor +# Remember Change the admin password from UI after launching Harbor. +{% if harbor_admin_password is defined %} +harbor_admin_password: {{ harbor_admin_password }} +{% else %} +harbor_admin_password: Harbor12345 +{% endif %} + +# Harbor DB configuration +database: +{% if database is defined %} + # The password for the root user of Harbor DB. Change this before any production use. + password: {{ database.password}} + # The maximum number of connections in the idle connection pool. If it <=0, no idle connections are retained. + max_idle_conns: {{ database.max_idle_conns }} + # The maximum number of open connections to the database. If it <= 0, then there is no limit on the number of open connections. + # Note: the default number of connections is 1024 for postgres of harbor. + max_open_conns: {{ database.max_open_conns }} + # The maximum amount of time a connection may be reused. Expired connections may be closed lazily before reuse. If it <= 0, connections are not closed due to a connection's age. + # The value is a duration string. A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". + {% if database.conn_max_lifetime is defined %} + conn_max_lifetime: {{ database.conn_max_lifetime }} + {% else %} + conn_max_lifetime: 5m + {% endif %} + # The maximum amount of time a connection may be idle. Expired connections may be closed lazily before reuse. If it <= 0, connections are not closed due to a connection's idle time. + # The value is a duration string. A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". + {% if database.conn_max_idle_time is defined %} + conn_max_idle_time: {{ database.conn_max_idle_time }} + {% else %} + conn_max_idle_time: 0 + {% endif %} +{% else %} + # The password for the root user 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 + # The maximum number of open connections to the database. If it <= 0, then there is no limit on the number of open connections. + # Note: the default number of connections is 1024 for postgres of harbor. + max_open_conns: 900 + # The maximum amount of time a connection may be reused. Expired connections may be closed lazily before reuse. If it <= 0, connections are not closed due to a connection's age. + # The value is a duration string. A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". + conn_max_lifetime: 5m + # The maximum amount of time a connection may be idle. Expired connections may be closed lazily before reuse. If it <= 0, connections are not closed due to a connection's idle time. + # The value is a duration string. A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". + conn_max_idle_time: 0 +{% endif %} + +{% if data_volume is defined %} +# The default data volume +data_volume: {{ data_volume }} +{% else %} +# The default data volume +data_volume: /data +{% endif %} + +# Harbor Storage settings by default is using /data dir on local filesystem +# Uncomment storage_service setting If you want to using external storage +{% if storage_service is defined %} +storage_service: + {% for key, value in storage_service.items() %} + {% if key == 'ca_bundle' %} +# # ca_bundle is the path to the custom root ca certificate, which will be injected into the truststore +# # of registry's and chart repository's containers. This is usually needed when the user hosts a internal storage with self signed certificate. + ca_bundle: {{ value if value is not none else '' }} + {% elif key == 'redirect' %} +# # set disable to true when you want to disable registry redirect + redirect: + {% if storage_service.redirect.disabled is defined %} + disable: {{ storage_service.redirect.disabled | lower}} + {% else %} + disable: {{ storage_service.redirect.disable | lower}} + {% endif %} + {% else %} +# # storage backend, default is filesystem, options include filesystem, azure, gcs, s3, swift and oss +# # for more info about this configuration please refer https://distribution.github.io/distribution/about/configuration/ +# # and https://distribution.github.io/distribution/storage-drivers/ + {{ key }}: + {% for k, v in value.items() %} + {{ k }}: {{ v if v is not none else '' }} + {% endfor %} + {% endif %} + {% endfor %} +{% else %} +# storage_service: +# # ca_bundle is the path to the custom root ca certificate, which will be injected into the truststore +# # of registry's and chart repository's containers. This is usually needed when the user hosts a internal storage with self signed certificate. +# ca_bundle: + +# # storage backend, default is filesystem, options include filesystem, azure, gcs, s3, swift and oss +# # for more info about this configuration please refer https://distribution.github.io/distribution/about/configuration/ +# # and https://distribution.github.io/distribution/storage-drivers/ +# filesystem: +# maxthreads: 100 +# # set disable to true when you want to disable registry redirect +# redirect: +# disable: false +{% endif %} + +# Trivy configuration +# +# Trivy DB contains vulnerability information from NVD, Red Hat, and many other upstream vulnerability databases. +# It is downloaded by Trivy from the GitHub release page https://github.com/aquasecurity/trivy-db/releases and cached +# in the local file system. In addition, the database contains the update timestamp so Trivy can detect whether it +# should download a newer version from the Internet or use the cached one. Currently, the database is updated every +# 12 hours and published as a new release to GitHub. +{% if trivy is defined %} +trivy: + # ignoreUnfixed The flag to display only fixed vulnerabilities + {% if trivy.ignore_unfixed is defined %} + ignore_unfixed: {{ trivy.ignore_unfixed | lower }} + {% else %} + ignore_unfixed: false + {% endif %} + # skipUpdate The flag to enable or disable Trivy DB downloads from GitHub + # + # You might want to enable this flag in test or CI/CD environments to avoid GitHub rate limiting issues. + # If the flag is enabled you have to download the `trivy-offline.tar.gz` archive manually, extract `trivy.db` and + # `metadata.json` files and mount them in the `/home/scanner/.cache/trivy/db` path. + {% if trivy.skip_update is defined %} + skip_update: {{ trivy.skip_update | lower }} + {% else %} + skip_update: false + {% endif %} + {% if trivy.skip_java_db_update is defined %} + # skipJavaDBUpdate If the flag is enabled you have to manually download the `trivy-java.db` file and mount it in the + # `/home/scanner/.cache/trivy/java-db/trivy-java.db` path + skip_java_db_update: {{ trivy.skip_java_db_update | lower }} + {% else %} + skip_java_db_update: false + {% endif %} + # + {% if trivy.offline_scan is defined %} + offline_scan: {{ trivy.offline_scan | lower }} + {% else %} + offline_scan: false + {% endif %} + # + # Comma-separated list of what security issues to detect. Possible values are `vuln`, `config` and `secret`. Defaults to `vuln`. + {% if trivy.security_check is defined %} + security_check: {{ trivy.security_check }} + {% else %} + security_check: vuln + {% endif %} + # + # insecure The flag to skip verifying registry certificate + {% if trivy.insecure is defined %} + insecure: {{ trivy.insecure | lower }} + {% else %} + insecure: false + {% endif %} + # + {% if trivy.timeout is defined %} + # timeout The duration to wait for scan completion. + # There is upper bound of 30 minutes defined in scan job. So if this `timeout` is larger than 30m0s, it will also timeout at 30m0s. + timeout: {{ trivy.timeout}} + {% else %} + timeout: 5m0s + {% endif %} + # + # github_token The GitHub access token to download Trivy DB + # + # Anonymous downloads from GitHub are subject to the limit of 60 requests per hour. Normally such rate limit is enough + # for production operations. If, for any reason, it's not enough, you could increase the rate limit to 5000 + # requests per hour by specifying the GitHub access token. For more details on GitHub rate limiting please consult + # https://developer.github.com/v3/#rate-limiting + # + # You can create a GitHub token by following the instructions in + # https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line + # + {% if trivy.github_token is defined %} + github_token: {{ trivy.github_token }} + {% else %} + # github_token: xxx + {% endif %} +{% else %} +# trivy: +# # ignoreUnfixed The flag to display only fixed vulnerabilities +# ignore_unfixed: false +# # skipUpdate The flag to enable or disable Trivy DB downloads from GitHub +# # +# # You might want to enable this flag in test or CI/CD environments to avoid GitHub rate limiting issues. +# # If the flag is enabled you have to download the `trivy-offline.tar.gz` archive manually, extract `trivy.db` and +# # `metadata.json` files and mount them in the `/home/scanner/.cache/trivy/db` path. +# skip_update: false +# # +# # skipJavaDBUpdate If the flag is enabled you have to manually download the `trivy-java.db` file and mount it in the +# # `/home/scanner/.cache/trivy/java-db/trivy-java.db` path +# skip_java_db_update: false +# # +# #The offline_scan option prevents Trivy from sending API requests to identify dependencies. +# # Scanning JAR files and pom.xml may require Internet access for better detection, but this option tries to avoid it. +# # For example, the offline mode will not try to resolve transitive dependencies in pom.xml when the dependency doesn't +# # exist in the local repositories. It means a number of detected vulnerabilities might be fewer in offline mode. +# # It would work if all the dependencies are in local. +# # This option doesn’t affect DB download. You need to specify "skip-update" as well as "offline-scan" in an air-gapped environment. +# offline_scan: false +# # +# # insecure The flag to skip verifying registry certificate +# insecure: false +# # github_token The GitHub access token to download Trivy DB +# # +# # Anonymous downloads from GitHub are subject to the limit of 60 requests per hour. Normally such rate limit is enough +# # for production operations. If, for any reason, it's not enough, you could increase the rate limit to 5000 +# # requests per hour by specifying the GitHub access token. For more details on GitHub rate limiting please consult +# # https://developer.github.com/v3/#rate-limiting +# # +# # timeout The duration to wait for scan completion. +# # There is upper bound of 30 minutes defined in scan job. So if this `timeout` is larger than 30m0s, it will also timeout at 30m0s. +# timeout: 5m0s +# # +# # You can create a GitHub token by following the instructions in +# # https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line +# # +# # github_token: xxx +{% endif %} + +jobservice: + # Maximum number of job workers in job service +{% if jobservice is defined %} + max_job_workers: {{ jobservice.max_job_workers }} + # The jobLoggers backend name, only support "STD_OUTPUT", "FILE" and/or "DB" + {% if jobservice.job_loggers is defined %} + job_loggers: + {% for job_logger in jobservice.job_loggers %} + - {{job_logger}} + {% endfor %} + {% else %} + job_loggers: + - STD_OUTPUT + - FILE + # - DB + {% endif %} + # The jobLogger sweeper duration (ignored if `jobLogger` is `stdout`) + {% if jobservice.logger_sweeper_duration is defined %} + logger_sweeper_duration: {{ jobservice.logger_sweeper_duration }} + {% else %} + logger_sweeper_duration: 1 + {% endif %} +{% else %} + max_job_workers: 10 + # The jobLoggers backend name, only support "STD_OUTPUT", "FILE" and/or "DB" + job_loggers: + - STD_OUTPUT + - FILE + # - DB + # The jobLogger sweeper duration (ignored if `jobLogger` is `stdout`) + logger_sweeper_duration: 1 +{% endif %} + +notification: + # Maximum retry count for webhook job +{% if notification is defined %} + webhook_job_max_retry: {{ notification.webhook_job_max_retry}} + # HTTP client timeout for webhook job + {% if notification.webhook_job_http_client_timeout is defined %} + webhook_job_http_client_timeout: {{ notification.webhook_job_http_client_timeout }} + {% else %} + webhook_job_http_client_timeout: 3 #seconds + {% endif %} +{% else %} + webhook_job_max_retry: 3 + # HTTP client timeout for webhook job + webhook_job_http_client_timeout: 3 #seconds +{% endif %} + +# Log configurations +log: + # options are debug, info, warning, error, fatal +{% if log is defined %} + level: {{ log.level }} + # configs for logs in local storage + local: + # Log files are rotated log_rotate_count times before being removed. If count is 0, old versions are removed rather than rotated. + rotate_count: {{ log.local.rotate_count }} + # Log files are rotated only if they grow bigger than log_rotate_size bytes. If size is followed by k, the size is assumed to be in kilobytes. + # If the M is used, the size is in megabytes, and if G is used, the size is in gigabytes. So size 100, size 100k, size 100M and size 100G + # are all valid. + rotate_size: {{ log.local.rotate_size }} + # The directory on your host that store log + location: {{ log.local.location }} + {% if log.external_endpoint is defined %} + external_endpoint: + # protocol used to transmit log to external endpoint, options is tcp or udp + protocol: {{ log.external_endpoint.protocol }} + # The host of external endpoint + host: {{ log.external_endpoint.host }} + # Port of external endpoint + port: {{ log.external_endpoint.port }} + {% else %} + # Uncomment following lines to enable external syslog endpoint. + # external_endpoint: + # # protocol used to transmit log to external endpoint, options is tcp or udp + # protocol: tcp + # # The host of external endpoint + # host: localhost + # # Port of external endpoint + # port: 5140 + {% endif %} +{% else %} + level: info + # configs for logs in local storage + local: + # Log files are rotated log_rotate_count times before being removed. If count is 0, old versions are removed rather than rotated. + rotate_count: 50 + # Log files are rotated only if they grow bigger than log_rotate_size bytes. If size is followed by k, the size is assumed to be in kilobytes. + # If the M is used, the size is in megabytes, and if G is used, the size is in gigabytes. So size 100, size 100k, size 100M and size 100G + # are all valid. + rotate_size: 200M + # The directory on your host that store log + location: /var/log/harbor + + # Uncomment following lines to enable external syslog endpoint. + # external_endpoint: + # # protocol used to transmit log to external endpoint, options is tcp or udp + # protocol: tcp + # # The host of external endpoint + # host: localhost + # # Port of external endpoint + # port: 5140 +{% endif %} + + +#This attribute is for migrator to detect the version of the .cfg file, DO NOT MODIFY! +_version: 2.11.0 +{% if external_database is defined %} +# Uncomment external_database if using external database. +external_database: + harbor: + host: {{ external_database.harbor.host }} + port: {{ external_database.harbor.port }} + db_name: {{ external_database.harbor.db_name }} + username: {{ external_database.harbor.username }} + password: {{ external_database.harbor.password }} + ssl_mode: {{ external_database.harbor.ssl_mode }} + max_idle_conns: {{ external_database.harbor.max_idle_conns}} + max_open_conns: {{ external_database.harbor.max_open_conns}} +{% else %} +# Uncomment external_database if using external database. +# external_database: +# harbor: +# host: harbor_db_host +# port: harbor_db_port +# db_name: harbor_db_name +# username: harbor_db_username +# password: harbor_db_password +# ssl_mode: disable +# max_idle_conns: 2 +# max_open_conns: 0 +{% endif %} + +{% if redis is defined %} +redis: +# # db_index 0 is for core, it's unchangeable +{% if redis.registry_db_index is defined %} + registry_db_index: {{ redis.registry_db_index }} +{% else %} +# # registry_db_index: 1 +{% endif %} +{% if redis.jobservice_db_index is defined %} + jobservice_db_index: {{ redis.jobservice_db_index }} +{% else %} +# # jobservice_db_index: 2 +{% endif %} +{% if redis.trivy_db_index is defined %} + trivy_db_index: {{ redis.trivy_db_index }} +{% else %} +# # trivy_db_index: 5 +{% endif %} +{% if redis.harbor_db_index is defined %} + harbor_db_index: {{ redis.harbor_db_index }} +{% else %} +# # it's optional, the db for harbor business misc, by default is 0, uncomment it if you want to change it. +# # harbor_db_index: 6 +{% endif %} +{% if redis.cache_layer_db_index is defined %} + cache_layer_db_index: {{ redis.cache_layer_db_index }} +{% else %} +# # it's optional, the db for harbor cache layer, by default is 0, uncomment it if you want to change it. +# # cache_layer_db_index: 7 +{% endif %} +{% else %} +# Uncomment redis if need to customize redis db +# redis: +# # db_index 0 is for core, it's unchangeable +# # registry_db_index: 1 +# # jobservice_db_index: 2 +# # trivy_db_index: 5 +# # it's optional, the db for harbor business misc, by default is 0, uncomment it if you want to change it. +# # harbor_db_index: 6 +# # it's optional, the db for harbor cache layer, by default is 0, uncomment it if you want to change it. +# # cache_layer_db_index: 7 +{% endif %} + +{% if external_redis is defined %} +external_redis: + # support redis, redis+sentinel + # host for redis: : + # host for redis+sentinel: + # :,:,: + host: {{ external_redis.host }} + password: {{ external_redis.password }} + # Redis AUTH command was extended in Redis 6, it is possible to use it in the two-arguments AUTH form. + {% if external_redis.username is defined %} + username: {{ external_redis.username }} + {% else %} + # username: + {% endif %} + # sentinel_master_set must be set to support redis+sentinel + #sentinel_master_set: + # db_index 0 is for core, it's unchangeable + registry_db_index: {{ external_redis.registry_db_index }} + jobservice_db_index: {{ external_redis.jobservice_db_index }} + trivy_db_index: 5 + idle_timeout_seconds: 30 + {% if external_redis.harbor_db_index is defined %} + harbor_db_index: {{ redis.harbor_db_index }} + {% else %} +# # it's optional, the db for harbor business misc, by default is 0, uncomment it if you want to change it. +# # harbor_db_index: 6 + {% endif %} + {% if external_redis.cache_layer_db_index is defined %} + cache_layer_db_index: {{ redis.cache_layer_db_index }} + {% else %} +# # it's optional, the db for harbor cache layer, by default is 0, uncomment it if you want to change it. +# # cache_layer_db_index: 7 + {% endif %} +{% else %} +# Uncomments external_redis if using external Redis server +# external_redis: +# # support redis, redis+sentinel +# # host for redis: : +# # host for redis+sentinel: +# # :,:,: +# host: redis:6379 +# password: +# # Redis AUTH command was extended in Redis 6, it is possible to use it in the two-arguments AUTH form. +# # username: +# # sentinel_master_set must be set to support redis+sentinel +# #sentinel_master_set: +# # db_index 0 is for core, it's unchangeable +# registry_db_index: 1 +# jobservice_db_index: 2 +# trivy_db_index: 5 +# idle_timeout_seconds: 30 +# # it's optional, the db for harbor business misc, by default is 0, uncomment it if you want to change it. +# # harbor_db_index: 6 +# # it's optional, the db for harbor cache layer, by default is 0, uncomment it if you want to change it. +# # cache_layer_db_index: 7 +{% endif %} + +{% if uaa is defined %} +# Uncomment uaa for trusting the certificate of uaa instance that is hosted via self-signed cert. +uaa: + ca_file: {{ uaa.ca_file }} +{% else %} +# Uncomment uaa for trusting the certificate of uaa instance that is hosted via self-signed cert. +# uaa: +# ca_file: /path/to/ca +{% endif %} + + +# Global proxy +# Config http proxy for components, e.g. http://my.proxy.com:3128 +# Components doesn't need to connect to each others via http proxy. +# Remove component from `components` array if want disable proxy +# for it. If you want use proxy for replication, MUST enable proxy +# for core and jobservice, and set `http_proxy` and `https_proxy`. +# Add domain to the `no_proxy` field, when you want disable proxy +# for some special registry. +{% if proxy is defined %} +proxy: + http_proxy: {{ proxy.http_proxy or ''}} + https_proxy: {{ proxy.https_proxy or ''}} + no_proxy: {{ proxy.no_proxy or ''}} + {% if proxy.components is defined %} + components: + {% for component in proxy.components %} + {% if component != 'clair' %} + - {{component}} + {% endif %} + {% endfor %} + {% endif %} +{% else %} +proxy: + http_proxy: + https_proxy: + no_proxy: + components: + - core + - jobservice + - trivy +{% endif %} + +{% if metric is defined %} +metric: + enabled: {{ metric.enabled }} + port: {{ metric.port }} + path: {{ metric.path }} +{% else %} +# metric: +# enabled: false +# port: 9090 +# path: /metrics +{% endif %} + +# Trace related config +# only can enable one trace provider(jaeger or otel) at the same time, +# and when using jaeger as provider, can only enable it with agent mode or collector mode. +# if using jaeger collector mode, uncomment endpoint and uncomment username, password if needed +# if using jaeger agetn mode uncomment agent_host and agent_port +{% if trace is defined %} +trace: + enabled: {{ trace.enabled | lower}} + sample_rate: {{ trace.sample_rate }} + # # namespace used to differentiate different harbor services + {% if trace.namespace is defined %} + namespace: {{ trace.namespace }} + {% else %} + # namespace: + {% endif %} + # # attributes is a key value dict contains user defined attributes used to initialize trace provider + {% if trace.attributes is defined%} + attributes: + {% for name, value in trace.attributes.items() %} + {{name}}: {{value}} + {% endfor %} + {% else %} + # attributes: + # application: harbor + {% endif %} + {% if trace.jaeger is defined%} + jaeger: + endpoint: {{trace.jaeger.endpoint or '' }} + username: {{trace.jaeger.username or ''}} + password: {{trace.jaeger.password or ''}} + agent_host: {{trace.jaeger.agent_host or ''}} + agent_port: {{trace.jaeger.agent_port or ''}} + {% else %} + # jaeger: + # endpoint: + # username: + # password: + # agent_host: + # agent_port: + {% endif %} + {% if trace. otel is defined %} + otel: + endpoint: {{trace.otel.endpoint or '' }} + url_path: {{trace.otel.url_path or '' }} + compression: {{trace.otel.compression | lower }} + insecure: {{trace.otel.insecure | lower }} + timeout: {{trace.otel.timeout or '' }} + {% else %} + # otel: + # endpoint: hostname:4318 + # url_path: /v1/traces + # compression: false + # insecure: true + # # timeout is in seconds + # timeout: 10 + {% endif%} +{% else %} +# trace: +# enabled: true +# # set sample_rate to 1 if you wanna sampling 100% of trace data; set 0.5 if you wanna sampling 50% of trace data, and so forth +# sample_rate: 1 +# # # namespace used to differentiate different harbor services +# # namespace: +# # # attributes is a key value dict contains user defined attributes used to initialize trace provider +# # attributes: +# # application: harbor +# # jaeger: +# # endpoint: http://hostname:14268/api/traces +# # username: +# # password: +# # agent_host: hostname +# # agent_port: 6831 +# # otel: +# # endpoint: hostname:4318 +# # url_path: /v1/traces +# # compression: false +# # insecure: true +# # # timeout is in seconds +# # timeout: 10 +{% endif %} + +# enable purge _upload directories +{% if upload_purging is defined %} +upload_purging: + enabled: {{ upload_purging.enabled | lower}} + age: {{ upload_purging.age }} + interval: {{ upload_purging.interval }} + dryrun: {{ upload_purging.dryrun | lower}} +{% else %} +upload_purging: + enabled: true + # remove files in _upload directories which exist for a period of time, default is one week. + age: 168h + # the interval of the purge operations + interval: 24h + dryrun: false +{% endif %} + +# Cache layer related config +{% if cache is defined %} +cache: + enabled: {{ cache.enabled | lower}} + expire_hours: {{ cache.expire_hours }} +{% else %} +cache: + enabled: false + expire_hours: 24 +{% endif %} + +# Harbor core configurations +# Uncomment to enable the following harbor core related configuration items. +{% if core is defined %} +core: + # The provider for updating project quota(usage), there are 2 options, redis or db, + # by default is implemented by db but you can switch the updation via redis which + # can improve the performance of high concurrent pushing to the same project, + # and reduce the database connections spike and occupies. + # By redis will bring up some delay for quota usage updation for display, so only + # suggest switch provider to redis if you were ran into the db connections spike aroud + # the scenario of high concurrent pushing to same project, no improvment for other scenes. + quota_update_provider: {{ core.quota_update_provider }} +{% else %} +# core: +# # The provider for updating project quota(usage), there are 2 options, redis or db, +# # by default is implemented by db but you can switch the updation via redis which +# # can improve the performance of high concurrent pushing to the same project, +# # and reduce the database connections spike and occupies. +# # By redis will bring up some delay for quota usage updation for display, so only +# # suggest switch provider to redis if you were ran into the db connections spike around +# # the scenario of high concurrent pushing to same project, no improvement for other scenes. +# quota_update_provider: redis # Or db +{% endif %} From b7d4bf0d076a64e8dd799f1cab9b4a3eb244c60e Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Mon, 22 Apr 2024 17:43:04 +0800 Subject: [PATCH 106/145] Log and skip adapter ping error when retrieve adapter capability (#20314) Signed-off-by: stonezdj --- src/controller/scanner/base_controller.go | 14 +++++++++----- src/controller/scanner/controller.go | 3 +++ src/server/v2.0/handler/project.go | 12 +++++------- src/server/v2.0/handler/project_test.go | 3 +++ src/testing/controller/scanner/controller.go | 18 ++++++++++++++++++ 5 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/controller/scanner/base_controller.go b/src/controller/scanner/base_controller.go index 068424a1e..d05878288 100644 --- a/src/controller/scanner/base_controller.go +++ b/src/controller/scanner/base_controller.go @@ -37,6 +37,8 @@ const ( proScannerMetaKey = "projectScanner" statusUnhealthy = "unhealthy" statusHealthy = "healthy" + // RetrieveCapFailMsg the message indicate failed to retrieve the scanner capabilities + RetrieveCapFailMsg = "failed to retrieve scanner capabilities, error %v" ) // DefaultController is a singleton api controller for plug scanners @@ -80,8 +82,9 @@ func (bc *basicController) ListRegistrations(ctx context.Context, query *q.Query return nil, errors.Wrap(err, "api controller: list registrations") } for _, r := range l { - if err := bc.appendCap(ctx, r); err != nil { - return nil, err + if err := bc.RetrieveCap(ctx, r); err != nil { + log.Warningf(RetrieveCapFailMsg, err) + return l, nil } } return l, nil @@ -129,13 +132,14 @@ func (bc *basicController) GetRegistration(ctx context.Context, registrationUUID if r == nil { return nil, nil } - if err := bc.appendCap(ctx, r); err != nil { - return nil, err + if err := bc.RetrieveCap(ctx, r); err != nil { + log.Warningf(RetrieveCapFailMsg, err) + return r, nil } return r, nil } -func (bc *basicController) appendCap(ctx context.Context, r *scanner.Registration) error { +func (bc *basicController) RetrieveCap(ctx context.Context, r *scanner.Registration) error { mt, err := bc.Ping(ctx, r) if err != nil { logger.Errorf("Get registration error: %s", err) diff --git a/src/controller/scanner/controller.go b/src/controller/scanner/controller.go index d2ae007dd..d9558d3e4 100644 --- a/src/controller/scanner/controller.go +++ b/src/controller/scanner/controller.go @@ -154,4 +154,7 @@ type Controller interface { // *v1.ScannerAdapterMetadata : metadata returned by the scanner if successfully ping // error : non nil error if any errors occurred GetMetadata(ctx context.Context, registrationUUID string) (*v1.ScannerAdapterMetadata, error) + + // RetrieveCap retrieve scanner capabilities + RetrieveCap(ctx context.Context, r *scanner.Registration) error } diff --git a/src/server/v2.0/handler/project.go b/src/server/v2.0/handler/project.go index f9848345f..29286f8ec 100644 --- a/src/server/v2.0/handler/project.go +++ b/src/server/v2.0/handler/project.go @@ -590,18 +590,16 @@ func (a *projectAPI) GetScannerOfProject(ctx context.Context, params operation.G return a.SendError(ctx, err) } - scanner, err := a.scannerCtl.GetRegistrationByProject(ctx, p.ProjectID) + s, err := a.scannerCtl.GetRegistrationByProject(ctx, p.ProjectID) if err != nil { return a.SendError(ctx, err) } - if scanner != nil { - metadata, err := a.scannerCtl.GetMetadata(ctx, scanner.UUID) - if err != nil { - return a.SendError(ctx, err) + if s != nil { + if err := a.scannerCtl.RetrieveCap(ctx, s); err != nil { + log.Warningf(scanner.RetrieveCapFailMsg, err) } - scanner.Capabilities = metadata.ConvertCapability() } - return operation.NewGetScannerOfProjectOK().WithPayload(model.NewScannerRegistration(scanner).ToSwagger(ctx)) + return operation.NewGetScannerOfProjectOK().WithPayload(model.NewScannerRegistration(s).ToSwagger(ctx)) } func (a *projectAPI) ListScannerCandidatesOfProject(ctx context.Context, params operation.ListScannerCandidatesOfProjectParams) middleware.Responder { diff --git a/src/server/v2.0/handler/project_test.go b/src/server/v2.0/handler/project_test.go index 9289829b8..eca7f09a3 100644 --- a/src/server/v2.0/handler/project_test.go +++ b/src/server/v2.0/handler/project_test.go @@ -89,6 +89,7 @@ func (suite *ProjectTestSuite) TestGetScannerOfProject() { mock.OnAnything(suite.projectCtl, "Get").Return(suite.project, nil).Once() mock.OnAnything(suite.scannerCtl, "GetRegistrationByProject").Return(nil, nil).Once() mock.OnAnything(suite.scannerCtl, "GetMetadata").Return(suite.metadata, nil).Once() + mock.OnAnything(suite.scannerCtl, "RetrieveCap").Return(nil).Once() res, err := suite.Get("/projects/1/scanner") suite.NoError(err) suite.Equal(200, res.StatusCode) @@ -98,6 +99,7 @@ func (suite *ProjectTestSuite) TestGetScannerOfProject() { mock.OnAnything(suite.projectCtl, "Get").Return(suite.project, nil).Once() mock.OnAnything(suite.scannerCtl, "GetRegistrationByProject").Return(suite.reg, nil).Once() mock.OnAnything(suite.scannerCtl, "GetMetadata").Return(suite.metadata, nil).Once() + mock.OnAnything(suite.scannerCtl, "RetrieveCap").Return(nil).Once() var scanner scanner.Registration res, err := suite.GetJSON("/projects/1/scanner", &scanner) suite.NoError(err) @@ -110,6 +112,7 @@ func (suite *ProjectTestSuite) TestGetScannerOfProject() { mock.OnAnything(suite.projectCtl, "Get").Return(suite.project, nil).Once() mock.OnAnything(suite.scannerCtl, "GetMetadata").Return(suite.metadata, nil).Once() mock.OnAnything(suite.scannerCtl, "GetRegistrationByProject").Return(suite.reg, nil).Once() + mock.OnAnything(suite.scannerCtl, "RetrieveCap").Return(nil).Once() var scanner scanner.Registration res, err := suite.GetJSON("/projects/library/scanner", &scanner) diff --git a/src/testing/controller/scanner/controller.go b/src/testing/controller/scanner/controller.go index 2f7f9777a..38d66f9b9 100644 --- a/src/testing/controller/scanner/controller.go +++ b/src/testing/controller/scanner/controller.go @@ -281,6 +281,24 @@ func (_m *Controller) RegistrationExists(ctx context.Context, registrationUUID s return r0 } +// RetrieveCap provides a mock function with given fields: ctx, r +func (_m *Controller) RetrieveCap(ctx context.Context, r *scanner.Registration) error { + ret := _m.Called(ctx, r) + + if len(ret) == 0 { + panic("no return value specified for RetrieveCap") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *scanner.Registration) error); ok { + r0 = rf(ctx, r) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // SetDefaultRegistration provides a mock function with given fields: ctx, registrationUUID func (_m *Controller) SetDefaultRegistration(ctx context.Context, registrationUUID string) error { ret := _m.Called(ctx, registrationUUID) From c80e9bf4771afd062f5e35440ba2c5cffad82ff6 Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Wed, 24 Apr 2024 09:57:46 +0800 Subject: [PATCH 107/145] Add 422 in the swagger.yaml (#20344) change log level with no content message fix time in sbom accessory fixes #20342 #20332 #20328 Signed-off-by: stonezdj --- api/v2.0/swagger.yaml | 16 ++++++++++++++++ src/controller/scan/base_controller.go | 2 +- src/pkg/scan/sbom/sbom.go | 6 ++++-- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index 5f0457790..6890602a6 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -1192,6 +1192,8 @@ paths: $ref: '#/responses/403' '404': $ref: '#/responses/404' + '422': + $ref: '#/responses/422' '500': $ref: '#/responses/500' /projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/scan/stop: @@ -1223,6 +1225,8 @@ paths: $ref: '#/responses/403' '404': $ref: '#/responses/404' + '422': + $ref: '#/responses/422' '500': $ref: '#/responses/500' /projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/scan/{report_id}/log: @@ -1476,6 +1480,8 @@ paths: $ref: '#/responses/403' '404': $ref: '#/responses/404' + '422': + $ref: '#/responses/422' '500': $ref: '#/responses/500' /projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/labels: @@ -4823,6 +4829,8 @@ paths: $ref: '#/responses/403' '404': $ref: '#/responses/404' + '422': + $ref: '#/responses/422' '500': $ref: '#/responses/500' /schedules: @@ -6456,6 +6464,14 @@ responses: type: string schema: $ref: '#/definitions/Errors' + '422': + description: Unsupported Type + headers: + X-Request-Id: + description: The ID of the corresponding request for the response + type: string + schema: + $ref: '#/definitions/Errors' '500': description: Internal server error headers: diff --git a/src/controller/scan/base_controller.go b/src/controller/scan/base_controller.go index c1b68947c..161481711 100644 --- a/src/controller/scan/base_controller.go +++ b/src/controller/scan/base_controller.go @@ -751,7 +751,7 @@ func (bc *basicController) GetSBOMSummary(ctx context.Context, art *ar.Artifact, reportContent := reports[0].Report result := map[string]interface{}{} if len(reportContent) == 0 { - log.Warning("no content for current report") + log.Debug("no content for current report") return result, nil } err = json.Unmarshal([]byte(reportContent), &result) diff --git a/src/pkg/scan/sbom/sbom.go b/src/pkg/scan/sbom/sbom.go index bbf405571..a3e0f8501 100644 --- a/src/pkg/scan/sbom/sbom.go +++ b/src/pkg/scan/sbom/sbom.go @@ -112,9 +112,11 @@ func (v *scanHandler) PostScan(ctx job.Context, sr *v1.ScanRequest, _ *scanModel // annotations defines the annotations for the accessory artifact func (v *scanHandler) annotations() map[string]string { + t := time.Now().Format(time.RFC3339) return map[string]string{ - "created-by": "Harbor", - "org.opencontainers.artifact.created": time.Now().Format(time.RFC3339), + "created": t, + "created-by": "Harbor", + "org.opencontainers.artifact.created": t, "org.opencontainers.artifact.description": "SPDX JSON SBOM", } } From 2af02f3b256de2825887f03d9d82e966019bc315 Mon Sep 17 00:00:00 2001 From: Shengwen YU Date: Wed, 24 Apr 2024 16:05:14 +0800 Subject: [PATCH 108/145] fix: update image reference to "@" in audit log when pushing & deleting images (#20348) Signed-off-by: Shengwen Yu --- src/controller/event/topic.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controller/event/topic.go b/src/controller/event/topic.go index d099a8dbb..5898bf4af 100644 --- a/src/controller/event/topic.go +++ b/src/controller/event/topic.go @@ -159,7 +159,7 @@ func (p *PushArtifactEvent) ResolveToAuditLog() (*model.AuditLog, error) { ResourceType: "artifact"} if len(p.Tags) == 0 { - auditLog.Resource = fmt.Sprintf("%s:%s", + auditLog.Resource = fmt.Sprintf("%s@%s", p.Artifact.RepositoryName, p.Artifact.Digest) } else { auditLog.Resource = fmt.Sprintf("%s:%s", @@ -222,7 +222,7 @@ func (d *DeleteArtifactEvent) ResolveToAuditLog() (*model.AuditLog, error) { Operation: rbac.ActionDelete.String(), Username: d.Operator, ResourceType: "artifact", - Resource: fmt.Sprintf("%s:%s", d.Artifact.RepositoryName, d.Artifact.Digest)} + Resource: fmt.Sprintf("%s@%s", d.Artifact.RepositoryName, d.Artifact.Digest)} return auditLog, nil } From ec8d692fe6038b2d0f35310ec10fdc665ad0e1c1 Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Thu, 25 Apr 2024 17:00:35 +0800 Subject: [PATCH 109/145] Add scanner info and report_id to sbom_overview on listing artifact (#20358) Add scan_status and report_id when scan has a failed task Signed-off-by: stonezdj --- api/v2.0/swagger.yaml | 2 ++ src/controller/scan/base_controller.go | 21 ++++++++++++++++ src/controller/scan/base_controller_test.go | 27 ++++++++++++++++++--- src/pkg/scan/sbom/model/summary.go | 4 +++ src/pkg/scan/sbom/sbom.go | 15 ++++++------ src/server/v2.0/handler/assembler/report.go | 22 ++++++++--------- 6 files changed, 68 insertions(+), 23 deletions(-) diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index 6890602a6..c9e1e8a50 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -6816,6 +6816,8 @@ definitions: format: int64 description: 'Time in seconds required to create the report' example: 300 + scanner: + $ref: '#/definitions/Scanner' NativeReportSummary: type: object description: 'The summary for the native report' diff --git a/src/controller/scan/base_controller.go b/src/controller/scan/base_controller.go index 161481711..c70221a0f 100644 --- a/src/controller/scan/base_controller.go +++ b/src/controller/scan/base_controller.go @@ -751,6 +751,11 @@ func (bc *basicController) GetSBOMSummary(ctx context.Context, art *ar.Artifact, reportContent := reports[0].Report result := map[string]interface{}{} if len(reportContent) == 0 { + status := bc.retrieveStatusFromTask(ctx, reports[0].UUID) + if len(status) > 0 { + result[sbomModel.ReportID] = reports[0].UUID + result[sbomModel.ScanStatus] = status + } log.Debug("no content for current report") return result, nil } @@ -758,6 +763,22 @@ func (bc *basicController) GetSBOMSummary(ctx context.Context, art *ar.Artifact, return result, err } +// retrieve the status from task +func (bc *basicController) retrieveStatusFromTask(ctx context.Context, reportID string) string { + if len(reportID) == 0 { + return "" + } + tasks, err := bc.taskMgr.ListScanTasksByReportUUID(ctx, reportID) + if err != nil { + log.Warningf("can not find the task with report UUID %v, error %v", reportID, err) + return "" + } + if len(tasks) > 0 { + return tasks[0].Status + } + return "" +} + // GetScanLog ... func (bc *basicController) GetScanLog(ctx context.Context, artifact *ar.Artifact, uuid string) ([]byte, error) { if len(uuid) == 0 { diff --git a/src/controller/scan/base_controller_test.go b/src/controller/scan/base_controller_test.go index 521325d79..19a559e0e 100644 --- a/src/controller/scan/base_controller_test.go +++ b/src/controller/scan/base_controller_test.go @@ -70,9 +70,10 @@ type ControllerTestSuite struct { tagCtl *tagtesting.FakeController - registration *scanner.Registration - artifact *artifact.Artifact - rawReport string + registration *scanner.Registration + artifact *artifact.Artifact + wrongArtifact *artifact.Artifact + rawReport string execMgr *tasktesting.ExecutionManager taskMgr *tasktesting.Manager @@ -101,6 +102,9 @@ func (suite *ControllerTestSuite) SetupSuite() { suite.artifact.Digest = "digest-code" suite.artifact.ManifestMediaType = v1.MimeTypeDockerArtifact + suite.wrongArtifact = &artifact.Artifact{Artifact: art.Artifact{ID: 2, ProjectID: 1}} + suite.wrongArtifact.Digest = "digest-wrong" + m := &v1.ScannerAdapterMetadata{ Scanner: &v1.Scanner{ Name: "Trivy", @@ -202,8 +206,11 @@ func (suite *ControllerTestSuite) SetupSuite() { Report: `{"sbom_digest": "sha256:1234567890", "scan_status": "Success", "duration": 3, "start_time": "2021-09-01T00:00:00Z", "end_time": "2021-09-01T00:00:03Z"}`, }, } + + emptySBOMReport := []*scan.Report{{Report: ``, UUID: "rp-uuid-004"}} mgr.On("GetBy", mock.Anything, suite.artifact.Digest, suite.registration.UUID, []string{v1.MimeTypeNativeReport}).Return(reports, nil) mgr.On("GetBy", mock.Anything, suite.artifact.Digest, suite.registration.UUID, []string{v1.MimeTypeSBOMReport}).Return(sbomReport, nil) + mgr.On("GetBy", mock.Anything, suite.wrongArtifact.Digest, suite.registration.UUID, []string{v1.MimeTypeSBOMReport}).Return(emptySBOMReport, nil) mgr.On("Get", mock.Anything, "rp-uuid-001").Return(reports[0], nil) mgr.On("UpdateReportData", "rp-uuid-001", suite.rawReport, (int64)(10000)).Return(nil) mgr.On("UpdateStatus", "the-uuid-123", "Success", (int64)(10000)).Return(nil) @@ -654,6 +661,12 @@ func (suite *ControllerTestSuite) TestGenerateSBOMSummary() { suite.NotNil(dgst) suite.Equal("Success", status) suite.Equal("sha256:1234567890", dgst) + tasks := []*task.Task{{Status: "Error"}} + suite.taskMgr.On("ListScanTasksByReportUUID", mock.Anything, "rp-uuid-004").Return(tasks, nil).Once() + sum2, err := suite.c.GetSummary(context.TODO(), suite.wrongArtifact, []string{v1.MimeTypeSBOMReport}) + suite.Nil(err) + suite.NotNil(sum2) + } func TestIsSBOMMimeTypes(t *testing.T) { @@ -683,5 +696,11 @@ func (suite *ControllerTestSuite) TestDeleteArtifactAccessories() { } ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{}) suite.NoError(suite.c.deleteArtifactAccessories(ctx, reports)) - +} + +func (suite *ControllerTestSuite) TestRetrieveStatusFromTask() { + tasks := []*task.Task{{Status: "Error"}} + suite.taskMgr.On("ListScanTasksByReportUUID", mock.Anything, "rp-uuid-004").Return(tasks, nil).Once() + status := suite.c.retrieveStatusFromTask(nil, "rp-uuid-004") + suite.Equal("Error", status) } diff --git a/src/pkg/scan/sbom/model/summary.go b/src/pkg/scan/sbom/model/summary.go index 46c870f97..0d7e6a2ef 100644 --- a/src/pkg/scan/sbom/model/summary.go +++ b/src/pkg/scan/sbom/model/summary.go @@ -27,6 +27,10 @@ const ( Duration = "duration" // ScanStatus ... ScanStatus = "scan_status" + // ReportID ... + ReportID = "report_id" + // Scanner ... + Scanner = "scanner" ) // Summary includes the sbom summary information diff --git a/src/pkg/scan/sbom/sbom.go b/src/pkg/scan/sbom/sbom.go index a3e0f8501..f8e6d2e43 100644 --- a/src/pkg/scan/sbom/sbom.go +++ b/src/pkg/scan/sbom/sbom.go @@ -87,7 +87,7 @@ func (v *scanHandler) RequiredPermissions() []*types.Policy { // PostScan defines task specific operations after the scan is complete func (v *scanHandler) PostScan(ctx job.Context, sr *v1.ScanRequest, _ *scanModel.Report, rawReport string, startTime time.Time, robot *model.Robot) (string, error) { - sbomContent, err := retrieveSBOMContent(rawReport) + sbomContent, s, err := retrieveSBOMContent(rawReport) if err != nil { return "", err } @@ -107,7 +107,7 @@ func (v *scanHandler) PostScan(ctx job.Context, sr *v1.ScanRequest, _ *scanModel myLogger.Errorf("error when create accessory from image %v", err) return "", err } - return v.generateReport(startTime, sr.Artifact.Repository, dgst, "Success") + return v.generateReport(startTime, sr.Artifact.Repository, dgst, "Success", s) } // annotations defines the annotations for the accessory artifact @@ -121,7 +121,7 @@ func (v *scanHandler) annotations() map[string]string { } } -func (v *scanHandler) generateReport(startTime time.Time, repository, digest, status string) (string, error) { +func (v *scanHandler) generateReport(startTime time.Time, repository, digest, status string, scanner *v1.Scanner) (string, error) { summary := sbom.Summary{} endTime := time.Now() summary[sbom.StartTime] = startTime @@ -130,6 +130,7 @@ func (v *scanHandler) generateReport(startTime time.Time, repository, digest, st summary[sbom.SBOMRepository] = repository summary[sbom.SBOMDigest] = digest summary[sbom.ScanStatus] = status + summary[sbom.Scanner] = scanner rep, err := json.Marshal(summary) if err != nil { return "", err @@ -150,15 +151,15 @@ func registryFQDN(ctx context.Context) string { } // retrieveSBOMContent retrieves the "sbom" field from the raw report -func retrieveSBOMContent(rawReport string) ([]byte, error) { +func retrieveSBOMContent(rawReport string) ([]byte, *v1.Scanner, error) { rpt := vuln.Report{} err := json.Unmarshal([]byte(rawReport), &rpt) if err != nil { - return nil, err + return nil, nil, err } sbomContent, err := json.Marshal(rpt.SBOM) if err != nil { - return nil, err + return nil, nil, err } - return sbomContent, nil + return sbomContent, rpt.Scanner, nil } diff --git a/src/server/v2.0/handler/assembler/report.go b/src/server/v2.0/handler/assembler/report.go index e4f9657ea..ff11e2b8f 100644 --- a/src/server/v2.0/handler/assembler/report.go +++ b/src/server/v2.0/handler/assembler/report.go @@ -21,16 +21,12 @@ import ( "github.com/goharbor/harbor/src/lib" "github.com/goharbor/harbor/src/lib/log" v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1" + sbomModel "github.com/goharbor/harbor/src/pkg/scan/sbom/model" "github.com/goharbor/harbor/src/server/v2.0/handler/model" ) const ( vulnerabilitiesAddition = "vulnerabilities" - startTime = "start_time" - endTime = "end_time" - scanStatus = "scan_status" - sbomDigest = "sbom_digest" - duration = "duration" ) // NewScanReportAssembler returns vul assembler @@ -92,17 +88,19 @@ func (assembler *ScanReportAssembler) Assemble(ctx context.Context) error { overview, err := assembler.scanCtl.GetSummary(ctx, &artifact.Artifact, []string{v1.MimeTypeSBOMReport}) if err != nil { log.Warningf("get scan summary of artifact %s@%s for %s failed, error:%v", artifact.RepositoryName, artifact.Digest, v1.MimeTypeSBOMReport, err) - } else if len(overview) > 0 { + } + if len(overview) > 0 { artifact.SBOMOverView = map[string]interface{}{ - startTime: overview[startTime], - endTime: overview[endTime], - scanStatus: overview[scanStatus], - sbomDigest: overview[sbomDigest], - duration: overview[duration], + sbomModel.StartTime: overview[sbomModel.StartTime], + sbomModel.EndTime: overview[sbomModel.EndTime], + sbomModel.ScanStatus: overview[sbomModel.ScanStatus], + sbomModel.SBOMDigest: overview[sbomModel.SBOMDigest], + sbomModel.Duration: overview[sbomModel.Duration], + sbomModel.ReportID: overview[sbomModel.ReportID], + sbomModel.Scanner: overview[sbomModel.Scanner], } } } } - return nil } From 0e8dce72be06b5082cc4b867683a1a36bd2ed37e Mon Sep 17 00:00:00 2001 From: Shengwen YU Date: Fri, 26 Apr 2024 10:52:11 +0800 Subject: [PATCH 110/145] fix: fresh scanner list when updating scanner (#20366) Signed-off-by: Shengwen Yu --- tests/resources/Harbor-Pages/Vulnerability_Elements.robot | 1 + tests/resources/Util.robot | 1 + tests/robot-cases/Group1-Nightly/Trivy.robot | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/resources/Harbor-Pages/Vulnerability_Elements.robot b/tests/resources/Harbor-Pages/Vulnerability_Elements.robot index e64594a87..593c9d1a7 100644 --- a/tests/resources/Harbor-Pages/Vulnerability_Elements.robot +++ b/tests/resources/Harbor-Pages/Vulnerability_Elements.robot @@ -51,3 +51,4 @@ ${scanner_password_input} //*[@id='scanner-password'] ${scanner_token_input} //*[@id='scanner-token'] ${scanner_apikey_input} //*[@id='scanner-apiKey'] ${scanner_set_default_btn} //*[@id='set-default'] +${scanner_list_refresh_btn} //span[@class='refresh-btn']//clr-icon[@role='none'] diff --git a/tests/resources/Util.robot b/tests/resources/Util.robot index 54972975c..d394a6430 100644 --- a/tests/resources/Util.robot +++ b/tests/resources/Util.robot @@ -76,6 +76,7 @@ Resource Harbor-Pages/Job_Service_Dashboard_Elements.robot Resource Harbor-Pages/SecurityHub.robot Resource Harbor-Pages/SecurityHub_Elements.robot Resource Harbor-Pages/Verify.robot +Resource Harbor-Pages/Vulnerability_Elements.robot Resource Docker-Util.robot Resource CNAB_Util.robot Resource Helm-Util.robot diff --git a/tests/robot-cases/Group1-Nightly/Trivy.robot b/tests/robot-cases/Group1-Nightly/Trivy.robot index 3db9513ed..bcfff07d2 100644 --- a/tests/robot-cases/Group1-Nightly/Trivy.robot +++ b/tests/robot-cases/Group1-Nightly/Trivy.robot @@ -182,7 +182,7 @@ Test Case - External Scanner CRUD Filter Scanner By Name scanner${d} Filter Scanner By Endpoint ${SCANNER_ENDPOINT} Retry Wait Element Count //clr-dg-row 1 - Retry Wait Until Page Contains Element //clr-dg-row[.//span[text()='scanner${d}'] and .//clr-dg-cell[text()='${SCANNER_ENDPOINT}'] and .//span[text()='Healthy'] and .//clr-dg-cell[text()='None']] + Retry Double Keywords When Error Retry Element Click xpath=${scanner_list_refresh_btn} Retry Wait Until Page Contains Element //clr-dg-row[.//span[text()='scanner${d}'] and .//clr-dg-cell[text()='${SCANNER_ENDPOINT}'] and .//span[text()='Healthy'] and .//clr-dg-cell[text()='None']] # Delete this scanner Delete Scanner scanner${d} Close Browser From d0cb200ed5591dbdcfbb794631317bd2460e7839 Mon Sep 17 00:00:00 2001 From: Shengwen YU Date: Fri, 26 Apr 2024 11:44:00 +0800 Subject: [PATCH 111/145] fix: update nightly test case for verifying audit log of image digest (#20354) Signed-off-by: Shengwen Yu --- tests/robot-cases/Group1-Nightly/Common.robot | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/robot-cases/Group1-Nightly/Common.robot b/tests/robot-cases/Group1-Nightly/Common.robot index d6915db20..fdc12f54b 100644 --- a/tests/robot-cases/Group1-Nightly/Common.robot +++ b/tests/robot-cases/Group1-Nightly/Common.robot @@ -885,13 +885,13 @@ Test Case - Audit Log And Purge # pull artifact Docker Pull ${ip}/project${d}/${image}:${tag1} Docker Logout ${ip} - Verify Log ${user} project${d}/${image}:${sha256} artifact pull + Verify Log ${user} project${d}/${image}@${sha256} artifact pull Go Into Repo project${d} ${image} # delete artifact @{tag_list} Create List ${tag1} Multi-delete Artifact @{tag_list} Switch To Logs - Verify Log ${user} project${d}/${image}:${sha256} artifact delete + Verify Log ${user} project${d}/${image}@${sha256} artifact delete Go Into Project project${d} # delete repository Delete Repo project${d} ${image} From 822784aac81fbce27fd320e0008eea87ebf11ae2 Mon Sep 17 00:00:00 2001 From: Shengwen YU Date: Fri, 26 Apr 2024 12:28:22 +0800 Subject: [PATCH 112/145] =?UTF-8?q?fix:=20update=20to=20"clr-dg-cell[10]"?= =?UTF-8?q?=20to=20fix=20the=20pull=20time=20tc=20due=20to=20addin?= =?UTF-8?q?=E2=80=A6=20(#20361)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: update to "clr-dg-cell[10]" to fix the pull time tc due to adding an SBOM column Signed-off-by: Shengwen Yu --- tests/robot-cases/Group1-Nightly/Common.robot | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/robot-cases/Group1-Nightly/Common.robot b/tests/robot-cases/Group1-Nightly/Common.robot index fdc12f54b..7730491e1 100644 --- a/tests/robot-cases/Group1-Nightly/Common.robot +++ b/tests/robot-cases/Group1-Nightly/Common.robot @@ -1151,8 +1151,8 @@ Test Case - Retain Image Last Pull Time Scan Repo ${tag} Succeed Sleep 15 Reload Page - Retry Wait Element Visible //clr-dg-row//clr-dg-cell[9] - ${last_pull_time}= Get Text //clr-dg-row//clr-dg-cell[9] + Retry Wait Element Visible //clr-dg-row//clr-dg-cell[10] + ${last_pull_time}= Get Text //clr-dg-row//clr-dg-cell[10] Should Be Empty ${last_pull_time} Switch To Configuration System Setting Set Up Retain Image Last Pull Time disable @@ -1160,8 +1160,8 @@ Test Case - Retain Image Last Pull Time Scan Repo ${tag} Succeed Sleep 15 Reload Page - Retry Wait Element Visible //clr-dg-row//clr-dg-cell[9] - ${last_pull_time}= Get Text //clr-dg-row//clr-dg-cell[9] + Retry Wait Element Visible //clr-dg-row//clr-dg-cell[10] + ${last_pull_time}= Get Text //clr-dg-row//clr-dg-cell[10] Should Not Be Empty ${last_pull_time} Close Browser From c791b39a26bf4b5a9c7f88d39025b531ab93f20d Mon Sep 17 00:00:00 2001 From: Shengwen YU Date: Fri, 26 Apr 2024 14:13:00 +0800 Subject: [PATCH 113/145] fix: add stop_scan_payload when call stop scan api (#20353) Signed-off-by: Shengwen Yu --- tests/apitests/python/test_project_permission.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/apitests/python/test_project_permission.py b/tests/apitests/python/test_project_permission.py index 1962c48e3..491eba2dc 100644 --- a/tests/apitests/python/test_project_permission.py +++ b/tests/apitests/python/test_project_permission.py @@ -89,8 +89,11 @@ copy_artifact = Permission("{}/projects/{}/repositories/target_repo/artifacts?fr delete_artifact = Permission("{}/projects/{}/repositories/target_repo/artifacts/{}".format(harbor_base_url, project_name, source_artifact_tag), "DELETE", 200) # 6. Resource scan actions: ['read', 'create', 'stop'] +stop_scan_payload = { + "scan_type": "vulnerability" +} create_scan = Permission("{}/projects/{}/repositories/{}/artifacts/{}/scan".format(harbor_base_url, project_name, source_artifact_name, source_artifact_tag), "POST", 202) -stop_scan = Permission("{}/projects/{}/repositories/{}/artifacts/{}/scan/stop".format(harbor_base_url, project_name, source_artifact_name, source_artifact_tag), "POST", 202) +stop_scan = Permission("{}/projects/{}/repositories/{}/artifacts/{}/scan/stop".format(harbor_base_url, project_name, source_artifact_name, source_artifact_tag), "POST", 202, stop_scan_payload) read_scan = Permission("{}/projects/{}/repositories/{}/artifacts/{}/scan/83be44fd-1234-5678-b49f-4b6d6e8f5730/log".format(harbor_base_url, project_name, source_artifact_name, source_artifact_tag), "get", 404) # 7. Resource tag actions: ['list', 'create', 'delete'] From dee73a44f30b4c60115157e53e3019ff758758b9 Mon Sep 17 00:00:00 2001 From: Lichao Xue <68891670+xuelichao@users.noreply.github.com> Date: Fri, 26 Apr 2024 14:56:23 +0800 Subject: [PATCH 114/145] Fix UI bugs (#20364) Signed-off-by: xuelichao --- .../system-robot-util.ts | 1 + .../artifact-additions.component.html | 11 ++++++- .../artifact-additions.component.spec.ts | 21 ++++++++++++ .../artifact-additions.component.ts | 33 ++++++++++++++----- .../artifact-sbom.component.html | 18 ++++++---- .../artifact-sbom.component.spec.ts | 1 - .../artifact-sbom/artifact-sbom.component.ts | 28 +++++----------- .../artifact-vulnerabilities.component.ts | 30 ++--------------- .../artifact-list-tab.component.html | 4 --- .../artifact-list-tab.component.ts | 2 +- .../project/repository/artifact/artifact.ts | 2 +- .../sbom-tip-histogram.component.ts | 4 +-- .../src/app/shared/units/shared.utils.ts | 11 ++++++- src/portal/src/i18n/lang/de-de-lang.json | 3 +- src/portal/src/i18n/lang/en-us-lang.json | 3 +- src/portal/src/i18n/lang/es-es-lang.json | 3 +- src/portal/src/i18n/lang/fr-fr-lang.json | 3 +- src/portal/src/i18n/lang/ko-kr-lang.json | 3 +- src/portal/src/i18n/lang/pt-br-lang.json | 3 +- src/portal/src/i18n/lang/tr-tr-lang.json | 3 +- src/portal/src/i18n/lang/zh-cn-lang.json | 3 +- src/portal/src/i18n/lang/zh-tw-lang.json | 3 +- 22 files changed, 110 insertions(+), 83 deletions(-) diff --git a/src/portal/src/app/base/left-side-nav/system-robot-accounts/system-robot-util.ts b/src/portal/src/app/base/left-side-nav/system-robot-accounts/system-robot-util.ts index f693753ad..6e3b4097c 100644 --- a/src/portal/src/app/base/left-side-nav/system-robot-accounts/system-robot-util.ts +++ b/src/portal/src/app/base/left-side-nav/system-robot-accounts/system-robot-util.ts @@ -78,6 +78,7 @@ export const ACTION_RESOURCE_I18N_MAP = { log: 'ROBOT_ACCOUNT.LOG', 'notification-policy': 'ROBOT_ACCOUNT.NOTIFICATION_POLICY', quota: 'ROBOT_ACCOUNT.QUOTA', + sbom: 'ROBOT_ACCOUNT.SBOM', }; export function convertKey(key: string) { diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.html b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.html index 99208d3f4..1a71ebf3a 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.html +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.html @@ -13,10 +13,13 @@ [clrIfActive]="currentTabLinkId === 'vulnerability'"> + @@ -50,6 +55,7 @@ [clrIfActive]="currentTabLinkId === 'build-history'"> @@ -67,6 +73,7 @@ [clrIfActive]="currentTabLinkId === 'summary-link'"> @@ -81,6 +88,7 @@ @@ -97,6 +105,7 @@ diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.spec.ts b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.spec.ts index 0f8a801e4..c4147cb57 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.spec.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.spec.ts @@ -4,6 +4,8 @@ import { AdditionLinks } from '../../../../../../../ng-swagger-gen/models/additi import { CURRENT_BASE_HREF } from '../../../../../shared/units/utils'; import { SharedTestingModule } from '../../../../../shared/shared.module'; import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ArtifactListPageService } from '../artifact-list-page/artifact-list-page.service'; +import { ClrLoadingState } from '@clr/angular'; describe('ArtifactAdditionsComponent', () => { const mockedAdditionLinks: AdditionLinks = { @@ -12,6 +14,18 @@ describe('ArtifactAdditionsComponent', () => { href: CURRENT_BASE_HREF + '/test', }, }; + const mockedArtifactListPageService = { + hasScannerSupportSBOM(): boolean { + return true; + }, + hasEnabledScanner(): boolean { + return true; + }, + getScanBtnState(): ClrLoadingState { + return ClrLoadingState.SUCCESS; + }, + init() {}, + }; let component: ArtifactAdditionsComponent; let fixture: ComponentFixture; @@ -20,6 +34,12 @@ describe('ArtifactAdditionsComponent', () => { imports: [SharedTestingModule], declarations: [ArtifactAdditionsComponent], schemas: [NO_ERRORS_SCHEMA], + providers: [ + { + provide: ArtifactListPageService, + useValue: mockedArtifactListPageService, + }, + ], }).compileComponents(); }); @@ -27,6 +47,7 @@ describe('ArtifactAdditionsComponent', () => { fixture = TestBed.createComponent(ArtifactAdditionsComponent); component = fixture.componentInstance; component.additionLinks = mockedAdditionLinks; + component.tab = 'vulnerability'; fixture.detectChanges(); }); diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.ts b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.ts index 45994ac8e..a0f5007b8 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-additions.component.ts @@ -10,7 +10,8 @@ import { ADDITIONS } from './models'; import { AdditionLinks } from '../../../../../../../ng-swagger-gen/models/addition-links'; import { AdditionLink } from '../../../../../../../ng-swagger-gen/models/addition-link'; import { Artifact } from '../../../../../../../ng-swagger-gen/models/artifact'; -import { ClrTabs } from '@clr/angular'; +import { ClrLoadingState, ClrTabs } from '@clr/angular'; +import { ArtifactListPageService } from '../artifact-list-page/artifact-list-page.service'; @Component({ selector: 'artifact-additions', @@ -32,14 +33,21 @@ export class ArtifactAdditionsComponent implements AfterViewChecked, OnInit { @Input() tab: string; - @Input() currentTabLinkId: string = 'vulnerability'; + @Input() currentTabLinkId: string = ''; activeTab: string = null; @ViewChild('additionsTab') tabs: ClrTabs; - constructor(private ref: ChangeDetectorRef) {} + constructor( + private ref: ChangeDetectorRef, + private artifactListPageService: ArtifactListPageService + ) {} ngOnInit(): void { this.activeTab = this.tab; + if (!this.activeTab) { + this.currentTabLinkId = 'vulnerability'; + } + this.artifactListPageService.init(this.projectId); } ngAfterViewChecked() { @@ -50,6 +58,10 @@ export class ArtifactAdditionsComponent implements AfterViewChecked, OnInit { this.ref.detectChanges(); } + hasScannerSupportSBOM(): boolean { + return this.artifactListPageService.hasScannerSupportSBOM(); + } + getVulnerability(): AdditionLink { if ( this.additionLinks && @@ -59,12 +71,7 @@ export class ArtifactAdditionsComponent implements AfterViewChecked, OnInit { } return null; } - getSbom(): AdditionLink { - if (this.additionLinks && this.additionLinks[ADDITIONS.SBOMS]) { - return this.additionLinks[ADDITIONS.SBOMS]; - } - return {}; - } + getBuildHistory(): AdditionLink { if (this.additionLinks && this.additionLinks[ADDITIONS.BUILD_HISTORY]) { return this.additionLinks[ADDITIONS.BUILD_HISTORY]; @@ -93,4 +100,12 @@ export class ArtifactAdditionsComponent implements AfterViewChecked, OnInit { actionTab(tab: string): void { this.currentTabLinkId = tab; } + + getScanBtnState(): ClrLoadingState { + return this.artifactListPageService.getScanBtnState(); + } + + hasEnabledScanner(): boolean { + return this.artifactListPageService.hasEnabledScanner(); + } } diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.html b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.html index c7b9cf8a6..577711f33 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.html +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.html @@ -32,12 +32,18 @@
- {{ - 'SBOM.GRID.COLUMN_PACKAGE' | translate - }} - {{ - 'SBOM.GRID.COLUMN_VERSION' | translate - }} + {{ 'SBOM.GRID.COLUMN_PACKAGE' | translate }} + {{ 'SBOM.GRID.COLUMN_VERSION' | translate }} {{ 'SBOM.GRID.COLUMN_LICENSE' | translate }} diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.spec.ts b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.spec.ts index e3978ad39..09e68430a 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.spec.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.spec.ts @@ -10,7 +10,6 @@ import { } from '@ngx-translate/core'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { UserPermissionService } from '../../../../../../shared/services'; -import { AdditionLink } from '../../../../../../../../ng-swagger-gen/models/addition-link'; import { ErrorHandler } from '../../../../../../shared/units/error-handler'; import { SessionService } from '../../../../../../shared/services/session.service'; import { SessionUser } from '../../../../../../shared/entities/session-user'; diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.ts b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.ts index ac352ff0f..c37ee3c16 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-sbom/artifact-sbom.component.ts @@ -1,13 +1,6 @@ -import { - AfterViewInit, - Component, - Input, - OnDestroy, - OnInit, -} from '@angular/core'; +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { ClrDatagridStateInterface, ClrLoadingState } from '@clr/angular'; import { finalize } from 'rxjs/operators'; -import { AdditionLink } from '../../../../../../../../ng-swagger-gen/models/addition-link'; import { ScannerVo, UserPermissionService, @@ -30,7 +23,6 @@ import { HarborEvent, } from '../../../../../../services/event-service/event.service'; import { severityText } from '../../../../../left-side-nav/interrogation-services/vulnerability-database/security-hub.interface'; -import { AppConfigService } from 'src/app/services/app-config.service'; import { ArtifactSbom, @@ -38,8 +30,7 @@ import { getArtifactSbom, } from '../../artifact'; import { ArtifactService } from 'ng-swagger-gen/services'; -import { ScanTypes } from 'src/app/shared/entities/shared.const'; -import { ArtifactListPageService } from '../../artifact-list-page/artifact-list-page.service'; +import { ScanTypes } from '../../../../../../shared/entities/shared.const'; @Component({ selector: 'hbr-artifact-sbom', @@ -56,13 +47,12 @@ export class ArtifactSbomComponent implements OnInit, OnDestroy { @Input() sbomDigest: string; @Input() artifact: Artifact; + @Input() hasScannerSupportSBOM: boolean = false; artifactSbom: ArtifactSbom; loading: boolean = false; - hasScannerSupportSBOM: boolean = false; downloadSbomBtnState: ClrLoadingState = ClrLoadingState.DEFAULT; hasSbomPermission: boolean = false; - hasShowLoading: boolean = false; sub: Subscription; hasViewInitWithDelay: boolean = false; @@ -73,16 +63,13 @@ export class ArtifactSbomComponent implements OnInit, OnDestroy { readonly severityText = severityText; constructor( private errorHandler: ErrorHandler, - private appConfigService: AppConfigService, private artifactService: ArtifactService, - private artifactListPageService: ArtifactListPageService, private userPermissionService: UserPermissionService, private eventService: EventService, private session: SessionService ) {} ngOnInit() { - this.artifactListPageService.init(this.projectId); this.getSbom(); this.getSbomPermission(); if (!this.sub) { @@ -222,8 +209,6 @@ export class ArtifactSbomComponent implements OnInit, OnDestroy { } canDownloadSbom(): boolean { - this.hasScannerSupportSBOM = - this.artifactListPageService.hasScannerSupportSBOM(); return ( this.hasScannerSupportSBOM && //this.hasSbomPermission && @@ -234,7 +219,12 @@ export class ArtifactSbomComponent implements OnInit, OnDestroy { } artifactSbomPackages(): ArtifactSbomPackageItem[] { - return this.artifactSbom?.sbomPackage?.packages ?? []; + return ( + this.artifactSbom?.sbomPackage?.packages?.filter( + item => + item?.name || item?.versionInfo || item?.licenseConcluded + ) ?? [] + ); } load(state: ClrDatagridStateInterface) { diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-vulnerabilities/artifact-vulnerabilities.component.ts b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-vulnerabilities/artifact-vulnerabilities.component.ts index 02ad708ea..9d83d167c 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-vulnerabilities/artifact-vulnerabilities.component.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-additions/artifact-vulnerabilities/artifact-vulnerabilities.component.ts @@ -50,14 +50,13 @@ export class ArtifactVulnerabilitiesComponent implements OnInit, OnDestroy { @Input() digest: string; @Input() artifact: Artifact; + @Input() scanBtnState: ClrLoadingState = ClrLoadingState.DEFAULT; + @Input() hasEnabledScanner: boolean = false; scan_overview: any; scanner: ScannerVo; - projectScanner: ScannerVo; scanningResults: VulnerabilityItem[] = []; loading: boolean = false; - hasEnabledScanner: boolean = false; - scanBtnState: ClrLoadingState = ClrLoadingState.DEFAULT; severitySort: ClrDatagridComparatorInterface; cvssSort: ClrDatagridComparatorInterface; hasScanningPermission: boolean = false; @@ -112,7 +111,6 @@ export class ArtifactVulnerabilitiesComponent implements OnInit, OnDestroy { ngOnInit() { this.getVulnerabilities(); this.getScanningPermission(); - this.getProjectScanner(); if (!this.sub) { this.sub = this.eventService.subscribe( HarborEvent.UPDATE_VULNERABILITY_INFO, @@ -203,30 +201,6 @@ export class ArtifactVulnerabilitiesComponent implements OnInit, OnDestroy { ); } - getProjectScanner(): void { - this.hasEnabledScanner = false; - this.scanBtnState = ClrLoadingState.LOADING; - this.scanningService.getProjectScanner(this.projectId).subscribe( - response => { - if ( - response && - '{}' !== JSON.stringify(response) && - !response.disabled && - response.health === 'healthy' - ) { - this.scanBtnState = ClrLoadingState.SUCCESS; - this.hasEnabledScanner = true; - } else { - this.scanBtnState = ClrLoadingState.ERROR; - } - this.projectScanner = response; - }, - error => { - this.scanBtnState = ClrLoadingState.ERROR; - } - ); - } - getLevel(v: VulnerabilityItem): number { if (v && v.severity && SEVERITY_LEVEL_MAP[v.severity]) { return SEVERITY_LEVEL_MAP[v.severity]; diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.html b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.html index d523b67c1..f8d10c908 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.html +++ b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.html @@ -65,10 +65,6 @@ class="action-dropdown" clrPosition="bottom-left" *clrIfOpen> -