From a98711c0fc11c99f2952e126924d279a8d7c8d37 Mon Sep 17 00:00:00 2001 From: Chlins Zhang Date: Wed, 31 May 2023 14:54:52 +0800 Subject: [PATCH 01/38] fix: clean up scan executions and reports after deleting artifact (#18693) Cleanup the associated resources(scan executions and scan reports) after deletion of artifact. Fixes: #18634 Signed-off-by: chlins --- src/controller/event/handler/init.go | 1 + .../event/handler/internal/artifact.go | 39 +++++++++++++++++++ .../event/handler/internal/artifact_test.go | 16 +++++++- 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/controller/event/handler/init.go b/src/controller/event/handler/init.go index b74c924ddb2..06dda44d510 100644 --- a/src/controller/event/handler/init.go +++ b/src/controller/event/handler/init.go @@ -70,6 +70,7 @@ func init() { // internal _ = notifier.Subscribe(event.TopicPullArtifact, &internal.Handler{}) _ = notifier.Subscribe(event.TopicPushArtifact, &internal.Handler{}) + _ = notifier.Subscribe(event.TopicDeleteArtifact, &internal.Handler{}) _ = task.RegisterTaskStatusChangePostFunc(job.ReplicationVendorType, func(ctx context.Context, taskID int64, status string) error { notification.AddEvent(ctx, &metadata.ReplicationMetaData{ diff --git a/src/controller/event/handler/internal/artifact.go b/src/controller/event/handler/internal/artifact.go index 284934d5ece..d07f2f5e4dc 100644 --- a/src/controller/event/handler/internal/artifact.go +++ b/src/controller/event/handler/internal/artifact.go @@ -27,10 +27,13 @@ import ( "github.com/goharbor/harbor/src/controller/event" "github.com/goharbor/harbor/src/controller/repository" "github.com/goharbor/harbor/src/controller/tag" + "github.com/goharbor/harbor/src/jobservice/job" "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/orm" "github.com/goharbor/harbor/src/lib/q" + "github.com/goharbor/harbor/src/pkg/scan/report" + "github.com/goharbor/harbor/src/pkg/task" ) const ( @@ -62,6 +65,11 @@ func init() { // Handler preprocess artifact event data type Handler struct { + // execMgr for managing executions + execMgr task.ExecutionManager + // reportMgr for managing scan reports + reportMgr report.Manager + once sync.Once // pullCountStore caches the pull count group by repository // map[repositoryID]counts @@ -87,6 +95,8 @@ func (a *Handler) Handle(ctx context.Context, value interface{}) error { return a.onPull(ctx, v.ArtifactEvent) case *event.PushArtifactEvent: return a.onPush(ctx, v.ArtifactEvent) + case *event.DeleteArtifactEvent: + return a.onDelete(ctx, v.ArtifactEvent) default: log.Errorf("Can not handler this event type! %#v", v) } @@ -244,6 +254,35 @@ func (a *Handler) onPush(ctx context.Context, event *event.ArtifactEvent) error return nil } +func (a *Handler) onDelete(ctx context.Context, event *event.ArtifactEvent) error { + execMgr := task.ExecMgr + reportMgr := report.Mgr + // for UT mock + if a.execMgr != nil { + execMgr = a.execMgr + } + if a.reportMgr != nil { + reportMgr = a.reportMgr + } + + // clean up the scan executions of this artifact by id + if err := execMgr.DeleteByVendor(ctx, job.ImageScanJobVendorType, event.Artifact.ID); err != nil { + log.Errorf("failed to delete scan executions of artifact %d, error: %v", event.Artifact.ID, err) + } + // clean up the scan reports of this artifact and it's references by digest + digests := []string{event.Artifact.Digest} + if len(event.Artifact.References) > 0 { + for _, ref := range event.Artifact.References { + digests = append(digests, ref.ChildDigest) + } + } + if err := reportMgr.DeleteByDigests(ctx, digests...); err != nil { + log.Errorf("failed to delete scan reports of artifact %v, error: %v", digests, err) + } + + return nil +} + // isScannerUser check if the current user is a scanner user by its prefix // usually a scanner user should be named like `robot$+--` // verify it by the prefix `robot$+` diff --git a/src/controller/event/handler/internal/artifact_test.go b/src/controller/event/handler/internal/artifact_test.go index 1a2a56af7f3..9824ec7f20f 100644 --- a/src/controller/event/handler/internal/artifact_test.go +++ b/src/controller/event/handler/internal/artifact_test.go @@ -36,6 +36,8 @@ import ( tagmodel "github.com/goharbor/harbor/src/pkg/tag/model/tag" scannerCtlMock "github.com/goharbor/harbor/src/testing/controller/scanner" projectMock "github.com/goharbor/harbor/src/testing/pkg/project" + reportMock "github.com/goharbor/harbor/src/testing/pkg/scan/report" + taskMock "github.com/goharbor/harbor/src/testing/pkg/task" ) // ArtifactHandlerTestSuite is test suite for artifact handler. @@ -46,6 +48,8 @@ type ArtifactHandlerTestSuite struct { handler *Handler projectManager project.Manager scannerCtl scanner.Controller + reportMgr *reportMock.Manager + execMgr *taskMock.ExecutionManager } // TestArtifactHandler tests ArtifactHandler. @@ -57,10 +61,12 @@ func TestArtifactHandler(t *testing.T) { func (suite *ArtifactHandlerTestSuite) SetupSuite() { common_dao.PrepareTestForPostgresSQL() config.Init() - suite.handler = &Handler{} suite.ctx = orm.NewContext(context.TODO(), beegoorm.NewOrm()) suite.projectManager = &projectMock.Manager{} suite.scannerCtl = &scannerCtlMock.Controller{} + suite.execMgr = &taskMock.ExecutionManager{} + suite.reportMgr = &reportMock.Manager{} + suite.handler = &Handler{execMgr: suite.execMgr, reportMgr: suite.reportMgr} // mock artifact _, err := pkg.ArtifactMgr.Create(suite.ctx, &artifact.Artifact{ID: 1, RepositoryID: 1}) @@ -152,6 +158,14 @@ func (suite *ArtifactHandlerTestSuite) TestOnPull() { }, 3*asyncFlushDuration, asyncFlushDuration/2, "wait for pull_count async update") } +func (suite *ArtifactHandlerTestSuite) TestOnDelete() { + evt := &event.ArtifactEvent{Artifact: &artifact.Artifact{ID: 1, RepositoryID: 1, Digest: "mock-digest", References: []*artifact.Reference{{ChildDigest: "ref-1"}, {ChildDigest: "ref-2"}}}} + suite.execMgr.On("DeleteByVendor", suite.ctx, "IMAGE_SCAN", int64(1)).Return(nil).Times(1) + suite.reportMgr.On("DeleteByDigests", suite.ctx, "mock-digest", "ref-1", "ref-2").Return(nil).Times(1) + err := suite.handler.onDelete(suite.ctx, evt) + suite.Nil(err, "onDelete should return nil") +} + func (suite *ArtifactHandlerTestSuite) TestIsScannerUser() { type args struct { prefix string From 97c1fdcd8e1e6d29e14bb13c45438c04da42f9f1 Mon Sep 17 00:00:00 2001 From: Yang Jiao <72076317+YangJiao0817@users.noreply.github.com> Date: Thu, 1 Jun 2023 16:34:40 +0800 Subject: [PATCH 02/38] Add Referrers API testcase (#18775) Fix #18617 Signed-off-by: Yang Jiao --- tests/apitests/python/library/cosign.py | 11 ++ .../apitests/python/library/referrers_api.py | 12 ++ tests/apitests/python/test_referrers_api.py | 117 ++++++++++++++++++ tests/files/sbom_test.json | 46 +++++++ tests/robot-cases/Group0-BAT/API_DB.robot | 4 + 5 files changed, 190 insertions(+) create mode 100644 tests/apitests/python/library/referrers_api.py create mode 100644 tests/apitests/python/test_referrers_api.py create mode 100644 tests/files/sbom_test.json diff --git a/tests/apitests/python/library/cosign.py b/tests/apitests/python/library/cosign.py index df3c4cdf46e..7638b19033a 100644 --- a/tests/apitests/python/library/cosign.py +++ b/tests/apitests/python/library/cosign.py @@ -1,10 +1,21 @@ # -*- coding: utf-8 -*- import base +import os def generate_key_pair(): + config_key_file = "cosign.key" + config_pub_file = "cosign.pub" + if os.path.exists(config_key_file) and os.path.exists(config_pub_file): + os.remove(config_key_file) + os.remove(config_pub_file) command = ["cosign", "generate-key-pair"] base.run_command(command) def sign_artifact(artifact): command = ["cosign", "sign", "-y", "--allow-insecure-registry", "--key", "cosign.key", artifact] base.run_command(command) + +def push_artifact_sbom(artifact, sbom_path, type="spdx"): + command = ["cosign", "attach", "sbom", "--allow-insecure-registry", "--registry-referrers-mode", "oci-1-1", + "--type", type, "--sbom", sbom_path, artifact] + base.run_command(command) diff --git a/tests/apitests/python/library/referrers_api.py b/tests/apitests/python/library/referrers_api.py new file mode 100644 index 00000000000..5d51a6d5309 --- /dev/null +++ b/tests/apitests/python/library/referrers_api.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +import requests + +def call(server, project_name, repo_name, digest, artifactType=None, **kwargs): + url=None + auth = (kwargs.get("username"), kwargs.get("password")) + if artifactType: + artifactType = artifactType.replace("+", "%2B") + url="https://{}/v2/{}/{}/referrers/{}?artifactType={}".format(server, project_name, repo_name, digest, artifactType) + else: + url="https://{}/v2/{}/{}/referrers/{}".format(server, project_name, repo_name, digest) + return requests.get(url, auth=auth, verify=False) diff --git a/tests/apitests/python/test_referrers_api.py b/tests/apitests/python/test_referrers_api.py new file mode 100644 index 00000000000..f1bd69f9209 --- /dev/null +++ b/tests/apitests/python/test_referrers_api.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import +import unittest + +from testutils import harbor_server, files_directory, ADMIN_CLIENT, suppress_urllib3_warning +from library import cosign, referrers_api +from library.project import Project +from library.user import User +from library.artifact import Artifact +from library.repository import push_self_build_image_to_project +from library import docker_api + +class TestReferrersApi(unittest.TestCase): + + @suppress_urllib3_warning + def setUp(self): + self.project= Project() + self.user= User() + self.artifact = Artifact() + self.image = "artifact_test" + self.tag = "dev" + self.sbom_path = files_directory + "sbom_test.json" + self.sbom_artifact_type = "application/vnd.dev.cosign.artifact.sbom.v1+json" + self.signature_artifact_type = "application/vnd.oci.image.config.v1+json" + + def testReferrersApi(self): + """ + Test case: + Referrers Api + Test step and expected result: + 1. Create a new user(UA); + 2. Create a new project(PA) by user(UA); + 3. Push a new image(IA) in project(PA) by user(UA); + 4. Push image(IA) SBOM to project(PA) by user(UA); + 5. Sign image(IA) with cosign; + 6. Sign image(IA) SBOM with cosign; + 7. Call the referrers api successfully; + 8. Call the referrers api and filter artifact_type; + Tear down: + 1. Delete project(PA); + 2. Delete user(UA). + """ + url = ADMIN_CLIENT["endpoint"] + user_password = "Aa123456" + + # 1. Create user(UA) + _, user_name = self.user.create_user(user_password = user_password, **ADMIN_CLIENT) + user_client = dict(endpoint = url, username = user_name, password = user_password, with_accessory = True) + + # 2. Create private project(PA) by user(UA) + _, project_name = self.project.create_project(metadata = {"public": "false"}, **user_client) + + # 3. Push a new image(IA) in project(PA) by user(UA) + push_self_build_image_to_project(project_name, harbor_server, user_name, user_password, self.image, self.tag) + + # 4. Push image(IA) SBOM to project(PA) by user(UA) + docker_api.docker_login_cmd(harbor_server, user_name, user_password, enable_manifest = False) + cosign.push_artifact_sbom("{}/{}/{}:{}".format(harbor_server, project_name, self.image, self.tag), self.sbom_path) + artifact_info = self.artifact.get_reference_info(project_name, self.image, self.tag, **user_client) + artifact_digest = artifact_info.digest + sbom_digest = artifact_info.accessories[0].digest + + # 5. Sign image(IA) with cosign + cosign.generate_key_pair() + cosign.sign_artifact("{}/{}/{}:{}".format(harbor_server, project_name, self.image, self.tag)) + artifact_info = self.artifact.get_reference_info(project_name, self.image, self.tag, **user_client) + self.assertEqual(len(artifact_info.accessories), 2) + signature_digest = None + for accessory in artifact_info.accessories: + if accessory.digest != sbom_digest: + signature_digest = accessory.digest + break + + # 6. Sign image(IA) SBOM cosign + cosign.sign_artifact("{}/{}/{}@{}".format(harbor_server, project_name, self.image, sbom_digest)) + + # 7. Call the referrers api successfully + res_json = referrers_api.call(harbor_server, project_name, self.image, artifact_digest, **user_client).json() + self.assertEqual(len(res_json["manifests"]), 2) + for manifest in res_json["manifests"]: + self.assertIn(manifest["digest"], [signature_digest, sbom_digest]) + self.assertIn(manifest["artifactType"], [self.signature_artifact_type, self.sbom_artifact_type]) + self.assertIsNotNone(manifest["mediaType"]) + self.assertIsNotNone(manifest["size"]) + + res_json = referrers_api.call(harbor_server, project_name, self.image, sbom_digest, **user_client).json() + self.assertEqual(len(res_json["manifests"]), 1) + manifest = res_json["manifests"][0] + self.assertIsNotNone(manifest["digest"]) + self.assertIsNotNone(manifest["artifactType"], [self.signature_artifact_type, self.sbom_artifact_type]) + self.assertIsNotNone(manifest["mediaType"]) + self.assertIsNotNone(manifest["size"]) + + # 8. Call the referrers api and filter artifact_type + res = referrers_api.call(harbor_server, project_name, self.image, artifact_digest, self.sbom_artifact_type, **user_client) + self.assertEqual(res.headers["Oci-Filters-Applied"], "artifactType") + res_json = res.json() + self.assertEqual(len(res_json["manifests"]), 1) + manifest = res_json["manifests"][0] + self.assertEqual(manifest["digest"], sbom_digest) + self.assertIn(manifest["artifactType"], self.sbom_artifact_type) + self.assertIsNotNone(manifest["mediaType"]) + self.assertIsNotNone(manifest["size"]) + + res = referrers_api.call(harbor_server, project_name, self.image, artifact_digest, self.signature_artifact_type, **user_client) + self.assertEqual(res.headers["Oci-Filters-Applied"], "artifactType") + res_json = res.json() + self.assertEqual(len(res_json["manifests"]), 1) + manifest = res_json["manifests"][0] + self.assertEqual(manifest["digest"], signature_digest) + self.assertIn(manifest["artifactType"], self.signature_artifact_type) + self.assertIsNotNone(manifest["mediaType"]) + self.assertIsNotNone(manifest["size"]) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/files/sbom_test.json b/tests/files/sbom_test.json new file mode 100644 index 00000000000..aa26ace0775 --- /dev/null +++ b/tests/files/sbom_test.json @@ -0,0 +1,46 @@ +{ + "SPDXID": "SPDXRef-DOCUMENT", + "creationInfo": { + "created": "2023-05-31T07:43:35.672590648Z", + "creators": [ + "Tool: trivy", + "Organization: aquasecurity" + ] + }, + "dataLicense": "CC0-1.0", + "documentDescribes": [ + "SPDXRef-ContainerImage-8e8b2798af13ee89" + ], + "documentNamespace": "http://aquasecurity.github.io/trivy/container_image/artifact_test:dev-9648faf4-428e-46ad-a9e6-1d3f84c6f297", + "name": "artifact_test:dev", + "packages": [ + { + "SPDXID": "SPDXRef-ContainerImage-8e8b2798af13ee89", + "attributionTexts": [ + "SchemaVersion: 2", + "ImageID: sha256:9517d37fc3457e070719ed8dead2a9134dd9d5126dd6ef55d15685337c3dc711", + "RepoDigest: 10.202.250.222/test02/test@sha256:0feefb1b81993c1299a7f75a2c86d7cfed4b25859037657377d563d955e8e20f", + "DiffID: sha256:6b245f040973e14e29f371ff2b4059c84ab2de8b7b9a04bc21fd4a7b0a72c446", + "DiffID: sha256:6491a698ac806b35795f23098e169f50b2b6f179900b5625e5677cb1df2651ca", + "RepoTag: artifact_test:dev" + ], + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceLocator": "pkg:oci/test@sha256:0feefb1b81993c1299a7f75a2c86d7cfed4b25859037657377d563d955e8e20f?repository_url=10.202.250.222%2Ftest02%2Ftest\u0026arch=amd64", + "referenceType": "purl" + } + ], + "filesAnalyzed": false, + "name": "artifact_test:dev" + } + ], + "relationships": [ + { + "relatedSpdxElement": "SPDXRef-ContainerImage-8e8b2798af13ee89", + "relationshipType": "DESCRIBE", + "spdxElementId": "SPDXRef-DOCUMENT" + } + ], + "spdxVersion": "SPDX-2.2" +} \ 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 022e3f2aa10..ed9da3957a6 100644 --- a/tests/robot-cases/Group0-BAT/API_DB.robot +++ b/tests/robot-cases/Group0-BAT/API_DB.robot @@ -182,3 +182,7 @@ Test Case - Job Service Dashboard Test Case - Retain Image Last Pull Time [Tags] retain_image_last_pull_time Harbor API Test ./tests/apitests/python/test_retain_image_last_pull_time.py + +Test Case - Referrers API + [Tags] referrers + Harbor API Test ./tests/apitests/python/test_referrers_api.py From 680c78d3683822c9922f4f4f4dc02561c7ed01f3 Mon Sep 17 00:00:00 2001 From: Wang Yan Date: Fri, 2 Jun 2023 17:33:09 +0800 Subject: [PATCH 03/38] add more details in gc history (#18779) Show more infors in the gc history, like the sweep size and how many blobs and manifests were removed by GC. Signed-off-by: Wang Yan --- src/controller/gc/callback.go | 45 ++++++++++++++++++- src/controller/gc/callback_test.go | 44 ++++++++++++++++++ .../job/impl/gc/garbage_collection.go | 28 +++++++++++- .../job/impl/gc/garbage_collection_test.go | 10 +++++ 4 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 src/controller/gc/callback_test.go diff --git a/src/controller/gc/callback.go b/src/controller/gc/callback.go index d8f33321c5a..04ef73d835c 100644 --- a/src/controller/gc/callback.go +++ b/src/controller/gc/callback.go @@ -35,7 +35,11 @@ func init() { } if err := task.RegisterTaskStatusChangePostFunc(job.GarbageCollectionVendorType, gcTaskStatusChange); err != nil { - log.Fatalf("failed to register the task status change post for the gc job, error %v", err) + log.Fatalf("failed to register the task status change post for the garbage collection job, error %v", err) + } + + if err := task.RegisterCheckInProcessor(job.GarbageCollectionVendorType, gcCheckIn); err != nil { + log.Fatalf("failed to register the checkin processor for the garbage collection job, error %v", err) } } @@ -60,3 +64,42 @@ func gcTaskStatusChange(ctx context.Context, taskID int64, status string) error return nil } + +func gcCheckIn(ctx context.Context, t *task.Task, sc *job.StatusChange) error { + taskID := t.ID + status := t.Status + + log.Infof("received garbage collection task status update event: task-%d, status-%s", taskID, status) + if sc.CheckIn != "" { + var gcObj struct { + SweepSize int64 `json:"freed_space"` + Blobs int64 `json:"purged_blobs"` + Manifests int64 `json:"purged_manifests"` + } + if err := json.Unmarshal([]byte(sc.CheckIn), &gcObj); err != nil { + log.Errorf("failed to resolve checkin of garbage collection task %d: %v", taskID, err) + + return err + } + t, err := task.Mgr.Get(ctx, taskID) + if err != nil { + return err + } + + e, err := task.ExecMgr.Get(ctx, t.ExecutionID) + if err != nil { + return err + } + + e.ExtraAttrs["freed_space"] = gcObj.SweepSize + e.ExtraAttrs["purged_blobs"] = gcObj.Blobs + e.ExtraAttrs["purged_manifests"] = gcObj.Manifests + + err = task.ExecMgr.UpdateExtraAttrs(ctx, e.ID, e.ExtraAttrs) + if err != nil { + log.G(ctx).WithField("error", err).Errorf("failed to update of garbage collection task %d", taskID) + return err + } + } + return nil +} diff --git a/src/controller/gc/callback_test.go b/src/controller/gc/callback_test.go new file mode 100644 index 00000000000..5f719407095 --- /dev/null +++ b/src/controller/gc/callback_test.go @@ -0,0 +1,44 @@ +package gc + +import ( + "context" + "testing" + + "github.com/goharbor/harbor/src/jobservice/job" + "github.com/goharbor/harbor/src/pkg/task" + "github.com/goharbor/harbor/src/testing/mock" + tasktesting "github.com/goharbor/harbor/src/testing/pkg/task" + "github.com/stretchr/testify/suite" +) + +type callbackTestSuite struct { + suite.Suite + execMgr *tasktesting.ExecutionManager + taskMgr *tasktesting.Manager +} + +func (c *callbackTestSuite) SetupTest() { + c.execMgr = &tasktesting.ExecutionManager{} + c.taskMgr = &tasktesting.Manager{} +} + +func (c *callbackTestSuite) TestCheckIn() { + t := &task.Task{ + ID: 1, + Status: "Success", + } + + sc := &job.StatusChange{ + CheckIn: "", + } + + c.taskMgr.On("Get", mock.Anything, int64(1)).Return(&task.Task{ID: 1, ExecutionID: 1}, nil) + c.execMgr.On("Get", mock.Anything, mock.Anything).Return(&task.Execution{ID: 1}, nil) + c.execMgr.On("UpdateExtraAttrs", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + gcCheckIn(context.Background(), t, sc) +} + +func TestCallBackTestSuite(t *testing.T) { + suite.Run(t, &callbackTestSuite{}) +} diff --git a/src/jobservice/job/impl/gc/garbage_collection.go b/src/jobservice/job/impl/gc/garbage_collection.go index aba2990f0d7..615f9e6c741 100644 --- a/src/jobservice/job/impl/gc/garbage_collection.go +++ b/src/jobservice/job/impl/gc/garbage_collection.go @@ -15,6 +15,7 @@ package gc import ( + "encoding/json" "os" "time" @@ -255,8 +256,8 @@ func (gc *GarbageCollector) mark(ctx job.Context) error { func (gc *GarbageCollector) sweep(ctx job.Context) error { gc.logger = ctx.GetLogger() sweepSize := int64(0) - blobCnt := 0 - mfCnt := 0 + blobCnt := int64(0) + mfCnt := int64(0) total := len(gc.deleteSet) for i, blob := range gc.deleteSet { if gc.shouldStop(ctx) { @@ -413,6 +414,11 @@ func (gc *GarbageCollector) sweep(ctx job.Context) error { } gc.logger.Infof("%d blobs and %d manifests are actually deleted", blobCnt, mfCnt) gc.logger.Infof("The GC job actual frees up %d MB space.", sweepSize/1024/1024) + + if err := saveGCRes(ctx, sweepSize, blobCnt, mfCnt); err != nil { + gc.logger.Errorf("failed to save the garbage collection results, errMsg=%v", err) + } + return nil } @@ -647,3 +653,21 @@ func (gc *GarbageCollector) shouldStop(ctx job.Context) bool { } return false } + +func saveGCRes(ctx job.Context, sweepSize, blobs, manifests int64) error { + gcObj := struct { + SweepSize int64 `json:"freed_space"` + Blobs int64 `json:"purged_blobs"` + Manifests int64 `json:"purged_manifests"` + }{ + SweepSize: sweepSize, + Blobs: blobs, + Manifests: manifests, + } + c, err := json.Marshal(gcObj) + if err != nil { + return err + } + _ = ctx.Checkin(string(c)) + return nil +} diff --git a/src/jobservice/job/impl/gc/garbage_collection_test.go b/src/jobservice/job/impl/gc/garbage_collection_test.go index e92b73ce121..3c78be42342 100644 --- a/src/jobservice/job/impl/gc/garbage_collection_test.go +++ b/src/jobservice/job/impl/gc/garbage_collection_test.go @@ -217,6 +217,7 @@ func (suite *gcTestSuite) TestRun() { ctx.On("GetLogger").Return(logger) ctx.On("OPCommand").Return(job.NilCommand, true) mock.OnAnything(ctx, "Get").Return("core url", true) + mock.OnAnything(ctx, "Checkin").Return(nil) suite.artifactCtl.On("List").Return([]*artifact.Artifact{ { @@ -357,6 +358,7 @@ func (suite *gcTestSuite) TestSweep() { logger := &mockjobservice.MockJobLogger{} ctx.On("GetLogger").Return(logger) ctx.On("OPCommand").Return(job.NilCommand, false) + mock.OnAnything(ctx, "Checkin").Return(nil) mock.OnAnything(suite.blobMgr, "UpdateBlobStatus").Return(int64(1), nil) mock.OnAnything(suite.blobMgr, "Delete").Return(nil) @@ -378,6 +380,14 @@ func (suite *gcTestSuite) TestSweep() { suite.Nil(gc.sweep(ctx)) } +func (suite *gcTestSuite) TestSaveRes() { + ctx := &mockjobservice.MockJobContext{} + logger := &mockjobservice.MockJobLogger{} + ctx.On("GetLogger").Return(logger) + mock.OnAnything(ctx, "Checkin").Return(nil) + suite.Nil(saveGCRes(ctx, 123456, 100, 100)) +} + func TestGCTestSuite(t *testing.T) { t.Setenv("UTTEST", "true") suite.Run(t, &gcTestSuite{}) From 9d28d1f43f3c73dc71d71ea95c4e9b4aad41501d Mon Sep 17 00:00:00 2001 From: sll552 Date: Fri, 2 Jun 2023 14:30:03 +0200 Subject: [PATCH 04/38] Remove wrong format for boolean value in api definition (#18783) type: boolean cannot be used with a format of int32 Signed-off-by: sll552 --- api/v2.0/swagger.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index 407699a5ece..de635c98128 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -291,7 +291,6 @@ paths: description: The project is public or private. required: false type: boolean - format: int32 - name: owner in: query description: The name of project owner. From fbeeaa7537853d6d7a936aa180714c9213d64f19 Mon Sep 17 00:00:00 2001 From: Chlins Zhang Date: Mon, 5 Jun 2023 15:12:54 +0800 Subject: [PATCH 05/38] fix: add checkpoint when enqueue scan tasks for scan all (#18680) Fix the scanAll cannot be stopped in case of large number of artifacts, add the checkpoint before submit scan tasks, mark the scanAll stopped flag in the redis. Fixes: #18044 Signed-off-by: chlins --- src/controller/artifact/helper.go | 8 ++- src/controller/scan/base_controller.go | 63 ++++++++++++++++++++- src/controller/scan/base_controller_test.go | 37 +++++++++--- src/controller/scan/callback_test.go | 2 +- src/controller/scan/controller.go | 10 ++++ src/server/v2.0/handler/scan_all.go | 11 ++-- src/server/v2.0/handler/scan_all_test.go | 1 + src/testing/controller/scan/controller.go | 14 +++++ 8 files changed, 127 insertions(+), 19 deletions(-) diff --git a/src/controller/artifact/helper.go b/src/controller/artifact/helper.go index 3a5d798e2ee..d5602dad822 100644 --- a/src/controller/artifact/helper.go +++ b/src/controller/artifact/helper.go @@ -40,7 +40,13 @@ func Iterator(ctx context.Context, chunkSize int, query *q.Query, option *Option } for _, artifact := range artifacts { - ch <- artifact + select { + case <-ctx.Done(): + log.G(ctx).Errorf("context done, list artifacts exited, error: %v", ctx.Err()) + return + case ch <- artifact: + continue + } } if len(artifacts) < chunkSize { diff --git a/src/controller/scan/base_controller.go b/src/controller/scan/base_controller.go index 2a2e94b6e98..bc504597276 100644 --- a/src/controller/scan/base_controller.go +++ b/src/controller/scan/base_controller.go @@ -21,6 +21,7 @@ import ( "reflect" "strings" "sync" + "time" "github.com/google/uuid" @@ -30,6 +31,7 @@ import ( sc "github.com/goharbor/harbor/src/controller/scanner" "github.com/goharbor/harbor/src/controller/tag" "github.com/goharbor/harbor/src/jobservice/job" + "github.com/goharbor/harbor/src/lib/cache" "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/log" @@ -50,8 +52,12 @@ import ( "github.com/goharbor/harbor/src/pkg/task" ) -// DefaultController is a default singleton scan API controller. -var DefaultController = NewController() +var ( + // DefaultController is a default singleton scan API controller. + DefaultController = NewController() + + errScanAllStopped = errors.New("scanAll stopped") +) // const definitions const ( @@ -74,6 +80,9 @@ type uuidGenerator func() (string, error) // utility methods. type configGetter func(cfg string) (string, error) +// cacheGetter returns cache +type cacheGetter func() cache.Cache + // launchScanJobParam is a param to launch scan job. type launchScanJobParam struct { ExecutionID int64 @@ -109,6 +118,8 @@ type basicController struct { taskMgr task.Manager // Converter for V1 report to V2 report reportConverter postprocessors.NativeScanReportConverter + // cache stores the stop scan all marks + cache cacheGetter } // NewController news a scan API controller @@ -154,6 +165,9 @@ func NewController() Controller { taskMgr: task.Mgr, // Get the scan V1 to V2 report converters reportConverter: postprocessors.Converter, + cache: func() cache.Cache { + return cache.Default() + }, } } @@ -368,6 +382,44 @@ func (bc *basicController) ScanAll(ctx context.Context, trigger string, async bo return executionID, nil } +func (bc *basicController) StopScanAll(ctx context.Context, executionID int64, async bool) error { + stopScanAll := func(ctx context.Context, executionID int64) error { + // mark scan all stopped + if err := bc.markScanAllStopped(ctx, executionID); err != nil { + return err + } + // stop the execution and sub tasks + return bc.execMgr.Stop(ctx, executionID) + } + + if async { + go func() { + if err := stopScanAll(ctx, executionID); err != nil { + log.Errorf("failed to stop scan all, error: %v", err) + } + }() + return nil + } + + return stopScanAll(ctx, executionID) +} + +func scanAllStoppedKey(execID int64) string { + return fmt.Sprintf("scan_all:execution_id:%d:stopped", execID) +} + +func (bc *basicController) markScanAllStopped(ctx context.Context, execID int64) error { + // set the expire time to 2 hours, the duration should be large enough + // for controller to capture the stop flag, leverage the key recycled + // by redis TTL, no need to clean by scan controller as the new scan all + // will have a new unique execution id, the old key has no effects to anything. + return bc.cache().Save(ctx, scanAllStoppedKey(execID), "", 2*time.Hour) +} + +func (bc *basicController) isScanAllStopped(ctx context.Context, execID int64) bool { + return bc.cache().Contains(ctx, scanAllStoppedKey(execID)) +} + func (bc *basicController) startScanAll(ctx context.Context, executionID int64) error { batchSize := 50 @@ -379,8 +431,15 @@ func (bc *basicController) startScanAll(ctx context.Context, executionID int64) UnsupportCount int `json:"unsupport_count"` UnknowCount int `json:"unknow_count"` }{} + // with cancel function to signal downstream worker + ctx, cancel := context.WithCancel(ctx) + defer cancel() for artifact := range ar.Iterator(ctx, batchSize, nil, nil) { + if bc.isScanAllStopped(ctx, executionID) { + return errScanAllStopped + } + summary.TotalCount++ scan := func(ctx context.Context) error { diff --git a/src/controller/scan/base_controller_test.go b/src/controller/scan/base_controller_test.go index 8b781fc501c..d922c3f2228 100644 --- a/src/controller/scan/base_controller_test.go +++ b/src/controller/scan/base_controller_test.go @@ -30,6 +30,7 @@ import ( "github.com/goharbor/harbor/src/common/rbac" "github.com/goharbor/harbor/src/controller/artifact" "github.com/goharbor/harbor/src/controller/robot" + "github.com/goharbor/harbor/src/lib/cache" "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/orm" "github.com/goharbor/harbor/src/lib/q" @@ -49,6 +50,7 @@ import ( robottesting "github.com/goharbor/harbor/src/testing/controller/robot" scannertesting "github.com/goharbor/harbor/src/testing/controller/scanner" tagtesting "github.com/goharbor/harbor/src/testing/controller/tag" + mockcache "github.com/goharbor/harbor/src/testing/lib/cache" ormtesting "github.com/goharbor/harbor/src/testing/lib/orm" "github.com/goharbor/harbor/src/testing/mock" accessorytesting "github.com/goharbor/harbor/src/testing/pkg/accessory" @@ -77,6 +79,7 @@ type ControllerTestSuite struct { ar artifact.Controller c Controller reportConverter *postprocessorstesting.ScanReportV1ToV2Converter + cache *mockcache.Cache } // TestController is the entry point of ControllerTestSuite. @@ -271,6 +274,8 @@ func (suite *ControllerTestSuite) SetupSuite() { suite.taskMgr = &tasktesting.Manager{} + suite.cache = &mockcache.Cache{} + suite.c = &basicController{ manager: mgr, ar: suite.ar, @@ -298,6 +303,7 @@ func (suite *ControllerTestSuite) SetupSuite() { execMgr: suite.execMgr, taskMgr: suite.taskMgr, reportConverter: &postprocessorstesting.ScanReportV1ToV2Converter{}, + cache: func() cache.Cache { return suite.cache }, } } @@ -522,25 +528,25 @@ func (suite *ControllerTestSuite) TestScanControllerGetMultiScanLog() { func (suite *ControllerTestSuite) TestScanAll() { { // no artifacts found when scan all - ctx := context.TODO() - executionID := int64(1) suite.execMgr.On( - "Create", ctx, "SCAN_ALL", int64(0), "SCHEDULE", + "Create", mock.Anything, "SCAN_ALL", int64(0), "SCHEDULE", ).Return(executionID, nil).Once() mock.OnAnything(suite.accessoryMgr, "List").Return([]accessoryModel.Accessory{}, nil).Once() mock.OnAnything(suite.artifactCtl, "List").Return([]*artifact.Artifact{}, nil).Once() - suite.taskMgr.On("Count", ctx, q.New(q.KeyWords{"execution_id": executionID})).Return(int64(0), nil).Once() + suite.taskMgr.On("Count", mock.Anything, q.New(q.KeyWords{"execution_id": executionID})).Return(int64(0), nil).Once() mock.OnAnything(suite.execMgr, "UpdateExtraAttrs").Return(nil).Once() - suite.execMgr.On("MarkDone", ctx, executionID, mock.Anything).Return(nil).Once() + suite.execMgr.On("MarkDone", mock.Anything, executionID, mock.Anything).Return(nil).Once() - _, err := suite.c.ScanAll(ctx, "SCHEDULE", false) + suite.cache.On("Contains", mock.Anything, scanAllStoppedKey(1)).Return(false).Once() + + _, err := suite.c.ScanAll(context.TODO(), "SCHEDULE", false) suite.NoError(err) } @@ -551,7 +557,7 @@ func (suite *ControllerTestSuite) TestScanAll() { executionID := int64(1) suite.execMgr.On( - "Create", ctx, "SCAN_ALL", int64(0), "SCHEDULE", + "Create", mock.Anything, "SCAN_ALL", int64(0), "SCHEDULE", ).Return(executionID, nil).Once() mock.OnAnything(suite.accessoryMgr, "List").Return([]accessoryModel.Accessory{}, nil).Once() @@ -568,13 +574,28 @@ func (suite *ControllerTestSuite) TestScanAll() { mock.OnAnything(suite.reportMgr, "Create").Return("uuid", nil).Once() mock.OnAnything(suite.taskMgr, "Create").Return(int64(0), fmt.Errorf("failed")).Once() mock.OnAnything(suite.execMgr, "UpdateExtraAttrs").Return(nil).Once() - suite.execMgr.On("MarkError", ctx, executionID, mock.Anything).Return(nil).Once() + suite.execMgr.On("MarkError", mock.Anything, executionID, mock.Anything).Return(nil).Once() _, err := suite.c.ScanAll(ctx, "SCHEDULE", false) suite.NoError(err) } } +func (suite *ControllerTestSuite) TestStopScanAll() { + mockExecID := int64(100) + // mock error case + mockErr := fmt.Errorf("stop scan all error") + suite.cache.On("Save", mock.Anything, scanAllStoppedKey(mockExecID), mock.Anything, mock.Anything).Return(mockErr).Once() + err := suite.c.StopScanAll(context.TODO(), mockExecID, false) + suite.EqualError(err, mockErr.Error()) + + // mock normal case + suite.cache.On("Save", mock.Anything, scanAllStoppedKey(mockExecID), mock.Anything, mock.Anything).Return(nil).Once() + suite.execMgr.On("Stop", mock.Anything, mockExecID).Return(nil).Once() + err = suite.c.StopScanAll(context.TODO(), mockExecID, false) + suite.NoError(err) +} + func (suite *ControllerTestSuite) TestDeleteReports() { suite.reportMgr.On("DeleteByDigests", context.TODO(), "digest").Return(nil).Once() diff --git a/src/controller/scan/callback_test.go b/src/controller/scan/callback_test.go index 19c3d565998..4a1be86b1f9 100644 --- a/src/controller/scan/callback_test.go +++ b/src/controller/scan/callback_test.go @@ -157,7 +157,7 @@ func (suite *CallbackTestSuite) TestScanAllCallback() { mock.OnAnything(suite.execMgr, "UpdateExtraAttrs").Return(nil).Once() - suite.execMgr.On("MarkDone", context.TODO(), executionID, mock.Anything).Return(nil).Once() + suite.execMgr.On("MarkDone", mock.Anything, executionID, mock.Anything).Return(nil).Once() suite.NoError(scanAllCallback(context.TODO(), "")) } diff --git a/src/controller/scan/controller.go b/src/controller/scan/controller.go index 5029a7dcc3f..b890ff7d3f0 100644 --- a/src/controller/scan/controller.go +++ b/src/controller/scan/controller.go @@ -115,6 +115,16 @@ type Controller interface { // error : non nil error if any errors occurred ScanAll(ctx context.Context, trigger string, async bool) (int64, error) + // StopScanAll stops the scanAll + // + // Arguments: + // ctx context.Context : the context for this method + // executionID int64 : the id of scan all execution + // async bool : stop scan all in background + // Returns: + // error : non nil error if any errors occurred + StopScanAll(ctx context.Context, executionID int64, async bool) error + // GetVulnerable returns the vulnerable of the artifact for the allowlist // // Arguments: diff --git a/src/server/v2.0/handler/scan_all.go b/src/server/v2.0/handler/scan_all.go index 1359fe8dee1..111101913d6 100644 --- a/src/server/v2.0/handler/scan_all.go +++ b/src/server/v2.0/handler/scan_all.go @@ -28,7 +28,6 @@ import ( "github.com/goharbor/harbor/src/controller/scanner" "github.com/goharbor/harbor/src/jobservice/job" "github.com/goharbor/harbor/src/lib/errors" - "github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/orm" "github.com/goharbor/harbor/src/lib/q" "github.com/goharbor/harbor/src/pkg/scheduler" @@ -74,12 +73,10 @@ func (s *scanAllAPI) StopScanAll(ctx context.Context, params operation.StopScanA if execution == nil { return s.SendError(ctx, errors.BadRequestError(nil).WithMessage("no scan all job is found currently")) } - go func(ctx context.Context, eid int64) { - err := s.execMgr.Stop(ctx, eid) - if err != nil { - log.Errorf("failed to stop the execution of executionID=%+v", execution.ID) - } - }(s.makeCtx(), execution.ID) + + if err = s.scanCtl.StopScanAll(s.makeCtx(), execution.ID, true); err != nil { + return s.SendError(ctx, err) + } return operation.NewStopScanAllAccepted() } diff --git a/src/server/v2.0/handler/scan_all_test.go b/src/server/v2.0/handler/scan_all_test.go index 3003955a63e..61e3a77ed24 100644 --- a/src/server/v2.0/handler/scan_all_test.go +++ b/src/server/v2.0/handler/scan_all_test.go @@ -247,6 +247,7 @@ func (suite *ScanAllTestSuite) TestStopScanAll() { times := 3 suite.Security.On("IsAuthenticated").Return(true).Times(times) suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times) + mock.OnAnything(suite.scanCtl, "StopScanAll").Return(nil).Times(times) mock.OnAnything(suite.scannerCtl, "ListRegistrations").Return([]*scanner.Registration{{ID: int64(1)}}, nil).Times(times) { diff --git a/src/testing/controller/scan/controller.go b/src/testing/controller/scan/controller.go index df31b2ab402..aae3b890750 100644 --- a/src/testing/controller/scan/controller.go +++ b/src/testing/controller/scan/controller.go @@ -205,6 +205,20 @@ func (_m *Controller) Stop(ctx context.Context, _a1 *artifact.Artifact) error { return r0 } +// StopScanAll provides a mock function with given fields: ctx, executionID, async +func (_m *Controller) StopScanAll(ctx context.Context, executionID int64, async bool) error { + ret := _m.Called(ctx, executionID, async) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int64, bool) error); ok { + r0 = rf(ctx, executionID, async) + } else { + r0 = ret.Error(0) + } + + return r0 +} + type mockConstructorTestingTNewController interface { mock.TestingT Cleanup(func()) From 8fe578e7ab67947aee5f5737864d5aabeeb5f67f Mon Sep 17 00:00:00 2001 From: "rongfu.leng" <1275177125@qq.com> Date: Mon, 5 Jun 2023 17:19:55 +0800 Subject: [PATCH 06/38] feat: Optimize quota checking when pushing images (#17392) Signed-off-by: lengrongfu <1275177125@qq.com> --- src/controller/quota/controller_test.go | 13 +++++++++++++ src/pkg/quota/util.go | 2 +- .../quota/post_initiate_blob_upload.go | 4 ++-- .../quota/post_initiate_blob_upload_test.go | 19 +++++++++++++++++++ 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/controller/quota/controller_test.go b/src/controller/quota/controller_test.go index a98db9b3ca6..037d39bb6b0 100644 --- a/src/controller/quota/controller_test.go +++ b/src/controller/quota/controller_test.go @@ -143,6 +143,19 @@ func (suite *ControllerTestSuite) TestRequestFunctionFailed() { suite.Error(suite.ctl.Request(ctx, suite.reference, referenceID, resources, func() error { return fmt.Errorf("error") })) } +func (suite *ControllerTestSuite) TestRequestResourceIsZero() { + suite.PrepareForUpdate(suite.quota, nil) + + ctx := orm.NewContext(context.TODO(), &ormtesting.FakeOrmer{}) + referenceID := uuid.New().String() + f := func() error { + return nil + } + res := types.ResourceList{types.ResourceStorage: 0} + err := suite.ctl.Request(ctx, suite.reference, referenceID, res, f) + suite.Nil(err) +} + func TestControllerTestSuite(t *testing.T) { suite.Run(t, &ControllerTestSuite{}) } diff --git a/src/pkg/quota/util.go b/src/pkg/quota/util.go index 8e3be434f39..00d796d38b1 100644 --- a/src/pkg/quota/util.go +++ b/src/pkg/quota/util.go @@ -32,7 +32,7 @@ func IsSafe(hardLimits types.ResourceList, currentUsed types.ResourceList, newUs continue } - if hardLimit == types.UNLIMITED || value == currentUsed[resource] { + if hardLimit == types.UNLIMITED { continue } diff --git a/src/server/middleware/quota/post_initiate_blob_upload.go b/src/server/middleware/quota/post_initiate_blob_upload.go index f4653a9c372..efeb8dd33eb 100644 --- a/src/server/middleware/quota/post_initiate_blob_upload.go +++ b/src/server/middleware/quota/post_initiate_blob_upload.go @@ -38,8 +38,8 @@ func postInitiateBlobUploadResources(r *http.Request, reference, referenceID str query := r.URL.Query() mount := query.Get("mount") if mount == "" { - // it is not mount blob http request, skip to request the resources - return nil, nil + // it is not mount blob http request, create length is zero resource to check quota is full + return types.ResourceList{types.ResourceStorage: 0}, nil } ctx := r.Context() diff --git a/src/server/middleware/quota/post_initiate_blob_upload_test.go b/src/server/middleware/quota/post_initiate_blob_upload_test.go index c6f0d1da568..d30e2cc938c 100644 --- a/src/server/middleware/quota/post_initiate_blob_upload_test.go +++ b/src/server/middleware/quota/post_initiate_blob_upload_test.go @@ -70,6 +70,25 @@ func (suite *PostInitiateBlobUploadMiddlewareTestSuite) TestMiddleware() { PostInitiateBlobUploadMiddleware()(next).ServeHTTP(rr, req) suite.Equal(http.StatusOK, rr.Code) } + + { + url = "/v2/library/photon/blobs/uploads" + mock.OnAnything(suite.quotaController, "Request").Return(nil).Once().Run(func(args mock.Arguments) { + resources := args.Get(3).(types.ResourceList) + suite.Len(resources, 1) + suite.Equal(resources[types.ResourceStorage], int64(0)) + + f := args.Get(4).(func() error) + f() + }) + mock.OnAnything(suite.quotaController, "GetByRef").Return("a.Quota{}, nil).Once() + + req := httptest.NewRequest(http.MethodPost, url, nil) + rr := httptest.NewRecorder() + + PostInitiateBlobUploadMiddleware()(next).ServeHTTP(rr, req) + suite.Equal(http.StatusOK, rr.Code) + } } func TestPostInitiateBlobUploadMiddlewareTestSuite(t *testing.T) { From 6e2b79a67a384fa744542d9863d8bcbeba4017eb Mon Sep 17 00:00:00 2001 From: Mac Chaffee Date: Mon, 5 Jun 2023 06:49:47 -0400 Subject: [PATCH 07/38] Update/improve grafana dashboard (#16661) * Update/improve grafana dashboard Signed-off-by: Mac Chaffee --- contrib/grafana-dashboard/README.md | 9 + contrib/grafana-dashboard/dashboard.png | Bin 0 -> 639258 bytes .../grafana-dashboard/metrics-example.json | 1 + .../grafana-dashborad/metrics-example.json | 4130 ----------------- 4 files changed, 10 insertions(+), 4130 deletions(-) create mode 100644 contrib/grafana-dashboard/README.md create mode 100644 contrib/grafana-dashboard/dashboard.png create mode 100644 contrib/grafana-dashboard/metrics-example.json delete mode 100644 contrib/grafana-dashborad/metrics-example.json diff --git a/contrib/grafana-dashboard/README.md b/contrib/grafana-dashboard/README.md new file mode 100644 index 00000000000..f56fdd11c91 --- /dev/null +++ b/contrib/grafana-dashboard/README.md @@ -0,0 +1,9 @@ +# Grafana Dashboard + +This dashboard can be used to visualize metrics from the [Harbor Exporter](https://goharbor.io/docs/main/administration/metrics/). + +The dashboard was created with Grafana v8 and may not work with older versions of Grafana. + +## Screenshot + +![dashboard](dashboard.png) \ No newline at end of file diff --git a/contrib/grafana-dashboard/dashboard.png b/contrib/grafana-dashboard/dashboard.png new file mode 100644 index 0000000000000000000000000000000000000000..97843217fb24c4dc6594fb5c974aa7796b820aa1 GIT binary patch literal 639258 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

7l?rg}Hw5{$TtB?%W$gLNU&?$&_&9(ij*ngmP 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 z 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∓>Q2MLQ#v$94#384dr@ool${ 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

8Ue5&;UU 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

p5}bg5sX}o*s)rDuUkfXzC`gD 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 Date: Tue, 6 Jun 2023 10:45:28 +0800 Subject: [PATCH 08/38] Add podman pull & push testcase (#18790) Fix #18788 Signed-off-by: Yang Jiao --- tests/apitests/python/library/podman.py | 18 +++++ .../apitests/python/test_podman_pull_push.py | 76 +++++++++++++++++++ tests/apitests/python/test_referrers_api.py | 3 - tests/robot-cases/Group0-BAT/API_DB.robot | 4 + tests/test-engine-image/Dockerfile.api_test | 4 +- 5 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 tests/apitests/python/library/podman.py create mode 100644 tests/apitests/python/test_podman_pull_push.py diff --git a/tests/apitests/python/library/podman.py b/tests/apitests/python/library/podman.py new file mode 100644 index 00000000000..c8d54dcdc7b --- /dev/null +++ b/tests/apitests/python/library/podman.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +import base + +def login(registry, username, password): + command = ["podman", "login", "-u", username, "-p", password, registry] + base.run_command(command) + +def logout(registry): + command = ["podman", "logout", registry] + base.run_command(command) + +def pull(artifact): + command = ["podman", "pull", artifact] + base.run_command(command) + +def push(source_artifact, target_artifact): + command = ["podman", "push", source_artifact, target_artifact] + base.run_command(command) diff --git a/tests/apitests/python/test_podman_pull_push.py b/tests/apitests/python/test_podman_pull_push.py new file mode 100644 index 00000000000..dbabe17f19a --- /dev/null +++ b/tests/apitests/python/test_podman_pull_push.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import +import unittest + +from testutils import harbor_server, ADMIN_CLIENT, suppress_urllib3_warning +from library import podman +from library.project import Project +from library.user import User +from library.artifact import Artifact +from library.repository import push_self_build_image_to_project + +class TestPodmanPullPush(unittest.TestCase): + + @suppress_urllib3_warning + def setUp(self): + self.project= Project() + self.user= User() + self.artifact = Artifact() + self.image = "image_test" + self.tag = "v1" + self.source_image = "ghcr.io/goharbor/harbor-core" + self.source_tag = "v2.8.2" + + def testPodman(self): + """ + Test case: + Podman pull and push + Test step and expected result: + 1. Create a new user; + 2. Create a new project by user; + 3. Push a new image in project by user; + 4. Podman login harbor; + 5. Podman pull image from project(PA) by user; + 6. Podman pull soure image; + 7. Podman push soure image to project by user; + 8. Verify the image; + 9. Podman logout harbor; + """ + url = ADMIN_CLIENT["endpoint"] + user_password = "Aa123456" + + # 1. Create user(UA) + _, user_name = self.user.create_user(user_password = user_password, **ADMIN_CLIENT) + user_client = dict(endpoint = url, username = user_name, password = user_password, with_accessory = True) + + # 2. Create private project(PA) by user(UA) + _, project_name = self.project.create_project(metadata = {"public": "false"}, **user_client) + + # 3. Push a new image(IA) in project(PA) by user(UA) + push_self_build_image_to_project(project_name, harbor_server, user_name, user_password, self.image, self.tag) + + # 4. Podman login harbor + podman.login(harbor_server, user_name, user_password) + + # 5. Podman pull image from project(PA) by user + podman.pull("{}/{}/{}:{}".format(harbor_server, project_name, self.image, self.tag)) + + # 6. Podman pull soure image + podman.pull("{}:{}".format(self.source_image, self.source_tag)) + + # 7. Podman push soure image to project by user + podman.push("{}:{}".format(self.source_image, self.source_tag), "{}/{}/{}:{}".format(harbor_server, project_name, self.image, self.tag)) + + # 8. Verify the image + image_info = self.artifact.get_reference_info(project_name, self.image, self.tag, **user_client) + self.assertIsNotNone(image_info) + self.assertIsNotNone(image_info.digest) + self.assertEqual(len(image_info.tags), 1) + self.assertEqual(image_info.tags[0].name, self.tag) + + # 9. Podman logout harbor + podman.logout(harbor_server) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/apitests/python/test_referrers_api.py b/tests/apitests/python/test_referrers_api.py index f1bd69f9209..a62f1dbfc6d 100644 --- a/tests/apitests/python/test_referrers_api.py +++ b/tests/apitests/python/test_referrers_api.py @@ -37,9 +37,6 @@ def testReferrersApi(self): 6. Sign image(IA) SBOM with cosign; 7. Call the referrers api successfully; 8. Call the referrers api and filter artifact_type; - Tear down: - 1. Delete project(PA); - 2. Delete user(UA). """ url = ADMIN_CLIENT["endpoint"] user_password = "Aa123456" diff --git a/tests/robot-cases/Group0-BAT/API_DB.robot b/tests/robot-cases/Group0-BAT/API_DB.robot index ed9da3957a6..c94bbfd9bf0 100644 --- a/tests/robot-cases/Group0-BAT/API_DB.robot +++ b/tests/robot-cases/Group0-BAT/API_DB.robot @@ -186,3 +186,7 @@ Test Case - Retain Image Last Pull Time Test Case - Referrers API [Tags] referrers Harbor API Test ./tests/apitests/python/test_referrers_api.py + +Test Case - Podman Pull And Push To Harbor + [Tags] podman_pull_push + Harbor API Test ./tests/apitests/python/test_podman_pull_push.py diff --git a/tests/test-engine-image/Dockerfile.api_test b/tests/test-engine-image/Dockerfile.api_test index ca8b39aee68..d786e6cd45f 100644 --- a/tests/test-engine-image/Dockerfile.api_test +++ b/tests/test-engine-image/Dockerfile.api_test @@ -8,7 +8,7 @@ ENV COSIGN_OCI_EXPERIMENTAL=1 COPY --from=tool_builder /tool/tools.tar.gz /usr/local/bin -RUN tdnf install -y \ +RUN tdnf update -y && tdnf install -y \ wget \ git \ openjdk8 \ @@ -45,4 +45,6 @@ RUN chmod +x /usr/local/bin/dockerd-entrypoint.sh && \ echo Harbor12345 > password.ca && \ certutil -d sql:$HOME/.pki/nssdb -N -f password.ca +RUN tdnf install -y podman + VOLUME /var/lib/docker From 2f51daf70788058f17092e64040ebf75690a65a7 Mon Sep 17 00:00:00 2001 From: Shijun Sun <30999793+AllForNothing@users.noreply.github.com> Date: Wed, 7 Jun 2023 16:14:36 +0800 Subject: [PATCH 09/38] Add a tooltip for slack notification (#18787) 1.Fixes #18507 2.Remind the users of Slack's rate limits Signed-off-by: AllForNothing --- .../add-webhook-form.component.html | 48 ++++++++++++------- src/portal/src/i18n/lang/de-de-lang.json | 3 +- src/portal/src/i18n/lang/en-us-lang.json | 4 +- 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/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 +- 9 files changed, 49 insertions(+), 24 deletions(-) diff --git a/src/portal/src/app/base/project/webhook/add-webhook-form/add-webhook-form.component.html b/src/portal/src/app/base/project/webhook/add-webhook-form/add-webhook-form.component.html index 79833d9a478..4b6803b1281 100644 --- a/src/portal/src/app/base/project/webhook/add-webhook-form/add-webhook-form.component.html +++ b/src/portal/src/app/base/project/webhook/add-webhook-form/add-webhook-form.component.html @@ -83,24 +83,40 @@ - -

+ diff --git a/src/portal/src/i18n/lang/de-de-lang.json b/src/portal/src/i18n/lang/de-de-lang.json index 31eea848a53..4d39db0ee89 100644 --- a/src/portal/src/i18n/lang/de-de-lang.json +++ b/src/portal/src/i18n/lang/de-de-lang.json @@ -431,7 +431,8 @@ "EVENT_TYPE_REQUIRED": "Mindestens ein Event Typ ist erforderlich", "PAYLOAD_FORMAT": "Payload Format", "CLOUD_EVENT": "CloudEvents", - "PAYLOAD_DATA": "Payload Data" + "PAYLOAD_DATA": "Payload Data", + "SLACK_RATE_LIMIT": "Please be aware of Slack Rate Limits" }, "GROUP": { "GROUP": "Gruppe", diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json index 1b0d135cf5d..fa1974c0c72 100644 --- a/src/portal/src/i18n/lang/en-us-lang.json +++ b/src/portal/src/i18n/lang/en-us-lang.json @@ -431,7 +431,9 @@ "EVENT_TYPE_REQUIRED": "Require at least one event type", "PAYLOAD_FORMAT": "Payload Format", "CLOUD_EVENT": "CloudEvents", - "PAYLOAD_DATA": "Payload Data" + "PAYLOAD_DATA": "Payload Data", + "SLACK_RATE_LIMIT": "Please be aware of Slack Rate Limits" + }, "GROUP": { "GROUP": "Group", diff --git a/src/portal/src/i18n/lang/es-es-lang.json b/src/portal/src/i18n/lang/es-es-lang.json index 561bf1a8a7c..25ef7708dc1 100644 --- a/src/portal/src/i18n/lang/es-es-lang.json +++ b/src/portal/src/i18n/lang/es-es-lang.json @@ -432,7 +432,8 @@ "EVENT_TYPE_REQUIRED": "Require at least one event type", "PAYLOAD_FORMAT": "Payload Format", "CLOUD_EVENT": "CloudEvents", - "PAYLOAD_DATA": "Payload Data" + "PAYLOAD_DATA": "Payload Data", + "SLACK_RATE_LIMIT": "Please be aware of Slack Rate Limits" }, "GROUP": { "GROUP": "Group", diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json index cdb932d4b1e..c3d9a256d21 100644 --- a/src/portal/src/i18n/lang/fr-fr-lang.json +++ b/src/portal/src/i18n/lang/fr-fr-lang.json @@ -421,7 +421,8 @@ "EVENT_TYPE_REQUIRED": "Au moins un type d'évènement est nécessaire", "PAYLOAD_FORMAT": "Payload Format", "CLOUD_EVENT": "CloudEvents", - "PAYLOAD_DATA": "Payload Data" + "PAYLOAD_DATA": "Payload Data", + "SLACK_RATE_LIMIT": "Please be aware of Slack Rate Limits" }, "GROUP": { "Group": "Groupe", diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json index 9f29bca1100..2e585f59998 100644 --- a/src/portal/src/i18n/lang/pt-br-lang.json +++ b/src/portal/src/i18n/lang/pt-br-lang.json @@ -463,7 +463,8 @@ "EVENT_TYPE_REQUIRED": "Pelo menos um tipo de evento é obrigatório", "PAYLOAD_FORMAT": "Payload Format", "CLOUD_EVENT": "CloudEvents", - "PAYLOAD_DATA": "Payload Data" + "PAYLOAD_DATA": "Payload Data", + "SLACK_RATE_LIMIT": "Please be aware of Slack Rate Limits" }, "AUDIT_LOG": { "USERNAME": "Nome do 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 47adcb3fccb..fdbf095dc47 100644 --- a/src/portal/src/i18n/lang/tr-tr-lang.json +++ b/src/portal/src/i18n/lang/tr-tr-lang.json @@ -431,7 +431,8 @@ "EVENT_TYPE_REQUIRED": "Require at least one event type", "PAYLOAD_FORMAT": "Payload Format", "CLOUD_EVENT": "CloudEvents", - "PAYLOAD_DATA": "Payload Data" + "PAYLOAD_DATA": "Payload Data", + "SLACK_RATE_LIMIT": "Please be aware of Slack Rate Limits" }, "GROUP": { "GROUP": "Grup", diff --git a/src/portal/src/i18n/lang/zh-cn-lang.json b/src/portal/src/i18n/lang/zh-cn-lang.json index e3ae304dc5c..26e50d0fb29 100644 --- a/src/portal/src/i18n/lang/zh-cn-lang.json +++ b/src/portal/src/i18n/lang/zh-cn-lang.json @@ -430,7 +430,8 @@ "EVENT_TYPE_REQUIRED": "请至少选择一种事件类型", "PAYLOAD_FORMAT": "载荷形式", "CLOUD_EVENT": "CloudEvents", - "PAYLOAD_DATA": "载荷数据" + "PAYLOAD_DATA": "载荷数据", + "SLACK_RATE_LIMIT": "请注意 Slack 的速率限制" }, "GROUP": { "GROUP": "组", diff --git a/src/portal/src/i18n/lang/zh-tw-lang.json b/src/portal/src/i18n/lang/zh-tw-lang.json index c9d0c02fa9b..c31f3d5472f 100644 --- a/src/portal/src/i18n/lang/zh-tw-lang.json +++ b/src/portal/src/i18n/lang/zh-tw-lang.json @@ -428,7 +428,8 @@ "EVENT_TYPE_REQUIRED": "請至少選擇一種事件類型", "PAYLOAD_FORMAT": "Payload 格式", "CLOUD_EVENT": "CloudEvents", - "PAYLOAD_DATA": "Payload 資料" + "PAYLOAD_DATA": "Payload 資料", + "SLACK_RATE_LIMIT": "Please be aware of Slack Rate Limits" }, "GROUP": { "GROUP": "群組", From 8251fd2dec0a3b5e0db1e7465bcdc6af10b90fc2 Mon Sep 17 00:00:00 2001 From: "rongfu.leng" <1275177125@qq.com> Date: Thu, 8 Jun 2023 10:06:09 +0800 Subject: [PATCH 10/38] =?UTF-8?q?=E3=80=90UT=E3=80=91add=20unit=20test=20f?= =?UTF-8?q?or=20collector=20system=20info=20(#18717)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add unit test for system collector test Signed-off-by: lengrongfu <1275177125@qq.com> --- src/pkg/exporter/system_collector_test.go | 101 ++++++++++++++++++---- 1 file changed, 82 insertions(+), 19 deletions(-) diff --git a/src/pkg/exporter/system_collector_test.go b/src/pkg/exporter/system_collector_test.go index ade0b915303..d38d055e684 100644 --- a/src/pkg/exporter/system_collector_test.go +++ b/src/pkg/exporter/system_collector_test.go @@ -1,23 +1,16 @@ package exporter import ( + "net/http" + "net/http/httptest" + "net/url" "reflect" + "strconv" "testing" "github.com/prometheus/client_golang/prometheus" - "github.com/stretchr/testify/suite" ) -type SysCollectorSuite struct { - suite.Suite -} - -func (c *SysCollectorSuite) SetupTest() { - CacheInit(&Opt{ - CacheDuration: 1, - }) -} - func TestNewSystemInfoCollector(t *testing.T) { type args struct { hbrCli *HarborClient @@ -27,7 +20,15 @@ func TestNewSystemInfoCollector(t *testing.T) { args args want *SystemInfoCollector }{ - // TODO: Add test cases. + { + name: "test new system info collector", + args: args{ + hbrCli: &HarborClient{}, + }, + want: &SystemInfoCollector{ + HarborClient: &HarborClient{}, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -43,45 +44,79 @@ func TestSystemInfoCollector_Describe(t *testing.T) { HarborClient *HarborClient } type args struct { - c chan<- *prometheus.Desc + c chan *prometheus.Desc } tests := []struct { name string fields fields args args + want *prometheus.Desc }{ - // TODO: Add test cases. + { + name: "test describe", + fields: fields{ + HarborClient: &HarborClient{}, + }, + args: args{ + c: make(chan *prometheus.Desc), + }, + want: harborSysInfo.Desc(), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { hc := &SystemInfoCollector{ HarborClient: tt.fields.HarborClient, } - hc.Describe(tt.args.c) + go hc.Describe(tt.args.c) + desc := <-tt.args.c + if !reflect.DeepEqual(tt.want, desc) { + t.Errorf("SystemInfoCollector.Describe() = %v, want %v", desc, harborSysInfo.Desc()) + } }) } } func TestSystemInfoCollector_Collect(t *testing.T) { + CacheInit(&Opt{ + CacheDuration: 60, + }) + data := []prometheus.Metric{ + prometheus.MustNewConstMetric(harborSysInfo.Desc(), prometheus.GaugeValue, 1, "ldap_auth", "v2.0.0", "true"), + } + CachePut(systemInfoCollectorName, data) type fields struct { HarborClient *HarborClient } type args struct { - c chan<- prometheus.Metric + c chan prometheus.Metric } tests := []struct { name string fields fields args args }{ - // TODO: Add test cases. + { + name: "test collect", + fields: fields{ + HarborClient: &HarborClient{}, + }, + args: args{ + c: make(chan prometheus.Metric), + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + InitHarborClient(tt.fields.HarborClient) hc := &SystemInfoCollector{ HarborClient: tt.fields.HarborClient, } - hc.Collect(tt.args.c) + go hc.Collect(tt.args.c) + metric := <-tt.args.c + if !reflect.DeepEqual(metric, data[0]) { + t.Errorf("SystemInfoCollector.Collect() = %v, want %v", metric, data[0]) + } }) } } @@ -90,15 +125,43 @@ func TestSystemInfoCollector_getSysInfo(t *testing.T) { type fields struct { HarborClient *HarborClient } + data := []prometheus.Metric{ + prometheus.MustNewConstMetric(harborSysInfo.Desc(), prometheus.GaugeValue, 1, "ldap_auth", "v2.0.0", "true"), + } + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/api/v2.0/systeminfo" { + w.Write([]byte(`{"auth_mode":"ldap_auth","harbor_version":"v2.0.0","self_registration":true}`)) + w.WriteHeader(http.StatusOK) + } else { + http.NotFound(w, r) + } + })) + defer server.Close() + parse, err := url.Parse(server.URL) + if err != nil { + t.Fatal(err) + } + port, _ := strconv.Atoi(parse.Port()) tests := []struct { name string fields fields want []prometheus.Metric }{ - // TODO: Add test cases. + { + name: "test get system info", + fields: fields{ + HarborClient: &HarborClient{ + HarborScheme: "http", + HarborHost: parse.Hostname(), + HarborPort: port, + }, + }, + want: data, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + InitHarborClient(tt.fields.HarborClient) hc := &SystemInfoCollector{ HarborClient: tt.fields.HarborClient, } From c08c7c52a09a5920d006605eb79b388f0724a62d Mon Sep 17 00:00:00 2001 From: Chlins Zhang Date: Thu, 8 Jun 2023 14:19:06 +0800 Subject: [PATCH 11/38] fix: optimize the mechanism of quota refresh (#18795) Signed-off-by: chlins --- src/controller/quota/controller.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/controller/quota/controller.go b/src/controller/quota/controller.go index a95143f685a..7e6d8f0959b 100644 --- a/src/controller/quota/controller.go +++ b/src/controller/quota/controller.go @@ -185,6 +185,13 @@ func (c *controller) updateUsageWithRetry(ctx context.Context, reference, refere return retry.Abort(err) } + // The PR https://github.com/goharbor/harbor/pull/17392 optimized the logic for post upload blob which use size 0 + // for checking quota, this will increase the pressure of optimistic lock, so here return earlier + // if the quota usage has not changed to reduce the probability of optimistic lock. + if types.Equals(used, newUsed) { + return nil + } + q.SetUsed(newUsed) err = c.quotaMgr.Update(ctx, q) From fc9c68a6fce8dccbaf24b2dca87ecd0c5b97dcf5 Mon Sep 17 00:00:00 2001 From: Shijun Sun <30999793+AllForNothing@users.noreply.github.com> Date: Mon, 12 Jun 2023 15:41:50 +0800 Subject: [PATCH 12/38] Add Details column for gc history (#18797) 1. Related #18779 2. Show how many blobs and manifest have been deleted and how much space has been freed up Signed-off-by: AllForNothing --- .../gc/gc-history/gc-history.component.html | 55 +++++++++++++++++++ .../gc/gc-history/gc-history.component.scss | 4 ++ .../gc/gc-history/gc-history.component.ts | 30 ++++++++++ src/portal/src/i18n/lang/de-de-lang.json | 6 +- src/portal/src/i18n/lang/en-us-lang.json | 6 +- src/portal/src/i18n/lang/es-es-lang.json | 6 +- src/portal/src/i18n/lang/fr-fr-lang.json | 6 +- src/portal/src/i18n/lang/pt-br-lang.json | 6 +- src/portal/src/i18n/lang/tr-tr-lang.json | 6 +- src/portal/src/i18n/lang/zh-cn-lang.json | 6 +- src/portal/src/i18n/lang/zh-tw-lang.json | 6 +- 11 files changed, 129 insertions(+), 8 deletions(-) diff --git a/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc-history/gc-history.component.html b/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc-history/gc-history.component.html index 344faaa1444..f9a68dee1c2 100644 --- a/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc-history/gc-history.component.html +++ b/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc-history/gc-history.component.html @@ -26,6 +26,9 @@
{{ 'GC.TRIGGER_TYPE' | translate }} {{ 'TAG_RETENTION.DRY_RUN' | translate }} {{ 'STATUS' | translate }} + {{ + 'TAG_RETENTION.DETAILS' | translate + }} {{ 'CREATION_TIME' | translate }} {{ 'UPDATE_TIME' | translate @@ -48,6 +51,58 @@
{{ job.job_status.toUpperCase() | translate }} + +
+ {{ + 'GC.DELETE_BLOB_AND_MANIFEST' + | translate + : { + blob: getBlobs(job?.job_parameters), + manifest: getManifest(job?.job_parameters) + } + }} + {{ + 'GC.DELETE_BLOB' + | translate + : { + blobs: getBlobs(job?.job_parameters), + manifest: getManifest(job?.job_parameters) + } + }} + {{ + 'GC.DELETE_MANIFEST' + | translate + : { + blob: getBlobs(job?.job_parameters), + manifest: getManifest(job?.job_parameters) + } + }} +
+
+ {{ + 'GC.FREE_UP_SIZE' + | translate : { size: getSize(job?.job_parameters) } + }} +
+
{{ job.creation_time | harborDatetime : 'medium' }} diff --git a/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc-history/gc-history.component.scss b/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc-history/gc-history.component.scss index 6f63db9c9d8..219bcc08aec 100644 --- a/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc-history/gc-history.component.scss +++ b/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc-history/gc-history.component.scss @@ -18,3 +18,7 @@ color: #007CBB; } } + +.detail { + min-width: 12rem; +} diff --git a/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc-history/gc-history.component.ts b/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc-history/gc-history.component.ts index 1bef783ff93..efbd0936d6e 100644 --- a/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc-history/gc-history.component.ts +++ b/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc-history/gc-history.component.ts @@ -9,6 +9,7 @@ import { import { GcService } from '../../../../../../../../ng-swagger-gen/services/gc.service'; import { CURRENT_BASE_HREF, + formatSize, getPageSizeFromLocalStorage, getSortingString, PageSizeMapKeys, @@ -202,6 +203,35 @@ export class GcHistoryComponent implements OnInit, OnDestroy { } return NO; } + getBlobs(param: string): number { + if (param) { + const paramObj: any = JSON.parse(param); + if (paramObj && paramObj.purged_blobs) { + return paramObj.purged_blobs; + } + } + return 0; + } + + getManifest(param: string): number { + if (param) { + const paramObj: any = JSON.parse(param); + if (paramObj && paramObj.purged_manifests) { + return paramObj.purged_manifests; + } + } + return 0; + } + + getSize(param: string): string { + if (param) { + const paramObj: any = JSON.parse(param); + if (paramObj && paramObj.freed_space) { + return formatSize(paramObj.freed_space); + } + } + return null; + } getLogLink(id): string { return `${CURRENT_BASE_HREF}/system/gc/${id}/log`; diff --git a/src/portal/src/i18n/lang/de-de-lang.json b/src/portal/src/i18n/lang/de-de-lang.json index 4d39db0ee89..8838a1e40a9 100644 --- a/src/portal/src/i18n/lang/de-de-lang.json +++ b/src/portal/src/i18n/lang/de-de-lang.json @@ -1230,7 +1230,11 @@ "DELETE_UNTAGGED": "Erlaube Speicherbereinigung auf Artefakte ohne Tag", "EXPLAIN": "Speicherbereinigung (Garbage Collection / GC) ist eine rechenintensive Operation, die die Registry-Leistung beeinflussen kann", "EXPLAIN_TIME_WINDOW": "Artifacts uploaded in the past 2 hours(the default window) are excluded from garbage collection", - "DRY_RUN_SUCCESS": "Probelauf erfolgreich gestartet" + "DRY_RUN_SUCCESS": "Probelauf erfolgreich gestartet", + "DELETE_BLOB_AND_MANIFEST": "{{blob}} blob(s) and {{manifest}} manifest(s) deleted", + "DELETE_BLOB": "{{blob}} blob(s) deleted", + "DELETE_MANIFEST": "{{manifest}} manifest(s) deleted", + "FREE_UP_SIZE": "{{size}} space freed up" }, "RETAG": { "MSG_SUCCESS": "Artefakt erfolgreich kopiert", diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json index fa1974c0c72..23d4caa33ef 100644 --- a/src/portal/src/i18n/lang/en-us-lang.json +++ b/src/portal/src/i18n/lang/en-us-lang.json @@ -1231,7 +1231,11 @@ "DELETE_UNTAGGED": "Allow garbage collection on untagged artifacts", "EXPLAIN": "GC is a compute intensive operation that may impact registry performance", "EXPLAIN_TIME_WINDOW": "Artifacts uploaded in the past 2 hours(the default window) are excluded from garbage collection", - "DRY_RUN_SUCCESS": "Triggered dry run successfully" + "DRY_RUN_SUCCESS": "Triggered dry run successfully", + "DELETE_BLOB_AND_MANIFEST": "{{blob}} blob(s) and {{manifest}} manifest(s) deleted", + "DELETE_BLOB": "{{blob}} blob(s) deleted", + "DELETE_MANIFEST": "{{manifest}} manifest(s) deleted", + "FREE_UP_SIZE": "{{size}} space freed up" }, "RETAG": { "MSG_SUCCESS": "Copy artifact successfully", diff --git a/src/portal/src/i18n/lang/es-es-lang.json b/src/portal/src/i18n/lang/es-es-lang.json index 25ef7708dc1..2710a8d1f29 100644 --- a/src/portal/src/i18n/lang/es-es-lang.json +++ b/src/portal/src/i18n/lang/es-es-lang.json @@ -1227,7 +1227,11 @@ "DELETE_UNTAGGED": "Allow garbage collection on untagged artifacts", "EXPLAIN": "GC is a compute intensive operation that may impact registry performance", "EXPLAIN_TIME_WINDOW": "Artifacts uploaded in the past 2 hours(the default window) are excluded from garbage collection", - "DRY_RUN_SUCCESS": "Triggered dry run successfully" + "DRY_RUN_SUCCESS": "Triggered dry run successfully", + "DELETE_BLOB_AND_MANIFEST": "{{blob}} blob(s) and {{manifest}} manifest(s) deleted", + "DELETE_BLOB": "{{blob}} blob(s) deleted", + "DELETE_MANIFEST": "{{manifest}} manifest(s) deleted", + "FREE_UP_SIZE": "{{size}} space freed up" }, "RETAG": { "MSG_SUCCESS": "Copy artifact successfully", diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json index c3d9a256d21..7a11d89146d 100644 --- a/src/portal/src/i18n/lang/fr-fr-lang.json +++ b/src/portal/src/i18n/lang/fr-fr-lang.json @@ -1197,7 +1197,11 @@ "DELETE_UNTAGGED": "Supprimer les artefacts non tagués", "EXPLAIN": "GC est une opération gourmande en puissance de calcul qui peut impacter les performances du registre", "EXPLAIN_TIME_WINDOW": "Artifacts uploaded in the past 2 hours(the default window) are excluded from garbage collection", - "DRY_RUN_SUCCESS": "Exécution à blanc déclenchée avec succès" + "DRY_RUN_SUCCESS": "Exécution à blanc déclenchée avec succès", + "DELETE_BLOB_AND_MANIFEST": "{{blob}} blob(s) and {{manifest}} manifest(s) deleted", + "DELETE_BLOB": "{{blob}} blob(s) deleted", + "DELETE_MANIFEST": "{{manifest}} manifest(s) deleted", + "FREE_UP_SIZE": "{{size}} space freed up" }, "RETAG": { "MSG_SUCCESS": "Artefact copié", diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json index 2e585f59998..9ff98904ffa 100644 --- a/src/portal/src/i18n/lang/pt-br-lang.json +++ b/src/portal/src/i18n/lang/pt-br-lang.json @@ -1227,7 +1227,11 @@ "DELETE_UNTAGGED": "Permitir coleta de artefatos sem tags", "EXPLAIN": "A limpeza exige recursos computacionais e pode impactar performance.", "EXPLAIN_TIME_WINDOW": "Artifacts uploaded in the past 2 hours(the default window) are excluded from garbage collection", - "DRY_RUN_SUCCESS": "Teste executado com sucesso" + "DRY_RUN_SUCCESS": "Teste executado com sucesso", + "DELETE_BLOB_AND_MANIFEST": "{{blob}} blob(s) and {{manifest}} manifest(s) deleted", + "DELETE_BLOB": "{{blob}} blob(s) deleted", + "DELETE_MANIFEST": "{{manifest}} manifest(s) deleted", + "FREE_UP_SIZE": "{{size}} space freed up" }, "RETAG": { "MSG_SUCCESS": "Artefato copiado com sucesso", diff --git a/src/portal/src/i18n/lang/tr-tr-lang.json b/src/portal/src/i18n/lang/tr-tr-lang.json index fdbf095dc47..195cb958a90 100644 --- a/src/portal/src/i18n/lang/tr-tr-lang.json +++ b/src/portal/src/i18n/lang/tr-tr-lang.json @@ -1230,7 +1230,11 @@ "DELETE_UNTAGGED": "Allow garbage collection on untagged artifacts", "EXPLAIN": "GC is a compute intensive operation that may impact registry performance", "EXPLAIN_TIME_WINDOW": "Artifacts uploaded in the past 2 hours(the default window) are excluded from garbage collection", - "DRY_RUN_SUCCESS": "Triggered dry run successfully" + "DRY_RUN_SUCCESS": "Triggered dry run successfully", + "DELETE_BLOB_AND_MANIFEST": "{{blob}} blob(s) and {{manifest}} manifest(s) deleted", + "DELETE_BLOB": "{{blob}} blob(s) deleted", + "DELETE_MANIFEST": "{{manifest}} manifest(s) deleted", + "FREE_UP_SIZE": "{{size}} space freed up" }, "RETAG": { "MSG_SUCCESS": "Copy artifact successfully", diff --git a/src/portal/src/i18n/lang/zh-cn-lang.json b/src/portal/src/i18n/lang/zh-cn-lang.json index 26e50d0fb29..c8b8a6422f5 100644 --- a/src/portal/src/i18n/lang/zh-cn-lang.json +++ b/src/portal/src/i18n/lang/zh-cn-lang.json @@ -1227,7 +1227,11 @@ "DELETE_UNTAGGED": "允许回收无 tag 的 artifacts", "EXPLAIN": "垃圾回收是一个计算密集型操作,可能会影响仓库性能", "EXPLAIN_TIME_WINDOW": "在最近的两小时(默认窗口期)内被推送的 Artifacts 不会被当做垃圾回收的目标", - "DRY_RUN_SUCCESS": "触发模拟运行成功" + "DRY_RUN_SUCCESS": "触发模拟运行成功", + "DELETE_BLOB_AND_MANIFEST": "{{blob}}个 blob(s) 和 {{manifest}}个 manifest(s) 已删除", + "DELETE_BLOB": "{{blob}}个 blob(s) 已删除", + "DELETE_MANIFEST": "{{manifest}}个 manifest(s) 已删除", + "FREE_UP_SIZE": "{{size}}的空间已清理" }, "RETAG": { "MSG_SUCCESS": "Artifact 拷贝成功", diff --git a/src/portal/src/i18n/lang/zh-tw-lang.json b/src/portal/src/i18n/lang/zh-tw-lang.json index c31f3d5472f..2f651545a03 100644 --- a/src/portal/src/i18n/lang/zh-tw-lang.json +++ b/src/portal/src/i18n/lang/zh-tw-lang.json @@ -1219,7 +1219,11 @@ "DELETE_UNTAGGED": "允許對未標籤檔案進行垃圾收集", "EXPLAIN": "清理垃圾是一個計算密集的操作,可能影響註冊表的效能", "EXPLAIN_TIME_WINDOW": "Artifacts uploaded in the past 2 hours(the default window) are excluded from garbage collection", - "DRY_RUN_SUCCESS": "成功觸發測試執行" + "DRY_RUN_SUCCESS": "成功觸發測試執行", + "DELETE_BLOB_AND_MANIFEST": "{{blob}} blob(s) and {{manifest}} manifest(s) deleted", + "DELETE_BLOB": "{{blob}} blob(s) deleted", + "DELETE_MANIFEST": "{{manifest}} manifest(s) deleted", + "FREE_UP_SIZE": "{{size}} space freed up" }, "RETAG": { "MSG_SUCCESS": "Artifact 複製成功", From 31a46a16ccfffb946d81e08615e690e026f60642 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jun 2023 17:22:47 +0800 Subject: [PATCH 13/38] chore(deps): bump mheap/github-action-required-labels from 4 to 5 (#18805) Bumps [mheap/github-action-required-labels](https://github.com/mheap/github-action-required-labels) from 4 to 5. - [Release notes](https://github.com/mheap/github-action-required-labels/releases) - [Commits](https://github.com/mheap/github-action-required-labels/compare/v4...v5) --- updated-dependencies: - dependency-name: mheap/github-action-required-labels 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: Wang Yan --- .github/workflows/label_check.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/label_check.yaml b/.github/workflows/label_check.yaml index 9d491cde8bf..6c2cd53adcc 100644 --- a/.github/workflows/label_check.yaml +++ b/.github/workflows/label_check.yaml @@ -16,7 +16,7 @@ jobs: name: Check release-note label set runs-on: ubuntu-latest steps: - - uses: mheap/github-action-required-labels@v4 + - uses: mheap/github-action-required-labels@v5 with: mode: minimum count: 1 From ca94a23a74ab9ef03f9eb974d015ec7b9dc5ce4e Mon Sep 17 00:00:00 2001 From: Shijun Sun <30999793+AllForNothing@users.noreply.github.com> Date: Tue, 13 Jun 2023 10:45:05 +0800 Subject: [PATCH 14/38] Add Podman push command to the UI (#18810) 1. Fixes #18781 Signed-off-by: AllForNothing --- .../gc/gc-history/gc-history.component.html | 4 +--- .../gc/gc-history/gc-history.component.scss | 2 +- .../push-image/push-image.component.html | 17 +++++++++++++++++ .../push-image/push-image.component.spec.ts | 2 +- .../push-image/push-image.component.ts | 5 +++++ src/portal/src/i18n/lang/de-de-lang.json | 1 + src/portal/src/i18n/lang/en-us-lang.json | 1 + src/portal/src/i18n/lang/es-es-lang.json | 1 + src/portal/src/i18n/lang/fr-fr-lang.json | 1 + src/portal/src/i18n/lang/pt-br-lang.json | 1 + src/portal/src/i18n/lang/tr-tr-lang.json | 1 + src/portal/src/i18n/lang/zh-cn-lang.json | 1 + src/portal/src/i18n/lang/zh-tw-lang.json | 1 + 13 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc-history/gc-history.component.html b/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc-history/gc-history.component.html index f9a68dee1c2..211d44bedfb 100644 --- a/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc-history/gc-history.component.html +++ b/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc-history/gc-history.component.html @@ -76,8 +76,7 @@
'GC.DELETE_BLOB' | translate : { - blobs: getBlobs(job?.job_parameters), - manifest: getManifest(job?.job_parameters) + blob: getBlobs(job?.job_parameters), } }} @@ -90,7 +89,6 @@
'GC.DELETE_MANIFEST' | translate : { - blob: getBlobs(job?.job_parameters), manifest: getManifest(job?.job_parameters) } }} }}"> +
+
+ {{ 'PUSH_IMAGE.PODMAN' | translate }} + {{ 'PUSH_IMAGE.TITLE' | translate }} +
+
+ +
+
{{ 'PUSH_IMAGE.HELM' | translate }} diff --git a/src/portal/src/app/shared/components/push-image/push-image.component.spec.ts b/src/portal/src/app/shared/components/push-image/push-image.component.spec.ts index 5e92a9a4642..0eeae647f80 100644 --- a/src/portal/src/app/shared/components/push-image/push-image.component.spec.ts +++ b/src/portal/src/app/shared/components/push-image/push-image.component.spec.ts @@ -43,7 +43,7 @@ describe('PushImageButtonComponent (inline template)', () => { await fixture.whenStable(); let copyInputs: HTMLInputElement[] = fixture.nativeElement.querySelectorAll('.command-input'); - expect(copyInputs.length).toEqual(5); + expect(copyInputs.length).toEqual(6); expect(copyInputs[0].value.trim()).toEqual( `docker tag SOURCE_IMAGE[:TAG] https://testing.harbor.com/testing/REPOSITORY[:TAG]` ); diff --git a/src/portal/src/app/shared/components/push-image/push-image.component.ts b/src/portal/src/app/shared/components/push-image/push-image.component.ts index 02d7cb2a660..f4a05d083d4 100644 --- a/src/portal/src/app/shared/components/push-image/push-image.component.ts +++ b/src/portal/src/app/shared/components/push-image/push-image.component.ts @@ -27,6 +27,11 @@ export class PushImageButtonComponent { public get pushCommandImage(): string { return `docker push ${this.registryUrl}/${this.projectName}/REPOSITORY[:TAG]`; } + + public get podmanPushCommand(): string { + return `podman push IMAGE_ID ${this.registryUrl}/${this.projectName}/REPOSITORY[:TAG]`; + } + public get tagCommandChart(): string { return `helm package CHART_PATH`; } diff --git a/src/portal/src/i18n/lang/de-de-lang.json b/src/portal/src/i18n/lang/de-de-lang.json index 8838a1e40a9..d94c91b88fe 100644 --- a/src/portal/src/i18n/lang/de-de-lang.json +++ b/src/portal/src/i18n/lang/de-de-lang.json @@ -1035,6 +1035,7 @@ "PUSH_IMAGE": { "TITLE": "Push Befehl", "DOCKER": "Docker", + "PODMAN": "Podman", "HELM": "Helm", "CNAB": "CNAB", "TAG_COMMAND_CHART": "Paketiere ein Chart für dieses Projekt:", diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json index 23d4caa33ef..a16a7b67eee 100644 --- a/src/portal/src/i18n/lang/en-us-lang.json +++ b/src/portal/src/i18n/lang/en-us-lang.json @@ -1036,6 +1036,7 @@ "PUSH_IMAGE": { "TITLE": "Push Command", "DOCKER": "Docker", + "PODMAN": "Podman", "HELM": "Helm", "CNAB": "CNAB", "TAG_COMMAND_CHART": "Package a chart for this project:", diff --git a/src/portal/src/i18n/lang/es-es-lang.json b/src/portal/src/i18n/lang/es-es-lang.json index 2710a8d1f29..3fdabf31011 100644 --- a/src/portal/src/i18n/lang/es-es-lang.json +++ b/src/portal/src/i18n/lang/es-es-lang.json @@ -1034,6 +1034,7 @@ "PUSH_IMAGE": { "TITLE": "Push Command", "DOCKER": "Docker", + "PODMAN": "Podman", "HELM": "Helm", "CNAB": "CNAB", "TAG_COMMAND_CHART": "Package a chart for this project:", diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json index 7a11d89146d..bb056de1972 100644 --- a/src/portal/src/i18n/lang/fr-fr-lang.json +++ b/src/portal/src/i18n/lang/fr-fr-lang.json @@ -1007,6 +1007,7 @@ "PUSH_IMAGE": { "TITLE": "Commande de push", "DOCKER": "Docker", + "PODMAN": "Podman", "HELM": "Helm", "CNAB": "CNAB", "TAG_COMMAND_CHART": "Taguer une chart pour ce projet :", diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json index 9ff98904ffa..b51ce073b79 100644 --- a/src/portal/src/i18n/lang/pt-br-lang.json +++ b/src/portal/src/i18n/lang/pt-br-lang.json @@ -1032,6 +1032,7 @@ "PUSH_IMAGE": { "TITLE": "Comando Push", "DOCKER": "Docker", + "PODMAN": "Podman", "HELM": "Helm", "CNAB": "CNAB", "TAG_COMMAND_CHART": "Package a chart for this project:", diff --git a/src/portal/src/i18n/lang/tr-tr-lang.json b/src/portal/src/i18n/lang/tr-tr-lang.json index 195cb958a90..9bc75fa3463 100644 --- a/src/portal/src/i18n/lang/tr-tr-lang.json +++ b/src/portal/src/i18n/lang/tr-tr-lang.json @@ -1035,6 +1035,7 @@ "PUSH_IMAGE": { "TITLE": "Push Command", "DOCKER": "Docker", + "PODMAN": "Podman", "HELM": "Helm", "CNAB": "CNAB", "TAG_COMMAND_CHART": "Package a chart for this project:", diff --git a/src/portal/src/i18n/lang/zh-cn-lang.json b/src/portal/src/i18n/lang/zh-cn-lang.json index c8b8a6422f5..95d3a8d8fe3 100644 --- a/src/portal/src/i18n/lang/zh-cn-lang.json +++ b/src/portal/src/i18n/lang/zh-cn-lang.json @@ -1034,6 +1034,7 @@ "PUSH_IMAGE": { "TITLE": "推送命令", "DOCKER": "Docker", + "PODMAN": "Podman", "HELM": "Helm", "CNAB": "CNAB", "TAG_COMMAND_CHART": "在项目中打包 chart", diff --git a/src/portal/src/i18n/lang/zh-tw-lang.json b/src/portal/src/i18n/lang/zh-tw-lang.json index 2f651545a03..253d6a99002 100644 --- a/src/portal/src/i18n/lang/zh-tw-lang.json +++ b/src/portal/src/i18n/lang/zh-tw-lang.json @@ -1029,6 +1029,7 @@ "PUSH_IMAGE": { "TITLE": "推送映像檔的 Docker 命令", "DOCKER": "Docker", + "PODMAN": "Podman", "HELM": "Helm", "CNAB": "CNAB", "TAG_COMMAND_CHART": "為此專案打包 Chart", From b8229525375e96bb67c37411716875d13acde2ef Mon Sep 17 00:00:00 2001 From: Shijun Sun <30999793+AllForNothing@users.noreply.github.com> Date: Fri, 16 Jun 2023 12:29:58 +0800 Subject: [PATCH 15/38] Update the text for the oidc cli secret tooltip (#18814) Update the text for oidc cli secret tooltip 1. Update the text for all the i18n files Signed-off-by: AllForNothing --- 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 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/portal/src/i18n/lang/de-de-lang.json b/src/portal/src/i18n/lang/de-de-lang.json index d94c91b88fe..364e9920cb3 100644 --- a/src/portal/src/i18n/lang/de-de-lang.json +++ b/src/portal/src/i18n/lang/de-de-lang.json @@ -130,7 +130,7 @@ "RENAME_SUCCESS": "Umbenennen erfolgreich!", "RENAME_CONFIRM_INFO": "Warnung, ändern des Namens auf admin@harbor.local, dies kann nicht rückgängig gemacht werden.", "CLI_PASSWORD": "CLI Secret", - "CLI_PASSWORD_TIP": "Das CLI Secret kann als Passwort mittels docker/helm cli verwendet werden, um auf Harbor zuzugreifen.", + "CLI_PASSWORD_TIP": "Das CLI-Secret kann als Passwort für den Docker- oder Helm-Client verwendet werden. Bei aktiviertem OIDC-Autorisierungsmodus empfehlen wir dringend die Verwendung von Robot-Zugängen, da CLI-Secrets von der Gültigkeit des ID-Tokens abhängen und regelmäßiges Anmelden über die Benutzeroberfläche erfordern.", "COPY_SUCCESS": "Kopieren erfolgreich", "COPY_ERROR": "Kopieren fehlgeschlagen", "ADMIN_CLI_SECRET_BUTTON": "ERZEUGE SECRET", diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json index a16a7b67eee..1ac867f86a3 100644 --- a/src/portal/src/i18n/lang/en-us-lang.json +++ b/src/portal/src/i18n/lang/en-us-lang.json @@ -130,7 +130,7 @@ "RENAME_SUCCESS": "Rename success!", "RENAME_CONFIRM_INFO": "Warning, changing the name to admin@harbor.local can not be undone.", "CLI_PASSWORD": "CLI secret", - "CLI_PASSWORD_TIP": "You can use this cli secret as password when using docker/helm cli to access Harbor.", + "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.", "COPY_SUCCESS": "copy success", "COPY_ERROR": "copy failed", "ADMIN_CLI_SECRET_BUTTON": "GENERATE SECRET", diff --git a/src/portal/src/i18n/lang/es-es-lang.json b/src/portal/src/i18n/lang/es-es-lang.json index 3fdabf31011..9cd6c62ca96 100644 --- a/src/portal/src/i18n/lang/es-es-lang.json +++ b/src/portal/src/i18n/lang/es-es-lang.json @@ -130,7 +130,7 @@ "RENAME_SUCCESS": "Rename success!", "RENAME_CONFIRM_INFO": "Warning, changing the name to admin@harbor.local can not be undone.", "CLI_PASSWORD": "CLI secreto", - "CLI_PASSWORD_TIP": "Puede utilizar este generador CLI secreto como utilizando Docker / Helm CLI para acceder a puerto.", + "CLI_PASSWORD_TIP": "El secreto CLI puede utilizarse como contraseña, para Docker o para el cliente Helm. Con el modo de autenticación de OIDC activado, recomendamos encarecidamente usar cuentas de robot, ya que los secretos de CLI dependen de la validez del token de ID y requieren que el usuario inicie sesión regularmente en la interfaz de usuario para actualizar el token.", "COPY_SUCCESS": "Copiar el éxito", "COPY_ERROR": "Copia no", "ADMIN_CLI_SECRET_BUTTON": "GENERATE SECRET", diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json index bb056de1972..e8faad26e17 100644 --- a/src/portal/src/i18n/lang/fr-fr-lang.json +++ b/src/portal/src/i18n/lang/fr-fr-lang.json @@ -125,7 +125,7 @@ "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": "Vous pouvez utiliser ce secret comme mot de passe quand vous utilisez la CLI de Docker/Helm pour accéder à Harbor.", + "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.", "COPY_SUCCESS": "Copie effectuée", "COPY_ERROR": "Copie échouée", "NEW_SECRET": "Secret", diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json index b51ce073b79..b9e46d39707 100644 --- a/src/portal/src/i18n/lang/pt-br-lang.json +++ b/src/portal/src/i18n/lang/pt-br-lang.json @@ -129,7 +129,7 @@ "RENAME_SUCCESS": "Renomeado com sucesso!", "RENAME_CONFIRM_INFO": "Atenção do nome para admin@harbor.local não poderá ser desfeita.", "CLI_PASSWORD": "Segredo CLI", - "CLI_PASSWORD_TIP": "Você pode usar este segredo como senha ao usar docker/helm cli para acessar o Harbor.", + "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.", "COPY_SUCCESS": "Cópia feita com sucesso", "COPY_ERROR": "Cópia falhou", "ADMIN_CLI_SECRET_BUTTON": "REDEFINIR SEGREDO", diff --git a/src/portal/src/i18n/lang/tr-tr-lang.json b/src/portal/src/i18n/lang/tr-tr-lang.json index 9bc75fa3463..c53ff98161a 100644 --- a/src/portal/src/i18n/lang/tr-tr-lang.json +++ b/src/portal/src/i18n/lang/tr-tr-lang.json @@ -130,7 +130,7 @@ "RENAME_SUCCESS": "Yeniden adlandırma başarılı!", "RENAME_CONFIRM_INFO": "Uyarı, ismi admin@harbor.local olarak değiştirmek geri alınamaz.", "CLI_PASSWORD": "CLI şifresi", - "CLI_PASSWORD_TIP": "Harbour'a erişmek için docker / helm cli kullanırken bu cli sırrını şifre olarak kullanabilirsiniz.", + "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.", "COPY_SUCCESS": "kopyalama başarılı", "COPY_ERROR": "kopyalama başarısız", "ADMIN_CLI_SECRET_BUTTON": "ŞİFRE OLUŞTUR", diff --git a/src/portal/src/i18n/lang/zh-cn-lang.json b/src/portal/src/i18n/lang/zh-cn-lang.json index 95d3a8d8fe3..31bfa22cffb 100644 --- a/src/portal/src/i18n/lang/zh-cn-lang.json +++ b/src/portal/src/i18n/lang/zh-cn-lang.json @@ -129,7 +129,7 @@ "ADMIN_RENAME_BUTTON": "更改用户名", "RENAME_CONFIRM_INFO": "更改用户名为admin@harbor.local是无法撤销的, 您确定更改吗?", "CLI_PASSWORD": "CLI密码", - "CLI_PASSWORD_TIP": "使用docker/helm cli访问Harbor时,可以使用此cli密码作为密码。", + "CLI_PASSWORD_TIP": "CLI 密码可用作访问 Docker 或 Helm 客户端的密码。 启用 OIDC 授权模式后,我们强烈建议您使用机器人帐户来访问Docker 或 Helm 客户端,因为 CLI 密码的有效性取决于用户的 ID 令牌,这意味着您需要定期登录 UI 来刷新令牌。", "COPY_SUCCESS": "复制成功", "COPY_ERROR": "复制失败", "ADMIN_CLI_SECRET_BUTTON": "生成新的CLI密码", diff --git a/src/portal/src/i18n/lang/zh-tw-lang.json b/src/portal/src/i18n/lang/zh-tw-lang.json index 253d6a99002..6c3070159b2 100644 --- a/src/portal/src/i18n/lang/zh-tw-lang.json +++ b/src/portal/src/i18n/lang/zh-tw-lang.json @@ -129,7 +129,7 @@ "ADMIN_RENAME_BUTTON": "更改帳號名稱", "RENAME_CONFIRM_INFO": "更改帳號名稱為 admin@harbor.local 是無法撤回的,您確定要更改嗎?", "CLI_PASSWORD": "CLI 密碼", - "CLI_PASSWORD_TIP": "當存取 Harbor 的 Docker / Helm CLI 時,可以使用此 CLI 密碼作為密碼。", + "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.", "COPY_SUCCESS": "複製成功", "COPY_ERROR": "複製失敗", "ADMIN_CLI_SECRET_BUTTON": "產生新的 CLI 密碼", From 4035f438fb69d2ff0f88915ab3c40ccadd2e370a Mon Sep 17 00:00:00 2001 From: Bin Liu Date: Mon, 19 Jun 2023 14:19:21 +0800 Subject: [PATCH 16/38] jobservice: add DB to job logger config (#18821) jobservice: add DB to job logger config in readme Job logger support file, db and stdout, the comment should include the type of DB as well as file and stdout. Signed-off-by: bin liu --- src/jobservice/README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/jobservice/README.md b/src/jobservice/README.md index efacd760d6f..6afc1a7aabf 100644 --- a/src/jobservice/README.md +++ b/src/jobservice/README.md @@ -413,7 +413,7 @@ worker_pool: #Loggers for the running job job_loggers: - - name: "STD_OUTPUT" # logger backend name, only support "FILE" and "STD_OUTPUT" + - name: "STD_OUTPUT" # logger backend name, only support "DB", "FILE" and "STD_OUTPUT" level: "DEBUG" # INFO/DEBUG/WARNING/ERROR/FATAL - name: "FILE" level: "DEBUG" @@ -423,6 +423,10 @@ job_loggers: duration: 1 #days settings: # Customized settings of sweeper work_dir: "/tmp/job_logs" + - name: "DB" + level: "DEBUG" + sweeper: + duration: 1 #days #Loggers for the job service loggers: @@ -630,4 +634,4 @@ go build -a -o jobservice jobservice -c ``` -Enjoy it! \ No newline at end of file +Enjoy it! From c13e7e6fa6913fadd7005f1631de2f579653a3f3 Mon Sep 17 00:00:00 2001 From: Orlix <7236111+OrlinVasilev@users.noreply.github.com> Date: Mon, 19 Jun 2023 11:21:59 +0300 Subject: [PATCH 17/38] Add Dynatrace as adopter and fix master to main (#18823) Signed-off-by: OrlinVasilev --- ADOPTERS.md | 55 ++++++++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index bd9c3109a9f..3de57534e23 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -9,32 +9,35 @@ publicly at this time. There are many additional adopters of Harbor in the evaluating phase that will be added to this list as they transition to production deployments. -JD.com      -trendmicro        -DataYes        -axatp       

-360 Total Security      -talkingdata        -BoerSmart        -OpenEdutainment        -iFRE       

-BOCOIT        -wise2c        -HYDSoft        -CloudStar        -BeyondSoft        -ChinaMobile        -CaiCloud        -Rancher        -TenxCloud        -BingoCloud        +JD.com      +trendmicro        +DataYes        +axatp       

+360 Total Security      +talkingdata        +BoerSmart        +OpenEdutainment        +iFRE       

+BOCOIT        +wise2c        +HYDSoft        +CloudStar        +BeyondSoft        +ChinaMobile        +CaiCloud        +Rancher        +TenxCloud        +BingoCloud       

-SlamTec        -CloudChef        -Pivotal        -Netease Cloud        -Yanrongyun        -Anchore        +SlamTec        +CloudChef        +Pivotal        +Netease Cloud        +Yanrongyun        +Anchore        +Dynatrace        + + ## Success Stories @@ -86,4 +89,4 @@ ELK stack, as part of their CI/CD pipeline. ## Adding your logo -If you would like to add your logo here and to the `Users and Partners of Harbor` section of the website, add a PNG or SVG version of your logo to the [adopters](https://github.com/goharbor/website/tree/master/docs/img/adopters) directory of the [website](https://github.com/goharbor/website) and submit a pull request with your change. Name the image file something that reflects your company (e.g., if your company is called Acme, name the image acme.png). We will follow up and make the change in the goharbor.io website as well. +If you would like to add your logo here and to the `Users and Partners of Harbor` section of the website, add a PNG or SVG version of your logo to the [adopters](https://github.com/goharbor/website/tree/main/docs/img/adopters) directory of the [website](https://github.com/goharbor/website) and submit a pull request with your change. Name the image file something that reflects your company (e.g., if your company is called Acme, name the image acme.png). We will follow up and make the change in the goharbor.io website as well. From 46f1fb0fd32a6372ff5bd4472dbf8740e9c82ff1 Mon Sep 17 00:00:00 2001 From: Orlix <7236111+OrlinVasilev@users.noreply.github.com> Date: Tue, 20 Jun 2023 16:51:39 +0300 Subject: [PATCH 18/38] Update ADOPTERS.md Signed-off-by: Orlix <7236111+OrlinVasilev@users.noreply.github.com> --- ADOPTERS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index 3de57534e23..559b271aa97 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -35,7 +35,7 @@ be added to this list as they transition to production deployments. Netease Cloud        Yanrongyun        Anchore        -Dynatrace        +Dynatrace        From 1d6c02f52dc12c1b4ac33d28adc36f3d0c944629 Mon Sep 17 00:00:00 2001 From: Bin Liu Date: Tue, 27 Jun 2023 09:13:47 +0800 Subject: [PATCH 19/38] jobservice: update readme (#18849) To reflect the newest job interface, and the missing parts of DB job service logger. Signed-off-by: bin liu --- src/jobservice/README.md | 37 +++++++++++++++++++++++++---------- src/jobservice/mgt/manager.go | 2 +- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/jobservice/README.md b/src/jobservice/README.md index 6afc1a7aabf..082a2d2f184 100644 --- a/src/jobservice/README.md +++ b/src/jobservice/README.md @@ -68,6 +68,7 @@ To let the job service recognize the job, the implementation of job should follo A valid job must implement the job interface. For the details of each method defined in the job interface, you can refer the comments attached with the method. ```go + // Interface defines the related injection and run entry methods. type Interface interface { // Declare how many times the job can be retried if failed. @@ -76,7 +77,13 @@ type Interface interface { // uint: the failure count allowed. If it is set to 0, then default value 4 is used. MaxFails() uint - // Tell the worker pool if retry the failed job when the fails is + // Max currency of the job. Unlike the WorkerPool concurrency, it controls the limit on the number jobs of that type + // that can be active at one time by within a single redis instance. + // + // The default value is 0, which means "no limit on job concurrency". + MaxCurrency() uint + + // Tell the worker worker if retry the failed job when the fails is // still less that the number declared by the method 'MaxFails'. // // Returns: @@ -87,18 +94,18 @@ type Interface interface { // // Return: // error if parameters are not valid. NOTES: If no parameters needed, directly return nil. - Validate(params map[string]interface{}) error + Validate(params Parameters) error // Run the business logic here. // The related arguments will be injected by the workerpool. // - // ctx env.JobContext : Job execution context. + // ctx Context : Job execution context. // params map[string]interface{} : parameters with key-pair style for the job execution. // // Returns: // error if failed to run. NOTES: If job is stopped or cancelled, a specified error should be returned // - Run(ctx env.JobContext, params map[string]interface{}) error + Run(ctx Context, params Parameters) error } ``` @@ -180,14 +187,19 @@ func (dj *DemoJob) MaxFails() uint { return 3 } +// MaxCurrency is implementation of same method in Interface. +func (dj *DemoJob) MaxCurrency() uint { + return 1 +} + // ShouldRetry ... func (dj *DemoJob) ShouldRetry() bool { return true } // Validate is implementation of same method in Interface. -func (dj *DemoJob) Validate(params map[string]interface{}) error { - if params == nil || len(params) == 0 { +func (dj *DemoJob) Validate(params job.Parameters) error { + if len(params) == 0 { return errors.New("parameters required for replication job") } name, ok := params["image"] @@ -196,14 +208,14 @@ func (dj *DemoJob) Validate(params map[string]interface{}) error { } if !strings.HasPrefix(name.(string), "demo") { - return fmt.Errorf("expected '%s' but got '%s'", "demo steven", name) + return fmt.Errorf("expected '%s' but got '%s'", "demo *", name) } return nil } // Run the replication logic here. -func (dj *DemoJob) Run(ctx env.JobContext, params map[string]interface{}) error { +func (dj *DemoJob) Run(ctx job.Context, params job.Parameters) error { logger := ctx.GetLogger() defer func() { @@ -292,7 +304,7 @@ Any jobs can launch new jobs through the launch function in the job context. All ```go -func (j *Job) Run(ctx env.JobContext, params map[string]interface{}) error{ +func (j *Job) Run(ctx job.Context, params job.Parameters) error{ // ... subJob, err := ctx.LaunchJob(models.JobRequest{}) // ... @@ -333,6 +345,7 @@ var knownLoggers = map[string]*Declaration{ So far, only the following two backends are supported: * **STD_OUTPUT**: Output the log to the std stream (stdout/stderr) +* **DB**: Output the log to the database with the table name `job_log` * **FILE**: Output the log to the log files * sweeper supports * getter supports @@ -354,7 +367,7 @@ An example: ```yaml #Loggers loggers: - - name: "STD_OUTPUT" # logger backend name, only support "FILE" and "STD_OUTPUT" + - name: "STD_OUTPUT" # logger backend name, only support "DB", "FILE" and "STD_OUTPUT" level: "DEBUG" # INFO/DEBUG/WARNING/ERROR/FATAL - name: "FILE" level: "DEBUG" @@ -364,6 +377,10 @@ loggers: duration: 1 #days settings: # Customized settings of sweeper work_dir: "/tmp/job_logs" + - name: "DB" + level: "DEBUG" + sweeper: + duration: 1 #days ``` ## Configuration diff --git a/src/jobservice/mgt/manager.go b/src/jobservice/mgt/manager.go index 2c6f2afdb11..e28b8bf5057 100644 --- a/src/jobservice/mgt/manager.go +++ b/src/jobservice/mgt/manager.go @@ -33,7 +33,7 @@ import ( "github.com/goharbor/harbor/src/lib/errors" ) -// Manager defies the related operations to handle the management of job stats. +// Manager defines the related operations to handle the management of job stats. type Manager interface { // Get the stats data of all kinds of jobs. // Data returned by pagination. From d36ca805b430d8d516c0f3aa49255c5887159745 Mon Sep 17 00:00:00 2001 From: Shijun Sun <30999793+AllForNothing@users.noreply.github.com> Date: Tue, 27 Jun 2023 11:12:41 +0800 Subject: [PATCH 20/38] Add new client Podman to the pull command (#18857) 1.Fixes #18832 Signed-off-by: AllForNothing --- .../artifact-list-tab.component.html | 20 +-- .../artifact-list-tab.component.ts | 29 ---- .../pull-command/pull-command.component.html | 131 +++++++++++++++ .../pull-command/pull-command.component.scss | 8 + .../pull-command.component.spec.ts | 22 +++ .../pull-command/pull-command.component.ts | 152 ++++++++++++++++++ .../artifact-tag/artifact-tag.component.html | 86 +++++----- .../artifact-tag/artifact-tag.component.scss | 15 +- .../artifact-tag/artifact-tag.component.ts | 44 +---- .../repository/artifact/artifact.module.ts | 2 + .../project/repository/artifact/artifact.ts | 54 +++++-- 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/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 +- 19 files changed, 439 insertions(+), 148 deletions(-) create mode 100644 src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/pull-command/pull-command.component.html create mode 100644 src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/pull-command/pull-command.component.scss create mode 100644 src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/pull-command/pull-command.component.spec.ts create mode 100644 src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/pull-command/pull-command.component.ts 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 508d490f11c..c30969817c6 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 @@ -126,6 +126,12 @@
+ - - - {{ 'REPOSITORY.PULL_COMMAND' | translate }} - - {{ 'REPOSITORY.PLATFORM' | translate }} @@ -244,15 +245,6 @@
- - -
{{ artifact.platform?.os }} 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 6c289db0ffd..72e0459f18d 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 @@ -57,9 +57,6 @@ import { ArtifactFilterEvent, ArtifactFront as Artifact, ArtifactFront, - ArtifactType, - getPullCommandByDigest, - getPullCommandByTag, } from '../../../artifact'; import { Project } from '../../../../../project'; import { ArtifactService as NewArtifactService } from '../../../../../../../../../ng-swagger-gen/services/artifact.service'; @@ -458,32 +455,6 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { this.clrLoad(st); } - getPullCommand(artifact: Artifact): string { - let pullCommand: string = ''; - if ( - artifact.type === ArtifactType.CHART && - artifact.tags && - artifact.tags[0] - ) { - pullCommand = getPullCommandByTag( - artifact.type, - `${this.registryUrl ? this.registryUrl : location.hostname}/${ - this.projectName - }/${this.repoName}`, - artifact.tags[0]?.name - ); - } else { - pullCommand = getPullCommandByDigest( - artifact.type, - `${this.registryUrl ? this.registryUrl : location.hostname}/${ - this.projectName - }/${this.repoName}`, - artifact.digest - ); - } - return pullCommand; - } - canAddLabel(): boolean { if (this.selectedRow && this.selectedRow.length === 1) { return true; diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/pull-command/pull-command.component.html b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/pull-command/pull-command.component.html new file mode 100644 index 00000000000..6fb33aa5456 --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/pull-command/pull-command.component.html @@ -0,0 +1,131 @@ + + + + +
+ + {{ 'PUSH_IMAGE.DOCKER' | translate }} +
+
+ + {{ 'PUSH_IMAGE.PODMAN' | translate }} +
+
+
+ + {{ 'PUSH_IMAGE.CNAB' | translate }} +
+
+ + {{ 'PUSH_IMAGE.HELM' | translate }} +
+
+
+ + + + + +
+ + {{ 'PUSH_IMAGE.DOCKER' | translate }} +
+
+ + {{ 'PUSH_IMAGE.PODMAN' | translate }} +
+
+
+ + {{ 'PUSH_IMAGE.CNAB' | translate }} +
+
+ + {{ 'PUSH_IMAGE.HELM' | translate }} +
+
+
diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/pull-command/pull-command.component.scss b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/pull-command/pull-command.component.scss new file mode 100644 index 00000000000..4740247d209 --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/pull-command/pull-command.component.scss @@ -0,0 +1,8 @@ +.copy-pull-command { + font-size: 14px; +} + +.flex { + display: flex; + align-items: flex-end; +} diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/pull-command/pull-command.component.spec.ts b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/pull-command/pull-command.component.spec.ts new file mode 100644 index 00000000000..d2f907d6c2e --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/pull-command/pull-command.component.spec.ts @@ -0,0 +1,22 @@ +import { PullCommandComponent } from './pull-command.component'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { SharedTestingModule } from '../../../../../../../../shared/shared.module'; + +describe('PullCommandComponent', () => { + let component: PullCommandComponent; + let fixture: ComponentFixture; + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [PullCommandComponent], + imports: [SharedTestingModule], + }).compileComponents(); + + fixture = TestBed.createComponent(PullCommandComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/pull-command/pull-command.component.ts b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/pull-command/pull-command.component.ts new file mode 100644 index 00000000000..b0e34e2deec --- /dev/null +++ b/src/portal/src/app/base/project/repository/artifact/artifact-list-page/artifact-list/artifact-list-tab/pull-command/pull-command.component.ts @@ -0,0 +1,152 @@ +import { Component, Input } from '@angular/core'; +import { + AccessoryType, + ArtifactFront as Artifact, + ArtifactType, + Clients, + getPullCommandByDigest, + getPullCommandByTag, + hasPullCommand, +} from '../../../../artifact'; +import { Tag } from '../../../../../../../../../../ng-swagger-gen/models/tag'; + +@Component({ + selector: 'app-pull-command', + templateUrl: './pull-command.component.html', + styleUrls: ['./pull-command.component.scss'], +}) +export class PullCommandComponent { + @Input() + isTagMode: boolean = false; // tagMode is for tag list datagrid, + @Input() + projectName: string; + @Input() + registryUrl: string; + @Input() + repoName: string; + @Input() + selectedRow: Artifact[]; + + // for tagMode + @Input() + selectedTags: Tag[]; + @Input() + artifact: Artifact; + @Input() + accessoryType: string; + + hasPullCommand(artifact: Artifact): boolean { + return hasPullCommand(artifact); + } + + isImage(artifact: Artifact): boolean { + return artifact.type === ArtifactType.IMAGE; + } + + isCNAB(artifact: Artifact): boolean { + return artifact.type === ArtifactType.CNAB; + } + + isChart(artifact: Artifact): boolean { + return artifact.type === ArtifactType.CHART; + } + + getPullCommandForDocker(artifact: Artifact): string { + return getPullCommandByDigest( + artifact.type, + `${this.registryUrl ? this.registryUrl : location.hostname}/${ + this.projectName + }/${this.repoName}`, + artifact.digest, + Clients.DOCKER + ); + } + + getPullCommandForPadMan(artifact: Artifact): string { + return getPullCommandByDigest( + artifact.type, + `${this.registryUrl ? this.registryUrl : location.hostname}/${ + this.projectName + }/${this.repoName}`, + artifact.digest, + Clients.PODMAN + ); + } + + getPullCommandForCNAB(artifact: Artifact): string { + return getPullCommandByDigest( + artifact.type, + `${this.registryUrl ? this.registryUrl : location.hostname}/${ + this.projectName + }/${this.repoName}`, + artifact.digest, + Clients.CNAB + ); + } + + getPullCommandForChart(artifact: Artifact): string { + return getPullCommandByTag( + artifact.type, + `${this.registryUrl ? this.registryUrl : location.hostname}/${ + this.projectName + }/${this.repoName}`, + artifact.tags[0].name, + Clients.CHART + ); + } + + // For tagMode + hasPullCommandForTag(artifact): boolean { + return ( + (artifact?.type === ArtifactType.IMAGE || + artifact?.type === ArtifactType.CHART || + artifact?.type === ArtifactType.CNAB) && + this.accessoryType !== AccessoryType.COSIGN && + this.accessoryType !== AccessoryType.NYDUS + ); + } + + getPullCommandForDockerByTag(artifact: Artifact): string { + return getPullCommandByTag( + artifact.type, + `${this.registryUrl ? this.registryUrl : location.hostname}/${ + this.projectName + }/${this.repoName}`, + this.selectedTags[0].name, + Clients.DOCKER + ); + } + + getPullCommandForPadManByTag(artifact: Artifact): string { + return getPullCommandByTag( + artifact.type, + `${this.registryUrl ? this.registryUrl : location.hostname}/${ + this.projectName + }/${this.repoName}`, + this.selectedTags[0].name, + Clients.PODMAN + ); + } + + getPullCommandForCNABByTag(artifact: Artifact): string { + return getPullCommandByTag( + artifact.type, + `${this.registryUrl ? this.registryUrl : location.hostname}/${ + this.projectName + }/${this.repoName}`, + this.selectedTags[0].name, + Clients.CNAB + ); + } + + getPullCommandForChartByTag(artifact: Artifact): string { + return getPullCommandByTag( + artifact.type, + `${this.registryUrl ? this.registryUrl : location.hostname}/${ + this.projectName + }/${this.repoName}`, + this.selectedTags[0].name, + Clients.CHART + ); + } +} diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-tag/artifact-tag.component.html b/src/portal/src/app/base/project/repository/artifact/artifact-tag/artifact-tag.component.html index 3b3bf7a5530..a6b86826078 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-tag/artifact-tag.component.html +++ b/src/portal/src/app/base/project/repository/artifact/artifact-tag/artifact-tag.component.html @@ -5,36 +5,52 @@

{{ 'REPOSITORY.TAGS' | translate }}

(clrDgRefresh)="getCurrentArtifactTags($event)" [(clrDgSelected)]="selectedRow"> - - - - - +
+
+ + +
+
+ + + + +
+
+
{{ 'REPOSITORY.TAGS' | translate }}
{{ 'TAG.NAME' | translate }} - {{ - 'REPOSITORY.PULL_COMMAND' | translate - }} {{ 'TAG.PULL_TIME' | translate }} @@ -128,13 +141,6 @@

{{ 'REPOSITORY.TAGS' | translate }}

}} - - - {{ tag.pull_time !== availableTime ? (tag.pull_time | harborDatetime : 'short') diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-tag/artifact-tag.component.scss b/src/portal/src/app/base/project/repository/artifact/artifact-tag/artifact-tag.component.scss index 6b36c09939e..0bdccd2008b 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-tag/artifact-tag.component.scss +++ b/src/portal/src/app/base/project/repository/artifact/artifact-tag/artifact-tag.component.scss @@ -39,9 +39,6 @@ } .refresh-btn { - float: right; - margin-top: 15px; - margin-right: 20px; cursor: pointer; } @@ -50,3 +47,15 @@ right: -.7rem; top: 1.2rem; } + +.action-bar { + display: flex; + align-items: center; + justify-content: space-between; +} + +.right-pos { + margin-right: 35px; + display: flex; + align-items: center; +} diff --git a/src/portal/src/app/base/project/repository/artifact/artifact-tag/artifact-tag.component.ts b/src/portal/src/app/base/project/repository/artifact/artifact-tag/artifact-tag.component.ts index 112abfa4905..fe4ab6922af 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact-tag/artifact-tag.component.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact-tag/artifact-tag.component.ts @@ -23,13 +23,7 @@ import { OperateInfo, OperationState, } from '../../../../../shared/components/operation/operate'; -import { - AccessoryQueryParams, - AccessoryType, - ArtifactFront as Artifact, - ArtifactType, - getPullCommandByTag, -} from '../artifact'; +import { AccessoryQueryParams, ArtifactFront as Artifact } from '../artifact'; import { ArtifactService } from '../../../../../../../ng-swagger-gen/services/artifact.service'; import { Tag } from '../../../../../../../ng-swagger-gen/models/tag'; import { @@ -49,7 +43,6 @@ import { PageSizeMapKeys, setPageSizeToLocalStorage, } from '../../../../../shared/units/utils'; -import { AppConfigService } from '../../../../../services/app-config.service'; import { errorHandler } from '../../../../../shared/units/shared.utils'; import { ConfirmationDialogComponent } from '../../../../../shared/components/confirmation-dialog'; import { ConfirmationMessage } from '../../../../global-confirmation-dialog/confirmation-message'; @@ -59,8 +52,6 @@ import { ActivatedRoute } from '@angular/router'; class InitTag { name = ''; } -const DeleteTagWithNotoryCommand1 = 'notary -s https://'; -const DeleteTagWithNotoryCommand2 = ':4443 -d ~/.docker/trust remove -p '; @Component({ selector: 'artifact-tag', templateUrl: './artifact-tag.component.html', @@ -73,7 +64,6 @@ export class ArtifactTagComponent implements OnInit, OnDestroy { @Input() projectId: number; @Input() repositoryName: string; newTagName = new InitTag(); - newTagForm: NgForm; @ViewChild('newTagForm', { static: true }) currentForm: NgForm; selectedRow: Tag[] = []; isTagNameExist = false; @@ -87,7 +77,6 @@ export class ArtifactTagComponent implements OnInit, OnDestroy { hasCreateTagPermission: boolean; totalCount: number = 0; - allTags: Tag[] = []; currentTags: Tag[] = []; pageSize: number = getPageSizeFromLocalStorage( PageSizeMapKeys.ARTIFACT_TAGS_COMPONENT @@ -341,12 +330,6 @@ export class ArtifactTagComponent implements OnInit, OnDestroy { } } } - deletePort(url): string { - if (url && url.indexOf(':') !== -1) { - return url.split(':')[0]; - } - return url; - } delOperate(tag: Tag): Observable | null { // init operation info let operMessage = new OperateInfo(); @@ -387,10 +370,6 @@ export class ArtifactTagComponent implements OnInit, OnDestroy { this.isTagNameExist = false; } } - toggleTagListOpenOrClose() { - this.openTag = !this.openTag; - this.newTagformShow = false; - } hasImmutableOnTag(): boolean { return this.selectedRow.some(artifact => artifact.immutable); } @@ -413,25 +392,4 @@ export class ArtifactTagComponent implements OnInit, OnDestroy { } return location.hostname; } - hasPullCommand(): boolean { - return ( - this.artifactDetails && - (this.artifactDetails.type === ArtifactType.IMAGE || - this.artifactDetails.type === ArtifactType.CHART || - this.artifactDetails.type === ArtifactType.CNAB) && - this.accessoryType !== AccessoryType.COSIGN && - this.accessoryType !== AccessoryType.NYDUS - ); - } - getPullCommand(tag: Tag): string { - let pullCommand: string = ''; - if (tag && tag.name && this.artifactDetails) { - pullCommand = getPullCommandByTag( - this.artifactDetails?.type, - `${this.registryUrl}/${this.projectName}/${this.repositoryName}`, - tag.name - ); - } - return pullCommand; - } } 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 05eef36636c..528fb0e9f75 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 @@ -24,6 +24,7 @@ import { ArtifactListPageService } from './artifact-list-page/artifact-list-page import { CopyArtifactComponent } from './artifact-list-page/artifact-list/artifact-list-tab/copy-artifact/copy-artifact.component'; 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'; const routes: Routes = [ { @@ -88,6 +89,7 @@ const routes: Routes = [ CopyArtifactComponent, CopyDigestComponent, ArtifactFilterComponent, + PullCommandComponent, ], imports: [RouterModule.forChild(routes), SharedModule], providers: [ 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 25a2036e3e8..003cc3be42e 100644 --- a/src/portal/src/app/base/project/repository/artifact/artifact.ts +++ b/src/portal/src/app/base/project/repository/artifact/artifact.ts @@ -89,41 +89,59 @@ export enum AccessoryQueryParams { ACCESSORY_TYPE = 'accessoryType', } +export function hasPullCommand(artifact: Artifact): boolean { + return ( + artifact.type === ArtifactType.IMAGE || + artifact.type === ArtifactType.CNAB || + artifact.type === ArtifactType.CHART + ); +} + export function getPullCommandByDigest( artifactType: string, url: string, - digest: string + digest: string, + client: Clients ): string { - let pullCommand: string = ''; if (artifactType && url && digest) { if (artifactType === ArtifactType.IMAGE) { - pullCommand = `docker pull ${url}@${digest}`; + if (client === Clients.DOCKER) { + return `${Clients.DOCKER} pull ${url}@${digest}`; + } + if (client === Clients.PODMAN) { + return `${Clients.PODMAN} pull ${url}@${digest}`; + } } if (artifactType === ArtifactType.CNAB) { - pullCommand = `cnab-to-oci pull ${url}@${digest}`; + return `${Clients.CNAB} pull ${url}@${digest}`; } } - return pullCommand; + return null; } export function getPullCommandByTag( artifactType: string, url: string, - tag: string + tag: string, + client: Clients ): string { - let pullCommand: string = ''; if (artifactType && url && tag) { if (artifactType === ArtifactType.IMAGE) { - pullCommand = `docker pull ${url}:${tag}`; + if (client === Clients.DOCKER) { + return `${Clients.DOCKER} pull ${url}:${tag}`; + } + if (client === Clients.PODMAN) { + return `${Clients.PODMAN} pull ${url}:${tag}`; + } } if (artifactType === ArtifactType.CNAB) { - pullCommand = `cnab-to-oci pull ${url}:${tag}`; + return `cnab-to-oci pull ${url}:${tag}`; } if (artifactType === ArtifactType.CHART) { - pullCommand = `helm pull oci://${url} --version ${tag}`; + return `helm pull oci://${url} --version ${tag}`; } } - return pullCommand; + return null; } export interface ArtifactFilterEvent { @@ -133,3 +151,17 @@ export interface ArtifactFilterEvent { isInputTag?: boolean; label?: Label; } + +export enum Clients { + DOCKER = 'docker', + PODMAN = 'podman', + CHART = 'helm', + CNAB = 'cnab-to-oci', +} + +export enum ClientNames { + DOCKER = 'Docker', + PODMAN = 'Podman', + CHART = 'Helm', + CNAB = 'CNAB', +} diff --git a/src/portal/src/i18n/lang/de-de-lang.json b/src/portal/src/i18n/lang/de-de-lang.json index 364e9920cb3..f425cc341bc 100644 --- a/src/portal/src/i18n/lang/de-de-lang.json +++ b/src/portal/src/i18n/lang/de-de-lang.json @@ -1044,7 +1044,8 @@ "TOOLTIP": "Befehlreferenz um ein Artefakt in das Projekt zu pushen.", "TAG_COMMAND": "Tag ein Image für dieses Projekt:", "PUSH_COMMAND": "Push ein Image für dieses Projekt:", - "COPY_ERROR": "Kopieren fehlgeschlagen, bitte die Befehlsreferenz manuell kopieren." + "COPY_ERROR": "Kopieren fehlgeschlagen, bitte die Befehlsreferenz manuell kopieren.", + "COPY_PULL_COMMAND": "COPY PULL COMMAND" }, "ARTIFACT": { "FILTER_FOR_ARTIFACTS": "Filter Artefakte", diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json index 1ac867f86a3..e68f0ae1e9f 100644 --- a/src/portal/src/i18n/lang/en-us-lang.json +++ b/src/portal/src/i18n/lang/en-us-lang.json @@ -1045,7 +1045,8 @@ "TOOLTIP": "Command references for pushing an artifact to this project.", "TAG_COMMAND": "Tag an image for this project:", "PUSH_COMMAND": "Push an image to this project:", - "COPY_ERROR": "Copy failed, please try to manually copy the command references." + "COPY_ERROR": "Copy failed, please try to manually copy the command references.", + "COPY_PULL_COMMAND": "COPY PULL COMMAND" }, "ARTIFACT": { "FILTER_FOR_ARTIFACTS": "Filter Artifacts", diff --git a/src/portal/src/i18n/lang/es-es-lang.json b/src/portal/src/i18n/lang/es-es-lang.json index 9cd6c62ca96..fdf1b50eaf4 100644 --- a/src/portal/src/i18n/lang/es-es-lang.json +++ b/src/portal/src/i18n/lang/es-es-lang.json @@ -1043,7 +1043,8 @@ "TOOLTIP": "Command references for pushing an artifact to this project.", "TAG_COMMAND": "Tag an image for this project:", "PUSH_COMMAND": "Push an image to this project:", - "COPY_ERROR": "Copy failed, please try to manually copy the command references." + "COPY_ERROR": "Copy failed, please try to manually copy the command references.", + "COPY_PULL_COMMAND": "COPY PULL COMMAND" }, "ARTIFACT": { "FILTER_FOR_ARTIFACTS": "Filter Artifacts", diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json index e8faad26e17..9ffa5070659 100644 --- a/src/portal/src/i18n/lang/fr-fr-lang.json +++ b/src/portal/src/i18n/lang/fr-fr-lang.json @@ -1016,7 +1016,8 @@ "TOOLTIP": "Commandes pour push un artefact dans ce projet.", "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_ERROR": "Copie échouée, veuillez essayer de copier manuellement les commandes de référence.", + "COPY_PULL_COMMAND": "COPY PULL COMMAND" }, "ARTIFACT": { "FILTER_FOR_ARTIFACTS": "Filtrer les artefacts", diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json index b9e46d39707..aea1bdaf431 100644 --- a/src/portal/src/i18n/lang/pt-br-lang.json +++ b/src/portal/src/i18n/lang/pt-br-lang.json @@ -1041,7 +1041,8 @@ "TOOLTIP": "Referência de comandos para enviar artefatos a este projeto.", "TAG_COMMAND": "Colocar tag em uma imagem deste projeto:", "PUSH_COMMAND": "Envia uma imagem a esse projeto:", - "COPY_ERROR": "Cópia falhou, por favor tente copiar o comando de referência manualmente." + "COPY_ERROR": "Cópia falhou, por favor tente copiar o comando de referência manualmente.", + "COPY_PULL_COMMAND": "COPY PULL COMMAND" }, "ARTIFACT": { "FILTER_FOR_ARTIFACTS": "Filtrar", diff --git a/src/portal/src/i18n/lang/tr-tr-lang.json b/src/portal/src/i18n/lang/tr-tr-lang.json index c53ff98161a..07246c927aa 100644 --- a/src/portal/src/i18n/lang/tr-tr-lang.json +++ b/src/portal/src/i18n/lang/tr-tr-lang.json @@ -1044,7 +1044,8 @@ "TOOLTIP": "Command references for pushing an artifact to this project.", "TAG_COMMAND": "Bu proje için bir imaj etiketleyin:", "PUSH_COMMAND": "Bu projeye bir imaj gönder:", - "COPY_ERROR": "Kopyalama başarısız oldu, lütfen komut referanslarını el ile kopyalamayı deneyin." + "COPY_ERROR": "Kopyalama başarısız oldu, lütfen komut referanslarını el ile kopyalamayı deneyin.", + "COPY_PULL_COMMAND": "COPY PULL COMMAND" }, "ARTIFACT": { "FILTER_FOR_ARTIFACTS": "Filter Artifacts", diff --git a/src/portal/src/i18n/lang/zh-cn-lang.json b/src/portal/src/i18n/lang/zh-cn-lang.json index 31bfa22cffb..982b7ce29ed 100644 --- a/src/portal/src/i18n/lang/zh-cn-lang.json +++ b/src/portal/src/i18n/lang/zh-cn-lang.json @@ -1043,7 +1043,8 @@ "TOOLTIP": "推送一个 artifact 到当前项目的参考命令。", "TAG_COMMAND": "在项目中标记镜像:", "PUSH_COMMAND": "推送镜像到当前项目:", - "COPY_ERROR": "拷贝失败,请尝试手动拷贝参考命令。" + "COPY_ERROR": "拷贝失败,请尝试手动拷贝参考命令。", + "COPY_PULL_COMMAND": "复制拉取命令" }, "ARTIFACT": { "FILTER_FOR_ARTIFACTS": "Filter Artifacts", diff --git a/src/portal/src/i18n/lang/zh-tw-lang.json b/src/portal/src/i18n/lang/zh-tw-lang.json index 6c3070159b2..9c81b58e784 100644 --- a/src/portal/src/i18n/lang/zh-tw-lang.json +++ b/src/portal/src/i18n/lang/zh-tw-lang.json @@ -1038,7 +1038,8 @@ "TOOLTIP": "將映像檔推送至此專案的參考命令。", "TAG_COMMAND": "在項目中標記映像檔:", "PUSH_COMMAND": "推送映像檔到目前項目:", - "COPY_ERROR": "複製失敗,請嘗試手動複製參考命令。" + "COPY_ERROR": "複製失敗,請嘗試手動複製參考命令。", + "COPY_PULL_COMMAND": "COPY PULL COMMAND" }, "ARTIFACT": { "FILTER_FOR_ARTIFACTS": "篩選器", From 02a1c417d4242c190d050c3eb18870232e02b276 Mon Sep 17 00:00:00 2001 From: Chlins Zhang Date: Thu, 29 Jun 2023 11:28:19 +0800 Subject: [PATCH 21/38] refactor: migrate the redis command keys to scan (#18825) Refine the cache interface, migrate the Keys to Scan, change the redis underlying keys command to scan. Signed-off-by: chlins --- src/go.mod | 1 - src/lib/cache/cache.go | 13 +- src/lib/cache/cache_test.go | 13 +- src/lib/cache/helper_test.go | 9 +- src/lib/cache/memory/memory.go | 58 ++++++-- src/lib/cache/memory/memory_test.go | 71 ++++++--- src/lib/cache/mock_cache_test.go | 133 +++++++++++++++++ src/lib/cache/redis/redis.go | 50 ++++--- src/lib/cache/redis/redis_test.go | 70 ++++++--- src/pkg/cached/artifact/redis/manager_test.go | 10 +- src/pkg/cached/base_manager.go | 19 ++- src/pkg/cached/base_manager_test.go | 13 +- src/pkg/cached/manifest/redis/manager_test.go | 10 +- src/pkg/cached/project/redis/manager_test.go | 10 +- .../cached/project_metadata/redis/manager.go | 8 +- .../project_metadata/redis/manager_test.go | 10 +- .../cached/repository/redis/manager_test.go | 10 +- src/pkg/task/dao/execution.go | 7 +- src/pkg/task/dao/execution_test.go | 4 +- src/testing/lib/cache/cache.go | 64 ++++----- src/testing/lib/cache/iterator.go | 57 ++++++++ src/testing/lib/lib.go | 1 + src/testing/lib/libcache/cache.go | 136 ++++++++++++++++++ 23 files changed, 615 insertions(+), 162 deletions(-) create mode 100644 src/lib/cache/mock_cache_test.go create mode 100644 src/testing/lib/cache/iterator.go create mode 100644 src/testing/lib/libcache/cache.go diff --git a/src/go.mod b/src/go.mod index b12401187fc..04d4529db35 100644 --- a/src/go.mod +++ b/src/go.mod @@ -47,7 +47,6 @@ require ( github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.14.0 github.com/robfig/cron/v3 v3.0.0 github.com/spf13/viper v1.8.1 diff --git a/src/lib/cache/cache.go b/src/lib/cache/cache.go index aa23efaaa64..85f1b921e6d 100644 --- a/src/lib/cache/cache.go +++ b/src/lib/cache/cache.go @@ -40,6 +40,14 @@ var ( ErrNotFound = errors.New("key not found") ) +// Iterator returns the ScanIterator +type Iterator interface { + Next(ctx context.Context) bool + Val() string +} + +//go:generate mockery --name Cache --output . --outpkg cache --filename mock_cache_test.go --structname mockCache --inpackage + // Cache cache interface type Cache interface { // Contains returns true if key exists @@ -57,8 +65,9 @@ type Cache interface { // Save cache the value by key Save(ctx context.Context, key string, value interface{}, expiration ...time.Duration) error - // Keys returns the key matched by prefixes - Keys(ctx context.Context, prefixes ...string) ([]string, error) + // Scan scans the keys matched by match string + // NOTICE: memory cache does not support use wildcard, compared by strings.Contains + Scan(ctx context.Context, match string) (Iterator, error) } var ( diff --git a/src/lib/cache/cache_test.go b/src/lib/cache/cache_test.go index 3024d8dafb3..72e4e6120c8 100644 --- a/src/lib/cache/cache_test.go +++ b/src/lib/cache/cache_test.go @@ -18,10 +18,9 @@ import ( "fmt" "testing" + "github.com/goharbor/harbor/src/lib/retry" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" - - cachetesting "github.com/goharbor/harbor/src/testing/lib/cache" - "github.com/goharbor/harbor/src/testing/mock" ) type CacheTestSuite struct { @@ -30,7 +29,7 @@ type CacheTestSuite struct { func (suite *CacheTestSuite) SetupSuite() { Register("mock", func(opts Options) (Cache, error) { - return &cachetesting.Cache{}, nil + return &mockCache{}, nil }) } @@ -62,8 +61,8 @@ func (suite *CacheTestSuite) TestInitialize() { { Register("cache", func(opts Options) (Cache, error) { - c := &cachetesting.Cache{} - c.On("Ping", mock.Anything).Return(fmt.Errorf("oops")) + c := &mockCache{} + c.On("Ping", mock.Anything).Return(retry.Abort(fmt.Errorf("oops"))) return c, nil }) @@ -75,7 +74,7 @@ func (suite *CacheTestSuite) TestInitialize() { { Register("cache", func(opts Options) (Cache, error) { - c := &cachetesting.Cache{} + c := &mockCache{} c.On("Ping", mock.Anything).Return(nil) return c, nil diff --git a/src/lib/cache/helper_test.go b/src/lib/cache/helper_test.go index c9ccf9f6fa2..1aed1d719c6 100644 --- a/src/lib/cache/helper_test.go +++ b/src/lib/cache/helper_test.go @@ -23,7 +23,6 @@ import ( "github.com/stretchr/testify/suite" - cachetesting "github.com/goharbor/harbor/src/testing/lib/cache" "github.com/goharbor/harbor/src/testing/mock" ) @@ -42,7 +41,7 @@ func (suite *FetchOrSaveTestSuite) SetupSuite() { } func (suite *FetchOrSaveTestSuite) TestFetchInternalError() { - c := &cachetesting.Cache{} + c := &mockCache{} mock.OnAnything(c, "Fetch").Return(fmt.Errorf("oops")) @@ -55,7 +54,7 @@ func (suite *FetchOrSaveTestSuite) TestFetchInternalError() { } func (suite *FetchOrSaveTestSuite) TestBuildError() { - c := &cachetesting.Cache{} + c := &mockCache{} mock.OnAnything(c, "Fetch").Return(ErrNotFound) @@ -68,7 +67,7 @@ func (suite *FetchOrSaveTestSuite) TestBuildError() { } func (suite *FetchOrSaveTestSuite) TestSaveError() { - c := &cachetesting.Cache{} + c := &mockCache{} mock.OnAnything(c, "Fetch").Return(ErrNotFound) mock.OnAnything(c, "Save").Return(fmt.Errorf("oops")) @@ -83,7 +82,7 @@ func (suite *FetchOrSaveTestSuite) TestSaveError() { } func (suite *FetchOrSaveTestSuite) TestSaveCalledOnlyOneTime() { - c := &cachetesting.Cache{} + c := &mockCache{} var data sync.Map diff --git a/src/lib/cache/memory/memory.go b/src/lib/cache/memory/memory.go index 6f65f23686d..e80757259d0 100644 --- a/src/lib/cache/memory/memory.go +++ b/src/lib/cache/memory/memory.go @@ -117,27 +117,55 @@ func (c *Cache) Save(ctx context.Context, key string, value interface{}, expirat return nil } -// Keys returns the key matched by prefixes. -func (c *Cache) Keys(ctx context.Context, prefixes ...string) ([]string, error) { - // if no prefix, means match all keys. - matchAll := len(prefixes) == 0 - // range map to get all keys - keys := make([]string, 0) +// Scan scans the keys matched by match string +func (c *Cache) Scan(ctx context.Context, match string) (cache.Iterator, error) { + var keys []string c.storage.Range(func(k, v interface{}) bool { - ks := k.(string) - if matchAll { - keys = append(keys, ks) - } else { - for _, p := range prefixes { - if strings.HasPrefix(ks, c.opts.Key(p)) { - keys = append(keys, strings.TrimPrefix(ks, c.opts.Prefix)) - } + matched := true + if match != "" { + matched = strings.Contains(k.(string), match) + } + + if matched { + if v.(*entry).isExpirated() { + c.storage.Delete(k) + } else { + keys = append(keys, strings.TrimPrefix(k.(string), c.opts.Prefix)) } } return true }) - return keys, nil + return &ScanIterator{keys: keys}, nil +} + +// ScanIterator is a ScanIterator for memory cache +type ScanIterator struct { + mu sync.Mutex + pos int + keys []string +} + +// Next checks whether has the next element +func (i *ScanIterator) Next(ctx context.Context) bool { + i.mu.Lock() + defer i.mu.Unlock() + + i.pos++ + return i.pos <= len(i.keys) +} + +// Val returns the key +func (i *ScanIterator) Val() string { + i.mu.Lock() + defer i.mu.Unlock() + + var val string + if i.pos <= len(i.keys) { + val = i.keys[i.pos-1] + } + + return val } // New returns memory cache diff --git a/src/lib/cache/memory/memory_test.go b/src/lib/cache/memory/memory_test.go index 9d6c6119de1..883045b5cc6 100644 --- a/src/lib/cache/memory/memory_test.go +++ b/src/lib/cache/memory/memory_test.go @@ -16,6 +16,7 @@ package memory import ( "context" + "fmt" "testing" "time" @@ -109,28 +110,54 @@ func (suite *CacheTestSuite) TestPing() { suite.NoError(suite.cache.Ping(suite.ctx)) } -func (suite *CacheTestSuite) TestKeys() { - key1 := "p1" - key2 := "p2" - - var err error - err = suite.cache.Save(suite.ctx, key1, "hello, p1") - suite.Nil(err) - err = suite.cache.Save(suite.ctx, key2, "hello, p2") - suite.Nil(err) - - // should match all - keys, err := suite.cache.Keys(suite.ctx, "p") - suite.Nil(err) - suite.ElementsMatch([]string{"p1", "p2"}, keys) - // only get p1 - keys, err = suite.cache.Keys(suite.ctx, key1) - suite.Nil(err) - suite.Equal([]string{"p1"}, keys) - // only get p2 - keys, err = suite.cache.Keys(suite.ctx, key2) - suite.Nil(err) - suite.Equal([]string{"p2"}, keys) +func (suite *CacheTestSuite) TestScan() { + seed := func(n int) { + for i := 0; i < n; i++ { + key := fmt.Sprintf("test-scan-%d", i) + err := suite.cache.Save(suite.ctx, key, "") + suite.NoError(err) + } + } + clean := func(n int) { + for i := 0; i < n; i++ { + key := fmt.Sprintf("test-scan-%d", i) + err := suite.cache.Delete(suite.ctx, key) + suite.NoError(err) + } + } + { + // no match should return all keys + expect := []string{"test-scan-0", "test-scan-1", "test-scan-2"} + // seed data + seed(3) + // test scan + iter, err := suite.cache.Scan(suite.ctx, "") + suite.NoError(err) + got := []string{} + for iter.Next(suite.ctx) { + got = append(got, iter.Val()) + } + suite.ElementsMatch(expect, got) + // clean up + clean(3) + } + + { + // with match should return matched keys + expect := []string{"test-scan-1", "test-scan-10"} + // seed data + seed(11) + // test scan + iter, err := suite.cache.Scan(suite.ctx, "test-scan-1") + suite.NoError(err) + got := []string{} + for iter.Next(suite.ctx) { + got = append(got, iter.Val()) + } + suite.ElementsMatch(expect, got) + // clean up + clean(11) + } } func TestCacheTestSuite(t *testing.T) { diff --git a/src/lib/cache/mock_cache_test.go b/src/lib/cache/mock_cache_test.go new file mode 100644 index 00000000000..e8e0b3574a1 --- /dev/null +++ b/src/lib/cache/mock_cache_test.go @@ -0,0 +1,133 @@ +// Code generated by mockery v2.22.1. DO NOT EDIT. + +package cache + +import ( + context "context" + time "time" + + mock "github.com/stretchr/testify/mock" +) + +// mockCache is an autogenerated mock type for the Cache type +type mockCache struct { + mock.Mock +} + +// Contains provides a mock function with given fields: ctx, key +func (_m *mockCache) Contains(ctx context.Context, key string) bool { + ret := _m.Called(ctx, key) + + var r0 bool + if rf, ok := ret.Get(0).(func(context.Context, string) bool); ok { + r0 = rf(ctx, key) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// Delete provides a mock function with given fields: ctx, key +func (_m *mockCache) Delete(ctx context.Context, key string) error { + ret := _m.Called(ctx, key) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, key) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Fetch provides a mock function with given fields: ctx, key, value +func (_m *mockCache) Fetch(ctx context.Context, key string, value interface{}) error { + ret := _m.Called(ctx, key, value) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, interface{}) error); ok { + r0 = rf(ctx, key, value) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Ping provides a mock function with given fields: ctx +func (_m *mockCache) Ping(ctx context.Context) error { + ret := _m.Called(ctx) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Save provides a mock function with given fields: ctx, key, value, expiration +func (_m *mockCache) Save(ctx context.Context, key string, value interface{}, expiration ...time.Duration) error { + _va := make([]interface{}, len(expiration)) + for _i := range expiration { + _va[_i] = expiration[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, key, value) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, interface{}, ...time.Duration) error); ok { + r0 = rf(ctx, key, value, expiration...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Scan provides a mock function with given fields: ctx, match +func (_m *mockCache) Scan(ctx context.Context, match string) (Iterator, error) { + ret := _m.Called(ctx, match) + + var r0 Iterator + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (Iterator, error)); ok { + return rf(ctx, match) + } + if rf, ok := ret.Get(0).(func(context.Context, string) Iterator); ok { + r0 = rf(ctx, match) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(Iterator) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, match) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTnewMockCache interface { + mock.TestingT + Cleanup(func()) +} + +// newMockCache creates a new instance of mockCache. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func newMockCache(t mockConstructorTestingTnewMockCache) *mockCache { + mock := &mockCache{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/src/lib/cache/redis/redis.go b/src/lib/cache/redis/redis.go index 3e7d5e433aa..a72b05e9c87 100644 --- a/src/lib/cache/redis/redis.go +++ b/src/lib/cache/redis/redis.go @@ -25,6 +25,7 @@ import ( "github.com/goharbor/harbor/src/lib/cache" "github.com/goharbor/harbor/src/lib/errors" + "github.com/goharbor/harbor/src/lib/log" ) var _ cache.Cache = (*Cache)(nil) @@ -89,30 +90,41 @@ func (c *Cache) Save(ctx context.Context, key string, value interface{}, expirat return c.Client.Set(ctx, c.opts.Key(key), data, exp).Err() } -// Keys returns the key matched by prefixes. -func (c *Cache) Keys(ctx context.Context, prefixes ...string) ([]string, error) { - patterns := make([]string, 0, len(prefixes)) - if len(prefixes) == 0 { - patterns = append(patterns, "*") - } else { - for _, p := range prefixes { - patterns = append(patterns, c.opts.Key(p)+"*") - } +// Scan scans the keys matched by match string +func (c *Cache) Scan(ctx context.Context, match string) (cache.Iterator, error) { + // the cursor and count are used for scan from redis, do not expose to outside + // by performance concern. + // cursor should start from 0 + cursor := uint64(0) + count := int64(1000) + match = fmt.Sprintf("%s*%s*", c.opts.Prefix, match) + iter := c.Client.Scan(ctx, cursor, match, count).Iterator() + if iter.Err() != nil { + return nil, iter.Err() } - keys := make([]string, 0) - for _, pattern := range patterns { - cmd := c.Client.Keys(ctx, pattern) - if err := cmd.Err(); err != nil { - return nil, err - } + return &ScanIterator{iter: iter, prefix: c.opts.Prefix}, nil +} - for _, k := range cmd.Val() { - keys = append(keys, strings.TrimPrefix(k, c.opts.Prefix)) - } +// ScanIterator is a wrapper for redis ScanIterator +type ScanIterator struct { + iter *redis.ScanIterator + prefix string +} + +// Next check whether has the next element +func (i *ScanIterator) Next(ctx context.Context) bool { + hasNext := i.iter.Next(ctx) + if !hasNext && i.iter.Err() != nil { + log.Errorf("error occurred when scan redis: %v", i.iter.Err()) } - return keys, nil + return hasNext +} + +// Val returns the key +func (i *ScanIterator) Val() string { + return strings.TrimPrefix(i.iter.Val(), i.prefix) } // New returns redis cache diff --git a/src/lib/cache/redis/redis_test.go b/src/lib/cache/redis/redis_test.go index cd8a67ea5f7..1170543aab1 100644 --- a/src/lib/cache/redis/redis_test.go +++ b/src/lib/cache/redis/redis_test.go @@ -110,28 +110,54 @@ func (suite *CacheTestSuite) TestPing() { suite.NoError(suite.cache.Ping(suite.ctx)) } -func (suite *CacheTestSuite) TestKeys() { - key1 := "p1" - key2 := "p2" - - var err error - err = suite.cache.Save(suite.ctx, key1, "hello, p1") - suite.Nil(err) - err = suite.cache.Save(suite.ctx, key2, "hello, p2") - suite.Nil(err) - - // should match all - keys, err := suite.cache.Keys(suite.ctx, "p") - suite.Nil(err) - suite.ElementsMatch([]string{"p1", "p2"}, keys) - // only get p1 - keys, err = suite.cache.Keys(suite.ctx, key1) - suite.Nil(err) - suite.Equal([]string{"p1"}, keys) - // only get p2 - keys, err = suite.cache.Keys(suite.ctx, key2) - suite.Nil(err) - suite.Equal([]string{"p2"}, keys) +func (suite *CacheTestSuite) TestScan() { + seed := func(n int) { + for i := 0; i < n; i++ { + key := fmt.Sprintf("test-scan-%d", i) + err := suite.cache.Save(suite.ctx, key, "") + suite.NoError(err) + } + } + clean := func(n int) { + for i := 0; i < n; i++ { + key := fmt.Sprintf("test-scan-%d", i) + err := suite.cache.Delete(suite.ctx, key) + suite.NoError(err) + } + } + { + // no match should return all keys + expect := []string{"test-scan-0", "test-scan-1", "test-scan-2"} + // seed data + seed(3) + // test scan + iter, err := suite.cache.Scan(suite.ctx, "") + suite.NoError(err) + got := []string{} + for iter.Next(suite.ctx) { + got = append(got, iter.Val()) + } + suite.ElementsMatch(expect, got) + // clean up + clean(3) + } + + { + // with match should return matched keys + expect := []string{"test-scan-1", "test-scan-10"} + // seed data + seed(11) + // test scan + iter, err := suite.cache.Scan(suite.ctx, "*test-scan-1*") + suite.NoError(err) + got := []string{} + for iter.Next(suite.ctx) { + got = append(got, iter.Val()) + } + suite.ElementsMatch(expect, got) + // clean up + clean(11) + } } func TestCacheTestSuite(t *testing.T) { diff --git a/src/pkg/cached/artifact/redis/manager_test.go b/src/pkg/cached/artifact/redis/manager_test.go index b3aca32faaa..a46accc060b 100644 --- a/src/pkg/cached/artifact/redis/manager_test.go +++ b/src/pkg/cached/artifact/redis/manager_test.go @@ -34,12 +34,14 @@ type managerTestSuite struct { cachedManager CachedManager artMgr *testArt.Manager cache *testcache.Cache + iterator *testcache.Iterator ctx context.Context } func (m *managerTestSuite) SetupTest() { m.artMgr = &testArt.Manager{} m.cache = &testcache.Cache{} + m.iterator = &testcache.Iterator{} m.cachedManager = NewManager(m.artMgr) m.cachedManager.(*Manager).WithCacheClient(m.cache) m.ctx = context.TODO() @@ -177,10 +179,11 @@ func (m *managerTestSuite) TestResourceType() { } func (m *managerTestSuite) TestCountCache() { - m.cache.On("Keys", mock.Anything, mock.Anything).Return([]string{"1"}, nil).Once() + m.iterator.On("Next", mock.Anything).Return(false).Once() + m.cache.On("Scan", mock.Anything, mock.Anything).Return(m.iterator, nil).Once() c, err := m.cachedManager.CountCache(m.ctx) m.NoError(err) - m.Equal(int64(1), c) + m.Equal(int64(0), c) } func (m *managerTestSuite) TestDeleteCache() { @@ -190,7 +193,8 @@ func (m *managerTestSuite) TestDeleteCache() { } func (m *managerTestSuite) TestFlushAll() { - m.cache.On("Keys", mock.Anything, mock.Anything).Return([]string{"1"}, nil).Once() + m.iterator.On("Next", mock.Anything).Return(false).Once() + m.cache.On("Scan", mock.Anything, mock.Anything).Return(m.iterator, nil).Once() m.cache.On("Delete", mock.Anything, mock.Anything).Return(nil).Once() err := m.cachedManager.FlushAll(m.ctx) m.NoError(err) diff --git a/src/pkg/cached/base_manager.go b/src/pkg/cached/base_manager.go index aa5cd8f4f8e..bc810372849 100644 --- a/src/pkg/cached/base_manager.go +++ b/src/pkg/cached/base_manager.go @@ -60,8 +60,8 @@ func (*cacheClient) Save(ctx context.Context, key string, value interface{}, exp return cache.Default().Save(ctx, key, value, expiration...) } -func (*cacheClient) Keys(ctx context.Context, prefixes ...string) ([]string, error) { - return cache.Default().Keys(ctx, prefixes...) +func (*cacheClient) Scan(ctx context.Context, match string) (cache.Iterator, error) { + return cache.Default().Scan(ctx, match) } var _ Manager = &BaseManager{} @@ -98,13 +98,18 @@ func (bm *BaseManager) ResourceType(ctx context.Context) string { // CountCache returns current this resource occupied cache count. func (bm *BaseManager) CountCache(ctx context.Context) (int64, error) { + var count int64 // prefix is resource type - keys, err := bm.CacheClient(ctx).Keys(ctx, bm.ResourceType(ctx)) + iter, err := bm.CacheClient(ctx).Scan(ctx, bm.ResourceType(ctx)) if err != nil { return 0, err } - return int64(len(keys)), nil + for iter.Next(ctx) { + count++ + } + + return count, nil } // DeleteCache deletes specific cache by key. @@ -115,14 +120,14 @@ func (bm *BaseManager) DeleteCache(ctx context.Context, key string) error { // FlushAll flush this resource's all cache. func (bm *BaseManager) FlushAll(ctx context.Context) error { // prefix is resource type - keys, err := bm.CacheClient(ctx).Keys(ctx, bm.ResourceType(ctx)) + iter, err := bm.CacheClient(ctx).Scan(ctx, bm.ResourceType(ctx)) if err != nil { return err } var errs errors.Errors - for _, key := range keys { - if err = bm.CacheClient(ctx).Delete(ctx, key); err != nil { + for iter.Next(ctx) { + if err = bm.CacheClient(ctx).Delete(ctx, iter.Val()); err != nil { errs = append(errs, err) } } diff --git a/src/pkg/cached/base_manager_test.go b/src/pkg/cached/base_manager_test.go index 7cdc76f2466..5db2613f9ea 100644 --- a/src/pkg/cached/base_manager_test.go +++ b/src/pkg/cached/base_manager_test.go @@ -30,6 +30,7 @@ var testResourceType = "resource-test" type testCache struct { *testcache.Cache + iterator *testcache.Iterator } func (tc *testCache) Save(ctx context.Context, key string, value interface{}, expiration ...time.Duration) error { @@ -47,7 +48,7 @@ type baseManagerTestSuite struct { } func (m *baseManagerTestSuite) SetupTest() { - m.cache = &testCache{Cache: &testcache.Cache{}} + m.cache = &testCache{Cache: &testcache.Cache{}, iterator: &testcache.Iterator{}} m.mgr = NewBaseManager(testResourceType).WithCacheClient(m.cache) } @@ -72,10 +73,11 @@ func (m *baseManagerTestSuite) TestResourceType() { } func (m *baseManagerTestSuite) TestCountCache() { - m.cache.On("Keys", mock.Anything, testResourceType).Return([]string{"k1", "k2"}, nil).Once() + m.cache.iterator.On("Next", mock.Anything).Return(false).Once() + m.cache.On("Scan", mock.Anything, mock.Anything).Return(m.cache.iterator, nil).Once() c, err := m.mgr.CountCache(context.TODO()) m.NoError(err) - m.Equal(int64(2), c) + m.Equal(int64(0), c) } func (m *baseManagerTestSuite) TestDeleteCache() { @@ -85,9 +87,8 @@ func (m *baseManagerTestSuite) TestDeleteCache() { } func (m *baseManagerTestSuite) TestFlushAll() { - m.cache.On("Keys", mock.Anything, testResourceType).Return([]string{"k1", "k2"}, nil).Once() - m.cache.On("Delete", mock.Anything, "k1").Return(nil).Once() - m.cache.On("Delete", mock.Anything, "k2").Return(nil).Once() + m.cache.iterator.On("Next", mock.Anything).Return(false).Once() + m.cache.On("Scan", mock.Anything, mock.Anything).Return(m.cache.iterator, nil).Once() err := m.mgr.FlushAll(context.TODO()) m.NoError(err) } diff --git a/src/pkg/cached/manifest/redis/manager_test.go b/src/pkg/cached/manifest/redis/manager_test.go index af2ff6a7d39..916bc7b9881 100644 --- a/src/pkg/cached/manifest/redis/manager_test.go +++ b/src/pkg/cached/manifest/redis/manager_test.go @@ -29,6 +29,7 @@ type managerTestSuite struct { suite.Suite cachedManager CachedManager cache *testcache.Cache + iterator *testcache.Iterator ctx context.Context digest string @@ -37,6 +38,7 @@ type managerTestSuite struct { func (m *managerTestSuite) SetupTest() { m.cache = &testcache.Cache{} + m.iterator = &testcache.Iterator{} m.cachedManager = NewManager() m.cachedManager.(*Manager).WithCacheClient(m.cache) m.ctx = context.TODO() @@ -69,10 +71,11 @@ func (m *managerTestSuite) TestResourceType() { } func (m *managerTestSuite) TestCountCache() { - m.cache.On("Keys", mock.Anything, mock.Anything).Return([]string{"1"}, nil).Once() + m.iterator.On("Next", mock.Anything).Return(false).Once() + m.cache.On("Scan", mock.Anything, mock.Anything).Return(m.iterator, nil).Once() c, err := m.cachedManager.CountCache(m.ctx) m.NoError(err) - m.Equal(int64(1), c) + m.Equal(int64(0), c) } func (m *managerTestSuite) TestDeleteCache() { @@ -82,7 +85,8 @@ func (m *managerTestSuite) TestDeleteCache() { } func (m *managerTestSuite) TestFlushAll() { - m.cache.On("Keys", mock.Anything, mock.Anything).Return([]string{"1"}, nil).Once() + m.iterator.On("Next", mock.Anything).Return(false).Once() + m.cache.On("Scan", mock.Anything, mock.Anything).Return(m.iterator, nil).Once() m.cache.On("Delete", mock.Anything, mock.Anything).Return(nil).Once() err := m.cachedManager.FlushAll(m.ctx) m.NoError(err) diff --git a/src/pkg/cached/project/redis/manager_test.go b/src/pkg/cached/project/redis/manager_test.go index e0630f92aac..fa9d8994b1f 100644 --- a/src/pkg/cached/project/redis/manager_test.go +++ b/src/pkg/cached/project/redis/manager_test.go @@ -34,12 +34,14 @@ type managerTestSuite struct { cachedManager CachedManager projectMgr *testProject.Manager cache *testcache.Cache + iterator *testcache.Iterator ctx context.Context } func (m *managerTestSuite) SetupTest() { m.projectMgr = &testProject.Manager{} m.cache = &testcache.Cache{} + m.iterator = &testcache.Iterator{} m.cachedManager = NewManager(m.projectMgr) m.cachedManager.(*Manager).WithCacheClient(m.cache) m.ctx = context.TODO() @@ -113,10 +115,11 @@ func (m *managerTestSuite) TestResourceType() { } func (m *managerTestSuite) TestCountCache() { - m.cache.On("Keys", mock.Anything, mock.Anything).Return([]string{"1"}, nil).Once() + m.iterator.On("Next", mock.Anything).Return(false).Once() + m.cache.On("Scan", mock.Anything, mock.Anything).Return(m.iterator, nil).Once() c, err := m.cachedManager.CountCache(m.ctx) m.NoError(err) - m.Equal(int64(1), c) + m.Equal(int64(0), c) } func (m *managerTestSuite) TestDeleteCache() { @@ -126,7 +129,8 @@ func (m *managerTestSuite) TestDeleteCache() { } func (m *managerTestSuite) TestFlushAll() { - m.cache.On("Keys", mock.Anything, mock.Anything).Return([]string{"1"}, nil).Once() + m.iterator.On("Next", mock.Anything).Return(false).Once() + m.cache.On("Scan", mock.Anything, mock.Anything).Return(m.iterator, nil).Once() m.cache.On("Delete", mock.Anything, mock.Anything).Return(nil).Once() err := m.cachedManager.FlushAll(m.ctx) m.NoError(err) diff --git a/src/pkg/cached/project_metadata/redis/manager.go b/src/pkg/cached/project_metadata/redis/manager.go index 07658296c08..1bf1ee6df07 100644 --- a/src/pkg/cached/project_metadata/redis/manager.go +++ b/src/pkg/cached/project_metadata/redis/manager.go @@ -119,14 +119,14 @@ func (m *Manager) Update(ctx context.Context, projectID int64, meta map[string]s return err } // lookup all keys with projectID prefix - keys, err := m.CacheClient(ctx).Keys(ctx, prefix) + iter, err := m.CacheClient(ctx).Scan(ctx, prefix) if err != nil { return err } - for _, key := range keys { - if err = retry.Retry(func() error { return m.CacheClient(ctx).Delete(ctx, key) }); err != nil { - log.Errorf("delete project metadata cache key %s error: %v", key, err) + for iter.Next(ctx) { + if err = retry.Retry(func() error { return m.CacheClient(ctx).Delete(ctx, iter.Val()) }); err != nil { + log.Errorf("delete project metadata cache key %s error: %v", iter.Val(), err) } } diff --git a/src/pkg/cached/project_metadata/redis/manager_test.go b/src/pkg/cached/project_metadata/redis/manager_test.go index 2a2172e1108..8315e406a07 100644 --- a/src/pkg/cached/project_metadata/redis/manager_test.go +++ b/src/pkg/cached/project_metadata/redis/manager_test.go @@ -33,12 +33,14 @@ type managerTestSuite struct { cachedManager CachedManager projectMetaMgr *testProjectMeta.Manager cache *testcache.Cache + iterator *testcache.Iterator ctx context.Context } func (m *managerTestSuite) SetupTest() { m.projectMetaMgr = &testProjectMeta.Manager{} m.cache = &testcache.Cache{} + m.iterator = &testcache.Iterator{} m.cachedManager = NewManager(m.projectMetaMgr) m.cachedManager.(*Manager).WithCacheClient(m.cache) m.ctx = context.TODO() @@ -98,10 +100,11 @@ func (m *managerTestSuite) TestResourceType() { } func (m *managerTestSuite) TestCountCache() { - m.cache.On("Keys", mock.Anything, mock.Anything).Return([]string{"1"}, nil).Once() + m.iterator.On("Next", mock.Anything).Return(false).Once() + m.cache.On("Scan", mock.Anything, mock.Anything).Return(m.iterator, nil).Once() c, err := m.cachedManager.CountCache(m.ctx) m.NoError(err) - m.Equal(int64(1), c) + m.Equal(int64(0), c) } func (m *managerTestSuite) TestDeleteCache() { @@ -111,7 +114,8 @@ func (m *managerTestSuite) TestDeleteCache() { } func (m *managerTestSuite) TestFlushAll() { - m.cache.On("Keys", mock.Anything, mock.Anything).Return([]string{"1"}, nil).Once() + m.iterator.On("Next", mock.Anything).Return(false).Once() + m.cache.On("Scan", mock.Anything, mock.Anything).Return(m.iterator, nil).Once() m.cache.On("Delete", mock.Anything, mock.Anything).Return(nil).Once() err := m.cachedManager.FlushAll(m.ctx) m.NoError(err) diff --git a/src/pkg/cached/repository/redis/manager_test.go b/src/pkg/cached/repository/redis/manager_test.go index 914ea5cce9d..f09f8e92c9d 100644 --- a/src/pkg/cached/repository/redis/manager_test.go +++ b/src/pkg/cached/repository/redis/manager_test.go @@ -33,12 +33,14 @@ type managerTestSuite struct { cachedManager CachedManager repoMgr *testRepo.Manager cache *testcache.Cache + iterator *testcache.Iterator ctx context.Context } func (m *managerTestSuite) SetupTest() { m.repoMgr = &testRepo.Manager{} m.cache = &testcache.Cache{} + m.iterator = &testcache.Iterator{} m.cachedManager = NewManager(m.repoMgr) m.cachedManager.(*Manager).WithCacheClient(m.cache) m.ctx = context.TODO() @@ -166,10 +168,11 @@ func (m *managerTestSuite) TestResourceType() { } func (m *managerTestSuite) TestCountCache() { - m.cache.On("Keys", mock.Anything, mock.Anything).Return([]string{"1"}, nil).Once() + m.iterator.On("Next", mock.Anything).Return(false).Once() + m.cache.On("Scan", mock.Anything, mock.Anything).Return(m.iterator, nil).Once() c, err := m.cachedManager.CountCache(m.ctx) m.NoError(err) - m.Equal(int64(1), c) + m.Equal(int64(0), c) } func (m *managerTestSuite) TestDeleteCache() { @@ -179,7 +182,8 @@ func (m *managerTestSuite) TestDeleteCache() { } func (m *managerTestSuite) TestFlushAll() { - m.cache.On("Keys", mock.Anything, mock.Anything).Return([]string{"1"}, nil).Once() + m.iterator.On("Next", mock.Anything).Return(false).Once() + m.cache.On("Scan", mock.Anything, mock.Anything).Return(m.iterator, nil).Once() m.cache.On("Delete", mock.Anything, mock.Anything).Return(nil).Once() err := m.cachedManager.FlushAll(m.ctx) m.NoError(err) diff --git a/src/pkg/task/dao/execution.go b/src/pkg/task/dao/execution.go index 611dca1fcef..cd26e792e56 100644 --- a/src/pkg/task/dao/execution.go +++ b/src/pkg/task/dao/execution.go @@ -447,11 +447,16 @@ func (e *executionDAO) AsyncRefreshStatus(ctx context.Context, id int64, vendor // scanAndRefreshOutdateStatus scans the outdate execution status from redis and then refresh the status to db, // do not want to expose to external use so keep it as private. func scanAndRefreshOutdateStatus(ctx context.Context) { - keys, err := cache.Default().Keys(ctx, "execution:id:") + iter, err := cache.Default().Scan(ctx, "execution:id:*vendor:*status_outdate") if err != nil { log.Errorf("failed to scan the outdate executions, error: %v", err) return } + + var keys []string + for iter.Next(ctx) { + keys = append(keys, iter.Val()) + } // return earlier if no keys found which represents no outdate execution if len(keys) == 0 { log.Debug("skip to refresh, no outdate execution status found") diff --git a/src/pkg/task/dao/execution_test.go b/src/pkg/task/dao/execution_test.go index e826ac774e9..732286eabdc 100644 --- a/src/pkg/task/dao/execution_test.go +++ b/src/pkg/task/dao/execution_test.go @@ -22,7 +22,7 @@ import ( "github.com/goharbor/harbor/src/common/dao" "github.com/goharbor/harbor/src/jobservice/job" "github.com/goharbor/harbor/src/lib/cache" - _ "github.com/goharbor/harbor/src/lib/cache/memory" + _ "github.com/goharbor/harbor/src/lib/cache/redis" "github.com/goharbor/harbor/src/lib/errors" "github.com/goharbor/harbor/src/lib/orm" "github.com/goharbor/harbor/src/lib/q" @@ -45,7 +45,7 @@ func (e *executionDAOTestSuite) SetupSuite() { taskDAO: e.taskDao, } // initializes cache for testing - err := cache.Initialize(cache.Memory, "") + err := cache.Initialize(cache.Redis, "redis://localhost:6379/0") e.NoError(err) } diff --git a/src/testing/lib/cache/cache.go b/src/testing/lib/cache/cache.go index efac85b0a83..230d170832d 100644 --- a/src/testing/lib/cache/cache.go +++ b/src/testing/lib/cache/cache.go @@ -4,9 +4,12 @@ package cache import ( context "context" - time "time" + + cache "github.com/goharbor/harbor/src/lib/cache" mock "github.com/stretchr/testify/mock" + + time "time" ) // Cache is an autogenerated mock type for the Cache type @@ -56,39 +59,6 @@ func (_m *Cache) Fetch(ctx context.Context, key string, value interface{}) error return r0 } -// Keys provides a mock function with given fields: ctx, prefixes -func (_m *Cache) Keys(ctx context.Context, prefixes ...string) ([]string, error) { - _va := make([]interface{}, len(prefixes)) - for _i := range prefixes { - _va[_i] = prefixes[_i] - } - var _ca []interface{} - _ca = append(_ca, ctx) - _ca = append(_ca, _va...) - ret := _m.Called(_ca...) - - var r0 []string - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, ...string) ([]string, error)); ok { - return rf(ctx, prefixes...) - } - if rf, ok := ret.Get(0).(func(context.Context, ...string) []string); ok { - r0 = rf(ctx, prefixes...) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]string) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, ...string) error); ok { - r1 = rf(ctx, prefixes...) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // Ping provides a mock function with given fields: ctx func (_m *Cache) Ping(ctx context.Context) error { ret := _m.Called(ctx) @@ -124,6 +94,32 @@ func (_m *Cache) Save(ctx context.Context, key string, value interface{}, expira return r0 } +// Scan provides a mock function with given fields: ctx, match +func (_m *Cache) Scan(ctx context.Context, match string) (cache.Iterator, error) { + ret := _m.Called(ctx, match) + + var r0 cache.Iterator + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (cache.Iterator, error)); ok { + return rf(ctx, match) + } + if rf, ok := ret.Get(0).(func(context.Context, string) cache.Iterator); ok { + r0 = rf(ctx, match) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(cache.Iterator) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, match) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + type mockConstructorTestingTNewCache interface { mock.TestingT Cleanup(func()) diff --git a/src/testing/lib/cache/iterator.go b/src/testing/lib/cache/iterator.go new file mode 100644 index 00000000000..2dacab3f42a --- /dev/null +++ b/src/testing/lib/cache/iterator.go @@ -0,0 +1,57 @@ +// Code generated by mockery v2.22.1. DO NOT EDIT. + +package cache + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// Iterator is an autogenerated mock type for the Iterator type +type Iterator struct { + mock.Mock +} + +// Next provides a mock function with given fields: ctx +func (_m *Iterator) Next(ctx context.Context) bool { + ret := _m.Called(ctx) + + var r0 bool + if rf, ok := ret.Get(0).(func(context.Context) bool); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// Val provides a mock function with given fields: +func (_m *Iterator) Val() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +type mockConstructorTestingTNewIterator interface { + mock.TestingT + Cleanup(func()) +} + +// NewIterator creates a new instance of Iterator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewIterator(t mockConstructorTestingTNewIterator) *Iterator { + mock := &Iterator{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/src/testing/lib/lib.go b/src/testing/lib/lib.go index 24eff32bd08..3b6f4a36a0e 100644 --- a/src/testing/lib/lib.go +++ b/src/testing/lib/lib.go @@ -16,4 +16,5 @@ package lib //go:generate mockery --case snake --dir ../../lib/orm --name Creator --output ./orm --outpkg orm //go:generate mockery --case snake --dir ../../lib/cache --name Cache --output ./cache --outpkg cache +//go:generate mockery --case snake --dir ../../lib/cache --name Iterator --output ./cache --outpkg cache //go:generate mockery --case snake --dir ../../lib/config --name Manager --output ./config --outpkg config diff --git a/src/testing/lib/libcache/cache.go b/src/testing/lib/libcache/cache.go new file mode 100644 index 00000000000..8eb215e52d9 --- /dev/null +++ b/src/testing/lib/libcache/cache.go @@ -0,0 +1,136 @@ +// Code generated by mockery v2.22.1. DO NOT EDIT. + +package libcache + +import ( + context "context" + + cache "github.com/goharbor/harbor/src/lib/cache" + + mock "github.com/stretchr/testify/mock" + + time "time" +) + +// Cache is an autogenerated mock type for the Cache type +type Cache struct { + mock.Mock +} + +// Contains provides a mock function with given fields: ctx, key +func (_m *Cache) Contains(ctx context.Context, key string) bool { + ret := _m.Called(ctx, key) + + var r0 bool + if rf, ok := ret.Get(0).(func(context.Context, string) bool); ok { + r0 = rf(ctx, key) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// Delete provides a mock function with given fields: ctx, key +func (_m *Cache) Delete(ctx context.Context, key string) error { + ret := _m.Called(ctx, key) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, key) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Fetch provides a mock function with given fields: ctx, key, value +func (_m *Cache) Fetch(ctx context.Context, key string, value interface{}) error { + ret := _m.Called(ctx, key, value) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, interface{}) error); ok { + r0 = rf(ctx, key, value) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Ping provides a mock function with given fields: ctx +func (_m *Cache) Ping(ctx context.Context) error { + ret := _m.Called(ctx) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Save provides a mock function with given fields: ctx, key, value, expiration +func (_m *Cache) Save(ctx context.Context, key string, value interface{}, expiration ...time.Duration) error { + _va := make([]interface{}, len(expiration)) + for _i := range expiration { + _va[_i] = expiration[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, key, value) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, interface{}, ...time.Duration) error); ok { + r0 = rf(ctx, key, value, expiration...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Scan provides a mock function with given fields: ctx, match +func (_m *Cache) Scan(ctx context.Context, match string) (cache.Iterator, error) { + ret := _m.Called(ctx, match) + + var r0 cache.Iterator + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (cache.Iterator, error)); ok { + return rf(ctx, match) + } + if rf, ok := ret.Get(0).(func(context.Context, string) cache.Iterator); ok { + r0 = rf(ctx, match) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(cache.Iterator) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, match) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewCache interface { + mock.TestingT + Cleanup(func()) +} + +// NewCache creates a new instance of Cache. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewCache(t mockConstructorTestingTNewCache) *Cache { + mock := &Cache{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} From 7435c8c5ab29c5251f21a3b99e04d1da2d073661 Mon Sep 17 00:00:00 2001 From: Wang Yan Date: Thu, 29 Jun 2023 16:22:18 +0800 Subject: [PATCH 22/38] add multiple deletion of GC (#18855) User can specify the workers when to issue an GC execution, the maxium count of workers is 5. Signed-off-by: wang yan --- src/controller/gc/controller.go | 2 + src/controller/gc/model.go | 2 + src/go.mod | 1 + src/go.sum | 2 + .../job/impl/gc/garbage_collection.go | 326 ++++++++++-------- .../job/impl/gc/garbage_collection_test.go | 4 + src/jobservice/job/impl/gc/util.go | 15 + src/jobservice/job/impl/gc/util_test.go | 50 +++ src/server/v2.0/handler/gc.go | 28 ++ src/server/v2.0/handler/gc_test.go | 15 + src/vendor/golang.org/x/sync/LICENSE | 27 ++ src/vendor/golang.org/x/sync/PATENTS | 22 ++ .../golang.org/x/sync/errgroup/errgroup.go | 132 +++++++ .../golang.org/x/sync/errgroup/go120.go | 14 + .../golang.org/x/sync/errgroup/pre_go120.go | 15 + src/vendor/modules.txt | 3 + 16 files changed, 523 insertions(+), 135 deletions(-) create mode 100644 src/jobservice/job/impl/gc/util_test.go create mode 100644 src/server/v2.0/handler/gc_test.go create mode 100644 src/vendor/golang.org/x/sync/LICENSE create mode 100644 src/vendor/golang.org/x/sync/PATENTS create mode 100644 src/vendor/golang.org/x/sync/errgroup/errgroup.go create mode 100644 src/vendor/golang.org/x/sync/errgroup/go120.go create mode 100644 src/vendor/golang.org/x/sync/errgroup/pre_go120.go diff --git a/src/controller/gc/controller.go b/src/controller/gc/controller.go index 7734c9d8a61..ab34a604105 100644 --- a/src/controller/gc/controller.go +++ b/src/controller/gc/controller.go @@ -78,6 +78,7 @@ func (c *controller) Start(ctx context.Context, policy Policy, trigger string) ( para := make(map[string]interface{}) para["delete_untagged"] = policy.DeleteUntagged para["dry_run"] = policy.DryRun + para["workers"] = policy.Workers para["redis_url_reg"] = policy.ExtraAttrs["redis_url_reg"] para["time_window"] = policy.ExtraAttrs["time_window"] @@ -233,6 +234,7 @@ func convertTask(task *task.Task) *Task { RunCount: task.RunCount, DeleteUntagged: task.GetBoolFromExtraAttrs("delete_untagged"), DryRun: task.GetBoolFromExtraAttrs("dry_run"), + Workers: int(task.GetNumFromExtraAttrs("workers")), JobID: task.JobID, CreationTime: task.CreationTime, StartTime: task.StartTime, diff --git a/src/controller/gc/model.go b/src/controller/gc/model.go index 39ff50e9557..f94dcd5aa95 100644 --- a/src/controller/gc/model.go +++ b/src/controller/gc/model.go @@ -23,6 +23,7 @@ type Policy struct { Trigger *Trigger `json:"trigger"` DeleteUntagged bool `json:"deleteuntagged"` DryRun bool `json:"dryrun"` + Workers int `json:"workers"` ExtraAttrs map[string]interface{} `json:"extra_attrs"` } @@ -60,6 +61,7 @@ type Task struct { RunCount int32 DeleteUntagged bool DryRun bool + Workers int JobID string CreationTime time.Time StartTime time.Time diff --git a/src/go.mod b/src/go.mod index 04d4529db35..a51c7130ad1 100644 --- a/src/go.mod +++ b/src/go.mod @@ -162,6 +162,7 @@ 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/sync v0.3.0 golang.org/x/sys v0.7.0 // indirect golang.org/x/term v0.7.0 // indirect google.golang.org/api v0.110.0 // indirect diff --git a/src/go.sum b/src/go.sum index 47331d87400..c5fef8f5268 100644 --- a/src/go.sum +++ b/src/go.sum @@ -1521,6 +1521,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/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.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-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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= diff --git a/src/jobservice/job/impl/gc/garbage_collection.go b/src/jobservice/job/impl/gc/garbage_collection.go index 615f9e6c741..df9f899014a 100644 --- a/src/jobservice/job/impl/gc/garbage_collection.go +++ b/src/jobservice/job/impl/gc/garbage_collection.go @@ -17,8 +17,12 @@ package gc import ( "encoding/json" "os" + "sync/atomic" "time" + "github.com/google/uuid" + "golang.org/x/sync/errgroup" + "github.com/goharbor/harbor/src/common/registryctl" "github.com/goharbor/harbor/src/controller/artifact" "github.com/goharbor/harbor/src/controller/project" @@ -66,6 +70,7 @@ type GarbageCollector struct { // hold all of GC candidates(non-referenced blobs), it's captured by mark and consumed by sweep. deleteSet []*blobModels.Blob timeWindowHours int64 + workers int } // MaxFails implements the interface in job/Interface @@ -141,8 +146,19 @@ func (gc *GarbageCollector) parseParams(params job.Parameters) { } } - gc.logger.Infof("Garbage Collection parameters: [delete_untagged: %t, dry_run: %t, time_window: %d]", - gc.deleteUntagged, gc.dryRun, gc.timeWindowHours) + // gc workers: default is 1. The business unit of removing blobs. + gc.workers = 1 + ws, exist := params["workers"] + if exist { + if workers, ok := ws.(float64); ok { + if int(workers) > 0 { + gc.workers = int(workers) + } + } + } + + gc.logger.Infof("Garbage Collection parameters: [delete_untagged: %t, dry_run: %t, time_window: %d, workers: %d]", + gc.deleteUntagged, gc.dryRun, gc.timeWindowHours, gc.workers) } // Run implements the interface in job/Interface @@ -220,6 +236,7 @@ func (gc *GarbageCollector) mark(ctx job.Context) error { blobCt := 0 mfCt := 0 makeSize := int64(0) + for _, blob := range blobs { if !gc.dryRun { if gc.shouldStop(ctx) { @@ -259,159 +276,198 @@ func (gc *GarbageCollector) sweep(ctx job.Context) error { blobCnt := int64(0) mfCnt := int64(0) total := len(gc.deleteSet) - for i, blob := range gc.deleteSet { - if gc.shouldStop(ctx) { - return errGcStop - } - idx := i + 1 - // set the status firstly, if the blob is updated by any HEAD/PUT request, it should be fail and skip. - blob.Status = blobModels.StatusDeleting - count, err := gc.blobMgr.UpdateBlobStatus(ctx.SystemContext(), blob) - if err != nil { - gc.logger.Errorf("[%d/%d] failed to mark gc candidate deleting, skip: %s, %s", idx, total, blob.Digest, blob.Status) - continue - } - if count == 0 { - gc.logger.Warningf("[%d/%d] no blob found to mark gc candidate deleting, ID:%d, digest:%s", idx, total, blob.ID, blob.Digest) - continue + + // split the full set into pieces (count workers) + if total <= 0 || gc.workers <= 0 { + return nil + } + blobChunkSize, err := divide(total, gc.workers) + if err != nil { + return err + } + blobChunkCount := (total + blobChunkSize - 1) / blobChunkSize + blobChunks := make([][]*blobModels.Blob, blobChunkCount) + for i, start := 0, 0; i < blobChunkCount; i, start = i+1, start+blobChunkSize { + end := start + blobChunkSize + if end > total { + end = total } + blobChunks[i] = gc.deleteSet[start:end] + } - // remove tags and revisions of a manifest - skippedBlob := false - if _, exist := gc.trashedArts[blob.Digest]; exist && blob.IsManifest() { - for _, art := range gc.trashedArts[blob.Digest] { - // Harbor cannot know the existing tags in the backend from its database, so let the v2 DELETE manifest to remove all of them. - gc.logger.Infof("[%d/%d] delete the manifest with registry v2 API: %s, %s, %s", - idx, total, art.RepositoryName, blob.ContentType, blob.Digest) - if err := retry.Retry(func() error { - return ignoreNotFound(func() error { - err := v2DeleteManifest(art.RepositoryName, blob.Digest) - // if the system is in read-only mode, return an Abort error to skip retrying - if err == readonly.Err { - return retry.Abort(err) - } - return err - }) - }, retry.Callback(func(err error, sleep time.Duration) { - gc.logger.Infof("[%d/%d] failed to exec v2DeleteManifest, error: %v, will retry again after: %s", idx, total, err, sleep) - })); err != nil { - gc.logger.Errorf("[%d/%d] failed to delete manifest with v2 API, %s, %s, %v", idx, total, art.RepositoryName, blob.Digest, err) - if err := ignoreNotFound(func() error { - return gc.markDeleteFailed(ctx, blob) - }); err != nil { - gc.logger.Errorf("[%d/%d] failed to call gc.markDeleteFailed() after v2DeleteManifest() error out: %s, %v", idx, total, blob.Digest, err) - return err - } - // if the system is set to read-only mode, return directly - if err == readonly.Err { - return err - } - skippedBlob = true + g := new(errgroup.Group) + g.SetLimit(gc.workers) + index := int64(0) + for _, blobChunk := range blobChunks { + blobChunk := blobChunk + g.Go(func() error { + uid := uuid.New().String() + for _, blob := range blobChunk { + if gc.shouldStop(ctx) { + return errGcStop + } + + atomic.AddInt64(&index, 1) + index := atomic.LoadInt64(&index) + + // set the status firstly, if the blob is updated by any HEAD/PUT request, it should be fail and skip. + blob.Status = blobModels.StatusDeleting + count, err := gc.blobMgr.UpdateBlobStatus(ctx.SystemContext(), blob) + if err != nil { + gc.logger.Errorf("[%s][%d/%d] failed to mark gc candidate deleting, skip: %s, %s", uid, index, total, blob.Digest, blob.Status) continue } - // for manifest, it has to delete the revisions folder of each repository - gc.logger.Infof("[%d/%d] delete manifest from storage: %s", idx, total, blob.Digest) - if err := retry.Retry(func() error { - return ignoreNotFound(func() error { - err := gc.registryCtlClient.DeleteManifest(art.RepositoryName, blob.Digest) - // if the system is in read-only mode, return an Abort error to skip retrying - if err == readonly.Err { - return retry.Abort(err) - } - return err - }) - }, retry.Callback(func(err error, sleep time.Duration) { - gc.logger.Infof("[%d/%d] failed to exec DeleteManifest, error: %v, will retry again after: %s", idx, total, err, sleep) - })); err != nil { - gc.logger.Errorf("[%d/%d] failed to remove manifest from storage: %s, %s, errMsg=%v", idx, total, art.RepositoryName, blob.Digest, err) - if err := ignoreNotFound(func() error { - return gc.markDeleteFailed(ctx, blob) - }); err != nil { - gc.logger.Errorf("[%d/%d] failed to call gc.markDeleteFailed() after gc.registryCtlClient.DeleteManifest() error out: %s, %s, %v", idx, total, art.RepositoryName, blob.Digest, err) - return err - } - // if the system is set to read-only mode, return directly - if err == readonly.Err { - return err - } - skippedBlob = true + if count == 0 { + gc.logger.Warningf("[%s][%d/%d] no blob found to mark gc candidate deleting, ID:%d, digest:%s", uid, index, total, blob.ID, blob.Digest) continue } - gc.logger.Infof("[%d/%d] delete artifact blob record from database: %d, %s, %s", idx, total, art.ID, art.RepositoryName, art.Digest) - if err := ignoreNotFound(func() error { - return gc.blobMgr.CleanupAssociationsForArtifact(ctx.SystemContext(), art.Digest) - }); err != nil { - gc.logger.Errorf("[%d/%d] failed to call gc.blobMgr.CleanupAssociationsForArtifact(): %v, errMsg=%v", idx, total, art.Digest, err) - return err - } + // remove tags and revisions of a manifest + skippedBlob := false + if _, exist := gc.trashedArts[blob.Digest]; exist && blob.IsManifest() { + for _, art := range gc.trashedArts[blob.Digest] { + // Harbor cannot know the existing tags in the backend from its database, so let the v2 DELETE manifest to remove all of them. + gc.logger.Infof("[%s][%d/%d] delete the manifest with registry v2 API: %s, %s, %s", + uid, index, total, art.RepositoryName, blob.ContentType, blob.Digest) + if err := retry.Retry(func() error { + return ignoreNotFound(func() error { + err := v2DeleteManifest(art.RepositoryName, blob.Digest) + // if the system is in read-only mode, return an Abort error to skip retrying + if err == readonly.Err { + return retry.Abort(err) + } + return err + }) + }, retry.Callback(func(err error, sleep time.Duration) { + gc.logger.Infof("[%s][%d/%d] failed to exec v2DeleteManifest, error: %v, will retry again after: %s", uid, index, total, err, sleep) + })); err != nil { + gc.logger.Errorf("[%s][%d/%d] failed to delete manifest with v2 API, %s, %s, %v", uid, index, total, art.RepositoryName, blob.Digest, err) + if err := ignoreNotFound(func() error { + return gc.markDeleteFailed(ctx, blob) + }); err != nil { + gc.logger.Errorf("[%s][%d/%d] failed to call gc.markDeleteFailed() after v2DeleteManifest() error out: %s, %v", uid, index, total, blob.Digest, err) + return err + } + // if the system is set to read-only mode, return directly + if err == readonly.Err { + return err + } + skippedBlob = true + continue + } + // for manifest, it has to delete the revisions folder of each repository + gc.logger.Infof("[%s][%d/%d] delete manifest from storage: %s", uid, index, total, blob.Digest) + if err := retry.Retry(func() error { + return ignoreNotFound(func() error { + err := gc.registryCtlClient.DeleteManifest(art.RepositoryName, blob.Digest) + // if the system is in read-only mode, return an Abort error to skip retrying + if err == readonly.Err { + return retry.Abort(err) + } + return err + }) + }, retry.Callback(func(err error, sleep time.Duration) { + gc.logger.Infof("[%s][%d/%d] failed to exec DeleteManifest, error: %v, will retry again after: %s", uid, index, total, err, sleep) + })); err != nil { + gc.logger.Errorf("[%s][%d/%d] failed to remove manifest from storage: %s, %s, errMsg=%v", uid, index, total, art.RepositoryName, blob.Digest, err) + if err := ignoreNotFound(func() error { + return gc.markDeleteFailed(ctx, blob) + }); err != nil { + gc.logger.Errorf("[%s][%d/%d] failed to call gc.markDeleteFailed() after gc.registryCtlClient.DeleteManifest() error out: %s, %s, %v", uid, index, total, art.RepositoryName, blob.Digest, err) + return err + } + // if the system is set to read-only mode, return directly + if err == readonly.Err { + return err + } + skippedBlob = true + continue + } - gc.logger.Infof("[%d/%d] delete artifact trash record from database: %d, %s, %s", idx, total, art.ID, art.RepositoryName, art.Digest) - if err := ignoreNotFound(func() error { - return gc.artrashMgr.Delete(ctx.SystemContext(), art.ID) - }); err != nil { - gc.logger.Errorf("[%d/%d] failed to call gc.artrashMgr.Delete(): %v, errMsg=%v", idx, total, art.ID, err) - return err + gc.logger.Infof("[%s][%d/%d] delete artifact blob record from database: %d, %s, %s", uid, index, total, art.ID, art.RepositoryName, art.Digest) + if err := ignoreNotFound(func() error { + return gc.blobMgr.CleanupAssociationsForArtifact(ctx.SystemContext(), art.Digest) + }); err != nil { + gc.logger.Errorf("[%s][%d/%d] failed to call gc.blobMgr.CleanupAssociationsForArtifact(): %v, errMsg=%v", uid, index, total, art.Digest, err) + return err + } + + gc.logger.Infof("[%s][%d/%d] delete artifact trash record from database: %d, %s, %s", uid, index, total, art.ID, art.RepositoryName, art.Digest) + if err := ignoreNotFound(func() error { + return gc.artrashMgr.Delete(ctx.SystemContext(), art.ID) + }); err != nil { + gc.logger.Errorf("[%s][%d/%d] failed to call gc.artrashMgr.Delete(): %v, errMsg=%v", uid, index, total, art.ID, err) + return err + } + } } - } - } - // skip deleting the blob if the manifest's tag/revision is not deleted - if skippedBlob { - continue - } + // skip deleting the blob if the manifest's tag/revision is not deleted + if skippedBlob { + continue + } - // delete all of blobs, which include config, layer and manifest - // for the foreign layer, as it's not stored in the storage, no need to call the delete api and count size, but still have to delete the DB record. - if !blob.IsForeignLayer() { - gc.logger.Infof("[%d/%d] delete blob from storage: %s", idx, total, blob.Digest) - if err := retry.Retry(func() error { - return ignoreNotFound(func() error { - err := gc.registryCtlClient.DeleteBlob(blob.Digest) - // if the system is in read-only mode, return an Abort error to skip retrying - if err == readonly.Err { - return retry.Abort(err) + // delete all the blobs, which include config, layer and manifest + // for the foreign layer, as it's not stored in the storage, no need to call the delete api and count size, but still have to delete the DB record. + if !blob.IsForeignLayer() { + gc.logger.Infof("[%s][%d/%d] delete blob from storage: %s", uid, index, total, blob.Digest) + if err := retry.Retry(func() error { + return ignoreNotFound(func() error { + err := gc.registryCtlClient.DeleteBlob(blob.Digest) + // if the system is in read-only mode, return an Abort error to skip retrying + if err == readonly.Err { + return retry.Abort(err) + } + return err + }) + }, retry.Callback(func(err error, sleep time.Duration) { + gc.logger.Infof("[%s][%d/%d] failed to exec DeleteBlob, error: %v, will retry again after: %s", uid, index, total, err, sleep) + })); err != nil { + gc.logger.Errorf("[%s][%d/%d] failed to delete blob from storage: %s, %s, errMsg=%v", uid, index, total, blob.Digest, blob.Status, err) + if err := ignoreNotFound(func() error { + return gc.markDeleteFailed(ctx, blob) + }); err != nil { + gc.logger.Errorf("[%s][%d/%d] failed to call gc.markDeleteFailed() after gc.registryCtlClient.DeleteBlob() error out: %s, %v", uid, index, total, blob.Digest, err) + return err + } + // if the system is set to read-only mode, return directly + if err == readonly.Err { + return err + } + continue } - return err - }) - }, retry.Callback(func(err error, sleep time.Duration) { - gc.logger.Infof("[%d/%d] failed to exec DeleteBlob, error: %v, will retry again after: %s", idx, total, err, sleep) - })); err != nil { - gc.logger.Errorf("[%d/%d] failed to delete blob from storage: %s, %s, errMsg=%v", idx, total, blob.Digest, blob.Status, err) + atomic.AddInt64(&sweepSize, blob.Size) + } + + gc.logger.Infof("[%s][%d/%d] delete blob record from database: %d, %s", uid, index, total, blob.ID, blob.Digest) if err := ignoreNotFound(func() error { - return gc.markDeleteFailed(ctx, blob) + return gc.blobMgr.Delete(ctx.SystemContext(), blob.ID) }); err != nil { - gc.logger.Errorf("[%d/%d] failed to call gc.markDeleteFailed() after gc.registryCtlClient.DeleteBlob() error out: %s, %v", idx, total, blob.Digest, err) + gc.logger.Errorf("[%s][%d/%d] failed to delete blob from database: %s, %s, errMsg=%v", uid, index, total, blob.Digest, blob.Status, err) + if err := ignoreNotFound(func() error { + return gc.markDeleteFailed(ctx, blob) + }); err != nil { + gc.logger.Errorf("[%s][%d/%d] failed to call gc.markDeleteFailed() after gc.blobMgr.Delete() error out, %d, %s %v", uid, index, total, blob.ID, blob.Digest, err) + return err + } return err } - // if the system is set to read-only mode, return directly - if err == readonly.Err { - return err + + if blob.IsManifest() { + atomic.AddInt64(&mfCnt, 1) + } else { + atomic.AddInt64(&blobCnt, 1) } - continue } - sweepSize = sweepSize + blob.Size - } + return nil + }) + } - gc.logger.Infof("[%d/%d] delete blob record from database: %d, %s", idx, total, blob.ID, blob.Digest) - if err := ignoreNotFound(func() error { - return gc.blobMgr.Delete(ctx.SystemContext(), blob.ID) - }); err != nil { - gc.logger.Errorf("[%d/%d] failed to delete blob from database: %s, %s, errMsg=%v", idx, total, blob.Digest, blob.Status, err) - if err := ignoreNotFound(func() error { - return gc.markDeleteFailed(ctx, blob) - }); err != nil { - gc.logger.Errorf("[%d/%d] failed to call gc.markDeleteFailed() after gc.blobMgr.Delete() error out, %d, %s %v", idx, total, blob.ID, blob.Digest, err) - return err - } - return err - } - if blob.IsManifest() { - mfCnt++ - } else { - blobCnt++ - } + if err := g.Wait(); err != nil { + gc.logger.Errorf("failed to execute mark(), error out, %v", err) + return err } + gc.logger.Infof("%d blobs and %d manifests are actually deleted", blobCnt, mfCnt) gc.logger.Infof("The GC job actual frees up %d MB space.", sweepSize/1024/1024) diff --git a/src/jobservice/job/impl/gc/garbage_collection_test.go b/src/jobservice/job/impl/gc/garbage_collection_test.go index 3c78be42342..124cff7f983 100644 --- a/src/jobservice/job/impl/gc/garbage_collection_test.go +++ b/src/jobservice/job/impl/gc/garbage_collection_test.go @@ -161,9 +161,11 @@ func (suite *gcTestSuite) TestInit() { "delete_untagged": true, "redis_url_reg": "redis url", "time_window": 1, + "workers": float64(3), } suite.Nil(gc.init(ctx, params)) suite.True(gc.deleteUntagged) + suite.Equal(3, gc.workers) params = map[string]interface{}{ "delete_untagged": "unsupported", @@ -279,6 +281,7 @@ func (suite *gcTestSuite) TestRun() { "delete_untagged": false, "redis_url_reg": tests.GetRedisURL(), "time_window": 1, + "workers": 3, } suite.Nil(gc.Run(ctx, params)) @@ -375,6 +378,7 @@ func (suite *gcTestSuite) TestSweep() { ContentType: schema2.MediaTypeLayer, }, }, + workers: 3, } suite.Nil(gc.sweep(ctx)) diff --git a/src/jobservice/job/impl/gc/util.go b/src/jobservice/job/impl/gc/util.go index 6f92d42742e..1c16aa3827b 100644 --- a/src/jobservice/job/impl/gc/util.go +++ b/src/jobservice/job/impl/gc/util.go @@ -76,3 +76,18 @@ func ignoreNotFound(f func() error) error { } return nil } + +// divide if it is divisible, it gives the quotient. if it's not, it gives the remainder. +func divide(a, b int) (int, error) { + if b == 0 { + return 0, errors.New("the divided cannot be zero") + } + + quotient := a / b + remainder := a % b + + if quotient == 0 { + return remainder, nil + } + return quotient, nil +} diff --git a/src/jobservice/job/impl/gc/util_test.go b/src/jobservice/job/impl/gc/util_test.go new file mode 100644 index 00000000000..773a42d6fff --- /dev/null +++ b/src/jobservice/job/impl/gc/util_test.go @@ -0,0 +1,50 @@ +package gc + +import ( + "github.com/goharbor/harbor/src/lib/errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIgnoreNotFound(t *testing.T) { + var f = func() error { + return nil + } + assert.Nil(t, ignoreNotFound(f)) + f = func() error { + return errors.New(nil).WithMessage("my error") + } + assert.NotNil(t, ignoreNotFound(f)) + f = func() error { + return errors.New(nil).WithMessage("my error").WithCode(errors.BadRequestCode) + } + assert.NotNil(t, ignoreNotFound(f)) + f = func() error { + return errors.New(nil).WithMessage("my error").WithCode(errors.NotFoundCode) + } + assert.Nil(t, ignoreNotFound(f)) +} + +func TestDivide(t *testing.T) { + var result int + var err error + result, err = divide(1, 10) + assert.Nil(t, err) + assert.Equal(t, 1, result) + + result, err = divide(5, 10) + assert.Nil(t, err) + assert.Equal(t, 5, result) + + result, err = divide(30, 10) + assert.Nil(t, err) + assert.Equal(t, 3, result) + + result, err = divide(33, 10) + assert.Nil(t, err) + assert.Equal(t, 3, result) + + result, err = divide(33, 0) + assert.NotNil(t, err) +} diff --git a/src/server/v2.0/handler/gc.go b/src/server/v2.0/handler/gc.go index b52ba215732..7be005b9dfd 100644 --- a/src/server/v2.0/handler/gc.go +++ b/src/server/v2.0/handler/gc.go @@ -99,6 +99,17 @@ func (g *gcAPI) kick(ctx context.Context, scheType string, cron string, paramete if deleteUntagged, ok := parameters["delete_untagged"].(bool); ok { policy.DeleteUntagged = deleteUntagged } + if workers, ok := parameters["workers"].(json.Number); ok { + wInt, err := workers.Int64() + if err != nil { + return 0, errors.BadRequestError(fmt.Errorf("workers should be integer format")) + } + if !validateWorkers(int(wInt)) { + return 0, errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("Error: Invalid number of workers:%s. Workers must be greater than 0 and less than or equal to 5.", workers) + } + policy.Workers = int(wInt) + } + id, err = g.gcCtr.Start(ctx, policy, task.ExecutionTriggerManual) case ScheduleNone: err = g.gcCtr.DeleteSchedule(ctx) @@ -112,6 +123,16 @@ func (g *gcAPI) kick(ctx context.Context, scheType string, cron string, paramete if deleteUntagged, ok := parameters["delete_untagged"].(bool); ok { policy.DeleteUntagged = deleteUntagged } + if workers, ok := parameters["workers"].(json.Number); ok { + wInt, err := workers.Int64() + if err != nil { + return 0, errors.BadRequestError(fmt.Errorf("workers should be integer format")) + } + if !validateWorkers(int(wInt)) { + return 0, errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("Error: Invalid number of workers:%s. Workers must be greater than 0 and less than or equal to 5.", workers) + } + policy.Workers = int(wInt) + } err = g.updateSchedule(ctx, scheType, cron, policy) } return id, err @@ -260,3 +281,10 @@ func (g *gcAPI) StopGC(ctx context.Context, params operation.StopGCParams) middl return operation.NewStopGCOK() } + +func validateWorkers(workers int) bool { + if workers <= 0 || workers > 5 { + return false + } + return true +} diff --git a/src/server/v2.0/handler/gc_test.go b/src/server/v2.0/handler/gc_test.go new file mode 100644 index 00000000000..af5a4fc7d12 --- /dev/null +++ b/src/server/v2.0/handler/gc_test.go @@ -0,0 +1,15 @@ +package handler + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestValidateWorkers(t *testing.T) { + assert.False(t, validateWorkers(0)) + assert.False(t, validateWorkers(10)) + assert.False(t, validateWorkers(-1)) + assert.True(t, validateWorkers(1)) + assert.True(t, validateWorkers(5)) +} diff --git a/src/vendor/golang.org/x/sync/LICENSE b/src/vendor/golang.org/x/sync/LICENSE new file mode 100644 index 00000000000..6a66aea5eaf --- /dev/null +++ b/src/vendor/golang.org/x/sync/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/vendor/golang.org/x/sync/PATENTS b/src/vendor/golang.org/x/sync/PATENTS new file mode 100644 index 00000000000..733099041f8 --- /dev/null +++ b/src/vendor/golang.org/x/sync/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/src/vendor/golang.org/x/sync/errgroup/errgroup.go b/src/vendor/golang.org/x/sync/errgroup/errgroup.go new file mode 100644 index 00000000000..b18efb743fe --- /dev/null +++ b/src/vendor/golang.org/x/sync/errgroup/errgroup.go @@ -0,0 +1,132 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package errgroup provides synchronization, error propagation, and Context +// cancelation for groups of goroutines working on subtasks of a common task. +package errgroup + +import ( + "context" + "fmt" + "sync" +) + +type token struct{} + +// A Group is a collection of goroutines working on subtasks that are part of +// the same overall task. +// +// A zero Group is valid, has no limit on the number of active goroutines, +// and does not cancel on error. +type Group struct { + cancel func(error) + + wg sync.WaitGroup + + sem chan token + + errOnce sync.Once + err error +} + +func (g *Group) done() { + if g.sem != nil { + <-g.sem + } + g.wg.Done() +} + +// WithContext returns a new Group and an associated Context derived from ctx. +// +// The derived Context is canceled the first time a function passed to Go +// returns a non-nil error or the first time Wait returns, whichever occurs +// first. +func WithContext(ctx context.Context) (*Group, context.Context) { + ctx, cancel := withCancelCause(ctx) + return &Group{cancel: cancel}, ctx +} + +// Wait blocks until all function calls from the Go method have returned, then +// returns the first non-nil error (if any) from them. +func (g *Group) Wait() error { + g.wg.Wait() + if g.cancel != nil { + g.cancel(g.err) + } + return g.err +} + +// Go calls the given function in a new goroutine. +// It blocks until the new goroutine can be added without the number of +// active goroutines in the group exceeding the configured limit. +// +// The first call to return a non-nil error cancels the group's context, if the +// group was created by calling WithContext. The error will be returned by Wait. +func (g *Group) Go(f func() error) { + if g.sem != nil { + g.sem <- token{} + } + + g.wg.Add(1) + go func() { + defer g.done() + + if err := f(); err != nil { + g.errOnce.Do(func() { + g.err = err + if g.cancel != nil { + g.cancel(g.err) + } + }) + } + }() +} + +// TryGo calls the given function in a new goroutine only if the number of +// active goroutines in the group is currently below the configured limit. +// +// The return value reports whether the goroutine was started. +func (g *Group) TryGo(f func() error) bool { + if g.sem != nil { + select { + case g.sem <- token{}: + // Note: this allows barging iff channels in general allow barging. + default: + return false + } + } + + g.wg.Add(1) + go func() { + defer g.done() + + if err := f(); err != nil { + g.errOnce.Do(func() { + g.err = err + if g.cancel != nil { + g.cancel(g.err) + } + }) + } + }() + return true +} + +// SetLimit limits the number of active goroutines in this group to at most n. +// A negative value indicates no limit. +// +// Any subsequent call to the Go method will block until it can add an active +// goroutine without exceeding the configured limit. +// +// The limit must not be modified while any goroutines in the group are active. +func (g *Group) SetLimit(n int) { + if n < 0 { + g.sem = nil + return + } + if len(g.sem) != 0 { + panic(fmt.Errorf("errgroup: modify limit while %v goroutines in the group are still active", len(g.sem))) + } + g.sem = make(chan token, n) +} diff --git a/src/vendor/golang.org/x/sync/errgroup/go120.go b/src/vendor/golang.org/x/sync/errgroup/go120.go new file mode 100644 index 00000000000..7d419d3760c --- /dev/null +++ b/src/vendor/golang.org/x/sync/errgroup/go120.go @@ -0,0 +1,14 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.20 +// +build go1.20 + +package errgroup + +import "context" + +func withCancelCause(parent context.Context) (context.Context, func(error)) { + return context.WithCancelCause(parent) +} diff --git a/src/vendor/golang.org/x/sync/errgroup/pre_go120.go b/src/vendor/golang.org/x/sync/errgroup/pre_go120.go new file mode 100644 index 00000000000..1795c18ace0 --- /dev/null +++ b/src/vendor/golang.org/x/sync/errgroup/pre_go120.go @@ -0,0 +1,15 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !go1.20 +// +build !go1.20 + +package errgroup + +import "context" + +func withCancelCause(parent context.Context) (context.Context, func(error)) { + ctx, cancel := context.WithCancel(parent) + return ctx, func(error) { cancel() } +} diff --git a/src/vendor/modules.txt b/src/vendor/modules.txt index 35b0e2f1eaf..408846e6254 100644 --- a/src/vendor/modules.txt +++ b/src/vendor/modules.txt @@ -696,6 +696,9 @@ golang.org/x/oauth2/google/internal/externalaccount golang.org/x/oauth2/internal golang.org/x/oauth2/jws golang.org/x/oauth2/jwt +# golang.org/x/sync v0.3.0 +## explicit; go 1.17 +golang.org/x/sync/errgroup # golang.org/x/sys v0.7.0 ## explicit; go 1.17 golang.org/x/sys/internal/unsafeheader From d84b1d07d200f1c64708d420cf9903c207e18b81 Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Thu, 29 Jun 2023 17:30:50 +0800 Subject: [PATCH 23/38] Update table scan_report and extract cvss_v3_score from vendor attribute (#18854) For better performance when query cve information, add summary information to scan_report Extract cve_score from vendor attribute in vulnerability_record SQL migrate script for the update Signed-off-by: stonezdj --- .../postgresql/0120_2.9.0_schema.up.sql | 71 ++++++++++++++- src/pkg/scan/dao/scan/model.go | 26 +++--- src/pkg/scan/dao/scan/report.go | 13 +++ .../scan/postprocessors/report_converters.go | 86 ++++++++++++++++++- .../postprocessors/report_converters_test.go | 76 ++++++++++++++++ src/pkg/scan/report/manager.go | 7 ++ src/testing/pkg/scan/report/manager.go | 21 +++++ 7 files changed, 287 insertions(+), 13 deletions(-) diff --git a/make/migrations/postgresql/0120_2.9.0_schema.up.sql b/make/migrations/postgresql/0120_2.9.0_schema.up.sql index f4424856d7d..8204b0c75c1 100644 --- a/make/migrations/postgresql/0120_2.9.0_schema.up.sql +++ b/make/migrations/postgresql/0120_2.9.0_schema.up.sql @@ -4,4 +4,73 @@ CREATE INDEX IF NOT EXISTS idx_task_extra_attrs_report_uuids ON task USING gin ( UPDATE execution SET vendor_id = (extra_attrs -> 'artifact' ->> 'id')::integer WHERE jsonb_path_exists(extra_attrs::jsonb, '$.artifact.id') AND vendor_id IN (SELECT id FROM scanner_registration) -AND vendor_type = 'IMAGE_SCAN'; \ No newline at end of file +AND vendor_type = 'IMAGE_SCAN'; + +/* extract score from vendor attribute */ +UPDATE vulnerability_record +SET cvss_score_v3 = (vendor_attributes->'CVSS'->'nvd'->>'V3Score')::double precision +WHERE jsonb_path_exists(vendor_attributes::jsonb, '$.CVSS.nvd.V3Score'); + +/* add summary information in scan_report */ +ALTER TABLE scan_report ADD COLUMN IF NOT EXISTS critical_cnt BIGINT; +ALTER TABLE scan_report ADD COLUMN IF NOT EXISTS high_cnt BIGINT; +ALTER TABLE scan_report ADD COLUMN IF NOT EXISTS medium_cnt BIGINT; +ALTER TABLE scan_report ADD COLUMN IF NOT EXISTS low_cnt BIGINT; +ALTER TABLE scan_report ADD COLUMN IF NOT EXISTS none_cnt BIGINT; +ALTER TABLE scan_report ADD COLUMN IF NOT EXISTS unknown_cnt BIGINT; +ALTER TABLE scan_report ADD COLUMN IF NOT EXISTS fixable_cnt BIGINT; + +/* extract summary information for previous scan_report */ +DO +$$ + DECLARE + report RECORD; + v RECORD; + critical_count BIGINT; + high_count BIGINT; + none_count BIGINT; + medium_count BIGINT; + low_count BIGINT; + unknown_count BIGINT; + fixable_count BIGINT; + BEGIN + FOR report IN SELECT uuid FROM scan_report + LOOP + critical_count := 0; + high_count := 0; + medium_count := 0; + none_count := 0; + low_count := 0; + unknown_count := 0; + FOR v IN SELECT vr.severity, vr.fixed_version + FROM report_vulnerability_record rvr, + vulnerability_record vr + WHERE rvr.report_uuid = report.uuid + AND rvr.vuln_record_id = vr.id + LOOP + IF v.severity = 'Critical' THEN + critical_count = critical_count + 1; + ELSIF v.severity = 'High' THEN + high_count = high_count + 1; + ELSIF v.severity = 'Medium' THEN + medium_count = medium_count + 1; + ELSIF v.severity = 'Low' THEN + low_count = low_count + 1; + ELSIF v.severity = 'None' THEN + none_count = none_count + 1; + ELSIF v.severity = 'Unknown' THEN + unknown_count = unknown_count + 1; + ELSIF v.fixed_version IS NOT NULL THEN + fixable_count = fixable_count + 1; + END IF; + END LOOP; + UPDATE scan_report + SET critical_cnt = critical_count, + high_cnt = high_count, + medium_cnt = medium_count, + low_cnt = low_count, + unknown_cnt = unknown_count + WHERE uuid = report.uuid; + END LOOP; + END +$$; diff --git a/src/pkg/scan/dao/scan/model.go b/src/pkg/scan/dao/scan/model.go index e392cb78e50..09e8a05ea3b 100644 --- a/src/pkg/scan/dao/scan/model.go +++ b/src/pkg/scan/dao/scan/model.go @@ -22,16 +22,22 @@ import ( // Report of the scan. // Identified by the `digest`, `registration_uuid` and `mime_type`. type Report struct { - ID int64 `orm:"pk;auto;column(id)"` - UUID string `orm:"unique;column(uuid)"` - Digest string `orm:"column(digest)"` - RegistrationUUID string `orm:"column(registration_uuid)"` - MimeType string `orm:"column(mime_type)"` - Report string `orm:"column(report);type(json)"` - - Status string `orm:"-"` - StartTime time.Time `orm:"-"` - EndTime time.Time `orm:"-"` + ID int64 `orm:"pk;auto;column(id)"` + UUID string `orm:"unique;column(uuid)"` + Digest string `orm:"column(digest)"` + RegistrationUUID string `orm:"column(registration_uuid)"` + MimeType string `orm:"column(mime_type)"` + Report string `orm:"column(report);type(json)"` + CriticalCnt int64 `orm:"column(critical_cnt)"` + HighCnt int64 `orm:"column(high_cnt)"` + MediumCnt int64 `orm:"column(medium_cnt)"` + LowCnt int64 `orm:"column(low_cnt)"` + UnknownCnt int64 `orm:"column(unknown_cnt)"` + NoneCnt int64 `orm:"column(none_cnt)"` + FixableCnt int64 `orm:"column(fixable_cnt)"` + Status string `orm:"-"` + StartTime time.Time `orm:"-"` + EndTime time.Time `orm:"-"` } // TableName for Report diff --git a/src/pkg/scan/dao/scan/report.go b/src/pkg/scan/dao/scan/report.go index cf1dfb03b34..f30b36ddb7d 100644 --- a/src/pkg/scan/dao/scan/report.go +++ b/src/pkg/scan/dao/scan/report.go @@ -36,6 +36,8 @@ type DAO interface { List(ctx context.Context, query *q.Query) ([]*Report, error) // UpdateReportData only updates the `report` column with conditions matched. UpdateReportData(ctx context.Context, uuid string, report string) error + // Update update report + Update(ctx context.Context, r *Report, cols ...string) error } // New returns an instance of the default DAO @@ -97,3 +99,14 @@ func (d *dao) UpdateReportData(ctx context.Context, uuid string, report string) _, err = qt.Filter("uuid", uuid).Update(data) return err } + +func (d *dao) Update(ctx context.Context, r *Report, cols ...string) error { + o, err := orm.FromContext(ctx) + if err != nil { + return err + } + if _, err := o.Update(r, cols...); err != nil { + return err + } + return nil +} diff --git a/src/pkg/scan/postprocessors/report_converters.go b/src/pkg/scan/postprocessors/report_converters.go index e1f67536eaf..c3bb51363b2 100644 --- a/src/pkg/scan/postprocessors/report_converters.go +++ b/src/pkg/scan/postprocessors/report_converters.go @@ -26,6 +26,7 @@ import ( "github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/q" "github.com/goharbor/harbor/src/pkg/scan/dao/scan" + "github.com/goharbor/harbor/src/pkg/scan/report" "github.com/goharbor/harbor/src/pkg/scan/vuln" ) @@ -151,7 +152,7 @@ func (c *nativeToRelationalSchemaConverter) toSchema(ctx context.Context, report var newRecords []*scan.VulnerabilityRecord for _, v := range vulnReport.Vulnerabilities { if !s.Exists(v.Key()) { - newRecords = append(newRecords, toVulnerabilityRecord(v, registrationUUID)) + newRecords = append(newRecords, toVulnerabilityRecord(ctx, v, registrationUUID)) } } @@ -230,7 +231,7 @@ func (c *nativeToRelationalSchemaConverter) getNativeV1ReportFromResolvedData(ct return report, nil } -func toVulnerabilityRecord(item *vuln.VulnerabilityItem, registrationUUID string) *scan.VulnerabilityRecord { +func toVulnerabilityRecord(ctx context.Context, item *vuln.VulnerabilityItem, registrationUUID string) *scan.VulnerabilityRecord { record := new(scan.VulnerabilityRecord) record.CVEID = item.ID @@ -261,6 +262,12 @@ func toVulnerabilityRecord(item *vuln.VulnerabilityItem, registrationUUID string if err == nil { record.VendorAttributes = string(vendorAttributes) } + + // parse the NVD score from the vendor attributes + nvdScore := parseScoreFromVendorAttribute(ctx, string(vendorAttributes)) + if record.CVE3Score == nil { + record.CVE3Score = &nvdScore + } } return record @@ -290,3 +297,78 @@ func toVulnerabilityItem(record *scan.VulnerabilityRecord, artifactDigest string return item } + +// updateReport updates the report summary with the vulnerability counts +func (c *nativeToRelationalSchemaConverter) updateReport(ctx context.Context, vulnerabilities []*vuln.VulnerabilityItem, reportUUID string) error { + log.G(ctx).WithFields(log.Fields{"reportUUID": reportUUID}).Debugf("Update report summary for report") + CriticalCnt := int64(0) + HighCnt := int64(0) + MediumCnt := int64(0) + LowCnt := int64(0) + NoneCnt := int64(0) + UnknownCnt := int64(0) + FixableCnt := int64(0) + + for _, v := range vulnerabilities { + v.Severity = vuln.ParseSeverityVersion3(v.Severity.String()) + switch v.Severity { + case vuln.Critical: + CriticalCnt++ + case vuln.High: + HighCnt++ + case vuln.Medium: + MediumCnt++ + case vuln.Low: + LowCnt++ + case vuln.None: + NoneCnt++ + case vuln.Unknown: + UnknownCnt++ + } + if len(v.FixVersion) > 0 { + FixableCnt++ + } + } + + reports, err := report.Mgr.List(ctx, q.New(q.KeyWords{"uuid": reportUUID})) + if err != nil { + return err + } + if len(reports) == 0 { + return errors.New(nil).WithMessage("report not found, uuid:%v", reportUUID) + } + r := reports[0] + + r.CriticalCnt = CriticalCnt + r.HighCnt = HighCnt + r.MediumCnt = MediumCnt + r.LowCnt = LowCnt + r.NoneCnt = NoneCnt + r.FixableCnt = FixableCnt + r.UnknownCnt = UnknownCnt + + return report.Mgr.Update(ctx, r, "CriticalCnt", "HighCnt", "MediumCnt", "LowCnt", "NoneCnt", "UnknownCnt", "FixableCnt") +} + +// CVSS ... +type CVSS struct { + NVD Nvd `json:"nvd"` +} + +// Nvd ... +type Nvd struct { + V3Score float64 `json:"V3Score"` +} + +func parseScoreFromVendorAttribute(ctx context.Context, vendorAttribute string) (NvdV3Score float64) { + var data map[string]CVSS + 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 + } + return 0 +} diff --git a/src/pkg/scan/postprocessors/report_converters_test.go b/src/pkg/scan/postprocessors/report_converters_test.go index 2da4325fda4..057113a4793 100644 --- a/src/pkg/scan/postprocessors/report_converters_test.go +++ b/src/pkg/scan/postprocessors/report_converters_test.go @@ -15,6 +15,7 @@ package postprocessors import ( + "context" "encoding/json" "testing" "time" @@ -294,6 +295,7 @@ type TestReportConverterSuite struct { vulnerabilityRecordDao scan.VulnerabilityRecordDao reportDao scan.DAO registrationID string + nc *nativeToRelationalSchemaConverter } // SetupTest prepares env for test cases. @@ -318,6 +320,7 @@ func TestReportConverterTests(t *testing.T) { // SetupSuite sets up the report converter suite test cases func (suite *TestReportConverterSuite) SetupSuite() { + suite.nc = &nativeToRelationalSchemaConverter{dao: scan.NewVulnerabilityRecordDao()} suite.rc = NewNativeToRelationalSchemaConverter() suite.Suite.SetupSuite() suite.vulnerabilityRecordDao = scan.NewVulnerabilityRecordDao() @@ -510,3 +513,76 @@ func (suite *TestReportConverterSuite) validateReportSummary(summary string, raw require.NoError(suite.T(), err) assert.Equal(suite.T(), string(data), summary) } + +func (suite *TestReportConverterSuite) TestUpdateReport() { + ctx := suite.Context() + vuls := []*vuln.VulnerabilityItem{ + { + Severity: "Critical", FixVersion: "2.9.1", + }, + { + Severity: "Critical", + }, + { + Severity: "High", + }, + { + Severity: "Medium", + }, + { + Severity: "Low", + }, + { + Severity: "None", + }, + { + Severity: "Unknown", + }, + } + rp := &scan.Report{ + Digest: "d1001", + RegistrationUUID: "ruuid", + MimeType: v1.MimeTypeGenericVulnerabilityReport, + Report: sampleReportWithMixedSeverity, + StartTime: time.Now(), + EndTime: time.Now().Add(1000), + UUID: "reportUUID3", + } + id, err := suite.reportDao.Create(ctx, rp) + suite.NoError(err) + suite.True(id > 0) + err = suite.nc.updateReport(ctx, vuls, rp.UUID) + suite.NoError(err) + rpts, err := suite.reportDao.List(ctx, q.New(q.KeyWords{"UUID": rp.UUID})) + suite.NoError(err) + suite.Equal(1, len(rpts)) + suite.Equal(int64(2), rpts[0].CriticalCnt) + suite.Equal(int64(1), rpts[0].HighCnt) + suite.Equal(int64(1), rpts[0].MediumCnt) + suite.Equal(int64(1), rpts[0].LowCnt) + suite.Equal(int64(1), rpts[0].NoneCnt) + suite.Equal(int64(1), rpts[0].UnknownCnt) + suite.Equal(int64(1), rpts[0].FixableCnt) +} + +func Test_parseScoreFromVendorAttribute(t *testing.T) { + type args struct { + vendorAttribute string + } + tests := []struct { + name string + args args + wantNvdV3Score float64 + }{ + {"normal", args{`{"CVSS":{"nvd":{"V2Score":4.3,"V2Vector":"AV:N/AC:M/Au:N/C:N/I:N/A:P","V3Score":6.5,"V3Vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H"}}}`}, 6.5}, + {"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}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotNvdV3Score := parseScoreFromVendorAttribute(context.Background(), tt.args.vendorAttribute) + assert.Equalf(t, tt.wantNvdV3Score, gotNvdV3Score, "parseScoreFromVendorAttribute(%v)", tt.args.vendorAttribute) + }) + } +} diff --git a/src/pkg/scan/report/manager.go b/src/pkg/scan/report/manager.go index d033e163a25..fa6415ed0bd 100644 --- a/src/pkg/scan/report/manager.go +++ b/src/pkg/scan/report/manager.go @@ -101,6 +101,9 @@ type Manager interface { // []*scan.Report : report list // error : non nil error if any errors occurred List(ctx context.Context, query *q.Query) ([]*scan.Report, error) + + // Update update report information + Update(ctx context.Context, r *scan.Report, cols ...string) error } // basicManager is a default implementation of report manager. @@ -219,3 +222,7 @@ func (bm *basicManager) DeleteByDigests(ctx context.Context, digests ...string) func (bm *basicManager) List(ctx context.Context, query *q.Query) ([]*scan.Report, error) { return bm.dao.List(ctx, query) } + +func (bm *basicManager) Update(ctx context.Context, r *scan.Report, cols ...string) error { + return bm.dao.Update(ctx, r, cols...) +} diff --git a/src/testing/pkg/scan/report/manager.go b/src/testing/pkg/scan/report/manager.go index 5fc83d0b44b..4a748241937 100644 --- a/src/testing/pkg/scan/report/manager.go +++ b/src/testing/pkg/scan/report/manager.go @@ -127,6 +127,27 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*scan.Report, er return r0, r1 } +// Update provides a mock function with given fields: ctx, r, cols +func (_m *Manager) Update(ctx context.Context, r *scan.Report, cols ...string) error { + _va := make([]interface{}, len(cols)) + for _i := range cols { + _va[_i] = cols[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, r) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *scan.Report, ...string) error); ok { + r0 = rf(ctx, r, cols...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // UpdateReportData provides a mock function with given fields: ctx, uuid, _a2 func (_m *Manager) UpdateReportData(ctx context.Context, uuid string, _a2 string) error { ret := _m.Called(ctx, uuid, _a2) From 8fe561865d980b4df55f98a80f24a9189cd156a7 Mon Sep 17 00:00:00 2001 From: Shijun Sun <30999793+AllForNothing@users.noreply.github.com> Date: Mon, 3 Jul 2023 10:58:14 +0800 Subject: [PATCH 24/38] Add unit test for hidden columns (#18873) 1. Fixes #18870 Signed-off-by: AllForNothing --- .../list-replication-rule.component.spec.ts | 8 ++++++++ .../artifact-list-tab.component.spec.ts | 7 +++++++ .../artifact-list-tab.component.ts | 14 +------------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/portal/src/app/base/left-side-nav/replication/replication/list-replication-rule/list-replication-rule.component.spec.ts b/src/portal/src/app/base/left-side-nav/replication/replication/list-replication-rule/list-replication-rule.component.spec.ts index 43b6edda12a..4c2f3fb5735 100644 --- a/src/portal/src/app/base/left-side-nav/replication/replication/list-replication-rule/list-replication-rule.component.spec.ts +++ b/src/portal/src/app/base/left-side-nav/replication/replication/list-replication-rule/list-replication-rule.component.spec.ts @@ -159,4 +159,12 @@ describe('ListReplicationRuleComponent (inline template)', () => { fixture.nativeElement.querySelector('.modal-body'); expect(body).toBeFalsy(); }); + + it('the length of hide array should equal to the number of column', async () => { + comp.loading = false; + fixture.detectChanges(); + await fixture.whenStable(); + const cols = fixture.nativeElement.querySelectorAll('clr-dg-column'); + expect(cols.length).toEqual(comp.hiddenArray.length); + }); }); 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 bd67bf39f4e..b9fb2f5cdea 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 @@ -353,6 +353,13 @@ describe('ArtifactListTabComponent (inline template)', () => { fixture.nativeElement.querySelector('.confirmation-title') ).toBeTruthy(); }); + it('the length of hide array should equal to the number of column', async () => { + comp.loading = false; + fixture.detectChanges(); + await fixture.whenStable(); + const cols = fixture.nativeElement.querySelectorAll('.datagrid-column'); + expect(cols.length).toEqual(comp.hiddenArray.length); + }); }); 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 72e0459f18d..2ffa0291f8b 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 @@ -162,19 +162,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { hiddenArray: boolean[] = getHiddenArrayFromLocalStorage( PageSizeMapKeys.ARTIFACT_LIST_TAB_COMPONENT, - [ - false, - false, - false, - false, - false, - false, - false, - true, - false, - false, - false, - ] + [false, false, false, false, false, false, true, false, false, false] ); deleteAccessorySub: Subscription; copyDigestSub: Subscription; From ef96c729c029bbf7a96121803fb2a6f12b846591 Mon Sep 17 00:00:00 2001 From: Shijun Sun <30999793+AllForNothing@users.noreply.github.com> Date: Mon, 3 Jul 2023 15:58:37 +0800 Subject: [PATCH 25/38] Add costomized banner message UI (#18827) 1.Fixes #18719 2.Add Banner Message item to configuration 3.Add banner_message property to systeminfo API Signed-off-by: AllForNothing --- README.md | 2 +- api/v2.0/swagger.yaml | 14 ++ src/common/const.go | 3 + src/controller/systeminfo/controller.go | 8 ++ src/controller/systeminfo/controller_test.go | 3 + src/lib/config/metadata/metadatalist.go | 2 + src/lib/config/userconfig.go | 5 + .../app-level-alerts.component.html | 9 ++ .../app-level-alerts.component.ts | 79 +++++++++++ .../left-side-nav/config/config.service.ts | 8 +- .../app/base/left-side-nav/config/config.ts | 30 +++++ .../system/system-settings.component.html | 121 +++++++++++++++++ .../system/system-settings.component.scss | 51 +++++++- .../system/system-settings.component.ts | 123 +++++++++++++++++- src/portal/src/app/services/app-config.ts | 3 + .../services/event-service/event.service.ts | 1 + src/portal/src/i18n/lang/de-de-lang.json | 12 ++ src/portal/src/i18n/lang/en-us-lang.json | 16 ++- src/portal/src/i18n/lang/es-es-lang.json | 12 ++ src/portal/src/i18n/lang/fr-fr-lang.json | 12 ++ src/portal/src/i18n/lang/pt-br-lang.json | 12 ++ src/portal/src/i18n/lang/tr-tr-lang.json | 12 ++ src/portal/src/i18n/lang/zh-cn-lang.json | 12 ++ src/portal/src/i18n/lang/zh-tw-lang.json | 12 ++ src/server/v2.0/handler/systeminfo.go | 1 + 25 files changed, 551 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 923eaf1fa3b..88d00b5a623 100644 --- a/README.md +++ b/README.md @@ -116,4 +116,4 @@ This project uses open source components which have additional licensing terms. ## Fossa Status -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fgoharbor%2Fharbor.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fgoharbor%2Fharbor?ref=badge_large) \ No newline at end of file +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fgoharbor%2Fharbor.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fgoharbor%2Fharbor?ref=badge_large) diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index de635c98128..8748592f97b 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -7682,6 +7682,12 @@ definitions: GeneralInfo: type: object properties: + banner_message: + type: string + x-nullable: true + x-omitempty: true + description: The banner message for the UI. It is the stringified result of the banner message object. + example: "{\"closable\":true,\"message\":\"your banner message content\",\"type\":\"warning\",\"fromDate\":\"06/19/2023\",\"toDate\":\"06/21/2023\"}" current_time: type: string format: date-time @@ -8820,6 +8826,9 @@ definitions: session_timeout: $ref: '#/definitions/IntegerConfigItem' description: The session timeout in minutes + banner_message: + $ref: '#/definitions/StringConfigItem' + description: The banner message for the UI.It is the stringified result of the banner message object Configurations: type: object properties: @@ -9088,6 +9097,11 @@ definitions: description: Whether or not to skip update pull time for scanner x-omitempty: true x-isnullable: true + banner_message: + type: string + description: The banner message for the UI.It is the stringified result of the banner message object + x-omitempty: true + x-isnullable: true StringConfigItem: type: object properties: diff --git a/src/common/const.go b/src/common/const.go index f030dacf1d9..5e30b78fcc0 100644 --- a/src/common/const.go +++ b/src/common/const.go @@ -219,6 +219,9 @@ const ( // SessionTimeout defines the web session timeout SessionTimeout = "session_timeout" + // Customized banner message + BannerMessage = "banner_message" + // UIMaxLengthLimitedOfNumber is the max length that UI limited for type number UIMaxLengthLimitedOfNumber = 10 // ExecutionStatusRefreshIntervalSeconds is the interval seconds for refreshing execution status diff --git a/src/controller/systeminfo/controller.go b/src/controller/systeminfo/controller.go index cfca363f51d..1487f036bb7 100644 --- a/src/controller/systeminfo/controller.go +++ b/src/controller/systeminfo/controller.go @@ -47,6 +47,7 @@ type Data struct { PrimaryAuthMode bool SelfRegistration bool HarborVersion string + BannerMessage string AuthProxySettings *models.HTTPAuthProxy Protected *protectedData } @@ -90,11 +91,18 @@ func (c *controller) GetInfo(ctx context.Context, opt Options) (*Data, error) { logger.Errorf("Error occurred getting config: %v", err) return nil, err } + mgr := config.GetCfgManager(ctx) + err = mgr.Load(ctx) + if err != nil { + logger.Errorf("Error occurred loading config: %v", err) + return nil, err + } res := &Data{ AuthMode: utils.SafeCastString(cfg[common.AUTHMode]), PrimaryAuthMode: utils.SafeCastBool(cfg[common.PrimaryAuthMode]), SelfRegistration: utils.SafeCastBool(cfg[common.SelfRegistration]), HarborVersion: fmt.Sprintf("%s-%s", version.ReleaseVersion, version.GitCommit), + BannerMessage: utils.SafeCastString(mgr.Get(ctx, common.BannerMessage).GetString()), } if res.AuthMode == common.HTTPAuth { if s, err := config.HTTPAuthProxySetting(ctx); err == nil { diff --git a/src/controller/systeminfo/controller_test.go b/src/controller/systeminfo/controller_test.go index a8156f1ed91..a2e98a1ba74 100644 --- a/src/controller/systeminfo/controller_test.go +++ b/src/controller/systeminfo/controller_test.go @@ -31,6 +31,7 @@ func (s *sysInfoCtlTestSuite) SetupTest() { common.RegistryStorageProviderName: "filesystem", common.ReadOnly: false, common.NotificationEnable: false, + common.BannerMessage: "{\"closable\":false,\"message\":\"Just for test\",\"type\":\" error\"}", } config.InitWithSettings(conf) @@ -58,6 +59,7 @@ func (s *sysInfoCtlTestSuite) TestGetInfo() { AuthMode: "db_auth", HarborVersion: "test-fakeid", SelfRegistration: true, + BannerMessage: "{\"closable\":false,\"message\":\"Just for test\",\"type\":\" error\"}", }, }, { @@ -66,6 +68,7 @@ func (s *sysInfoCtlTestSuite) TestGetInfo() { AuthMode: "db_auth", HarborVersion: "test-fakeid", SelfRegistration: true, + BannerMessage: "{\"closable\":false,\"message\":\"Just for test\",\"type\":\" error\"}", Protected: &protectedData{ RegistryURL: "test.goharbor.io", ExtURL: "https://test.goharbor.io", diff --git a/src/lib/config/metadata/metadatalist.go b/src/lib/config/metadata/metadatalist.go index 3f7c308128d..bb3285e5ff5 100644 --- a/src/lib/config/metadata/metadatalist.go +++ b/src/lib/config/metadata/metadatalist.go @@ -189,5 +189,7 @@ var ( {Name: common.SessionTimeout, Scope: UserScope, Group: BasicGroup, EnvKey: "SESSION_TIMEOUT", DefaultValue: "60", ItemType: &Int64Type{}, Editable: true, Description: `The session timeout in minutes`}, {Name: common.ExecutionStatusRefreshIntervalSeconds, Scope: SystemScope, Group: BasicGroup, EnvKey: "EXECUTION_STATUS_REFRESH_INTERVAL_SECONDS", DefaultValue: "30", ItemType: &Int64Type{}, Editable: false, Description: `The interval seconds to refresh the execution status`}, + + {Name: common.BannerMessage, Scope: UserScope, Group: BasicGroup, EnvKey: "BANNER_MESSAGE", DefaultValue: "", ItemType: &StringType{}, Editable: true, Description: `The customized banner message for the UI`}, } ) diff --git a/src/lib/config/userconfig.go b/src/lib/config/userconfig.go index 863c8fb9e71..e22b438f7eb 100644 --- a/src/lib/config/userconfig.go +++ b/src/lib/config/userconfig.go @@ -255,3 +255,8 @@ func ScannerSkipUpdatePullTime(ctx context.Context) bool { log.Infof("skip_update_pull_time:%v", DefaultMgr().Get(ctx, common.ScannerSkipUpdatePullTime).GetBool()) return DefaultMgr().Get(ctx, common.ScannerSkipUpdatePullTime).GetBool() } + +// BannerMessage returns the customized banner message +func BannerMessage(ctx context.Context) string { + return DefaultMgr().Get(ctx, common.BannerMessage).GetString() +} diff --git a/src/portal/src/app/base/harbor-shell/app-level-alerts/app-level-alerts.component.html b/src/portal/src/app/base/harbor-shell/app-level-alerts/app-level-alerts.component.html index 797d1aad3b2..769891065a2 100644 --- a/src/portal/src/app/base/harbor-shell/app-level-alerts/app-level-alerts.component.html +++ b/src/portal/src/app/base/harbor-shell/app-level-alerts/app-level-alerts.component.html @@ -1,4 +1,13 @@ + + + {{ getBannerMessage() }} + + = new Date(bm.fromDate) + ); + } + if (bm?.fromDate && !bm?.toDate) { + return new Date(current) >= new Date(bm.fromDate); + } + + if (!bm?.fromDate && bm?.toDate) { + return new Date(current) <= new Date(bm.toDate); + } + } + return false; + } + + getBannerMessage() { + if ( + this.appConfigService.getConfig()?.banner_message && + ( + JSON.parse( + this.appConfigService.getConfig()?.banner_message + ) as BannerMessage + )?.message + ) { + return ( + JSON.parse( + this.appConfigService.getConfig()?.banner_message + ) as BannerMessage + )?.message; + } + return null; + } + + getBannerMessageType() { + if ( + this.appConfigService.getConfig()?.banner_message && + ( + JSON.parse( + this.appConfigService.getConfig()?.banner_message + ) as BannerMessage + )?.type + ) { + return ( + JSON.parse( + this.appConfigService.getConfig()?.banner_message + ) as BannerMessage + )?.type; + } + return BannerMessageType.WARNING; + } + + getBannerMessageClosable(): boolean { + if (this.appConfigService.getConfig()?.banner_message) { + return ( + JSON.parse( + this.appConfigService.getConfig()?.banner_message + ) as BannerMessage + )?.closable; + } + return true; + } } diff --git a/src/portal/src/app/base/left-side-nav/config/config.service.ts b/src/portal/src/app/base/left-side-nav/config/config.service.ts index 26ac30f2f3f..516eac239e9 100644 --- a/src/portal/src/app/base/left-side-nav/config/config.service.ts +++ b/src/portal/src/app/base/left-side-nav/config/config.service.ts @@ -11,6 +11,10 @@ import { clone } from '../../../shared/units/utils'; import { MessageHandlerService } from '../../../shared/services/message-handler.service'; import { finalize } from 'rxjs/operators'; import { Observable, Subscription } from 'rxjs'; +import { + EventService, + HarborEvent, +} from '../../../services/event-service/event.service'; const fakePass = 'aWpLOSYkIzJTTU4wMDkx'; @@ -24,7 +28,8 @@ export class ConfigService { constructor( private confirmService: ConfirmationDialogService, private configureService: ConfigureService, - private msgHandler: MessageHandlerService + private msgHandler: MessageHandlerService, + private event: EventService ) { this._confirmSub = this.confirmService.confirmationConfirm$.subscribe( confirmation => { @@ -66,6 +71,7 @@ export class ConfigService { .subscribe( res => { this._currentConfig = res as Configuration; + this.event.publish(HarborEvent.REFRESH_BANNER_MESSAGE); // Add password fields this._currentConfig.email_password = new StringValueItem( fakePass, diff --git a/src/portal/src/app/base/left-side-nav/config/config.ts b/src/portal/src/app/base/left-side-nav/config/config.ts index 30babedd00d..0fedfca3007 100644 --- a/src/portal/src/app/base/left-side-nav/config/config.ts +++ b/src/portal/src/app/base/left-side-nav/config/config.ts @@ -114,6 +114,7 @@ export class Configuration { skip_audit_log_database: BoolValueItem; session_timeout: NumberValueItem; scanner_skip_update_pulltime: BoolValueItem; + banner_message: StringValueItem; public constructor() { this.auth_mode = new StringValueItem('db_auth', true); this.primary_auth_mode = new BoolValueItem(false, true); @@ -190,6 +191,10 @@ export class Configuration { this.skip_audit_log_database = new BoolValueItem(false, true); this.session_timeout = new NumberValueItem(60, true); this.scanner_skip_update_pulltime = new BoolValueItem(false, true); + this.banner_message = new StringValueItem( + JSON.stringify(new BannerMessage()), + true + ); } } @@ -208,3 +213,28 @@ export enum Triggers { SCHEDULE = 'Schedule', EVENT = 'Event', } + +export class BannerMessage { + message: string; + closable: boolean; + type: string; + fromDate: Date; + toDate: Date; + constructor() { + this.closable = false; + } +} + +export enum BannerMessageType { + SUCCESS = 'success', + INFO = 'info', + WARNING = 'warning', + ERROR = 'danger', +} + +export const BannerMessageI18nMap = { + [BannerMessageType.SUCCESS]: 'BANNER_MESSAGE.SUCCESS', + [BannerMessageType.INFO]: 'BANNER_MESSAGE.INFO', + [BannerMessageType.WARNING]: 'BANNER_MESSAGE.WARNING', + [BannerMessageType.ERROR]: 'BANNER_MESSAGE.DANGER', +}; diff --git a/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.html b/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.html index 2968cbde476..08a1e469d10 100644 --- a/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.html +++ b/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.html @@ -346,6 +346,127 @@ " /> +
+ +
+
+ +
+
+
+ + +
+
+
+ + +
+
+
+ +
+ +
+
+ +
+
+ + +
+
+ + +
+
+
diff --git a/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.scss b/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.scss index e107684c70d..08ffcc9730c 100644 --- a/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.scss +++ b/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.scss @@ -1,3 +1,6 @@ +$input-width: 12rem; + + .subtitle { font-size: 14px; font-weight: 600; @@ -148,11 +151,11 @@ } .clr-input { - width: 12rem; + width: $input-width; } .pro-creation { - width: 12rem; + width: $input-width; } input::-webkit-outer-spin-button, @@ -165,3 +168,47 @@ input::-webkit-inner-spin-button { input[type=number] { appearance: textfield; } + +.clr-textarea { + max-width: none; + width: $input-width; +} + +.flex-baseline { + display: flex; + align-items: baseline; +} + +$message-type-width: 12rem; + +.message-type { + margin-left: 2rem; + width: $message-type-width; +} + +.message-label { + margin-right: 0.25rem; +} + +.duration { + width: $input-width; + display: flex; + justify-content: right; +} + +.flex { + display: flex; +} + +:host::ng-deep clr-date-container{ + margin-top: 0; + margin-left: 0.5rem; +} + +.message-select { + width: 6.75rem; +} + +.date { + width: 6rem; +} diff --git a/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.ts b/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.ts index 8888664e90b..dc84b9ca661 100644 --- a/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.ts +++ b/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.ts @@ -1,6 +1,11 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; +import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { NgForm } from '@angular/forms'; -import { Configuration } from '../config'; +import { + BannerMessage, + BannerMessageI18nMap, + BannerMessageType, + Configuration, +} from '../config'; import { CURRENT_BASE_HREF, getChanges, @@ -10,13 +15,19 @@ import { ConfigService } from '../config.service'; import { AppConfigService } from '../../../../services/app-config.service'; import { finalize } from 'rxjs/operators'; import { MessageHandlerService } from '../../../../shared/services/message-handler.service'; +import { + EventService, + HarborEvent, +} from '../../../../services/event-service/event.service'; +import { Subscription } from 'rxjs'; @Component({ selector: 'system-settings', templateUrl: './system-settings.component.html', styleUrls: ['./system-settings.component.scss'], }) -export class SystemSettingsComponent implements OnInit { +export class SystemSettingsComponent implements OnInit, OnDestroy { + bannerMessageTypes: string[] = Object.values(BannerMessageType); onGoing = false; downloadLink: string; get currentConfig(): Configuration { @@ -26,18 +37,90 @@ export class SystemSettingsComponent implements OnInit { set currentConfig(cfg: Configuration) { this.conf.setConfig(cfg); } + + messageText: string; + messageType: string; + messageClosable: boolean; + messageFromDate: Date; + messageToDate: Date; + // the copy of bannerMessage + messageTextCopy: string; + messageTypeCopy: string; + messageClosableCopy: boolean; + messageFromDateCopy: Date; + messageToDateCopy: Date; + bannerRefreshSub: Subscription; + @ViewChild('systemConfigFrom') systemSettingsForm: NgForm; constructor( private appConfigService: AppConfigService, private errorHandler: MessageHandlerService, - private conf: ConfigService + private conf: ConfigService, + private event: EventService ) { this.downloadLink = CURRENT_BASE_HREF + '/systeminfo/getcert'; } ngOnInit() { this.conf.resetConfig(); + if (!this.bannerRefreshSub) { + this.bannerRefreshSub = this.event.subscribe( + HarborEvent.REFRESH_BANNER_MESSAGE, + () => { + this.setValueForBannerMessage(); + } + ); + } + if (this.currentConfig.banner_message) { + this.setValueForBannerMessage(); + } + } + + ngOnDestroy() { + if (this.bannerRefreshSub) { + this.bannerRefreshSub.unsubscribe(); + this.bannerRefreshSub = null; + } + } + + setValueForBannerMessage() { + if (this.currentConfig.banner_message.value) { + this.messageText = ( + JSON.parse( + this.currentConfig.banner_message.value + ) as BannerMessage + ).message; + this.messageType = ( + JSON.parse( + this.currentConfig.banner_message.value + ) as BannerMessage + ).type; + this.messageClosable = ( + JSON.parse( + this.currentConfig.banner_message.value + ) as BannerMessage + ).closable; + this.messageFromDate = ( + JSON.parse( + this.currentConfig.banner_message.value + ) as BannerMessage + ).fromDate; + this.messageToDate = ( + JSON.parse( + this.currentConfig.banner_message.value + ) as BannerMessage + ).toDate; + } else { + this.messageText = null; + this.messageType = BannerMessageType.WARNING; + this.messageClosable = false; + } + this.messageTextCopy = this.messageText; + this.messageTypeCopy = this.messageType; + this.messageClosableCopy = this.messageClosable; + this.messageFromDateCopy = this.messageFromDate; + this.messageToDateCopy = this.messageToDate; } get editable(): boolean { @@ -69,7 +152,17 @@ export class SystemSettingsComponent implements OnInit { } public hasChanges(): boolean { - return !isEmpty(this.getChanges()); + return !isEmpty(this.getChanges()) || this.hasBannerMessageChanged(); + } + + hasBannerMessageChanged() { + return ( + this.messageTextCopy != this.messageText || + this.messageTypeCopy != this.messageType || + this.messageClosableCopy != this.messageClosable || + this.messageFromDateCopy != this.messageFromDate || + this.messageToDateCopy != this.messageToDate + ); } public getChanges() { @@ -96,7 +189,8 @@ export class SystemSettingsComponent implements OnInit { prop === 'audit_log_forward_endpoint' || prop === 'skip_audit_log_database' || prop === 'session_timeout' || - prop === 'scanner_skip_update_pulltime' + prop === 'scanner_skip_update_pulltime' || + prop === 'banner_message' ) { changes[prop] = allChanges[prop]; } @@ -128,6 +222,19 @@ export class SystemSettingsComponent implements OnInit { */ public save(): void { let changes = this.getChanges(); + if (this.hasBannerMessageChanged()) { + const bm = new BannerMessage(); + bm.message = this.messageText; + bm.type = this.messageType; + bm.closable = this.messageClosable; + bm.fromDate = this.messageFromDate; + bm.toDate = this.messageToDate; + if (bm.message) { + changes['banner_message'] = JSON.stringify(bm); + } else { + changes['banner_message'] = ''; + } + } if (!isEmpty(changes)) { this.onGoing = true; this.conf @@ -184,4 +291,8 @@ export class SystemSettingsComponent implements OnInit { this.currentConfig.skip_audit_log_database.value = false; } } + + translateMessageType(type: string): string { + return BannerMessageI18nMap[type] || type; + } } diff --git a/src/portal/src/app/services/app-config.ts b/src/portal/src/app/services/app-config.ts index 2e5c1cad2e6..0235ffa1a28 100644 --- a/src/portal/src/app/services/app-config.ts +++ b/src/portal/src/app/services/app-config.ts @@ -29,6 +29,8 @@ export class AppConfig { registry_storage_provider_name: string; read_only: boolean; show_popular_repo: boolean; + banner_message: string; + current_time: string; constructor() { // Set default value @@ -49,5 +51,6 @@ export class AppConfig { this.registry_storage_provider_name = ''; this.read_only = false; this.show_popular_repo = false; + this.banner_message = ''; } } 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 e9506598fd5..c920edc5937 100644 --- a/src/portal/src/app/services/event-service/event.service.ts +++ b/src/portal/src/app/services/event-service/event.service.ts @@ -81,4 +81,5 @@ export enum HarborEvent { REFRESH_EXPORT_JOBS = 'refreshExportJobs', DELETE_ACCESSORY = 'deleteAccessory', COPY_DIGEST = 'copyDigest', + REFRESH_BANNER_MESSAGE = 'refreshBannerMessage', } diff --git a/src/portal/src/i18n/lang/de-de-lang.json b/src/portal/src/i18n/lang/de-de-lang.json index f425cc341bc..c38ae7b27a1 100644 --- a/src/portal/src/i18n/lang/de-de-lang.json +++ b/src/portal/src/i18n/lang/de-de-lang.json @@ -1860,5 +1860,17 @@ "DATE_PICKER_SELECT_MONTH_TEXT": "Select month, the current month is {CALENDAR_MONTH}", "DATE_PICKER_SELECT_YEAR_TEXT": "Select year, the current year is {CALENDAR_YEAR}", "DATE_PICKER_SELECTED_LABEL": "{FULL_DATE} - Selected" + }, + "BANNER_MESSAGE": { + "BANNER_MESSAGE": "Banner Message", + "MESSAGE_TYPE": "Message type", + "CLOSABLE": "Closable", + "FROM": "From", + "TO": "To", + "SUCCESS": "Success", + "INFO": "Info", + "WARNING": "Warning", + "DANGER": "Danger", + "ENTER_MESSAGE": "Enter your message here" } } diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json index e68f0ae1e9f..1c6e09533bf 100644 --- a/src/portal/src/i18n/lang/en-us-lang.json +++ b/src/portal/src/i18n/lang/en-us-lang.json @@ -804,7 +804,7 @@ "LABEL": "Labels", "REPOSITORY": "Repository", "REPO_READ_ONLY": "Repository Read Only", - "WEBHOOK_NOTIFICATION_ENABLED": "Webhooks enabled", + "WEBHOOK_NOTIFICATION_ENABLED": "Webhooks Enabled", "SYSTEM": "System Settings", "PROJECT_QUOTAS": "Project Quotas", "VULNERABILITY": "Vulnerability", @@ -834,7 +834,7 @@ "ROOT_CERT_LINK": "Download", "REGISTRY_CERTIFICATE": "Registry certificate", "NO_CHANGE": "Save abort because nothing changed", - "SKIP_SCANNER_PULL_TIME": "Retain image \"last pull time\" on scanning", + "SKIP_SCANNER_PULL_TIME": "Retain Image \"last pull time\" On Scanning", "TOOLTIP": { "SELF_REGISTRATION_ENABLE": "Enable sign up.", "SELF_REGISTRATION_DISABLE": "Deactivate sign up.", @@ -1861,5 +1861,17 @@ "DATE_PICKER_SELECT_MONTH_TEXT": "Select month, the current month is {CALENDAR_MONTH}", "DATE_PICKER_SELECT_YEAR_TEXT": "Select year, the current year is {CALENDAR_YEAR}", "DATE_PICKER_SELECTED_LABEL": "{FULL_DATE} - Selected" + }, + "BANNER_MESSAGE": { + "BANNER_MESSAGE": "Banner Message", + "MESSAGE_TYPE": "Message type", + "CLOSABLE": "Closable", + "FROM": "From", + "TO": "To", + "SUCCESS": "Success", + "INFO": "Info", + "WARNING": "Warning", + "DANGER": "Danger", + "ENTER_MESSAGE": "Enter your message here" } } diff --git a/src/portal/src/i18n/lang/es-es-lang.json b/src/portal/src/i18n/lang/es-es-lang.json index fdf1b50eaf4..9e40ca8a7ab 100644 --- a/src/portal/src/i18n/lang/es-es-lang.json +++ b/src/portal/src/i18n/lang/es-es-lang.json @@ -1857,5 +1857,17 @@ "DATE_PICKER_SELECT_MONTH_TEXT": "Select month, the current month is {CALENDAR_MONTH}", "DATE_PICKER_SELECT_YEAR_TEXT": "Select year, the current year is {CALENDAR_YEAR}", "DATE_PICKER_SELECTED_LABEL": "{FULL_DATE} - Selected" + }, + "BANNER_MESSAGE": { + "BANNER_MESSAGE": "Banner Message", + "MESSAGE_TYPE": "Message type", + "CLOSABLE": "Closable", + "FROM": "From", + "TO": "To", + "SUCCESS": "Success", + "INFO": "Info", + "WARNING": "Warning", + "DANGER": "Danger", + "ENTER_MESSAGE": "Enter your message here" } } diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json index 9ffa5070659..24f5feb9bce 100644 --- a/src/portal/src/i18n/lang/fr-fr-lang.json +++ b/src/portal/src/i18n/lang/fr-fr-lang.json @@ -1827,5 +1827,17 @@ "DATE_PICKER_SELECT_MONTH_TEXT": "Sélectionner le mois, le mois courant est {CALENDAR_MONTH}", "DATE_PICKER_SELECT_YEAR_TEXT": "Sélectionner l'année, l'année courante est {CALENDAR_YEAR}", "DATE_PICKER_SELECTED_LABEL": "{FULL_DATE} - Sélectionné" + }, + "BANNER_MESSAGE": { + "BANNER_MESSAGE": "Banner Message", + "MESSAGE_TYPE": "Message type", + "CLOSABLE": "Closable", + "FROM": "From", + "TO": "To", + "SUCCESS": "Success", + "INFO": "Info", + "WARNING": "Warning", + "DANGER": "Danger", + "ENTER_MESSAGE": "Enter your message here" } } diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json index aea1bdaf431..5f4ad235862 100644 --- a/src/portal/src/i18n/lang/pt-br-lang.json +++ b/src/portal/src/i18n/lang/pt-br-lang.json @@ -1857,5 +1857,17 @@ "DATE_PICKER_SELECT_MONTH_TEXT": "Select month, the current month is {CALENDAR_MONTH}", "DATE_PICKER_SELECT_YEAR_TEXT": "Select year, the current year is {CALENDAR_YEAR}", "DATE_PICKER_SELECTED_LABEL": "{FULL_DATE} - Selected" + }, + "BANNER_MESSAGE": { + "BANNER_MESSAGE": "Banner Message", + "MESSAGE_TYPE": "Message type", + "CLOSABLE": "Closable", + "FROM": "From", + "TO": "To", + "SUCCESS": "Success", + "INFO": "Info", + "WARNING": "Warning", + "DANGER": "Danger", + "ENTER_MESSAGE": "Enter your message here" } } diff --git a/src/portal/src/i18n/lang/tr-tr-lang.json b/src/portal/src/i18n/lang/tr-tr-lang.json index 07246c927aa..19277aac646 100644 --- a/src/portal/src/i18n/lang/tr-tr-lang.json +++ b/src/portal/src/i18n/lang/tr-tr-lang.json @@ -1860,5 +1860,17 @@ "DATE_PICKER_SELECT_MONTH_TEXT": "Select month, the current month is {CALENDAR_MONTH}", "DATE_PICKER_SELECT_YEAR_TEXT": "Select year, the current year is {CALENDAR_YEAR}", "DATE_PICKER_SELECTED_LABEL": "{FULL_DATE} - Selected" + }, + "BANNER_MESSAGE": { + "BANNER_MESSAGE": "Banner Message", + "MESSAGE_TYPE": "Message type", + "CLOSABLE": "Closable", + "FROM": "From", + "TO": "To", + "SUCCESS": "Success", + "INFO": "Info", + "WARNING": "Warning", + "DANGER": "Danger", + "ENTER_MESSAGE": "Enter your message here" } } diff --git a/src/portal/src/i18n/lang/zh-cn-lang.json b/src/portal/src/i18n/lang/zh-cn-lang.json index 982b7ce29ed..f828778dfc8 100644 --- a/src/portal/src/i18n/lang/zh-cn-lang.json +++ b/src/portal/src/i18n/lang/zh-cn-lang.json @@ -1857,5 +1857,17 @@ "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": "从", + "TO": "至", + "SUCCESS": "成功", + "INFO": "信息", + "WARNING": "警告", + "DANGER": "危险", + "ENTER_MESSAGE": "请输入消息内容" } } diff --git a/src/portal/src/i18n/lang/zh-tw-lang.json b/src/portal/src/i18n/lang/zh-tw-lang.json index 9c81b58e784..080d89d337b 100644 --- a/src/portal/src/i18n/lang/zh-tw-lang.json +++ b/src/portal/src/i18n/lang/zh-tw-lang.json @@ -1849,5 +1849,17 @@ "DATE_PICKER_SELECT_MONTH_TEXT": "選擇月份,目前月份為 {CALENDAR_MONTH}", "DATE_PICKER_SELECT_YEAR_TEXT": "選擇年份,目前年份為 {CALENDAR_YEAR}", "DATE_PICKER_SELECTED_LABEL": "{FULL_DATE} - 已選擇" + }, + "BANNER_MESSAGE": { + "BANNER_MESSAGE": "Banner Message", + "MESSAGE_TYPE": "Message type", + "CLOSABLE": "Closable", + "FROM": "From", + "TO": "To", + "SUCCESS": "Success", + "INFO": "Info", + "WARNING": "Warning", + "DANGER": "Danger", + "ENTER_MESSAGE": "Enter your message here" } } diff --git a/src/server/v2.0/handler/systeminfo.go b/src/server/v2.0/handler/systeminfo.go index 9fa19dfb969..c2d3435ac26 100644 --- a/src/server/v2.0/handler/systeminfo.go +++ b/src/server/v2.0/handler/systeminfo.go @@ -86,6 +86,7 @@ func (s *sysInfoAPI) convertInfo(d *si.Data) *models.GeneralInfo { PrimaryAuthMode: &d.PrimaryAuthMode, SelfRegistration: &d.SelfRegistration, HarborVersion: &d.HarborVersion, + BannerMessage: &d.BannerMessage, } if d.AuthProxySettings != nil { res.AuthproxySettings = &models.AuthproxySetting{ From 1d2624eefcdd75aec6013a85fde3383d154ed35b Mon Sep 17 00:00:00 2001 From: Shijun Sun <30999793+AllForNothing@users.noreply.github.com> Date: Tue, 4 Jul 2023 10:36:39 +0800 Subject: [PATCH 26/38] Add worker parameter for GC (#18882) 1. Related back-end PR #18855 Signed-off-by: AllForNothing --- .../clearing-job/clearing-job-interfact.ts | 2 ++ .../clearing-job/gc-page/gc/gc.component.html | 31 +++++++++++++++++++ .../clearing-job/gc-page/gc/gc.component.scss | 4 +++ .../clearing-job/gc-page/gc/gc.component.ts | 10 ++++++ 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/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 +- 12 files changed, 63 insertions(+), 8 deletions(-) diff --git a/src/portal/src/app/base/left-side-nav/clearing-job/clearing-job-interfact.ts b/src/portal/src/app/base/left-side-nav/clearing-job/clearing-job-interfact.ts index 262e4bb66db..c6de076307a 100644 --- a/src/portal/src/app/base/left-side-nav/clearing-job/clearing-job-interfact.ts +++ b/src/portal/src/app/base/left-side-nav/clearing-job/clearing-job-interfact.ts @@ -20,3 +20,5 @@ export const YES: string = 'TAG_RETENTION.YES'; export const NO: string = 'TAG_RETENTION.NO'; export const REFRESH_STATUS_TIME_DIFFERENCE: number = 5000; + +export const WORKER_OPTIONS: number[] = [1, 2, 3, 4, 5]; diff --git a/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc.component.html b/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc.component.html index 2b63b0ad366..5878f296bae 100644 --- a/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc.component.html +++ b/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc.component.html @@ -50,6 +50,37 @@ [originCron]="originCron" (inputvalue)="saveGcSchedule($event)">
+
+
+ {{ 'JOB_SERVICE_DASHBOARD.WORKERS' | translate + }} + + + {{ 'GC.WORKERS_TOOLTIP' | translate }} + + +
+
+
+ +
+
+
diff --git a/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc.component.scss b/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc.component.scss index 88f4a102b09..4d0cf4bc715 100644 --- a/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc.component.scss +++ b/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc.component.scss @@ -35,3 +35,7 @@ padding-left: .6rem; padding-right: .6rem; } + +.worker-select { + width: 4.5rem; +} diff --git a/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc.component.ts b/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc.component.ts index 13879de85d4..b48855fa26d 100644 --- a/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc.component.ts +++ b/src/portal/src/app/base/left-side-nav/clearing-job/gc-page/gc/gc.component.ts @@ -10,7 +10,9 @@ import { GcHistoryComponent } from './gc-history/gc-history.component'; import { JOB_STATUS, REFRESH_STATUS_TIME_DIFFERENCE, + WORKER_OPTIONS, } from '../../clearing-job-interfact'; +import { clone } from '../../../../../shared/units/utils'; const ONE_MINUTE = 60000; @@ -27,6 +29,8 @@ export class GcComponent implements OnInit, OnDestroy { @ViewChild(CronScheduleComponent) cronScheduleComponent: CronScheduleComponent; shouldDeleteUntagged: boolean; + workerNum: number = 1; + workerOptions: number[] = clone(WORKER_OPTIONS); dryRunOnGoing: boolean = false; lastCompletedTime: string; @@ -116,8 +120,10 @@ export class GcComponent implements OnInit, OnDestroy { this.shouldDeleteUntagged = JSON.parse( gcHistory.job_parameters ).delete_untagged; + this.workerNum = +JSON.parse(gcHistory.job_parameters).workers; } else { this.shouldDeleteUntagged = false; + this.workerNum = 1; } } @@ -132,6 +138,7 @@ export class GcComponent implements OnInit, OnDestroy { schedule: { parameters: { delete_untagged: this.shouldDeleteUntagged, + workers: +this.workerNum, dry_run: false, }, schedule: { @@ -157,6 +164,7 @@ export class GcComponent implements OnInit, OnDestroy { schedule: { parameters: { delete_untagged: this.shouldDeleteUntagged, + workers: +this.workerNum, dry_run: true, }, schedule: { @@ -188,6 +196,7 @@ export class GcComponent implements OnInit, OnDestroy { schedule: { parameters: { delete_untagged: this.shouldDeleteUntagged, + workers: +this.workerNum, dry_run: false, }, schedule: { @@ -212,6 +221,7 @@ export class GcComponent implements OnInit, OnDestroy { schedule: { parameters: { delete_untagged: this.shouldDeleteUntagged, + workers: +this.workerNum, dry_run: false, }, schedule: { diff --git a/src/portal/src/i18n/lang/de-de-lang.json b/src/portal/src/i18n/lang/de-de-lang.json index c38ae7b27a1..a55ee0b90cb 100644 --- a/src/portal/src/i18n/lang/de-de-lang.json +++ b/src/portal/src/i18n/lang/de-de-lang.json @@ -1236,7 +1236,8 @@ "DELETE_BLOB_AND_MANIFEST": "{{blob}} blob(s) and {{manifest}} manifest(s) deleted", "DELETE_BLOB": "{{blob}} blob(s) deleted", "DELETE_MANIFEST": "{{manifest}} manifest(s) deleted", - "FREE_UP_SIZE": "{{size}} space freed up" + "FREE_UP_SIZE": "{{size}} space freed up", + "WORKERS_TOOLTIP": "Set the number of workers that can execute GC tasks in parallel, the default value is 1." }, "RETAG": { "MSG_SUCCESS": "Artefakt erfolgreich kopiert", diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json index 1c6e09533bf..2aa6a5217e2 100644 --- a/src/portal/src/i18n/lang/en-us-lang.json +++ b/src/portal/src/i18n/lang/en-us-lang.json @@ -1237,7 +1237,8 @@ "DELETE_BLOB_AND_MANIFEST": "{{blob}} blob(s) and {{manifest}} manifest(s) deleted", "DELETE_BLOB": "{{blob}} blob(s) deleted", "DELETE_MANIFEST": "{{manifest}} manifest(s) deleted", - "FREE_UP_SIZE": "{{size}} space freed up" + "FREE_UP_SIZE": "{{size}} space freed up", + "WORKERS_TOOLTIP": "Set the number of workers that can execute GC tasks in parallel, the default value is 1." }, "RETAG": { "MSG_SUCCESS": "Copy artifact successfully", diff --git a/src/portal/src/i18n/lang/es-es-lang.json b/src/portal/src/i18n/lang/es-es-lang.json index 9e40ca8a7ab..22fb70c0f43 100644 --- a/src/portal/src/i18n/lang/es-es-lang.json +++ b/src/portal/src/i18n/lang/es-es-lang.json @@ -1233,7 +1233,8 @@ "DELETE_BLOB_AND_MANIFEST": "{{blob}} blob(s) and {{manifest}} manifest(s) deleted", "DELETE_BLOB": "{{blob}} blob(s) deleted", "DELETE_MANIFEST": "{{manifest}} manifest(s) deleted", - "FREE_UP_SIZE": "{{size}} space freed up" + "FREE_UP_SIZE": "{{size}} space freed up", + "WORKERS_TOOLTIP": "Set the number of workers that can execute GC tasks in parallel, the default value is 1." }, "RETAG": { "MSG_SUCCESS": "Copy artifact successfully", diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json index 24f5feb9bce..6e02fc0f705 100644 --- a/src/portal/src/i18n/lang/fr-fr-lang.json +++ b/src/portal/src/i18n/lang/fr-fr-lang.json @@ -1203,7 +1203,8 @@ "DELETE_BLOB_AND_MANIFEST": "{{blob}} blob(s) and {{manifest}} manifest(s) deleted", "DELETE_BLOB": "{{blob}} blob(s) deleted", "DELETE_MANIFEST": "{{manifest}} manifest(s) deleted", - "FREE_UP_SIZE": "{{size}} space freed up" + "FREE_UP_SIZE": "{{size}} space freed up", + "WORKERS_TOOLTIP": "Set the number of workers that can execute GC tasks in parallel, the default value is 1." }, "RETAG": { "MSG_SUCCESS": "Artefact copié", diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json index 5f4ad235862..997bcaa381a 100644 --- a/src/portal/src/i18n/lang/pt-br-lang.json +++ b/src/portal/src/i18n/lang/pt-br-lang.json @@ -1233,7 +1233,8 @@ "DELETE_BLOB_AND_MANIFEST": "{{blob}} blob(s) and {{manifest}} manifest(s) deleted", "DELETE_BLOB": "{{blob}} blob(s) deleted", "DELETE_MANIFEST": "{{manifest}} manifest(s) deleted", - "FREE_UP_SIZE": "{{size}} space freed up" + "FREE_UP_SIZE": "{{size}} space freed up", + "WORKERS_TOOLTIP": "Set the number of workers that can execute GC tasks in parallel, the default value is 1." }, "RETAG": { "MSG_SUCCESS": "Artefato copiado com sucesso", diff --git a/src/portal/src/i18n/lang/tr-tr-lang.json b/src/portal/src/i18n/lang/tr-tr-lang.json index 19277aac646..cd5f5681388 100644 --- a/src/portal/src/i18n/lang/tr-tr-lang.json +++ b/src/portal/src/i18n/lang/tr-tr-lang.json @@ -1236,7 +1236,8 @@ "DELETE_BLOB_AND_MANIFEST": "{{blob}} blob(s) and {{manifest}} manifest(s) deleted", "DELETE_BLOB": "{{blob}} blob(s) deleted", "DELETE_MANIFEST": "{{manifest}} manifest(s) deleted", - "FREE_UP_SIZE": "{{size}} space freed up" + "FREE_UP_SIZE": "{{size}} space freed up", + "WORKERS_TOOLTIP": "Set the number of workers that can execute GC tasks in parallel, the default value is 1." }, "RETAG": { "MSG_SUCCESS": "Copy artifact successfully", diff --git a/src/portal/src/i18n/lang/zh-cn-lang.json b/src/portal/src/i18n/lang/zh-cn-lang.json index f828778dfc8..15b97d35a04 100644 --- a/src/portal/src/i18n/lang/zh-cn-lang.json +++ b/src/portal/src/i18n/lang/zh-cn-lang.json @@ -1233,7 +1233,8 @@ "DELETE_BLOB_AND_MANIFEST": "{{blob}}个 blob(s) 和 {{manifest}}个 manifest(s) 已删除", "DELETE_BLOB": "{{blob}}个 blob(s) 已删除", "DELETE_MANIFEST": "{{manifest}}个 manifest(s) 已删除", - "FREE_UP_SIZE": "{{size}}的空间已清理" + "FREE_UP_SIZE": "{{size}}的空间已清理", + "WORKERS_TOOLTIP": "设置可并行执行垃圾回收任务的工作者数量,默认值为1。" }, "RETAG": { "MSG_SUCCESS": "Artifact 拷贝成功", diff --git a/src/portal/src/i18n/lang/zh-tw-lang.json b/src/portal/src/i18n/lang/zh-tw-lang.json index 080d89d337b..0a3af0cd62c 100644 --- a/src/portal/src/i18n/lang/zh-tw-lang.json +++ b/src/portal/src/i18n/lang/zh-tw-lang.json @@ -1225,7 +1225,8 @@ "DELETE_BLOB_AND_MANIFEST": "{{blob}} blob(s) and {{manifest}} manifest(s) deleted", "DELETE_BLOB": "{{blob}} blob(s) deleted", "DELETE_MANIFEST": "{{manifest}} manifest(s) deleted", - "FREE_UP_SIZE": "{{size}} space freed up" + "FREE_UP_SIZE": "{{size}} space freed up", + "WORKERS_TOOLTIP": "Set the number of workers that can execute GC tasks in parallel, the default value is 1." }, "RETAG": { "MSG_SUCCESS": "Artifact 複製成功", From dbaae9e64ec4be76ce38ebe47918b9500bddc0db Mon Sep 17 00:00:00 2001 From: Wang Yan Date: Wed, 5 Jul 2023 11:25:19 +0800 Subject: [PATCH 27/38] support OCI-Subject header (#18885) fixes #18865 the response header OCI-Subject to indicate to the client that the registry processed the request's subject. Signed-off-by: wang yan Co-authored-by: System Administrator --- src/server/middleware/subject/subject.go | 1 + src/server/middleware/subject/subject_test.go | 1 + 2 files changed, 2 insertions(+) diff --git a/src/server/middleware/subject/subject.go b/src/server/middleware/subject/subject.go index c3b464325aa..cc7dac86297 100644 --- a/src/server/middleware/subject/subject.go +++ b/src/server/middleware/subject/subject.go @@ -129,6 +129,7 @@ func Middleware() func(http.Handler) http.Handler { return err } } + w.Header().Set("OCI-Subject", subjectArt.Digest) } return nil diff --git a/src/server/middleware/subject/subject_test.go b/src/server/middleware/subject/subject_test.go index 29b66bd86db..aa39fb6c145 100644 --- a/src/server/middleware/subject/subject_test.go +++ b/src/server/middleware/subject/subject_test.go @@ -159,6 +159,7 @@ func (suite *MiddlewareTestSuite) TestSubject() { suite.Equal(name, accs[0].GetData().SubArtifactRepo) suite.True(accs[0].IsHard()) suite.Equal(accessorymodel.TypeSubject, accs[0].GetData().Type) + suite.Equal(subArtDigest, res.Header().Values("OCI-Subject")[0]) }) } From 1efe944a8d1bc76b41037fc2c4f49512b45a4e06 Mon Sep 17 00:00:00 2001 From: Shijun Sun <30999793+AllForNothing@users.noreply.github.com> Date: Wed, 5 Jul 2023 14:31:35 +0800 Subject: [PATCH 28/38] Correct the hidden property for clrDgHideableColumn (#18890) 1.Fixes #18870 Signed-off-by: AllForNothing --- .../artifact-list-tab.component.html | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) 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 c30969817c6..deef6fdbe31 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 @@ -149,48 +149,47 @@ - + {{ 'REPOSITORY.PLATFORM' | translate }} - + {{ 'REPOSITORY.TAGS' | translate }} - + {{ 'ACCESSORY.CO_SIGNED' | translate }} - + {{ 'REPOSITORY.SIZE' | translate }} - + {{ 'REPOSITORY.VULNERABILITY' | translate }} - + {{ 'ARTIFACT.ANNOTATION' | translate }} - + {{ 'REPOSITORY.LABELS' | translate }} - + {{ 'REPOSITORY.PUSH_TIME' | translate }} - + {{ 'REPOSITORY.PULL_TIME' | translate }} From c8120d57123f3eb0ec87866b66ab0747197463ac Mon Sep 17 00:00:00 2001 From: Bin Liu Date: Wed, 5 Jul 2023 17:57:06 +0800 Subject: [PATCH 29/38] API: update ScannerRegistration.properties.url format (#18799) The format of ScannerRegistration.properties.url should be `uri` but not `url`. Fixes: #18798 Signed-off-by: bin liu Co-authored-by: Wang Yan --- api/v2.0/swagger.yaml | 2 +- src/server/v2.0/handler/model/scanner.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index 8748592f97b..46d97702840 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -8182,7 +8182,7 @@ definitions: x-omitempty: false url: type: string - format: url + format: uri description: A base URL of the scanner adapter example: http://harbor-scanner-trivy:8080 disabled: diff --git a/src/server/v2.0/handler/model/scanner.go b/src/server/v2.0/handler/model/scanner.go index 6c783f504b8..9218c4c3acd 100644 --- a/src/server/v2.0/handler/model/scanner.go +++ b/src/server/v2.0/handler/model/scanner.go @@ -38,7 +38,7 @@ func (s *ScannerRegistration) ToSwagger(ctx context.Context) *models.ScannerRegi return &models.ScannerRegistration{ UUID: s.UUID, Name: s.Name, - URL: s.URL, + URL: strfmt.URI(s.URL), Description: s.Description, Auth: s.Auth, AccessCredential: s.AccessCredential, From fb52fdb3e010b5d34891cf4be75ed2cb9b9a7e12 Mon Sep 17 00:00:00 2001 From: Yang Jiao <72076317+YangJiao0817@users.noreply.github.com> Date: Thu, 6 Jul 2023 13:47:00 +0800 Subject: [PATCH 30/38] Refactor the keyword in the testcase (#18898) Fix #17952 Signed-off-by: Yang Jiao --- tests/apitests/python/test_proxy_cache.py | 2 +- .../Harbor-Pages/Project-Config.robot | 4 - .../Harbor-Pages/Project-Members.robot | 24 --- .../Harbor-Pages/Project-P2P-Preheat.robot | 3 +- .../Harbor-Pages/Project-Repository.robot | 5 +- .../Harbor-Pages/Project-Tag-Retention.robot | 3 - .../Harbor-Pages/Project-Webhooks.robot | 2 - tests/resources/Harbor-Pages/Project.robot | 73 ++++----- .../resources/Harbor-Pages/Replication.robot | 28 +--- tests/resources/Harbor-Pages/ToolKit.robot | 1 - tests/resources/Harbor-Pages/Verify.robot | 7 +- .../Harbor-Pages/Vulnerability.robot | 8 +- tests/resources/TestCaseBody.robot | 40 ++--- tests/resources/Util.robot | 3 +- tests/robot-cases/Group0-Util/bundle.json | 2 +- tests/robot-cases/Group1-Nightly/Common.robot | 151 +++++------------ .../Group1-Nightly/Common_GC.robot | 13 +- .../Group1-Nightly/Replication.robot | 155 ++++++++---------- .../robot-cases/Group1-Nightly/Routing.robot | 2 +- .../robot-cases/Group1-Nightly/Schedule.robot | 25 ++- tests/robot-cases/Group1-Nightly/Trivy.robot | 7 +- .../Group1-Nightly/multi_scanners.robot | 8 +- 22 files changed, 198 insertions(+), 368 deletions(-) diff --git a/tests/apitests/python/test_proxy_cache.py b/tests/apitests/python/test_proxy_cache.py index d5beff18b3e..8e1a3a0c953 100644 --- a/tests/apitests/python/test_proxy_cache.py +++ b/tests/apitests/python/test_proxy_cache.py @@ -58,7 +58,7 @@ def do_validate(self, registry_type): image_for_docker = dict(image = "for_proxy", tag = "1.0") image_for_ctr = dict(image = "redis", tag = "latest") - index_for_docker = dict(image = "index081597864867", tag = "index_tag081597864867") + index_for_docker = dict(image = "index", tag = "index_tag") access_key = "" access_secret = "" diff --git a/tests/resources/Harbor-Pages/Project-Config.robot b/tests/resources/Harbor-Pages/Project-Config.robot index bacbd711630..03d6c6eda41 100644 --- a/tests/resources/Harbor-Pages/Project-Config.robot +++ b/tests/resources/Harbor-Pages/Project-Config.robot @@ -7,11 +7,8 @@ Resource ../../resources/Util.robot *** Keywords *** Goto Project Config - Sleep 3 Retry Element Click //project-detail//ul/li[contains(.,'Summary')] - Sleep 3 Retry Double Keywords When Error Retry Element Click //project-detail//ul/li[contains(.,'Configuration')] Retry Wait Element //clr-checkbox-wrapper/label[contains(.,'Prevent vulnerable images from running.')] - Sleep 2 Click Project Public Mouse Down //hbr-project-policy-config//input[@name='public'] @@ -36,7 +33,6 @@ Click Auto Scan Mouse Up //hbr-project-policy-config//input[@name='scan-image-on-push'] Save Project Config - Sleep 1 Retry Element Click //hbr-project-policy-config//button[contains(.,'SAVE')] Public Should Be Selected diff --git a/tests/resources/Harbor-Pages/Project-Members.robot b/tests/resources/Harbor-Pages/Project-Members.robot index 53f09fa5580..bd877846129 100644 --- a/tests/resources/Harbor-Pages/Project-Members.robot +++ b/tests/resources/Harbor-Pages/Project-Members.robot @@ -19,28 +19,6 @@ Resource ../../resources/Util.robot *** Variables *** *** Keywords *** -Go Into Project - [Arguments] ${project} ${has_image}=${true} - Retry Keyword N Times When Error 5 Clear Search Input And Go Into Project ${project} ${has_image} - -Clear Search Input And Go Into Project - [Arguments] ${project} ${has_image} - # Close prompt in header - Sleep 2 - #go To ${url} - Reload Page - Sleep 2 - # Clear Search Input - Retry Element Click xpath=//harbor-app/harbor-shell/clr-main-container/navigator/clr-header/div[1]/a/span - Sleep 2 - Retry Text Input ${search_input} ${project} - Sleep 2 - # Go Into Project - ${out} Run Keyword If ${has_image}==${false} Run Keywords Retry Element Click xpath=//*[@id='project-results']//clr-dg-cell[contains(.,'${project}')]/a AND Wait Until Element Is Visible And Enabled xpath=//clr-dg-placeholder[contains(.,\"We couldn\'t find any repositories!\")] - ... ELSE Run Keywords Retry Element Click xpath=//*[@id='project-results']//clr-dg-cell[contains(.,'${project}')]/a AND Wait Until Element Is Visible And Enabled xpath=//project-detail//hbr-repository-gridview//clr-dg-cell[contains(.,'${project}/')] - Sleep 1 - Capture Page Screenshot - Add User To Project Admin [Arguments] ${project} ${user} # *** this keyword has not been used *** @@ -50,7 +28,6 @@ Add User To Project Admin Retry Text Input xpath=${project_member_add_username_xpath} ${user} Retry Element Click xpath=${project_member_add_admin_xpath} Retry Element Click xpath=${project_member_add_save_button_xpath} - Sleep 4 Search Project Member [Arguments] ${project} ${user} @@ -112,7 +89,6 @@ Delete Project Member Retry Double Keywords When Error Retry Element Click ${member_action_xpath} Retry Wait Until Page Contains Element ${delete_action_xpath} Retry Double Keywords When Error Retry Element Click ${delete_action_xpath} Retry Wait Until Page Contains Element ${repo_delete_on_card_view_btn} Retry Double Keywords When Error Retry Element Click ${repo_delete_on_card_view_btn} Retry Wait Element xpath=${project_member_xpath} - Sleep 1 User Should Be Owner Of Project [Arguments] ${user} ${pwd} ${project} diff --git a/tests/resources/Harbor-Pages/Project-P2P-Preheat.robot b/tests/resources/Harbor-Pages/Project-P2P-Preheat.robot index f72c0d98d1c..2467bc2f261 100644 --- a/tests/resources/Harbor-Pages/Project-P2P-Preheat.robot +++ b/tests/resources/Harbor-Pages/Project-P2P-Preheat.robot @@ -150,8 +150,7 @@ Verify Artifact Is Labeled Event Go Into Project ${project_name} Switch To Project Label Create New Labels ${label} - Switch To Project Repo - Go Into Repo ${image} + Go Into Repo ${project_name} ${image} Add Labels To Tag ${tag} ${label} Back Project Home ${project_name} Switch To P2P Preheat diff --git a/tests/resources/Harbor-Pages/Project-Repository.robot b/tests/resources/Harbor-Pages/Project-Repository.robot index 152257ec083..9d89350e822 100644 --- a/tests/resources/Harbor-Pages/Project-Repository.robot +++ b/tests/resources/Harbor-Pages/Project-Repository.robot @@ -32,8 +32,7 @@ View Scan Error Log Scan Artifact [Arguments] ${project} ${repo} ${label_xpath}=//clr-dg-row//label[1] - Go Into Project ${project} - Go Into Repo ${project}/${repo} + Go Into Repo ${project} ${repo} Retry Element Click ${label_xpath} Retry Element Click ${scan_artifact_btn} @@ -46,4 +45,4 @@ Check Scan Artifact Job Status Is Stopped Should Be Equal As Strings '${job_status}' 'Scan stopped' Refresh Repositories - Retry Element Click ${refresh_repositories_xpath} \ No newline at end of file + Retry Element Click ${refresh_repositories_xpath} diff --git a/tests/resources/Harbor-Pages/Project-Tag-Retention.robot b/tests/resources/Harbor-Pages/Project-Tag-Retention.robot index bd602d67de6..f2b3192695b 100644 --- a/tests/resources/Harbor-Pages/Project-Tag-Retention.robot +++ b/tests/resources/Harbor-Pages/Project-Tag-Retention.robot @@ -33,7 +33,6 @@ Retry Add A Tag Immutability Rule Add A Tag Immutability Rule [Arguments] ${scope} ${tag} Reload Page - Sleep 3 Retry Double Keywords When Error Retry Element Click xpath=${project_tag_retention_add_rule_xpath} Retry Wait Until Page Contains Element xpath=${project_tag_immutability_save_add_button_xpath} Retry Clear Element Text ${project_tag_immutability_scope_input_xpath} Retry Text Input ${project_tag_immutability_scope_input_xpath} ${scope} @@ -97,9 +96,7 @@ Execute Result Should Be FOR ${idx} IN RANGE 0 20 ${out} Run Keyword And Ignore Error Retry Wait Until Page Contains Element //app-tag-retention-tasks//clr-datagrid//clr-dg-row[contains(., '${image}') and contains(., '${result}')] Exit For Loop If '${out[0]}'=='PASS' - Sleep 1 Retry Element Click ${project_tag_retention_refresh_xpath} - Sleep 5 Retry Wait Until Page Contains Element xpath=${project_tag_retention_record_yes_xpath} Retry Element Click ${project_tag_retention_list_expand_icon_xpath} END diff --git a/tests/resources/Harbor-Pages/Project-Webhooks.robot b/tests/resources/Harbor-Pages/Project-Webhooks.robot index 5a8673615c3..9e75e14ab89 100644 --- a/tests/resources/Harbor-Pages/Project-Webhooks.robot +++ b/tests/resources/Harbor-Pages/Project-Webhooks.robot @@ -6,9 +6,7 @@ Resource ../../resources/Util.robot *** Keywords *** Switch To Project Webhooks - #Switch To Project Tab Overflow Retry Element Click xpath=//project-detail//a[contains(.,'Webhooks')] - Sleep 1 Create A New Webhook [Arguments] ${webhook_name} ${webhook_endpoint_url} ${event_type}=@{EMPTY} diff --git a/tests/resources/Harbor-Pages/Project.robot b/tests/resources/Harbor-Pages/Project.robot index ca72c6b60f4..5922cc0d347 100644 --- a/tests/resources/Harbor-Pages/Project.robot +++ b/tests/resources/Harbor-Pages/Project.robot @@ -26,7 +26,6 @@ Create An New Project And Go Into Project ${out} Run Keyword And Ignore Error Retry Button Click xpath=${create_project_button_xpath} Log All Return value is ${out[0]} Exit For Loop If '${out[0]}'=='PASS' - Sleep 1 END Log To Console Project Name: ${projectname} Retry Text Input xpath=${project_name_xpath} ${projectname} @@ -36,7 +35,6 @@ Create An New Project And Go Into Project Run Keyword If '${storage_quota}'!='${null}' Input Storage Quota ${storage_quota} ${storage_quota_unit} Run Keyword If '${proxy_cache}' == '${true}' Run Keywords Retry Element Click ${project_proxy_cache_switcher_xpath} AND Retry Element Click ${project_registry_select_id} AND Retry Element Click xpath=//select[@id='registry']//option[contains(.,'${registry}')] Retry Double Keywords When Error Retry Element Click ${create_project_OK_button_xpath} Retry Wait Until Page Not Contains Element ${create_project_OK_button_xpath} - Sleep 2 Go Into Project ${projectname} has_image=${false} Create An New Project With New User @@ -45,44 +43,35 @@ Create An New Project With New User Logout Harbor Sign In Harbor ${url} ${username} ${newPassword} Create An New Project And Go Into Project ${projectname} ${public} - Sleep 1 Artifact Exist [Arguments] ${tag_name} Retry Wait Until Page Contains Element //artifact-list-tab//clr-datagrid//clr-dg-row[contains(.,'sha256') and contains(.,'${tag_name}')] -#It's the log of project. + Go To Project Log #Switch To Project Tab Overflow Retry Element Click xpath=${project_log_xpath} - Sleep 2 Switch To Member - Sleep 3 Retry Element Click xpath=${project_member_xpath} - Sleep 1 Switch To Replication Retry Element Click xpath=${project_replication_xpath} - Sleep 1 Switch To Project Configuration Retry Element Click ${project_config_tabsheet} - Sleep 1 Switch To Tag Retention #Switch To Project Tab Overflow Retry Element Click xpath=${project_tag_strategy_xpath} - Sleep 1 Switch To Tag Immutability #Switch To Project Tab Overflow Retry Double Keywords When Error Retry Element Click xpath=${project_tag_strategy_xpath} Retry Wait Until Page Contains Element ${project_tag_immutability_switch} Retry Double Keywords When Error Retry Element Click xpath=${project_tag_immutability_switch} Retry Wait Until Page Contains Immutability rules - Sleep 1 Switch To Project Tab Overflow Retry Element Click xpath=${project_tab_overflow_btn} - Sleep 1 Navigate To Projects Retry Element Click xpath=${projects_xpath} @@ -98,7 +87,6 @@ Project Should Not Display Search Private Projects Retry Element Click xpath=//select Retry Element Click xpath=//select/option[@value=1] - Sleep 1 Make Project Private [Arguments] ${projectname} @@ -153,7 +141,6 @@ Delete Repo on CardView Retry Element Click //hbr-gridview//span[contains(.,'${reponame}')]//clr-dropdown/button Retry Element Click //hbr-gridview//span[contains(.,'${reponame}')]//clr-dropdown/clr-dropdown-menu/button[contains(.,'Delete')] Retry Element Click ${repo_delete_on_card_view_btn} - Sleep 2 Delete Project [Arguments] ${projectname} @@ -162,7 +149,6 @@ Delete Project Retry Element Click ${project_action_xpath} Retry Element Click xpath=//*[@id='delete-project'] Retry Element Click //clr-modal//button[contains(.,'DELETE')] - Sleep 1 Project Should Not Be Deleted [Arguments] ${projname} @@ -202,9 +188,7 @@ Do Log Advanced Search Retry Element Click xpath=//audit-log//clr-dropdown//a[contains(.,'Others')] Retry Element Click xpath=//audit-log//hbr-filter//clr-icon Retry Text Input xpath=//audit-log//hbr-filter//input harbor-jobservice - Sleep 1 - ${rc} = Get Element Count //audit-log//clr-dg-row - Should Be Equal As Integers ${rc} 1 + Retry Wait Until Page Not Contains Element //audit-log//clr-dg-row[2] Retry Click Repo Name [Arguments] ${repo_name_element} @@ -220,25 +204,44 @@ Retry Click Repo Name END Should Be Equal As Strings '${out[0]}' 'PASS' -Go Into Repo - [Arguments] ${repoName} - Sleep 2 - Retry Wait Until Page Not Contains Element ${repo_list_spinner} - ${repo_name_element}= Set Variable xpath=//clr-dg-cell[contains(.,'${repoName}')]/a - FOR ${n} IN RANGE 1 3 - Retry Element Click ${repo_search_icon} - Retry Clear Element Text ${repo_search_input} - Retry Text Input ${repo_search_input} ${repoName} - ${out} Run Keyword And Ignore Error Retry Wait Until Page Contains Element ${repo_name_element} +Go Into Project + [Arguments] ${project} ${has_image}=${true} + FOR ${n} IN RANGE 1 4 + ${out} Run Keyword And Ignore Error Retry Go Into Project ${project} ${has_image} + Run Keyword If '${out[0]}'=='PASS' Exit For Loop + Reload Page Sleep 2 - Run Keyword If '${out[0]}'=='FAIL' Reload Page - Continue For Loop If '${out[0]}'=='FAIL' - Retry Click Repo Name ${repo_name_element} + END + Run Keyword If '${out[0]}'=='FAIL' Capture Page Screenshot + Should Be Equal As Strings '${out[0]}' 'PASS' + +Retry Go Into Project + [Arguments] ${project} ${has_image} + Retry Text Input ${search_input} ${project} + Wait Until Page Contains Element //list-project-ro//a[contains(., '${project}')] + Retry Link Click //list-project-ro//a[contains(., '${project}')] + Wait Until Page Contains Element //project-detail//h1[contains(., '${project}')] + Run Keyword If ${has_image}==${false} Wait Until Element Is Visible And Enabled //clr-dg-placeholder[contains(.,\"We couldn\'t find any repositories!\")] + ... ELSE Wait Until Element Is Visible And Enabled //project-detail//hbr-repository-gridview//clr-dg-cell[contains(.,'${project}/')] + +Go Into Repo + [Arguments] ${project_name} ${repo_name} + FOR ${n} IN RANGE 1 4 + ${out} Run Keyword And Ignore Error Retry Go Into Repo ${project_name} ${repo_name} + Run Keyword If '${out[0]}'=='PASS' Exit For Loop + Reload Page Sleep 2 - Exit For Loop END + Run Keyword If '${out[0]}'=='FAIL' Capture Page Screenshot Should Be Equal As Strings '${out[0]}' 'PASS' +Retry Go Into Repo + [Arguments] ${project_name} ${repo_name} + Retry Text Input ${search_input} ${project_name}/${repo_name} + Wait Until Page Contains Element //list-repository-ro//a[contains(., '${project_name}/${repo_name}')] + Retry Link Click //list-repository-ro//a[contains(., '${project_name}/${repo_name}')] + Wait Until Page Contains Element //artifact-list-page//h2[contains(., '${repo_name}')] + Click Index Achieve [Arguments] ${tag_name} Retry Element Click //artifact-list-tab//clr-datagrid//clr-dg-row[contains(.,'sha256') and contains(.,'${tag_name}')]//clr-dg-cell[1]//clr-tooltip//a @@ -261,12 +264,10 @@ Go Into Index And Contain Artifacts Switch To CardView Retry Element Click xpath=//hbr-repository-gridview//span[@class='card-btn']/clr-icon - Sleep 5 Expand Repo [Arguments] ${projectname} Retry Element Click //repository//clr-dg-row[contains(.,'${projectname}')]//button/clr-icon - Sleep 1 Edit Repo Info Retry Element Click //*[@id='repo-info'] @@ -285,11 +286,9 @@ Edit Repo Info Switch To Project Label Retry Element Click xpath=//project-detail//a[contains(.,'Labels')] - Sleep 1 Switch To Project Repo Retry Element Click xpath=//project-detail//a[contains(.,'Repositories')] - Sleep 1 Add Labels To Tag [Arguments] ${tagName} ${labelName} @@ -312,14 +311,12 @@ Filter Labels In Tags Retry Element Click xpath=//*[@id='search-btn'] Retry Element Click xpath=//*[@id='filterArea']//div//button[contains(.,'${labelName2}')] Retry Element Click xpath=//app-artifact-filter//clr-icon[contains(@shape,'search')] - Sleep 2 Retry Wait Until Page Contains Element xpath=//clr-dg-row[contains(.,'${labelName2}')] Retry Wait Until Page Not Contains Element xpath=//clr-dg-row[contains(.,'${labelName1}')] Get Statics [Arguments] ${locator} Reload Page - Sleep 5 ${privaterepo}= Get Text ${locator} [Return] ${privaterepo} diff --git a/tests/resources/Harbor-Pages/Replication.robot b/tests/resources/Harbor-Pages/Replication.robot index e57b0b2116c..580c7bc6deb 100644 --- a/tests/resources/Harbor-Pages/Replication.robot +++ b/tests/resources/Harbor-Pages/Replication.robot @@ -31,8 +31,6 @@ Filter Replication Rule Filter Registry [Arguments] ${registry_name} ${registry_name_element}= Set Variable xpath=//clr-dg-cell[contains(.,'${registry_name}')] - Switch To Replication Manage - Switch To Registries Retry Element Click ${filter_registry_btn} Retry Text Input ${filter_registry_input} ${registry_name} Retry Wait Until Page Contains Element ${registry_name_element} @@ -138,7 +136,6 @@ Create A Rule With Existing Endpoint Run Keyword If '${bandwidth_unit}' != 'Kbps' Select Bandwidth Unit ${bandwidth_unit} #click save Retry Double Keywords When Error Retry Element Click ${rule_save_button} Retry Wait Until Page Not Contains Element ${rule_save_button} - Sleep 2 Endpoint Is Unpingable Retry Element Click ${ping_test_button} @@ -151,20 +148,16 @@ Endpoint Is Pingable Disable Certificate Verification Checkbox Should Be Selected ${destination_insecure_checkbox} Retry Element Click ${destination_insecure_xpath} - Sleep 1 Enable Certificate Verification Checkbox Should Not Be Selected ${destination_insecure_checkbox} Retry Element Click ${destination_insecure_xpath} - Sleep 1 Switch To Registries Retry Element Click ${nav_to_registries} - Sleep 1 Switch To Replication Manage Retry Element Click ${nav_to_replications} - Sleep 1 Trigger Replication Manual [Arguments] ${rule} @@ -176,9 +169,7 @@ Trigger Replication Manual #change from click to mouse down and up Mouse Down ${dialog_replicate} Mouse Up ${dialog_replicate} - Sleep 2 Retry Wait Until Page Contains Element //*[@id='contentAll']//div[contains(.,'${rule}')]/../div/clr-icon[@shape='success-standard'] - Sleep 1 Rename Rule [Arguments] ${rule} ${newname} @@ -192,7 +183,7 @@ Rename Rule Select Rule [Arguments] ${rule} - Retry Double Keywords When Error Retry Element Click //clr-dg-row[contains(.,'${rule}')]/div/div[1]/div Retry Wait Element ${replication_rule_exec_id} + Retry Double Keywords When Error Retry Element Click //clr-dg-row[contains(.,'${rule}')]/div/div[1]/div Element Should Be Enabled ${replication_rule_exec_id} Stop Jobs Retry Element Click ${stop_jobs_button} @@ -249,10 +240,8 @@ Rename Endpoint Delete Endpoint [Arguments] ${name} - Retry Element Click ${endpoint_filter_search} - Retry Text Input ${endpoint_filter_input} ${name} - #click checkbox before target endpoint - Retry Double Keywords When Error Retry Element Click //clr-dg-row[contains(.,'${name}')]//div[contains(@class,'clr-checkbox-wrapper')] Retry Wait Element ${registry_del_btn} + Filter Registry ${name} + Retry Double Keywords When Error Retry Element Click //clr-dg-row[contains(.,'${name}')]//div[contains(@class,'clr-checkbox-wrapper')] Checkbox Should Be Selected //clr-dg-row[contains(.,'${name}')]//input Retry Element Click ${registry_del_btn} Wait Until Page Contains Element ${dialog_delete} Retry Element Click ${dialog_delete} @@ -264,21 +253,20 @@ Select Rule And Replicate Retry Double Keywords When Error Retry Element Click xpath=${dialog_replicate} Retry Wait Until Page Not Contains Element xpath=${dialog_replicate} Image Should Be Replicated To Project - [Arguments] ${project} ${image} ${period}=60 ${times}=20 ${tag}=${EMPTY} ${expected_image_size_in_regexp}=${null} ${total_artifact_count}=${null} ${archive_count}=${null} + [Arguments] ${project} ${image} ${times}=6 ${tag}=${EMPTY} ${expected_image_size_in_regexp}=${null} ${total_artifact_count}=${null} ${archive_count}=${null} FOR ${n} IN RANGE 0 ${times} - Sleep ${period} - Go Into Project ${project} + Go Into Project ${project} Switch To Project Repo ${out} Run Keyword And Ignore Error Retry Wait Until Page Contains ${project}/${image} Log To Console Return value is ${out[0]} Continue For Loop If '${out[0]}'=='FAIL' - Go Into Repo ${project}/${image} - ${size}= Run Keyword If '${tag}'!='${EMPTY}' and '${expected_image_size_in_regexp}'!='${null}' Get Text //clr-dg-row[contains(., '${tag}')]//clr-dg-cell[5]/div + Go Into Repo ${project} ${image} + ${size}= Run Keyword If '${tag}'!='${EMPTY}' and '${expected_image_size_in_regexp}'!='${null}' Get Text //clr-dg-row[contains(., '${tag}')]//clr-dg-cell[4]/div Run Keyword If '${tag}'!='${EMPTY}' and '${expected_image_size_in_regexp}'!='${null}' Should Match Regexp '${size}' '${expected_image_size_in_regexp}' ${index_out} Go Into Index And Contain Artifacts ${tag} total_artifact_count=${total_artifact_count} archive_count=${archive_count} return_immediately=${true} Log All index_out: ${index_out} Run Keyword If '${index_out}'=='PASS' Exit For Loop - Sleep 30 + Sleep 10 END Verify Artifacts Counts In Archive diff --git a/tests/resources/Harbor-Pages/ToolKit.robot b/tests/resources/Harbor-Pages/ToolKit.robot index 99af28d0951..3b29e9343d9 100644 --- a/tests/resources/Harbor-Pages/ToolKit.robot +++ b/tests/resources/Harbor-Pages/ToolKit.robot @@ -24,7 +24,6 @@ Delete Success FOR ${obj} IN @{obj} Retry Wait Until Page Contains Element //*[@id='contentAll']//div[contains(.,'${obj}')]/../div/clr-icon[@shape='success-standard'] END - Sleep 1 Delete Fail [Arguments] @{obj} diff --git a/tests/resources/Harbor-Pages/Verify.robot b/tests/resources/Harbor-Pages/Verify.robot index e9a48bafd94..190e75383bc 100644 --- a/tests/resources/Harbor-Pages/Verify.robot +++ b/tests/resources/Harbor-Pages/Verify.robot @@ -463,8 +463,7 @@ Verify Artifact Index FOR ${project} IN @{project} ${name}= Get Value From Json ${json} $.projects[?(@.name=${project})].artifact_index.name ${tag}= Get Value From Json ${json} $.projects[?(@.name=${project})].artifact_index.tag - Go Into Project ${project} has_image=${true} - Go Into Repo ${project}/${name}[0] + Go Into Repo ${project} ${name}[0] Go Into Index And Contain Artifacts ${tag}[0] total_artifact_count=2 Pull image ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} ${project} ${name}[0]:${tag}[0] Navigate To Projects @@ -474,9 +473,7 @@ Verify Artifact Index Loop Repo [Arguments] ${project} @{repos} FOR ${repo} IN @{repos} - Navigate To Projects - Go Into Project ${project} has_image=${true} - Go Into Repo ${project}/${repo}[0][cache_image_namespace]/${repo}[0][cache_image] + Go Into Repo ${project} ${repo}[0][cache_image_namespace]/${repo}[0][cache_image] Pull image ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} ${project} ${repo}[0][cache_image_namespace]/${repo}[0][cache_image]:${repo}[0][tag] END diff --git a/tests/resources/Harbor-Pages/Vulnerability.robot b/tests/resources/Harbor-Pages/Vulnerability.robot index 0afe032c575..4fc3741abfa 100644 --- a/tests/resources/Harbor-Pages/Vulnerability.robot +++ b/tests/resources/Harbor-Pages/Vulnerability.robot @@ -28,7 +28,6 @@ Switch To Vulnerability Page Retry Wait Element ${scan_now_button} Set Vulnerabilty Serverity -#0 is critical, 1 is high, 2 is medium, 3 is low, 4 is negligible. [Arguments] ${level} Goto Project Config #enable first @@ -36,7 +35,6 @@ Set Vulnerabilty Serverity Checkbox Should Be Selected //project-detail//clr-checkbox-wrapper//input[@name='prevent-vulenrability-image-input'] Retry Element Click //project-detail//select #wait for dropdown popup - Sleep 1 Select From List By Index //project-detail//select ${level} Retry Element Click ${project_config_save_btn} @@ -44,9 +42,7 @@ Scan Is Disabled Retry Wait Until Page Contains Element //button[@id='scan-btn' and @disabled=''] Scan Repo -#use fail for image can not scan, otherwise use success [Arguments] ${tagname} ${status} - #select one tag Retry Element Click //clr-dg-row[contains(.,'${tagname}')]//label Retry Element Click //button[@id='scan-btn'] Run Keyword If '${status}' == 'Succeed' Wait Until Element Is Visible //hbr-vulnerability-bar//hbr-result-tip-histogram 300 @@ -62,7 +58,6 @@ Enable Scan On Push Retry Element Click //clr-checkbox-wrapper[@id='scan-image-on-push-wrapper']//label Checkbox Should Be Selected //clr-checkbox-wrapper[@id='scan-image-on-push-wrapper']//input Retry Element Click ${project_config_save_btn} - Sleep 10 Vulnerability Not Ready Project Hint Sleep 2 @@ -89,8 +84,7 @@ Set Default Scanner Check Listed In CVE Allowlist [Arguments] ${project_name} ${image} ${tag} ${cve_id} ${is_in}=Yes - Go Into Project ${project_name} - Go Into Repo ${project_name}/${image} + Go Into Repo ${project_name} ${image} Go Into Artifact ${tag} Scroll Element Into View //clr-dg-row[contains(.,'${cve_id}')] diff --git a/tests/resources/TestCaseBody.robot b/tests/resources/TestCaseBody.robot index e2677f10fb4..61c77389612 100644 --- a/tests/resources/TestCaseBody.robot +++ b/tests/resources/TestCaseBody.robot @@ -62,8 +62,7 @@ Body Of Scan A Tag In The Repo Sign In Harbor ${HARBOR_URL} user023 Test1@34 Create An New Project And Go Into Project project${d} Push Image ${ip} user023 Test1@34 project${d} ${image_argument}:${tag_argument} - Go Into Project project${d} - Go Into Repo project${d}/${image_argument} + Go Into Repo project${d} ${image_argument} Scan Repo ${tag_argument} Succeed Scan Result Should Display In List Row ${tag_argument} is_no_vulerabilty=${is_no_vulerabilty} Pull Image ${ip} user023 Test1@34 project${d} ${image_argument} ${tag_argument} @@ -76,8 +75,7 @@ Body Of Scan Image With Empty Vul ${tag}= Set Variable ${tag_argument} Push Image ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} library ${image_argument}:${tag_argument} Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} - Go Into Project library - Go Into Repo ${image_argument} + Go Into Repo library ${image_argument} Scan Repo ${tag} Succeed Scan Result Should Display In List Row ${tag} is_no_vulerabilty=${true} Close Browser @@ -90,9 +88,7 @@ Body Of Manual Scan All Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} Switch To Vulnerability Page Trigger Scan Now And Wait Until The Result Appears - Navigate To Projects - Go Into Project library - Go Into Repo redis + Go Into Repo library redis Scan Result Should Display In List Row ${sha256} View Repo Scan Details @{vulnerability_levels} Close Browser @@ -105,8 +101,7 @@ Body Of View Scan Results Sign In Harbor ${HARBOR_URL} user025 Test1@34 Create An New Project And Go Into Project project${d} Push Image ${ip} user025 Test1@34 project${d} tomcat - Go Into Project project${d} - Go Into Repo project${d}/tomcat + Go Into Repo project${d} tomcat Scan Repo latest Succeed Scan Result Should Display In List Row latest View Repo Scan Details @{vulnerability_levels} @@ -121,9 +116,7 @@ Body Of Scan Image On Push Goto Project Config Enable Scan On Push Push Image ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} project${d} memcached - Navigate To Projects - Go Into Project project${d} - Go Into Repo memcached + Go Into Repo project${d} memcached Scan Result Should Display In List Row latest View Repo Scan Details @{vulnerability_levels} Close Browser @@ -193,8 +186,7 @@ Body Of Verfiy System Level CVE Allowlist Go Into Project project${d} Set Vulnerabilty Serverity 2 Cannot Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} err_msg=cannot be pulled due to configured policy - Go Into Project project${d} - Go Into Repo project${d}/${image} + Go Into Repo project${d} ${image} Scan Repo ${sha256} Succeed Logout Harbor @@ -229,8 +221,7 @@ Body Of Verfiy Project Level CVE Allowlist Go Into Project project${d} Set Vulnerabilty Serverity 2 Cannot Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} - Go Into Project project${d} - Go Into Repo project${d}/${image} + Go Into Repo project${d} ${image} Scan Repo ${sha256} Succeed Go Into Project project${d} Add Items to Project CVE Allowlist ${most_cve_list} @@ -259,8 +250,7 @@ Body Of Verfiy Project Level CVE Allowlist By Quick Way of Add System Push Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} sha256=${sha256} Go Into Project project${d} Set Vulnerabilty Serverity 2 - Go Into Project project${d} - Go Into Repo project${d}/${image} + Go Into Repo project${d} ${image} Scan Repo ${sha256} Succeed Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} Go Into Project project${d} @@ -313,6 +303,7 @@ Body Of Replication Of Pull Images from Registry To Self Switch To Replication Manage Create A Rule With Existing Endpoint rule${d} pull ${src_project_name} all e${d} ${_des_pro_name} flattening=${flattening} Select Rule And Replicate rule${d} + Check Latest Replication Job Status Succeeded Run Keyword If '${verify_verbose}'=='Y' Verify Artifact Display Verbose ${_des_pro_name} @{target_images} ... ELSE Verify Artifact Display ${_des_pro_name} @{target_images} Close Browser @@ -329,7 +320,7 @@ Verify Artifact Display Verbose ${total_artifact_count}= Get From Dictionary ${item} total_artifact_count ${archive_count}= Get From Dictionary ${item} archive_count Log To Console Check image ${image}:${tag} replication to Project ${pro_name} - Image Should Be Replicated To Project ${pro_name} ${image} tag=${tag} total_artifact_count=${total_artifact_count} archive_count=${archive_count} times=6 + Image Should Be Replicated To Project ${pro_name} ${image} tag=${tag} total_artifact_count=${total_artifact_count} archive_count=${archive_count} END Verify Artifact Display @@ -340,7 +331,7 @@ Verify Artifact Display ${item}= Get Substring ${item} 1 -1 ${item}= Evaluate ${item} ${image}= Get From Dictionary ${item} image - Image Should Be Replicated To Project ${pro_name} ${image} times=6 + Image Should Be Replicated To Project ${pro_name} ${image} END Replication With Flattening @@ -375,7 +366,6 @@ Replication With Flattening Check Harbor Api Page Retry Link Click //a[contains(.,'Harbor API V2.0')] - Sleep 3 Switch Window locator=NEW Title Should Be Harbor Swagger Retry Wait Element xpath=//h2[contains(.,"Harbor API")] @@ -434,8 +424,7 @@ Verify Webhook By Artifact Deleted Event Switch Window ${webhook_handle} Delete All Requests Switch Window ${harbor_handle} - Go Into Project ${project_name} - Go Into Repo ${project_name}/${image} + Go Into Repo ${project_name} ${image} @{tag_list} Create List ${tag} Multi-delete Artifact @{tag_list} Switch Window ${webhook_handle} @@ -447,8 +436,7 @@ Verify Webhook By Scanning Finished Event Switch Window ${webhook_handle} Delete All Requests Switch Window ${harbor_handle} - Go Into Project ${project_name} - Go Into Repo ${project_name}/${image} + Go Into Repo ${project_name} ${image} Scan Repo ${tag} Succeed Switch Window ${webhook_handle} &{scanning_finished_property}= Create Dictionary type=SCANNING_COMPLETED scan_status=Success namespace=${project_name} tag=${tag} name=${image} @@ -487,7 +475,7 @@ Verify Webhook By Replication Status Changed Event Switch Window ${harbor_handle} Switch To Replication Manage Select Rule And Replicate ${replication_rule_name} - Retry Wait Until Page Contains Succeeded + Check Latest Replication Job Status Succeeded Switch Window ${webhook_handle} &{replication_finished_property}= Create Dictionary type=REPLICATION operator=MANUAL registry_type=harbor harbor_hostname=${ip} Verify Request &{replication_finished_property} diff --git a/tests/resources/Util.robot b/tests/resources/Util.robot index 0be8293dc82..cc916519751 100644 --- a/tests/resources/Util.robot +++ b/tests/resources/Util.robot @@ -237,7 +237,6 @@ Wait Unitl Command Success Log Trying ${cmd}: ${n} ... console=True ${rc} ${output}= Run And Return Rc And Output ${cmd} Exit For Loop If '${rc}'=='0' - Sleep 2 END Log Command Result is ${output} Should Be Equal As Strings '${rc}' '0' @@ -256,7 +255,7 @@ Retry Keyword N Times When Error ${out} Run Keyword And Ignore Error ${keyword} @{elements} Run Keyword If '${keyword}'=='Make Swagger Client' Exit For Loop If '${out[0]}'=='PASS' and '${out[1]}'=='0' ... ELSE Exit For Loop If '${out[0]}'=='PASS' - Sleep 10 + Sleep 5 END Run Keyword If '${out[0]}'=='FAIL' Capture Page Screenshot Should Be Equal As Strings '${out[0]}' 'PASS' diff --git a/tests/robot-cases/Group0-Util/bundle.json b/tests/robot-cases/Group0-Util/bundle.json index aeba663fcb4..83a27359011 100644 --- a/tests/robot-cases/Group0-Util/bundle.json +++ b/tests/robot-cases/Group0-Util/bundle.json @@ -1 +1 @@ -{"actions":{"io.cnab.status":{}},"definitions":{"port":{"default":"8080","type":"string"},"text":{"default":"Hello, World!","type":"string"}},"description":"Hello, World!","images":{"hello":{"description":"hello","image":"registry/namespace/image1","imageType":"docker","mediaType":"application/vnd.docker.distribution.manifest.v2+json","size":528}},"invocationImages":[{"image":"registry/namespace/image2","imageType":"docker","mediaType":"application/vnd.docker.distribution.manifest.v2+json","size":941}],"maintainers":[{"email":"user@email.com","name":"user"}],"name":"hello-world","parameters":{"fields":{"definition":"","destination":null}},"schemaVersion":"v1.0.0","version":"0.1.0"} \ No newline at end of file +{"actions":{"io.cnab.status":{}},"definitions":{"port":{"default":"8080","type":"string"},"text":{"default":"Hello, World!","type":"string"}},"description":"Hello, World!","images":{"hello":{"description":"hello","image":"registry/namespace/image1","imageType":"docker","mediaType":"application/vnd.docker.distribution.manifest.v2+json","size":528}},"invocationImages":[{"image":"registry/namespace/image2","imageType":"docker","mediaType":"application/vnd.docker.distribution.manifest.v2+json","size":941}],"maintainers":[{"email":"user@email.com","name":"user"}],"name":"hello-world","parameters":{"fields":{"definition":"","destination":null}},"schemaVersion":"v1.0.0","version":"0.1.0"} diff --git a/tests/robot-cases/Group1-Nightly/Common.robot b/tests/robot-cases/Group1-Nightly/Common.robot index 7c6f8b2d128..7346860ccda 100644 --- a/tests/robot-cases/Group1-Nightly/Common.robot +++ b/tests/robot-cases/Group1-Nightly/Common.robot @@ -36,43 +36,15 @@ Test Case - Push ORAS and Display [Tags] push_oras Init Chrome Driver ${d}= Get Current Date result_format=%m%s - Sign In Harbor ${HARBOR_URL} user010 Test1@34 Create An New Project And Go Into Project test${d} - ${repo_name}= Set Variable hello-oras-artifact ${tag}= Set Variable 1.0.0 Retry Keyword N Times When Error 5 Oras Push ${ip} user010 Test1@34 test${d} ${repo_name} ${tag} - - Go Into Project test${d} - Wait Until Page Contains test${d}/${repo_name} - - Go Into Repo test${d}/${repo_name} + Go Into Repo test${d} ${repo_name} Wait Until Page Contains ${tag} Close Browser -## TODO: uncomment it once #14470 fixed -# Test Case - Push SIF and Display -# [Tags] push_sif -# Init Chrome Driver -# ${d}= Get Current Date result_format=%m%s -# ${user}= Set Variable user010 -# ${pwd}= Set Variable Test1@34 - -# Sign In Harbor ${HARBOR_URL} ${user} ${pwd} -# Create An New Project And Go Into Project test${d} - -# ${repo_name}= Set Variable busybox -# ${tag}= Set Variable 1.28 -# Retry Keyword N Times When Error 5 Push Singularity To Harbor library: library/default/ ${ip} ${user} ${pwd} test${d} ${repo_name} ${tag} - -# Go Into Project test${d} -# Wait Until Page Contains test${d}/${repo_name} - -# Go Into Repo test${d}/${repo_name} -# Wait Until Page Contains ${tag} -# Close Browser - Test Case - Push CNAB Bundle and Display [Tags] push_cnab Init Chrome Driver @@ -91,12 +63,11 @@ Test Case - Push CNAB Bundle and Display Go Into Project test${d} Wait Until Page Contains test${d}/cnab${d} - - Go Into Repo test${d}/cnab${d} + Go Into Repo test${d} cnab${d} Wait Until Page Contains cnab_tag${d} Go Into Project test${d} Wait Until Page Contains test${d}/cnab${d} - Go Into Repo test${d}/cnab${d} + Go Into Repo test${d} cnab${d} Go Into Index And Contain Artifacts cnab_tag${d} total_artifact_count=3 archive_count=2 Close Browser @@ -118,8 +89,7 @@ Test Case - Repo Size ${d}= Get Current Date result_format=%m%s Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} Push Image With Tag ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} library alpine 2.6 2.6 - Go Into Project library - Go Into Repo alpine + Go Into Repo library alpine Wait Until Page Contains 1.92MiB Close Browser @@ -229,7 +199,6 @@ Test Case - Update Label Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} Switch To System Labels Create New Labels label_${d} - Sleep 3 ${d1}= Get Current Date Update A Label label_${d} Close Browser @@ -240,7 +209,6 @@ Test Case - Delete Label Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} Switch To System Labels Create New Labels label_${d} - Sleep 3 Delete A Label label_${d} Close Browser @@ -286,8 +254,6 @@ Test Case - User View Logs Delete Repo project${d} ${replication_image} Delete Repo project${d} ${img} - Sleep 3 - Go To Project Log Advanced Search Should Display @@ -326,7 +292,6 @@ Test Case - Edit Project Creation Project Creation Should Display Logout Harbor - Sleep 3 Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} Set Pro Create Admin Only Logout Harbor @@ -346,8 +311,7 @@ Test Case - Edit Repo Info Sign In Harbor ${HARBOR_URL} user011 Test1@34 Create An New Project And Go Into Project project${d} Push Image ${ip} user011 Test1@34 project${d} hello-world - Go Into Project project${d} - Go Into Repo project${d}/hello-world + Go Into Repo project${d} hello-world Edit Repo Info Close Browser @@ -382,7 +346,6 @@ Test Case - Delete Multi Repo Create An New Project And Go Into Project project${d} Push Image ${ip} user013 Test1@34 project${d} hello-world Push Image ${ip} user013 Test1@34 project${d} busybox - Sleep 2 Go Into Project project${d} @{repo_list} Create List hello-world busybox Multi-delete Object ${repo_delete_btn} @{repo_list} @@ -397,8 +360,7 @@ Test Case - Delete Multi Artifacts Create An New Project And Go Into Project project${d} Push Image With Tag ${ip} user014 Test1@34 project${d} redis 3.2.10-alpine 3.2.10-alpine Push Image With Tag ${ip} user014 Test1@34 project${d} redis 4.0.7-alpine 4.0.7-alpine - Go Into Project project${d} - Go Into Repo redis + Go Into Repo project${d} redis @{tag_list} Create List 3.2.10-alpine 4.0.7-alpine Multi-delete Artifact @{tag_list} # Verify @@ -436,13 +398,10 @@ Test Case - Project Admin Operate Labels ${d}= Get Current Date result_format=%m%s Sign In Harbor ${HARBOR_URL} user019 Test1@34 Create An New Project And Go Into Project project${d} - Sleep 2 # Add labels Switch To Project Label Create New Labels label_${d} - Sleep 2 Update A Label label_${d} - Sleep 2 Delete A Label label_${d} Close Browser @@ -454,14 +413,11 @@ Test Case - Project Admin Add Labels To Repo Push Image With Tag ${ip} user020 Test1@34 project${d} redis 3.2.10-alpine 3.2.10-alpine Push Image With Tag ${ip} user020 Test1@34 project${d} redis 4.0.7-alpine 4.0.7-alpine Go Into Project project${d} - Sleep 2 # Add labels Switch To Project Label Create New Labels label111 Create New Labels label22 - Sleep 2 - Switch To Project Repo - Go Into Repo project${d}/redis + Go Into Repo project${d} redis Add Labels To Tag 3.2.10-alpine label111 Add Labels To Tag 4.0.7-alpine label22 Filter Labels In Tags label111 label22 @@ -480,7 +436,6 @@ Test Case - Developer Operate Labels Sign In Harbor ${HARBOR_URL} user022 Test1@34 Go Into Project project${d} has_image=${false} - Sleep 3 Retry Wait Until Page Not Contains Element xpath=//a[contains(.,'Labels')] Close Browser @@ -493,17 +448,13 @@ Test Case - Copy A Image Create An New Project And Go Into Project project${random_num1}${random_num2} Create An New Project And Go Into Project project${random_num1} - Sleep 1 Push Image With Tag ${ip} user028 Test1@34 project${random_num1} redis ${image_tag} - Sleep 1 - Go Into Repo project${random_num1}/redis + Go Into Repo project${random_num1} redis Copy Image ${image_tag} project${random_num1}${random_num2} ${target_image_name} Navigate To Projects Go Into Project project${random_num1}${random_num2} - Sleep 1 Page Should Contain ${target_image_name} - Go Into Repo project${random_num1}${random_num2}/${target_image_name} - Sleep 1 + Go Into Repo project${random_num1}${random_num2} ${target_image_name} Retry Wait Until Page Contains Element xpath=${tag_value_xpath} Close Browser @@ -527,12 +478,14 @@ Test Case - Copy A Image And Accessory Docker Login ${ip} ${user} ${pwd} Cosign Sign ${ip}/${source_project}/${image}:${tag} Docker Logout ${ip} - Retry Double Keywords When Error Go Into Repo ${source_project}/${image} Should Be Signed By Cosign ${tag} + Go Into Repo ${source_project} ${image} + Should Be Signed By Cosign ${tag} Copy Image ${tag} ${target_project} ${image} Retry Double Keywords When Error Go Into Project ${target_project} Retry Wait Until Page Contains ${image} - Retry Double Keywords When Error Go Into Repo ${target_project}/${image} Retry Wait Until Page Contains Element //clr-dg-row[contains(.,${tag})] + Go Into Repo ${target_project} ${image} + Retry Wait Until Page Contains Element //clr-dg-row[contains(.,${tag})] Should Be Signed By Cosign ${tag} Close Browser @@ -590,15 +543,11 @@ Test Case - Project Quotas Control Under Copy Create An New Project And Go Into Project project_b_${d} storage_quota=${storage_quota} storage_quota_unit=${storage_quota_unit} Push Image With Tag ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} project_a_${d} ${image_a} tag=${image_a_ver} tag1=${image_a_ver} Push Image With Tag ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} project_a_${d} ${image_b} tag=${image_b_ver} tag1=${image_b_ver} - Go Into Project project_a_${d} - Go Into Repo project_a_${d}/${image_a} + Go Into Repo project_a_${d} ${image_a} Copy Image ${image_a_ver} project_b_${d} ${image_a} - Go Into Project project_a_${d} - Go Into Repo project_a_${d}/${image_b} + Go Into Repo project_a_${d} ${image_b} Copy Image ${image_b_ver} project_b_${d} ${image_b} is_success=${false} - Sleep 2 Go Into Project project_b_${d} - Sleep 2 Retry Wait Until Page Contains Element xpath=//clr-dg-cell[contains(.,'${image_a}')]/a Retry Wait Until Page Not Contains Element xpath=//clr-dg-cell[contains(.,'${image_b}')]/a Close Browser @@ -609,8 +558,7 @@ Test Case - Tag CRUD ${d}= Get Current Date result_format=%m%s Create An New Project And Go Into Project project${d} Push Image With Tag ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} project${d} hello-world latest - Switch To Project Repo - Go Into Repo hello-world + Go Into Repo project${d} hello-world Go Into Artifact latest Should Contain Tag latest # add more than one tag @@ -696,15 +644,12 @@ Test Case - Push Docker Manifest Index and Display Docker Push Index ${ip} user010 Test1@34 ${ip}/test${d}/index${d}:index_tag${d} ${ip}/test${d}/${image_a}:${image_a_ver} ${ip}/test${d}/${image_b}:${image_b_ver} - Go Into Project test${d} - Wait Until Page Contains test${d}/index${d} - - Go Into Repo test${d}/index${d} + Go Into Repo test${d} index${d} Wait Until Page Contains index_tag${d} Go Into Project test${d} Wait Until Page Contains test${d}/index${d} - Go Into Repo test${d}/index${d} + Go Into Repo test${d} index${d} Go Into Index And Contain Artifacts index_tag${d} total_artifact_count=2 Close Browser @@ -717,19 +662,15 @@ Test Case - Can Not Copy Image In ReadOnly Mode Create An New Project And Go Into Project project${random_num1}${random_num2} Create An New Project And Go Into Project project${random_num1} - Sleep 1 Push Image With Tag ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} project${random_num1} redis ${image_tag} - Sleep 1 Enable Read Only - Go Into Repo project${random_num1}/redis + Go Into Repo project${random_num1} redis Copy Image ${image_tag} project${random_num1}${random_num2} ${target_image_name} is_success=${false} Retry Wait Element Not Visible ${repo_retag_confirm_dlg} Navigate To Projects Go Into Project project${random_num1}${random_num2} has_image=${false} - Sleep 10 Go Into Project project${random_num1}${random_num2} has_image=${false} Disable Read Only - Sleep 10 Close Browser Test Case - Read Only Mode @@ -742,7 +683,6 @@ Test Case - Read Only Mode Cannot Push image ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} project${d} busybox:latest Disable Read Only - Sleep 5 Push image ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} project${d} busybox:latest Close Browser @@ -831,7 +771,8 @@ Test Case - Cosign And Cosign Deployment Security Policy Push Image With Tag ${ip} ${user} ${pwd} project${d} ${image} ${tag} Go Into Project project${d} - Retry Double Keywords When Error Go Into Repo project${d}/${image} Should Not Be Signed By Cosign ${tag} + Go Into Repo project${d} ${image} + Should Not Be Signed By Cosign ${tag} Cannot Pull Image ${ip} ${user} ${pwd} project${d} ${image}:${tag} err_msg=The image is not signed in Cosign. Cosign Generate Key Pair Cosign Verify ${ip}/project${d}/${image}:${tag} ${false} @@ -863,8 +804,7 @@ Test Case - Audit Log And Purge Push Image With Tag ${ip} ${user} ${pwd} project${d} ${image} ${tag1} ${tag1} Clean All Local Images Verify Log ${user} project${d}/${image}:${tag1} artifact create - Go Into Project project${d} - Go Into Repo ${image} + Go Into Repo project${d} ${image} Go Into Artifact ${tag1} # create tag Add A New Tag ${tag2} @@ -878,8 +818,7 @@ Test Case - Audit Log And Purge Docker Pull ${ip}/project${d}/${image}:${tag1} Docker Logout ${ip} Verify Log ${user} project${d}/${image}:${sha256} artifact pull - Go Into Project project${d} - Go Into Repo project${d}/${image} + Go Into Repo project${d} ${image} # delete artifact @{tag_list} Create List ${tag1} Multi-delete Artifact @{tag_list} @@ -928,8 +867,7 @@ Test Case - Audit Log Forward Retry Action Keyword Verify Log In Syslog Service ${HARBOR_ADMIN} project${d}/${image}:${tag1} artifact create # Enable Skip Audit Log Database Enable Skip Audit Log Database - Go Into Project project${d} - Go Into Repo ${image} + Go Into Repo project${d} ${image} Go Into Artifact ${tag1} # create tag Add A New Tag ${tag2} @@ -939,8 +877,7 @@ Test Case - Audit Log Forward Set Audit Log Forward ${null} Configuration has been successfully saved. Retry Wait Element Should Be Disabled ${skip_audit_log_database_checkbox} Checkbox Should Not Be Selected ${skip_audit_log_database_checkbox} - Go Into Project project${d} - Go Into Repo ${image} + Go Into Repo project${d} ${image} Go Into Artifact ${tag1} # delete tag Delete A Tag ${tag2} @@ -979,17 +916,15 @@ Test Case - Export CVE # scan images Refresh Repositories FOR ${image} IN @{images.keys()} - Go Into Repo ${image} + Go Into Repo project${d} ${image} Scan Repo ${images['${image}']} Succeed - Back Project Home project${d} END + Back Project Home project${d} Switch To Project Label Create New Labels ${labels}[1] - Switch To Project Repo - Go Into Repo nginx + Go Into Repo project${d} nginx Add Labels To Tag ${images['nginx']} ${labels}[0] - Back Project Home project${d} - Go Into Repo redis + Go Into Repo project${d} redis Add Labels To Tag ${images['redis']} ${labels}[1] Navigate To Projects Should Not Be Export CVEs @@ -1032,8 +967,7 @@ Test Case - Job Service Dashboard Job Queues ${retention_execution1}= Execute Dry Run photon 0/0 ${retention_execution2}= Execute Run photon 0/0 # Triggers three IMAGE_SCAN jobs - Switch To Project Repo - Go Into Repo photon + Go Into Repo project${d} photon Retry Element Click //clr-datagrid//label[contains(.,'Select All')] Retry Button Click ${scan_artifact_btn} # Triggers a GARBAGE_COLLECTION job @@ -1069,8 +1003,7 @@ Test Case - Job Service Dashboard Job Queues Switch to Log Rotation Purge Now 1 Days Running # Triggers three IMAGE_SCAN jobs - Go Into Project project${d} - Go Into Repo photon + Go Into Repo project${d} photon Retry Element Click //clr-datagrid//label[contains(.,'Select All')] Retry Button Click ${scan_artifact_btn} # Check job queues @@ -1117,15 +1050,15 @@ Test Case - Job Service Dashboard Workers Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} Create An New Project And Go Into Project ${project_name} Switch to Registries - Create A New Endpoint harbor ${endpoint_name} https://${LOCAL_REGISTRY} ${null} ${null} + Create A New Endpoint harbor ${endpoint_name} https://cicd.harbor.vmwarecna.net ${null} ${null} Switch To Replication Manage - Create A Rule With Existing Endpoint ${rule_name} pull ${LOCAL_REGISTRY_NAMESPACE}/test_replication image ${endpoint_name} ${project_name} bandwidth=50 bandwidth_unit=Mbps + Create A Rule With Existing Endpoint ${rule_name} pull nightly/test_replication image ${endpoint_name} ${project_name} bandwidth=50 bandwidth_unit=Mbps Select Rule And Replicate ${rule_name} - Retry Wait Until Page Contains Running + Check Latest Replication Job Status InProgress Switch To Job Workers Retry Wait Until Page Contains Element //clr-datagrid[.//button[text()='Worker ID']]//clr-dg-row//clr-dg-cell[text()='REPLICATION'] Retry Wait Until Page Contains Element //app-donut-chart//div[text()=' 1/10 '] - Check Worker Log REPLICATION copying ${LOCAL_REGISTRY_NAMESPACE}/test_replication + Check Worker Log REPLICATION copying nightly/test_replication Switch To Replication Manage Select Rule ${rule_name} Retry Action Keyword Check Latest Replication Job Status Succeeded @@ -1146,22 +1079,20 @@ Test Case - Retain Image Last Pull Time Push Image With Tag ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} ${project_name} ${image} ${tag} ${tag} Switch To Configuration System Setting Set Up Retain Image Last Pull Time enable - Go Into Project ${project_name} - Go Into Repo ${project_name}/${image} + Go Into Repo ${project_name} ${image} Scan Repo ${tag} Succeed Sleep 15 Reload Page - Retry Wait Element Visible //clr-dg-row//clr-dg-cell[10] - ${last_pull_time}= Get Text //clr-dg-row//clr-dg-cell[10] + Retry Wait Element Visible //clr-dg-row//clr-dg-cell[9] + ${last_pull_time}= Get Text //clr-dg-row//clr-dg-cell[9] Should Be Empty ${last_pull_time} Switch To Configuration System Setting Set Up Retain Image Last Pull Time disable - Go Into Project ${project_name} - Go Into Repo ${project_name}/${image} + Go Into Repo ${project_name} ${image} Scan Repo ${tag} Succeed Sleep 15 Reload Page - Retry Wait Element Visible //clr-dg-row//clr-dg-cell[10] - ${last_pull_time}= Get Text //clr-dg-row//clr-dg-cell[10] + Retry Wait Element Visible //clr-dg-row//clr-dg-cell[9] + ${last_pull_time}= Get Text //clr-dg-row//clr-dg-cell[9] Should Not Be Empty ${last_pull_time} Close Browser diff --git a/tests/robot-cases/Group1-Nightly/Common_GC.robot b/tests/robot-cases/Group1-Nightly/Common_GC.robot index b6224fdc128..140f46db236 100644 --- a/tests/robot-cases/Group1-Nightly/Common_GC.robot +++ b/tests/robot-cases/Group1-Nightly/Common_GC.robot @@ -45,8 +45,7 @@ Test Case - GC Untagged Images Create An New Project And Go Into Project project${d} Push Image With Tag ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} project${d} hello-world latest # make hello-world untagged - Go Into Project project${d} - Go Into Repo hello-world + Go Into Repo project${d} hello-world Go Into Artifact latest Should Contain Tag latest Delete A Tag latest @@ -55,18 +54,14 @@ Test Case - GC Untagged Images GC Now ${latest_job_id}= Get Text ${latest_job_id_xpath} Retry GC Should Be Successful ${latest_job_id} ${null} - Go Into Project project${d} - Switch To Project Repo - Go Into Repo hello-world + Go Into Repo project${d} hello-world Should Contain Artifact # run gc with param delete untagged artifacts checked, should delete hello-world Switch To Garbage Collection GC Now untag=${true} ${latest_job_id}= Get Text ${latest_job_id_xpath} Retry GC Should Be Successful ${latest_job_id} ${null} - Go Into Project project${d} - Switch To Project Repo - Go Into Repo hello-world + Go Into Repo project${d} hello-world Should Not Contain Any Artifact Close Browser @@ -93,4 +88,4 @@ Test Case - Project Quotas Control Under GC Sleep 1 END Should Be Equal As Strings '${out2[0]}' 'PASS' - Close Browser \ No newline at end of file + Close Browser diff --git a/tests/robot-cases/Group1-Nightly/Replication.robot b/tests/robot-cases/Group1-Nightly/Replication.robot index 5b086aabb83..d1b4c7c8271 100644 --- a/tests/robot-cases/Group1-Nightly/Replication.robot +++ b/tests/robot-cases/Group1-Nightly/Replication.robot @@ -33,7 +33,6 @@ ${REMOTE_SERVER_API_ENDPOINT} ${REMOTE_SERVER_URL}/api *** Test Cases *** Test Case - Get Harbor Version -#Just get harbor version and log it Get Harbor Version Test Case - Pro Replication Rules Add @@ -45,7 +44,6 @@ Test Case - Pro Replication Rules Add Close Browser Test Case - Harbor Endpoint Verification - #This case need vailid info and selfsign cert Init Chrome Driver ${d}= Get Current Date result_format=%m%s Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} @@ -57,12 +55,11 @@ Test Case - Harbor Endpoint Verification Close Browser Test Case - Harbor Endpoint Add - #This case need vailid info and selfsign cert Init Chrome Driver - ${d}= Get Current Date result_format=%m%s - Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} + ${d}= Get Current Date result_format=%m%s + Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} Switch To Registries - Create A New Endpoint harbor testabc https://${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} Y + Create A New Endpoint harbor testabc https://${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} Y Close Browser Test Case - Harbor Endpoint Edit @@ -171,7 +168,7 @@ Test Case - Replication Of Push Images from Self To Harbor Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} Switch To Replication Manage Select Rule And Replicate rule${d} - Sleep 20 + Check Latest Replication Job Status Succeeded Logout Harbor Sign In Harbor https://${ip1} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} Image Should Be Replicated To Project project_dest${d} hello-world @@ -191,8 +188,7 @@ Test Case - Replication Exclusion Mode And Set Bandwidth # push mode Switch To System Labels Create New Labels bad_${d} - Go Into Project project${d} - Go Into Repo project${d}/busybox + Go Into Repo project${d} busybox Add Labels To Tag latest bad_${d} Switch To Registries Create A New Endpoint harbor e${d} https://${ip1} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} @@ -207,10 +203,10 @@ Test Case - Replication Exclusion Mode And Set Bandwidth Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} Switch To Replication Manage Select Rule And Replicate rule${d} - Retry Wait Until Page Contains Succeeded + Check Latest Replication Job Status Succeeded Logout Harbor Sign In Harbor https://${ip1} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} - Image Should Be Replicated To Project project_dest${d} hello-world period=0 + Image Should Be Replicated To Project project_dest${d} hello-world # make sure the excluded image is not replication Retry Wait Until Page Contains 1 - 1 of 1 items @@ -223,8 +219,8 @@ Test Case - Replication Exclusion Mode And Set Bandwidth Switch To Replication Manage Create A Rule With Existing Endpoint rule${d} pull project${d}/* image e${d} project${d} filter_tag=3.10 filter_tag_model=excluding filter_label=bad_${d} filter_label_model=excluding bandwidth=2 bandwidth_unit=Mbps Select Rule And Replicate rule${d} - Retry Wait Until Page Contains Succeeded - Image Should Be Replicated To Project project${d} hello-world period=0 + Check Latest Replication Job Status Succeeded + Image Should Be Replicated To Project project${d} hello-world # make sure the excluded image is not replication Retry Wait Until Page Contains 1 - 1 of 1 items Close Browser @@ -264,6 +260,7 @@ Test Case - Replication Of Pull Images from AWS-ECR To Self Switch To Replication Manage Create A Rule With Existing Endpoint rule${d} pull a/* image e${d} project${d} Select Rule And Replicate rule${d} + Check Latest Replication Job Status Succeeded Image Should Be Replicated To Project project${d} httpd Image Should Be Replicated To Project project${d} alpine Image Should Be Replicated To Project project${d} hello-world @@ -281,6 +278,7 @@ Test Case - Replication Of Pull Images from Google-GCR To Self Create A Rule With Existing Endpoint rule${d} pull eminent-nation-87317/* image e${d} project${d} Filter Replication Rule rule${d} Select Rule And Replicate rule${d} + Check Latest Replication Job Status Succeeded Image Should Be Replicated To Project project${d} httpd Image Should Be Replicated To Project project${d} tomcat Close Browser @@ -308,8 +306,8 @@ Test Case - Replication Of Push Images to Gitlab Triggered By Event Test Case - Replication Of Pull Manifest List and CNAB from Harbor To Self &{image1_with_tag}= Create Dictionary image=busybox tag=1.32.0 total_artifact_count=9 archive_count=0 - &{image2_with_tag}= Create Dictionary image=index101603308079 tag=index_tag101603308079 total_artifact_count=2 archive_count=0 - &{image3_with_tag}= Create Dictionary image=cnab011609785126 tag=cnab_tag011609785126 total_artifact_count=3 archive_count=2 + &{image2_with_tag}= Create Dictionary image=index tag=index_tag total_artifact_count=2 archive_count=0 + &{image3_with_tag}= Create Dictionary image=cnab tag=cnab_tag total_artifact_count=3 archive_count=2 ${image1}= Get From Dictionary ${image1_with_tag} image ${image2}= Get From Dictionary ${image2_with_tag} image ${image3}= Get From Dictionary ${image3_with_tag} image @@ -386,20 +384,19 @@ Test Case - Robot Account Do Replication Switch To Replication Manage Create A Rule With Existing Endpoint rule_push_${d} push project${d}/* image e${d} project_dest${d} Select Rule And Replicate rule_push_${d} - Retry Wait Until Page Contains Succeeded + Check Latest Replication Job Status Succeeded Logout Harbor Sign In Harbor https://${ip1} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} - Image Should Be Replicated To Project project_dest${d} ${image1} period=0 + Image Should Be Replicated To Project project_dest${d} ${image1} Should Be Signed By Cosign ${tag1} - Image Should Be Replicated To Project project_dest${d} ${image2} period=0 + Image Should Be Replicated To Project project_dest${d} ${image2} Should Be Signed By Cosign ${tag2} Back Project Home project_dest${d} - Retry Double Keywords When Error Go Into Repo project_dest${d}/${index} Should Be Signed By Cosign ${index_tag} - Back Project Home project_dest${d} - Go Into Repo project_dest${d}/${index} + Go Into Repo project_dest${d} ${index} + Should Be Signed By Cosign ${index_tag} + Go Into Repo project_dest${d} ${index} Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Be Signed By Cosign ${image1_short_sha256} - Back Project Home project_dest${d} - Go Into Repo project_dest${d}/${index} + Go Into Repo project_dest${d} ${index} Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Not Be Signed By Cosign ${image2_short_sha256} # pull mode Logout Harbor @@ -408,18 +405,17 @@ Test Case - Robot Account Do Replication Switch To Replication Manage Create A Rule With Existing Endpoint rule_pull_${d} pull project_dest${d}/* image e${d} project_dest${d} Select Rule And Replicate rule_pull_${d} - Retry Wait Until Page Contains Succeeded - Image Should Be Replicated To Project project_dest${d} ${image1} period=0 + Check Latest Replication Job Status Succeeded + Image Should Be Replicated To Project project_dest${d} ${image1} Should Be Signed By Cosign ${tag1} - Image Should Be Replicated To Project project_dest${d} ${image2} period=0 + Image Should Be Replicated To Project project_dest${d} ${image2} Should Be Signed By Cosign ${tag2} Back Project Home project_dest${d} - Retry Double Keywords When Error Go Into Repo project_dest${d}/${index} Should Be Signed By Cosign ${index_tag} - Back Project Home project_dest${d} - Go Into Repo project_dest${d}/${index} + Go Into Repo project_dest${d} ${index} + Should Be Signed By Cosign ${index_tag} + Go Into Repo project_dest${d} ${index} Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Be Signed By Cosign ${image1_short_sha256} - Back Project Home project_dest${d} - Go Into Repo project_dest${d}/${index} + Go Into Repo project_dest${d} ${index} Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Not Be Signed By Cosign ${image2_short_sha256} Close Browser @@ -458,9 +454,9 @@ Test Case - Replication Triggered By Events Logout Harbor Sign In Harbor https://${ip1} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} - Image Should Be Replicated To Project project_dest${d} ${image1} period=0 - Image Should Be Replicated To Project project_dest${d} ${image2} period=0 - Image Should Be Replicated To Project project_dest${d} ${index} period=0 + Image Should Be Replicated To Project project_dest${d} ${image1} + Image Should Be Replicated To Project project_dest${d} ${image2} + Image Should Be Replicated To Project project_dest${d} ${index} # sign Cosign Generate Key Pair Docker Login ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} @@ -469,32 +465,28 @@ Test Case - Replication Triggered By Events Cosign Sign ${ip}/project${d}/${index}@${image1sha256} Logout Harbor Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} - Go Into Project project${d} - Retry Double Keywords When Error Go Into Repo project${d}/${image1} Should Be Signed By Cosign ${tag1} - Back Project Home project${d} - Retry Double Keywords When Error Go Into Repo project${d}/${index} Should Be Signed By Cosign ${index_tag} - Back Project Home project${d} - Go Into Repo project${d}/${index} + Go Into Repo project${d} ${image1} + Should Be Signed By Cosign ${tag1} + Go Into Repo project${d} ${index} + Should Be Signed By Cosign ${index_tag} + Go Into Repo project${d} ${index} Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Be Signed By Cosign ${image1_short_sha256} - Back Project Home project${d} - Retry Double Keywords When Error Go Into Repo project${d}/${image2} Should Not Be Signed By Cosign ${tag2} - Back Project Home project${d} - Go Into Repo project${d}/${index} + Go Into Repo project${d} ${image2} + Should Not Be Signed By Cosign ${tag2} + Go Into Repo project${d} ${index} Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Not Be Signed By Cosign ${image2_short_sha256} Logout Harbor Sign In Harbor https://${ip1} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} - Go Into Project project_dest${d} - Retry Double Keywords When Error Go Into Repo project_dest${d}/${image1} Should Be Signed By Cosign ${tag1} - Back Project Home project_dest${d} - Retry Double Keywords When Error Go Into Repo project_dest${d}/${index} Should Be Signed By Cosign ${index_tag} - Back Project Home project_dest${d} - Go Into Repo project_dest${d}/${index} + Go Into Repo project_dest${d} ${image1} + Should Be Signed By Cosign ${tag1} + Go Into Repo project_dest${d} ${index} + Should Be Signed By Cosign ${index_tag} + Go Into Repo project_dest${d} ${index} Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Be Signed By Cosign ${image1_short_sha256} - Back Project Home project_dest${d} - Retry Double Keywords When Error Go Into Repo project_dest${d}/${image2} Should Not Be Signed By Cosign ${tag2} - Back Project Home project_dest${d} - Go Into Repo project_dest${d}/${index} + Go Into Repo project_dest${d} ${image2} + Should Not Be Signed By Cosign ${tag2} + Go Into Repo project_dest${d} ${index} Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Not Be Signed By Cosign ${image2_short_sha256} Logout Harbor # delete @@ -502,12 +494,10 @@ Test Case - Replication Triggered By Events Go Into Project project${d} Delete Repo project${d} ${image2} Repo Not Exist project${d} ${image2} - Go Into Project project${d} - Go Into Repo project${d}/${image1} + Go Into Repo project${d} ${image1} Retry Double Keywords When Error Delete Accessory ${tag1} Should be Accessory deleted ${tag1} Should Not Be Signed By Cosign ${tag1} - Back Project Home project${d} - Go Into Repo project${d}/${index} + Go Into Repo project${d} ${index} Retry Double Keywords When Error Delete Accessory ${index_tag} Should be Accessory deleted ${index_tag} Should Not Be Signed By Cosign ${index_tag} Click Index Achieve ${index_tag} @@ -516,14 +506,13 @@ Test Case - Replication Triggered By Events Logout Harbor Sign In Harbor https://${ip1} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} - Go Into Project project_dest${d} - Go Into Repo project_dest${d}/${image2} + Go Into Repo project_dest${d} ${image2} Wait Until Page Contains We couldn't find any artifacts! - Back Project Home project_dest${d} - Retry Double Keywords When Error Go Into Repo project_dest${d}/${image1} Should be Accessory deleted ${tag1} + Go Into Repo project_dest${d} ${image1} + Should be Accessory deleted ${tag1} Should Not Be Signed By Cosign ${tag1} - Back Project Home project_dest${d} - Retry Double Keywords When Error Go Into Repo project_dest${d}/${index} Should be Accessory deleted ${index_tag} + Go Into Repo project_dest${d} ${index} + Should be Accessory deleted ${index_tag} Should Not Be Signed By Cosign ${index_tag} Retry Double Keywords When Error Click Index Achieve ${index_tag} Should be Accessory deleted ${image1_short_sha256} Should Not Be Signed By Cosign ${image1_short_sha256} @@ -603,16 +592,15 @@ Test Case - Enable Replication Of Cosign Deployment Security Policy Repo Exist project_pull_dest${d} ${image1} Repo Exist project_pull_dest${d} ${image2} Repo Exist project_pull_dest${d} ${index} - Retry Double Keywords When Error Go Into Repo project_pull_dest${d}/${image1} Should Be Signed By Cosign ${tag1} - Back Project Home project_pull_dest${d} - Retry Double Keywords When Error Go Into Repo project_pull_dest${d}/${image2} Should Be Signed By Cosign ${tag2} - Back Project Home project_pull_dest${d} - Retry Double Keywords When Error Go Into Repo project_pull_dest${d}/${index} Should Be Signed By Cosign ${index_tag} - Back Project Home project_pull_dest${d} - Go Into Repo project_pull_dest${d}/${index} + Go Into Repo project_pull_dest${d} ${image1} + Should Be Signed By Cosign ${tag1} + Go Into Repo project_pull_dest${d} ${image2} + Should Be Signed By Cosign ${tag2} + Go Into Repo project_pull_dest${d} ${index} + Should Be Signed By Cosign ${index_tag} + Go Into Repo project_pull_dest${d} ${index} Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Be Signed By Cosign ${image1_short_sha256} - Back Project Home project_pull_dest${d} - Go Into Repo project_pull_dest${d}/${index} + Go Into Repo project_pull_dest${d} ${index} Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Be Signed By Cosign ${image2_short_sha256} # check project_push_dest Go Into Project project_push_dest${d} @@ -620,16 +608,15 @@ Test Case - Enable Replication Of Cosign Deployment Security Policy Repo Exist project_push_dest${d} ${image1} Repo Exist project_push_dest${d} ${image2} Repo Exist project_push_dest${d} ${index} - Retry Double Keywords When Error Go Into Repo project_push_dest${d}/${image1} Should Be Signed By Cosign ${tag1} - Back Project Home project_push_dest${d} - Retry Double Keywords When Error Go Into Repo project_push_dest${d}/${image2} Should Be Signed By Cosign ${tag2} - Back Project Home project_push_dest${d} - Retry Double Keywords When Error Go Into Repo project_push_dest${d}/${index} Should Be Signed By Cosign ${index_tag} - Back Project Home project_push_dest${d} - Go Into Repo project_push_dest${d}/${index} + Go Into Repo project_push_dest${d} ${image1} + Should Be Signed By Cosign ${tag1} + Go Into Repo project_push_dest${d} ${image2} + Should Be Signed By Cosign ${tag2} + Go Into Repo project_push_dest${d} ${index} + Should Be Signed By Cosign ${index_tag} + Go Into Repo project_push_dest${d} ${index} Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Be Signed By Cosign ${image1_short_sha256} - Back Project Home project_push_dest${d} - Go Into Repo project_push_dest${d}/${index} + Go Into Repo project_push_dest${d} ${index} Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Be Signed By Cosign ${image2_short_sha256} Close Browser @@ -653,7 +640,7 @@ Test Case - Carvel Imgpkg Copy To Harbor Imgpkg Copy From Registry To Registry ${ip}/project${d}/${repository}:${tag} ${ip1}/project_dest${d}/${repository} Refresh Repositories Repo Exist project_dest${d} ${repository} - Go Into Repo project_dest${d}/${repository} + Go Into Repo project_dest${d} ${repository} Artifact Exist ${tag} Back Project Home project_dest${d} Delete Repo project_dest${d} ${repository} @@ -664,7 +651,7 @@ Test Case - Carvel Imgpkg Copy To Harbor Refresh Repositories Repo Exist project_dest${d} ${repository} Retry Element Click ${repo_search_icon} - Go Into Repo project_dest${d}/${repository} + Go Into Repo project_dest${d} ${repository} Artifact Exist ${tag} Docker Logout ${ip} Docker Logout ${ip1} diff --git a/tests/robot-cases/Group1-Nightly/Routing.robot b/tests/robot-cases/Group1-Nightly/Routing.robot index 5c64e10b585..7242667e330 100644 --- a/tests/robot-cases/Group1-Nightly/Routing.robot +++ b/tests/robot-cases/Group1-Nightly/Routing.robot @@ -93,7 +93,7 @@ Test Case - Open CVE Details Page Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} Create An New Project And Go Into Project project${d} Push Image ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} project${d} ${image} sha256=${sha256} - Go Into Repo project${d}/${image} + Go Into Repo project${d} ${image} Scan Repo ${sha256} Succeed Go Into Artifact ${sha256} Retry Double Keywords When Error Click Link New Tab And Switch //hbr-artifact-vulnerabilities//clr-dg-row//a[contains(.,'${cve}')] Retry Wait Element //h1[contains(.,'${cve}')] diff --git a/tests/robot-cases/Group1-Nightly/Schedule.robot b/tests/robot-cases/Group1-Nightly/Schedule.robot index 02b624e0665..8982a3ad085 100644 --- a/tests/robot-cases/Group1-Nightly/Schedule.robot +++ b/tests/robot-cases/Group1-Nightly/Schedule.robot @@ -48,14 +48,13 @@ Test Case - Proxy Cache Pull Image ${ip} ${test_user} ${test_pwd} project${d} ${user_namespace}/${manifest_index} tag=${manifest_tag} Log To Console Start to Sleep 3 minitues...... Sleep 180 - Go Into Project project${d} - Go Into Repo project${d}/${user_namespace}/${image} + Go Into Repo project${d} ${user_namespace}/${image} FOR ${idx} IN RANGE 0 15 Log All Checking manifest ${idx} round...... Sleep 60 Go Into Project project${d} - ${repo_out}= Run Keyword And Ignore Error Go Into Repo project${d}/${user_namespace}/${manifest_index} + ${repo_out}= Run Keyword And Ignore Error Go Into Repo project${d} ${user_namespace}/${manifest_index} Continue For Loop If '${repo_out[0]}'=='FAIL' ${artifact_out}= Run Keyword And Ignore Error Go Into Index And Contain Artifacts ${manifest_tag} total_artifact_count=1 Exit For Loop If '${artifact_out[0]}'=='PASS' @@ -78,7 +77,7 @@ Test Case - GC Schedule Job Create An New Project And Go Into Project ${project_name} Push image ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} ${project_name} ${image} sha256=${sha256} Sleep 50 - Go Into Repo ${project_name}/${image} + Go Into Repo ${project_name} ${image} Switch To Garbage Collection Set GC Schedule custom value=0 */2 * * * * Sleep 480 @@ -118,7 +117,7 @@ Test Case - Scan Schedule Job Create An New Project And Go Into Project ${project_name} Push image ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} ${project_name} ${image} sha256=${sha256} Sleep 50 - Go Into Repo ${project_name}/${image} + Go Into Repo ${project_name} ${image} Retry Wait Until Page Contains Element ${not_scanned_icon} Switch To Vulnerability Page ${flag}= Set Variable ${false} @@ -134,14 +133,12 @@ Test Case - Scan Schedule Job # After scan custom schedule is set, image should stay in unscanned status. Log To Console Sleep for 300 seconds...... Sleep 180 - Go Into Project ${project_name} - Go Into Repo ${project_name}/${image} + Go Into Repo ${project_name} ${image} Retry Wait Until Page Contains Element ${not_scanned_icon} Log To Console Sleep for 500 seconds...... Sleep 500 - Go Into Project ${project_name} - Go Into Repo ${project_name}/${image} + Go Into Repo ${project_name} ${image} Scan Result Should Display In List Row ${sha256} View Repo Scan Details Critical High Close Browser @@ -175,11 +172,10 @@ Test Case - Replication Schedule Job # After replication schedule is set, project should contain 2 images. Log To Console Sleep for 720 seconds...... Sleep 720 - Go Into Project ${project_name} - Go Into Repo ${project_name}/${image_a} + Go Into Repo ${project_name} ${image_a} Artifact Exist ${tag_a} Go Into Project ${project_name} - Go Into Repo ${project_name}/${image_b} + Go Into Repo ${project_name} ${image_b} Artifact Exist ${tag_b} # Delete repository @@ -190,11 +186,10 @@ Test Case - Replication Schedule Job # After replication schedule is set, project should contain 2 images. Log To Console Sleep for 600 seconds...... Sleep 600 - Go Into Project ${project_name} - Go Into Repo ${project_name}/${image_a} + Go Into Repo ${project_name} ${image_a} Artifact Exist ${tag_a} Go Into Project ${project_name} - Go Into Repo ${project_name}/${image_b} + Go Into Repo ${project_name} ${image_b} Artifact Exist ${tag_b} Close Browser diff --git a/tests/robot-cases/Group1-Nightly/Trivy.robot b/tests/robot-cases/Group1-Nightly/Trivy.robot index 34cb9e5353e..c05584bf548 100644 --- a/tests/robot-cases/Group1-Nightly/Trivy.robot +++ b/tests/robot-cases/Group1-Nightly/Trivy.robot @@ -54,10 +54,8 @@ Test Case - Scan A Tag In The Repo Test Case - Scan As An Unprivileged User Init Chrome Driver Push Image ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} library hello-world - Sign In Harbor ${HARBOR_URL} user024 Test1@34 - Go Into Project library - Go Into Repo hello-world + Go Into Repo library hello-world Select Object latest Scan Is Disabled Close Browser @@ -83,8 +81,7 @@ Test Case - Project Level Image Serverity Policy ${image}= Set Variable redis Create An New Project And Go Into Project project${d} Push Image ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} project${d} ${image} sha256=${sha256} - Go Into Project project${d} - Go Into Repo ${image} + Go Into Repo project${d} ${image} Scan Repo ${sha256} Succeed Navigate To Projects Go Into Project project${d} diff --git a/tests/robot-cases/Group1-Nightly/multi_scanners.robot b/tests/robot-cases/Group1-Nightly/multi_scanners.robot index a18b9d23f74..6f7869f8ce5 100644 --- a/tests/robot-cases/Group1-Nightly/multi_scanners.robot +++ b/tests/robot-cases/Group1-Nightly/multi_scanners.robot @@ -38,16 +38,14 @@ Test Case - Switch Scanner Create An New Project And Go Into Project project${d} Push Image ${ip} admin Harbor12345 project${d} hello-world:latest - Go Into Project project${d} - Go Into Repo project${d}/hello-world + Go Into Repo project${d} hello-world Scan Repo latest Succeed Move To Summary Chart Wait Until Page Contains No vulnerability Switch To Scanners Page - Go Into Project project${d} - Go Into Repo project${d}/hello-world + Go Into Repo project${d} hello-world Retry Wait Until Page Contains Element ${not_scanned_icon} Scan Repo latest Succeed Move To Summary Chart @@ -55,4 +53,4 @@ Test Case - Switch Scanner Switch To Scanners Page Set Default Scanner Trivy - Close Browser \ No newline at end of file + Close Browser From adf80e921e17a329842214ffe18722452d516069 Mon Sep 17 00:00:00 2001 From: Yang Jiao <72076317+YangJiao0817@users.noreply.github.com> Date: Fri, 7 Jul 2023 15:18:22 +0800 Subject: [PATCH 31/38] Add replication by chunk testcase (#18904) Fix #17951 Signed-off-by: Yang Jiao --- .../resources/Harbor-Pages/Replication.robot | 24 +++++++++++++------ .../Harbor-Pages/Replication_Elements.robot | 1 + .../Group1-Nightly/Replication.robot | 6 +++-- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/tests/resources/Harbor-Pages/Replication.robot b/tests/resources/Harbor-Pages/Replication.robot index 580c7bc6deb..2f09e35d494 100644 --- a/tests/resources/Harbor-Pages/Replication.robot +++ b/tests/resources/Harbor-Pages/Replication.robot @@ -111,14 +111,14 @@ Create A New Endpoint Create A Rule With Existing Endpoint [Arguments] ${name} ${replication_mode} ${filter_project_name} ${resource_type} ${endpoint} ${dest_namespace} ... ${mode}=Manual ${cron}="* */59 * * * *" ${del_remote}=${false} ${filter_tag}=${false} ${filter_tag_model}=matching ${filter_label}=${false} ${filter_label_model}=matching - ... ${flattening}=Flatten 1 Level ${bandwidth}=-1 ${bandwidth_unit}=Kbps - #click new + ... ${flattening}=Flatten 1 Level ${bandwidth}=-1 ${bandwidth_unit}=Kbps ${copy_by_chunk}=${false} + # Click new Retry Double Keywords When Error Retry Element Click ${new_name_xpath} Wait Until Element Is Enabled ${rule_name} - #input name + # Input name Retry Text Input ${rule_name} ${name} Run Keyword If '${replication_mode}' == 'push' Run Keywords Retry Element Click ${replication_mode_radio_push} AND Select Dest Registry ${endpoint} ... ELSE Run Keywords Retry Element Click ${replication_mode_radio_pull} AND Select Source Registry ${endpoint} - #set filter + # Set filter Retry Password Input ${filter_name_id} ${filter_project_name} Run Keyword If '${filter_tag_model}' != 'matching' Select Filter Tag Model ${filter_tag_model} Run Keyword If '${filter_tag}' != '${false}' Retry Text Input ${filter_tag_id} ${filter_tag} @@ -127,14 +127,16 @@ Create A Rule With Existing Endpoint Run Keyword And Ignore Error Select From List By Value ${rule_resource_selector} ${resource_type} Retry Text Input ${dest_namespace_xpath} ${dest_namespace} Select flattening ${flattening} - #set trigger + # Set trigger Select Trigger ${mode} Run Keyword If '${mode}' == 'Scheduled' Retry Text Input ${targetCron_id} ${cron} Run Keyword If '${mode}' == 'Event Based' and '${del_remote}' == '${true}' Retry Element Click ${del_remote_checkbox} - #set bandwidth + # Set bandwidth Run Keyword If '${bandwidth}' != '-1' Retry Text Input ${bandwidth_input} ${bandwidth} Run Keyword If '${bandwidth_unit}' != 'Kbps' Select Bandwidth Unit ${bandwidth_unit} - #click save + # Set copy by chunk + Run Keyword If '${copy_by_chunk}' == '${true}' Retry Element Click ${copy_by_chunk_checkbox} + # Click save Retry Double Keywords When Error Retry Element Click ${rule_save_button} Retry Wait Until Page Not Contains Element ${rule_save_button} Endpoint Is Unpingable @@ -261,6 +263,7 @@ Image Should Be Replicated To Project Log To Console Return value is ${out[0]} Continue For Loop If '${out[0]}'=='FAIL' Go Into Repo ${project} ${image} + Wait Until Page Contains Element //clr-dg-row[contains(., '${tag}')]//clr-dg-cell[4]/div ${size}= Run Keyword If '${tag}'!='${EMPTY}' and '${expected_image_size_in_regexp}'!='${null}' Get Text //clr-dg-row[contains(., '${tag}')]//clr-dg-cell[4]/div Run Keyword If '${tag}'!='${EMPTY}' and '${expected_image_size_in_regexp}'!='${null}' Should Match Regexp '${size}' '${expected_image_size_in_regexp}' ${index_out} Go Into Index And Contain Artifacts ${tag} total_artifact_count=${total_artifact_count} archive_count=${archive_count} return_immediately=${true} @@ -281,3 +284,10 @@ Executions Result Count Should Be Check Latest Replication Job Status [Arguments] ${expected_status} Retry Wait Element //hbr-replication//div[contains(@class,'datagrid')]//clr-dg-row[1][contains(.,'${expected_status}')] + +Check Latest Replication Enabled Copy By Chunk + Retry Link Click //hbr-replication//div[contains(@class,'datagrid')]//clr-dg-row[1]//a + Retry Link Click //clr-dg-row[1]//a + Switch Window locator=NEW + Retry Wait Until Page Contains chunk completed + Switch Window locator=MAIN diff --git a/tests/resources/Harbor-Pages/Replication_Elements.robot b/tests/resources/Harbor-Pages/Replication_Elements.robot index 57574d170d6..3ad520220f8 100644 --- a/tests/resources/Harbor-Pages/Replication_Elements.robot +++ b/tests/resources/Harbor-Pages/Replication_Elements.robot @@ -95,3 +95,4 @@ ${del_remote_checkbox} //label[@for='ruleDeletion'] ${filter_registry_btn} //hbr-filter ${filter_registry_input} //input[contains(@class,'filter-input')] ${flattening_select} //select[@id='dest_namespace_replace_count'] +${copy_by_chunk_checkbox} //label[@for='by-chunk'] diff --git a/tests/robot-cases/Group1-Nightly/Replication.robot b/tests/robot-cases/Group1-Nightly/Replication.robot index d1b4c7c8271..a30577ae556 100644 --- a/tests/robot-cases/Group1-Nightly/Replication.robot +++ b/tests/robot-cases/Group1-Nightly/Replication.robot @@ -382,9 +382,10 @@ Test Case - Robot Account Do Replication Switch To Registries Create A New Endpoint harbor e${d} https://${ip1} ${robot_account_name} ${robot_account_secret} Switch To Replication Manage - Create A Rule With Existing Endpoint rule_push_${d} push project${d}/* image e${d} project_dest${d} + Create A Rule With Existing Endpoint rule_push_${d} push project${d}/* image e${d} project_dest${d} copy_by_chunk=${true} Select Rule And Replicate rule_push_${d} Check Latest Replication Job Status Succeeded + Check Latest Replication Enabled Copy By Chunk Logout Harbor Sign In Harbor https://${ip1} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} Image Should Be Replicated To Project project_dest${d} ${image1} @@ -403,9 +404,10 @@ Test Case - Robot Account Do Replication Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} Create An New Project And Go Into Project project_dest${d} Switch To Replication Manage - Create A Rule With Existing Endpoint rule_pull_${d} pull project_dest${d}/* image e${d} project_dest${d} + Create A Rule With Existing Endpoint rule_pull_${d} pull project_dest${d}/* image e${d} project_dest${d} copy_by_chunk=${true} Select Rule And Replicate rule_pull_${d} Check Latest Replication Job Status Succeeded + Check Latest Replication Enabled Copy By Chunk Image Should Be Replicated To Project project_dest${d} ${image1} Should Be Signed By Cosign ${tag1} Image Should Be Replicated To Project project_dest${d} ${image2} From 78799ccb2f2cb2c522a815d5d7f00d70c306810f Mon Sep 17 00:00:00 2001 From: Chlins Zhang Date: Mon, 10 Jul 2023 13:24:37 +0800 Subject: [PATCH 32/38] perf: introduce update quota by redis (#18871) Introduce the quota update provider, improve the performance of pushing artifacts to same project with high concurrency by implementing optimistic lock in redis. By default the function is disabled, open it by set env 'QUOTA_UPDATE_PROVIDER=Redis' for the core container. Fixes: #18440 Signed-off-by: chlins --- src/common/const.go | 2 + src/controller/blob/controller.go | 14 +- src/controller/quota/controller.go | 215 ++++++++++++++++-- src/go.mod | 2 +- src/lib/cache/redis/redis_test.go | 8 +- src/lib/config/metadata/metadatalist.go | 1 + src/lib/config/systemconfig.go | 5 + src/lib/redis/client.go | 85 +++++++ src/lib/redis/client_test.go | 63 +++++ src/lib/redis/instance.go | 50 ---- src/lib/redis/instance_test.go | 40 ---- src/lib/redis/{redisclient.go => pool.go} | 0 .../x/sync/singleflight/singleflight.go | 205 +++++++++++++++++ src/vendor/modules.txt | 1 + 14 files changed, 573 insertions(+), 118 deletions(-) create mode 100644 src/lib/redis/client.go create mode 100644 src/lib/redis/client_test.go delete mode 100644 src/lib/redis/instance.go delete mode 100644 src/lib/redis/instance_test.go rename src/lib/redis/{redisclient.go => pool.go} (100%) create mode 100644 src/vendor/golang.org/x/sync/singleflight/singleflight.go diff --git a/src/common/const.go b/src/common/const.go index 5e30b78fcc0..d94edf1afd9 100644 --- a/src/common/const.go +++ b/src/common/const.go @@ -226,4 +226,6 @@ const ( UIMaxLengthLimitedOfNumber = 10 // ExecutionStatusRefreshIntervalSeconds is the interval seconds for refreshing execution status ExecutionStatusRefreshIntervalSeconds = "execution_status_refresh_interval_seconds" + // QuotaUpdateProvider is the provider for updating quota, currently support Redis and DB + QuotaUpdateProvider = "quota_update_provider" ) diff --git a/src/controller/blob/controller.go b/src/controller/blob/controller.go index 112f0d8e1f9..f59a457ffe0 100644 --- a/src/controller/blob/controller.go +++ b/src/controller/blob/controller.go @@ -323,7 +323,12 @@ func (c *controller) Sync(ctx context.Context, references []distribution.Descrip func (c *controller) SetAcceptedBlobSize(ctx context.Context, sessionID string, size int64) error { key := blobSizeKey(sessionID) - err := libredis.Instance().Set(ctx, key, size, c.blobSizeExpiration).Err() + rc, err := libredis.GetRegistryClient() + if err != nil { + return err + } + + err = rc.Set(ctx, key, size, c.blobSizeExpiration).Err() if err != nil { log.Errorf("failed to set accepted blob size for session %s in redis, error: %v", sessionID, err) return err @@ -334,7 +339,12 @@ func (c *controller) SetAcceptedBlobSize(ctx context.Context, sessionID string, func (c *controller) GetAcceptedBlobSize(ctx context.Context, sessionID string) (int64, error) { key := blobSizeKey(sessionID) - size, err := libredis.Instance().Get(ctx, key).Int64() + rc, err := libredis.GetRegistryClient() + if err != nil { + return 0, err + } + + size, err := rc.Get(ctx, key).Int64() if err != nil { if err == redis.Nil { return 0, nil diff --git a/src/controller/quota/controller.go b/src/controller/quota/controller.go index 7e6d8f0959b..3c4b857d502 100644 --- a/src/controller/quota/controller.go +++ b/src/controller/quota/controller.go @@ -19,20 +19,48 @@ import ( "fmt" "time" + "github.com/go-redis/redis/v8" + "golang.org/x/sync/singleflight" + // quota driver _ "github.com/goharbor/harbor/src/controller/quota/driver" + "github.com/goharbor/harbor/src/lib/cache" + "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/errors" + "github.com/goharbor/harbor/src/lib/gtask" "github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/orm" "github.com/goharbor/harbor/src/lib/q" + libredis "github.com/goharbor/harbor/src/lib/redis" "github.com/goharbor/harbor/src/lib/retry" "github.com/goharbor/harbor/src/pkg/quota" "github.com/goharbor/harbor/src/pkg/quota/driver" "github.com/goharbor/harbor/src/pkg/quota/types" + + // init the db config + _ "github.com/goharbor/harbor/src/pkg/config/db" ) +func init() { + // register the async task for flushing quota to db when enable update quota by redis + if provider := config.GetQuotaUpdateProvider(); provider == updateQuotaProviderRedis.String() { + gtask.DefaultPool().AddTask(flushQuota, 30*time.Second) + } +} + +type updateQuotaProviderType string + +func (t updateQuotaProviderType) String() string { + return string(t) +} + var ( defaultRetryTimeout = time.Minute * 5 + // quotaExpireTimeout is the expire time for quota when update quota by redis + quotaExpireTimeout = time.Minute * 5 + + updateQuotaProviderRedis updateQuotaProviderType = "Redis" + updateQuotaProviderDB updateQuotaProviderType = "DB" ) var ( @@ -87,6 +115,31 @@ type controller struct { reservedExpiration time.Duration quotaMgr quota.Manager + g singleflight.Group +} + +// flushQuota flushes the quota info from redis to db asynchronously. +func flushQuota(ctx context.Context) { + iter, err := cache.Default().Scan(ctx, "quota:*") + if err != nil { + log.Errorf("failed to scan out the quota records from redis") + } + + for iter.Next(ctx) { + key := iter.Val() + q := "a.Quota{} + err = cache.Default().Fetch(ctx, key, q) + if err != nil { + log.Errorf("failed to fetch quota: %s, error: %v", key, err) + continue + } + + if err = Ctl.Update(ctx, q); err != nil { + log.Errorf("failed to refresh quota: %s, error: %v", key, err) + } else { + log.Debugf("successfully refreshed quota: %s", key) + } + } } func (c *controller) Count(ctx context.Context, query *q.Query) (int64, error) { @@ -163,13 +216,83 @@ func (c *controller) List(ctx context.Context, query *q.Query, options ...Option return quotas, nil } -func (c *controller) updateUsageWithRetry(ctx context.Context, reference, referenceID string, op func(hardLimits, used types.ResourceList) (types.ResourceList, error), retryOpts ...retry.Option) error { - f := func() error { - q, err := c.quotaMgr.GetByRef(ctx, reference, referenceID) - if err != nil { +// updateUsageByDB updates the quota usage by the database which updates the quota usage immediately. +func (c *controller) updateUsageByDB(ctx context.Context, reference, referenceID string, op func(hardLimits, used types.ResourceList) (types.ResourceList, error)) error { + q, err := c.quotaMgr.GetByRef(ctx, reference, referenceID) + if err != nil { + return retry.Abort(err) + } + + hardLimits, err := q.GetHard() + if err != nil { + return retry.Abort(err) + } + + used, err := q.GetUsed() + if err != nil { + return retry.Abort(err) + } + + newUsed, err := op(hardLimits, used) + if err != nil { + return retry.Abort(err) + } + + // The PR https://github.com/goharbor/harbor/pull/17392 optimized the logic for post upload blob which use size 0 + // for checking quota, this will increase the pressure of optimistic lock, so here return earlier + // if the quota usage has not changed to reduce the probability of optimistic lock. + if types.Equals(used, newUsed) { + return nil + } + + q.SetUsed(newUsed) + + err = c.quotaMgr.Update(ctx, q) + if err != nil && !errors.Is(err, orm.ErrOptimisticLock) { + return retry.Abort(err) + } + + return err +} + +// updateUsageByRedis updates the quota usage by the redis and flush the quota usage to db asynchronously. +func (c *controller) updateUsageByRedis(ctx context.Context, reference, referenceID string, op func(hardLimits, used types.ResourceList) (types.ResourceList, error)) error { + // earlier abort if context is error such as context canceled + if ctx.Err() != nil { + return retry.Abort(ctx.Err()) + } + + client, err := libredis.GetCoreClient() + if err != nil { + return retry.Abort(err) + } + // normally use cache.Save will append prefix "cache:", in order to keep consistent + // here adopts raw redis client should also pad the prefix manually. + key := fmt.Sprintf("%s:quota:%s:%s", "cache", reference, referenceID) + return client.Watch(ctx, func(tx *redis.Tx) error { + data, err := tx.Get(ctx, key).Result() + if err != nil && err != redis.Nil { return retry.Abort(err) } + q := "a.Quota{} + // calc the quota usage in real time if no key found + if err == redis.Nil { + // use singleflight to prevent cache penetration and cause pressure on the database. + realQuota, err, _ := c.g.Do(key, func() (interface{}, error) { + return c.calcQuota(ctx, reference, referenceID) + }) + if err != nil { + return retry.Abort(err) + } + + q = realQuota.(*quota.Quota) + } else { + if err = cache.DefaultCodec().Decode([]byte(data), q); err != nil { + return retry.Abort(err) + } + } + hardLimits, err := q.GetHard() if err != nil { return retry.Abort(err) @@ -185,21 +308,42 @@ func (c *controller) updateUsageWithRetry(ctx context.Context, reference, refere return retry.Abort(err) } - // The PR https://github.com/goharbor/harbor/pull/17392 optimized the logic for post upload blob which use size 0 - // for checking quota, this will increase the pressure of optimistic lock, so here return earlier - // if the quota usage has not changed to reduce the probability of optimistic lock. - if types.Equals(used, newUsed) { - return nil + q.SetUsed(newUsed) + + val, err := cache.DefaultCodec().Encode(q) + if err != nil { + return retry.Abort(err) } - q.SetUsed(newUsed) + _, err = tx.TxPipelined(ctx, func(p redis.Pipeliner) error { + _, err = p.Set(ctx, key, val, quotaExpireTimeout).Result() + return err + }) - err = c.quotaMgr.Update(ctx, q) - if err != nil && !errors.Is(err, orm.ErrOptimisticLock) { + if err != nil && err != redis.TxFailedErr { return retry.Abort(err) } return err + }, key) +} + +func (c *controller) updateUsageWithRetry(ctx context.Context, reference, referenceID string, op func(hardLimits, used types.ResourceList) (types.ResourceList, error), provider updateQuotaProviderType, retryOpts ...retry.Option) error { + var f func() error + switch provider { + case updateQuotaProviderDB: + f = func() error { + return c.updateUsageByDB(ctx, reference, referenceID, op) + } + case updateQuotaProviderRedis: + f = func() error { + return c.updateUsageByRedis(ctx, reference, referenceID, op) + } + default: + // by default is update quota by db + f = func() error { + return c.updateUsageByDB(ctx, reference, referenceID, op) + } } options := []retry.Option{ @@ -235,7 +379,8 @@ func (c *controller) Refresh(ctx context.Context, reference, referenceID string, return newUsed, err } - return c.updateUsageWithRetry(ctx, reference, referenceID, refreshResources(calculateUsage, opts.IgnoreLimitation), opts.RetryOptions...) + // update quota usage by db for refresh operation + return c.updateUsageWithRetry(ctx, reference, referenceID, refreshResources(calculateUsage, opts.IgnoreLimitation), updateQuotaProviderType(config.GetQuotaUpdateProvider()), opts.RetryOptions...) } func (c *controller) Request(ctx context.Context, reference, referenceID string, resources types.ResourceList, f func() error) error { @@ -243,7 +388,8 @@ func (c *controller) Request(ctx context.Context, reference, referenceID string, return f() } - if err := c.updateUsageWithRetry(ctx, reference, referenceID, reserveResources(resources)); err != nil { + provider := updateQuotaProviderType(config.GetQuotaUpdateProvider()) + if err := c.updateUsageWithRetry(ctx, reference, referenceID, reserveResources(resources), provider); err != nil { log.G(ctx).Errorf("reserve resources %s for %s %s failed, error: %v", resources.String(), reference, referenceID, err) return err } @@ -251,7 +397,7 @@ func (c *controller) Request(ctx context.Context, reference, referenceID string, err := f() if err != nil { - if er := c.updateUsageWithRetry(ctx, reference, referenceID, rollbackResources(resources)); er != nil { + if er := c.updateUsageWithRetry(ctx, reference, referenceID, rollbackResources(resources), provider); er != nil { // ignore this error, the quota usage will be correct when users do operations which will call refresh quota log.G(ctx).Warningf("rollback resources %s for %s %s failed, error: %v", resources.String(), reference, referenceID, er) } @@ -260,6 +406,29 @@ func (c *controller) Request(ctx context.Context, reference, referenceID string, return err } +// calcQuota calculates the quota and usage in real time. +func (c *controller) calcQuota(ctx context.Context, reference, referenceID string) (*quota.Quota, error) { + // get quota and usage from db + q, err := c.quotaMgr.GetByRef(ctx, reference, referenceID) + if err != nil { + return nil, err + } + // the usage in the db maybe outdated, calc it in real time + driver, err := Driver(ctx, reference) + if err != nil { + return nil, err + } + + newUsed, err := driver.CalculateUsage(ctx, referenceID) + if err != nil { + log.G(ctx).Errorf("failed to calculate quota usage for %s %s, error: %v", reference, referenceID, err) + return nil, err + } + + q.SetUsed(newUsed) + return q, nil +} + func (c *controller) Update(ctx context.Context, u *quota.Quota) error { f := func() error { q, err := c.quotaMgr.GetByRef(ctx, u.Reference, u.ReferenceID) @@ -267,15 +436,19 @@ func (c *controller) Update(ctx context.Context, u *quota.Quota) error { return err } - if q.Hard != u.Hard { - if hard, err := u.GetHard(); err == nil { - q.SetHard(hard) + if oldHard, err := q.GetHard(); err == nil { + if newHard, err := u.GetHard(); err == nil { + if !types.Equals(oldHard, newHard) { + q.SetHard(newHard) + } } } - if q.Used != u.Used { - if used, err := u.GetUsed(); err == nil { - q.SetUsed(used) + if oldUsed, err := q.GetUsed(); err == nil { + if newUsed, err := u.GetUsed(); err == nil { + if !types.Equals(oldUsed, newUsed) { + q.SetUsed(newUsed) + } } } diff --git a/src/go.mod b/src/go.mod index a51c7130ad1..33666794303 100644 --- a/src/go.mod +++ b/src/go.mod @@ -64,6 +64,7 @@ require ( golang.org/x/crypto v0.5.0 golang.org/x/net v0.9.0 golang.org/x/oauth2 v0.5.0 + golang.org/x/sync v0.3.0 golang.org/x/text v0.9.0 golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 gopkg.in/h2non/gock.v1 v1.0.16 @@ -162,7 +163,6 @@ 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/sync v0.3.0 golang.org/x/sys v0.7.0 // indirect golang.org/x/term v0.7.0 // indirect google.golang.org/api v0.110.0 // indirect diff --git a/src/lib/cache/redis/redis_test.go b/src/lib/cache/redis/redis_test.go index 1170543aab1..17b6616ef37 100644 --- a/src/lib/cache/redis/redis_test.go +++ b/src/lib/cache/redis/redis_test.go @@ -126,12 +126,12 @@ func (suite *CacheTestSuite) TestScan() { } } { - // no match should return all keys + // return all keys with test-scan-* expect := []string{"test-scan-0", "test-scan-1", "test-scan-2"} // seed data seed(3) // test scan - iter, err := suite.cache.Scan(suite.ctx, "") + iter, err := suite.cache.Scan(suite.ctx, "test-scan-*") suite.NoError(err) got := []string{} for iter.Next(suite.ctx) { @@ -143,12 +143,12 @@ func (suite *CacheTestSuite) TestScan() { } { - // with match should return matched keys + // return matched keys with test-scan-1* expect := []string{"test-scan-1", "test-scan-10"} // seed data seed(11) // test scan - iter, err := suite.cache.Scan(suite.ctx, "*test-scan-1*") + iter, err := suite.cache.Scan(suite.ctx, "test-scan-1*") suite.NoError(err) got := []string{} for iter.Next(suite.ctx) { diff --git a/src/lib/config/metadata/metadatalist.go b/src/lib/config/metadata/metadatalist.go index bb3285e5ff5..535226bc837 100644 --- a/src/lib/config/metadata/metadatalist.go +++ b/src/lib/config/metadata/metadatalist.go @@ -191,5 +191,6 @@ var ( {Name: common.ExecutionStatusRefreshIntervalSeconds, Scope: SystemScope, Group: BasicGroup, EnvKey: "EXECUTION_STATUS_REFRESH_INTERVAL_SECONDS", DefaultValue: "30", ItemType: &Int64Type{}, Editable: false, Description: `The interval seconds to refresh the execution status`}, {Name: common.BannerMessage, Scope: UserScope, Group: BasicGroup, EnvKey: "BANNER_MESSAGE", DefaultValue: "", ItemType: &StringType{}, Editable: true, Description: `The customized banner message for the UI`}, + {Name: common.QuotaUpdateProvider, Scope: SystemScope, Group: BasicGroup, EnvKey: "QUOTA_UPDATE_PROVIDER", DefaultValue: "db", ItemType: &StringType{}, Editable: false, Description: `The provider for updating quota, 'db' or 'redis' is supported`}, } ) diff --git a/src/lib/config/systemconfig.go b/src/lib/config/systemconfig.go index ac835fff9ce..5babc655070 100644 --- a/src/lib/config/systemconfig.go +++ b/src/lib/config/systemconfig.go @@ -132,6 +132,11 @@ func GetExecutionStatusRefreshIntervalSeconds() int64 { return DefaultMgr().Get(backgroundCtx, common.ExecutionStatusRefreshIntervalSeconds).GetInt64() } +// GetQuotaUpdateProvider returns the provider for updating quota. +func GetQuotaUpdateProvider() string { + return DefaultMgr().Get(backgroundCtx, common.QuotaUpdateProvider).GetString() +} + // WithTrivy returns a bool value to indicate if Harbor's deployed with Trivy. func WithTrivy() bool { return DefaultMgr().Get(backgroundCtx, common.WithTrivy).GetBool() diff --git a/src/lib/redis/client.go b/src/lib/redis/client.go new file mode 100644 index 00000000000..d0547185734 --- /dev/null +++ b/src/lib/redis/client.go @@ -0,0 +1,85 @@ +// 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 redis + +import ( + "errors" + "os" + "sync" + + "github.com/go-redis/redis/v8" + + "github.com/goharbor/harbor/src/lib/cache" + libredis "github.com/goharbor/harbor/src/lib/cache/redis" + "github.com/goharbor/harbor/src/lib/log" +) + +var ( + // registry is a global redis client for registry db + registry *redis.Client + registryOnce = &sync.Once{} + + // core is a global redis client for core db + core *redis.Client + coreOnce = &sync.Once{} +) + +// GetRegistryClient returns the registry redis client. +func GetRegistryClient() (*redis.Client, error) { + registryOnce.Do(func() { + url := os.Getenv("_REDIS_URL_REG") + c, err := libredis.New(cache.Options{Address: url}) + if err != nil { + log.Errorf("failed to initialize redis client for registry, error: %v", err) + // reset the once to support retry if error occurred + registryOnce = &sync.Once{} + return + } + + if c != nil { + registry = c.(*libredis.Cache).Client + } + }) + + if registry == nil { + return nil, errors.New("no registry redis client initialized") + } + + return registry, nil +} + +// GetCoreClient returns the core redis client. +func GetCoreClient() (*redis.Client, error) { + coreOnce.Do(func() { + url := os.Getenv("_REDIS_URL_CORE") + c, err := libredis.New(cache.Options{Address: url}) + if err != nil { + log.Errorf("failed to initialize redis client for core, error: %v", err) + // reset the once to support retry if error occurred + coreOnce = &sync.Once{} + return + } + + if c != nil { + core = c.(*libredis.Cache).Client + } + }) + + if core == nil { + return nil, errors.New("no core redis client initialized") + } + + return core, nil +} diff --git a/src/lib/redis/client_test.go b/src/lib/redis/client_test.go new file mode 100644 index 00000000000..1e288ba44ba --- /dev/null +++ b/src/lib/redis/client_test.go @@ -0,0 +1,63 @@ +// 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 redis + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetRegistryClient(t *testing.T) { + // failure case with invalid address + t.Setenv("_REDIS_URL_REG", "invalid-address") + client, err := GetRegistryClient() + assert.Error(t, err) + assert.Nil(t, client) + + // normal case with valid address + t.Setenv("_REDIS_URL_REG", "redis://localhost:6379/1") + client, err = GetRegistryClient() + assert.NoError(t, err) + assert.NotNil(t, client) + + // multiple calls should return the same client + for i := 0; i < 10; i++ { + newClient, err := GetRegistryClient() + assert.NoError(t, err) + assert.Equal(t, client, newClient) + } +} + +func TestGetCoreClient(t *testing.T) { + // failure case with invalid address + t.Setenv("_REDIS_URL_CORE", "invalid-address") + client, err := GetCoreClient() + assert.Error(t, err) + assert.Nil(t, client) + + // normal case with valid address + t.Setenv("_REDIS_URL_CORE", "redis://localhost:6379/0") + client, err = GetCoreClient() + assert.NoError(t, err) + assert.NotNil(t, client) + + // multiple calls should return the same client + for i := 0; i < 10; i++ { + newClient, err := GetCoreClient() + assert.NoError(t, err) + assert.Equal(t, client, newClient) + } +} diff --git a/src/lib/redis/instance.go b/src/lib/redis/instance.go deleted file mode 100644 index 9e59c1a0ab4..00000000000 --- a/src/lib/redis/instance.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright Project Harbor Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package redis - -import ( - "os" - "sync" - - "github.com/go-redis/redis/v8" - - "github.com/goharbor/harbor/src/lib/cache" - libredis "github.com/goharbor/harbor/src/lib/cache/redis" -) - -var ( - // instance is a global redis client. - _instance *redis.Client - _once sync.Once -) - -// Instance returns the redis instance. -func Instance() *redis.Client { - _once.Do(func() { - url := os.Getenv("_REDIS_URL_REG") - if url == "" { - url = "redis://localhost:6379/1" - } - - c, err := libredis.New(cache.Options{Address: url}) - if err != nil { - panic(err) - } - - _instance = c.(*libredis.Cache).Client - }) - - return _instance -} diff --git a/src/lib/redis/instance_test.go b/src/lib/redis/instance_test.go deleted file mode 100644 index 64d0646124a..00000000000 --- a/src/lib/redis/instance_test.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright Project Harbor Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package redis - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestInstance(t *testing.T) { - ins := Instance() - assert.NotNil(t, ins, "should get instance") - - ctx := context.TODO() - // Test set - err := ins.Set(ctx, "foo", "bar", 0).Err() - assert.NoError(t, err, "redis set should be success") - // Test get - val := ins.Get(ctx, "foo").Val() - assert.Equal(t, "bar", val, "redis get should be success") - // Test delete - err = ins.Del(ctx, "foo").Err() - assert.NoError(t, err, "redis delete should be success") - exist := ins.Exists(ctx, "foo").Val() - assert.Equal(t, int64(0), exist, "key should not exist") -} diff --git a/src/lib/redis/redisclient.go b/src/lib/redis/pool.go similarity index 100% rename from src/lib/redis/redisclient.go rename to src/lib/redis/pool.go diff --git a/src/vendor/golang.org/x/sync/singleflight/singleflight.go b/src/vendor/golang.org/x/sync/singleflight/singleflight.go new file mode 100644 index 00000000000..8473fb7922c --- /dev/null +++ b/src/vendor/golang.org/x/sync/singleflight/singleflight.go @@ -0,0 +1,205 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package singleflight provides a duplicate function call suppression +// mechanism. +package singleflight // import "golang.org/x/sync/singleflight" + +import ( + "bytes" + "errors" + "fmt" + "runtime" + "runtime/debug" + "sync" +) + +// errGoexit indicates the runtime.Goexit was called in +// the user given function. +var errGoexit = errors.New("runtime.Goexit was called") + +// A panicError is an arbitrary value recovered from a panic +// with the stack trace during the execution of given function. +type panicError struct { + value interface{} + stack []byte +} + +// Error implements error interface. +func (p *panicError) Error() string { + return fmt.Sprintf("%v\n\n%s", p.value, p.stack) +} + +func newPanicError(v interface{}) error { + stack := debug.Stack() + + // The first line of the stack trace is of the form "goroutine N [status]:" + // but by the time the panic reaches Do the goroutine may no longer exist + // and its status will have changed. Trim out the misleading line. + if line := bytes.IndexByte(stack[:], '\n'); line >= 0 { + stack = stack[line+1:] + } + return &panicError{value: v, stack: stack} +} + +// call is an in-flight or completed singleflight.Do call +type call struct { + wg sync.WaitGroup + + // These fields are written once before the WaitGroup is done + // and are only read after the WaitGroup is done. + val interface{} + err error + + // These fields are read and written with the singleflight + // mutex held before the WaitGroup is done, and are read but + // not written after the WaitGroup is done. + dups int + chans []chan<- Result +} + +// Group represents a class of work and forms a namespace in +// which units of work can be executed with duplicate suppression. +type Group struct { + mu sync.Mutex // protects m + m map[string]*call // lazily initialized +} + +// Result holds the results of Do, so they can be passed +// on a channel. +type Result struct { + Val interface{} + Err error + Shared bool +} + +// Do executes and returns the results of the given function, making +// sure that only one execution is in-flight for a given key at a +// time. If a duplicate comes in, the duplicate caller waits for the +// original to complete and receives the same results. +// The return value shared indicates whether v was given to multiple callers. +func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) { + g.mu.Lock() + if g.m == nil { + g.m = make(map[string]*call) + } + if c, ok := g.m[key]; ok { + c.dups++ + g.mu.Unlock() + c.wg.Wait() + + if e, ok := c.err.(*panicError); ok { + panic(e) + } else if c.err == errGoexit { + runtime.Goexit() + } + return c.val, c.err, true + } + c := new(call) + c.wg.Add(1) + g.m[key] = c + g.mu.Unlock() + + g.doCall(c, key, fn) + return c.val, c.err, c.dups > 0 +} + +// DoChan is like Do but returns a channel that will receive the +// results when they are ready. +// +// The returned channel will not be closed. +func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result { + ch := make(chan Result, 1) + g.mu.Lock() + if g.m == nil { + g.m = make(map[string]*call) + } + if c, ok := g.m[key]; ok { + c.dups++ + c.chans = append(c.chans, ch) + g.mu.Unlock() + return ch + } + c := &call{chans: []chan<- Result{ch}} + c.wg.Add(1) + g.m[key] = c + g.mu.Unlock() + + go g.doCall(c, key, fn) + + return ch +} + +// doCall handles the single call for a key. +func (g *Group) doCall(c *call, key string, fn func() (interface{}, error)) { + normalReturn := false + recovered := false + + // use double-defer to distinguish panic from runtime.Goexit, + // more details see https://golang.org/cl/134395 + defer func() { + // the given function invoked runtime.Goexit + if !normalReturn && !recovered { + c.err = errGoexit + } + + g.mu.Lock() + defer g.mu.Unlock() + c.wg.Done() + if g.m[key] == c { + delete(g.m, key) + } + + if e, ok := c.err.(*panicError); ok { + // In order to prevent the waiting channels from being blocked forever, + // needs to ensure that this panic cannot be recovered. + if len(c.chans) > 0 { + go panic(e) + select {} // Keep this goroutine around so that it will appear in the crash dump. + } else { + panic(e) + } + } else if c.err == errGoexit { + // Already in the process of goexit, no need to call again + } else { + // Normal return + for _, ch := range c.chans { + ch <- Result{c.val, c.err, c.dups > 0} + } + } + }() + + func() { + defer func() { + if !normalReturn { + // Ideally, we would wait to take a stack trace until we've determined + // whether this is a panic or a runtime.Goexit. + // + // Unfortunately, the only way we can distinguish the two is to see + // whether the recover stopped the goroutine from terminating, and by + // the time we know that, the part of the stack trace relevant to the + // panic has been discarded. + if r := recover(); r != nil { + c.err = newPanicError(r) + } + } + }() + + c.val, c.err = fn() + normalReturn = true + }() + + if !normalReturn { + recovered = true + } +} + +// Forget tells the singleflight to forget about a key. Future calls +// to Do for this key will call the function rather than waiting for +// an earlier call to complete. +func (g *Group) Forget(key string) { + g.mu.Lock() + delete(g.m, key) + g.mu.Unlock() +} diff --git a/src/vendor/modules.txt b/src/vendor/modules.txt index 408846e6254..61be24ea266 100644 --- a/src/vendor/modules.txt +++ b/src/vendor/modules.txt @@ -699,6 +699,7 @@ golang.org/x/oauth2/jwt # golang.org/x/sync v0.3.0 ## explicit; go 1.17 golang.org/x/sync/errgroup +golang.org/x/sync/singleflight # golang.org/x/sys v0.7.0 ## explicit; go 1.17 golang.org/x/sys/internal/unsafeheader From cbb211e6702517fddcc8b9a7341e8a3479211ca7 Mon Sep 17 00:00:00 2001 From: Yang Jiao <72076317+YangJiao0817@users.noreply.github.com> Date: Mon, 10 Jul 2023 15:05:56 +0800 Subject: [PATCH 33/38] Add CloudEvents format webhook testcase (#18908) Fix #18616 Signed-off-by: Yang Jiao --- .../Harbor-Pages/Project-Webhooks.robot | 10 +- .../Project-Webhooks_Elements.robot | 1 + tests/resources/TestCaseBody.robot | 124 ++++++++++++++--- tests/resources/Webhook-Util.robot | 25 +++- .../robot-cases/Group1-Nightly/Webhook.robot | 125 ++++++++++++++++-- 5 files changed, 247 insertions(+), 38 deletions(-) diff --git a/tests/resources/Harbor-Pages/Project-Webhooks.robot b/tests/resources/Harbor-Pages/Project-Webhooks.robot index 9e75e14ab89..234fdfeb339 100644 --- a/tests/resources/Harbor-Pages/Project-Webhooks.robot +++ b/tests/resources/Harbor-Pages/Project-Webhooks.robot @@ -9,15 +9,20 @@ Switch To Project Webhooks Retry Element Click xpath=//project-detail//a[contains(.,'Webhooks')] Create A New Webhook - [Arguments] ${webhook_name} ${webhook_endpoint_url} ${event_type}=@{EMPTY} + [Arguments] ${webhook_name} ${webhook_endpoint_url} ${payload_format}=Default ${event_type}=@{EMPTY} Retry Element Click ${new_webhook_button_xpath} Retry Text Input ${webhook_name_xpath} ${webhook_name} Retry Text Input ${webhook_endpoint_id_xpath} ${webhook_endpoint_url} + Run Keyword If '${payload_format}' != 'Default' Select Payload Format ${payload_format} ${len}= Get Length ${event_type} Run Keyword If ${len} > 0 Select Event Type @{event_type} Retry Double Keywords When Error Retry Element Click ${create_webhooks_continue_button_xpath} Retry Wait Until Page Not Contains Element ${create_webhooks_continue_button_xpath} Retry Wait Until Page Contains ${webhook_name} +Select Payload Format + [Arguments] ${payload_format} + Retry Double Keywords When Error Retry Element Click ${webhook_payload_format_xpath} Retry Element Click ${webhook_payload_format_xpath}//option[@value='${payload_format}'] + Select Event Type [Arguments] @{event_type} ${elements}= Get WebElements //form//div[contains(@class,'clr-control-inline')]//label @@ -29,7 +34,7 @@ Select Event Type END Update A Webhook - [Arguments] ${old_webhook_name} ${new_webhook_name} ${new_webhook_enpoint} + [Arguments] ${old_webhook_name} ${new_webhook_name} ${new_webhook_enpoint} ${payload_format}=Default # select one webhook Retry Element Click xpath=//clr-dg-row[contains(.,'${old_webhook_name}')]//div[contains(@class,'datagrid-select')] Retry Element Click ${action_webhook_xpath} @@ -42,6 +47,7 @@ Update A Webhook Retry Element Click ${action_webhook_edit_button} Retry Text Input ${webhook_name_xpath} ${new_webhook_name} Retry Text Input ${webhook_endpoint_id_xpath} ${new_webhook_enpoint} + Select Payload Format ${payload_format} Retry Double Keywords When Error Retry Element Click ${edit_webhooks_save_button_xpath} Retry Wait Until Page Not Contains Element ${edit_webhooks_save_button_xpath} Retry Wait Until Page Contains ${new_webhook_name} diff --git a/tests/resources/Harbor-Pages/Project-Webhooks_Elements.robot b/tests/resources/Harbor-Pages/Project-Webhooks_Elements.robot index 9a61d753954..b792f0e6e9d 100644 --- a/tests/resources/Harbor-Pages/Project-Webhooks_Elements.robot +++ b/tests/resources/Harbor-Pages/Project-Webhooks_Elements.robot @@ -5,6 +5,7 @@ Documentation This resource provides any keywords related to the Harbor private ${new_webhook_button_xpath} xpath=//*[@id='new-webhook'] ${webhook_name_xpath} xpath=//*[@id='name'] ${webhook_endpoint_id_xpath} xpath=//*[@id='edit_endpoint_url'] +${webhook_payload_format_xpath} xpath=//*[@id='payload-format'] ${webhook_auth_header_xpath} xpath=//*[@id='auth_header'] ${action_webhook_xpath} xpath=//*[@id='action-webhook'] ${action_webhook_edit_button} xpath=//*[@id='edit-webhook'] diff --git a/tests/resources/TestCaseBody.robot b/tests/resources/TestCaseBody.robot index 61c77389612..fda86c095df 100644 --- a/tests/resources/TestCaseBody.robot +++ b/tests/resources/TestCaseBody.robot @@ -400,62 +400,107 @@ Prepare Image Package Test Files ${rc} ${output}= Run And Return Rc And Output bash tests/robot-cases/Group0-Util/prepare_imgpkg_test_files.sh ${files_path} Verify Webhook By Artifact Pushed Event - [Arguments] ${project_name} ${image} ${tag} ${user} ${pwd} ${webhook_handle} + [Arguments] ${project_name} ${webhook_name} ${image} ${tag} ${user} ${pwd} ${harbor_handle} ${webhook_handle} ${payload_format}=Default + &{artifact_pushed_property}= Create Dictionary + Run Keyword If '${payload_format}' == 'Default' Set To Dictionary ${artifact_pushed_property} type=PUSH_ARTIFACT operator=${user} namespace=${project_name} name=${image} tag=${tag} + ... ELSE Set To Dictionary ${artifact_pushed_property} specversion=1.0 type=harbor.artifact.pushed datacontenttype=application/json namespace=${project_name} name=${image} repo_full_name=${project_name}/${image} tag=${tag} operator=${user} Switch Window ${webhook_handle} Delete All Requests Push Image With Tag ${ip} ${user} ${pwd} ${project_name} ${image} ${tag} - &{artifact_pushed_property}= Create Dictionary type=PUSH_ARTIFACT operator=${user} namespace=${project_name} name=${image} tag=${tag} + Switch Window ${harbor_handle} + Retry Element Click xpath=//clr-dg-row[contains(.,'${webhook_name}')]//div[contains(@class,'datagrid-select')] + ${webhook_execution_id}= Get Latest Webhook Execution ID + Retry Action Keyword Verify Webhook Execution ${webhook_execution_id} WEBHOOK Success Artifact pushed ${artifact_pushed_property} + Verify Webhook Execution Log ${webhook_execution_id} + Switch Window ${webhook_handle} Verify Request &{artifact_pushed_property} Clean All Local Images Verify Webhook By Artifact Pulled Event - [Arguments] ${project_name} ${image} ${tag} ${user} ${pwd} ${webhook_handle} + [Arguments] ${project_name} ${webhook_name} ${image} ${tag} ${user} ${pwd} ${harbor_handle} ${webhook_handle} ${payload_format}=Default + &{artifact_pulled_property}= Create Dictionary + Run Keyword If '${payload_format}' == 'Default' Set To Dictionary ${artifact_pulled_property} type=PULL_ARTIFACT operator=${user} namespace=${project_name} name=${image} + ... ELSE Set To Dictionary ${artifact_pulled_property} specversion=1.0 type=harbor.artifact.pulled datacontenttype=application/json namespace=${project_name} name=${image} repo_full_name=${project_name}/${image} operator=${user} Switch Window ${webhook_handle} Delete All Requests Clean All Local Images Docker Login ${ip} ${user} ${pwd} Docker Pull ${ip}/${project_name}/${image}:${tag} Docker Logout ${ip} - &{artifact_pulled_property}= Create Dictionary type=PULL_ARTIFACT operator=${user} namespace=${project_name} name=${image} + Switch Window ${harbor_handle} + Go Into Project ${project_name} + Switch To Project Webhooks + Retry Element Click xpath=//clr-dg-row[contains(.,'${webhook_name}')]//div[contains(@class,'datagrid-select')] + ${webhook_execution_id}= Get Latest Webhook Execution ID + Retry Action Keyword Verify Webhook Execution ${webhook_execution_id} WEBHOOK Success Artifact pulled ${artifact_pulled_property} + Verify Webhook Execution Log ${webhook_execution_id} + Switch Window ${webhook_handle} Verify Request &{artifact_pulled_property} Verify Webhook By Artifact Deleted Event - [Arguments] ${project_name} ${image} ${tag} ${user} ${harbor_handle} ${webhook_handle} + [Arguments] ${project_name} ${webhook_name} ${image} ${tag} ${user} ${harbor_handle} ${webhook_handle} ${payload_format}=Default + &{artifact_deleted_property}= Create Dictionary + Run Keyword If '${payload_format}' == 'Default' Set To Dictionary ${artifact_deleted_property} type=DELETE_ARTIFACT operator=${user} namespace=${project_name} name=${image} tag=${tag} + ... ELSE Set To Dictionary ${artifact_deleted_property} specversion=1.0 type=harbor.artifact.deleted datacontenttype=application/json namespace=${project_name} name=${image} repo_full_name=${project_name}/${image} tag=${tag} operator=${user} Switch Window ${webhook_handle} Delete All Requests Switch Window ${harbor_handle} Go Into Repo ${project_name} ${image} @{tag_list} Create List ${tag} Multi-delete Artifact @{tag_list} + Go Into Project ${project_name} + Switch To Project Webhooks + Retry Element Click xpath=//clr-dg-row[contains(.,'${webhook_name}')]//div[contains(@class,'datagrid-select')] + ${webhook_execution_id}= Get Latest Webhook Execution ID + Retry Action Keyword Verify Webhook Execution ${webhook_execution_id} WEBHOOK Success Artifact deleted ${artifact_deleted_property} + Verify Webhook Execution Log ${webhook_execution_id} Switch Window ${webhook_handle} - &{artifact_deleted_property}= Create Dictionary type=DELETE_ARTIFACT operator=${user} namespace=${project_name} name=${image} tag=${tag} Verify Request &{artifact_deleted_property} Verify Webhook By Scanning Finished Event - [Arguments] ${project_name} ${image} ${tag} ${harbor_handle} ${webhook_handle} + [Arguments] ${project_name} ${webhook_name} ${image} ${tag} ${harbor_handle} ${webhook_handle} ${payload_format}=Default + &{scanning_finished_property}= Create Dictionary + Run Keyword If '${payload_format}' == 'Default' Set To Dictionary ${scanning_finished_property} type=SCANNING_COMPLETED scan_status=Success namespace=${project_name} tag=${tag} name=${image} + ... ELSE Set To Dictionary ${scanning_finished_property} specversion=1.0 type=harbor.scan.completed datacontenttype=application/json namespace=${project_name} name=${image} repo_full_name=${project_name}/${image} tag=${tag} scan_status=Success Switch Window ${webhook_handle} Delete All Requests Switch Window ${harbor_handle} Go Into Repo ${project_name} ${image} Scan Repo ${tag} Succeed + Go Into Project ${project_name} + Switch To Project Webhooks + Retry Element Click xpath=//clr-dg-row[contains(.,'${webhook_name}')]//div[contains(@class,'datagrid-select')] + ${webhook_execution_id}= Get Latest Webhook Execution ID + Retry Action Keyword Verify Webhook Execution ${webhook_execution_id} WEBHOOK Success Scanning finished ${scanning_finished_property} + Verify Webhook Execution Log ${webhook_execution_id} Switch Window ${webhook_handle} - &{scanning_finished_property}= Create Dictionary type=SCANNING_COMPLETED scan_status=Success namespace=${project_name} tag=${tag} name=${image} Verify Request &{scanning_finished_property} Verify Webhook By Scanning Stopped Event - [Arguments] ${project_name} ${image} ${tag} ${harbor_handle} ${webhook_handle} + [Arguments] ${project_name} ${webhook_name} ${image} ${tag} ${harbor_handle} ${webhook_handle} ${payload_format}=Default + &{scanning_stopped_property}= Create Dictionary + Run Keyword If '${payload_format}' == 'Default' Set To Dictionary ${scanning_stopped_property} type=SCANNING_STOPPED scan_status=Stopped namespace=${project_name} tag=${tag} name=${image} + ... ELSE Set To Dictionary ${scanning_stopped_property} specversion=1.0 type=harbor.scan.stopped datacontenttype=application/json namespace=${project_name} name=${image} repo_full_name=${project_name}/${image} tag=${tag} scan_status=Stopped Switch Window ${webhook_handle} Delete All Requests Switch Window ${harbor_handle} Scan Artifact ${project_name} ${image} Stop Scan Artifact Check Scan Artifact Job Status Is Stopped + Go Into Project ${project_name} + Switch To Project Webhooks + Retry Element Click xpath=//clr-dg-row[contains(.,'${webhook_name}')]//div[contains(@class,'datagrid-select')] + ${webhook_execution_id}= Get Latest Webhook Execution ID + Retry Action Keyword Verify Webhook Execution ${webhook_execution_id} WEBHOOK Success Scanning stopped ${scanning_stopped_property} + Verify Webhook Execution Log ${webhook_execution_id} Switch Window ${webhook_handle} - &{scanning_stopped_property}= Create Dictionary type=SCANNING_STOPPED scan_status=Stopped namespace=${project_name} tag=${tag} name=${image} Verify Request &{scanning_stopped_property} Verify Webhook By Tag Retention Finished Event - [Arguments] ${project_name} ${image} ${tag1} ${tag2} ${harbor_handle} ${webhook_handle} + [Arguments] ${project_name} ${webhook_name} ${image} ${tag1} ${tag2} ${harbor_handle} ${webhook_handle} ${payload_format}=Default + &{tag_retention_finished_property}= Create Dictionary + Run Keyword If '${payload_format}' == 'Default' Set To Dictionary ${tag_retention_finished_property} type=TAG_RETENTION operator=MANUAL project_name=${project_name} name_tag=${image}:${tag2} status=SUCCESS + ... ELSE Set To Dictionary ${tag_retention_finished_property} specversion=1.0 type=harbor.tag_retention.finished datacontenttype=application/json project_name=${project_name} name_tag=${image}:${tag2} status=SUCCESS Switch Window ${webhook_handle} Delete All Requests Push Image With Tag ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} ${project_name} ${image} ${tag1} ${tag1} @@ -464,24 +509,39 @@ Verify Webhook By Tag Retention Finished Event Go Into Project ${project_name} Switch To Tag Retention Execute Run ${image} + Go Into Project ${project_name} + Switch To Project Webhooks + Retry Element Click xpath=//clr-dg-row[contains(.,'${webhook_name}')]//div[contains(@class,'datagrid-select')] + ${webhook_execution_id}= Get Latest Webhook Execution ID + Retry Action Keyword Verify Webhook Execution ${webhook_execution_id} WEBHOOK Success Tag retention finished ${tag_retention_finished_property} + Verify Webhook Execution Log ${webhook_execution_id} Switch Window ${webhook_handle} - &{tag_retention_finished_property}= Create Dictionary type=TAG_RETENTION operator=MANUAL project_name=${project_name} name_tag=${image}:${tag2} status=SUCCESS Verify Request &{tag_retention_finished_property} + Wait Until Page Contains "total":2 + Wait Until Page Contains "retained":1 Verify Webhook By Replication Status Changed Event - [Arguments] ${project_name} ${project_dest_name} ${replication_rule_name} ${harbor_handle} ${webhook_handle} + [Arguments] ${project_name} ${webhook_name} ${project_dest_name} ${replication_rule_name} ${harbor_handle} ${webhook_handle} ${payload_format}=Default + &{replication_finished_property}= Create Dictionary + Run Keyword If '${payload_format}' == 'Default' Set To Dictionary ${replication_finished_property} type=REPLICATION operator=MANUAL registry_type=harbor harbor_hostname=${ip} + ... ELSE Set To Dictionary ${replication_finished_property} specversion=1.0 type=harbor.replication.status.changed datacontenttype=application/json trigger_type=MANUAL namespace=${project_name} Switch Window ${webhook_handle} Delete All Requests Switch Window ${harbor_handle} Switch To Replication Manage Select Rule And Replicate ${replication_rule_name} Check Latest Replication Job Status Succeeded + Go Into Project ${project_name} + Switch To Project Webhooks + Retry Element Click xpath=//clr-dg-row[contains(.,'${webhook_name}')]//div[contains(@class,'datagrid-select')] + ${webhook_execution_id}= Get Latest Webhook Execution ID + Retry Action Keyword Verify Webhook Execution ${webhook_execution_id} WEBHOOK Success Replication status changed ${replication_finished_property} + Verify Webhook Execution Log ${webhook_execution_id} Switch Window ${webhook_handle} - &{replication_finished_property}= Create Dictionary type=REPLICATION operator=MANUAL registry_type=harbor harbor_hostname=${ip} Verify Request &{replication_finished_property} Verify Webhook By Quota Near Threshold Event And Quota Exceed Event - [Arguments] ${webhook_endpoint_url} ${harbor_handle} ${webhook_handle} + [Arguments] ${webhook_endpoint_url} ${harbor_handle} ${webhook_handle} ${payload_format}=Default ${d}= Get Current Date result_format=%m%s ${image}= Set Variable nginx ${tag1}= Set Variable 1.17.6 @@ -490,26 +550,46 @@ Verify Webhook By Quota Near Threshold Event And Quota Exceed Event Create An New Project And Go Into Project project${d} storage_quota=${storage_quota} storage_quota_unit=MiB Switch To Project Webhooks ${event_type} Create List Quota near threshold - Create A New Webhook webhook${d} ${webhook_endpoint_url} ${event_type} + Create A New Webhook webhook${d} ${webhook_endpoint_url} ${payload_format} ${event_type} + &{quota_near_threshold_property}= Create Dictionary + Run Keyword If '${payload_format}' == 'Default' Set To Dictionary ${quota_near_threshold_property} type=QUOTA_WARNING name=nginx namespace=project${d} + ... ELSE Set To Dictionary ${quota_near_threshold_property} specversion=1.0 type=harbor.quota.warned datacontenttype=application/json name=${image} repo_full_name=project${d}/${image} namespace=project${d} Switch Window ${webhook_handle} Delete All Requests # Quota near threshold Push Image With Tag ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} project${d} ${image} ${tag1} ${tag1} - &{quota_near_threshold_property}= Create Dictionary type=QUOTA_WARNING name=nginx namespace=project${d} + Switch Window ${harbor_handle} + Retry Element Click xpath=//clr-dg-row[contains(.,'webhook${d}')]//div[contains(@class,'datagrid-select')] + ${webhook_execution_id}= Get Latest Webhook Execution ID + Retry Action Keyword Verify Webhook Execution ${webhook_execution_id} WEBHOOK Success Quota near threshold ${quota_near_threshold_property} + Verify Webhook Execution Log ${webhook_execution_id} + Switch Window ${webhook_handle} Verify Request &{quota_near_threshold_property} - Retry Action Keyword Verify Webhook By Quota Exceed Event project${d} webhook${d} ${image} ${tag2} ${webhook_endpoint_url} ${storage_quota} ${harbor_handle} ${webhook_handle} + Retry Action Keyword Verify Webhook By Quota Exceed Event project${d} webhook${d} ${image} ${tag2} ${webhook_endpoint_url} ${storage_quota} ${harbor_handle} ${webhook_handle} ${payload_format} Verify Webhook By Quota Exceed Event - [Arguments] ${project_name} ${webhook_name} ${image} ${tag} ${webhook_endpoint_url} ${storage_quota} ${harbor_handle} ${webhook_handle} + [Arguments] ${project_name} ${webhook_name} ${image} ${tag} ${webhook_endpoint_url} ${storage_quota} ${harbor_handle} ${webhook_handle} ${payload_format}=Default + &{quota_exceed_property}= Create Dictionary + Run Keyword If '${payload_format}' == 'Default' Set To Dictionary ${quota_exceed_property} type=QUOTA_EXCEED name=${image} namespace=${project_name} + ... ELSE Set To Dictionary ${quota_exceed_property} specversion=1.0 type=harbor.quota.exceeded datacontenttype=application/json name=${image} repo_full_name=${project_name}/${image} namespace=${project_name} # Quota exceed Switch Window ${harbor_handle} + Go Into Project ${project_name} + Switch To Project Webhooks Delete A Webhook ${webhook_name} ${event_type} Create List Quota exceed - Create A New Webhook ${webhook_name} ${webhook_endpoint_url} ${event_type} + Create A New Webhook ${webhook_name} ${webhook_endpoint_url} ${payload_format} ${event_type} Switch Window ${webhook_handle} Delete All Requests - Cannot Push image ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} ${project_name} ${image}:${tag} err_msg=adding 21.1 MiB of storage resource, which when updated to current usage of 48.5 MiB will exceed the configured upper limit of ${storage_quota}.0 MiB. - &{quota_exceed_property}= Create Dictionary type=QUOTA_EXCEED name=${image} namespace=${project_name} + Cannot Push image ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} ${project_name} ${image}:${tag} + Switch Window ${harbor_handle} + Go Into Project ${project_name} + Switch To Project Webhooks + Retry Element Click xpath=//clr-dg-row[contains(.,'${webhook_name}')]//div[contains(@class,'datagrid-select')] + ${webhook_execution_id}= Get Latest Webhook Execution ID + Retry Action Keyword Verify Webhook Execution ${webhook_execution_id} WEBHOOK Success Quota exceed ${quota_exceed_property} + Verify Webhook Execution Log ${webhook_execution_id} + Switch Window ${webhook_handle} Verify Request &{quota_exceed_property} Create Schedules For Job Service Dashboard Schedules diff --git a/tests/resources/Webhook-Util.robot b/tests/resources/Webhook-Util.robot index 892328f0e43..9c5364b8864 100644 --- a/tests/resources/Webhook-Util.robot +++ b/tests/resources/Webhook-Util.robot @@ -20,10 +20,31 @@ Library Process *** Keywords *** Delete All Requests Sleep 3 - Run Keyword And Ignore Error Button Click //button[contains(., 'Delete all requests')] + Run Keyword And Ignore Error Click button //button[contains(., 'Delete all requests')] Verify Request [Arguments] &{property} FOR ${key} IN @{property.keys()} Wait Until Page Contains "${key}":"${property['${key}']}" - END \ No newline at end of file + END + +Get Latest Webhook Execution ID + ${execution_id}= Get Text //clr-dg-row[1]//clr-dg-cell[1]//a + [Return] ${execution_id} + +Verify Webhook Execution + [Arguments] ${execution_id} ${vendor_type} ${status} ${event_type} ${payload_data} + Retry Wait Until Page Contains Element //clr-dg-row[.//clr-dg-cell/a[text()=${execution_id}]]//clr-dg-cell[3][contains(.,'${status}')] + Wait Until Page Contains Element //clr-dg-row[.//clr-dg-cell/a[text()=${execution_id}]]//clr-dg-cell[2][contains(.,'WEBHOOK')] + Wait Until Page Contains Element //clr-dg-row[.//clr-dg-cell/a[text()=${execution_id}]]//clr-dg-cell[4][contains(.,'${event_type}')] + Retry Element Click //clr-dg-row[.//clr-dg-cell/a[text()=${execution_id}]]//clr-dg-cell[5] + FOR ${key} IN @{payload_data.keys()} + Wait Until Page Contains "${key}": "${payload_data['${key}']}" + END + +Verify Webhook Execution Log + [Arguments] ${execution_id} ${log}=success to run webhook job + Retry Link Click //clr-dg-row//clr-dg-cell/a[text()=${execution_id}] + Retry Link Click //clr-dg-row[1]//clr-dg-cell[5]//a + Switch Window locator=NEW + Wait Until Page Contains ${log} diff --git a/tests/robot-cases/Group1-Nightly/Webhook.robot b/tests/robot-cases/Group1-Nightly/Webhook.robot index 4cd9c885555..20b9478d3fb 100644 --- a/tests/robot-cases/Group1-Nightly/Webhook.robot +++ b/tests/robot-cases/Group1-Nightly/Webhook.robot @@ -31,8 +31,8 @@ Test Case - Webhook CRUD Switch To Project Webhooks # create more than one webhooks Create A New Webhook webhook${d} https://test.com - Create A New Webhook webhook2${d} https://test2.com - Update A Webhook webhook${d} newWebhook${d} https://new-test.com + Create A New Webhook webhook2${d} https://test2.com CloudEvents + Update A Webhook webhook${d} newWebhook${d} https://new-test.com CloudEvents Enable/Deactivate State of Same Webhook newWebhook${d} Delete A Webhook newWebhook${d} Close Browser @@ -50,16 +50,16 @@ Test Case - Artifact Event Type Webhook Functionality Create An New Project And Go Into Project project${d} Switch To Project Webhooks ${event_type} Create List Artifact deleted Artifact pulled Artifact pushed - Create A New Webhook webhook${d} ${webhook_endpoint_url} ${event_type} + Create A New Webhook webhook${d} ${webhook_endpoint_url} Default ${event_type} ${handles}= Get Window Handles ${webhook_handle}= Set Variable ${handles}[0] ${harbor_handle}= Set Variable ${handles}[1] # Artifact pushed - Retry Action Keyword Verify Webhook By Artifact Pushed Event project${d} ${image} ${tag} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} ${webhook_handle} + Retry Action Keyword Verify Webhook By Artifact Pushed Event project${d} webhook${d} ${image} ${tag} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} ${harbor_handle} ${webhook_handle} # Artifact pulled - Retry Action Keyword Verify Webhook By Artifact Pulled Event project${d} ${image} ${tag} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} ${webhook_handle} + Retry Action Keyword Verify Webhook By Artifact Pulled Event project${d} webhook${d} ${image} ${tag} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} ${harbor_handle} ${webhook_handle} # Artifact deleted - Retry Action Keyword Verify Webhook By Artifact Deleted Event project${d} ${image} ${tag} ${HARBOR_ADMIN} ${harbor_handle} ${webhook_handle} + Retry Action Keyword Verify Webhook By Artifact Deleted Event project${d} webhook${d} ${image} ${tag} ${HARBOR_ADMIN} ${harbor_handle} ${webhook_handle} Close Browser Test Case - Scan Event Type Webhook Functionality @@ -79,14 +79,14 @@ Test Case - Scan Event Type Webhook Functionality Push Image With Tag ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} project${d} ${image2} ${tag2} ${tag2} Switch To Project Webhooks ${event_type} Create List Scanning finished Scanning stopped - Create A New Webhook webhook${d} ${webhook_endpoint_url} ${event_type} + Create A New Webhook webhook${d} ${webhook_endpoint_url} Default ${event_type} ${handles}= Get Window Handles ${webhook_handle}= Set Variable ${handles}[0] ${harbor_handle}= Set Variable ${handles}[1] # Scanning finished - Retry Action Keyword Verify Webhook By Scanning Finished Event project${d} ${image1} ${tag1} ${harbor_handle} ${webhook_handle} + Retry Action Keyword Verify Webhook By Scanning Finished Event project${d} webhook${d} ${image1} ${tag1} ${harbor_handle} ${webhook_handle} # Scanning stopped - Retry Action Keyword Verify Webhook By Scanning Stopped Event project${d} ${image2} ${tag2} ${harbor_handle} ${webhook_handle} + Retry Action Keyword Verify Webhook By Scanning Stopped Event project${d} webhook${d} ${image2} ${tag2} ${harbor_handle} ${webhook_handle} Close Browser Test Case - Tag Retention And Replication Event Type Webhook Functionality @@ -107,7 +107,7 @@ Test Case - Tag Retention And Replication Event Type Webhook Functionality Edit A Tag Retention Rule ** ${tag1} Switch To Project Webhooks ${event_type} Create List Tag retention finished Replication status changed - Create A New Webhook webhook${d} ${webhook_endpoint_url} ${event_type} + Create A New Webhook webhook${d} ${webhook_endpoint_url} Default ${event_type} Switch To Registries Create A New Endpoint harbor e${d} https://${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} Switch To Replication Manage @@ -116,9 +116,9 @@ Test Case - Tag Retention And Replication Event Type Webhook Functionality ${webhook_handle}= Set Variable ${handles}[0] ${harbor_handle}= Set Variable ${handles}[1] # Tag retention finished - Retry Action Keyword Verify Webhook By Tag Retention Finished Event project${d} ${image} ${tag1} ${tag2} ${harbor_handle} ${webhook_handle} + Retry Action Keyword Verify Webhook By Tag Retention Finished Event project${d} webhook${d} ${image} ${tag1} ${tag2} ${harbor_handle} ${webhook_handle} # Replication status changed - Retry Action Keyword Verify Webhook By Replication Status Changed Event project${d} project_push_dest${d} rule_push_${d} ${harbor_handle} ${webhook_handle} + Retry Action Keyword Verify Webhook By Replication Status Changed Event project${d} webhook${d} project_push_dest${d} rule_push_${d} ${harbor_handle} ${webhook_handle} Close Browser Test Case - Tag Quota Event Type Webhook Functionality @@ -133,3 +133,104 @@ Test Case - Tag Quota Event Type Webhook Functionality ${harbor_handle}= Set Variable ${handles}[1] Retry Action Keyword Verify Webhook By Quota Near Threshold Event And Quota Exceed Event ${webhook_endpoint_url} ${harbor_handle} ${webhook_handle} Close Browser + +Test Case - Artifact Event Type Webhook Functionality By CloudEvents Format + [Tags] artifact_webhook_cloudevents need_webhook_endpoint + Init Chrome Driver + ${image}= Set Variable busybox + ${tag}= Set Variable latest + ${payload_format}= Set Variable CloudEvents + ${d}= Get Current Date result_format=%m%s + Go To http://${WEBHOOK_ENDPOINT} + ${webhook_endpoint_url}= Get Text //p//code + New Tab + Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} + Create An New Project And Go Into Project project${d} + Switch To Project Webhooks + ${event_type} Create List Artifact deleted Artifact pulled Artifact pushed + Create A New Webhook webhook${d} ${webhook_endpoint_url} ${payload_format} ${event_type} + ${handles}= Get Window Handles + ${webhook_handle}= Set Variable ${handles}[0] + ${harbor_handle}= Set Variable ${handles}[1] + # Artifact pushed + Retry Action Keyword Verify Webhook By Artifact Pushed Event project${d} webhook${d} ${image} ${tag} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} ${harbor_handle} ${webhook_handle} ${payload_format} + # Artifact pulled + Retry Action Keyword Verify Webhook By Artifact Pulled Event project${d} webhook${d} ${image} ${tag} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} ${harbor_handle} ${webhook_handle} ${payload_format} + # Artifact deleted + Retry Action Keyword Verify Webhook By Artifact Deleted Event project${d} webhook${d} ${image} ${tag} ${HARBOR_ADMIN} ${harbor_handle} ${webhook_handle} ${payload_format} + Close Browser + +Test Case - Scan Event Type Webhook Functionality By CloudEvents Format + [Tags] scan_webhook_cloudevents need_webhook_endpoint + Init Chrome Driver + ${image1}= Set Variable busybox + ${tag1}= Set Variable latest + ${image2}= Set Variable goharbor/harbor-e2e-engine + ${tag2}= Set Variable 5.0.0-api + ${payload_format}= Set Variable CloudEvents + ${d}= Get Current Date result_format=%m%s + Go To http://${WEBHOOK_ENDPOINT} + ${webhook_endpoint_url}= Get Text //p//code + New Tab + Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} + Create An New Project And Go Into Project project${d} + Push Image With Tag ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} project${d} ${image1} ${tag1} ${tag1} + Push Image With Tag ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} project${d} ${image2} ${tag2} ${tag2} + Switch To Project Webhooks + ${event_type} Create List Scanning finished Scanning stopped + Create A New Webhook webhook${d} ${webhook_endpoint_url} ${payload_format} ${event_type} + ${handles}= Get Window Handles + ${webhook_handle}= Set Variable ${handles}[0] + ${harbor_handle}= Set Variable ${handles}[1] + # Scanning finished + Retry Action Keyword Verify Webhook By Scanning Finished Event project${d} webhook${d} ${image1} ${tag1} ${harbor_handle} ${webhook_handle} ${payload_format} + # Scanning stopped + Retry Action Keyword Verify Webhook By Scanning Stopped Event project${d} webhook${d} ${image2} ${tag2} ${harbor_handle} ${webhook_handle} ${payload_format} + Close Browser + +Test Case - Tag Retention And Replication Event Type Webhook Functionality By CloudEvents Format + [Tags] tag_retention_replication_webhook_cloudevents need_webhook_endpoint + Init Chrome Driver + ${image}= Set Variable busybox + ${tag1}= Set Variable latest + ${tag2}= Set Variable stable + ${payload_format}= Set Variable CloudEvents + ${d}= Get Current Date result_format=%m%s + Go To http://${WEBHOOK_ENDPOINT} + ${webhook_endpoint_url}= Get Text //p//code + New Tab + Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} + Create An New Project And Go Into Project project_dest${d} + Create An New Project And Go Into Project project${d} + Switch To Tag Retention + Add A Tag Retention Rule + Edit A Tag Retention Rule ** ${tag1} + Switch To Project Webhooks + ${event_type} Create List Tag retention finished Replication status changed + Create A New Webhook webhook${d} ${webhook_endpoint_url} ${payload_format} ${event_type} + Switch To Registries + Create A New Endpoint harbor e${d} https://${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} + Switch To Replication Manage + Create A Rule With Existing Endpoint rule_push_${d} push project${d}/* image e${d} project_push_dest${d} + ${handles}= Get Window Handles + ${webhook_handle}= Set Variable ${handles}[0] + ${harbor_handle}= Set Variable ${handles}[1] + # Tag retention finished + Retry Action Keyword Verify Webhook By Tag Retention Finished Event project${d} webhook${d} ${image} ${tag1} ${tag2} ${harbor_handle} ${webhook_handle} ${payload_format} + # Replication status changed + Retry Action Keyword Verify Webhook By Replication Status Changed Event project${d} webhook${d} project_push_dest${d} rule_push_${d} ${harbor_handle} ${webhook_handle} ${payload_format} + Close Browser + +Test Case - Tag Quota Event Type Webhook Functionality By CloudEvents Format + [Tags] quota_webhook_cloudevents need_webhook_endpoint + ${payload_format}= Set Variable CloudEvents + Init Chrome Driver + Go To http://${WEBHOOK_ENDPOINT} + ${webhook_endpoint_url}= Get Text //p//code + New Tab + Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} + ${handles}= Get Window Handles + ${webhook_handle}= Set Variable ${handles}[0] + ${harbor_handle}= Set Variable ${handles}[1] + Retry Action Keyword Verify Webhook By Quota Near Threshold Event And Quota Exceed Event ${webhook_endpoint_url} ${harbor_handle} ${webhook_handle} ${payload_format} + Close Browser From 98f592f94fe5c11ec63d3ad5707fbdef957d3aee Mon Sep 17 00:00:00 2001 From: Chlins Zhang Date: Tue, 11 Jul 2023 10:21:12 +0800 Subject: [PATCH 34/38] chore: upgrade golang-migrate to v4.16.2 (#18879) Signed-off-by: chlins --- src/go.mod | 33 +- src/go.sum | 841 ++---------------- .../github.com/golang-jwt/jwt/v4/README.md | 30 +- .../github.com/golang-jwt/jwt/v4/SECURITY.md | 19 + .../github.com/golang-jwt/jwt/v4/claims.go | 12 +- .../github.com/golang-jwt/jwt/v4/errors.go | 48 + .../golang-jwt/jwt/v4/map_claims.go | 3 + .../golang-jwt/jwt/v4/parser_option.go | 4 +- .../github.com/golang-jwt/jwt/v4/rsa_pss.go | 1 + .../github.com/golang-jwt/jwt/v4/token.go | 26 +- .../github.com/golang-jwt/jwt/v4/types.go | 24 +- .../golang-migrate/migrate/v4/.goreleaser.yml | 1 + .../golang-migrate/migrate/v4/Dockerfile | 4 +- .../migrate/v4/Dockerfile.github-actions | 4 +- .../migrate/v4/GETTING_STARTED.md | 6 +- .../golang-migrate/migrate/v4/Makefile | 2 +- .../golang-migrate/migrate/v4/README.md | 16 +- .../golang-migrate/migrate/v4/SECURITY.md | 16 + .../migrate/v4/database/driver.go | 28 +- .../migrate/v4/database/pgx/README.md | 2 + .../migrate/v4/database/pgx/pgx.go | 11 +- .../migrate/v4/source/driver.go | 26 +- .../migrate/v4/source/migration.go | 18 +- .../golang-migrate/migrate/v4/source/parse.go | 5 +- .../golang-migrate/migrate/v4/util.go | 3 +- .../google/go-querystring/query/encode.go | 97 +- .../github.com/jackc/pgconn/CHANGELOG.md | 43 + src/vendor/github.com/jackc/pgconn/README.md | 6 + .../github.com/jackc/pgconn/auth_scram.go | 20 +- src/vendor/github.com/jackc/pgconn/config.go | 278 ++++-- .../github.com/jackc/pgconn/defaults.go | 1 + src/vendor/github.com/jackc/pgconn/errors.go | 27 + .../internal/ctxwatch/context_watcher.go | 13 +- src/vendor/github.com/jackc/pgconn/krb5.go | 99 +++ src/vendor/github.com/jackc/pgconn/pgconn.go | 212 +++-- .../github.com/jackc/pgconn/stmtcache/lru.go | 20 +- .../github.com/jackc/pgerrcode/errcode.go | 5 +- src/vendor/github.com/jackc/pgerrcode/gen.rb | 4 +- .../github.com/jackc/pgproto3/v2/README.md | 6 + .../jackc/pgproto3/v2/authentication_gss.go | 58 ++ .../v2/authentication_gss_continue.go | 67 ++ .../github.com/jackc/pgproto3/v2/backend.go | 9 + .../jackc/pgproto3/v2/copy_both_response.go | 2 +- .../github.com/jackc/pgproto3/v2/frontend.go | 9 +- .../jackc/pgproto3/v2/function_call.go | 94 ++ .../jackc/pgproto3/v2/gss_response.go | 48 + .../pgproto3/v2/sasl_initial_response.go | 11 +- .../jackc/pgproto3/v2/sasl_response.go | 11 +- .../github.com/jackc/pgtype/.travis.yml | 34 - .../github.com/jackc/pgtype/CHANGELOG.md | 65 ++ src/vendor/github.com/jackc/pgtype/README.md | 6 + src/vendor/github.com/jackc/pgtype/array.go | 2 +- .../github.com/jackc/pgtype/array_type.go | 2 +- src/vendor/github.com/jackc/pgtype/bpchar.go | 21 +- src/vendor/github.com/jackc/pgtype/cidr.go | 12 + src/vendor/github.com/jackc/pgtype/convert.go | 6 +- src/vendor/github.com/jackc/pgtype/date.go | 41 +- .../github.com/jackc/pgtype/enum_type.go | 2 +- src/vendor/github.com/jackc/pgtype/float8.go | 2 +- src/vendor/github.com/jackc/pgtype/hstore.go | 30 +- src/vendor/github.com/jackc/pgtype/inet.go | 60 +- src/vendor/github.com/jackc/pgtype/int2.go | 17 + .../jackc/pgtype/int4_multirange.go | 239 +++++ .../jackc/pgtype/int8_multirange.go | 239 +++++ .../github.com/jackc/pgtype/interval.go | 2 +- src/vendor/github.com/jackc/pgtype/json.go | 4 + .../github.com/jackc/pgtype/json_array.go | 546 ++++++++++++ .../github.com/jackc/pgtype/jsonb_array.go | 29 + src/vendor/github.com/jackc/pgtype/lseg.go | 2 +- src/vendor/github.com/jackc/pgtype/ltree.go | 72 ++ .../github.com/jackc/pgtype/multirange.go | 83 ++ .../github.com/jackc/pgtype/num_multirange.go | 239 +++++ src/vendor/github.com/jackc/pgtype/numeric.go | 151 +++- src/vendor/github.com/jackc/pgtype/pgtype.go | 207 +++-- src/vendor/github.com/jackc/pgtype/record.go | 2 +- .../github.com/jackc/pgtype/record_array.go | 318 +++++++ src/vendor/github.com/jackc/pgtype/text.go | 30 + .../github.com/jackc/pgtype/timestamp.go | 24 +- .../github.com/jackc/pgtype/timestamptz.go | 36 +- .../jackc/pgtype/typed_array.go.erb | 22 +- .../jackc/pgtype/typed_array_gen.sh | 51 +- .../jackc/pgtype/typed_multirange.go.erb | 239 +++++ .../jackc/pgtype/typed_multirange_gen.sh | 8 + src/vendor/github.com/jackc/pgtype/uuid.go | 9 +- .../github.com/jackc/pgx/v4/CHANGELOG.md | 78 ++ src/vendor/github.com/jackc/pgx/v4/README.md | 37 +- src/vendor/github.com/jackc/pgx/v4/batch.go | 63 +- src/vendor/github.com/jackc/pgx/v4/conn.go | 111 +-- .../github.com/jackc/pgx/v4/copy_from.go | 4 +- src/vendor/github.com/jackc/pgx/v4/doc.go | 2 +- .../jackc/pgx/v4/extended_query_builder.go | 31 +- .../pgx/v4/internal/sanitize/sanitize.go | 68 +- .../github.com/jackc/pgx/v4/large_objects.go | 12 +- src/vendor/github.com/jackc/pgx/v4/logger.go | 9 + src/vendor/github.com/jackc/pgx/v4/rows.go | 12 +- .../github.com/jackc/pgx/v4/stdlib/sql.go | 25 +- src/vendor/github.com/jackc/pgx/v4/tx.go | 56 +- src/vendor/github.com/jackc/pgx/v4/values.go | 2 + .../github.com/sirupsen/logrus/README.md | 8 +- .../golang.org/x/crypto/acme/rfc8555.go | 2 +- .../golang.org/x/net/http2/transport.go | 30 +- src/vendor/golang.org/x/sys/unix/mkerrors.sh | 3 +- .../golang.org/x/sys/unix/zerrors_linux.go | 14 + .../golang.org/x/sys/windows/env_windows.go | 6 +- .../golang.org/x/sys/windows/exec_windows.go | 7 +- .../golang.org/x/sys/windows/service.go | 7 + .../golang.org/x/sys/windows/types_windows.go | 6 +- .../x/sys/windows/zsyscall_windows.go | 9 + src/vendor/modules.txt | 40 +- 109 files changed, 4352 insertions(+), 1465 deletions(-) create mode 100644 src/vendor/github.com/golang-jwt/jwt/v4/SECURITY.md create mode 100644 src/vendor/github.com/golang-migrate/migrate/v4/SECURITY.md create mode 100644 src/vendor/github.com/jackc/pgconn/krb5.go create mode 100644 src/vendor/github.com/jackc/pgproto3/v2/authentication_gss.go create mode 100644 src/vendor/github.com/jackc/pgproto3/v2/authentication_gss_continue.go create mode 100644 src/vendor/github.com/jackc/pgproto3/v2/function_call.go create mode 100644 src/vendor/github.com/jackc/pgproto3/v2/gss_response.go delete mode 100644 src/vendor/github.com/jackc/pgtype/.travis.yml create mode 100644 src/vendor/github.com/jackc/pgtype/int4_multirange.go create mode 100644 src/vendor/github.com/jackc/pgtype/int8_multirange.go create mode 100644 src/vendor/github.com/jackc/pgtype/json_array.go create mode 100644 src/vendor/github.com/jackc/pgtype/ltree.go create mode 100644 src/vendor/github.com/jackc/pgtype/multirange.go create mode 100644 src/vendor/github.com/jackc/pgtype/num_multirange.go create mode 100644 src/vendor/github.com/jackc/pgtype/record_array.go create mode 100644 src/vendor/github.com/jackc/pgtype/typed_multirange.go.erb create mode 100644 src/vendor/github.com/jackc/pgtype/typed_multirange_gen.sh diff --git a/src/go.mod b/src/go.mod index 33666794303..57367e4b24d 100644 --- a/src/go.mod +++ b/src/go.mod @@ -16,7 +16,7 @@ require ( github.com/cloudevents/sdk-go/v2 v2.13.0 github.com/coreos/go-oidc/v3 v3.0.0 github.com/dghubble/sling v1.1.0 - github.com/docker/distribution v2.8.1+incompatible + 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 @@ -30,16 +30,16 @@ 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.2.0 - github.com/golang-migrate/migrate/v4 v4.15.1 + github.com/golang-jwt/jwt/v4 v4.4.2 + github.com/golang-migrate/migrate/v4 v4.16.2 github.com/gomodule/redigo v2.0.0+incompatible github.com/google/uuid v1.3.0 github.com/gorilla/csrf v1.6.2 github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.8.0 github.com/graph-gophers/dataloader v5.0.0+incompatible - github.com/jackc/pgconn v1.9.0 - github.com/jackc/pgx/v4 v4.12.0 + github.com/jackc/pgconn v1.14.0 + github.com/jackc/pgx/v4 v4.18.1 github.com/jpillora/backoff v1.0.0 github.com/ncw/swift v1.0.49 // indirect github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 @@ -61,8 +61,8 @@ require ( go.opentelemetry.io/otel/sdk v1.8.0 go.opentelemetry.io/otel/trace v1.14.0 go.uber.org/ratelimit v0.2.0 - golang.org/x/crypto v0.5.0 - golang.org/x/net v0.9.0 + golang.org/x/crypto v0.7.0 + golang.org/x/net v0.10.0 golang.org/x/oauth2 v0.5.0 golang.org/x/sync v0.3.0 golang.org/x/text v0.9.0 @@ -90,7 +90,6 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c // indirect github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect - github.com/Microsoft/go-winio v0.5.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 @@ -110,7 +109,7 @@ require ( github.com/go-stack/stack v1.8.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/google/go-querystring v1.0.0 // 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 github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect @@ -120,12 +119,12 @@ require ( github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgerrcode v0.0.0-20201024163028-a0d42d470451 // indirect + 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.1.1 // indirect - github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect - github.com/jackc/pgtype v1.8.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.2 // 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 github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -145,7 +144,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.0 // indirect + github.com/sirupsen/logrus v1.9.2 // 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 @@ -153,7 +152,7 @@ require ( github.com/stretchr/objx v0.5.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect github.com/vmihailenco/tagparser v0.1.2 // indirect - go.mongodb.org/mongo-driver v1.7.0 // indirect + go.mongodb.org/mongo-driver v1.7.5 // indirect go.opentelemetry.io/contrib v0.22.0 // indirect go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0 // indirect @@ -163,8 +162,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.7.0 // indirect - golang.org/x/term v0.7.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/term v0.8.0 // indirect google.golang.org/api v0.110.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8 // indirect diff --git a/src/go.sum b/src/go.sum index c5fef8f5268..15f011b5dc6 100644 --- a/src/go.sum +++ b/src/go.sum @@ -1,4 +1,3 @@ -bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= @@ -12,7 +11,6 @@ cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bP cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.88.0/go.mod h1:dnKwfYbP9hQhefiUvpbcAyoGSHUrOxR20JVElLiUvEY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -30,32 +28,21 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/spanner v1.24.0/go.mod h1:EZI0yH1D/PrXK0XH9Ba5LGXTXWeqZv0ClOD/19a0Z58= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= -github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-sdk-for-go v37.2.0+incompatible h1:LTdcd2GK+cv+e7yhWCN8S7yf3eblBypKFZsPfKjCQ7E= github.com/Azure/azure-sdk-for-go v37.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A= github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= -github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= -github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/adal v0.9.20 h1:gJ3E98kMpFB1MFqQCvA1yFab8vthOeD4VlFRQULxahg= github.com/Azure/go-autorest/autorest/adal v0.9.20/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= @@ -64,14 +51,12 @@ github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8K github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= github.com/Azure/go-autorest/autorest/to v0.3.0 h1:zebkZaadz7+wIQYgC7GXaz3Wb28yKYfVkkBKwc38VF8= github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= @@ -81,7 +66,6 @@ github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzU github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/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/ClickHouse/clickhouse-go v1.4.3/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= github.com/FZambia/sentinel v1.1.0 h1:qrCBfxc8SvJihYNjBWgwUI93ZCvFe/PJIPTHKmlp8a8= github.com/FZambia/sentinel v1.1.0/go.mod h1:ytL1Am/RLlAoAXG6Kj5LNuw/TRRQrv2rt2FT26vP5gI= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= @@ -91,98 +75,34 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 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/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= -github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= -github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= -github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= -github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= -github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= -github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= -github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= -github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= -github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= -github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= -github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/Unknwon/goconfig v0.0.0-20160216183935-5f601ca6ef4d h1:RjxaKUAINjr+fYbaYjpdBUZc8R3+wF/Yr2XkDHho4Sg= github.com/Unknwon/goconfig v0.0.0-20160216183935-5f601ca6ef4d/go.mod h1:wngxua9XCNjvHjDiTiV26DaKDT+0c63QR6H5hjVUUxw= -github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= 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/apache/arrow/go/arrow v0.0.0-20210818145353-234c94e4ce64/go.mod h1:2qMFB56yOP3KzkB3PbYZ4AlUFg3a88F67TIx5lB/WwY= -github.com/apache/arrow/go/arrow v0.0.0-20211013220434-5962184e7a30/go.mod h1:Q7yQnSMnLvcXlZ8RV+jwz/6y1rQTqbX6C82SndT52Zs= -github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= -github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 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-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= -github.com/aws/aws-sdk-go-v2 v1.8.0/go.mod h1:xEFuWz+3TYdlPRuo+CqATbeDWIWyaT5uAPwPaWtgse0= -github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2/config v1.6.0/go.mod h1:TNtBVmka80lRPk5+S9ZqVfFszOQAGJJ9KbT3EM3CHNU= -github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw= -github.com/aws/aws-sdk-go-v2/credentials v1.3.2/go.mod h1:PACKuTJdt6AlXvEq8rFI4eDmoqDFC5DpVKQbWysaDgM= -github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.4.0/go.mod h1:Mj/U8OpDbcVcoctrYwA2bak8k/HFPdcLzI/vaiXMwuM= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0/go.mod h1:gqlclDEZp4aqJOancXK6TN24aKhT0W0Ae9MHk3wzTMM= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.4.0/go.mod h1:eHwXu2+uE/T6gpnYWwBwqoeqRf9IXyCcolyOWDRAErQ= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.5.4/go.mod h1:Ex7XQmbFmgFHrjUX6TN3mApKW5Hglyga+F7wZHTtYhA= -github.com/aws/aws-sdk-go-v2/internal/ini v1.2.0/go.mod h1:Q5jATQc+f1MfZp3PDMhn6ry18hGvE0i8yvbXoKbnZaE= -github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4/go.mod h1:ZcBrrI3zBKlhGFNYWvju0I3TR93I7YIgAfy82Fh4lcQ= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.2.2/go.mod h1:EASdTcM1lGhUe1/p4gkojHwlGJkeoRjjr1sRCzup3Is= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.3.0/go.mod h1:v8ygadNyATSm6elwJ/4gzJwcFhri9RqS8skgHKiwXPU= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.2.2/go.mod h1:NXmNI41bdEsJMrD0v9rUvbGCB5GwdBEpKvUvIY3vTFg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72HRZDLMtmVQiLG2tLfQcaWLCssELvGl+Zf2WVxMmR8= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.5.2/go.mod h1:QuL2Ym8BkrLmN4lUofXYq6000/i5jPjosCNK//t6gak= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.7.2/go.mod h1:np7TMuJNT83O0oDOSF8i4dF3dvGqA6hPYYo6YYkzgRA= -github.com/aws/aws-sdk-go-v2/service/s3 v1.12.0/go.mod h1:6J++A5xpo7QDsIeSqPK4UHqMSyPOCopa+zKtqAMhqVQ= -github.com/aws/aws-sdk-go-v2/service/s3 v1.16.1/go.mod h1:CQe/KvWV1AqRc65KqeJjrLzr5X2ijnFTTVzJW0VBRCI= -github.com/aws/aws-sdk-go-v2/service/sso v1.3.2/go.mod h1:J21I6kF+d/6XHVk7kp/cx9YVD2TMD2TbLwtRGVcinXo= -github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk= -github.com/aws/aws-sdk-go-v2/service/sts v1.6.1/go.mod h1:hLZ/AnkIKHLuPGjEiyghNEdvJ2PP0MgOxcmv9EBJ4xs= -github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g= -github.com/aws/smithy-go v1.7.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= 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= @@ -195,172 +115,43 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= -github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= -github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= -github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmatcuk/doublestar v1.1.1 h1:YroD6BJCZBYx06yYFEWvUuKVWQn3vLLQAVmDmvTSaiQ= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/casbin/casbin v1.9.1 h1:ucjbS5zTrmSLtH4XogqOG920Poe6QatdXtz1FEbApeM= github.com/casbin/casbin v1.9.1/go.mod h1:z8uPsfBJGUsnkagrt3G8QvjgTKFMBJ32UP8HpZllfog= -github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= -github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= -github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= -github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= -github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= -github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= -github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudevents/sdk-go/v2 v2.13.0 h1:2zxDS8RyY1/wVPULGGbdgniGXSzLaRJVl136fLXGsYw= github.com/cloudevents/sdk-go/v2 v2.13.0/go.mod h1:xDmKfzNjM8gBvjaF8ijFjM1VYOVUEeUfapHMUX1T5To= -github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= 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/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 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/cockroachdb/cockroach-go/v2 v2.1.1/go.mod h1:7NtUnP6eK+l6k483WSYNrq3Kb23bWV10IRV1TyeSpwM= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= -github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= -github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= -github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= -github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= -github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= -github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= -github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= -github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= -github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= -github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= -github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= -github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= -github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= -github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= -github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= -github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= -github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= -github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= -github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= -github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= -github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= -github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= -github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= -github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= -github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= -github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= -github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= -github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= -github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= -github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= -github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= -github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= -github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= -github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= -github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= -github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= -github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= -github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= -github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= -github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= -github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= -github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= -github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= -github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= -github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= -github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= -github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= -github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= -github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= -github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= -github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= -github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= -github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= -github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= -github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= -github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= -github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= -github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-oidc/v3 v3.0.0 h1:/mAA0XMgYJw2Uqm7WKGCsKnjitE/+A0FFbOmiRJm7LQ= github.com/coreos/go-oidc/v3 v3.0.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 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.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 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/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= -github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= -github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= -github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= -github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= -github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denverdino/aliyungo v0.0.0-20191227032621-df38c6fa730c h1:ZjNKFQ2pBtbkmtporMvGVu2M7fs3Ip3sSy0Gyqsq8xc= github.com/denverdino/aliyungo v0.0.0-20191227032621-df38c6fa730c/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dghubble/sling v1.1.0 h1:DLu20Bq2qsB9cI5Hldaxj+TMPEaPpPE8IR2kvD22Atg= @@ -368,19 +159,13 @@ github.com/dghubble/sling v1.1.0/go.mod h1:ZcPRuLm0qrcULW2gOrjXrAWgf76sahqSyxXyV github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/dhui/dktest v0.3.7 h1:jWjWgHAPDAdqgUr7lAsB3bqB2DKWC3OaA+isfekjRew= -github.com/dhui/dktest v0.3.7/go.mod h1:nYMOkafiA07WchSwKnKFUSbGMb2hMm5DrCGiXYG6gwM= +github.com/dhui/dktest v0.3.16 h1:i6gq2YQEtcrjKbeJpBkWjE8MmLZPYllcjOFbTZuPDnw= github.com/distribution/distribution v2.8.2+incompatible h1:k9+4DKdOG+quPFZXT/mUsiQrGu9vYCp+dXpuPkuqhk8= 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.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE= 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-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= @@ -388,78 +173,42 @@ github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= -github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= -github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/form3tech-oss/jwt-go v3.2.5+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= -github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= -github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 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/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw= -github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= -github.com/gabriel-vasile/mimetype v1.3.1/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= -github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= -github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= 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-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= -github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= -github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= -github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= 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-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= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -537,7 +286,6 @@ github.com/go-openapi/validate v0.19.10 h1:tG3SZ5DC5KF4cyt7nqLVcQXGj5A7mpaYkAcNP github.com/go-openapi/validate v0.19.10/go.mod h1:RKEZTUWDkxKQxN2jDT7ZnZi2bhZlbNMAuKvKB+IaGx8= 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.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 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-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= @@ -559,7 +307,6 @@ github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= -github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= @@ -570,39 +317,22 @@ github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/V github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/gocarina/gocsv v0.0.0-20210516172204-ca9e8a8ddea8 h1:hp1oqdzmv37vPLYFGjuM/RmUgUMfD9vQfMszc54l55Y= github.com/gocarina/gocsv v0.0.0-20210516172204-ca9e8a8ddea8/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= -github.com/gocql/gocql v0.0.0-20210515062232-b7ef815b4556/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY= github.com/gocraft/work v0.5.1 h1:3bRjMiOo6N4zcRgZWV3Y7uX7R22SF+A9bPTk4xRXr34= github.com/gocraft/work v0.5.1/go.mod h1:pc3n9Pb5FAESPPGfM0nL+7Q1xtgtRnF8rr/azzhQVlM= -github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= -github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= -github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= -github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 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 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-migrate/migrate/v4 v4.15.1 h1:Sakl3Nm6+wQKq0Q62tpFMi5a503bgGhceo2icrgQ9vM= -github.com/golang-migrate/migrate/v4 v4.15.1/go.mod h1:/CrBenUbcDqsW29jGTR/XFqCfVi/Y6mHXlooCcSOJMQ= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +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-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/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -612,8 +342,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -631,16 +359,11 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4/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= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/flatbuffers v2.0.0+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -650,82 +373,55 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-github/v35 v35.2.0/go.mod h1:s0515YVTI+IMrDoy9Y4pHt9ShGpzHvHO8rZ7L7acgvs= -github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +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= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20210715191844-86eeefc3e471/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.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/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= 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= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/csrf v1.6.2 h1:QqQ/OWwuFp4jMKgBFAzJVW3FMULdyUW7JoM4pEWuqKg= github.com/gorilla/csrf v1.6.2/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI= -github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 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/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 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/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= @@ -733,7 +429,6 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -746,17 +441,8 @@ github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0m github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -764,20 +450,19 @@ github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgO github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk= -github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= -github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= -github.com/jackc/pgconn v1.8.1/go.mod h1:JV6m6b6jhjdmzchES0drzCcYcAHS1OPD5xu3OZ/lE2g= -github.com/jackc/pgconn v1.9.0 h1:gqibKSTJup/ahCsNKyMZAniPuZEfIqfXFc8FOWVYR+Q= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgerrcode v0.0.0-20201024163028-a0d42d470451 h1:WAvSpGf7MsFuzAtK4Vk7R4EVe+liW4x83r4oWu0WHKw= -github.com/jackc/pgerrcode v0.0.0-20201024163028-a0d42d470451/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= +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/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= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= -github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd h1:eDErF6V/JPJON/B7s68BxwHgfmyOntHJQ8IOaz0x4R8= github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= @@ -785,50 +470,35 @@ github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.0.7/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +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/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= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0= -github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po= -github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ= -github.com/jackc/pgtype v1.6.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig= -github.com/jackc/pgtype v1.7.0/go.mod h1:ZnHF+rMePVqDKaOfJVI4Q8IVvAQMryDlDkZnKOI75BE= -github.com/jackc/pgtype v1.8.0 h1:iFVCcVhYlw0PulYCVoguRGm0SE9guIcPcccnLzHj8bA= -github.com/jackc/pgtype v1.8.0/go.mod h1:PqDKcEBtllAtk/2p6z6SHdXW5UB+MhE75tUol2OKexE= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= 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.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA= -github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o= -github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg= -github.com/jackc/pgx/v4 v4.10.1/go.mod h1:QlrWebbs3kqEZPHCTGyxecvzG6tvIsYu+A5b1raylkA= -github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CIdYfcc= -github.com/jackc/pgx/v4 v4.12.0 h1:xiP3TdnkwyslWNp77yE5XAPfxAsU9RMFDe0c1SwN8h4= -github.com/jackc/pgx/v4 v4.12.0/go.mod h1:fE547h6VulLPA3kySjfnSG/e2D861g/50JlVUa/ub60= +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/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.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +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= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= -github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= @@ -836,7 +506,6 @@ github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -847,23 +516,12 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= -github.com/k0kubun/pp v2.3.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= -github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 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.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.13.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 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= @@ -871,8 +529,6 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/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.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= @@ -880,78 +536,48 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= 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.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= -github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= -github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 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-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= -github.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= -github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= 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/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= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= -github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= -github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= -github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -962,179 +588,82 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mutecomm/go-sqlcipher/v4 v4.4.0/go.mod h1:PyN04SaWalavxRGH9E8ZftG6Ju7rsPrGmQRjrEaVpiY= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= -github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= -github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= -github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/ncw/swift v1.0.49 h1:eQaKIjSt/PXLKfYgzg01nevmO+CMXfXGRhB1gOhDs7E= github.com/ncw/swift v1.0.49/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= -github.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba/go.mod h1:ncO5VaFWh0Nrt+4KT4mOZboaczBZcLuHrG+/sUeP8gI= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= -github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= 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.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= 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/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= -github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= -github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= -github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= -github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= -github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= -github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= -github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= -github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 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/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= -github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= -github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= -github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= -github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= -github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pierrec/lz4/v4 v4.1.8/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/robfig/cron v1.0.0 h1:slmQxIUH6U9ruw4XoJ7C2pyyx4yYeiHx8S9pNootHsM= github.com/robfig/cron v1.0.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E= github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -1143,77 +672,46 @@ github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBO 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.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= -github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 h1:DAYUYH5869yV94zvCES9F51oYtN5oGlwjxJJz7ZCnik= github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.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.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= +github.com/sirupsen/logrus v1.9.2/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= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/snowflakedb/gosnowflake v1.6.3/go.mod h1:6hLajn6yxuJ4xUHZegMekpq9rnQbGJ7TMwXjgTmA6lg= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= -github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= -github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= -github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -1222,62 +720,33 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 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/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= 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 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= -github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vmihailenco/msgpack/v5 v5.0.0-rc.2 h1:ognci8XPlosGhIHK1OLYSpSpnlhSFeBklfe18zIEwcU= github.com/vmihailenco/msgpack/v5 v5.0.0-rc.2/go.mod h1:HVxBVPUK/+fZMonk4bi1islLa8V3cfnBug0+4dykPzo= github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc= github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= -github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= -github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= @@ -1285,15 +754,12 @@ go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qL go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= go.mongodb.org/mongo-driver v1.3.4/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= -go.mongodb.org/mongo-driver v1.7.0 h1:hHrvOBWlWB2c7+8Gh/Xi5jj82AgidK/t7KVXBZ+IyUA= -go.mongodb.org/mongo-driver v1.7.0/go.mod h1:Q4oFMbo1+MSNqICAdYMlC/zSTrwCogR4R8NzkI+yfU8= -go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= -go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.mongodb.org/mongo-driver v1.7.5 h1:ny3p0reEpgsR2cfA5cjgwFZg3Cv/ofFh/8jbhGtz9VI= +go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/contrib v0.22.0 h1:0F7gDEjgb1WGn4ODIjaCAg75hmqF+UN0LiVgwxsCodc= go.opentelemetry.io/contrib v0.22.0/go.mod h1:EH4yDYeNoaTqn/8yCWQmfNB78VHfGX2Jt2bvnvzBlGM= go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.22.0 h1:Mfz1DMQ43mhQePKqiny6kUTnUrtin+395V67yAIyYhg= @@ -1358,13 +824,10 @@ go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -1372,47 +835,31 @@ golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/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-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/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-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 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.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/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= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -1433,26 +880,21 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB 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/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190225153610-fe579d43d832/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 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= @@ -1460,9 +902,7 @@ golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/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-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1478,34 +918,25 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211013171255-e13a2654a71e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +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 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= @@ -1521,17 +952,15 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/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.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-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1541,41 +970,26 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1585,48 +999,32 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210818153620-00dd8d7831e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/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 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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= @@ -1635,21 +1033,16 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -1665,12 +1058,10 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1701,32 +1092,20 @@ golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= 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= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= -gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff h1:mk5zS3XLqVUzdF/CQCZ5ERujSF/8JFo+Wpkp/5I93NA= google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/appengine v1.0.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 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.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= @@ -1737,9 +1116,6 @@ google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8 h1:Cpp2P6TPjujNoC5M2K 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= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= -google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= @@ -1748,7 +1124,6 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= @@ -1764,46 +1139,26 @@ google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1m google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210207032614-bba0dbe2a9ea/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210630183607-d20f26d13c79/go.mod h1:yiaVoXHpRzHGyxV3o4DktVWY4mSUErTKaeEOq6C3t3U= -google.golang.org/genproto v0.0.0-20210721163202-f1cecdd8b78a/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210726143408-b02e89920bf0/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20211013025323-ce878158c4d4/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA= google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= 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= @@ -1819,19 +1174,14 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= 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 h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 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= -gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= -gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/h2non/gentleman.v1 v1.0.4/go.mod h1:JYuHVdFzS4MKOXe0o+chKJ4hCe6tqKKw9XH9YP6WFrg= gopkg.in/h2non/gock.v1 v1.0.16 h1:F11k+OafeuFENsjei5t2vMTSTs9L62AdyTe4E1cgdG8= gopkg.in/h2non/gock.v1 v1.0.16/go.mod h1:XVuDAssexPLwgxCLMvDTWNU5eqklsydR6I5phZ9oPB8= @@ -1841,22 +1191,15 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -1867,97 +1210,31 @@ 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= -gorm.io/driver/postgres v1.0.8/go.mod h1:4eOzrI1MUfm6ObJU/UcmbXyiHSs8jSwH95G5P5dxcAg= -gorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= -gorm.io/gorm v1.21.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= 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= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 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= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= -k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= -k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= k8s.io/api v0.26.2 h1:dM3cinp3PGB6asOySalOZxEG4CZ0IAdJsrYZXE/ovGQ= k8s.io/api v0.26.2/go.mod h1:1kjMQsFE+QHPfskEcVNgL3+Hp88B80uj0QtSOlj8itU= -k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= k8s.io/apimachinery v0.26.2 h1:da1u3D5wfR5u2RpLhE/ZtZS2P7QvDgLZTi9wrNZl/tQ= k8s.io/apimachinery v0.26.2/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= -k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= -k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= -k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= -k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= -k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= -k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= k8s.io/client-go v0.26.2 h1:s1WkVujHX3kTp4Zn4yGNFK+dlDXy1bAAkIl+cFAiuYI= k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU= -k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= -k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= -k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= -k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= -k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= -k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= -k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= -k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= -k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= -k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk= k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -modernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg= -modernc.org/cc/v3 v3.32.4/go.mod h1:0R6jl1aZlIl2avnYfbfHBS1QB6/f+16mihBObaBC878= -modernc.org/ccgo/v3 v3.9.2/go.mod h1:gnJpy6NIVqkETT+L5zPsQFj7L2kkhfPMzOghRNv/CFo= -modernc.org/db v1.0.0/go.mod h1:kYD/cO29L/29RM0hXYl4i3+Q5VojL31kTUVpVJDw0s8= -modernc.org/file v1.0.0/go.mod h1:uqEokAEn1u6e+J45e54dsEA/pw4o7zLrA2GwyntZzjw= -modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8= -modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= -modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= -modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= -modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= -modernc.org/libc v1.9.5/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= -modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8= -modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= -modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc= -modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY= -modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k= -modernc.org/sqlite v1.10.6/go.mod h1:Z9FEjUtZP4qFEg6/SiADg9XCER7aYy9a/j7Pg9P7CPs= -modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= -modernc.org/tcl v1.5.2/go.mod h1:pmJYOLgpiys3oI4AeAafkcUfE+TKKilminxNyU/+Zlo= -modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.0.1-0.20210308123920-1f282aa71362/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA= -modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA= -modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= -sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/src/vendor/github.com/golang-jwt/jwt/v4/README.md b/src/vendor/github.com/golang-jwt/jwt/v4/README.md index 3072d24a9da..f5d551ca8fd 100644 --- a/src/vendor/github.com/golang-jwt/jwt/v4/README.md +++ b/src/vendor/github.com/golang-jwt/jwt/v4/README.md @@ -36,9 +36,23 @@ The part in the middle is the interesting bit. It's called the Claims and conta This library supports the parsing and verification as well as the generation and signing of JWTs. Current supported signing algorithms are HMAC SHA, RSA, RSA-PSS, and ECDSA, though hooks are present for adding your own. +## Installation Guidelines + +1. To install the jwt package, you first need to have [Go](https://go.dev/doc/install) installed, then you can use the command below to add `jwt-go` as a dependency in your Go program. + +```sh +go get -u github.com/golang-jwt/jwt/v4 +``` + +2. Import it in your code: + +```go +import "github.com/golang-jwt/jwt/v4" +``` + ## Examples -See [the project documentation](https://pkg.go.dev/github.com/golang-jwt/jwt) for examples of usage: +See [the project documentation](https://pkg.go.dev/github.com/golang-jwt/jwt/v4) for examples of usage: * [Simple example of parsing and validating a token](https://pkg.go.dev/github.com/golang-jwt/jwt#example-Parse-Hmac) * [Simple example of building and signing a token](https://pkg.go.dev/github.com/golang-jwt/jwt#example-New-Hmac) @@ -46,9 +60,17 @@ See [the project documentation](https://pkg.go.dev/github.com/golang-jwt/jwt) fo ## Extensions -This library publishes all the necessary components for adding your own signing methods. Simply implement the `SigningMethod` interface and register a factory method using `RegisterSigningMethod`. +This library publishes all the necessary components for adding your own signing methods or key functions. Simply implement the `SigningMethod` interface and register a factory method using `RegisterSigningMethod` or provide a `jwt.Keyfunc`. + +A common use case would be integrating with different 3rd party signature providers, like key management services from various cloud providers or Hardware Security Modules (HSMs) or to implement additional standards. -Here's an example of an extension that integrates with multiple Google Cloud Platform signing tools (AppEngine, IAM API, Cloud KMS): https://github.com/someone1/gcp-jwt-go +| Extension | Purpose | Repo | +| --------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------ | +| GCP | Integrates with multiple Google Cloud Platform signing tools (AppEngine, IAM API, Cloud KMS) | https://github.com/someone1/gcp-jwt-go | +| AWS | Integrates with AWS Key Management Service, KMS | https://github.com/matelang/jwt-go-aws-kms | +| JWKS | Provides support for JWKS ([RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517)) as a `jwt.Keyfunc` | https://github.com/MicahParks/keyfunc | + +*Disclaimer*: Unless otherwise specified, these integrations are maintained by third parties and should not be considered as a primary offer by any of the mentioned cloud providers ## Compliance @@ -112,3 +134,5 @@ This library uses descriptive error messages whenever possible. If you are not g Documentation can be found [on pkg.go.dev](https://pkg.go.dev/github.com/golang-jwt/jwt). The command line utility included in this project (cmd/jwt) provides a straightforward example of token creation and parsing as well as a useful tool for debugging your own integration. You'll also find several implementation examples in the documentation. + +[golang-jwt](https://github.com/orgs/golang-jwt) incorporates a modified version of the JWT logo, which is distributed under the terms of the [MIT License](https://github.com/jsonwebtoken/jsonwebtoken.github.io/blob/master/LICENSE.txt). diff --git a/src/vendor/github.com/golang-jwt/jwt/v4/SECURITY.md b/src/vendor/github.com/golang-jwt/jwt/v4/SECURITY.md new file mode 100644 index 00000000000..b08402c3427 --- /dev/null +++ b/src/vendor/github.com/golang-jwt/jwt/v4/SECURITY.md @@ -0,0 +1,19 @@ +# Security Policy + +## Supported Versions + +As of February 2022 (and until this document is updated), the latest version `v4` is supported. + +## Reporting a Vulnerability + +If you think you found a vulnerability, and even if you are not sure, please report it to jwt-go-security@googlegroups.com or one of the other [golang-jwt maintainers](https://github.com/orgs/golang-jwt/people). Please try be explicit, describe steps to reproduce the security issue with code example(s). + +You will receive a response within a timely manner. If the issue is confirmed, we will do our best to release a patch as soon as possible given the complexity of the problem. + +## Public Discussions + +Please avoid publicly discussing a potential security vulnerability. + +Let's take this offline and find a solution first, this limits the potential impact as much as possible. + +We appreciate your help! diff --git a/src/vendor/github.com/golang-jwt/jwt/v4/claims.go b/src/vendor/github.com/golang-jwt/jwt/v4/claims.go index 41cc826563b..9d95cad2bf2 100644 --- a/src/vendor/github.com/golang-jwt/jwt/v4/claims.go +++ b/src/vendor/github.com/golang-jwt/jwt/v4/claims.go @@ -56,17 +56,17 @@ func (c RegisteredClaims) Valid() error { // default value in Go, let's not fail the verification for them. if !c.VerifyExpiresAt(now, false) { delta := now.Sub(c.ExpiresAt.Time) - vErr.Inner = fmt.Errorf("token is expired by %v", delta) + vErr.Inner = fmt.Errorf("%s by %s", ErrTokenExpired, delta) vErr.Errors |= ValidationErrorExpired } if !c.VerifyIssuedAt(now, false) { - vErr.Inner = fmt.Errorf("token used before issued") + vErr.Inner = ErrTokenUsedBeforeIssued vErr.Errors |= ValidationErrorIssuedAt } if !c.VerifyNotBefore(now, false) { - vErr.Inner = fmt.Errorf("token is not valid yet") + vErr.Inner = ErrTokenNotValidYet vErr.Errors |= ValidationErrorNotValidYet } @@ -149,17 +149,17 @@ func (c StandardClaims) Valid() error { // default value in Go, let's not fail the verification for them. if !c.VerifyExpiresAt(now, false) { delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0)) - vErr.Inner = fmt.Errorf("token is expired by %v", delta) + vErr.Inner = fmt.Errorf("%s by %s", ErrTokenExpired, delta) vErr.Errors |= ValidationErrorExpired } if !c.VerifyIssuedAt(now, false) { - vErr.Inner = fmt.Errorf("token used before issued") + vErr.Inner = ErrTokenUsedBeforeIssued vErr.Errors |= ValidationErrorIssuedAt } if !c.VerifyNotBefore(now, false) { - vErr.Inner = fmt.Errorf("token is not valid yet") + vErr.Inner = ErrTokenNotValidYet vErr.Errors |= ValidationErrorNotValidYet } diff --git a/src/vendor/github.com/golang-jwt/jwt/v4/errors.go b/src/vendor/github.com/golang-jwt/jwt/v4/errors.go index b9d18e498e3..10ac8835cc8 100644 --- a/src/vendor/github.com/golang-jwt/jwt/v4/errors.go +++ b/src/vendor/github.com/golang-jwt/jwt/v4/errors.go @@ -9,6 +9,18 @@ var ( ErrInvalidKey = errors.New("key is invalid") ErrInvalidKeyType = errors.New("key is of invalid type") ErrHashUnavailable = errors.New("the requested hash function is unavailable") + + ErrTokenMalformed = errors.New("token is malformed") + ErrTokenUnverifiable = errors.New("token is unverifiable") + ErrTokenSignatureInvalid = errors.New("token signature is invalid") + + ErrTokenInvalidAudience = errors.New("token has invalid audience") + ErrTokenExpired = errors.New("token is expired") + ErrTokenUsedBeforeIssued = errors.New("token used before issued") + ErrTokenInvalidIssuer = errors.New("token has invalid issuer") + ErrTokenNotValidYet = errors.New("token is not valid yet") + ErrTokenInvalidId = errors.New("token has invalid id") + ErrTokenInvalidClaims = errors.New("token has invalid claims") ) // The errors that might occur when parsing and validating a token @@ -62,3 +74,39 @@ func (e *ValidationError) Unwrap() error { func (e *ValidationError) valid() bool { return e.Errors == 0 } + +// Is checks if this ValidationError is of the supplied error. We are first checking for the exact error message +// by comparing the inner error message. If that fails, we compare using the error flags. This way we can use +// custom error messages (mainly for backwards compatability) and still leverage errors.Is using the global error variables. +func (e *ValidationError) Is(err error) bool { + // Check, if our inner error is a direct match + if errors.Is(errors.Unwrap(e), err) { + return true + } + + // Otherwise, we need to match using our error flags + switch err { + case ErrTokenMalformed: + return e.Errors&ValidationErrorMalformed != 0 + case ErrTokenUnverifiable: + return e.Errors&ValidationErrorUnverifiable != 0 + case ErrTokenSignatureInvalid: + return e.Errors&ValidationErrorSignatureInvalid != 0 + case ErrTokenInvalidAudience: + return e.Errors&ValidationErrorAudience != 0 + case ErrTokenExpired: + return e.Errors&ValidationErrorExpired != 0 + case ErrTokenUsedBeforeIssued: + return e.Errors&ValidationErrorIssuedAt != 0 + case ErrTokenInvalidIssuer: + return e.Errors&ValidationErrorIssuer != 0 + case ErrTokenNotValidYet: + return e.Errors&ValidationErrorNotValidYet != 0 + case ErrTokenInvalidId: + return e.Errors&ValidationErrorId != 0 + case ErrTokenInvalidClaims: + return e.Errors&ValidationErrorClaimsInvalid != 0 + } + + return false +} diff --git a/src/vendor/github.com/golang-jwt/jwt/v4/map_claims.go b/src/vendor/github.com/golang-jwt/jwt/v4/map_claims.go index e7da633b93c..2700d64a0d0 100644 --- a/src/vendor/github.com/golang-jwt/jwt/v4/map_claims.go +++ b/src/vendor/github.com/golang-jwt/jwt/v4/map_claims.go @@ -126,16 +126,19 @@ func (m MapClaims) Valid() error { now := TimeFunc().Unix() if !m.VerifyExpiresAt(now, false) { + // TODO(oxisto): this should be replaced with ErrTokenExpired vErr.Inner = errors.New("Token is expired") vErr.Errors |= ValidationErrorExpired } if !m.VerifyIssuedAt(now, false) { + // TODO(oxisto): this should be replaced with ErrTokenUsedBeforeIssued vErr.Inner = errors.New("Token used before issued") vErr.Errors |= ValidationErrorIssuedAt } if !m.VerifyNotBefore(now, false) { + // TODO(oxisto): this should be replaced with ErrTokenNotValidYet vErr.Inner = errors.New("Token is not valid yet") vErr.Errors |= ValidationErrorNotValidYet } diff --git a/src/vendor/github.com/golang-jwt/jwt/v4/parser_option.go b/src/vendor/github.com/golang-jwt/jwt/v4/parser_option.go index 0fede4f15c9..6ea6f9527de 100644 --- a/src/vendor/github.com/golang-jwt/jwt/v4/parser_option.go +++ b/src/vendor/github.com/golang-jwt/jwt/v4/parser_option.go @@ -1,6 +1,6 @@ package jwt -// ParserOption is used to implement functional-style options that modify the behaviour of the parser. To add +// ParserOption is used to implement functional-style options that modify the behavior of the parser. To add // new options, just create a function (ideally beginning with With or Without) that returns an anonymous function that // takes a *Parser type as input and manipulates its configuration accordingly. type ParserOption func(*Parser) @@ -13,7 +13,7 @@ func WithValidMethods(methods []string) ParserOption { } } -// WithJSONNumber is an option to configure the underyling JSON parser with UseNumber +// WithJSONNumber is an option to configure the underlying JSON parser with UseNumber func WithJSONNumber() ParserOption { return func(p *Parser) { p.UseJSONNumber = true diff --git a/src/vendor/github.com/golang-jwt/jwt/v4/rsa_pss.go b/src/vendor/github.com/golang-jwt/jwt/v4/rsa_pss.go index 5a8502feb34..4fd6f9e610b 100644 --- a/src/vendor/github.com/golang-jwt/jwt/v4/rsa_pss.go +++ b/src/vendor/github.com/golang-jwt/jwt/v4/rsa_pss.go @@ -1,3 +1,4 @@ +//go:build go1.4 // +build go1.4 package jwt diff --git a/src/vendor/github.com/golang-jwt/jwt/v4/token.go b/src/vendor/github.com/golang-jwt/jwt/v4/token.go index 12344138bed..3cb0f3f0e4c 100644 --- a/src/vendor/github.com/golang-jwt/jwt/v4/token.go +++ b/src/vendor/github.com/golang-jwt/jwt/v4/token.go @@ -7,7 +7,6 @@ import ( "time" ) - // DecodePaddingAllowed will switch the codec used for decoding JWTs respectively. Note that the JWS RFC7515 // states that the tokens will utilize a Base64url encoding with no padding. Unfortunately, some implementations // of JWT are producing non-standard tokens, and thus require support for decoding. Note that this is a global @@ -74,22 +73,19 @@ func (t *Token) SignedString(key interface{}) (string, error) { // the SignedString. func (t *Token) SigningString() (string, error) { var err error - parts := make([]string, 2) - for i := range parts { - var jsonValue []byte - if i == 0 { - if jsonValue, err = json.Marshal(t.Header); err != nil { - return "", err - } - } else { - if jsonValue, err = json.Marshal(t.Claims); err != nil { - return "", err - } - } + var jsonValue []byte - parts[i] = EncodeSegment(jsonValue) + if jsonValue, err = json.Marshal(t.Header); err != nil { + return "", err } - return strings.Join(parts, "."), nil + header := EncodeSegment(jsonValue) + + if jsonValue, err = json.Marshal(t.Claims); err != nil { + return "", err + } + claim := EncodeSegment(jsonValue) + + return strings.Join([]string{header, claim}, "."), nil } // Parse parses, validates, verifies the signature and returns the parsed token. diff --git a/src/vendor/github.com/golang-jwt/jwt/v4/types.go b/src/vendor/github.com/golang-jwt/jwt/v4/types.go index 80b1b96948e..ac8e140eb11 100644 --- a/src/vendor/github.com/golang-jwt/jwt/v4/types.go +++ b/src/vendor/github.com/golang-jwt/jwt/v4/types.go @@ -49,9 +49,27 @@ func newNumericDateFromSeconds(f float64) *NumericDate { // MarshalJSON is an implementation of the json.RawMessage interface and serializes the UNIX epoch // represented in NumericDate to a byte array, using the precision specified in TimePrecision. func (date NumericDate) MarshalJSON() (b []byte, err error) { - f := float64(date.Truncate(TimePrecision).UnixNano()) / float64(time.Second) - - return []byte(strconv.FormatFloat(f, 'f', -1, 64)), nil + var prec int + if TimePrecision < time.Second { + prec = int(math.Log10(float64(time.Second) / float64(TimePrecision))) + } + truncatedDate := date.Truncate(TimePrecision) + + // For very large timestamps, UnixNano would overflow an int64, but this + // function requires nanosecond level precision, so we have to use the + // following technique to get round the issue: + // 1. Take the normal unix timestamp to form the whole number part of the + // output, + // 2. Take the result of the Nanosecond function, which retuns the offset + // within the second of the particular unix time instance, to form the + // decimal part of the output + // 3. Concatenate them to produce the final result + seconds := strconv.FormatInt(truncatedDate.Unix(), 10) + nanosecondsOffset := strconv.FormatFloat(float64(truncatedDate.Nanosecond())/float64(time.Second), 'f', prec, 64) + + output := append([]byte(seconds), []byte(nanosecondsOffset)[1:]...) + + return output, nil } // UnmarshalJSON is an implementation of the json.RawMessage interface and deserializses a diff --git a/src/vendor/github.com/golang-migrate/migrate/v4/.goreleaser.yml b/src/vendor/github.com/golang-migrate/migrate/v4/.goreleaser.yml index 682248f65a4..65d80468d0d 100644 --- a/src/vendor/github.com/golang-migrate/migrate/v4/.goreleaser.yml +++ b/src/vendor/github.com/golang-migrate/migrate/v4/.goreleaser.yml @@ -87,6 +87,7 @@ release: prerelease: auto source: enabled: true + rlcp: true format: zip changelog: skip: false diff --git a/src/vendor/github.com/golang-migrate/migrate/v4/Dockerfile b/src/vendor/github.com/golang-migrate/migrate/v4/Dockerfile index eb818821188..c576a57b0bf 100644 --- a/src/vendor/github.com/golang-migrate/migrate/v4/Dockerfile +++ b/src/vendor/github.com/golang-migrate/migrate/v4/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.16-alpine3.13 AS builder +FROM golang:1.20-alpine3.18 AS builder ARG VERSION RUN apk add --no-cache git gcc musl-dev make @@ -15,7 +15,7 @@ COPY . ./ RUN make build-docker -FROM alpine:3.13 +FROM alpine:3.18 RUN apk add --no-cache ca-certificates diff --git a/src/vendor/github.com/golang-migrate/migrate/v4/Dockerfile.github-actions b/src/vendor/github.com/golang-migrate/migrate/v4/Dockerfile.github-actions index 559c1e79c41..c9723c38c66 100644 --- a/src/vendor/github.com/golang-migrate/migrate/v4/Dockerfile.github-actions +++ b/src/vendor/github.com/golang-migrate/migrate/v4/Dockerfile.github-actions @@ -1,4 +1,4 @@ -FROM alpine:3.13 +FROM alpine:3.18 RUN apk add --no-cache ca-certificates @@ -8,4 +8,4 @@ RUN ln -s /usr/local/bin/migrate /usr/bin/migrate RUN ln -s /usr/local/bin/migrate /migrate ENTRYPOINT ["migrate"] -CMD ["--help"] \ No newline at end of file +CMD ["--help"] diff --git a/src/vendor/github.com/golang-migrate/migrate/v4/GETTING_STARTED.md b/src/vendor/github.com/golang-migrate/migrate/v4/GETTING_STARTED.md index 5946005cd23..5a204c07ebe 100644 --- a/src/vendor/github.com/golang-migrate/migrate/v4/GETTING_STARTED.md +++ b/src/vendor/github.com/golang-migrate/migrate/v4/GETTING_STARTED.md @@ -1,7 +1,7 @@ # Getting started Before you start, you should understand the concept of forward/up and reverse/down database migrations. -Configure a database for your application. Make sure that your database driver is supported [here](README.md#databases) +Configure a database for your application. Make sure that your database driver is supported [here](README.md#databases). ## Create migrations Create some migrations using migrate CLI. Here is an example: @@ -10,7 +10,7 @@ migrate create -ext sql -dir db/migrations -seq create_users_table ``` Once you create your files, you should fill them. -**IMPORTANT:** In a project developed by more than one person there is a chance of migrations inconsistency - e.g. two developers can create conflicting migrations, and the developer that created his migration later gets it merged to the repository first. +**IMPORTANT:** In a project developed by more than one person there is a chance of migrations inconsistency - e.g. two developers can create conflicting migrations, and the developer that created their migration later gets it merged to the repository first. Developers and Teams should keep an eye on such cases (especially during code review). [Here](https://github.com/golang-migrate/migrate/issues/179#issuecomment-475821264) is the issue summary if you would like to read more. @@ -30,7 +30,7 @@ Just add the code to your app and you're ready to go! Before commiting your migrations you should run your migrations up, down, and then up again to see if migrations are working properly both ways. (e.g. if you created a table in a migration but reverse migration did not delete it, you will encounter an error when running the forward migration again) -It's also worth checking your migrations in a separate, containerized environment. You can find some tools in the end of this document. +It's also worth checking your migrations in a separate, containerized environment. You can find some tools at the [end of this document](#further-reading). **IMPORTANT:** If you would like to run multiple instances of your app on different machines be sure to use a database that supports locking when running migrations. Otherwise you may encounter issues. diff --git a/src/vendor/github.com/golang-migrate/migrate/v4/Makefile b/src/vendor/github.com/golang-migrate/migrate/v4/Makefile index 79fa85ffbfb..61e035c0ed7 100644 --- a/src/vendor/github.com/golang-migrate/migrate/v4/Makefile +++ b/src/vendor/github.com/golang-migrate/migrate/v4/Makefile @@ -1,5 +1,5 @@ SOURCE ?= file go_bindata github github_ee bitbucket aws_s3 google_cloud_storage godoc_vfs gitlab -DATABASE ?= postgres mysql redshift cassandra spanner cockroachdb clickhouse mongodb sqlserver firebird neo4j pgx +DATABASE ?= postgres mysql redshift cassandra spanner cockroachdb yugabytedb clickhouse mongodb sqlserver firebird neo4j pgx pgx5 DATABASE_TEST ?= $(DATABASE) sqlite sqlite3 sqlcipher VERSION ?= $(shell git describe --tags 2>/dev/null | cut -c 2-) TEST_FLAGS ?= diff --git a/src/vendor/github.com/golang-migrate/migrate/v4/README.md b/src/vendor/github.com/golang-migrate/migrate/v4/README.md index 1af974c8623..3beed0076ff 100644 --- a/src/vendor/github.com/golang-migrate/migrate/v4/README.md +++ b/src/vendor/github.com/golang-migrate/migrate/v4/README.md @@ -1,11 +1,11 @@ -[![GitHub Workflow Status (branch)](https://img.shields.io/github/workflow/status/golang-migrate/migrate/CI/master)](https://github.com/golang-migrate/migrate/actions/workflows/ci.yaml?query=branch%3Amaster) +[![GitHub Workflow Status (branch)](https://img.shields.io/github/actions/workflow/status/golang-migrate/migrate/ci.yaml?branch=master)](https://github.com/golang-migrate/migrate/actions/workflows/ci.yaml?query=branch%3Amaster) [![GoDoc](https://pkg.go.dev/badge/github.com/golang-migrate/migrate)](https://pkg.go.dev/github.com/golang-migrate/migrate/v4) [![Coverage Status](https://img.shields.io/coveralls/github/golang-migrate/migrate/master.svg)](https://coveralls.io/github/golang-migrate/migrate?branch=master) [![packagecloud.io](https://img.shields.io/badge/deb-packagecloud.io-844fec.svg)](https://packagecloud.io/golang-migrate/migrate?filter=debs) [![Docker Pulls](https://img.shields.io/docker/pulls/migrate/migrate.svg)](https://hub.docker.com/r/migrate/migrate/) -![Supported Go Versions](https://img.shields.io/badge/Go-1.16%2C%201.17-lightgrey.svg) +![Supported Go Versions](https://img.shields.io/badge/Go-1.19%2C%201.20-lightgrey.svg) [![GitHub Release](https://img.shields.io/github/release/golang-migrate/migrate.svg)](https://github.com/golang-migrate/migrate/releases) -[![Go Report Card](https://goreportcard.com/badge/github.com/golang-migrate/migrate)](https://goreportcard.com/report/github.com/golang-migrate/migrate) +[![Go Report Card](https://goreportcard.com/badge/github.com/golang-migrate/migrate/v4)](https://goreportcard.com/report/github.com/golang-migrate/migrate/v4) # migrate @@ -24,7 +24,8 @@ Forked from [mattes/migrate](https://github.com/mattes/migrate) Database drivers run migrations. [Add a new database?](database/driver.go) * [PostgreSQL](database/postgres) -* [PGX](database/pgx) +* [PGX v4](database/pgx) +* [PGX v5](database/pgx/v5) * [Redshift](database/redshift) * [Ql](database/ql) * [Cassandra](database/cassandra) @@ -38,6 +39,7 @@ Database drivers run migrations. [Add a new database?](database/driver.go) * [Shell](database/shell) ([todo #171](https://github.com/mattes/migrate/issues/171)) * [Google Cloud Spanner](database/spanner) * [CockroachDB](database/cockroachdb) +* [YugabyteDB](database/yugabytedb) * [ClickHouse](database/clickhouse) * [Firebird](database/firebird) * [MS SQL Server](database/sqlserver) @@ -68,7 +70,9 @@ $ Source drivers read migrations from local or remote sources. [Add a new source?](source/driver.go) * [Filesystem](source/file) - read from filesystem +* [io/fs](source/iofs) - read from a Go [io/fs](https://pkg.go.dev/io/fs#FS) * [Go-Bindata](source/go_bindata) - read from embedded binary data ([jteeuwen/go-bindata](https://github.com/jteeuwen/go-bindata)) +* [pkger](source/pkger) - read from embedded binary data ([markbates/pkger](https://github.com/markbates/pkger)) * [GitHub](source/github) - read from remote GitHub repositories * [GitHub Enterprise](source/github_ee) - read from remote GitHub Enterprise repositories * [Bitbucket](source/bitbucket) - read from remote Bitbucket repositories @@ -106,7 +110,7 @@ $ docker run -v {{ migration dir }}:/migrations --network host migrate/migrate * Uses `io.Reader` streams internally for low memory overhead. * Thread-safe and no goroutine leaks. -__[Go Documentation](https://godoc.org/github.com/golang-migrate/migrate)__ +__[Go Documentation](https://pkg.go.dev/github.com/golang-migrate/migrate/v4)__ ```go import ( @@ -140,7 +144,7 @@ func main() { m, err := migrate.NewWithDatabaseInstance( "file:///migrations", "postgres", driver) - m.Steps(2) + m.Up() // or m.Step(2) if you want to explicitly set the number of migrations to run } ``` diff --git a/src/vendor/github.com/golang-migrate/migrate/v4/SECURITY.md b/src/vendor/github.com/golang-migrate/migrate/v4/SECURITY.md new file mode 100644 index 00000000000..1d7146f6b14 --- /dev/null +++ b/src/vendor/github.com/golang-migrate/migrate/v4/SECURITY.md @@ -0,0 +1,16 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| master | :white_check_mark: | +| 4.x | :white_check_mark: | +| 3.x | :x: | +| < 3.0 | :x: | + +## Reporting a Vulnerability + +We prefer [coordinated disclosures](https://en.wikipedia.org/wiki/Coordinated_vulnerability_disclosure). To start one, create a GitHub security advisory following [these instructions](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability) + +Please suggest potential impact and urgency in your reports. diff --git a/src/vendor/github.com/golang-migrate/migrate/v4/database/driver.go b/src/vendor/github.com/golang-migrate/migrate/v4/database/driver.go index fa80f455c05..11268e6b98b 100644 --- a/src/vendor/github.com/golang-migrate/migrate/v4/database/driver.go +++ b/src/vendor/github.com/golang-migrate/migrate/v4/database/driver.go @@ -1,4 +1,4 @@ -// Package database provides the Database interface. +// Package database provides the Driver interface. // All database drivers must implement this interface, register themselves, // optionally provide a `WithInstance` function and pass the tests // in package database/testing. @@ -25,22 +25,22 @@ var drivers = make(map[string]Driver) // Driver is the interface every database driver must implement. // // How to implement a database driver? -// 1. Implement this interface. -// 2. Optionally, add a function named `WithInstance`. -// This function should accept an existing DB instance and a Config{} struct -// and return a driver instance. -// 3. Add a test that calls database/testing.go:Test() -// 4. Add own tests for Open(), WithInstance() (when provided) and Close(). -// All other functions are tested by tests in database/testing. -// Saves you some time and makes sure all database drivers behave the same way. -// 5. Call Register in init(). -// 6. Create a internal/cli/build_.go file -// 7. Add driver name in 'DATABASE' variable in Makefile +// 1. Implement this interface. +// 2. Optionally, add a function named `WithInstance`. +// This function should accept an existing DB instance and a Config{} struct +// and return a driver instance. +// 3. Add a test that calls database/testing.go:Test() +// 4. Add own tests for Open(), WithInstance() (when provided) and Close(). +// All other functions are tested by tests in database/testing. +// Saves you some time and makes sure all database drivers behave the same way. +// 5. Call Register in init(). +// 6. Create a internal/cli/build_.go file +// 7. Add driver name in 'DATABASE' variable in Makefile // // Guidelines: -// * Don't try to correct user input. Don't assume things. +// - Don't try to correct user input. Don't assume things. // When in doubt, return an error and explain the situation to the user. -// * All configuration input must come from the URL string in func Open() +// - All configuration input must come from the URL string in func Open() // or the Config{} struct in WithInstance. Don't os.Getenv(). type Driver interface { // Open returns a new driver instance configured with parameters diff --git a/src/vendor/github.com/golang-migrate/migrate/v4/database/pgx/README.md b/src/vendor/github.com/golang-migrate/migrate/v4/database/pgx/README.md index dfe150a7218..bad669315ae 100644 --- a/src/vendor/github.com/golang-migrate/migrate/v4/database/pgx/README.md +++ b/src/vendor/github.com/golang-migrate/migrate/v4/database/pgx/README.md @@ -1,5 +1,7 @@ # pgx +This package is for [pgx/v4](https://pkg.go.dev/github.com/jackc/pgx/v4). A backend for the newer [pgx/v5](https://pkg.go.dev/github.com/jackc/pgx/v5) is [also available](v5). + `pgx://user:password@host:port/dbname?query` | URL Query | WithInstance Config | Description | diff --git a/src/vendor/github.com/golang-migrate/migrate/v4/database/pgx/pgx.go b/src/vendor/github.com/golang-migrate/migrate/v4/database/pgx/pgx.go index 2fffe54df73..deaca94ea56 100644 --- a/src/vendor/github.com/golang-migrate/migrate/v4/database/pgx/pgx.go +++ b/src/vendor/github.com/golang-migrate/migrate/v4/database/pgx/pgx.go @@ -7,19 +7,19 @@ import ( "context" "database/sql" "fmt" - "go.uber.org/atomic" "io" - "io/ioutil" nurl "net/url" "regexp" "strconv" "strings" "time" + "go.uber.org/atomic" + "github.com/golang-migrate/migrate/v4" "github.com/golang-migrate/migrate/v4/database" "github.com/golang-migrate/migrate/v4/database/multistmt" - multierror "github.com/hashicorp/go-multierror" + "github.com/hashicorp/go-multierror" "github.com/jackc/pgconn" "github.com/jackc/pgerrcode" _ "github.com/jackc/pgx/v4/stdlib" @@ -28,6 +28,7 @@ import ( func init() { db := Postgres{} database.Register("pgx", &db) + database.Register("pgx4", &db) } var ( @@ -150,7 +151,7 @@ func (p *Postgres) Open(url string) (database.Driver, error) { // i.e. pgx://user:password@host:port/db => postgres://user:password@host:port/db purl.Scheme = "postgres" - db, err := sql.Open("pgx", migrate.FilterCustomQuery(purl).String()) + db, err := sql.Open("pgx/v4", migrate.FilterCustomQuery(purl).String()) if err != nil { return nil, err } @@ -265,7 +266,7 @@ func (p *Postgres) Run(migration io.Reader) error { } return err } - migr, err := ioutil.ReadAll(migration) + migr, err := io.ReadAll(migration) if err != nil { return err } diff --git a/src/vendor/github.com/golang-migrate/migrate/v4/source/driver.go b/src/vendor/github.com/golang-migrate/migrate/v4/source/driver.go index 63e6393c321..396eabfae74 100644 --- a/src/vendor/github.com/golang-migrate/migrate/v4/source/driver.go +++ b/src/vendor/github.com/golang-migrate/migrate/v4/source/driver.go @@ -17,23 +17,23 @@ var drivers = make(map[string]Driver) // Driver is the interface every source driver must implement. // // How to implement a source driver? -// 1. Implement this interface. -// 2. Optionally, add a function named `WithInstance`. -// This function should accept an existing source instance and a Config{} struct -// and return a driver instance. -// 3. Add a test that calls source/testing.go:Test() -// 4. Add own tests for Open(), WithInstance() (when provided) and Close(). -// All other functions are tested by tests in source/testing. -// Saves you some time and makes sure all source drivers behave the same way. -// 5. Call Register in init(). +// 1. Implement this interface. +// 2. Optionally, add a function named `WithInstance`. +// This function should accept an existing source instance and a Config{} struct +// and return a driver instance. +// 3. Add a test that calls source/testing.go:Test() +// 4. Add own tests for Open(), WithInstance() (when provided) and Close(). +// All other functions are tested by tests in source/testing. +// Saves you some time and makes sure all source drivers behave the same way. +// 5. Call Register in init(). // // Guidelines: -// * All configuration input must come from the URL string in func Open() +// - All configuration input must come from the URL string in func Open() // or the Config{} struct in WithInstance. Don't os.Getenv(). -// * Drivers are supposed to be read only. -// * Ideally don't load any contents (into memory) in Open or WithInstance. +// - Drivers are supposed to be read only. +// - Ideally don't load any contents (into memory) in Open or WithInstance. type Driver interface { - // Open returns a a new driver instance configured with parameters + // Open returns a new driver instance configured with parameters // coming from the URL string. Migrate will call this function // only once per instance. Open(url string) (Driver, error) diff --git a/src/vendor/github.com/golang-migrate/migrate/v4/source/migration.go b/src/vendor/github.com/golang-migrate/migrate/v4/source/migration.go index b8bb79020b7..74f6523cb74 100644 --- a/src/vendor/github.com/golang-migrate/migrate/v4/source/migration.go +++ b/src/vendor/github.com/golang-migrate/migrate/v4/source/migration.go @@ -66,11 +66,13 @@ func (i *Migrations) Append(m *Migration) (ok bool) { } func (i *Migrations) buildIndex() { - i.index = make(uintSlice, 0) + i.index = make(uintSlice, 0, len(i.migrations)) for version := range i.migrations { i.index = append(i.index, version) } - sort.Sort(i.index) + sort.Slice(i.index, func(x, y int) bool { + return i.index[x] < i.index[y] + }) } func (i *Migrations) First() (version uint, ok bool) { @@ -126,18 +128,6 @@ func (i *Migrations) findPos(version uint) int { type uintSlice []uint -func (s uintSlice) Len() int { - return len(s) -} - -func (s uintSlice) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} - -func (s uintSlice) Less(i, j int) bool { - return s[i] < s[j] -} - func (s uintSlice) Search(x uint) int { return sort.Search(len(s), func(i int) bool { return s[i] >= x }) } diff --git a/src/vendor/github.com/golang-migrate/migrate/v4/source/parse.go b/src/vendor/github.com/golang-migrate/migrate/v4/source/parse.go index 2f888fe7537..df085ae2995 100644 --- a/src/vendor/github.com/golang-migrate/migrate/v4/source/parse.go +++ b/src/vendor/github.com/golang-migrate/migrate/v4/source/parse.go @@ -16,8 +16,9 @@ var ( ) // Regex matches the following pattern: -// 123_name.up.ext -// 123_name.down.ext +// +// 123_name.up.ext +// 123_name.down.ext var Regex = regexp.MustCompile(`^([0-9]+)_(.*)\.(` + string(Down) + `|` + string(Up) + `)\.(.*)$`) // Parse returns Migration for matching Regex pattern. diff --git a/src/vendor/github.com/golang-migrate/migrate/v4/util.go b/src/vendor/github.com/golang-migrate/migrate/v4/util.go index 26131a3ffaa..663d68f1697 100644 --- a/src/vendor/github.com/golang-migrate/migrate/v4/util.go +++ b/src/vendor/github.com/golang-migrate/migrate/v4/util.go @@ -16,7 +16,6 @@ type MultiError struct { // NewMultiError returns an error type holding multiple errors. // // Deprecated: Use github.com/hashicorp/go-multierror instead -// func NewMultiError(errs ...error) MultiError { compactErrs := make([]error, 0) for _, e := range errs { @@ -53,7 +52,7 @@ func FilterCustomQuery(u *nurl.URL) *nurl.URL { ux := *u vx := make(nurl.Values) for k, v := range ux.Query() { - if len(k) <= 1 || (len(k) > 1 && k[0:2] != "x-") { + if len(k) <= 1 || k[0:2] != "x-" { vx[k] = v } } diff --git a/src/vendor/github.com/google/go-querystring/query/encode.go b/src/vendor/github.com/google/go-querystring/query/encode.go index 37080b19b5d..91198f819a7 100644 --- a/src/vendor/github.com/google/go-querystring/query/encode.go +++ b/src/vendor/github.com/google/go-querystring/query/encode.go @@ -51,8 +51,8 @@ type Encoder interface { // - the field is empty and its tag specifies the "omitempty" option // // The empty values are false, 0, any nil pointer or interface value, any array -// slice, map, or string of length zero, and any time.Time that returns true -// for IsZero(). +// slice, map, or string of length zero, and any type (such as time.Time) that +// returns true for IsZero(). // // The URL parameter name defaults to the struct field name but can be // specified in the struct field's tag value. The "url" key in the struct @@ -82,7 +82,14 @@ type Encoder interface { // // time.Time values default to encoding as RFC3339 timestamps. Including the // "unix" option signals that the field should be encoded as a Unix time (see -// time.Unix()) +// time.Unix()). The "unixmilli" and "unixnano" options will encode the number +// of milliseconds and nanoseconds, respectively, since January 1, 1970 (see +// time.UnixNano()). Including the "layout" struct tag (separate from the +// "url" tag) will use the value of the "layout" tag as a layout passed to +// time.Format. For example: +// +// // Encode a time.Time as YYYY-MM-DD +// Field time.Time `layout:"2006-01-02"` // // Slice and Array values default to encoding as multiple URL values of the // same name. Including the "comma" option signals that the field should be @@ -92,7 +99,13 @@ type Encoder interface { // Including the "brackets" option signals that the multiple URL values should // have "[]" appended to the value name. "numbered" will append a number to // the end of each incidence of the value name, example: -// name0=value0&name1=value1, etc. +// name0=value0&name1=value1, etc. Including the "del" struct tag (separate +// from the "url" tag) will use the value of the "del" tag as the delimiter. +// For example: +// +// // Encode a slice of bools as ints ("1" for true, "0" for false), +// // separated by exclamation points "!". +// Field []bool `url:",int" del:"!"` // // Anonymous struct fields are usually encoded as if their inner exported // fields were fields in the outer struct, subject to the standard Go @@ -151,11 +164,15 @@ func reflectValue(values url.Values, val reflect.Value, scope string) error { continue } name, opts := parseTag(tag) + if name == "" { - if sf.Anonymous && sv.Kind() == reflect.Struct { - // save embedded struct for later processing - embedded = append(embedded, sv) - continue + if sf.Anonymous { + v := reflect.Indirect(sv) + if v.IsValid() && v.Kind() == reflect.Struct { + // save embedded struct for later processing + embedded = append(embedded, v) + continue + } } name = sf.Name @@ -170,7 +187,9 @@ func reflectValue(values url.Values, val reflect.Value, scope string) error { } if sv.Type().Implements(encoderType) { - if !reflect.Indirect(sv).IsValid() { + // if sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(encoderType) { sv = reflect.New(sv.Type().Elem()) } @@ -181,28 +200,38 @@ func reflectValue(values url.Values, val reflect.Value, scope string) error { continue } + // recursively dereference pointers. break on nil pointers + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { - var del byte + var del string if opts.Contains("comma") { - del = ',' + del = "," } else if opts.Contains("space") { - del = ' ' + del = " " } else if opts.Contains("semicolon") { - del = ';' + del = ";" } else if opts.Contains("brackets") { name = name + "[]" + } else { + del = sf.Tag.Get("del") } - if del != 0 { + if del != "" { s := new(bytes.Buffer) first := true for i := 0; i < sv.Len(); i++ { if first { first = false } else { - s.WriteByte(del) + s.WriteString(del) } - s.WriteString(valueString(sv.Index(i), opts)) + s.WriteString(valueString(sv.Index(i), opts, sf)) } values.Add(name, s.String()) } else { @@ -211,30 +240,25 @@ func reflectValue(values url.Values, val reflect.Value, scope string) error { if opts.Contains("numbered") { k = fmt.Sprintf("%s%d", name, i) } - values.Add(k, valueString(sv.Index(i), opts)) + values.Add(k, valueString(sv.Index(i), opts, sf)) } } continue } - for sv.Kind() == reflect.Ptr { - if sv.IsNil() { - break - } - sv = sv.Elem() - } - if sv.Type() == timeType { - values.Add(name, valueString(sv, opts)) + values.Add(name, valueString(sv, opts, sf)) continue } if sv.Kind() == reflect.Struct { - reflectValue(values, sv, name) + if err := reflectValue(values, sv, name); err != nil { + return err + } continue } - values.Add(name, valueString(sv, opts)) + values.Add(name, valueString(sv, opts, sf)) } for _, f := range embedded { @@ -247,7 +271,7 @@ func reflectValue(values url.Values, val reflect.Value, scope string) error { } // valueString returns the string representation of a value. -func valueString(v reflect.Value, opts tagOptions) string { +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { for v.Kind() == reflect.Ptr { if v.IsNil() { return "" @@ -267,6 +291,15 @@ func valueString(v reflect.Value, opts tagOptions) string { if opts.Contains("unix") { return strconv.FormatInt(t.Unix(), 10) } + if opts.Contains("unixmilli") { + return strconv.FormatInt((t.UnixNano() / 1e6), 10) + } + if opts.Contains("unixnano") { + return strconv.FormatInt(t.UnixNano(), 10) + } + if layout := sf.Tag.Get("layout"); layout != "" { + return t.Format(layout) + } return t.Format(time.RFC3339) } @@ -291,8 +324,12 @@ func isEmptyValue(v reflect.Value) bool { return v.IsNil() } - if v.Type() == timeType { - return v.Interface().(time.Time).IsZero() + type zeroable interface { + IsZero() bool + } + + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() } return false diff --git a/src/vendor/github.com/jackc/pgconn/CHANGELOG.md b/src/vendor/github.com/jackc/pgconn/CHANGELOG.md index c496ea30ace..3550b437e4e 100644 --- a/src/vendor/github.com/jackc/pgconn/CHANGELOG.md +++ b/src/vendor/github.com/jackc/pgconn/CHANGELOG.md @@ -1,3 +1,46 @@ +# 1.14.0 (February 11, 2023) + +* Fix: each connection attempt to new node gets own timeout (Nathan Giardina) +* Set SNI for SSL connections (Stas Kelvich) +* Fix: CopyFrom I/O race (Tommy Reilly) +* Minor dependency upgrades + +# 1.13.0 (August 6, 2022) + +* Add sslpassword support (Eric McCormack and yun.xu) +* Add prefer-standby target_session_attrs support (sergey.bashilov) +* Fix GSS ErrorResponse handling (Oliver Tan) + +# 1.12.1 (May 7, 2022) + +* Fix: setting krbspn and krbsrvname in connection string (sireax) +* Add support for Unix sockets on Windows (Eno Compton) +* Stop ignoring ErrorResponse during SCRAM auth (Rafi Shamim) + +# 1.12.0 (April 21, 2022) + +* Add pluggable GSSAPI support (Oliver Tan) +* Fix: Consider any "0A000" error a possible cached plan changed error due to locale +* Better match psql fallback behavior with multiple hosts + +# 1.11.0 (February 7, 2022) + +* Support port in ip from LookupFunc to override config (James Hartig) +* Fix TLS connection timeout (Blake Embrey) +* Add support for read-only, primary, standby, prefer-standby target_session_attributes (Oscar) +* Fix connect when receiving NoticeResponse + +# 1.10.1 (November 20, 2021) + +* Close without waiting for response (Kei Kamikawa) +* Save waiting for network round-trip in CopyFrom (Rueian) +* Fix concurrency issue with ContextWatcher +* LRU.Get always checks context for cancellation / expiration (Georges Varouchas) + +# 1.10.0 (July 24, 2021) + +* net.Timeout errors are no longer returned when a query is canceled via context. A wrapped context error is returned. + # 1.9.0 (July 10, 2021) * pgconn.Timeout only is true for errors originating in pgconn (Michael Darr) diff --git a/src/vendor/github.com/jackc/pgconn/README.md b/src/vendor/github.com/jackc/pgconn/README.md index 1c698a11807..9af04fe7492 100644 --- a/src/vendor/github.com/jackc/pgconn/README.md +++ b/src/vendor/github.com/jackc/pgconn/README.md @@ -1,6 +1,12 @@ [![](https://godoc.org/github.com/jackc/pgconn?status.svg)](https://godoc.org/github.com/jackc/pgconn) ![CI](https://github.com/jackc/pgconn/workflows/CI/badge.svg) +--- + +This version is used with pgx `v4`. In pgx `v5` it is part of the https://github.com/jackc/pgx repository. + +--- + # pgconn Package pgconn is a low-level PostgreSQL database driver. It operates at nearly the same level as the C library libpq. diff --git a/src/vendor/github.com/jackc/pgconn/auth_scram.go b/src/vendor/github.com/jackc/pgconn/auth_scram.go index 6a143fcdc88..d8d7111633c 100644 --- a/src/vendor/github.com/jackc/pgconn/auth_scram.go +++ b/src/vendor/github.com/jackc/pgconn/auth_scram.go @@ -78,12 +78,14 @@ func (c *PgConn) rxSASLContinue() (*pgproto3.AuthenticationSASLContinue, error) if err != nil { return nil, err } - saslContinue, ok := msg.(*pgproto3.AuthenticationSASLContinue) - if ok { - return saslContinue, nil + switch m := msg.(type) { + case *pgproto3.AuthenticationSASLContinue: + return m, nil + case *pgproto3.ErrorResponse: + return nil, ErrorResponseToPgError(m) } - return nil, errors.New("expected AuthenticationSASLContinue message but received unexpected message") + return nil, fmt.Errorf("expected AuthenticationSASLContinue message but received unexpected message %T", msg) } func (c *PgConn) rxSASLFinal() (*pgproto3.AuthenticationSASLFinal, error) { @@ -91,12 +93,14 @@ func (c *PgConn) rxSASLFinal() (*pgproto3.AuthenticationSASLFinal, error) { if err != nil { return nil, err } - saslFinal, ok := msg.(*pgproto3.AuthenticationSASLFinal) - if ok { - return saslFinal, nil + switch m := msg.(type) { + case *pgproto3.AuthenticationSASLFinal: + return m, nil + case *pgproto3.ErrorResponse: + return nil, ErrorResponseToPgError(m) } - return nil, errors.New("expected AuthenticationSASLFinal message but received unexpected message") + return nil, fmt.Errorf("expected AuthenticationSASLFinal message but received unexpected message %T", msg) } type scramClient struct { diff --git a/src/vendor/github.com/jackc/pgconn/config.go b/src/vendor/github.com/jackc/pgconn/config.go index 172e7478b6a..4080f2c6a9c 100644 --- a/src/vendor/github.com/jackc/pgconn/config.go +++ b/src/vendor/github.com/jackc/pgconn/config.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "crypto/x509" + "encoding/pem" "errors" "fmt" "io" @@ -25,6 +26,7 @@ import ( type AfterConnectFunc func(ctx context.Context, pgconn *PgConn) error type ValidateConnectFunc func(ctx context.Context, pgconn *PgConn) error +type GetSSLPasswordFunc func(ctx context.Context) string // Config is the settings used to establish a connection to a PostgreSQL server. It must be created by ParseConfig. A // manually initialized Config will cause ConnectConfig to panic. @@ -41,7 +43,9 @@ type Config struct { BuildFrontend BuildFrontendFunc RuntimeParams map[string]string // Run-time parameters to set on connection as session default values (e.g. search_path or application_name) - Fallbacks []*FallbackConfig + KerberosSrvName string + KerberosSpn string + Fallbacks []*FallbackConfig // ValidateConnect is called during a connection attempt after a successful authentication with the PostgreSQL server. // It can be used to validate that the server is acceptable. If this returns an error the connection is closed and the next @@ -61,6 +65,13 @@ type Config struct { createdByParseConfig bool // Used to enforce created by ParseConfig rule. } +// ParseConfigOptions contains options that control how a config is built such as getsslpassword. +type ParseConfigOptions struct { + // GetSSLPassword gets the password to decrypt a SSL client certificate. This is analogous to the the libpq function + // PQsetSSLKeyPassHook_OpenSSL. + GetSSLPassword GetSSLPasswordFunc +} + // Copy returns a deep copy of the config that is safe to use and modify. // The only exception is the TLSConfig field: // according to the tls.Config docs it must not be modified after creation. @@ -98,10 +109,29 @@ type FallbackConfig struct { TLSConfig *tls.Config // nil disables TLS } +// isAbsolutePath checks if the provided value is an absolute path either +// beginning with a forward slash (as on Linux-based systems) or with a capital +// letter A-Z followed by a colon and a backslash, e.g., "C:\", (as on Windows). +func isAbsolutePath(path string) bool { + isWindowsPath := func(p string) bool { + if len(p) < 3 { + return false + } + drive := p[0] + colon := p[1] + backslash := p[2] + if drive >= 'A' && drive <= 'Z' && colon == ':' && backslash == '\\' { + return true + } + return false + } + return strings.HasPrefix(path, "/") || isWindowsPath(path) +} + // NetworkAddress converts a PostgreSQL host and port into network and address suitable for use with // net.Dial. func NetworkAddress(host string, port uint16) (network, address string) { - if strings.HasPrefix(host, "/") { + if isAbsolutePath(host) { network = "unix" address = filepath.Join(host, ".s.PGSQL.") + strconv.FormatInt(int64(port), 10) } else { @@ -111,10 +141,10 @@ func NetworkAddress(host string, port uint16) (network, address string) { return network, address } -// ParseConfig builds a *Config with similar behavior to the PostgreSQL standard C library libpq. It uses the same -// defaults as libpq (e.g. port=5432) and understands most PG* environment variables. ParseConfig closely matches -// the parsing behavior of libpq. connString may either be in URL format or keyword = value format (DSN style). See -// https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING for details. connString also may be +// ParseConfig builds a *Config from connString with similar behavior to the PostgreSQL standard C library libpq. It +// uses the same defaults as libpq (e.g. port=5432) and understands most PG* environment variables. ParseConfig closely +// matches the parsing behavior of libpq. connString may either be in URL format or keyword = value format (DSN style). +// See https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING for details. connString also may be // empty to only read from the environment. If a password is not supplied it will attempt to read the .pgpass file. // // # Example DSN @@ -138,21 +168,22 @@ func NetworkAddress(host string, port uint16) (network, address string) { // ParseConfig currently recognizes the following environment variable and their parameter key word equivalents passed // via database URL or DSN: // -// PGHOST -// PGPORT -// PGDATABASE -// PGUSER -// PGPASSWORD -// PGPASSFILE -// PGSERVICE -// PGSERVICEFILE -// PGSSLMODE -// PGSSLCERT -// PGSSLKEY -// PGSSLROOTCERT -// PGAPPNAME -// PGCONNECT_TIMEOUT -// PGTARGETSESSIONATTRS +// PGHOST +// PGPORT +// PGDATABASE +// PGUSER +// PGPASSWORD +// PGPASSFILE +// PGSERVICE +// PGSERVICEFILE +// PGSSLMODE +// PGSSLCERT +// PGSSLKEY +// PGSSLROOTCERT +// PGSSLPASSWORD +// PGAPPNAME +// PGCONNECT_TIMEOUT +// PGTARGETSESSIONATTRS // // See http://www.postgresql.org/docs/11/static/libpq-envars.html for details on the meaning of environment variables. // @@ -172,23 +203,29 @@ func NetworkAddress(host string, port uint16) (network, address string) { // sslmode "prefer" this means it will first try the main Config settings which use TLS, then it will try the fallback // which does not use TLS. This can lead to an unexpected unencrypted connection if the main TLS config is manually // changed later but the unencrypted fallback is present. Ensure there are no stale fallbacks when manually setting -// TLCConfig. +// TLSConfig. // // Other known differences with libpq: // -// If a host name resolves into multiple addresses, libpq will try all addresses. pgconn will only try the first. -// // When multiple hosts are specified, libpq allows them to have different passwords set via the .pgpass file. pgconn // does not. // // In addition, ParseConfig accepts the following options: // -// min_read_buffer_size -// The minimum size of the internal read buffer. Default 8192. -// servicefile -// libpq only reads servicefile from the PGSERVICEFILE environment variable. ParseConfig accepts servicefile as a -// part of the connection string. +// min_read_buffer_size +// The minimum size of the internal read buffer. Default 8192. +// servicefile +// libpq only reads servicefile from the PGSERVICEFILE environment variable. ParseConfig accepts servicefile as a +// part of the connection string. func ParseConfig(connString string) (*Config, error) { + var parseConfigOptions ParseConfigOptions + return ParseConfigWithOptions(connString, parseConfigOptions) +} + +// ParseConfigWithOptions builds a *Config from connString and options with similar behavior to the PostgreSQL standard +// C library libpq. options contains settings that cannot be specified in a connString such as providing a function to +// get the SSL password. +func ParseConfigWithOptions(connString string, options ParseConfigOptions) (*Config, error) { defaultSettings := defaultSettings() envSettings := parseEnvSettings() @@ -248,21 +285,33 @@ func ParseConfig(connString string) (*Config, error) { config.LookupFunc = makeDefaultResolver().LookupHost notRuntimeParams := map[string]struct{}{ - "host": struct{}{}, - "port": struct{}{}, - "database": struct{}{}, - "user": struct{}{}, - "password": struct{}{}, - "passfile": struct{}{}, - "connect_timeout": struct{}{}, - "sslmode": struct{}{}, - "sslkey": struct{}{}, - "sslcert": struct{}{}, - "sslrootcert": struct{}{}, - "target_session_attrs": struct{}{}, - "min_read_buffer_size": struct{}{}, - "service": struct{}{}, - "servicefile": struct{}{}, + "host": {}, + "port": {}, + "database": {}, + "user": {}, + "password": {}, + "passfile": {}, + "connect_timeout": {}, + "sslmode": {}, + "sslkey": {}, + "sslcert": {}, + "sslrootcert": {}, + "sslpassword": {}, + "sslsni": {}, + "krbspn": {}, + "krbsrvname": {}, + "target_session_attrs": {}, + "min_read_buffer_size": {}, + "service": {}, + "servicefile": {}, + } + + // Adding kerberos configuration + if _, present := settings["krbsrvname"]; present { + config.KerberosSrvName = settings["krbsrvname"] + } + if _, present := settings["krbspn"]; present { + config.KerberosSpn = settings["krbspn"] } for k, v := range settings { @@ -297,7 +346,7 @@ func ParseConfig(connString string) (*Config, error) { tlsConfigs = append(tlsConfigs, nil) } else { var err error - tlsConfigs, err = configTLS(settings, host) + tlsConfigs, err = configTLS(settings, host, options) if err != nil { return nil, &parseConfigError{connString: connString, msg: "failed to configure TLS", err: err} } @@ -329,10 +378,21 @@ func ParseConfig(connString string) (*Config, error) { } } - if settings["target_session_attrs"] == "read-write" { + switch tsa := settings["target_session_attrs"]; tsa { + case "read-write": config.ValidateConnect = ValidateConnectTargetSessionAttrsReadWrite - } else if settings["target_session_attrs"] != "any" { - return nil, &parseConfigError{connString: connString, msg: fmt.Sprintf("unknown target_session_attrs value: %v", settings["target_session_attrs"])} + case "read-only": + config.ValidateConnect = ValidateConnectTargetSessionAttrsReadOnly + case "primary": + config.ValidateConnect = ValidateConnectTargetSessionAttrsPrimary + case "standby": + config.ValidateConnect = ValidateConnectTargetSessionAttrsStandby + case "prefer-standby": + config.ValidateConnect = ValidateConnectTargetSessionAttrsPreferStandby + case "any": + // do nothing + default: + return nil, &parseConfigError{connString: connString, msg: fmt.Sprintf("unknown target_session_attrs value: %v", tsa)} } return config, nil @@ -365,7 +425,9 @@ func parseEnvSettings() map[string]string { "PGSSLMODE": "sslmode", "PGSSLKEY": "sslkey", "PGSSLCERT": "sslcert", + "PGSSLSNI": "sslsni", "PGSSLROOTCERT": "sslrootcert", + "PGSSLPASSWORD": "sslpassword", "PGTARGETSESSIONATTRS": "target_session_attrs", "PGSERVICE": "service", "PGSERVICEFILE": "servicefile", @@ -552,17 +614,22 @@ func parseServiceSettings(servicefilePath, serviceName string) (map[string]strin // configTLS uses libpq's TLS parameters to construct []*tls.Config. It is // necessary to allow returning multiple TLS configs as sslmode "allow" and // "prefer" allow fallback. -func configTLS(settings map[string]string, thisHost string) ([]*tls.Config, error) { +func configTLS(settings map[string]string, thisHost string, parseConfigOptions ParseConfigOptions) ([]*tls.Config, error) { host := thisHost sslmode := settings["sslmode"] sslrootcert := settings["sslrootcert"] sslcert := settings["sslcert"] sslkey := settings["sslkey"] + sslpassword := settings["sslpassword"] + sslsni := settings["sslsni"] // Match libpq default behavior if sslmode == "" { sslmode = "prefer" } + if sslsni == "" { + sslsni = "1" + } tlsConfig := &tls.Config{} @@ -645,14 +712,63 @@ func configTLS(settings map[string]string, thisHost string) ([]*tls.Config, erro } if sslcert != "" && sslkey != "" { - cert, err := tls.LoadX509KeyPair(sslcert, sslkey) + buf, err := ioutil.ReadFile(sslkey) if err != nil { - return nil, fmt.Errorf("unable to read cert: %w", err) + return nil, fmt.Errorf("unable to read sslkey: %w", err) } + block, _ := pem.Decode(buf) + var pemKey []byte + var decryptedKey []byte + var decryptedError error + // If PEM is encrypted, attempt to decrypt using pass phrase + if x509.IsEncryptedPEMBlock(block) { + // Attempt decryption with pass phrase + // NOTE: only supports RSA (PKCS#1) + if sslpassword != "" { + decryptedKey, decryptedError = x509.DecryptPEMBlock(block, []byte(sslpassword)) + } + //if sslpassword not provided or has decryption error when use it + //try to find sslpassword with callback function + if sslpassword == "" || decryptedError != nil { + if parseConfigOptions.GetSSLPassword != nil { + sslpassword = parseConfigOptions.GetSSLPassword(context.Background()) + } + if sslpassword == "" { + return nil, fmt.Errorf("unable to find sslpassword") + } + } + decryptedKey, decryptedError = x509.DecryptPEMBlock(block, []byte(sslpassword)) + // Should we also provide warning for PKCS#1 needed? + if decryptedError != nil { + return nil, fmt.Errorf("unable to decrypt key: %w", err) + } + pemBytes := pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: decryptedKey, + } + pemKey = pem.EncodeToMemory(&pemBytes) + } else { + pemKey = pem.EncodeToMemory(block) + } + certfile, err := ioutil.ReadFile(sslcert) + if err != nil { + return nil, fmt.Errorf("unable to read cert: %w", err) + } + cert, err := tls.X509KeyPair(certfile, pemKey) + if err != nil { + return nil, fmt.Errorf("unable to load cert: %w", err) + } tlsConfig.Certificates = []tls.Certificate{cert} } + // Set Server Name Indication (SNI), if enabled by connection parameters. + // Per RFC 6066, do not set it if the host is a literal IP address (IPv4 + // or IPv6). + if sslsni == "1" && net.ParseIP(host) == nil { + tlsConfig.ServerName = host + } + switch sslmode { case "allow": return []*tls.Config{nil, tlsConfig}, nil @@ -727,3 +843,63 @@ func ValidateConnectTargetSessionAttrsReadWrite(ctx context.Context, pgConn *PgC return nil } + +// ValidateConnectTargetSessionAttrsReadOnly is an ValidateConnectFunc that implements libpq compatible +// target_session_attrs=read-only. +func ValidateConnectTargetSessionAttrsReadOnly(ctx context.Context, pgConn *PgConn) error { + result := pgConn.ExecParams(ctx, "show transaction_read_only", nil, nil, nil, nil).Read() + if result.Err != nil { + return result.Err + } + + if string(result.Rows[0][0]) != "on" { + return errors.New("connection is not read only") + } + + return nil +} + +// ValidateConnectTargetSessionAttrsStandby is an ValidateConnectFunc that implements libpq compatible +// target_session_attrs=standby. +func ValidateConnectTargetSessionAttrsStandby(ctx context.Context, pgConn *PgConn) error { + result := pgConn.ExecParams(ctx, "select pg_is_in_recovery()", nil, nil, nil, nil).Read() + if result.Err != nil { + return result.Err + } + + if string(result.Rows[0][0]) != "t" { + return errors.New("server is not in hot standby mode") + } + + return nil +} + +// ValidateConnectTargetSessionAttrsPrimary is an ValidateConnectFunc that implements libpq compatible +// target_session_attrs=primary. +func ValidateConnectTargetSessionAttrsPrimary(ctx context.Context, pgConn *PgConn) error { + result := pgConn.ExecParams(ctx, "select pg_is_in_recovery()", nil, nil, nil, nil).Read() + if result.Err != nil { + return result.Err + } + + if string(result.Rows[0][0]) == "t" { + return errors.New("server is in standby mode") + } + + return nil +} + +// ValidateConnectTargetSessionAttrsPreferStandby is an ValidateConnectFunc that implements libpq compatible +// target_session_attrs=prefer-standby. +func ValidateConnectTargetSessionAttrsPreferStandby(ctx context.Context, pgConn *PgConn) error { + result := pgConn.ExecParams(ctx, "select pg_is_in_recovery()", nil, nil, nil, nil).Read() + if result.Err != nil { + return result.Err + } + + if string(result.Rows[0][0]) != "t" { + return &NotPreferredError{err: errors.New("server is not in hot standby mode")} + } + + return nil +} diff --git a/src/vendor/github.com/jackc/pgconn/defaults.go b/src/vendor/github.com/jackc/pgconn/defaults.go index f69cad317a7..c7209fdd37f 100644 --- a/src/vendor/github.com/jackc/pgconn/defaults.go +++ b/src/vendor/github.com/jackc/pgconn/defaults.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package pgconn diff --git a/src/vendor/github.com/jackc/pgconn/errors.go b/src/vendor/github.com/jackc/pgconn/errors.go index 64401d659ad..66d35584afc 100644 --- a/src/vendor/github.com/jackc/pgconn/errors.go +++ b/src/vendor/github.com/jackc/pgconn/errors.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "net" "net/url" "regexp" "strings" @@ -105,6 +106,15 @@ func (e *parseConfigError) Unwrap() error { return e.err } +// preferContextOverNetTimeoutError returns ctx.Err() if ctx.Err() is present and err is a net.Error with Timeout() == +// true. Otherwise returns err. +func preferContextOverNetTimeoutError(ctx context.Context, err error) error { + if err, ok := err.(net.Error); ok && err.Timeout() && ctx.Err() != nil { + return &errTimeout{err: ctx.Err()} + } + return err +} + type pgconnError struct { msg string err error @@ -209,3 +219,20 @@ func redactURL(u *url.URL) string { } return u.String() } + +type NotPreferredError struct { + err error + safeToRetry bool +} + +func (e *NotPreferredError) Error() string { + return fmt.Sprintf("standby server not found: %s", e.err.Error()) +} + +func (e *NotPreferredError) SafeToRetry() bool { + return e.safeToRetry +} + +func (e *NotPreferredError) Unwrap() error { + return e.err +} diff --git a/src/vendor/github.com/jackc/pgconn/internal/ctxwatch/context_watcher.go b/src/vendor/github.com/jackc/pgconn/internal/ctxwatch/context_watcher.go index 391f0b79102..b39cb3ee57a 100644 --- a/src/vendor/github.com/jackc/pgconn/internal/ctxwatch/context_watcher.go +++ b/src/vendor/github.com/jackc/pgconn/internal/ctxwatch/context_watcher.go @@ -2,6 +2,7 @@ package ctxwatch import ( "context" + "sync" ) // ContextWatcher watches a context and performs an action when the context is canceled. It can watch one context at a @@ -10,8 +11,10 @@ type ContextWatcher struct { onCancel func() onUnwatchAfterCancel func() unwatchChan chan struct{} - watchInProgress bool - onCancelWasCalled bool + + lock sync.Mutex + watchInProgress bool + onCancelWasCalled bool } // NewContextWatcher returns a ContextWatcher. onCancel will be called when a watched context is canceled. @@ -29,6 +32,9 @@ func NewContextWatcher(onCancel func(), onUnwatchAfterCancel func()) *ContextWat // Watch starts watching ctx. If ctx is canceled then the onCancel function passed to NewContextWatcher will be called. func (cw *ContextWatcher) Watch(ctx context.Context) { + cw.lock.Lock() + defer cw.lock.Unlock() + if cw.watchInProgress { panic("Watch already in progress") } @@ -54,6 +60,9 @@ func (cw *ContextWatcher) Watch(ctx context.Context) { // Unwatch stops watching the previously watched context. If the onCancel function passed to NewContextWatcher was // called then onUnwatchAfterCancel will also be called. func (cw *ContextWatcher) Unwatch() { + cw.lock.Lock() + defer cw.lock.Unlock() + if cw.watchInProgress { cw.unwatchChan <- struct{}{} if cw.onCancelWasCalled { diff --git a/src/vendor/github.com/jackc/pgconn/krb5.go b/src/vendor/github.com/jackc/pgconn/krb5.go new file mode 100644 index 00000000000..08427b8e69a --- /dev/null +++ b/src/vendor/github.com/jackc/pgconn/krb5.go @@ -0,0 +1,99 @@ +package pgconn + +import ( + "errors" + "fmt" + + "github.com/jackc/pgproto3/v2" +) + +// NewGSSFunc creates a GSS authentication provider, for use with +// RegisterGSSProvider. +type NewGSSFunc func() (GSS, error) + +var newGSS NewGSSFunc + +// RegisterGSSProvider registers a GSS authentication provider. For example, if +// you need to use Kerberos to authenticate with your server, add this to your +// main package: +// +// import "github.com/otan/gopgkrb5" +// +// func init() { +// pgconn.RegisterGSSProvider(func() (pgconn.GSS, error) { return gopgkrb5.NewGSS() }) +// } +func RegisterGSSProvider(newGSSArg NewGSSFunc) { + newGSS = newGSSArg +} + +// GSS provides GSSAPI authentication (e.g., Kerberos). +type GSS interface { + GetInitToken(host string, service string) ([]byte, error) + GetInitTokenFromSPN(spn string) ([]byte, error) + Continue(inToken []byte) (done bool, outToken []byte, err error) +} + +func (c *PgConn) gssAuth() error { + if newGSS == nil { + return errors.New("kerberos error: no GSSAPI provider registered, see https://github.com/otan/gopgkrb5") + } + cli, err := newGSS() + if err != nil { + return err + } + + var nextData []byte + if c.config.KerberosSpn != "" { + // Use the supplied SPN if provided. + nextData, err = cli.GetInitTokenFromSPN(c.config.KerberosSpn) + } else { + // Allow the kerberos service name to be overridden + service := "postgres" + if c.config.KerberosSrvName != "" { + service = c.config.KerberosSrvName + } + nextData, err = cli.GetInitToken(c.config.Host, service) + } + if err != nil { + return err + } + + for { + gssResponse := &pgproto3.GSSResponse{ + Data: nextData, + } + _, err = c.conn.Write(gssResponse.Encode(nil)) + if err != nil { + return err + } + resp, err := c.rxGSSContinue() + if err != nil { + return err + } + var done bool + done, nextData, err = cli.Continue(resp.Data) + if err != nil { + return err + } + if done { + break + } + } + return nil +} + +func (c *PgConn) rxGSSContinue() (*pgproto3.AuthenticationGSSContinue, error) { + msg, err := c.receiveMessage() + if err != nil { + return nil, err + } + + switch m := msg.(type) { + case *pgproto3.AuthenticationGSSContinue: + return m, nil + case *pgproto3.ErrorResponse: + return nil, ErrorResponseToPgError(m) + } + + return nil, fmt.Errorf("expected AuthenticationGSSContinue message but received unexpected message %T", msg) +} diff --git a/src/vendor/github.com/jackc/pgconn/pgconn.go b/src/vendor/github.com/jackc/pgconn/pgconn.go index a17a108da77..6601194ceeb 100644 --- a/src/vendor/github.com/jackc/pgconn/pgconn.go +++ b/src/vendor/github.com/jackc/pgconn/pgconn.go @@ -11,6 +11,7 @@ import ( "io" "math" "net" + "strconv" "strings" "sync" "time" @@ -44,7 +45,8 @@ type Notification struct { // DialFunc is a function that can be used to connect to a PostgreSQL server. type DialFunc func(ctx context.Context, network, addr string) (net.Conn, error) -// LookupFunc is a function that can be used to lookup IPs addrs from host. +// LookupFunc is a function that can be used to lookup IPs addrs from host. Optionally an ip:port combination can be +// returned in order to override the connection string's port. type LookupFunc func(ctx context.Context, host string) (addrs []string, err error) // BuildFrontendFunc is a function that can be used to create Frontend implementation for connection. @@ -97,7 +99,7 @@ type PgConn struct { } // Connect establishes a connection to a PostgreSQL server using the environment and connString (in URL or DSN format) -// to provide configuration. See documention for ParseConfig for details. ctx can be used to cancel a connect attempt. +// to provide configuration. See documentation for ParseConfig for details. ctx can be used to cancel a connect attempt. func Connect(ctx context.Context, connString string) (*PgConn, error) { config, err := ParseConfig(connString) if err != nil { @@ -107,6 +109,18 @@ func Connect(ctx context.Context, connString string) (*PgConn, error) { return ConnectConfig(ctx, config) } +// Connect establishes a connection to a PostgreSQL server using the environment and connString (in URL or DSN format) +// and ParseConfigOptions to provide additional configuration. See documentation for ParseConfig for details. ctx can be +// used to cancel a connect attempt. +func ConnectWithOptions(ctx context.Context, connString string, parseConfigOptions ParseConfigOptions) (*PgConn, error) { + config, err := ParseConfigWithOptions(connString, parseConfigOptions) + if err != nil { + return nil, err + } + + return ConnectConfig(ctx, config) +} + // Connect establishes a connection to a PostgreSQL server using config. config must have been constructed with // ParseConfig. ctx can be used to cancel a connect attempt. // @@ -114,19 +128,13 @@ func Connect(ctx context.Context, connString string) (*PgConn, error) { // authentication error will terminate the chain of attempts (like libpq: // https://www.postgresql.org/docs/11/libpq-connect.html#LIBPQ-MULTIPLE-HOSTS) and be returned as the error. Otherwise, // if all attempts fail the last error is returned. -func ConnectConfig(ctx context.Context, config *Config) (pgConn *PgConn, err error) { +func ConnectConfig(octx context.Context, config *Config) (pgConn *PgConn, err error) { // Default values are set in ParseConfig. Enforce initial creation by ParseConfig rather than setting defaults from // zero values. if !config.createdByParseConfig { panic("config must be created by ParseConfig") } - // ConnectTimeout restricts the whole connection process. - if config.ConnectTimeout != 0 { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, config.ConnectTimeout) - defer cancel() - } // Simplify usage by treating primary config and fallbacks the same. fallbackConfigs := []*FallbackConfig{ { @@ -136,7 +144,7 @@ func ConnectConfig(ctx context.Context, config *Config) (pgConn *PgConn, err err }, } fallbackConfigs = append(fallbackConfigs, config.Fallbacks...) - + ctx := octx fallbackConfigs, err = expandWithIPs(ctx, config.LookupFunc, fallbackConfigs) if err != nil { return nil, &connectError{config: config, msg: "hostname resolving error", err: err} @@ -146,17 +154,44 @@ func ConnectConfig(ctx context.Context, config *Config) (pgConn *PgConn, err err return nil, &connectError{config: config, msg: "hostname resolving error", err: errors.New("ip addr wasn't found")} } + foundBestServer := false + var fallbackConfig *FallbackConfig for _, fc := range fallbackConfigs { - pgConn, err = connect(ctx, config, fc) + // ConnectTimeout restricts the whole connection process. + if config.ConnectTimeout != 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(octx, config.ConnectTimeout) + defer cancel() + } else { + ctx = octx + } + pgConn, err = connect(ctx, config, fc, false) if err == nil { + foundBestServer = true break } else if pgerr, ok := err.(*PgError); ok { err = &connectError{config: config, msg: "server error", err: pgerr} - ERRCODE_INVALID_PASSWORD := "28P01" // worng password - ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION := "28000" // db does not exist - if pgerr.Code == ERRCODE_INVALID_PASSWORD || pgerr.Code == ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION { + const ERRCODE_INVALID_PASSWORD = "28P01" // wrong password + const ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION = "28000" // wrong password or bad pg_hba.conf settings + const ERRCODE_INVALID_CATALOG_NAME = "3D000" // db does not exist + const ERRCODE_INSUFFICIENT_PRIVILEGE = "42501" // missing connect privilege + if pgerr.Code == ERRCODE_INVALID_PASSWORD || + pgerr.Code == ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION || + pgerr.Code == ERRCODE_INVALID_CATALOG_NAME || + pgerr.Code == ERRCODE_INSUFFICIENT_PRIVILEGE { break } + } else if cerr, ok := err.(*connectError); ok { + if _, ok := cerr.err.(*NotPreferredError); ok { + fallbackConfig = fc + } + } + } + + if !foundBestServer && fallbackConfig != nil { + pgConn, err = connect(ctx, config, fallbackConfig, true) + if pgerr, ok := err.(*PgError); ok { + err = &connectError{config: config, msg: "server error", err: pgerr} } } @@ -180,7 +215,7 @@ func expandWithIPs(ctx context.Context, lookupFn LookupFunc, fallbacks []*Fallba for _, fb := range fallbacks { // skip resolve for unix sockets - if strings.HasPrefix(fb.Host, "/") { + if isAbsolutePath(fb.Host) { configs = append(configs, &FallbackConfig{ Host: fb.Host, Port: fb.Port, @@ -196,18 +231,32 @@ func expandWithIPs(ctx context.Context, lookupFn LookupFunc, fallbacks []*Fallba } for _, ip := range ips { - configs = append(configs, &FallbackConfig{ - Host: ip, - Port: fb.Port, - TLSConfig: fb.TLSConfig, - }) + splitIP, splitPort, err := net.SplitHostPort(ip) + if err == nil { + port, err := strconv.ParseUint(splitPort, 10, 16) + if err != nil { + return nil, fmt.Errorf("error parsing port (%s) from lookup: %w", splitPort, err) + } + configs = append(configs, &FallbackConfig{ + Host: splitIP, + Port: uint16(port), + TLSConfig: fb.TLSConfig, + }) + } else { + configs = append(configs, &FallbackConfig{ + Host: ip, + Port: fb.Port, + TLSConfig: fb.TLSConfig, + }) + } } } return configs, nil } -func connect(ctx context.Context, config *Config, fallbackConfig *FallbackConfig) (*PgConn, error) { +func connect(ctx context.Context, config *Config, fallbackConfig *FallbackConfig, + ignoreNotPreferredErr bool) (*PgConn, error) { pgConn := new(PgConn) pgConn.config = config pgConn.wbuf = make([]byte, 0, wbufLen) @@ -215,7 +264,7 @@ func connect(ctx context.Context, config *Config, fallbackConfig *FallbackConfig var err error network, address := NetworkAddress(fallbackConfig.Host, fallbackConfig.Port) - pgConn.conn, err = config.DialFunc(ctx, network, address) + netConn, err := config.DialFunc(ctx, network, address) if err != nil { var netErr net.Error if errors.As(err, &netErr) && netErr.Timeout() { @@ -224,24 +273,27 @@ func connect(ctx context.Context, config *Config, fallbackConfig *FallbackConfig return nil, &connectError{config: config, msg: "dial error", err: err} } - pgConn.parameterStatuses = make(map[string]string) + pgConn.conn = netConn + pgConn.contextWatcher = newContextWatcher(netConn) + pgConn.contextWatcher.Watch(ctx) if fallbackConfig.TLSConfig != nil { - if err := pgConn.startTLS(fallbackConfig.TLSConfig); err != nil { - pgConn.conn.Close() + tlsConn, err := startTLS(netConn, fallbackConfig.TLSConfig) + pgConn.contextWatcher.Unwatch() // Always unwatch `netConn` after TLS. + if err != nil { + netConn.Close() return nil, &connectError{config: config, msg: "tls error", err: err} } - } - pgConn.status = connStatusConnecting - pgConn.contextWatcher = ctxwatch.NewContextWatcher( - func() { pgConn.conn.SetDeadline(time.Date(1, 1, 1, 1, 1, 1, 1, time.UTC)) }, - func() { pgConn.conn.SetDeadline(time.Time{}) }, - ) + pgConn.conn = tlsConn + pgConn.contextWatcher = newContextWatcher(tlsConn) + pgConn.contextWatcher.Watch(ctx) + } - pgConn.contextWatcher.Watch(ctx) defer pgConn.contextWatcher.Unwatch() + pgConn.parameterStatuses = make(map[string]string) + pgConn.status = connStatusConnecting pgConn.frontend = config.BuildFrontend(pgConn.conn, pgConn.conn) startupMsg := pgproto3.StartupMessage{ @@ -271,7 +323,7 @@ func connect(ctx context.Context, config *Config, fallbackConfig *FallbackConfig if err, ok := err.(*PgError); ok { return nil, err } - return nil, &connectError{config: config, msg: "failed to receive message", err: err} + return nil, &connectError{config: config, msg: "failed to receive message", err: preferContextOverNetTimeoutError(ctx, err)} } switch msg := msg.(type) { @@ -299,7 +351,12 @@ func connect(ctx context.Context, config *Config, fallbackConfig *FallbackConfig pgConn.conn.Close() return nil, &connectError{config: config, msg: "failed SASL auth", err: err} } - + case *pgproto3.AuthenticationGSS: + err = pgConn.gssAuth() + if err != nil { + pgConn.conn.Close() + return nil, &connectError{config: config, msg: "failed GSS auth", err: err} + } case *pgproto3.ReadyForQuery: pgConn.status = connStatusIdle if config.ValidateConnect != nil { @@ -312,12 +369,15 @@ func connect(ctx context.Context, config *Config, fallbackConfig *FallbackConfig err := config.ValidateConnect(ctx, pgConn) if err != nil { + if _, ok := err.(*NotPreferredError); ignoreNotPreferredErr && ok { + return pgConn, nil + } pgConn.conn.Close() return nil, &connectError{config: config, msg: "ValidateConnect failed", err: err} } } return pgConn, nil - case *pgproto3.ParameterStatus: + case *pgproto3.ParameterStatus, *pgproto3.NoticeResponse: // handled by ReceiveMessage case *pgproto3.ErrorResponse: pgConn.conn.Close() @@ -329,24 +389,29 @@ func connect(ctx context.Context, config *Config, fallbackConfig *FallbackConfig } } -func (pgConn *PgConn) startTLS(tlsConfig *tls.Config) (err error) { - err = binary.Write(pgConn.conn, binary.BigEndian, []int32{8, 80877103}) +func newContextWatcher(conn net.Conn) *ctxwatch.ContextWatcher { + return ctxwatch.NewContextWatcher( + func() { conn.SetDeadline(time.Date(1, 1, 1, 1, 1, 1, 1, time.UTC)) }, + func() { conn.SetDeadline(time.Time{}) }, + ) +} + +func startTLS(conn net.Conn, tlsConfig *tls.Config) (net.Conn, error) { + err := binary.Write(conn, binary.BigEndian, []int32{8, 80877103}) if err != nil { - return + return nil, err } response := make([]byte, 1) - if _, err = io.ReadFull(pgConn.conn, response); err != nil { - return + if _, err = io.ReadFull(conn, response); err != nil { + return nil, err } if response[0] != 'S' { - return errors.New("server refused TLS connection") + return nil, errors.New("server refused TLS connection") } - pgConn.conn = tls.Client(pgConn.conn, tlsConfig) - - return nil + return tls.Client(conn, tlsConfig), nil } func (pgConn *PgConn) txPasswordMessage(password string) (err error) { @@ -434,7 +499,10 @@ func (pgConn *PgConn) ReceiveMessage(ctx context.Context) (pgproto3.BackendMessa msg, err := pgConn.receiveMessage() if err != nil { - err = &pgconnError{msg: "receive message failed", err: err, safeToRetry: true} + err = &pgconnError{ + msg: "receive message failed", + err: preferContextOverNetTimeoutError(ctx, err), + safeToRetry: true} } return msg, err } @@ -469,8 +537,6 @@ func (pgConn *PgConn) peekMessage() (pgproto3.BackendMessage, error) { isNetErr := errors.As(err, &netErr) if !(isNetErr && netErr.Timeout()) { pgConn.asyncClose() - } else if isNetErr && netErr.Timeout() { - err = &errTimeout{err: err} } return nil, err @@ -489,8 +555,6 @@ func (pgConn *PgConn) receiveMessage() (pgproto3.BackendMessage, error) { isNetErr := errors.As(err, &netErr) if !(isNetErr && netErr.Timeout()) { pgConn.asyncClose() - } else if isNetErr && netErr.Timeout() { - err = &errTimeout{err: err} } return nil, err @@ -579,7 +643,6 @@ func (pgConn *PgConn) Close(ctx context.Context) error { // // See https://github.com/jackc/pgx/issues/637 pgConn.conn.Write([]byte{'X', 0, 0, 0, 4}) - pgConn.conn.Read(make([]byte, 1)) return pgConn.conn.Close() } @@ -606,7 +669,6 @@ func (pgConn *PgConn) asyncClose() { pgConn.conn.SetDeadline(deadline) pgConn.conn.Write([]byte{'X', 0, 0, 0, 4}) - pgConn.conn.Read(make([]byte, 1)) }() } @@ -785,7 +847,7 @@ readloop: msg, err := pgConn.receiveMessage() if err != nil { pgConn.asyncClose() - return nil, err + return nil, preferContextOverNetTimeoutError(ctx, err) } switch msg := msg.(type) { @@ -888,7 +950,7 @@ func (pgConn *PgConn) WaitForNotification(ctx context.Context) error { if ctx != context.Background() { select { case <-ctx.Done(): - return ctx.Err() + return newContextAlreadyDoneError(ctx) default: } @@ -899,7 +961,7 @@ func (pgConn *PgConn) WaitForNotification(ctx context.Context) error { for { msg, err := pgConn.receiveMessage() if err != nil { - return err + return preferContextOverNetTimeoutError(ctx, err) } switch msg.(type) { @@ -1136,7 +1198,7 @@ func (pgConn *PgConn) CopyTo(ctx context.Context, w io.Writer, sql string) (Comm msg, err := pgConn.receiveMessage() if err != nil { pgConn.asyncClose() - return nil, err + return nil, preferContextOverNetTimeoutError(ctx, err) } switch msg := msg.(type) { @@ -1188,33 +1250,15 @@ func (pgConn *PgConn) CopyFrom(ctx context.Context, r io.Reader, sql string) (Co return nil, &writeError{err: err, safeToRetry: n == 0} } - // Read until copy in response or error. - var commandTag CommandTag - var pgErr error - pendingCopyInResponse := true - for pendingCopyInResponse { - msg, err := pgConn.receiveMessage() - if err != nil { - pgConn.asyncClose() - return nil, err - } - - switch msg := msg.(type) { - case *pgproto3.CopyInResponse: - pendingCopyInResponse = false - case *pgproto3.ErrorResponse: - pgErr = ErrorResponseToPgError(msg) - case *pgproto3.ReadyForQuery: - return commandTag, pgErr - } - } - // Send copy data abortCopyChan := make(chan struct{}) copyErrChan := make(chan error, 1) signalMessageChan := pgConn.signalMessage() + var wg sync.WaitGroup + wg.Add(1) go func() { + defer wg.Done() buf := make([]byte, 0, 65536) buf = append(buf, 'd') sp := len(buf) @@ -1247,6 +1291,7 @@ func (pgConn *PgConn) CopyFrom(ctx context.Context, r io.Reader, sql string) (Co } }() + var pgErr error var copyErr error for copyErr == nil && pgErr == nil { select { @@ -1255,7 +1300,7 @@ func (pgConn *PgConn) CopyFrom(ctx context.Context, r io.Reader, sql string) (Co msg, err := pgConn.receiveMessage() if err != nil { pgConn.asyncClose() - return nil, err + return nil, preferContextOverNetTimeoutError(ctx, err) } switch msg := msg.(type) { @@ -1267,6 +1312,8 @@ func (pgConn *PgConn) CopyFrom(ctx context.Context, r io.Reader, sql string) (Co } } close(abortCopyChan) + // Make sure io goroutine finishes before writing. + wg.Wait() buf = buf[:0] if copyErr == io.EOF || pgErr != nil { @@ -1283,11 +1330,12 @@ func (pgConn *PgConn) CopyFrom(ctx context.Context, r io.Reader, sql string) (Co } // Read results + var commandTag CommandTag for { msg, err := pgConn.receiveMessage() if err != nil { pgConn.asyncClose() - return nil, err + return nil, preferContextOverNetTimeoutError(ctx, err) } switch msg := msg.(type) { @@ -1329,7 +1377,7 @@ func (mrr *MultiResultReader) receiveMessage() (pgproto3.BackendMessage, error) if err != nil { mrr.pgConn.contextWatcher.Unwatch() - mrr.err = err + mrr.err = preferContextOverNetTimeoutError(mrr.ctx, err) mrr.closed = true mrr.pgConn.asyncClose() return nil, mrr.err @@ -1536,6 +1584,7 @@ func (rr *ResultReader) receiveMessage() (msg pgproto3.BackendMessage, err error } if err != nil { + err = preferContextOverNetTimeoutError(rr.ctx, err) rr.concludeCommand(nil, err) rr.pgConn.contextWatcher.Unwatch() rr.closed = true @@ -1715,10 +1764,7 @@ func Construct(hc *HijackedConn) (*PgConn, error) { cleanupDone: make(chan struct{}), } - pgConn.contextWatcher = ctxwatch.NewContextWatcher( - func() { pgConn.conn.SetDeadline(time.Date(1, 1, 1, 1, 1, 1, 1, time.UTC)) }, - func() { pgConn.conn.SetDeadline(time.Time{}) }, - ) + pgConn.contextWatcher = newContextWatcher(pgConn.conn) return pgConn, nil } diff --git a/src/vendor/github.com/jackc/pgconn/stmtcache/lru.go b/src/vendor/github.com/jackc/pgconn/stmtcache/lru.go index f58f2ac3446..f0fb53b9c25 100644 --- a/src/vendor/github.com/jackc/pgconn/stmtcache/lru.go +++ b/src/vendor/github.com/jackc/pgconn/stmtcache/lru.go @@ -42,6 +42,14 @@ func NewLRU(conn *pgconn.PgConn, mode int, cap int) *LRU { // Get returns the prepared statement description for sql preparing or describing the sql on the server as needed. func (c *LRU) Get(ctx context.Context, sql string) (*pgconn.StatementDescription, error) { + if ctx != context.Background() { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + } + // flush an outstanding bad statements txStatus := c.conn.TxStatus() if (txStatus == 'I' || txStatus == 'T') && len(c.stmtsToClear) > 0 { @@ -94,10 +102,14 @@ func (c *LRU) StatementErrored(sql string, err error) { return } - isInvalidCachedPlanError := pgErr.Severity == "ERROR" && - pgErr.Code == "0A000" && - pgErr.Message == "cached plan must not change result type" - if isInvalidCachedPlanError { + // https://github.com/jackc/pgx/issues/1162 + // + // We used to look for the message "cached plan must not change result type". However, that message can be localized. + // Unfortunately, error code "0A000" - "FEATURE NOT SUPPORTED" is used for many different errors and the only way to + // tell the difference is by the message. But all that happens is we clear a statement that we otherwise wouldn't + // have so it should be safe. + possibleInvalidCachedPlanError := pgErr.Code == "0A000" + if possibleInvalidCachedPlanError { c.stmtsToClear = append(c.stmtsToClear, sql) } } diff --git a/src/vendor/github.com/jackc/pgerrcode/errcode.go b/src/vendor/github.com/jackc/pgerrcode/errcode.go index c4b6d310f8a..654be186c77 100644 --- a/src/vendor/github.com/jackc/pgerrcode/errcode.go +++ b/src/vendor/github.com/jackc/pgerrcode/errcode.go @@ -1,7 +1,7 @@ // Package pgerrcode contains constants for PostgreSQL error codes. package pgerrcode -// Source: https://www.postgresql.org/docs/13/errcodes-appendix.html +// Source: https://www.postgresql.org/docs/14/errcodes-appendix.html // See gen.rb for script that can convert the error code table to Go code. const ( @@ -295,6 +295,7 @@ const ( CrashShutdown = "57P02" CannotConnectNow = "57P03" DatabaseDropped = "57P04" + IdleSessionTimeout = "57P05" // Class 58 — System Error (errors external to PostgreSQL itself) SystemError = "58000" @@ -678,7 +679,7 @@ func IsObjectNotInPrerequisiteState(code string) bool { // IsOperatorIntervention asserts the error code class is Class 57 — Operator Intervention func IsOperatorIntervention(code string) bool { switch code { - case OperatorIntervention, QueryCanceled, AdminShutdown, CrashShutdown, CannotConnectNow, DatabaseDropped: + case OperatorIntervention, QueryCanceled, AdminShutdown, CrashShutdown, CannotConnectNow, DatabaseDropped, IdleSessionTimeout: return true } return false diff --git a/src/vendor/github.com/jackc/pgerrcode/gen.rb b/src/vendor/github.com/jackc/pgerrcode/gen.rb index 910a4e4951f..4d42477fa69 100644 --- a/src/vendor/github.com/jackc/pgerrcode/gen.rb +++ b/src/vendor/github.com/jackc/pgerrcode/gen.rb @@ -1,4 +1,4 @@ -# Run this script against the data in table A.1. on https://www.postgresql.org/docs/13/errcodes-appendix.html. +# Run this script against the data in table A.1. on https://www.postgresql.org/docs/14/errcodes-appendix.html. # # Source data should be formatted like the following: # @@ -57,7 +57,7 @@ def build_assert_func(last_cls, last_cls_full, cls_errs) // Package pgerrcode contains constants for PostgreSQL error codes. package pgerrcode -// Source: https://www.postgresql.org/docs/13/errcodes-appendix.html +// Source: https://www.postgresql.org/docs/14/errcodes-appendix.html // See gen.rb for script that can convert the error code table to Go code. const ( diff --git a/src/vendor/github.com/jackc/pgproto3/v2/README.md b/src/vendor/github.com/jackc/pgproto3/v2/README.md index 565b3efd5b2..77a31700ad3 100644 --- a/src/vendor/github.com/jackc/pgproto3/v2/README.md +++ b/src/vendor/github.com/jackc/pgproto3/v2/README.md @@ -1,6 +1,12 @@ [![](https://godoc.org/github.com/jackc/pgproto3?status.svg)](https://godoc.org/github.com/jackc/pgproto3) [![Build Status](https://travis-ci.org/jackc/pgproto3.svg)](https://travis-ci.org/jackc/pgproto3) +--- + +This version is used with pgx `v4`. In pgx `v5` it is part of the https://github.com/jackc/pgx repository. + +--- + # pgproto3 Package pgproto3 is a encoder and decoder of the PostgreSQL wire protocol version 3. diff --git a/src/vendor/github.com/jackc/pgproto3/v2/authentication_gss.go b/src/vendor/github.com/jackc/pgproto3/v2/authentication_gss.go new file mode 100644 index 00000000000..5a3f3b1d55b --- /dev/null +++ b/src/vendor/github.com/jackc/pgproto3/v2/authentication_gss.go @@ -0,0 +1,58 @@ +package pgproto3 + +import ( + "encoding/binary" + "encoding/json" + "errors" + "github.com/jackc/pgio" +) + +type AuthenticationGSS struct{} + +func (a *AuthenticationGSS) Backend() {} + +func (a *AuthenticationGSS) AuthenticationResponse() {} + +func (a *AuthenticationGSS) Decode(src []byte) error { + if len(src) < 4 { + return errors.New("authentication message too short") + } + + authType := binary.BigEndian.Uint32(src) + + if authType != AuthTypeGSS { + return errors.New("bad auth type") + } + return nil +} + +func (a *AuthenticationGSS) Encode(dst []byte) []byte { + dst = append(dst, 'R') + dst = pgio.AppendInt32(dst, 4) + dst = pgio.AppendUint32(dst, AuthTypeGSS) + return dst +} + +func (a *AuthenticationGSS) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type string + Data []byte + }{ + Type: "AuthenticationGSS", + }) +} + +func (a *AuthenticationGSS) UnmarshalJSON(data []byte) error { + // Ignore null, like in the main JSON package. + if string(data) == "null" { + return nil + } + + var msg struct { + Type string + } + if err := json.Unmarshal(data, &msg); err != nil { + return err + } + return nil +} diff --git a/src/vendor/github.com/jackc/pgproto3/v2/authentication_gss_continue.go b/src/vendor/github.com/jackc/pgproto3/v2/authentication_gss_continue.go new file mode 100644 index 00000000000..cf8b1834524 --- /dev/null +++ b/src/vendor/github.com/jackc/pgproto3/v2/authentication_gss_continue.go @@ -0,0 +1,67 @@ +package pgproto3 + +import ( + "encoding/binary" + "encoding/json" + "errors" + "github.com/jackc/pgio" +) + +type AuthenticationGSSContinue struct { + Data []byte +} + +func (a *AuthenticationGSSContinue) Backend() {} + +func (a *AuthenticationGSSContinue) AuthenticationResponse() {} + +func (a *AuthenticationGSSContinue) Decode(src []byte) error { + if len(src) < 4 { + return errors.New("authentication message too short") + } + + authType := binary.BigEndian.Uint32(src) + + if authType != AuthTypeGSSCont { + return errors.New("bad auth type") + } + + a.Data = src[4:] + return nil +} + +func (a *AuthenticationGSSContinue) Encode(dst []byte) []byte { + dst = append(dst, 'R') + dst = pgio.AppendInt32(dst, int32(len(a.Data))+8) + dst = pgio.AppendUint32(dst, AuthTypeGSSCont) + dst = append(dst, a.Data...) + return dst +} + +func (a *AuthenticationGSSContinue) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type string + Data []byte + }{ + Type: "AuthenticationGSSContinue", + Data: a.Data, + }) +} + +func (a *AuthenticationGSSContinue) UnmarshalJSON(data []byte) error { + // Ignore null, like in the main JSON package. + if string(data) == "null" { + return nil + } + + var msg struct { + Type string + Data []byte + } + if err := json.Unmarshal(data, &msg); err != nil { + return err + } + + a.Data = msg.Data + return nil +} diff --git a/src/vendor/github.com/jackc/pgproto3/v2/backend.go b/src/vendor/github.com/jackc/pgproto3/v2/backend.go index e9ba38fc3df..1f143652934 100644 --- a/src/vendor/github.com/jackc/pgproto3/v2/backend.go +++ b/src/vendor/github.com/jackc/pgproto3/v2/backend.go @@ -2,6 +2,7 @@ package pgproto3 import ( "encoding/binary" + "errors" "fmt" "io" ) @@ -21,6 +22,7 @@ type Backend struct { describe Describe execute Execute flush Flush + functionCall FunctionCall gssEncRequest GSSEncRequest parse Parse query Query @@ -113,6 +115,9 @@ func (b *Backend) Receive() (FrontendMessage, error) { b.msgType = header[0] b.bodyLen = int(binary.BigEndian.Uint32(header[1:])) - 4 b.partialMsg = true + if b.bodyLen < 0 { + return nil, errors.New("invalid message with negative body length received") + } } var msg FrontendMessage @@ -125,6 +130,8 @@ func (b *Backend) Receive() (FrontendMessage, error) { msg = &b.describe case 'E': msg = &b.execute + case 'F': + msg = &b.functionCall case 'f': msg = &b.copyFail case 'd': @@ -143,6 +150,8 @@ func (b *Backend) Receive() (FrontendMessage, error) { msg = &SASLResponse{} case AuthTypeSASLFinal: msg = &SASLResponse{} + case AuthTypeGSS, AuthTypeGSSCont: + msg = &GSSResponse{} case AuthTypeCleartextPassword, AuthTypeMD5Password: fallthrough default: diff --git a/src/vendor/github.com/jackc/pgproto3/v2/copy_both_response.go b/src/vendor/github.com/jackc/pgproto3/v2/copy_both_response.go index fbd985d8656..4a1c3a07b2a 100644 --- a/src/vendor/github.com/jackc/pgproto3/v2/copy_both_response.go +++ b/src/vendor/github.com/jackc/pgproto3/v2/copy_both_response.go @@ -48,7 +48,7 @@ func (src *CopyBothResponse) Encode(dst []byte) []byte { dst = append(dst, 'W') sp := len(dst) dst = pgio.AppendInt32(dst, -1) - + dst = append(dst, src.OverallFormat) dst = pgio.AppendUint16(dst, uint16(len(src.ColumnFormatCodes))) for _, fc := range src.ColumnFormatCodes { dst = pgio.AppendUint16(dst, fc) diff --git a/src/vendor/github.com/jackc/pgproto3/v2/frontend.go b/src/vendor/github.com/jackc/pgproto3/v2/frontend.go index c33dfb0848c..5be8de80821 100644 --- a/src/vendor/github.com/jackc/pgproto3/v2/frontend.go +++ b/src/vendor/github.com/jackc/pgproto3/v2/frontend.go @@ -16,6 +16,8 @@ type Frontend struct { authenticationOk AuthenticationOk authenticationCleartextPassword AuthenticationCleartextPassword authenticationMD5Password AuthenticationMD5Password + authenticationGSS AuthenticationGSS + authenticationGSSContinue AuthenticationGSSContinue authenticationSASL AuthenticationSASL authenticationSASLContinue AuthenticationSASLContinue authenticationSASLFinal AuthenticationSASLFinal @@ -77,6 +79,9 @@ func (f *Frontend) Receive() (BackendMessage, error) { f.msgType = header[0] f.bodyLen = int(binary.BigEndian.Uint32(header[1:])) - 4 f.partialMsg = true + if f.bodyLen < 0 { + return nil, errors.New("invalid message with negative body length received") + } } msgBody, err := f.cr.Next(f.bodyLen) @@ -178,9 +183,9 @@ func (f *Frontend) findAuthenticationMessageType(src []byte) (BackendMessage, er case AuthTypeSCMCreds: return nil, errors.New("AuthTypeSCMCreds is unimplemented") case AuthTypeGSS: - return nil, errors.New("AuthTypeGSS is unimplemented") + return &f.authenticationGSS, nil case AuthTypeGSSCont: - return nil, errors.New("AuthTypeGSSCont is unimplemented") + return &f.authenticationGSSContinue, nil case AuthTypeSSPI: return nil, errors.New("AuthTypeSSPI is unimplemented") case AuthTypeSASL: diff --git a/src/vendor/github.com/jackc/pgproto3/v2/function_call.go b/src/vendor/github.com/jackc/pgproto3/v2/function_call.go new file mode 100644 index 00000000000..b3a22c4fb69 --- /dev/null +++ b/src/vendor/github.com/jackc/pgproto3/v2/function_call.go @@ -0,0 +1,94 @@ +package pgproto3 + +import ( + "encoding/binary" + "github.com/jackc/pgio" +) + +type FunctionCall struct { + Function uint32 + ArgFormatCodes []uint16 + Arguments [][]byte + ResultFormatCode uint16 +} + +// Frontend identifies this message as sendable by a PostgreSQL frontend. +func (*FunctionCall) Frontend() {} + +// Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message +// type identifier and 4 byte message length. +func (dst *FunctionCall) Decode(src []byte) error { + *dst = FunctionCall{} + rp := 0 + // Specifies the object ID of the function to call. + dst.Function = binary.BigEndian.Uint32(src[rp:]) + rp += 4 + // The number of argument format codes that follow (denoted C below). + // This can be zero to indicate that there are no arguments or that the arguments all use the default format (text); + // or one, in which case the specified format code is applied to all arguments; + // or it can equal the actual number of arguments. + nArgumentCodes := int(binary.BigEndian.Uint16(src[rp:])) + rp += 2 + argumentCodes := make([]uint16, nArgumentCodes) + for i := 0; i < nArgumentCodes; i++ { + // The argument format codes. Each must presently be zero (text) or one (binary). + ac := binary.BigEndian.Uint16(src[rp:]) + if ac != 0 && ac != 1 { + return &invalidMessageFormatErr{messageType: "FunctionCall"} + } + argumentCodes[i] = ac + rp += 2 + } + dst.ArgFormatCodes = argumentCodes + + // Specifies the number of arguments being supplied to the function. + nArguments := int(binary.BigEndian.Uint16(src[rp:])) + rp += 2 + arguments := make([][]byte, nArguments) + for i := 0; i < nArguments; i++ { + // The length of the argument value, in bytes (this count does not include itself). Can be zero. + // As a special case, -1 indicates a NULL argument value. No value bytes follow in the NULL case. + argumentLength := int(binary.BigEndian.Uint32(src[rp:])) + rp += 4 + if argumentLength == -1 { + arguments[i] = nil + } else { + // The value of the argument, in the format indicated by the associated format code. n is the above length. + argumentValue := src[rp : rp+argumentLength] + rp += argumentLength + arguments[i] = argumentValue + } + } + dst.Arguments = arguments + // The format code for the function result. Must presently be zero (text) or one (binary). + resultFormatCode := binary.BigEndian.Uint16(src[rp:]) + if resultFormatCode != 0 && resultFormatCode != 1 { + return &invalidMessageFormatErr{messageType: "FunctionCall"} + } + dst.ResultFormatCode = resultFormatCode + return nil +} + +// Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. +func (src *FunctionCall) Encode(dst []byte) []byte { + dst = append(dst, 'F') + sp := len(dst) + dst = pgio.AppendUint32(dst, 0) // Unknown length, set it at the end + dst = pgio.AppendUint32(dst, src.Function) + dst = pgio.AppendUint16(dst, uint16(len(src.ArgFormatCodes))) + for _, argFormatCode := range src.ArgFormatCodes { + dst = pgio.AppendUint16(dst, argFormatCode) + } + dst = pgio.AppendUint16(dst, uint16(len(src.Arguments))) + for _, argument := range src.Arguments { + if argument == nil { + dst = pgio.AppendInt32(dst, -1) + } else { + dst = pgio.AppendInt32(dst, int32(len(argument))) + dst = append(dst, argument...) + } + } + dst = pgio.AppendUint16(dst, src.ResultFormatCode) + pgio.SetInt32(dst[sp:], int32(len(dst[sp:]))) + return dst +} diff --git a/src/vendor/github.com/jackc/pgproto3/v2/gss_response.go b/src/vendor/github.com/jackc/pgproto3/v2/gss_response.go new file mode 100644 index 00000000000..62da99c793e --- /dev/null +++ b/src/vendor/github.com/jackc/pgproto3/v2/gss_response.go @@ -0,0 +1,48 @@ +package pgproto3 + +import ( + "encoding/json" + "github.com/jackc/pgio" +) + +type GSSResponse struct { + Data []byte +} + +// Frontend identifies this message as sendable by a PostgreSQL frontend. +func (g *GSSResponse) Frontend() {} + +func (g *GSSResponse) Decode(data []byte) error { + g.Data = data + return nil +} + +func (g *GSSResponse) Encode(dst []byte) []byte { + dst = append(dst, 'p') + dst = pgio.AppendInt32(dst, int32(4+len(g.Data))) + dst = append(dst, g.Data...) + return dst +} + +// MarshalJSON implements encoding/json.Marshaler. +func (g *GSSResponse) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type string + Data []byte + }{ + Type: "GSSResponse", + Data: g.Data, + }) +} + +// UnmarshalJSON implements encoding/json.Unmarshaler. +func (g *GSSResponse) UnmarshalJSON(data []byte) error { + var msg struct { + Data []byte + } + if err := json.Unmarshal(data, &msg); err != nil { + return err + } + g.Data = msg.Data + return nil +} diff --git a/src/vendor/github.com/jackc/pgproto3/v2/sasl_initial_response.go b/src/vendor/github.com/jackc/pgproto3/v2/sasl_initial_response.go index f7e5f36a97d..a6b553e7264 100644 --- a/src/vendor/github.com/jackc/pgproto3/v2/sasl_initial_response.go +++ b/src/vendor/github.com/jackc/pgproto3/v2/sasl_initial_response.go @@ -2,7 +2,6 @@ package pgproto3 import ( "bytes" - "encoding/hex" "encoding/json" "errors" @@ -64,7 +63,7 @@ func (src SASLInitialResponse) MarshalJSON() ([]byte, error) { }{ Type: "SASLInitialResponse", AuthMechanism: src.AuthMechanism, - Data: hex.EncodeToString(src.Data), + Data: string(src.Data), }) } @@ -83,12 +82,6 @@ func (dst *SASLInitialResponse) UnmarshalJSON(data []byte) error { return err } dst.AuthMechanism = msg.AuthMechanism - if msg.Data != "" { - decoded, err := hex.DecodeString(msg.Data) - if err != nil { - return err - } - dst.Data = decoded - } + dst.Data = []byte(msg.Data) return nil } diff --git a/src/vendor/github.com/jackc/pgproto3/v2/sasl_response.go b/src/vendor/github.com/jackc/pgproto3/v2/sasl_response.go index 41fb4c397cb..d3e5d6a5668 100644 --- a/src/vendor/github.com/jackc/pgproto3/v2/sasl_response.go +++ b/src/vendor/github.com/jackc/pgproto3/v2/sasl_response.go @@ -1,7 +1,6 @@ package pgproto3 import ( - "encoding/hex" "encoding/json" "github.com/jackc/pgio" @@ -38,7 +37,7 @@ func (src SASLResponse) MarshalJSON() ([]byte, error) { Data string }{ Type: "SASLResponse", - Data: hex.EncodeToString(src.Data), + Data: string(src.Data), }) } @@ -50,12 +49,6 @@ func (dst *SASLResponse) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, &msg); err != nil { return err } - if msg.Data != "" { - decoded, err := hex.DecodeString(msg.Data) - if err != nil { - return err - } - dst.Data = decoded - } + dst.Data = []byte(msg.Data) return nil } diff --git a/src/vendor/github.com/jackc/pgtype/.travis.yml b/src/vendor/github.com/jackc/pgtype/.travis.yml deleted file mode 100644 index d676273503b..00000000000 --- a/src/vendor/github.com/jackc/pgtype/.travis.yml +++ /dev/null @@ -1,34 +0,0 @@ -# source: https://github.com/jackc/pgx/blob/master/.travis.yml - -language: go - -go: - - 1.14.x - - 1.13.x - - tip - -# Derived from https://github.com/lib/pq/blob/master/.travis.yml -before_install: - - ./travis/before_install.bash - -env: - global: - - GO111MODULE=on - - PGX_TEST_DATABASE=postgres://pgx_md5:secret@127.0.0.1/pgx_test - - matrix: - - PGVERSION=12 - - PGVERSION=11 - - PGVERSION=10 - - PGVERSION=9.6 - - PGVERSION=9.5 - -before_script: - - ./travis/before_script.bash - -script: - - ./travis/script.bash - -matrix: - allow_failures: - - go: tip \ No newline at end of file diff --git a/src/vendor/github.com/jackc/pgtype/CHANGELOG.md b/src/vendor/github.com/jackc/pgtype/CHANGELOG.md index 0c8514e3c56..a362a1df596 100644 --- a/src/vendor/github.com/jackc/pgtype/CHANGELOG.md +++ b/src/vendor/github.com/jackc/pgtype/CHANGELOG.md @@ -1,3 +1,68 @@ +# 1.14.0 (February 11, 2023) + +* Fix: BC timestamp text format support (jozeflami) +* Add Scanner and Valuer interfaces to CIDR (Yurii Popivniak) +* Fix crash when nilifying pointer to sql.Scanner + +# 1.13.0 (December 1, 2022) + +* Fix: Reset jsonb before unmarshal (Tomas Odinas) +* Fix: return correct zero value when UUID conversion fails (ndrpnt) +* Fix: EncodeText for Lseg includes [ and ] +* Support sql Value and Scan for custom date type (Hubert Krauze) +* Support Ltree binary encoding (AmineChikhaoui) +* Fix: dates with "BC" (jozeflami) + +# 1.12.0 (August 6, 2022) + +* Add JSONArray (Jakob Ackermann) +* Support Inet from fmt.Stringer and encoding.TextMarshaler (Ville Skyttä) +* Support UUID from fmt.Stringer interface (Lasse Hyldahl Jensen) +* Fix: shopspring-numeric extension does not panic on NaN +* Numeric can be assigned to string +* Fix: Do not send IPv4 networks as IPv4-mapped IPv6 (William Storey) +* Fix: PlanScan for interface{}(nil) (James Hartig) +* Fix: *sql.Scanner for NULL handling (James Hartig) +* Timestamp[tz].Set() supports string (Harmen) +* Fix: Hstore AssignTo with map of *string (Diego Becciolini) + +# 1.11.0 (April 21, 2022) + +* Add multirange for numeric, int4, and int8 (Vu) +* JSONBArray now supports json.RawMessage (Jens Emil Schulz Østergaard) +* Add RecordArray (WGH) +* Add UnmarshalJSON to pgtype.Int2 +* Hstore.Set accepts map[string]Text + +# 1.10.0 (February 7, 2022) + +* Normalize UTC timestamps to comply with stdlib (Torkel Rogstad) +* Assign Numeric to *big.Rat (Oleg Lomaka) +* Fix typo in float8 error message (Pinank Solanki) +* Scan type aliases for floating point types (Collin Forsyth) + +# 1.9.1 (November 28, 2021) + +* Fix: binary timestamp is assumed to be in UTC (restored behavior changed in v1.9.0) + +# 1.9.0 (November 20, 2021) + +* Fix binary hstore null decoding +* Add shopspring/decimal.NullDecimal support to integration (Eli Treuherz) +* Inet.Set supports bare IP address (Carl Dunham) +* Add zeronull.Float8 +* Fix NULL being lost when scanning unknown OID into sql.Scanner +* Fix BPChar.AssignTo **rune +* Add support for fmt.Stringer and driver.Valuer in String fields encoding (Jan Dubsky) +* Fix really big timestamp(tz)s binary format parsing (e.g. year 294276) (Jim Tsao) +* Support `map[string]*string` as hstore (Adrian Sieger) +* Fix parsing text array with negative bounds +* Add infinity support for numeric (Jim Tsao) + +# 1.8.1 (July 24, 2021) + +* Cleaned up Go module dependency chain + # 1.8.0 (July 10, 2021) * Maintain host bits for inet types (Cameron Daniel) diff --git a/src/vendor/github.com/jackc/pgtype/README.md b/src/vendor/github.com/jackc/pgtype/README.md index 77d59b31390..72dadcfc8c6 100644 --- a/src/vendor/github.com/jackc/pgtype/README.md +++ b/src/vendor/github.com/jackc/pgtype/README.md @@ -1,6 +1,12 @@ [![](https://godoc.org/github.com/jackc/pgtype?status.svg)](https://godoc.org/github.com/jackc/pgtype) ![CI](https://github.com/jackc/pgtype/workflows/CI/badge.svg) +--- + +This version is used with pgx `v4`. In pgx `v5` it is part of the https://github.com/jackc/pgx repository. + +--- + # pgtype pgtype implements Go types for over 70 PostgreSQL types. pgtype is the type system underlying the diff --git a/src/vendor/github.com/jackc/pgtype/array.go b/src/vendor/github.com/jackc/pgtype/array.go index 3d5930c1c40..174007c1737 100644 --- a/src/vendor/github.com/jackc/pgtype/array.go +++ b/src/vendor/github.com/jackc/pgtype/array.go @@ -305,7 +305,7 @@ func arrayParseInteger(buf *bytes.Buffer) (int32, error) { return 0, err } - if '0' <= r && r <= '9' { + if ('0' <= r && r <= '9') || r == '-' { s.WriteRune(r) } else { buf.UnreadRune() diff --git a/src/vendor/github.com/jackc/pgtype/array_type.go b/src/vendor/github.com/jackc/pgtype/array_type.go index 1bd0244b7aa..71466554408 100644 --- a/src/vendor/github.com/jackc/pgtype/array_type.go +++ b/src/vendor/github.com/jackc/pgtype/array_type.go @@ -11,7 +11,7 @@ import ( // ArrayType represents an array type. While it implements Value, this is only in service of its type conversion duties // when registered as a data type in a ConnType. It should not be used directly as a Value. ArrayType is a convenience -// type for types that do not have an concrete array type. +// type for types that do not have a concrete array type. type ArrayType struct { elements []ValueTranscoder dimensions []ArrayDimension diff --git a/src/vendor/github.com/jackc/pgtype/bpchar.go b/src/vendor/github.com/jackc/pgtype/bpchar.go index e4d058e922d..c5fa42eac83 100644 --- a/src/vendor/github.com/jackc/pgtype/bpchar.go +++ b/src/vendor/github.com/jackc/pgtype/bpchar.go @@ -2,6 +2,7 @@ package pgtype import ( "database/sql/driver" + "fmt" ) // BPChar is fixed-length, blank padded char type @@ -20,7 +21,8 @@ func (dst BPChar) Get() interface{} { // AssignTo assigns from src to dst. func (src *BPChar) AssignTo(dst interface{}) error { - if src.Status == Present { + switch src.Status { + case Present: switch v := dst.(type) { case *rune: runes := []rune(src.String) @@ -28,9 +30,24 @@ func (src *BPChar) AssignTo(dst interface{}) error { *v = runes[0] return nil } + case *string: + *v = src.String + return nil + case *[]byte: + *v = make([]byte, len(src.String)) + copy(*v, src.String) + return nil + default: + if nextDst, retry := GetAssignToDstType(dst); retry { + return src.AssignTo(nextDst) + } + return fmt.Errorf("unable to assign to %T", dst) } + case Null: + return NullAssignTo(dst) } - return (*Text)(src).AssignTo(dst) + + return fmt.Errorf("cannot decode %#v into %T", src, dst) } func (BPChar) PreferredResultFormat() int16 { diff --git a/src/vendor/github.com/jackc/pgtype/cidr.go b/src/vendor/github.com/jackc/pgtype/cidr.go index 2241ca1c05d..7c562cf2e97 100644 --- a/src/vendor/github.com/jackc/pgtype/cidr.go +++ b/src/vendor/github.com/jackc/pgtype/cidr.go @@ -1,5 +1,7 @@ package pgtype +import "database/sql/driver" + type CIDR Inet func (dst *CIDR) Set(src interface{}) error { @@ -29,3 +31,13 @@ func (src CIDR) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { func (src CIDR) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { return (Inet)(src).EncodeBinary(ci, buf) } + +// Scan implements the database/sql Scanner interface. +func (dst *CIDR) Scan(src interface{}) error { + return (*Inet)(dst).Scan(src) +} + +// Value implements the database/sql/driver Valuer interface. +func (src CIDR) Value() (driver.Value, error) { + return (Inet)(src).Value() +} diff --git a/src/vendor/github.com/jackc/pgtype/convert.go b/src/vendor/github.com/jackc/pgtype/convert.go index de9ba9ba308..377fe3eac92 100644 --- a/src/vendor/github.com/jackc/pgtype/convert.go +++ b/src/vendor/github.com/jackc/pgtype/convert.go @@ -172,7 +172,7 @@ func underlyingUUIDType(val interface{}) (interface{}, bool) { switch refVal.Kind() { case reflect.Ptr: if refVal.IsNil() { - return time.Time{}, false + return nil, false } convVal := refVal.Elem().Interface() return convVal, true @@ -337,6 +337,10 @@ func float64AssignTo(srcVal float64, srcStatus Status, dst interface{}) error { if v := reflect.ValueOf(dst); v.Kind() == reflect.Ptr { el := v.Elem() switch el.Kind() { + // if dst is a type alias of a float32 or 64, set dst val + case reflect.Float32, reflect.Float64: + el.SetFloat(srcVal) + return nil // if dst is a pointer to pointer, strip the pointer and try again case reflect.Ptr: if el.IsNil() { diff --git a/src/vendor/github.com/jackc/pgtype/date.go b/src/vendor/github.com/jackc/pgtype/date.go index e8d21a78c19..e68abf01ddb 100644 --- a/src/vendor/github.com/jackc/pgtype/date.go +++ b/src/vendor/github.com/jackc/pgtype/date.go @@ -1,10 +1,12 @@ package pgtype import ( + "database/sql" "database/sql/driver" "encoding/binary" "encoding/json" "fmt" + "strings" "time" "github.com/jackc/pgio" @@ -34,17 +36,25 @@ func (dst *Date) Set(src interface{}) error { } } + if value, ok := src.(interface{ Value() (driver.Value, error) }); ok { + v, err := value.Value() + if err != nil { + return fmt.Errorf("cannot get value %v for Date: %v", value, err) + } + return dst.Set(v) + } + switch value := src.(type) { case time.Time: *dst = Date{Time: value, Status: Present} - case string: - return dst.DecodeText(nil, []byte(value)) case *time.Time: if value == nil { *dst = Date{Status: Null} } else { return dst.Set(*value) } + case string: + return dst.DecodeText(nil, []byte(value)) case *string: if value == nil { *dst = Date{Status: Null} @@ -76,6 +86,24 @@ func (dst Date) Get() interface{} { } func (src *Date) AssignTo(dst interface{}) error { + if scanner, ok := dst.(sql.Scanner); ok { + var err error + switch src.Status { + case Present: + if src.InfinityModifier != None { + err = scanner.Scan(src.InfinityModifier.String()) + } else { + err = scanner.Scan(src.Time) + } + case Null: + err = scanner.Scan(nil) + } + if err != nil { + return fmt.Errorf("unable assign %v to %T: %s", src, dst, err) + } + return nil + } + switch src.Status { case Present: switch v := dst.(type) { @@ -111,6 +139,15 @@ func (dst *Date) DecodeText(ci *ConnInfo, src []byte) error { case "-infinity": *dst = Date{Status: Present, InfinityModifier: -Infinity} default: + if strings.HasSuffix(sbuf, " BC") { + t, err := time.ParseInLocation("2006-01-02", strings.TrimRight(sbuf, " BC"), time.UTC) + t2 := time.Date(1-t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location()) + if err != nil { + return err + } + *dst = Date{Time: t2, Status: Present} + return nil + } t, err := time.ParseInLocation("2006-01-02", sbuf, time.UTC) if err != nil { return err diff --git a/src/vendor/github.com/jackc/pgtype/enum_type.go b/src/vendor/github.com/jackc/pgtype/enum_type.go index d340320fa26..52657822662 100644 --- a/src/vendor/github.com/jackc/pgtype/enum_type.go +++ b/src/vendor/github.com/jackc/pgtype/enum_type.go @@ -2,7 +2,7 @@ package pgtype import "fmt" -// EnumType represents a enum type. While it implements Value, this is only in service of its type conversion duties +// EnumType represents an enum type. While it implements Value, this is only in service of its type conversion duties // when registered as a data type in a ConnType. It should not be used directly as a Value. type EnumType struct { value string diff --git a/src/vendor/github.com/jackc/pgtype/float8.go b/src/vendor/github.com/jackc/pgtype/float8.go index 4d9e7116a86..6297ab5e27e 100644 --- a/src/vendor/github.com/jackc/pgtype/float8.go +++ b/src/vendor/github.com/jackc/pgtype/float8.go @@ -204,7 +204,7 @@ func (dst *Float8) DecodeBinary(ci *ConnInfo, src []byte) error { } if len(src) != 8 { - return fmt.Errorf("invalid length for float4: %v", len(src)) + return fmt.Errorf("invalid length for float8: %v", len(src)) } n := int64(binary.BigEndian.Uint64(src)) diff --git a/src/vendor/github.com/jackc/pgtype/hstore.go b/src/vendor/github.com/jackc/pgtype/hstore.go index 18b413c6b90..e42b75512af 100644 --- a/src/vendor/github.com/jackc/pgtype/hstore.go +++ b/src/vendor/github.com/jackc/pgtype/hstore.go @@ -40,6 +40,18 @@ func (dst *Hstore) Set(src interface{}) error { m[k] = Text{String: v, Status: Present} } *dst = Hstore{Map: m, Status: Present} + case map[string]*string: + m := make(map[string]Text, len(value)) + for k, v := range value { + if v == nil { + m[k] = Text{Status: Null} + } else { + m[k] = Text{String: *v, Status: Present} + } + } + *dst = Hstore{Map: m, Status: Present} + case map[string]Text: + *dst = Hstore{Map: value, Status: Present} default: return fmt.Errorf("cannot convert %v to Hstore", src) } @@ -71,6 +83,20 @@ func (src *Hstore) AssignTo(dst interface{}) error { (*v)[k] = val.String } return nil + case *map[string]*string: + *v = make(map[string]*string, len(src.Map)) + for k, val := range src.Map { + switch val.Status { + case Null: + (*v)[k] = nil + case Present: + str := val.String + (*v)[k] = &str + default: + return fmt.Errorf("cannot decode %#v into %T", src, dst) + } + } + return nil default: if nextDst, retry := GetAssignToDstType(dst); retry { return src.AssignTo(nextDst) @@ -142,8 +168,8 @@ func (dst *Hstore) DecodeBinary(ci *ConnInfo, src []byte) error { var valueBuf []byte if valueLen >= 0 { valueBuf = src[rp : rp+valueLen] + rp += valueLen } - rp += valueLen var value Text err := value.DecodeBinary(ci, valueBuf) @@ -388,7 +414,7 @@ func parseHstore(s string) (k []string, v []Text, err error) { r, end = p.Consume() switch { case end: - err = errors.New("Found EOS after ',', expcting space") + err = errors.New("Found EOS after ',', expecting space") case (unicode.IsSpace(r)): r, end = p.Consume() state = hsKey diff --git a/src/vendor/github.com/jackc/pgtype/inet.go b/src/vendor/github.com/jackc/pgtype/inet.go index 1645334e337..976f0d7b969 100644 --- a/src/vendor/github.com/jackc/pgtype/inet.go +++ b/src/vendor/github.com/jackc/pgtype/inet.go @@ -2,8 +2,10 @@ package pgtype import ( "database/sql/driver" + "encoding" "fmt" "net" + "strings" ) // Network address family is dependent on server socket.h value for AF_INET. @@ -47,9 +49,26 @@ func (dst *Inet) Set(src interface{}) error { case string: ip, ipnet, err := net.ParseCIDR(value) if err != nil { - return err + ip := net.ParseIP(value) + if ip == nil { + return fmt.Errorf("unable to parse inet address: %s", value) + } + + if ipv4 := maybeGetIPv4(value, ip); ipv4 != nil { + ipnet = &net.IPNet{IP: ipv4, Mask: net.CIDRMask(32, 32)} + } else { + ipnet = &net.IPNet{IP: ip, Mask: net.CIDRMask(128, 128)} + } + } else { + ipnet.IP = ip + if ipv4 := maybeGetIPv4(value, ipnet.IP); ipv4 != nil { + ipnet.IP = ipv4 + if len(ipnet.Mask) == 16 { + ipnet.Mask = ipnet.Mask[12:] // Not sure this is ever needed. + } + } } - ipnet.IP = ip + *dst = Inet{IPNet: ipnet, Status: Present} case *net.IPNet: if value == nil { @@ -70,6 +89,16 @@ func (dst *Inet) Set(src interface{}) error { return dst.Set(*value) } default: + if tv, ok := src.(encoding.TextMarshaler); ok { + text, err := tv.MarshalText() + if err != nil { + return fmt.Errorf("cannot marshal %v: %w", value, err) + } + return dst.Set(string(text)) + } + if sv, ok := src.(fmt.Stringer); ok { + return dst.Set(sv.String()) + } if originalSrc, ok := underlyingPtrType(src); ok { return dst.Set(originalSrc) } @@ -79,6 +108,25 @@ func (dst *Inet) Set(src interface{}) error { return nil } +// Convert the net.IP to IPv4, if appropriate. +// +// When parsing a string to a net.IP using net.ParseIP() and the like, we get a +// 16 byte slice for IPv4 addresses as well as IPv6 addresses. This function +// calls To4() to convert them to a 4 byte slice. This is useful as it allows +// users of the net.IP check for IPv4 addresses based on the length and makes +// it clear we are handling IPv4 as opposed to IPv6 or IPv4-mapped IPv6 +// addresses. +func maybeGetIPv4(input string, ip net.IP) net.IP { + // Do not do this if the provided input looks like IPv6. This is because + // To4() on IPv4-mapped IPv6 addresses converts them to IPv4, which behave + // different in some cases. + if strings.Contains(input, ":") { + return nil + } + + return ip.To4() +} + func (dst Inet) Get() interface{} { switch dst.Status { case Present: @@ -110,6 +158,12 @@ func (src *Inet) AssignTo(dst interface{}) error { copy(*v, src.IPNet.IP) return nil default: + if tv, ok := dst.(encoding.TextUnmarshaler); ok { + if err := tv.UnmarshalText([]byte(src.IPNet.String())); err != nil { + return fmt.Errorf("cannot unmarshal %v to %T: %w", src, dst, err) + } + return nil + } if nextDst, retry := GetAssignToDstType(dst); retry { return src.AssignTo(nextDst) } @@ -161,7 +215,7 @@ func (dst *Inet) DecodeBinary(ci *ConnInfo, src []byte) error { } if len(src) != 8 && len(src) != 20 { - return fmt.Errorf("Received an invalid size for a inet: %d", len(src)) + return fmt.Errorf("Received an invalid size for an inet: %d", len(src)) } // ignore family diff --git a/src/vendor/github.com/jackc/pgtype/int2.go b/src/vendor/github.com/jackc/pgtype/int2.go index 3eb5aeb5513..0775882abd1 100644 --- a/src/vendor/github.com/jackc/pgtype/int2.go +++ b/src/vendor/github.com/jackc/pgtype/int2.go @@ -3,6 +3,7 @@ package pgtype import ( "database/sql/driver" "encoding/binary" + "encoding/json" "fmt" "math" "strconv" @@ -302,3 +303,19 @@ func (src Int2) MarshalJSON() ([]byte, error) { return nil, errBadStatus } + +func (dst *Int2) UnmarshalJSON(b []byte) error { + var n *int16 + err := json.Unmarshal(b, &n) + if err != nil { + return err + } + + if n == nil { + *dst = Int2{Status: Null} + } else { + *dst = Int2{Int: *n, Status: Present} + } + + return nil +} diff --git a/src/vendor/github.com/jackc/pgtype/int4_multirange.go b/src/vendor/github.com/jackc/pgtype/int4_multirange.go new file mode 100644 index 00000000000..c3432ce6372 --- /dev/null +++ b/src/vendor/github.com/jackc/pgtype/int4_multirange.go @@ -0,0 +1,239 @@ +package pgtype + +import ( + "database/sql/driver" + "encoding/binary" + "fmt" + + "github.com/jackc/pgio" +) + +type Int4multirange struct { + Ranges []Int4range + Status Status +} + +func (dst *Int4multirange) Set(src interface{}) error { + //untyped nil and typed nil interfaces are different + if src == nil { + *dst = Int4multirange{Status: Null} + return nil + } + + switch value := src.(type) { + case Int4multirange: + *dst = value + case *Int4multirange: + *dst = *value + case string: + return dst.DecodeText(nil, []byte(value)) + case []Int4range: + if value == nil { + *dst = Int4multirange{Status: Null} + } else if len(value) == 0 { + *dst = Int4multirange{Status: Present} + } else { + elements := make([]Int4range, len(value)) + for i := range value { + if err := elements[i].Set(value[i]); err != nil { + return err + } + } + *dst = Int4multirange{ + Ranges: elements, + Status: Present, + } + } + case []*Int4range: + if value == nil { + *dst = Int4multirange{Status: Null} + } else if len(value) == 0 { + *dst = Int4multirange{Status: Present} + } else { + elements := make([]Int4range, len(value)) + for i := range value { + if err := elements[i].Set(value[i]); err != nil { + return err + } + } + *dst = Int4multirange{ + Ranges: elements, + Status: Present, + } + } + default: + return fmt.Errorf("cannot convert %v to Int4multirange", src) + } + + return nil + +} + +func (dst Int4multirange) Get() interface{} { + switch dst.Status { + case Present: + return dst + case Null: + return nil + default: + return dst.Status + } +} + +func (src *Int4multirange) AssignTo(dst interface{}) error { + return fmt.Errorf("cannot assign %v to %T", src, dst) +} + +func (dst *Int4multirange) DecodeText(ci *ConnInfo, src []byte) error { + if src == nil { + *dst = Int4multirange{Status: Null} + return nil + } + + utmr, err := ParseUntypedTextMultirange(string(src)) + if err != nil { + return err + } + + var elements []Int4range + + if len(utmr.Elements) > 0 { + elements = make([]Int4range, len(utmr.Elements)) + + for i, s := range utmr.Elements { + var elem Int4range + + elemSrc := []byte(s) + + err = elem.DecodeText(ci, elemSrc) + if err != nil { + return err + } + + elements[i] = elem + } + } + + *dst = Int4multirange{Ranges: elements, Status: Present} + + return nil +} + +func (dst *Int4multirange) DecodeBinary(ci *ConnInfo, src []byte) error { + if src == nil { + *dst = Int4multirange{Status: Null} + return nil + } + + rp := 0 + + numElems := int(binary.BigEndian.Uint32(src[rp:])) + rp += 4 + + if numElems == 0 { + *dst = Int4multirange{Status: Present} + return nil + } + + elements := make([]Int4range, numElems) + + for i := range elements { + elemLen := int(int32(binary.BigEndian.Uint32(src[rp:]))) + rp += 4 + var elemSrc []byte + if elemLen >= 0 { + elemSrc = src[rp : rp+elemLen] + rp += elemLen + } + err := elements[i].DecodeBinary(ci, elemSrc) + if err != nil { + return err + } + } + + *dst = Int4multirange{Ranges: elements, Status: Present} + return nil +} + +func (src Int4multirange) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { + switch src.Status { + case Null: + return nil, nil + case Undefined: + return nil, errUndefined + } + + buf = append(buf, '{') + + inElemBuf := make([]byte, 0, 32) + for i, elem := range src.Ranges { + if i > 0 { + buf = append(buf, ',') + } + + elemBuf, err := elem.EncodeText(ci, inElemBuf) + if err != nil { + return nil, err + } + if elemBuf == nil { + return nil, fmt.Errorf("multi-range does not allow null range") + } else { + buf = append(buf, string(elemBuf)...) + } + + } + + buf = append(buf, '}') + + return buf, nil +} + +func (src Int4multirange) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { + switch src.Status { + case Null: + return nil, nil + case Undefined: + return nil, errUndefined + } + + buf = pgio.AppendInt32(buf, int32(len(src.Ranges))) + + for i := range src.Ranges { + sp := len(buf) + buf = pgio.AppendInt32(buf, -1) + + elemBuf, err := src.Ranges[i].EncodeBinary(ci, buf) + if err != nil { + return nil, err + } + if elemBuf != nil { + buf = elemBuf + pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4)) + } + } + + return buf, nil +} + +// Scan implements the database/sql Scanner interface. +func (dst *Int4multirange) Scan(src interface{}) error { + if src == nil { + return dst.DecodeText(nil, nil) + } + + switch src := src.(type) { + case string: + return dst.DecodeText(nil, []byte(src)) + case []byte: + srcCopy := make([]byte, len(src)) + copy(srcCopy, src) + return dst.DecodeText(nil, srcCopy) + } + + return fmt.Errorf("cannot scan %T", src) +} + +// Value implements the database/sql/driver Valuer interface. +func (src Int4multirange) Value() (driver.Value, error) { + return EncodeValueText(src) +} diff --git a/src/vendor/github.com/jackc/pgtype/int8_multirange.go b/src/vendor/github.com/jackc/pgtype/int8_multirange.go new file mode 100644 index 00000000000..e0976427a21 --- /dev/null +++ b/src/vendor/github.com/jackc/pgtype/int8_multirange.go @@ -0,0 +1,239 @@ +package pgtype + +import ( + "database/sql/driver" + "encoding/binary" + "fmt" + + "github.com/jackc/pgio" +) + +type Int8multirange struct { + Ranges []Int8range + Status Status +} + +func (dst *Int8multirange) Set(src interface{}) error { + //untyped nil and typed nil interfaces are different + if src == nil { + *dst = Int8multirange{Status: Null} + return nil + } + + switch value := src.(type) { + case Int8multirange: + *dst = value + case *Int8multirange: + *dst = *value + case string: + return dst.DecodeText(nil, []byte(value)) + case []Int8range: + if value == nil { + *dst = Int8multirange{Status: Null} + } else if len(value) == 0 { + *dst = Int8multirange{Status: Present} + } else { + elements := make([]Int8range, len(value)) + for i := range value { + if err := elements[i].Set(value[i]); err != nil { + return err + } + } + *dst = Int8multirange{ + Ranges: elements, + Status: Present, + } + } + case []*Int8range: + if value == nil { + *dst = Int8multirange{Status: Null} + } else if len(value) == 0 { + *dst = Int8multirange{Status: Present} + } else { + elements := make([]Int8range, len(value)) + for i := range value { + if err := elements[i].Set(value[i]); err != nil { + return err + } + } + *dst = Int8multirange{ + Ranges: elements, + Status: Present, + } + } + default: + return fmt.Errorf("cannot convert %v to Int8multirange", src) + } + + return nil + +} + +func (dst Int8multirange) Get() interface{} { + switch dst.Status { + case Present: + return dst + case Null: + return nil + default: + return dst.Status + } +} + +func (src *Int8multirange) AssignTo(dst interface{}) error { + return fmt.Errorf("cannot assign %v to %T", src, dst) +} + +func (dst *Int8multirange) DecodeText(ci *ConnInfo, src []byte) error { + if src == nil { + *dst = Int8multirange{Status: Null} + return nil + } + + utmr, err := ParseUntypedTextMultirange(string(src)) + if err != nil { + return err + } + + var elements []Int8range + + if len(utmr.Elements) > 0 { + elements = make([]Int8range, len(utmr.Elements)) + + for i, s := range utmr.Elements { + var elem Int8range + + elemSrc := []byte(s) + + err = elem.DecodeText(ci, elemSrc) + if err != nil { + return err + } + + elements[i] = elem + } + } + + *dst = Int8multirange{Ranges: elements, Status: Present} + + return nil +} + +func (dst *Int8multirange) DecodeBinary(ci *ConnInfo, src []byte) error { + if src == nil { + *dst = Int8multirange{Status: Null} + return nil + } + + rp := 0 + + numElems := int(binary.BigEndian.Uint32(src[rp:])) + rp += 4 + + if numElems == 0 { + *dst = Int8multirange{Status: Present} + return nil + } + + elements := make([]Int8range, numElems) + + for i := range elements { + elemLen := int(int32(binary.BigEndian.Uint32(src[rp:]))) + rp += 4 + var elemSrc []byte + if elemLen >= 0 { + elemSrc = src[rp : rp+elemLen] + rp += elemLen + } + err := elements[i].DecodeBinary(ci, elemSrc) + if err != nil { + return err + } + } + + *dst = Int8multirange{Ranges: elements, Status: Present} + return nil +} + +func (src Int8multirange) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { + switch src.Status { + case Null: + return nil, nil + case Undefined: + return nil, errUndefined + } + + buf = append(buf, '{') + + inElemBuf := make([]byte, 0, 32) + for i, elem := range src.Ranges { + if i > 0 { + buf = append(buf, ',') + } + + elemBuf, err := elem.EncodeText(ci, inElemBuf) + if err != nil { + return nil, err + } + if elemBuf == nil { + return nil, fmt.Errorf("multi-range does not allow null range") + } else { + buf = append(buf, string(elemBuf)...) + } + + } + + buf = append(buf, '}') + + return buf, nil +} + +func (src Int8multirange) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { + switch src.Status { + case Null: + return nil, nil + case Undefined: + return nil, errUndefined + } + + buf = pgio.AppendInt32(buf, int32(len(src.Ranges))) + + for i := range src.Ranges { + sp := len(buf) + buf = pgio.AppendInt32(buf, -1) + + elemBuf, err := src.Ranges[i].EncodeBinary(ci, buf) + if err != nil { + return nil, err + } + if elemBuf != nil { + buf = elemBuf + pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4)) + } + } + + return buf, nil +} + +// Scan implements the database/sql Scanner interface. +func (dst *Int8multirange) Scan(src interface{}) error { + if src == nil { + return dst.DecodeText(nil, nil) + } + + switch src := src.(type) { + case string: + return dst.DecodeText(nil, []byte(src)) + case []byte: + srcCopy := make([]byte, len(src)) + copy(srcCopy, src) + return dst.DecodeText(nil, srcCopy) + } + + return fmt.Errorf("cannot scan %T", src) +} + +// Value implements the database/sql/driver Valuer interface. +func (src Int8multirange) Value() (driver.Value, error) { + return EncodeValueText(src) +} diff --git a/src/vendor/github.com/jackc/pgtype/interval.go b/src/vendor/github.com/jackc/pgtype/interval.go index b01fbb7cb26..00ec47c5351 100644 --- a/src/vendor/github.com/jackc/pgtype/interval.go +++ b/src/vendor/github.com/jackc/pgtype/interval.go @@ -174,7 +174,7 @@ func (dst *Interval) DecodeBinary(ci *ConnInfo, src []byte) error { } if len(src) != 16 { - return fmt.Errorf("Received an invalid size for a interval: %d", len(src)) + return fmt.Errorf("Received an invalid size for an interval: %d", len(src)) } microseconds := int64(binary.BigEndian.Uint64(src)) diff --git a/src/vendor/github.com/jackc/pgtype/json.go b/src/vendor/github.com/jackc/pgtype/json.go index 32bef5e7608..a9508bdd846 100644 --- a/src/vendor/github.com/jackc/pgtype/json.go +++ b/src/vendor/github.com/jackc/pgtype/json.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "reflect" ) type JSON struct { @@ -107,6 +108,9 @@ func (src *JSON) AssignTo(dst interface{}) error { data = []byte("null") } + p := reflect.ValueOf(dst).Elem() + p.Set(reflect.Zero(p.Type())) + return json.Unmarshal(data, dst) } diff --git a/src/vendor/github.com/jackc/pgtype/json_array.go b/src/vendor/github.com/jackc/pgtype/json_array.go new file mode 100644 index 00000000000..8d68882f0ca --- /dev/null +++ b/src/vendor/github.com/jackc/pgtype/json_array.go @@ -0,0 +1,546 @@ +// Code generated by erb. DO NOT EDIT. + +package pgtype + +import ( + "database/sql/driver" + "encoding/binary" + "encoding/json" + "fmt" + "reflect" + + "github.com/jackc/pgio" +) + +type JSONArray struct { + Elements []JSON + Dimensions []ArrayDimension + Status Status +} + +func (dst *JSONArray) Set(src interface{}) error { + // untyped nil and typed nil interfaces are different + if src == nil { + *dst = JSONArray{Status: Null} + return nil + } + + if value, ok := src.(interface{ Get() interface{} }); ok { + value2 := value.Get() + if value2 != value { + return dst.Set(value2) + } + } + + // Attempt to match to select common types: + switch value := src.(type) { + + case []string: + if value == nil { + *dst = JSONArray{Status: Null} + } else if len(value) == 0 { + *dst = JSONArray{Status: Present} + } else { + elements := make([]JSON, len(value)) + for i := range value { + if err := elements[i].Set(value[i]); err != nil { + return err + } + } + *dst = JSONArray{ + Elements: elements, + Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}}, + Status: Present, + } + } + + case [][]byte: + if value == nil { + *dst = JSONArray{Status: Null} + } else if len(value) == 0 { + *dst = JSONArray{Status: Present} + } else { + elements := make([]JSON, len(value)) + for i := range value { + if err := elements[i].Set(value[i]); err != nil { + return err + } + } + *dst = JSONArray{ + Elements: elements, + Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}}, + Status: Present, + } + } + + case []json.RawMessage: + if value == nil { + *dst = JSONArray{Status: Null} + } else if len(value) == 0 { + *dst = JSONArray{Status: Present} + } else { + elements := make([]JSON, len(value)) + for i := range value { + if err := elements[i].Set(value[i]); err != nil { + return err + } + } + *dst = JSONArray{ + Elements: elements, + Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}}, + Status: Present, + } + } + + case []JSON: + if value == nil { + *dst = JSONArray{Status: Null} + } else if len(value) == 0 { + *dst = JSONArray{Status: Present} + } else { + *dst = JSONArray{ + Elements: value, + Dimensions: []ArrayDimension{{Length: int32(len(value)), LowerBound: 1}}, + Status: Present, + } + } + default: + // Fallback to reflection if an optimised match was not found. + // The reflection is necessary for arrays and multidimensional slices, + // but it comes with a 20-50% performance penalty for large arrays/slices + reflectedValue := reflect.ValueOf(src) + if !reflectedValue.IsValid() || reflectedValue.IsZero() { + *dst = JSONArray{Status: Null} + return nil + } + + dimensions, elementsLength, ok := findDimensionsFromValue(reflectedValue, nil, 0) + if !ok { + return fmt.Errorf("cannot find dimensions of %v for JSONArray", src) + } + if elementsLength == 0 { + *dst = JSONArray{Status: Present} + return nil + } + if len(dimensions) == 0 { + if originalSrc, ok := underlyingSliceType(src); ok { + return dst.Set(originalSrc) + } + return fmt.Errorf("cannot convert %v to JSONArray", src) + } + + *dst = JSONArray{ + Elements: make([]JSON, elementsLength), + Dimensions: dimensions, + Status: Present, + } + elementCount, err := dst.setRecursive(reflectedValue, 0, 0) + if err != nil { + // Maybe the target was one dimension too far, try again: + if len(dst.Dimensions) > 1 { + dst.Dimensions = dst.Dimensions[:len(dst.Dimensions)-1] + elementsLength = 0 + for _, dim := range dst.Dimensions { + if elementsLength == 0 { + elementsLength = int(dim.Length) + } else { + elementsLength *= int(dim.Length) + } + } + dst.Elements = make([]JSON, elementsLength) + elementCount, err = dst.setRecursive(reflectedValue, 0, 0) + if err != nil { + return err + } + } else { + return err + } + } + if elementCount != len(dst.Elements) { + return fmt.Errorf("cannot convert %v to JSONArray, expected %d dst.Elements, but got %d instead", src, len(dst.Elements), elementCount) + } + } + + return nil +} + +func (dst *JSONArray) setRecursive(value reflect.Value, index, dimension int) (int, error) { + switch value.Kind() { + case reflect.Array: + fallthrough + case reflect.Slice: + if len(dst.Dimensions) == dimension { + break + } + + valueLen := value.Len() + if int32(valueLen) != dst.Dimensions[dimension].Length { + return 0, fmt.Errorf("multidimensional arrays must have array expressions with matching dimensions") + } + for i := 0; i < valueLen; i++ { + var err error + index, err = dst.setRecursive(value.Index(i), index, dimension+1) + if err != nil { + return 0, err + } + } + + return index, nil + } + if !value.CanInterface() { + return 0, fmt.Errorf("cannot convert all values to JSONArray") + } + if err := dst.Elements[index].Set(value.Interface()); err != nil { + return 0, fmt.Errorf("%v in JSONArray", err) + } + index++ + + return index, nil +} + +func (dst JSONArray) Get() interface{} { + switch dst.Status { + case Present: + return dst + case Null: + return nil + default: + return dst.Status + } +} + +func (src *JSONArray) AssignTo(dst interface{}) error { + switch src.Status { + case Present: + if len(src.Dimensions) <= 1 { + // Attempt to match to select common types: + switch v := dst.(type) { + + case *[]string: + *v = make([]string, len(src.Elements)) + for i := range src.Elements { + if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil { + return err + } + } + return nil + + case *[][]byte: + *v = make([][]byte, len(src.Elements)) + for i := range src.Elements { + if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil { + return err + } + } + return nil + + case *[]json.RawMessage: + *v = make([]json.RawMessage, len(src.Elements)) + for i := range src.Elements { + if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil { + return err + } + } + return nil + + } + } + + // Try to convert to something AssignTo can use directly. + if nextDst, retry := GetAssignToDstType(dst); retry { + return src.AssignTo(nextDst) + } + + // Fallback to reflection if an optimised match was not found. + // The reflection is necessary for arrays and multidimensional slices, + // but it comes with a 20-50% performance penalty for large arrays/slices + value := reflect.ValueOf(dst) + if value.Kind() == reflect.Ptr { + value = value.Elem() + } + + switch value.Kind() { + case reflect.Array, reflect.Slice: + default: + return fmt.Errorf("cannot assign %T to %T", src, dst) + } + + if len(src.Elements) == 0 { + if value.Kind() == reflect.Slice { + value.Set(reflect.MakeSlice(value.Type(), 0, 0)) + return nil + } + } + + elementCount, err := src.assignToRecursive(value, 0, 0) + if err != nil { + return err + } + if elementCount != len(src.Elements) { + return fmt.Errorf("cannot assign %v, needed to assign %d elements, but only assigned %d", dst, len(src.Elements), elementCount) + } + + return nil + case Null: + return NullAssignTo(dst) + } + + return fmt.Errorf("cannot decode %#v into %T", src, dst) +} + +func (src *JSONArray) assignToRecursive(value reflect.Value, index, dimension int) (int, error) { + switch kind := value.Kind(); kind { + case reflect.Array: + fallthrough + case reflect.Slice: + if len(src.Dimensions) == dimension { + break + } + + length := int(src.Dimensions[dimension].Length) + if reflect.Array == kind { + typ := value.Type() + if typ.Len() != length { + return 0, fmt.Errorf("expected size %d array, but %s has size %d array", length, typ, typ.Len()) + } + value.Set(reflect.New(typ).Elem()) + } else { + value.Set(reflect.MakeSlice(value.Type(), length, length)) + } + + var err error + for i := 0; i < length; i++ { + index, err = src.assignToRecursive(value.Index(i), index, dimension+1) + if err != nil { + return 0, err + } + } + + return index, nil + } + if len(src.Dimensions) != dimension { + return 0, fmt.Errorf("incorrect dimensions, expected %d, found %d", len(src.Dimensions), dimension) + } + if !value.CanAddr() { + return 0, fmt.Errorf("cannot assign all values from JSONArray") + } + addr := value.Addr() + if !addr.CanInterface() { + return 0, fmt.Errorf("cannot assign all values from JSONArray") + } + if err := src.Elements[index].AssignTo(addr.Interface()); err != nil { + return 0, err + } + index++ + return index, nil +} + +func (dst *JSONArray) DecodeText(ci *ConnInfo, src []byte) error { + if src == nil { + *dst = JSONArray{Status: Null} + return nil + } + + uta, err := ParseUntypedTextArray(string(src)) + if err != nil { + return err + } + + var elements []JSON + + if len(uta.Elements) > 0 { + elements = make([]JSON, len(uta.Elements)) + + for i, s := range uta.Elements { + var elem JSON + var elemSrc []byte + if s != "NULL" || uta.Quoted[i] { + elemSrc = []byte(s) + } + err = elem.DecodeText(ci, elemSrc) + if err != nil { + return err + } + + elements[i] = elem + } + } + + *dst = JSONArray{Elements: elements, Dimensions: uta.Dimensions, Status: Present} + + return nil +} + +func (dst *JSONArray) DecodeBinary(ci *ConnInfo, src []byte) error { + if src == nil { + *dst = JSONArray{Status: Null} + return nil + } + + var arrayHeader ArrayHeader + rp, err := arrayHeader.DecodeBinary(ci, src) + if err != nil { + return err + } + + if len(arrayHeader.Dimensions) == 0 { + *dst = JSONArray{Dimensions: arrayHeader.Dimensions, Status: Present} + return nil + } + + elementCount := arrayHeader.Dimensions[0].Length + for _, d := range arrayHeader.Dimensions[1:] { + elementCount *= d.Length + } + + elements := make([]JSON, elementCount) + + for i := range elements { + elemLen := int(int32(binary.BigEndian.Uint32(src[rp:]))) + rp += 4 + var elemSrc []byte + if elemLen >= 0 { + elemSrc = src[rp : rp+elemLen] + rp += elemLen + } + err = elements[i].DecodeBinary(ci, elemSrc) + if err != nil { + return err + } + } + + *dst = JSONArray{Elements: elements, Dimensions: arrayHeader.Dimensions, Status: Present} + return nil +} + +func (src JSONArray) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { + switch src.Status { + case Null: + return nil, nil + case Undefined: + return nil, errUndefined + } + + if len(src.Dimensions) == 0 { + return append(buf, '{', '}'), nil + } + + buf = EncodeTextArrayDimensions(buf, src.Dimensions) + + // dimElemCounts is the multiples of elements that each array lies on. For + // example, a single dimension array of length 4 would have a dimElemCounts of + // [4]. A multi-dimensional array of lengths [3,5,2] would have a + // dimElemCounts of [30,10,2]. This is used to simplify when to render a '{' + // or '}'. + dimElemCounts := make([]int, len(src.Dimensions)) + dimElemCounts[len(src.Dimensions)-1] = int(src.Dimensions[len(src.Dimensions)-1].Length) + for i := len(src.Dimensions) - 2; i > -1; i-- { + dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1] + } + + inElemBuf := make([]byte, 0, 32) + for i, elem := range src.Elements { + if i > 0 { + buf = append(buf, ',') + } + + for _, dec := range dimElemCounts { + if i%dec == 0 { + buf = append(buf, '{') + } + } + + elemBuf, err := elem.EncodeText(ci, inElemBuf) + if err != nil { + return nil, err + } + if elemBuf == nil { + buf = append(buf, `NULL`...) + } else { + buf = append(buf, QuoteArrayElementIfNeeded(string(elemBuf))...) + } + + for _, dec := range dimElemCounts { + if (i+1)%dec == 0 { + buf = append(buf, '}') + } + } + } + + return buf, nil +} + +func (src JSONArray) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { + switch src.Status { + case Null: + return nil, nil + case Undefined: + return nil, errUndefined + } + + arrayHeader := ArrayHeader{ + Dimensions: src.Dimensions, + } + + if dt, ok := ci.DataTypeForName("json"); ok { + arrayHeader.ElementOID = int32(dt.OID) + } else { + return nil, fmt.Errorf("unable to find oid for type name %v", "json") + } + + for i := range src.Elements { + if src.Elements[i].Status == Null { + arrayHeader.ContainsNull = true + break + } + } + + buf = arrayHeader.EncodeBinary(ci, buf) + + for i := range src.Elements { + sp := len(buf) + buf = pgio.AppendInt32(buf, -1) + + elemBuf, err := src.Elements[i].EncodeBinary(ci, buf) + if err != nil { + return nil, err + } + if elemBuf != nil { + buf = elemBuf + pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4)) + } + } + + return buf, nil +} + +// Scan implements the database/sql Scanner interface. +func (dst *JSONArray) Scan(src interface{}) error { + if src == nil { + return dst.DecodeText(nil, nil) + } + + switch src := src.(type) { + case string: + return dst.DecodeText(nil, []byte(src)) + case []byte: + srcCopy := make([]byte, len(src)) + copy(srcCopy, src) + return dst.DecodeText(nil, srcCopy) + } + + return fmt.Errorf("cannot scan %T", src) +} + +// Value implements the database/sql/driver Valuer interface. +func (src JSONArray) Value() (driver.Value, error) { + buf, err := src.EncodeText(nil, nil) + if err != nil { + return nil, err + } + if buf == nil { + return nil, nil + } + + return string(buf), nil +} diff --git a/src/vendor/github.com/jackc/pgtype/jsonb_array.go b/src/vendor/github.com/jackc/pgtype/jsonb_array.go index c4b7cd3d8ce..e78ad37761f 100644 --- a/src/vendor/github.com/jackc/pgtype/jsonb_array.go +++ b/src/vendor/github.com/jackc/pgtype/jsonb_array.go @@ -5,6 +5,7 @@ package pgtype import ( "database/sql/driver" "encoding/binary" + "encoding/json" "fmt" "reflect" @@ -72,6 +73,25 @@ func (dst *JSONBArray) Set(src interface{}) error { } } + case []json.RawMessage: + if value == nil { + *dst = JSONBArray{Status: Null} + } else if len(value) == 0 { + *dst = JSONBArray{Status: Present} + } else { + elements := make([]JSONB, len(value)) + for i := range value { + if err := elements[i].Set(value[i]); err != nil { + return err + } + } + *dst = JSONBArray{ + Elements: elements, + Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}}, + Status: Present, + } + } + case []JSONB: if value == nil { *dst = JSONBArray{Status: Null} @@ -214,6 +234,15 @@ func (src *JSONBArray) AssignTo(dst interface{}) error { } return nil + case *[]json.RawMessage: + *v = make([]json.RawMessage, len(src.Elements)) + for i := range src.Elements { + if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil { + return err + } + } + return nil + } } diff --git a/src/vendor/github.com/jackc/pgtype/lseg.go b/src/vendor/github.com/jackc/pgtype/lseg.go index 5c4babb6910..894dae860b8 100644 --- a/src/vendor/github.com/jackc/pgtype/lseg.go +++ b/src/vendor/github.com/jackc/pgtype/lseg.go @@ -115,7 +115,7 @@ func (src Lseg) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { return nil, errUndefined } - buf = append(buf, fmt.Sprintf(`(%s,%s),(%s,%s)`, + buf = append(buf, fmt.Sprintf(`[(%s,%s),(%s,%s)]`, strconv.FormatFloat(src.P[0].X, 'f', -1, 64), strconv.FormatFloat(src.P[0].Y, 'f', -1, 64), strconv.FormatFloat(src.P[1].X, 'f', -1, 64), diff --git a/src/vendor/github.com/jackc/pgtype/ltree.go b/src/vendor/github.com/jackc/pgtype/ltree.go new file mode 100644 index 00000000000..8c8d421334a --- /dev/null +++ b/src/vendor/github.com/jackc/pgtype/ltree.go @@ -0,0 +1,72 @@ +package pgtype + +import ( + "database/sql/driver" + "fmt" +) + +type Ltree Text + +func (dst *Ltree) Set(src interface{}) error { + return (*Text)(dst).Set(src) +} + +func (dst Ltree) Get() interface{} { + return (Text)(dst).Get() +} + +func (src *Ltree) AssignTo(dst interface{}) error { + return (*Text)(src).AssignTo(dst) +} + +func (src Ltree) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { + return (Text)(src).EncodeText(ci, buf) +} + +func (src Ltree) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { + switch src.Status { + case Null: + return nil, nil + case Undefined: + return nil, errUndefined + } + buf = append(buf, 1) + return append(buf, src.String...), nil +} + +func (Ltree) PreferredResultFormat() int16 { + return TextFormatCode +} + +func (dst *Ltree) DecodeText(ci *ConnInfo, src []byte) error { + return (*Text)(dst).DecodeText(ci, src) +} + +func (dst *Ltree) DecodeBinary(ci *ConnInfo, src []byte) error { + if src == nil { + *dst = Ltree{Status: Null} + return nil + } + + // Get Ltree version, only 1 is allowed + version := src[0] + if version != 1 { + return fmt.Errorf("unsupported ltree version %d", version) + } + + ltreeStr := string(src[1:]) + *dst = Ltree{String: ltreeStr, Status: Present} + return nil +} + +func (Ltree) PreferredParamFormat() int16 { + return TextFormatCode +} + +func (dst *Ltree) Scan(src interface{}) error { + return (*Text)(dst).Scan(src) +} + +func (src Ltree) Value() (driver.Value, error) { + return (Text)(src).Value() +} diff --git a/src/vendor/github.com/jackc/pgtype/multirange.go b/src/vendor/github.com/jackc/pgtype/multirange.go new file mode 100644 index 00000000000..beb11f70254 --- /dev/null +++ b/src/vendor/github.com/jackc/pgtype/multirange.go @@ -0,0 +1,83 @@ +package pgtype + +import ( + "bytes" + "fmt" +) + +type UntypedTextMultirange struct { + Elements []string +} + +func ParseUntypedTextMultirange(src string) (*UntypedTextMultirange, error) { + utmr := &UntypedTextMultirange{} + utmr.Elements = make([]string, 0) + + buf := bytes.NewBufferString(src) + + skipWhitespace(buf) + + r, _, err := buf.ReadRune() + if err != nil { + return nil, fmt.Errorf("invalid array: %v", err) + } + + if r != '{' { + return nil, fmt.Errorf("invalid multirange, expected '{': %v", err) + } + +parseValueLoop: + for { + r, _, err = buf.ReadRune() + if err != nil { + return nil, fmt.Errorf("invalid multirange: %v", err) + } + + switch r { + case ',': // skip range separator + case '}': + break parseValueLoop + default: + buf.UnreadRune() + value, err := parseRange(buf) + if err != nil { + return nil, fmt.Errorf("invalid multirange value: %v", err) + } + utmr.Elements = append(utmr.Elements, value) + } + } + + skipWhitespace(buf) + + if buf.Len() > 0 { + return nil, fmt.Errorf("unexpected trailing data: %v", buf.String()) + } + + return utmr, nil + +} + +func parseRange(buf *bytes.Buffer) (string, error) { + + s := &bytes.Buffer{} + + boundSepRead := false + for { + r, _, err := buf.ReadRune() + if err != nil { + return "", err + } + + switch r { + case ',', '}': + if r == ',' && !boundSepRead { + boundSepRead = true + break + } + buf.UnreadRune() + return s.String(), nil + } + + s.WriteRune(r) + } +} diff --git a/src/vendor/github.com/jackc/pgtype/num_multirange.go b/src/vendor/github.com/jackc/pgtype/num_multirange.go new file mode 100644 index 00000000000..cbabc8acb34 --- /dev/null +++ b/src/vendor/github.com/jackc/pgtype/num_multirange.go @@ -0,0 +1,239 @@ +package pgtype + +import ( + "database/sql/driver" + "encoding/binary" + "fmt" + + "github.com/jackc/pgio" +) + +type Nummultirange struct { + Ranges []Numrange + Status Status +} + +func (dst *Nummultirange) Set(src interface{}) error { + //untyped nil and typed nil interfaces are different + if src == nil { + *dst = Nummultirange{Status: Null} + return nil + } + + switch value := src.(type) { + case Nummultirange: + *dst = value + case *Nummultirange: + *dst = *value + case string: + return dst.DecodeText(nil, []byte(value)) + case []Numrange: + if value == nil { + *dst = Nummultirange{Status: Null} + } else if len(value) == 0 { + *dst = Nummultirange{Status: Present} + } else { + elements := make([]Numrange, len(value)) + for i := range value { + if err := elements[i].Set(value[i]); err != nil { + return err + } + } + *dst = Nummultirange{ + Ranges: elements, + Status: Present, + } + } + case []*Numrange: + if value == nil { + *dst = Nummultirange{Status: Null} + } else if len(value) == 0 { + *dst = Nummultirange{Status: Present} + } else { + elements := make([]Numrange, len(value)) + for i := range value { + if err := elements[i].Set(value[i]); err != nil { + return err + } + } + *dst = Nummultirange{ + Ranges: elements, + Status: Present, + } + } + default: + return fmt.Errorf("cannot convert %v to Nummultirange", src) + } + + return nil + +} + +func (dst Nummultirange) Get() interface{} { + switch dst.Status { + case Present: + return dst + case Null: + return nil + default: + return dst.Status + } +} + +func (src *Nummultirange) AssignTo(dst interface{}) error { + return fmt.Errorf("cannot assign %v to %T", src, dst) +} + +func (dst *Nummultirange) DecodeText(ci *ConnInfo, src []byte) error { + if src == nil { + *dst = Nummultirange{Status: Null} + return nil + } + + utmr, err := ParseUntypedTextMultirange(string(src)) + if err != nil { + return err + } + + var elements []Numrange + + if len(utmr.Elements) > 0 { + elements = make([]Numrange, len(utmr.Elements)) + + for i, s := range utmr.Elements { + var elem Numrange + + elemSrc := []byte(s) + + err = elem.DecodeText(ci, elemSrc) + if err != nil { + return err + } + + elements[i] = elem + } + } + + *dst = Nummultirange{Ranges: elements, Status: Present} + + return nil +} + +func (dst *Nummultirange) DecodeBinary(ci *ConnInfo, src []byte) error { + if src == nil { + *dst = Nummultirange{Status: Null} + return nil + } + + rp := 0 + + numElems := int(binary.BigEndian.Uint32(src[rp:])) + rp += 4 + + if numElems == 0 { + *dst = Nummultirange{Status: Present} + return nil + } + + elements := make([]Numrange, numElems) + + for i := range elements { + elemLen := int(int32(binary.BigEndian.Uint32(src[rp:]))) + rp += 4 + var elemSrc []byte + if elemLen >= 0 { + elemSrc = src[rp : rp+elemLen] + rp += elemLen + } + err := elements[i].DecodeBinary(ci, elemSrc) + if err != nil { + return err + } + } + + *dst = Nummultirange{Ranges: elements, Status: Present} + return nil +} + +func (src Nummultirange) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { + switch src.Status { + case Null: + return nil, nil + case Undefined: + return nil, errUndefined + } + + buf = append(buf, '{') + + inElemBuf := make([]byte, 0, 32) + for i, elem := range src.Ranges { + if i > 0 { + buf = append(buf, ',') + } + + elemBuf, err := elem.EncodeText(ci, inElemBuf) + if err != nil { + return nil, err + } + if elemBuf == nil { + return nil, fmt.Errorf("multi-range does not allow null range") + } else { + buf = append(buf, string(elemBuf)...) + } + + } + + buf = append(buf, '}') + + return buf, nil +} + +func (src Nummultirange) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { + switch src.Status { + case Null: + return nil, nil + case Undefined: + return nil, errUndefined + } + + buf = pgio.AppendInt32(buf, int32(len(src.Ranges))) + + for i := range src.Ranges { + sp := len(buf) + buf = pgio.AppendInt32(buf, -1) + + elemBuf, err := src.Ranges[i].EncodeBinary(ci, buf) + if err != nil { + return nil, err + } + if elemBuf != nil { + buf = elemBuf + pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4)) + } + } + + return buf, nil +} + +// Scan implements the database/sql Scanner interface. +func (dst *Nummultirange) Scan(src interface{}) error { + if src == nil { + return dst.DecodeText(nil, nil) + } + + switch src := src.(type) { + case string: + return dst.DecodeText(nil, []byte(src)) + case []byte: + srcCopy := make([]byte, len(src)) + copy(srcCopy, src) + return dst.DecodeText(nil, srcCopy) + } + + return fmt.Errorf("cannot scan %T", src) +} + +// Value implements the database/sql/driver Valuer interface. +func (src Nummultirange) Value() (driver.Value, error) { + return EncodeValueText(src) +} diff --git a/src/vendor/github.com/jackc/pgtype/numeric.go b/src/vendor/github.com/jackc/pgtype/numeric.go index a7efa704cc2..1f32b36b4d9 100644 --- a/src/vendor/github.com/jackc/pgtype/numeric.go +++ b/src/vendor/github.com/jackc/pgtype/numeric.go @@ -1,6 +1,7 @@ package pgtype import ( + "bytes" "database/sql/driver" "encoding/binary" "fmt" @@ -18,6 +19,12 @@ const nbase = 10000 const ( pgNumericNaN = 0x00000000c0000000 pgNumericNaNSign = 0xc000 + + pgNumericPosInf = 0x00000000d0000000 + pgNumericPosInfSign = 0xd000 + + pgNumericNegInf = 0x00000000f0000000 + pgNumericNegInfSign = 0xf000 ) var big0 *big.Int = big.NewInt(0) @@ -49,10 +56,11 @@ var bigNBaseX3 *big.Int = big.NewInt(nbase * nbase * nbase) var bigNBaseX4 *big.Int = big.NewInt(nbase * nbase * nbase * nbase) type Numeric struct { - Int *big.Int - Exp int32 - Status Status - NaN bool + Int *big.Int + Exp int32 + Status Status + NaN bool + InfinityModifier InfinityModifier } func (dst *Numeric) Set(src interface{}) error { @@ -73,6 +81,12 @@ func (dst *Numeric) Set(src interface{}) error { if math.IsNaN(float64(value)) { *dst = Numeric{Status: Present, NaN: true} return nil + } else if math.IsInf(float64(value), 1) { + *dst = Numeric{Status: Present, InfinityModifier: Infinity} + return nil + } else if math.IsInf(float64(value), -1) { + *dst = Numeric{Status: Present, InfinityModifier: NegativeInfinity} + return nil } num, exp, err := parseNumericString(strconv.FormatFloat(float64(value), 'f', -1, 64)) if err != nil { @@ -83,6 +97,12 @@ func (dst *Numeric) Set(src interface{}) error { if math.IsNaN(value) { *dst = Numeric{Status: Present, NaN: true} return nil + } else if math.IsInf(value, 1) { + *dst = Numeric{Status: Present, InfinityModifier: Infinity} + return nil + } else if math.IsInf(value, -1) { + *dst = Numeric{Status: Present, InfinityModifier: NegativeInfinity} + return nil } num, exp, err := parseNumericString(strconv.FormatFloat(value, 'f', -1, 64)) if err != nil { @@ -193,6 +213,8 @@ func (dst *Numeric) Set(src interface{}) error { } else { return dst.Set(*value) } + case InfinityModifier: + *dst = Numeric{InfinityModifier: value, Status: Present} default: if originalSrc, ok := underlyingNumberType(src); ok { return dst.Set(originalSrc) @@ -206,6 +228,9 @@ func (dst *Numeric) Set(src interface{}) error { func (dst Numeric) Get() interface{} { switch dst.Status { case Present: + if dst.InfinityModifier != None { + return dst.InfinityModifier + } return dst case Null: return nil @@ -345,6 +370,18 @@ func (src *Numeric) AssignTo(dst interface{}) error { return fmt.Errorf("%d is greater than maximum value for %T", normalizedInt, *v) } *v = normalizedInt.Uint64() + case *big.Rat: + rat, err := src.toBigRat() + if err != nil { + return err + } + v.Set(rat) + case *string: + buf, err := encodeNumericText(*src, nil) + if err != nil { + return err + } + *v = string(buf) default: if nextDst, retry := GetAssignToDstType(dst); retry { return src.AssignTo(nextDst) @@ -382,9 +419,33 @@ func (dst *Numeric) toBigInt() (*big.Int, error) { return num, nil } +func (dst *Numeric) toBigRat() (*big.Rat, error) { + if dst.NaN { + return nil, fmt.Errorf("%v is not a number", dst) + } else if dst.InfinityModifier == Infinity { + return nil, fmt.Errorf("%v is infinity", dst) + } else if dst.InfinityModifier == NegativeInfinity { + return nil, fmt.Errorf("%v is -infinity", dst) + } + + num := new(big.Rat).SetInt(dst.Int) + if dst.Exp > 0 { + mul := new(big.Int).Exp(big10, big.NewInt(int64(dst.Exp)), nil) + num.Mul(num, new(big.Rat).SetInt(mul)) + } else if dst.Exp < 0 { + mul := new(big.Int).Exp(big10, big.NewInt(int64(-dst.Exp)), nil) + num.Quo(num, new(big.Rat).SetInt(mul)) + } + return num, nil +} + func (src *Numeric) toFloat64() (float64, error) { if src.NaN { return math.NaN(), nil + } else if src.InfinityModifier == Infinity { + return math.Inf(1), nil + } else if src.InfinityModifier == NegativeInfinity { + return math.Inf(-1), nil } buf := make([]byte, 0, 32) @@ -409,6 +470,12 @@ func (dst *Numeric) DecodeText(ci *ConnInfo, src []byte) error { if string(src) == "NaN" { *dst = Numeric{Status: Present, NaN: true} return nil + } else if string(src) == "Infinity" { + *dst = Numeric{Status: Present, InfinityModifier: Infinity} + return nil + } else if string(src) == "-Infinity" { + *dst = Numeric{Status: Present, InfinityModifier: NegativeInfinity} + return nil } num, exp, err := parseNumericString(string(src)) @@ -452,11 +519,11 @@ func (dst *Numeric) DecodeBinary(ci *ConnInfo, src []byte) error { } rp := 0 - ndigits := int16(binary.BigEndian.Uint16(src[rp:])) + ndigits := binary.BigEndian.Uint16(src[rp:]) rp += 2 weight := int16(binary.BigEndian.Uint16(src[rp:])) rp += 2 - sign := uint16(binary.BigEndian.Uint16(src[rp:])) + sign := binary.BigEndian.Uint16(src[rp:]) rp += 2 dscale := int16(binary.BigEndian.Uint16(src[rp:])) rp += 2 @@ -464,6 +531,12 @@ func (dst *Numeric) DecodeBinary(ci *ConnInfo, src []byte) error { if sign == pgNumericNaNSign { *dst = Numeric{Status: Present, NaN: true} return nil + } else if sign == pgNumericPosInfSign { + *dst = Numeric{Status: Present, InfinityModifier: Infinity} + return nil + } else if sign == pgNumericNegInfSign { + *dst = Numeric{Status: Present, InfinityModifier: NegativeInfinity} + return nil } if ndigits == 0 { @@ -504,7 +577,7 @@ func (dst *Numeric) DecodeBinary(ci *ConnInfo, src []byte) error { exp := (int32(weight) - int32(ndigits) + 1) * 4 if dscale > 0 { - fracNBaseDigits := ndigits - weight - 1 + fracNBaseDigits := int16(int32(ndigits) - int32(weight) - 1) fracDecimalDigits := fracNBaseDigits * 4 if dscale > fracDecimalDigits { @@ -575,6 +648,12 @@ func (src Numeric) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { if src.NaN { buf = append(buf, "NaN"...) return buf, nil + } else if src.InfinityModifier == Infinity { + buf = append(buf, "Infinity"...) + return buf, nil + } else if src.InfinityModifier == NegativeInfinity { + buf = append(buf, "-Infinity"...) + return buf, nil } buf = append(buf, src.Int.String()...) @@ -594,6 +673,12 @@ func (src Numeric) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { if src.NaN { buf = pgio.AppendUint64(buf, pgNumericNaN) return buf, nil + } else if src.InfinityModifier == Infinity { + buf = pgio.AppendUint64(buf, pgNumericPosInf) + return buf, nil + } else if src.InfinityModifier == NegativeInfinity { + buf = pgio.AppendUint64(buf, pgNumericNegInf) + return buf, nil } var sign int16 @@ -714,3 +799,55 @@ func (src Numeric) Value() (driver.Value, error) { return nil, errUndefined } } + +func encodeNumericText(n Numeric, buf []byte) (newBuf []byte, err error) { + // if !n.Valid { + // return nil, nil + // } + + if n.NaN { + buf = append(buf, "NaN"...) + return buf, nil + } else if n.InfinityModifier == Infinity { + buf = append(buf, "Infinity"...) + return buf, nil + } else if n.InfinityModifier == NegativeInfinity { + buf = append(buf, "-Infinity"...) + return buf, nil + } + + buf = append(buf, n.numberTextBytes()...) + + return buf, nil +} + +// numberString returns a string of the number. undefined if NaN, infinite, or NULL +func (n Numeric) numberTextBytes() []byte { + intStr := n.Int.String() + buf := &bytes.Buffer{} + exp := int(n.Exp) + if exp > 0 { + buf.WriteString(intStr) + for i := 0; i < exp; i++ { + buf.WriteByte('0') + } + } else if exp < 0 { + if len(intStr) <= -exp { + buf.WriteString("0.") + leadingZeros := -exp - len(intStr) + for i := 0; i < leadingZeros; i++ { + buf.WriteByte('0') + } + buf.WriteString(intStr) + } else if len(intStr) > -exp { + dpPos := len(intStr) + exp + buf.WriteString(intStr[:dpPos]) + buf.WriteByte('.') + buf.WriteString(intStr[dpPos:]) + } + } else { + buf.WriteString(intStr) + } + + return buf.Bytes() +} diff --git a/src/vendor/github.com/jackc/pgtype/pgtype.go b/src/vendor/github.com/jackc/pgtype/pgtype.go index 4a680844918..a52740e7977 100644 --- a/src/vendor/github.com/jackc/pgtype/pgtype.go +++ b/src/vendor/github.com/jackc/pgtype/pgtype.go @@ -26,6 +26,7 @@ const ( XIDOID = 28 CIDOID = 29 JSONOID = 114 + JSONArrayOID = 199 PointOID = 600 LsegOID = 601 PathOID = 602 @@ -74,12 +75,15 @@ const ( JSONBArrayOID = 3807 DaterangeOID = 3912 Int4rangeOID = 3904 + Int4multirangeOID = 4451 NumrangeOID = 3906 + NummultirangeOID = 4532 TsrangeOID = 3908 TsrangeArrayOID = 3909 TstzrangeOID = 3910 TstzrangeArrayOID = 3911 Int8rangeOID = 3926 + Int8multirangeOID = 4536 ) type Status byte @@ -288,10 +292,13 @@ func NewConnInfo() *ConnInfo { ci.RegisterDataType(DataType{Value: &Int2{}, Name: "int2", OID: Int2OID}) ci.RegisterDataType(DataType{Value: &Int4{}, Name: "int4", OID: Int4OID}) ci.RegisterDataType(DataType{Value: &Int4range{}, Name: "int4range", OID: Int4rangeOID}) + ci.RegisterDataType(DataType{Value: &Int4multirange{}, Name: "int4multirange", OID: Int4multirangeOID}) ci.RegisterDataType(DataType{Value: &Int8{}, Name: "int8", OID: Int8OID}) ci.RegisterDataType(DataType{Value: &Int8range{}, Name: "int8range", OID: Int8rangeOID}) + ci.RegisterDataType(DataType{Value: &Int8multirange{}, Name: "int8multirange", OID: Int8multirangeOID}) ci.RegisterDataType(DataType{Value: &Interval{}, Name: "interval", OID: IntervalOID}) ci.RegisterDataType(DataType{Value: &JSON{}, Name: "json", OID: JSONOID}) + ci.RegisterDataType(DataType{Value: &JSONArray{}, Name: "_json", OID: JSONArrayOID}) ci.RegisterDataType(DataType{Value: &JSONB{}, Name: "jsonb", OID: JSONBOID}) ci.RegisterDataType(DataType{Value: &JSONBArray{}, Name: "_jsonb", OID: JSONBArrayOID}) ci.RegisterDataType(DataType{Value: &Line{}, Name: "line", OID: LineOID}) @@ -300,6 +307,7 @@ func NewConnInfo() *ConnInfo { ci.RegisterDataType(DataType{Value: &Name{}, Name: "name", OID: NameOID}) ci.RegisterDataType(DataType{Value: &Numeric{}, Name: "numeric", OID: NumericOID}) ci.RegisterDataType(DataType{Value: &Numrange{}, Name: "numrange", OID: NumrangeOID}) + ci.RegisterDataType(DataType{Value: &Nummultirange{}, Name: "nummultirange", OID: NummultirangeOID}) ci.RegisterDataType(DataType{Value: &OIDValue{}, Name: "oid", OID: OIDOID}) ci.RegisterDataType(DataType{Value: &Path{}, Name: "path", OID: PathOID}) ci.RegisterDataType(DataType{Value: &Point{}, Name: "point", OID: PointOID}) @@ -527,8 +535,22 @@ type scanPlanDataTypeSQLScanner DataType func (plan *scanPlanDataTypeSQLScanner) Scan(ci *ConnInfo, oid uint32, formatCode int16, src []byte, dst interface{}) error { scanner, ok := dst.(sql.Scanner) if !ok { - newPlan := ci.PlanScan(oid, formatCode, dst) - return newPlan.Scan(ci, oid, formatCode, src, dst) + dv := reflect.ValueOf(dst) + if dv.Kind() != reflect.Ptr || !dv.Type().Elem().Implements(scannerType) { + newPlan := ci.PlanScan(oid, formatCode, dst) + return newPlan.Scan(ci, oid, formatCode, src, dst) + } + if src == nil { + // Ensure the pointer points to a zero version of the value + dv.Elem().Set(reflect.Zero(dv.Type().Elem())) + return nil + } + dv = dv.Elem() + // If the pointer is to a nil pointer then set that before scanning + if dv.Kind() == reflect.Ptr && dv.IsNil() { + dv.Set(reflect.New(dv.Type().Elem())) + } + scanner = dv.Interface().(sql.Scanner) } dt := (*DataType)(plan) @@ -587,8 +609,30 @@ func (plan *scanPlanDataTypeAssignTo) Scan(ci *ConnInfo, oid uint32, formatCode type scanPlanSQLScanner struct{} func (scanPlanSQLScanner) Scan(ci *ConnInfo, oid uint32, formatCode int16, src []byte, dst interface{}) error { - scanner := dst.(sql.Scanner) - if formatCode == BinaryFormatCode { + scanner, ok := dst.(sql.Scanner) + if !ok { + dv := reflect.ValueOf(dst) + if dv.Kind() != reflect.Ptr || !dv.Type().Elem().Implements(scannerType) { + newPlan := ci.PlanScan(oid, formatCode, dst) + return newPlan.Scan(ci, oid, formatCode, src, dst) + } + if src == nil { + // Ensure the pointer points to a zero version of the value + dv.Elem().Set(reflect.Zero(dv.Elem().Type())) + return nil + } + dv = dv.Elem() + // If the pointer is to a nil pointer then set that before scanning + if dv.Kind() == reflect.Ptr && dv.IsNil() { + dv.Set(reflect.New(dv.Type().Elem())) + } + scanner = dv.Interface().(sql.Scanner) + } + if src == nil { + // This is necessary because interface value []byte:nil does not equal nil:nil for the binary format path and the + // text format path would be converted to empty string. + return scanner.Scan(nil) + } else if formatCode == BinaryFormatCode { return scanner.Scan(src) } else { return scanner.Scan(string(src)) @@ -751,6 +795,18 @@ func (scanPlanString) Scan(ci *ConnInfo, oid uint32, formatCode int16, src []byt return newPlan.Scan(ci, oid, formatCode, src, dst) } +var scannerType = reflect.TypeOf((*sql.Scanner)(nil)).Elem() + +func isScanner(dst interface{}) bool { + if _, ok := dst.(sql.Scanner); ok { + return true + } + if t := reflect.TypeOf(dst); t != nil && t.Kind() == reflect.Ptr && t.Elem().Implements(scannerType) { + return true + } + return false +} + // PlanScan prepares a plan to scan a value into dst. func (ci *ConnInfo) PlanScan(oid uint32, formatCode int16, dst interface{}) ScanPlan { switch formatCode { @@ -815,13 +871,13 @@ func (ci *ConnInfo) PlanScan(oid uint32, formatCode int16, dst interface{}) Scan } if dt != nil { - if _, ok := dst.(sql.Scanner); ok { + if isScanner(dst) { return (*scanPlanDataTypeSQLScanner)(dt) } return (*scanPlanDataTypeAssignTo)(dt) } - if _, ok := dst.(sql.Scanner); ok { + if isScanner(dst) { return scanPlanSQLScanner{} } @@ -869,72 +925,77 @@ var nameValues map[string]Value func init() { nameValues = map[string]Value{ - "_aclitem": &ACLItemArray{}, - "_bool": &BoolArray{}, - "_bpchar": &BPCharArray{}, - "_bytea": &ByteaArray{}, - "_cidr": &CIDRArray{}, - "_date": &DateArray{}, - "_float4": &Float4Array{}, - "_float8": &Float8Array{}, - "_inet": &InetArray{}, - "_int2": &Int2Array{}, - "_int4": &Int4Array{}, - "_int8": &Int8Array{}, - "_numeric": &NumericArray{}, - "_text": &TextArray{}, - "_timestamp": &TimestampArray{}, - "_timestamptz": &TimestamptzArray{}, - "_uuid": &UUIDArray{}, - "_varchar": &VarcharArray{}, - "_jsonb": &JSONBArray{}, - "aclitem": &ACLItem{}, - "bit": &Bit{}, - "bool": &Bool{}, - "box": &Box{}, - "bpchar": &BPChar{}, - "bytea": &Bytea{}, - "char": &QChar{}, - "cid": &CID{}, - "cidr": &CIDR{}, - "circle": &Circle{}, - "date": &Date{}, - "daterange": &Daterange{}, - "float4": &Float4{}, - "float8": &Float8{}, - "hstore": &Hstore{}, - "inet": &Inet{}, - "int2": &Int2{}, - "int4": &Int4{}, - "int4range": &Int4range{}, - "int8": &Int8{}, - "int8range": &Int8range{}, - "interval": &Interval{}, - "json": &JSON{}, - "jsonb": &JSONB{}, - "line": &Line{}, - "lseg": &Lseg{}, - "macaddr": &Macaddr{}, - "name": &Name{}, - "numeric": &Numeric{}, - "numrange": &Numrange{}, - "oid": &OIDValue{}, - "path": &Path{}, - "point": &Point{}, - "polygon": &Polygon{}, - "record": &Record{}, - "text": &Text{}, - "tid": &TID{}, - "timestamp": &Timestamp{}, - "timestamptz": &Timestamptz{}, - "tsrange": &Tsrange{}, - "_tsrange": &TsrangeArray{}, - "tstzrange": &Tstzrange{}, - "_tstzrange": &TstzrangeArray{}, - "unknown": &Unknown{}, - "uuid": &UUID{}, - "varbit": &Varbit{}, - "varchar": &Varchar{}, - "xid": &XID{}, + "_aclitem": &ACLItemArray{}, + "_bool": &BoolArray{}, + "_bpchar": &BPCharArray{}, + "_bytea": &ByteaArray{}, + "_cidr": &CIDRArray{}, + "_date": &DateArray{}, + "_float4": &Float4Array{}, + "_float8": &Float8Array{}, + "_inet": &InetArray{}, + "_int2": &Int2Array{}, + "_int4": &Int4Array{}, + "_int8": &Int8Array{}, + "_numeric": &NumericArray{}, + "_text": &TextArray{}, + "_timestamp": &TimestampArray{}, + "_timestamptz": &TimestamptzArray{}, + "_uuid": &UUIDArray{}, + "_varchar": &VarcharArray{}, + "_json": &JSONArray{}, + "_jsonb": &JSONBArray{}, + "aclitem": &ACLItem{}, + "bit": &Bit{}, + "bool": &Bool{}, + "box": &Box{}, + "bpchar": &BPChar{}, + "bytea": &Bytea{}, + "char": &QChar{}, + "cid": &CID{}, + "cidr": &CIDR{}, + "circle": &Circle{}, + "date": &Date{}, + "daterange": &Daterange{}, + "float4": &Float4{}, + "float8": &Float8{}, + "hstore": &Hstore{}, + "inet": &Inet{}, + "int2": &Int2{}, + "int4": &Int4{}, + "int4range": &Int4range{}, + "int4multirange": &Int4multirange{}, + "int8": &Int8{}, + "int8range": &Int8range{}, + "int8multirange": &Int8multirange{}, + "interval": &Interval{}, + "json": &JSON{}, + "jsonb": &JSONB{}, + "line": &Line{}, + "lseg": &Lseg{}, + "ltree": &Ltree{}, + "macaddr": &Macaddr{}, + "name": &Name{}, + "numeric": &Numeric{}, + "numrange": &Numrange{}, + "nummultirange": &Nummultirange{}, + "oid": &OIDValue{}, + "path": &Path{}, + "point": &Point{}, + "polygon": &Polygon{}, + "record": &Record{}, + "text": &Text{}, + "tid": &TID{}, + "timestamp": &Timestamp{}, + "timestamptz": &Timestamptz{}, + "tsrange": &Tsrange{}, + "_tsrange": &TsrangeArray{}, + "tstzrange": &Tstzrange{}, + "_tstzrange": &TstzrangeArray{}, + "unknown": &Unknown{}, + "uuid": &UUID{}, + "varbit": &Varbit{}, + "varchar": &Varchar{}, + "xid": &XID{}, } } diff --git a/src/vendor/github.com/jackc/pgtype/record.go b/src/vendor/github.com/jackc/pgtype/record.go index 718c3570245..5cf2c93ab46 100644 --- a/src/vendor/github.com/jackc/pgtype/record.go +++ b/src/vendor/github.com/jackc/pgtype/record.go @@ -6,7 +6,7 @@ import ( ) // Record is the generic PostgreSQL record type such as is created with the -// "row" function. Record only implements BinaryEncoder and Value. The text +// "row" function. Record only implements BinaryDecoder and Value. The text // format output format from PostgreSQL does not include type information and is // therefore impossible to decode. No encoders are implemented because // PostgreSQL does not support input of generic records. diff --git a/src/vendor/github.com/jackc/pgtype/record_array.go b/src/vendor/github.com/jackc/pgtype/record_array.go new file mode 100644 index 00000000000..2271717a51b --- /dev/null +++ b/src/vendor/github.com/jackc/pgtype/record_array.go @@ -0,0 +1,318 @@ +// Code generated by erb. DO NOT EDIT. + +package pgtype + +import ( + "encoding/binary" + "fmt" + "reflect" +) + +type RecordArray struct { + Elements []Record + Dimensions []ArrayDimension + Status Status +} + +func (dst *RecordArray) Set(src interface{}) error { + // untyped nil and typed nil interfaces are different + if src == nil { + *dst = RecordArray{Status: Null} + return nil + } + + if value, ok := src.(interface{ Get() interface{} }); ok { + value2 := value.Get() + if value2 != value { + return dst.Set(value2) + } + } + + // Attempt to match to select common types: + switch value := src.(type) { + + case [][]Value: + if value == nil { + *dst = RecordArray{Status: Null} + } else if len(value) == 0 { + *dst = RecordArray{Status: Present} + } else { + elements := make([]Record, len(value)) + for i := range value { + if err := elements[i].Set(value[i]); err != nil { + return err + } + } + *dst = RecordArray{ + Elements: elements, + Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}}, + Status: Present, + } + } + + case []Record: + if value == nil { + *dst = RecordArray{Status: Null} + } else if len(value) == 0 { + *dst = RecordArray{Status: Present} + } else { + *dst = RecordArray{ + Elements: value, + Dimensions: []ArrayDimension{{Length: int32(len(value)), LowerBound: 1}}, + Status: Present, + } + } + default: + // Fallback to reflection if an optimised match was not found. + // The reflection is necessary for arrays and multidimensional slices, + // but it comes with a 20-50% performance penalty for large arrays/slices + reflectedValue := reflect.ValueOf(src) + if !reflectedValue.IsValid() || reflectedValue.IsZero() { + *dst = RecordArray{Status: Null} + return nil + } + + dimensions, elementsLength, ok := findDimensionsFromValue(reflectedValue, nil, 0) + if !ok { + return fmt.Errorf("cannot find dimensions of %v for RecordArray", src) + } + if elementsLength == 0 { + *dst = RecordArray{Status: Present} + return nil + } + if len(dimensions) == 0 { + if originalSrc, ok := underlyingSliceType(src); ok { + return dst.Set(originalSrc) + } + return fmt.Errorf("cannot convert %v to RecordArray", src) + } + + *dst = RecordArray{ + Elements: make([]Record, elementsLength), + Dimensions: dimensions, + Status: Present, + } + elementCount, err := dst.setRecursive(reflectedValue, 0, 0) + if err != nil { + // Maybe the target was one dimension too far, try again: + if len(dst.Dimensions) > 1 { + dst.Dimensions = dst.Dimensions[:len(dst.Dimensions)-1] + elementsLength = 0 + for _, dim := range dst.Dimensions { + if elementsLength == 0 { + elementsLength = int(dim.Length) + } else { + elementsLength *= int(dim.Length) + } + } + dst.Elements = make([]Record, elementsLength) + elementCount, err = dst.setRecursive(reflectedValue, 0, 0) + if err != nil { + return err + } + } else { + return err + } + } + if elementCount != len(dst.Elements) { + return fmt.Errorf("cannot convert %v to RecordArray, expected %d dst.Elements, but got %d instead", src, len(dst.Elements), elementCount) + } + } + + return nil +} + +func (dst *RecordArray) setRecursive(value reflect.Value, index, dimension int) (int, error) { + switch value.Kind() { + case reflect.Array: + fallthrough + case reflect.Slice: + if len(dst.Dimensions) == dimension { + break + } + + valueLen := value.Len() + if int32(valueLen) != dst.Dimensions[dimension].Length { + return 0, fmt.Errorf("multidimensional arrays must have array expressions with matching dimensions") + } + for i := 0; i < valueLen; i++ { + var err error + index, err = dst.setRecursive(value.Index(i), index, dimension+1) + if err != nil { + return 0, err + } + } + + return index, nil + } + if !value.CanInterface() { + return 0, fmt.Errorf("cannot convert all values to RecordArray") + } + if err := dst.Elements[index].Set(value.Interface()); err != nil { + return 0, fmt.Errorf("%v in RecordArray", err) + } + index++ + + return index, nil +} + +func (dst RecordArray) Get() interface{} { + switch dst.Status { + case Present: + return dst + case Null: + return nil + default: + return dst.Status + } +} + +func (src *RecordArray) AssignTo(dst interface{}) error { + switch src.Status { + case Present: + if len(src.Dimensions) <= 1 { + // Attempt to match to select common types: + switch v := dst.(type) { + + case *[][]Value: + *v = make([][]Value, len(src.Elements)) + for i := range src.Elements { + if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil { + return err + } + } + return nil + + } + } + + // Try to convert to something AssignTo can use directly. + if nextDst, retry := GetAssignToDstType(dst); retry { + return src.AssignTo(nextDst) + } + + // Fallback to reflection if an optimised match was not found. + // The reflection is necessary for arrays and multidimensional slices, + // but it comes with a 20-50% performance penalty for large arrays/slices + value := reflect.ValueOf(dst) + if value.Kind() == reflect.Ptr { + value = value.Elem() + } + + switch value.Kind() { + case reflect.Array, reflect.Slice: + default: + return fmt.Errorf("cannot assign %T to %T", src, dst) + } + + if len(src.Elements) == 0 { + if value.Kind() == reflect.Slice { + value.Set(reflect.MakeSlice(value.Type(), 0, 0)) + return nil + } + } + + elementCount, err := src.assignToRecursive(value, 0, 0) + if err != nil { + return err + } + if elementCount != len(src.Elements) { + return fmt.Errorf("cannot assign %v, needed to assign %d elements, but only assigned %d", dst, len(src.Elements), elementCount) + } + + return nil + case Null: + return NullAssignTo(dst) + } + + return fmt.Errorf("cannot decode %#v into %T", src, dst) +} + +func (src *RecordArray) assignToRecursive(value reflect.Value, index, dimension int) (int, error) { + switch kind := value.Kind(); kind { + case reflect.Array: + fallthrough + case reflect.Slice: + if len(src.Dimensions) == dimension { + break + } + + length := int(src.Dimensions[dimension].Length) + if reflect.Array == kind { + typ := value.Type() + if typ.Len() != length { + return 0, fmt.Errorf("expected size %d array, but %s has size %d array", length, typ, typ.Len()) + } + value.Set(reflect.New(typ).Elem()) + } else { + value.Set(reflect.MakeSlice(value.Type(), length, length)) + } + + var err error + for i := 0; i < length; i++ { + index, err = src.assignToRecursive(value.Index(i), index, dimension+1) + if err != nil { + return 0, err + } + } + + return index, nil + } + if len(src.Dimensions) != dimension { + return 0, fmt.Errorf("incorrect dimensions, expected %d, found %d", len(src.Dimensions), dimension) + } + if !value.CanAddr() { + return 0, fmt.Errorf("cannot assign all values from RecordArray") + } + addr := value.Addr() + if !addr.CanInterface() { + return 0, fmt.Errorf("cannot assign all values from RecordArray") + } + if err := src.Elements[index].AssignTo(addr.Interface()); err != nil { + return 0, err + } + index++ + return index, nil +} + +func (dst *RecordArray) DecodeBinary(ci *ConnInfo, src []byte) error { + if src == nil { + *dst = RecordArray{Status: Null} + return nil + } + + var arrayHeader ArrayHeader + rp, err := arrayHeader.DecodeBinary(ci, src) + if err != nil { + return err + } + + if len(arrayHeader.Dimensions) == 0 { + *dst = RecordArray{Dimensions: arrayHeader.Dimensions, Status: Present} + return nil + } + + elementCount := arrayHeader.Dimensions[0].Length + for _, d := range arrayHeader.Dimensions[1:] { + elementCount *= d.Length + } + + elements := make([]Record, elementCount) + + for i := range elements { + elemLen := int(int32(binary.BigEndian.Uint32(src[rp:]))) + rp += 4 + var elemSrc []byte + if elemLen >= 0 { + elemSrc = src[rp : rp+elemLen] + rp += elemLen + } + err = elements[i].DecodeBinary(ci, elemSrc) + if err != nil { + return err + } + } + + *dst = RecordArray{Elements: elements, Dimensions: arrayHeader.Dimensions, Status: Present} + return nil +} diff --git a/src/vendor/github.com/jackc/pgtype/text.go b/src/vendor/github.com/jackc/pgtype/text.go index 6b01d1b4992..a01815d945e 100644 --- a/src/vendor/github.com/jackc/pgtype/text.go +++ b/src/vendor/github.com/jackc/pgtype/text.go @@ -39,7 +39,37 @@ func (dst *Text) Set(src interface{}) error { } else { *dst = Text{String: string(value), Status: Present} } + case fmt.Stringer: + if value == fmt.Stringer(nil) { + *dst = Text{Status: Null} + } else { + *dst = Text{String: value.String(), Status: Present} + } default: + // Cannot be part of the switch: If Value() returns nil on + // non-string, we should still try to checks the underlying type + // using reflection. + // + // For example the struct might implement driver.Valuer with + // pointer receiver and fmt.Stringer with value receiver. + if value, ok := src.(driver.Valuer); ok { + if value == driver.Valuer(nil) { + *dst = Text{Status: Null} + return nil + } else { + v, err := value.Value() + if err != nil { + return fmt.Errorf("driver.Valuer Value() method failed: %w", err) + } + + // Handles also v == nil case. + if s, ok := v.(string); ok { + *dst = Text{String: s, Status: Present} + return nil + } + } + } + if originalSrc, ok := underlyingStringType(src); ok { return dst.Set(originalSrc) } diff --git a/src/vendor/github.com/jackc/pgtype/timestamp.go b/src/vendor/github.com/jackc/pgtype/timestamp.go index 4664411585a..fce490c8307 100644 --- a/src/vendor/github.com/jackc/pgtype/timestamp.go +++ b/src/vendor/github.com/jackc/pgtype/timestamp.go @@ -4,6 +4,7 @@ import ( "database/sql/driver" "encoding/binary" "fmt" + "strings" "time" "github.com/jackc/pgio" @@ -46,6 +47,14 @@ func (dst *Timestamp) Set(src interface{}) error { } else { return dst.Set(*value) } + case string: + return dst.DecodeText(nil, []byte(value)) + case *string: + if value == nil { + *dst = Timestamp{Status: Null} + } else { + return dst.Set(*value) + } case InfinityModifier: *dst = Timestamp{InfinityModifier: value, Status: Present} default: @@ -110,6 +119,15 @@ func (dst *Timestamp) DecodeText(ci *ConnInfo, src []byte) error { case "-infinity": *dst = Timestamp{Status: Present, InfinityModifier: -Infinity} default: + if strings.HasSuffix(sbuf, " BC") { + t, err := time.Parse(pgTimestampFormat, strings.TrimRight(sbuf, " BC")) + t2 := time.Date(1-t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location()) + if err != nil { + return err + } + *dst = Timestamp{Time: t2, Status: Present} + return nil + } tim, err := time.Parse(pgTimestampFormat, sbuf) if err != nil { return err @@ -141,8 +159,10 @@ func (dst *Timestamp) DecodeBinary(ci *ConnInfo, src []byte) error { case negativeInfinityMicrosecondOffset: *dst = Timestamp{Status: Present, InfinityModifier: -Infinity} default: - microsecSinceUnixEpoch := microsecFromUnixEpochToY2K + microsecSinceY2K - tim := time.Unix(microsecSinceUnixEpoch/1000000, (microsecSinceUnixEpoch%1000000)*1000).UTC() + tim := time.Unix( + microsecFromUnixEpochToY2K/1000000+microsecSinceY2K/1000000, + (microsecFromUnixEpochToY2K%1000000*1000)+(microsecSinceY2K%1000000*1000), + ).UTC() *dst = Timestamp{Time: tim, Status: Present} } diff --git a/src/vendor/github.com/jackc/pgtype/timestamptz.go b/src/vendor/github.com/jackc/pgtype/timestamptz.go index e0743060bf0..72ae4991d29 100644 --- a/src/vendor/github.com/jackc/pgtype/timestamptz.go +++ b/src/vendor/github.com/jackc/pgtype/timestamptz.go @@ -48,6 +48,14 @@ func (dst *Timestamptz) Set(src interface{}) error { } else { return dst.Set(*value) } + case string: + return dst.DecodeText(nil, []byte(value)) + case *string: + if value == nil { + *dst = Timestamptz{Status: Null} + } else { + return dst.Set(*value) + } case InfinityModifier: *dst = Timestamptz{InfinityModifier: value, Status: Present} default: @@ -124,7 +132,7 @@ func (dst *Timestamptz) DecodeText(ci *ConnInfo, src []byte) error { return err } - *dst = Timestamptz{Time: tim, Status: Present} + *dst = Timestamptz{Time: normalizePotentialUTC(tim), Status: Present} } return nil @@ -148,8 +156,10 @@ func (dst *Timestamptz) DecodeBinary(ci *ConnInfo, src []byte) error { case negativeInfinityMicrosecondOffset: *dst = Timestamptz{Status: Present, InfinityModifier: -Infinity} default: - microsecSinceUnixEpoch := microsecFromUnixEpochToY2K + microsecSinceY2K - tim := time.Unix(microsecSinceUnixEpoch/1000000, (microsecSinceUnixEpoch%1000000)*1000) + tim := time.Unix( + microsecFromUnixEpochToY2K/1000000+microsecSinceY2K/1000000, + (microsecFromUnixEpochToY2K%1000000*1000)+(microsecSinceY2K%1000000*1000), + ) *dst = Timestamptz{Time: tim, Status: Present} } @@ -229,6 +239,9 @@ func (src Timestamptz) Value() (driver.Value, error) { if src.InfinityModifier != None { return src.InfinityModifier.String(), nil } + if src.Time.Location().String() == time.UTC.String() { + return src.Time.UTC(), nil + } return src.Time, nil case Null: return nil, nil @@ -287,8 +300,23 @@ func (dst *Timestamptz) UnmarshalJSON(b []byte) error { return err } - *dst = Timestamptz{Time: tim, Status: Present} + *dst = Timestamptz{Time: normalizePotentialUTC(tim), Status: Present} } return nil } + +// Normalize timestamps in UTC location to behave similarly to how the Golang +// standard library does it: UTC timestamps lack a .loc value. +// +// Reason for this: when comparing two timestamps with reflect.DeepEqual (generally +// speaking not a good idea, but several testing libraries (for example testify) +// does this), their location data needs to be equal for them to be considered +// equal. +func normalizePotentialUTC(timestamp time.Time) time.Time { + if timestamp.Location().String() != time.UTC.String() { + return timestamp + } + + return timestamp.UTC() +} diff --git a/src/vendor/github.com/jackc/pgtype/typed_array.go.erb b/src/vendor/github.com/jackc/pgtype/typed_array.go.erb index 5788626b4a7..e8433c04391 100644 --- a/src/vendor/github.com/jackc/pgtype/typed_array.go.erb +++ b/src/vendor/github.com/jackc/pgtype/typed_array.go.erb @@ -1,5 +1,17 @@ // Code generated by erb. DO NOT EDIT. +<% + # defaults when not explicitly set on command line + + binary_format ||= "true" + text_format ||= "true" + + text_null ||= "NULL" + + encode_binary ||= binary_format + decode_binary ||= binary_format +%> + package pgtype import ( @@ -279,6 +291,7 @@ func (src *<%= pgtype_array_type %>) assignToRecursive(value reflect.Value, inde return index, nil } +<% if text_format == "true" %> func (dst *<%= pgtype_array_type %>) DecodeText(ci *ConnInfo, src []byte) error { if src == nil { *dst = <%= pgtype_array_type %>{Status: Null} @@ -314,8 +327,9 @@ func (dst *<%= pgtype_array_type %>) DecodeText(ci *ConnInfo, src []byte) error return nil } +<% end %> -<% if binary_format == "true" %> +<% if decode_binary == "true" %> func (dst *<%= pgtype_array_type %>) DecodeBinary(ci *ConnInfo, src []byte) error { if src == nil { *dst = <%= pgtype_array_type %>{Status: Null} @@ -359,6 +373,7 @@ func (dst *<%= pgtype_array_type %>) DecodeBinary(ci *ConnInfo, src []byte) erro } <% end %> +<% if text_format == "true" %> func (src <%= pgtype_array_type %>) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { switch src.Status { case Null: @@ -415,8 +430,9 @@ func (src <%= pgtype_array_type %>) EncodeText(ci *ConnInfo, buf []byte) ([]byte return buf, nil } +<% end %> -<% if binary_format == "true" %> +<% if encode_binary == "true" %> func (src <%= pgtype_array_type %>) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { switch src.Status { case Null: @@ -462,6 +478,7 @@ func (src <%= pgtype_array_type %>) EncodeText(ci *ConnInfo, buf []byte) ([]byte } <% end %> +<% if text_format == "true" %> // Scan implements the database/sql Scanner interface. func (dst *<%= pgtype_array_type %>) Scan(src interface{}) error { if src == nil { @@ -492,3 +509,4 @@ func (src <%= pgtype_array_type %>) Value() (driver.Value, error) { return string(buf), nil } +<% end %> diff --git a/src/vendor/github.com/jackc/pgtype/typed_array_gen.sh b/src/vendor/github.com/jackc/pgtype/typed_array_gen.sh index ea28be07744..9ec768bf1a9 100644 --- a/src/vendor/github.com/jackc/pgtype/typed_array_gen.sh +++ b/src/vendor/github.com/jackc/pgtype/typed_array_gen.sh @@ -1,28 +1,31 @@ -erb pgtype_array_type=Int2Array pgtype_element_type=Int2 go_array_types=[]int16,[]*int16,[]uint16,[]*uint16,[]int32,[]*int32,[]uint32,[]*uint32,[]int64,[]*int64,[]uint64,[]*uint64,[]int,[]*int,[]uint,[]*uint element_type_name=int2 text_null=NULL binary_format=true typed_array.go.erb > int2_array.go -erb pgtype_array_type=Int4Array pgtype_element_type=Int4 go_array_types=[]int16,[]*int16,[]uint16,[]*uint16,[]int32,[]*int32,[]uint32,[]*uint32,[]int64,[]*int64,[]uint64,[]*uint64,[]int,[]*int,[]uint,[]*uint element_type_name=int4 text_null=NULL binary_format=true typed_array.go.erb > int4_array.go -erb pgtype_array_type=Int8Array pgtype_element_type=Int8 go_array_types=[]int16,[]*int16,[]uint16,[]*uint16,[]int32,[]*int32,[]uint32,[]*uint32,[]int64,[]*int64,[]uint64,[]*uint64,[]int,[]*int,[]uint,[]*uint element_type_name=int8 text_null=NULL binary_format=true typed_array.go.erb > int8_array.go -erb pgtype_array_type=BoolArray pgtype_element_type=Bool go_array_types=[]bool,[]*bool element_type_name=bool text_null=NULL binary_format=true typed_array.go.erb > bool_array.go -erb pgtype_array_type=DateArray pgtype_element_type=Date go_array_types=[]time.Time,[]*time.Time element_type_name=date text_null=NULL binary_format=true typed_array.go.erb > date_array.go -erb pgtype_array_type=TimestamptzArray pgtype_element_type=Timestamptz go_array_types=[]time.Time,[]*time.Time element_type_name=timestamptz text_null=NULL binary_format=true typed_array.go.erb > timestamptz_array.go -erb pgtype_array_type=TstzrangeArray pgtype_element_type=Tstzrange go_array_types=[]Tstzrange element_type_name=tstzrange text_null=NULL binary_format=true typed_array.go.erb > tstzrange_array.go -erb pgtype_array_type=TsrangeArray pgtype_element_type=Tsrange go_array_types=[]Tsrange element_type_name=tsrange text_null=NULL binary_format=true typed_array.go.erb > tsrange_array.go -erb pgtype_array_type=TimestampArray pgtype_element_type=Timestamp go_array_types=[]time.Time,[]*time.Time element_type_name=timestamp text_null=NULL binary_format=true typed_array.go.erb > timestamp_array.go -erb pgtype_array_type=Float4Array pgtype_element_type=Float4 go_array_types=[]float32,[]*float32 element_type_name=float4 text_null=NULL binary_format=true typed_array.go.erb > float4_array.go -erb pgtype_array_type=Float8Array pgtype_element_type=Float8 go_array_types=[]float64,[]*float64 element_type_name=float8 text_null=NULL binary_format=true typed_array.go.erb > float8_array.go -erb pgtype_array_type=InetArray pgtype_element_type=Inet go_array_types=[]*net.IPNet,[]net.IP,[]*net.IP element_type_name=inet text_null=NULL binary_format=true typed_array.go.erb > inet_array.go -erb pgtype_array_type=MacaddrArray pgtype_element_type=Macaddr go_array_types=[]net.HardwareAddr,[]*net.HardwareAddr element_type_name=macaddr text_null=NULL binary_format=true typed_array.go.erb > macaddr_array.go -erb pgtype_array_type=CIDRArray pgtype_element_type=CIDR go_array_types=[]*net.IPNet,[]net.IP,[]*net.IP element_type_name=cidr text_null=NULL binary_format=true typed_array.go.erb > cidr_array.go -erb pgtype_array_type=TextArray pgtype_element_type=Text go_array_types=[]string,[]*string element_type_name=text text_null=NULL binary_format=true typed_array.go.erb > text_array.go -erb pgtype_array_type=VarcharArray pgtype_element_type=Varchar go_array_types=[]string,[]*string element_type_name=varchar text_null=NULL binary_format=true typed_array.go.erb > varchar_array.go -erb pgtype_array_type=BPCharArray pgtype_element_type=BPChar go_array_types=[]string,[]*string element_type_name=bpchar text_null=NULL binary_format=true typed_array.go.erb > bpchar_array.go -erb pgtype_array_type=ByteaArray pgtype_element_type=Bytea go_array_types=[][]byte element_type_name=bytea text_null=NULL binary_format=true typed_array.go.erb > bytea_array.go -erb pgtype_array_type=ACLItemArray pgtype_element_type=ACLItem go_array_types=[]string,[]*string element_type_name=aclitem text_null=NULL binary_format=false typed_array.go.erb > aclitem_array.go -erb pgtype_array_type=HstoreArray pgtype_element_type=Hstore go_array_types=[]map[string]string element_type_name=hstore text_null=NULL binary_format=true typed_array.go.erb > hstore_array.go -erb pgtype_array_type=NumericArray pgtype_element_type=Numeric go_array_types=[]float32,[]*float32,[]float64,[]*float64,[]int64,[]*int64,[]uint64,[]*uint64 element_type_name=numeric text_null=NULL binary_format=true typed_array.go.erb > numeric_array.go -erb pgtype_array_type=UUIDArray pgtype_element_type=UUID go_array_types=[][16]byte,[][]byte,[]string,[]*string element_type_name=uuid text_null=NULL binary_format=true typed_array.go.erb > uuid_array.go -erb pgtype_array_type=JSONBArray pgtype_element_type=JSONB go_array_types=[]string,[][]byte element_type_name=jsonb text_null=NULL binary_format=true typed_array.go.erb > jsonb_array.go +erb pgtype_array_type=Int2Array pgtype_element_type=Int2 go_array_types=[]int16,[]*int16,[]uint16,[]*uint16,[]int32,[]*int32,[]uint32,[]*uint32,[]int64,[]*int64,[]uint64,[]*uint64,[]int,[]*int,[]uint,[]*uint element_type_name=int2 typed_array.go.erb > int2_array.go +erb pgtype_array_type=Int4Array pgtype_element_type=Int4 go_array_types=[]int16,[]*int16,[]uint16,[]*uint16,[]int32,[]*int32,[]uint32,[]*uint32,[]int64,[]*int64,[]uint64,[]*uint64,[]int,[]*int,[]uint,[]*uint element_type_name=int4 typed_array.go.erb > int4_array.go +erb pgtype_array_type=Int8Array pgtype_element_type=Int8 go_array_types=[]int16,[]*int16,[]uint16,[]*uint16,[]int32,[]*int32,[]uint32,[]*uint32,[]int64,[]*int64,[]uint64,[]*uint64,[]int,[]*int,[]uint,[]*uint element_type_name=int8 typed_array.go.erb > int8_array.go +erb pgtype_array_type=BoolArray pgtype_element_type=Bool go_array_types=[]bool,[]*bool element_type_name=bool typed_array.go.erb > bool_array.go +erb pgtype_array_type=DateArray pgtype_element_type=Date go_array_types=[]time.Time,[]*time.Time element_type_name=date typed_array.go.erb > date_array.go +erb pgtype_array_type=TimestamptzArray pgtype_element_type=Timestamptz go_array_types=[]time.Time,[]*time.Time element_type_name=timestamptz typed_array.go.erb > timestamptz_array.go +erb pgtype_array_type=TstzrangeArray pgtype_element_type=Tstzrange go_array_types=[]Tstzrange element_type_name=tstzrange typed_array.go.erb > tstzrange_array.go +erb pgtype_array_type=TsrangeArray pgtype_element_type=Tsrange go_array_types=[]Tsrange element_type_name=tsrange typed_array.go.erb > tsrange_array.go +erb pgtype_array_type=TimestampArray pgtype_element_type=Timestamp go_array_types=[]time.Time,[]*time.Time element_type_name=timestamp typed_array.go.erb > timestamp_array.go +erb pgtype_array_type=Float4Array pgtype_element_type=Float4 go_array_types=[]float32,[]*float32 element_type_name=float4 typed_array.go.erb > float4_array.go +erb pgtype_array_type=Float8Array pgtype_element_type=Float8 go_array_types=[]float64,[]*float64 element_type_name=float8 typed_array.go.erb > float8_array.go +erb pgtype_array_type=InetArray pgtype_element_type=Inet go_array_types=[]*net.IPNet,[]net.IP,[]*net.IP element_type_name=inet typed_array.go.erb > inet_array.go +erb pgtype_array_type=MacaddrArray pgtype_element_type=Macaddr go_array_types=[]net.HardwareAddr,[]*net.HardwareAddr element_type_name=macaddr typed_array.go.erb > macaddr_array.go +erb pgtype_array_type=CIDRArray pgtype_element_type=CIDR go_array_types=[]*net.IPNet,[]net.IP,[]*net.IP element_type_name=cidr typed_array.go.erb > cidr_array.go +erb pgtype_array_type=TextArray pgtype_element_type=Text go_array_types=[]string,[]*string element_type_name=text typed_array.go.erb > text_array.go +erb pgtype_array_type=VarcharArray pgtype_element_type=Varchar go_array_types=[]string,[]*string element_type_name=varchar typed_array.go.erb > varchar_array.go +erb pgtype_array_type=BPCharArray pgtype_element_type=BPChar go_array_types=[]string,[]*string element_type_name=bpchar typed_array.go.erb > bpchar_array.go +erb pgtype_array_type=ByteaArray pgtype_element_type=Bytea go_array_types=[][]byte element_type_name=bytea typed_array.go.erb > bytea_array.go +erb pgtype_array_type=ACLItemArray pgtype_element_type=ACLItem go_array_types=[]string,[]*string element_type_name=aclitem binary_format=false typed_array.go.erb > aclitem_array.go +erb pgtype_array_type=HstoreArray pgtype_element_type=Hstore go_array_types=[]map[string]string element_type_name=hstore typed_array.go.erb > hstore_array.go +erb pgtype_array_type=NumericArray pgtype_element_type=Numeric go_array_types=[]float32,[]*float32,[]float64,[]*float64,[]int64,[]*int64,[]uint64,[]*uint64 element_type_name=numeric typed_array.go.erb > numeric_array.go +erb pgtype_array_type=UUIDArray pgtype_element_type=UUID go_array_types=[][16]byte,[][]byte,[]string,[]*string element_type_name=uuid typed_array.go.erb > uuid_array.go +erb pgtype_array_type=JSONArray pgtype_element_type=JSON go_array_types=[]string,[][]byte,[]json.RawMessage element_type_name=json typed_array.go.erb > json_array.go +erb pgtype_array_type=JSONBArray pgtype_element_type=JSONB go_array_types=[]string,[][]byte,[]json.RawMessage element_type_name=jsonb typed_array.go.erb > jsonb_array.go # While the binary format is theoretically possible it is only practical to use the text format. -erb pgtype_array_type=EnumArray pgtype_element_type=GenericText go_array_types=[]string,[]*string text_null=NULL binary_format=false typed_array.go.erb > enum_array.go +erb pgtype_array_type=EnumArray pgtype_element_type=GenericText go_array_types=[]string,[]*string binary_format=false typed_array.go.erb > enum_array.go + +erb pgtype_array_type=RecordArray pgtype_element_type=Record go_array_types=[][]Value element_type_name=record text_null=NULL encode_binary=false text_format=false typed_array.go.erb > record_array.go goimports -w *_array.go diff --git a/src/vendor/github.com/jackc/pgtype/typed_multirange.go.erb b/src/vendor/github.com/jackc/pgtype/typed_multirange.go.erb new file mode 100644 index 00000000000..84c8299fa50 --- /dev/null +++ b/src/vendor/github.com/jackc/pgtype/typed_multirange.go.erb @@ -0,0 +1,239 @@ +package pgtype + +import ( + "database/sql/driver" + "encoding/binary" + "fmt" + + "github.com/jackc/pgio" +) + +type <%= multirange_type %> struct { + Ranges []<%= range_type %> + Status Status +} + +func (dst *<%= multirange_type %>) Set(src interface{}) error { + //untyped nil and typed nil interfaces are different + if src == nil { + *dst = <%= multirange_type %>{Status: Null} + return nil + } + + switch value := src.(type) { + case <%= multirange_type %>: + *dst = value + case *<%= multirange_type %>: + *dst = *value + case string: + return dst.DecodeText(nil, []byte(value)) + case []<%= range_type %>: + if value == nil { + *dst = <%= multirange_type %>{Status: Null} + } else if len(value) == 0 { + *dst = <%= multirange_type %>{Status: Present} + } else { + elements := make([]<%= range_type %>, len(value)) + for i := range value { + if err := elements[i].Set(value[i]); err != nil { + return err + } + } + *dst = <%= multirange_type %>{ + Ranges: elements, + Status: Present, + } + } + case []*<%= range_type %>: + if value == nil { + *dst = <%= multirange_type %>{Status: Null} + } else if len(value) == 0 { + *dst = <%= multirange_type %>{Status: Present} + } else { + elements := make([]<%= range_type %>, len(value)) + for i := range value { + if err := elements[i].Set(value[i]); err != nil { + return err + } + } + *dst = <%= multirange_type %>{ + Ranges: elements, + Status: Present, + } + } + default: + return fmt.Errorf("cannot convert %v to <%= multirange_type %>", src) + } + + return nil + +} + +func (dst <%= multirange_type %>) Get() interface{} { + switch dst.Status { + case Present: + return dst + case Null: + return nil + default: + return dst.Status + } +} + +func (src *<%= multirange_type %>) AssignTo(dst interface{}) error { + return fmt.Errorf("cannot assign %v to %T", src, dst) +} + +func (dst *<%= multirange_type %>) DecodeText(ci *ConnInfo, src []byte) error { + if src == nil { + *dst = <%= multirange_type %>{Status: Null} + return nil + } + + utmr, err := ParseUntypedTextMultirange(string(src)) + if err != nil { + return err + } + + var elements []<%= range_type %> + + if len(utmr.Elements) > 0 { + elements = make([]<%= range_type %>, len(utmr.Elements)) + + for i, s := range utmr.Elements { + var elem <%= range_type %> + + elemSrc := []byte(s) + + err = elem.DecodeText(ci, elemSrc) + if err != nil { + return err + } + + elements[i] = elem + } + } + + *dst = <%= multirange_type %>{Ranges: elements, Status: Present} + + return nil +} + +func (dst *<%= multirange_type %>) DecodeBinary(ci *ConnInfo, src []byte) error { + if src == nil { + *dst = <%= multirange_type %>{Status: Null} + return nil + } + + rp := 0 + + numElems := int(binary.BigEndian.Uint32(src[rp:])) + rp += 4 + + if numElems == 0 { + *dst = <%= multirange_type %>{Status: Present} + return nil + } + + elements := make([]<%= range_type %>, numElems) + + for i := range elements { + elemLen := int(int32(binary.BigEndian.Uint32(src[rp:]))) + rp += 4 + var elemSrc []byte + if elemLen >= 0 { + elemSrc = src[rp : rp+elemLen] + rp += elemLen + } + err := elements[i].DecodeBinary(ci, elemSrc) + if err != nil { + return err + } + } + + *dst = <%= multirange_type %>{Ranges: elements, Status: Present} + return nil +} + +func (src <%= multirange_type %>) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { + switch src.Status { + case Null: + return nil, nil + case Undefined: + return nil, errUndefined + } + + buf = append(buf, '{') + + inElemBuf := make([]byte, 0, 32) + for i, elem := range src.Ranges { + if i > 0 { + buf = append(buf, ',') + } + + elemBuf, err := elem.EncodeText(ci, inElemBuf) + if err != nil { + return nil, err + } + if elemBuf == nil { + return nil, fmt.Errorf("multi-range does not allow null range") + } else { + buf = append(buf, string(elemBuf)...) + } + + } + + buf = append(buf, '}') + + return buf, nil +} + +func (src <%= multirange_type %>) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { + switch src.Status { + case Null: + return nil, nil + case Undefined: + return nil, errUndefined + } + + buf = pgio.AppendInt32(buf, int32(len(src.Ranges))) + + for i := range src.Ranges { + sp := len(buf) + buf = pgio.AppendInt32(buf, -1) + + elemBuf, err := src.Ranges[i].EncodeBinary(ci, buf) + if err != nil { + return nil, err + } + if elemBuf != nil { + buf = elemBuf + pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4)) + } + } + + return buf, nil +} + +// Scan implements the database/sql Scanner interface. +func (dst *<%= multirange_type %>) Scan(src interface{}) error { + if src == nil { + return dst.DecodeText(nil, nil) + } + + switch src := src.(type) { + case string: + return dst.DecodeText(nil, []byte(src)) + case []byte: + srcCopy := make([]byte, len(src)) + copy(srcCopy, src) + return dst.DecodeText(nil, srcCopy) + } + + return fmt.Errorf("cannot scan %T", src) +} + +// Value implements the database/sql/driver Valuer interface. +func (src <%= multirange_type %>) Value() (driver.Value, error) { + return EncodeValueText(src) +} diff --git a/src/vendor/github.com/jackc/pgtype/typed_multirange_gen.sh b/src/vendor/github.com/jackc/pgtype/typed_multirange_gen.sh new file mode 100644 index 00000000000..610f40a1ebd --- /dev/null +++ b/src/vendor/github.com/jackc/pgtype/typed_multirange_gen.sh @@ -0,0 +1,8 @@ +erb range_type=Numrange multirange_type=Nummultirange typed_multirange.go.erb > num_multirange.go +erb range_type=Int4range multirange_type=Int4multirange typed_multirange.go.erb > int4_multirange.go +erb range_type=Int8range multirange_type=Int8multirange typed_multirange.go.erb > int8_multirange.go +# TODO +# erb range_type=Tsrange multirange_type=Tsmultirange typed_multirange.go.erb > ts_multirange.go +# erb range_type=Tstzrange multirange_type=Tstzmultirange typed_multirange.go.erb > tstz_multirange.go +# erb range_type=Daterange multirange_type=Datemultirange typed_multirange.go.erb > date_multirange.go +goimports -w *multirange.go \ No newline at end of file diff --git a/src/vendor/github.com/jackc/pgtype/uuid.go b/src/vendor/github.com/jackc/pgtype/uuid.go index fa0be07feb7..6839c052dec 100644 --- a/src/vendor/github.com/jackc/pgtype/uuid.go +++ b/src/vendor/github.com/jackc/pgtype/uuid.go @@ -18,14 +18,15 @@ func (dst *UUID) Set(src interface{}) error { return nil } - if value, ok := src.(interface{ Get() interface{} }); ok { + switch value := src.(type) { + case interface{ Get() interface{} }: value2 := value.Get() if value2 != value { return dst.Set(value2) } - } - - switch value := src.(type) { + case fmt.Stringer: + value2 := value.String() + return dst.Set(value2) case [16]byte: *dst = UUID{Bytes: value, Status: Present} case []byte: diff --git a/src/vendor/github.com/jackc/pgx/v4/CHANGELOG.md b/src/vendor/github.com/jackc/pgx/v4/CHANGELOG.md index 3d19829efc0..8efe01a9c2e 100644 --- a/src/vendor/github.com/jackc/pgx/v4/CHANGELOG.md +++ b/src/vendor/github.com/jackc/pgx/v4/CHANGELOG.md @@ -1,3 +1,81 @@ +# 4.18.1 (February 27, 2023) + +* Fix: Support pgx v4 and v5 stdlib in same program (Tomáš Procházka) + +# 4.18.0 (February 11, 2023) + +* Upgrade pgconn to v1.14.0 +* Upgrade pgproto3 to v2.3.2 +* Upgrade pgtype to v1.14.0 +* Fix query sanitizer when query text contains Unicode replacement character +* Fix context with value in BeforeConnect (David Harju) +* Support pgx v4 and v5 stdlib in same program (Vitalii Solodilov) + +# 4.17.2 (September 3, 2022) + +* Fix panic when logging batch error (Tom Möller) + +# 4.17.1 (August 27, 2022) + +* Upgrade puddle to v1.3.0 - fixes context failing to cancel Acquire when acquire is creating resource which was introduced in v4.17.0 (James Hartig) +* Fix atomic alignment on 32-bit platforms + +# 4.17.0 (August 6, 2022) + +* Upgrade pgconn to v1.13.0 +* Upgrade pgproto3 to v2.3.1 +* Upgrade pgtype to v1.12.0 +* Allow background pool connections to continue even if cause is canceled (James Hartig) +* Add LoggerFunc (Gabor Szabad) +* pgxpool: health check should avoid going below minConns (James Hartig) +* Add pgxpool.Conn.Hijack() +* Logging improvements (Stepan Rabotkin) + +# 4.16.1 (May 7, 2022) + +* Upgrade pgconn to v1.12.1 +* Fix explicitly prepared statements with describe statement cache mode + +# 4.16.0 (April 21, 2022) + +* Upgrade pgconn to v1.12.0 +* Upgrade pgproto3 to v2.3.0 +* Upgrade pgtype to v1.11.0 +* Fix: Do not panic when context cancelled while getting statement from cache. +* Fix: Less memory pinning from old Rows. +* Fix: Support '\r' line ending when sanitizing SQL comment. +* Add pluggable GSSAPI support (Oliver Tan) + +# 4.15.0 (February 7, 2022) + +* Upgrade to pgconn v1.11.0 +* Upgrade to pgtype v1.10.0 +* Upgrade puddle to v1.2.1 +* Make BatchResults.Close safe to be called multiple times + +# 4.14.1 (November 28, 2021) + +* Upgrade pgtype to v1.9.1 (fixes unintentional change to timestamp binary decoding) +* Start pgxpool background health check after initial connections + +# 4.14.0 (November 20, 2021) + +* Upgrade pgconn to v1.10.1 +* Upgrade pgproto3 to v2.2.0 +* Upgrade pgtype to v1.9.0 +* Upgrade puddle to v1.2.0 +* Add QueryFunc to BatchResults +* Add context options to zerologadapter (Thomas Frössman) +* Add zerologadapter.NewContextLogger (urso) +* Eager initialize minpoolsize on connect (Daniel) +* Unpin memory used by large queries immediately after use + +# 4.13.0 (July 24, 2021) + +* Trimmed pseudo-dependencies in Go modules from other packages tests +* Upgrade pgconn -- context cancellation no longer will return a net.Error +* Support time durations for simple protocol (Michael Darr) + # 4.12.0 (July 10, 2021) * ResetSession hook is called before a connection is reused from pool for another query (Dmytro Haranzha) diff --git a/src/vendor/github.com/jackc/pgx/v4/README.md b/src/vendor/github.com/jackc/pgx/v4/README.md index 73232044765..ec9212715a7 100644 --- a/src/vendor/github.com/jackc/pgx/v4/README.md +++ b/src/vendor/github.com/jackc/pgx/v4/README.md @@ -1,6 +1,11 @@ [![](https://godoc.org/github.com/jackc/pgx?status.svg)](https://pkg.go.dev/github.com/jackc/pgx/v4) [![Build Status](https://travis-ci.org/jackc/pgx.svg)](https://travis-ci.org/jackc/pgx) +--- + +This is the previous stable `v4` release. `v5` been released. + +--- # pgx - PostgreSQL Driver and Toolkit pgx is a pure Go driver and toolkit for PostgreSQL. @@ -73,7 +78,7 @@ pgx supports many features beyond what is available through `database/sql`: * Single-round trip query mode * Full TLS connection control * Binary format support for custom types (allows for much quicker encoding/decoding) -* Copy protocol support for faster bulk data loads +* COPY protocol support for faster bulk data loads * Extendable logging support including built-in support for `log15adapter`, [`logrus`](https://github.com/sirupsen/logrus), [`zap`](https://github.com/uber-go/zap), and [`zerolog`](https://github.com/rs/zerolog) * Connection pool with after-connect hook for arbitrary connection setup * Listen / notify @@ -98,26 +103,6 @@ There are three areas in particular where pgx can provide a significant performa perform nearly 3x the number of queries per second. 3. Batched queries - Multiple queries can be batched together to minimize network round trips. -## Comparison with Alternatives - -* [pq](http://godoc.org/github.com/lib/pq) -* [go-pg](https://github.com/go-pg/pg) - -For prepared queries with small sets of simple data types, all drivers will have have similar performance. However, if prepared statements aren't being explicitly used, pgx can have a significant performance advantage due to automatic statement preparation. -pgx also can perform better when using PostgreSQL-specific data types or query batching. See -[go_db_bench](https://github.com/jackc/go_db_bench) for some database driver benchmarks. - -### Compatibility with `database/sql` - -pq is exclusively used with `database/sql`. go-pg does not use `database/sql` at all. pgx supports `database/sql` as well as -its own interface. - -### Level of access, ORM - -go-pg is a PostgreSQL client and ORM. It includes many features that traditionally sit above the database driver, such as ORM, struct mapping, soft deletes, schema migrations, and sharding support. - -pgx is "closer to the metal" and such abstractions are beyond the scope of the pgx project, which first and foremost, aims to be a performant driver and toolkit. - ## Testing pgx tests naturally require a PostgreSQL database. It will connect to the database specified in the `PGX_TEST_DATABASE` environment @@ -149,7 +134,7 @@ In addition, there are tests specific for PgBouncer that will be executed if `PG ## Supported Go and PostgreSQL Versions -pgx supports the same versions of Go and PostgreSQL that are supported by their respective teams. For [Go](https://golang.org/doc/devel/release.html#policy) that is the two most recent major releases and for [PostgreSQL](https://www.postgresql.org/support/versioning/) the major releases in the last 5 years. This means pgx supports Go 1.15 and higher and PostgreSQL 9.6 and higher. pgx also is tested against the latest version of [CockroachDB](https://www.cockroachlabs.com/product/). +pgx supports the same versions of Go and PostgreSQL that are supported by their respective teams. For [Go](https://golang.org/doc/devel/release.html#policy) that is the two most recent major releases and for [PostgreSQL](https://www.postgresql.org/support/versioning/) the major releases in the last 5 years. This means pgx supports Go 1.16 and higher and PostgreSQL 10 and higher. pgx also is tested against the latest version of [CockroachDB](https://www.cockroachlabs.com/product/). ## Version Policy @@ -201,3 +186,11 @@ pgerrcode contains constants for the PostgreSQL error codes. ### [github.com/georgysavva/scany](https://github.com/georgysavva/scany) Library for scanning data from a database into Go structs and more. + +### [https://github.com/otan/gopgkrb5](https://github.com/otan/gopgkrb5) + +Adds GSSAPI / Kerberos authentication support. + +### [https://github.com/vgarvardt/pgx-google-uuid](https://github.com/vgarvardt/pgx-google-uuid) + +Adds support for [`github.com/google/uuid`](https://github.com/google/uuid). diff --git a/src/vendor/github.com/jackc/pgx/v4/batch.go b/src/vendor/github.com/jackc/pgx/v4/batch.go index 4b96ca1942e..7f86ad5c359 100644 --- a/src/vendor/github.com/jackc/pgx/v4/batch.go +++ b/src/vendor/github.com/jackc/pgx/v4/batch.go @@ -3,6 +3,7 @@ package pgx import ( "context" "errors" + "fmt" "github.com/jackc/pgconn" ) @@ -41,19 +42,23 @@ type BatchResults interface { // QueryRow reads the results from the next query in the batch as if the query has been sent with Conn.QueryRow. QueryRow() Row + // QueryFunc reads the results from the next query in the batch as if the query has been sent with Conn.QueryFunc. + QueryFunc(scans []interface{}, f func(QueryFuncRow) error) (pgconn.CommandTag, error) + // Close closes the batch operation. This must be called before the underlying connection can be used again. Any error // that occurred during a batch operation may have made it impossible to resyncronize the connection with the server. - // In this case the underlying connection will have been closed. + // In this case the underlying connection will have been closed. Close is safe to call multiple times. Close() error } type batchResults struct { - ctx context.Context - conn *Conn - mrr *pgconn.MultiResultReader - err error - b *Batch - ix int + ctx context.Context + conn *Conn + mrr *pgconn.MultiResultReader + err error + b *Batch + ix int + closed bool } // Exec reads the results from the next query in the batch as if the query has been sent with Exec. @@ -61,6 +66,9 @@ func (br *batchResults) Exec() (pgconn.CommandTag, error) { if br.err != nil { return nil, br.err } + if br.closed { + return nil, fmt.Errorf("batch already closed") + } query, arguments, _ := br.nextQueryAndArgs() @@ -111,6 +119,11 @@ func (br *batchResults) Query() (Rows, error) { return &connRows{err: br.err, closed: true}, br.err } + if br.closed { + alreadyClosedErr := fmt.Errorf("batch already closed") + return &connRows{err: alreadyClosedErr, closed: true}, alreadyClosedErr + } + rows := br.conn.getRows(br.ctx, query, arguments) if !br.mrr.NextResult() { @@ -135,6 +148,37 @@ func (br *batchResults) Query() (Rows, error) { return rows, nil } +// QueryFunc reads the results from the next query in the batch as if the query has been sent with Conn.QueryFunc. +func (br *batchResults) QueryFunc(scans []interface{}, f func(QueryFuncRow) error) (pgconn.CommandTag, error) { + if br.closed { + return nil, fmt.Errorf("batch already closed") + } + + rows, err := br.Query() + if err != nil { + return nil, err + } + defer rows.Close() + + for rows.Next() { + err = rows.Scan(scans...) + if err != nil { + return nil, err + } + + err = f(rows) + if err != nil { + return nil, err + } + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return rows.CommandTag(), nil +} + // QueryRow reads the results from the next query in the batch as if the query has been sent with QueryRow. func (br *batchResults) QueryRow() Row { rows, _ := br.Query() @@ -149,6 +193,11 @@ func (br *batchResults) Close() error { return br.err } + if br.closed { + return nil + } + br.closed = true + // log any queries that haven't yet been logged by Exec or Query for { query, args, ok := br.nextQueryAndArgs() diff --git a/src/vendor/github.com/jackc/pgx/v4/conn.go b/src/vendor/github.com/jackc/pgx/v4/conn.go index 9636f2fd61a..6f83f497232 100644 --- a/src/vendor/github.com/jackc/pgx/v4/conn.go +++ b/src/vendor/github.com/jackc/pgx/v4/conn.go @@ -50,6 +50,7 @@ func (cc *ConnConfig) Copy() *ConnConfig { return newConfig } +// ConnString returns the connection string as parsed by pgx.ParseConfig into pgx.ConnConfig. func (cc *ConnConfig) ConnString() string { return cc.connString } // BuildStatementCacheFunc is a function that can be used to create a stmtcache.Cache implementation for connection. @@ -72,9 +73,8 @@ type Conn struct { connInfo *pgtype.ConnInfo - wbuf []byte - preallocatedRows []connRows - eqb extendedQueryBuilder + wbuf []byte + eqb extendedQueryBuilder } // Identifier a PostgreSQL identifier or name. Identifiers can be composed of @@ -107,8 +107,8 @@ func Connect(ctx context.Context, connString string) (*Conn, error) { return connect(ctx, connConfig) } -// Connect establishes a connection with a PostgreSQL server with a configuration struct. connConfig must have been -// created by ParseConfig. +// ConnectConfig establishes a connection with a PostgreSQL server with a configuration struct. +// connConfig must have been created by ParseConfig. func ConnectConfig(ctx context.Context, connConfig *ConnConfig) (*Conn, error) { return connect(ctx, connConfig) } @@ -116,14 +116,14 @@ func ConnectConfig(ctx context.Context, connConfig *ConnConfig) (*Conn, error) { // ParseConfig creates a ConnConfig from a connection string. ParseConfig handles all options that pgconn.ParseConfig // does. In addition, it accepts the following options: // -// statement_cache_capacity -// The maximum size of the automatic statement cache. Set to 0 to disable automatic statement caching. Default: 512. +// statement_cache_capacity +// The maximum size of the automatic statement cache. Set to 0 to disable automatic statement caching. Default: 512. // -// statement_cache_mode -// Possible values: "prepare" and "describe". "prepare" will create prepared statements on the PostgreSQL server. -// "describe" will use the anonymous prepared statement to describe a statement without creating a statement on the -// server. "describe" is primarily useful when the environment does not allow prepared statements such as when -// running a connection pooler like PgBouncer. Default: "prepare" +// statement_cache_mode +// Possible values: "prepare" and "describe". "prepare" will create prepared statements on the PostgreSQL server. +// "describe" will use the anonymous prepared statement to describe a statement without creating a statement on the +// server. "describe" is primarily useful when the environment does not allow prepared statements such as when +// running a connection pooler like PgBouncer. Default: "prepare" // // prefer_simple_protocol // Possible values: "true" and "false". Use the simple protocol instead of extended protocol. Default: false @@ -324,6 +324,7 @@ func (c *Conn) WaitForNotification(ctx context.Context) (*pgconn.Notification, e return n, err } +// IsClosed reports if the connection has been closed. func (c *Conn) IsClosed() bool { return c.pgConn.IsClosed() } @@ -357,35 +358,13 @@ func quoteIdentifier(s string) string { return `"` + strings.ReplaceAll(s, `"`, `""`) + `"` } +// Ping executes an empty sql statement against the *Conn +// If the sql returns without error, the database Ping is considered successful, otherwise, the error is returned. func (c *Conn) Ping(ctx context.Context) error { _, err := c.Exec(ctx, ";") return err } -func connInfoFromRows(rows Rows, err error) (map[string]uint32, error) { - if err != nil { - return nil, err - } - defer rows.Close() - - nameOIDs := make(map[string]uint32, 256) - for rows.Next() { - var oid uint32 - var name pgtype.Text - if err = rows.Scan(&oid, &name); err != nil { - return nil, err - } - - nameOIDs[name.String] = oid - } - - if err = rows.Err(); err != nil { - return nil, err - } - - return nameOIDs, err -} - // PgConn returns the underlying *pgconn.PgConn. This is an escape hatch method that allows lower level access to the // PostgreSQL connection than pgx exposes. // @@ -410,7 +389,8 @@ func (c *Conn) Exec(ctx context.Context, sql string, arguments ...interface{}) ( commandTag, err := c.exec(ctx, sql, arguments...) if err != nil { if c.shouldLog(LogLevelError) { - c.log(ctx, LogLevelError, "Exec", map[string]interface{}{"sql": sql, "args": logQueryArgs(arguments), "err": err}) + endTime := time.Now() + c.log(ctx, LogLevelError, "Exec", map[string]interface{}{"sql": sql, "args": logQueryArgs(arguments), "err": err, "time": endTime.Sub(startTime)}) } return commandTag, err } @@ -517,6 +497,7 @@ func (c *Conn) execParams(ctx context.Context, sd *pgconn.StatementDescription, } result := c.pgConn.ExecParams(ctx, sd.SQL, c.eqb.paramValues, sd.ParamOIDs, c.eqb.paramFormats, c.eqb.resultFormats).Read() + c.eqb.Reset() // Allow c.eqb internal memory to be GC'ed as soon as possible. return result.CommandTag, result.Err } @@ -527,16 +508,12 @@ func (c *Conn) execPrepared(ctx context.Context, sd *pgconn.StatementDescription } result := c.pgConn.ExecPrepared(ctx, sd.Name, c.eqb.paramValues, c.eqb.paramFormats, c.eqb.resultFormats).Read() + c.eqb.Reset() // Allow c.eqb internal memory to be GC'ed as soon as possible. return result.CommandTag, result.Err } func (c *Conn) getRows(ctx context.Context, sql string, args []interface{}) *connRows { - if len(c.preallocatedRows) == 0 { - c.preallocatedRows = make([]connRows, 64) - } - - r := &c.preallocatedRows[len(c.preallocatedRows)-1] - c.preallocatedRows = c.preallocatedRows[0 : len(c.preallocatedRows)-1] + r := &connRows{} r.ctx = ctx r.logger = c @@ -558,8 +535,16 @@ type QueryResultFormats []int16 // QueryResultFormatsByOID controls the result format (text=0, binary=1) of a query by the result column OID. type QueryResultFormatsByOID map[uint32]int16 -// Query executes sql with args. If there is an error the returned Rows will be returned in an error state. So it is -// allowed to ignore the error returned from Query and handle it in Rows. +// Query sends a query to the server and returns a Rows to read the results. Only errors encountered sending the query +// and initializing Rows will be returned. Err() on the returned Rows must be checked after the Rows is closed to +// determine if the query executed successfully. +// +// The returned Rows must be closed before the connection can be used again. It is safe to attempt to read from the +// returned Rows even if an error is returned. The error will be the available in rows.Err() after rows are closed. It +// is allowed to ignore the error returned from Query and handle it in Rows. +// +// Err() on the returned Rows must be checked after the Rows is closed to determine if the query executed successfully +// as some errors can only be detected by reading the entire response. e.g. A divide by zero error on the last row. // // For extra control over how the query is executed, the types QuerySimpleProtocol, QueryResultFormats, and // QueryResultFormatsByOID may be used as the first args to control exactly how the query is executed. This is rarely @@ -664,12 +649,14 @@ optionLoop: resultFormats = c.eqb.resultFormats } - if c.stmtcache != nil && c.stmtcache.Mode() == stmtcache.ModeDescribe { + if c.stmtcache != nil && c.stmtcache.Mode() == stmtcache.ModeDescribe && !ok { rows.resultReader = c.pgConn.ExecParams(ctx, sql, c.eqb.paramValues, sd.ParamOIDs, c.eqb.paramFormats, resultFormats) } else { rows.resultReader = c.pgConn.ExecPrepared(ctx, sd.Name, c.eqb.paramValues, c.eqb.paramFormats, resultFormats) } + c.eqb.Reset() // Allow c.eqb internal memory to be GC'ed as soon as possible. + return rows, rows.err } @@ -727,6 +714,8 @@ func (c *Conn) QueryFunc(ctx context.Context, sql string, args []interface{}, sc // explicit transaction control statements are executed. The returned BatchResults must be closed before the connection // is used again. func (c *Conn) SendBatch(ctx context.Context, b *Batch) BatchResults { + startTime := time.Now() + simpleProtocol := c.config.PreferSimpleProtocol var sb strings.Builder if simpleProtocol { @@ -785,24 +774,23 @@ func (c *Conn) SendBatch(ctx context.Context, b *Batch) BatchResults { var err error sd, err = stmtCache.Get(ctx, bi.query) if err != nil { - // the stmtCache was prefilled from distinctUnpreparedQueries above so we are guaranteed no errors - panic("BUG: unexpected error from stmtCache") + return c.logBatchResults(ctx, startTime, &batchResults{ctx: ctx, conn: c, err: err}) } } if len(sd.ParamOIDs) != len(bi.arguments) { - return &batchResults{ctx: ctx, conn: c, err: fmt.Errorf("mismatched param and argument count")} + return c.logBatchResults(ctx, startTime, &batchResults{ctx: ctx, conn: c, err: fmt.Errorf("mismatched param and argument count")}) } args, err := convertDriverValuers(bi.arguments) if err != nil { - return &batchResults{ctx: ctx, conn: c, err: err} + return c.logBatchResults(ctx, startTime, &batchResults{ctx: ctx, conn: c, err: err}) } for i := range args { err = c.eqb.AppendParam(c.connInfo, sd.ParamOIDs[i], args[i]) if err != nil { - return &batchResults{ctx: ctx, conn: c, err: err} + return c.logBatchResults(ctx, startTime, &batchResults{ctx: ctx, conn: c, err: err}) } } @@ -817,15 +805,34 @@ func (c *Conn) SendBatch(ctx context.Context, b *Batch) BatchResults { } } + c.eqb.Reset() // Allow c.eqb internal memory to be GC'ed as soon as possible. + mrr := c.pgConn.ExecBatch(ctx, batch) - return &batchResults{ + return c.logBatchResults(ctx, startTime, &batchResults{ ctx: ctx, conn: c, mrr: mrr, b: b, ix: 0, + }) +} + +func (c *Conn) logBatchResults(ctx context.Context, startTime time.Time, results *batchResults) BatchResults { + if results.err != nil { + if c.shouldLog(LogLevelError) { + endTime := time.Now() + c.log(ctx, LogLevelError, "SendBatch", map[string]interface{}{"err": results.err, "time": endTime.Sub(startTime)}) + } + return results + } + + if c.shouldLog(LogLevelInfo) { + endTime := time.Now() + c.log(ctx, LogLevelInfo, "SendBatch", map[string]interface{}{"batchLen": results.b.Len(), "time": endTime.Sub(startTime)}) } + + return results } func (c *Conn) sanitizeForSimpleQuery(sql string, args ...interface{}) (string, error) { diff --git a/src/vendor/github.com/jackc/pgx/v4/copy_from.go b/src/vendor/github.com/jackc/pgx/v4/copy_from.go index 3494e28f907..49139d050f7 100644 --- a/src/vendor/github.com/jackc/pgx/v4/copy_from.go +++ b/src/vendor/github.com/jackc/pgx/v4/copy_from.go @@ -153,13 +153,13 @@ func (ct *copyFrom) run(ctx context.Context) (int64, error) { <-doneChan rowsAffected := commandTag.RowsAffected() + endTime := time.Now() if err == nil { if ct.conn.shouldLog(LogLevelInfo) { - endTime := time.Now() ct.conn.log(ctx, LogLevelInfo, "CopyFrom", map[string]interface{}{"tableName": ct.tableName, "columnNames": ct.columnNames, "time": endTime.Sub(startTime), "rowCount": rowsAffected}) } } else if ct.conn.shouldLog(LogLevelError) { - ct.conn.log(ctx, LogLevelError, "CopyFrom", map[string]interface{}{"err": err, "tableName": ct.tableName, "columnNames": ct.columnNames}) + ct.conn.log(ctx, LogLevelError, "CopyFrom", map[string]interface{}{"err": err, "tableName": ct.tableName, "columnNames": ct.columnNames, "time": endTime.Sub(startTime)}) } return rowsAffected, err diff --git a/src/vendor/github.com/jackc/pgx/v4/doc.go b/src/vendor/github.com/jackc/pgx/v4/doc.go index 51b0d9f4466..222f90479cf 100644 --- a/src/vendor/github.com/jackc/pgx/v4/doc.go +++ b/src/vendor/github.com/jackc/pgx/v4/doc.go @@ -309,7 +309,7 @@ CopyFrom can be faster than an insert with as few as 5 rows. Listen and Notify pgx can listen to the PostgreSQL notification system with the `Conn.WaitForNotification` method. It blocks until a -context is received or the context is canceled. +notification is received or the context is canceled. _, err := conn.Exec(context.Background(), "listen channelname") if err != nil { diff --git a/src/vendor/github.com/jackc/pgx/v4/extended_query_builder.go b/src/vendor/github.com/jackc/pgx/v4/extended_query_builder.go index 09419f0d044..d06f63fd121 100644 --- a/src/vendor/github.com/jackc/pgx/v4/extended_query_builder.go +++ b/src/vendor/github.com/jackc/pgx/v4/extended_query_builder.go @@ -13,8 +13,6 @@ type extendedQueryBuilder struct { paramValueBytes []byte paramFormats []int16 resultFormats []int16 - - resetCount int } func (eqb *extendedQueryBuilder) AppendParam(ci *pgtype.ConnInfo, oid uint32, arg interface{}) error { @@ -34,32 +32,27 @@ func (eqb *extendedQueryBuilder) AppendResultFormat(f int16) { eqb.resultFormats = append(eqb.resultFormats, f) } +// Reset readies eqb to build another query. func (eqb *extendedQueryBuilder) Reset() { eqb.paramValues = eqb.paramValues[0:0] eqb.paramValueBytes = eqb.paramValueBytes[0:0] eqb.paramFormats = eqb.paramFormats[0:0] eqb.resultFormats = eqb.resultFormats[0:0] - eqb.resetCount++ - - // Every so often shrink our reserved memory if it is abnormally high - if eqb.resetCount%128 == 0 { - if cap(eqb.paramValues) > 64 { - eqb.paramValues = make([][]byte, 0, cap(eqb.paramValues)/2) - } - - if cap(eqb.paramValueBytes) > 256 { - eqb.paramValueBytes = make([]byte, 0, cap(eqb.paramValueBytes)/2) - } + if cap(eqb.paramValues) > 64 { + eqb.paramValues = make([][]byte, 0, 64) + } - if cap(eqb.paramFormats) > 64 { - eqb.paramFormats = make([]int16, 0, cap(eqb.paramFormats)/2) - } - if cap(eqb.resultFormats) > 64 { - eqb.resultFormats = make([]int16, 0, cap(eqb.resultFormats)/2) - } + if cap(eqb.paramValueBytes) > 256 { + eqb.paramValueBytes = make([]byte, 0, 256) } + if cap(eqb.paramFormats) > 64 { + eqb.paramFormats = make([]int16, 0, 64) + } + if cap(eqb.resultFormats) > 64 { + eqb.resultFormats = make([]int16, 0, 64) + } } func (eqb *extendedQueryBuilder) encodeExtendedParamValue(ci *pgtype.ConnInfo, oid uint32, formatCode int16, arg interface{}) ([]byte, error) { diff --git a/src/vendor/github.com/jackc/pgx/v4/internal/sanitize/sanitize.go b/src/vendor/github.com/jackc/pgx/v4/internal/sanitize/sanitize.go index 2dba3b810a9..5eef456c382 100644 --- a/src/vendor/github.com/jackc/pgx/v4/internal/sanitize/sanitize.go +++ b/src/vendor/github.com/jackc/pgx/v4/internal/sanitize/sanitize.go @@ -18,6 +18,12 @@ type Query struct { Parts []Part } +// utf.DecodeRune returns the utf8.RuneError for errors. But that is actually rune U+FFFD -- the unicode replacement +// character. utf8.RuneError is not an error if it is also width 3. +// +// https://github.com/jackc/pgx/issues/1380 +const replacementcharacterwidth = 3 + func (q *Query) Sanitize(args ...interface{}) (string, error) { argUse := make([]bool, len(args)) buf := &bytes.Buffer{} @@ -138,11 +144,13 @@ func rawState(l *sqlLexer) stateFn { return multilineCommentState } case utf8.RuneError: - if l.pos-l.start > 0 { - l.parts = append(l.parts, l.src[l.start:l.pos]) - l.start = l.pos + if width != replacementcharacterwidth { + if l.pos-l.start > 0 { + l.parts = append(l.parts, l.src[l.start:l.pos]) + l.start = l.pos + } + return nil } - return nil } } } @@ -160,11 +168,13 @@ func singleQuoteState(l *sqlLexer) stateFn { } l.pos += width case utf8.RuneError: - if l.pos-l.start > 0 { - l.parts = append(l.parts, l.src[l.start:l.pos]) - l.start = l.pos + if width != replacementcharacterwidth { + if l.pos-l.start > 0 { + l.parts = append(l.parts, l.src[l.start:l.pos]) + l.start = l.pos + } + return nil } - return nil } } } @@ -182,11 +192,13 @@ func doubleQuoteState(l *sqlLexer) stateFn { } l.pos += width case utf8.RuneError: - if l.pos-l.start > 0 { - l.parts = append(l.parts, l.src[l.start:l.pos]) - l.start = l.pos + if width != replacementcharacterwidth { + if l.pos-l.start > 0 { + l.parts = append(l.parts, l.src[l.start:l.pos]) + l.start = l.pos + } + return nil } - return nil } } } @@ -228,11 +240,13 @@ func escapeStringState(l *sqlLexer) stateFn { } l.pos += width case utf8.RuneError: - if l.pos-l.start > 0 { - l.parts = append(l.parts, l.src[l.start:l.pos]) - l.start = l.pos + if width != replacementcharacterwidth { + if l.pos-l.start > 0 { + l.parts = append(l.parts, l.src[l.start:l.pos]) + l.start = l.pos + } + return nil } - return nil } } } @@ -246,14 +260,16 @@ func oneLineCommentState(l *sqlLexer) stateFn { case '\\': _, width = utf8.DecodeRuneInString(l.src[l.pos:]) l.pos += width - case '\n': + case '\n', '\r': return rawState case utf8.RuneError: - if l.pos-l.start > 0 { - l.parts = append(l.parts, l.src[l.start:l.pos]) - l.start = l.pos + if width != replacementcharacterwidth { + if l.pos-l.start > 0 { + l.parts = append(l.parts, l.src[l.start:l.pos]) + l.start = l.pos + } + return nil } - return nil } } } @@ -283,11 +299,13 @@ func multilineCommentState(l *sqlLexer) stateFn { l.nested-- case utf8.RuneError: - if l.pos-l.start > 0 { - l.parts = append(l.parts, l.src[l.start:l.pos]) - l.start = l.pos + if width != replacementcharacterwidth { + if l.pos-l.start > 0 { + l.parts = append(l.parts, l.src[l.start:l.pos]) + l.start = l.pos + } + return nil } - return nil } } } diff --git a/src/vendor/github.com/jackc/pgx/v4/large_objects.go b/src/vendor/github.com/jackc/pgx/v4/large_objects.go index 5255a3b48c0..c238ab9c220 100644 --- a/src/vendor/github.com/jackc/pgx/v4/large_objects.go +++ b/src/vendor/github.com/jackc/pgx/v4/large_objects.go @@ -56,10 +56,10 @@ func (o *LargeObjects) Unlink(ctx context.Context, oid uint32) error { // A LargeObject is a large object stored on the server. It is only valid within the transaction that it was initialized // in. It uses the context it was initialized with for all operations. It implements these interfaces: // -// io.Writer -// io.Reader -// io.Seeker -// io.Closer +// io.Writer +// io.Reader +// io.Seeker +// io.Closer type LargeObject struct { ctx context.Context tx Tx @@ -108,13 +108,13 @@ func (o *LargeObject) Tell() (n int64, err error) { return n, err } -// Trunctes the large object to size. +// Truncate the large object to size. func (o *LargeObject) Truncate(size int64) (err error) { _, err = o.tx.Exec(o.ctx, "select lo_truncate64($1, $2)", o.fd, size) return err } -// Close closees the large object descriptor. +// Close the large object descriptor. func (o *LargeObject) Close() error { _, err := o.tx.Exec(o.ctx, "select lo_close($1)", o.fd) return err diff --git a/src/vendor/github.com/jackc/pgx/v4/logger.go b/src/vendor/github.com/jackc/pgx/v4/logger.go index 89fd5af51bb..41f8b7e87bd 100644 --- a/src/vendor/github.com/jackc/pgx/v4/logger.go +++ b/src/vendor/github.com/jackc/pgx/v4/logger.go @@ -47,9 +47,18 @@ type Logger interface { Log(ctx context.Context, level LogLevel, msg string, data map[string]interface{}) } +// LoggerFunc is a wrapper around a function to satisfy the pgx.Logger interface +type LoggerFunc func(ctx context.Context, level LogLevel, msg string, data map[string]interface{}) + +// Log delegates the logging request to the wrapped function +func (f LoggerFunc) Log(ctx context.Context, level LogLevel, msg string, data map[string]interface{}) { + f(ctx, level, msg, data) +} + // LogLevelFromString converts log level string to constant // // Valid levels: +// // trace // debug // info diff --git a/src/vendor/github.com/jackc/pgx/v4/rows.go b/src/vendor/github.com/jackc/pgx/v4/rows.go index d57d5cbf640..4749ead99b1 100644 --- a/src/vendor/github.com/jackc/pgx/v4/rows.go +++ b/src/vendor/github.com/jackc/pgx/v4/rows.go @@ -41,10 +41,13 @@ type Rows interface { // Scan reads the values from the current row into dest values positionally. // dest can include pointers to core types, values implementing the Scanner - // interface, and nil. nil will skip the value entirely. + // interface, and nil. nil will skip the value entirely. It is an error to + // call Scan without first calling Next() and checking that it returned true. Scan(dest ...interface{}) error - // Values returns the decoded row values. + // Values returns the decoded row values. As with Scan(), it is an error to + // call Values without first calling Next() and checking that it returned + // true. Values() ([]interface{}, error) // RawValues returns the unparsed bytes of the row values. The returned [][]byte is only valid until the next Next @@ -140,14 +143,15 @@ func (rows *connRows) Close() { } if rows.logger != nil { + endTime := time.Now() + if rows.err == nil { if rows.logger.shouldLog(LogLevelInfo) { - endTime := time.Now() rows.logger.log(rows.ctx, LogLevelInfo, "Query", map[string]interface{}{"sql": rows.sql, "args": logQueryArgs(rows.args), "time": endTime.Sub(rows.startTime), "rowCount": rows.rowCount}) } } else { if rows.logger.shouldLog(LogLevelError) { - rows.logger.log(rows.ctx, LogLevelError, "Query", map[string]interface{}{"err": rows.err, "sql": rows.sql, "args": logQueryArgs(rows.args)}) + rows.logger.log(rows.ctx, LogLevelError, "Query", map[string]interface{}{"err": rows.err, "sql": rows.sql, "time": endTime.Sub(rows.startTime), "args": logQueryArgs(rows.args)}) } if rows.err != nil && rows.conn.stmtcache != nil { rows.conn.stmtcache.StatementErrored(rows.sql, rows.err) diff --git a/src/vendor/github.com/jackc/pgx/v4/stdlib/sql.go b/src/vendor/github.com/jackc/pgx/v4/stdlib/sql.go index fa81e73d554..f43ae3249d9 100644 --- a/src/vendor/github.com/jackc/pgx/v4/stdlib/sql.go +++ b/src/vendor/github.com/jackc/pgx/v4/stdlib/sql.go @@ -84,7 +84,13 @@ func init() { configs: make(map[string]*pgx.ConnConfig), } fakeTxConns = make(map[*pgx.Conn]*sql.Tx) - sql.Register("pgx", pgxDriver) + + // if pgx driver was already registered by different pgx major version then we + // skip registration under the default name. + if !contains(sql.Drivers(), "pgx") { + sql.Register("pgx", pgxDriver) + } + sql.Register("pgx/v4", pgxDriver) databaseSQLResultFormats = pgx.QueryResultFormatsByOID{ pgtype.BoolOID: 1, @@ -103,6 +109,17 @@ func init() { } } +// TODO replace by slices.Contains when experimental package will be merged to stdlib +// https://pkg.go.dev/golang.org/x/exp/slices#Contains +func contains(list []string, y string) bool { + for _, x := range list { + if x == y { + return true + } + } + return false +} + var ( fakeTxMutex sync.Mutex fakeTxConns map[*pgx.Conn]*sql.Tx @@ -163,7 +180,7 @@ func RandomizeHostOrderFunc(ctx context.Context, connConfig *pgx.ConnConfig) err return nil } -func OpenDB(config pgx.ConnConfig, opts ...OptionOpenDB) *sql.DB { +func GetConnector(config pgx.ConnConfig, opts ...OptionOpenDB) driver.Connector { c := connector{ ConnConfig: config, BeforeConnect: func(context.Context, *pgx.ConnConfig) error { return nil }, // noop before connect by default @@ -175,7 +192,11 @@ func OpenDB(config pgx.ConnConfig, opts ...OptionOpenDB) *sql.DB { for _, opt := range opts { opt(&c) } + return c +} +func OpenDB(config pgx.ConnConfig, opts ...OptionOpenDB) *sql.DB { + c := GetConnector(config, opts...) return sql.OpenDB(c) } diff --git a/src/vendor/github.com/jackc/pgx/v4/tx.go b/src/vendor/github.com/jackc/pgx/v4/tx.go index 7a296f4fe45..2914ada7dab 100644 --- a/src/vendor/github.com/jackc/pgx/v4/tx.go +++ b/src/vendor/github.com/jackc/pgx/v4/tx.go @@ -10,32 +10,36 @@ import ( "github.com/jackc/pgconn" ) +// TxIsoLevel is the transaction isolation level (serializable, repeatable read, read committed or read uncommitted) type TxIsoLevel string // Transaction isolation levels const ( - Serializable = TxIsoLevel("serializable") - RepeatableRead = TxIsoLevel("repeatable read") - ReadCommitted = TxIsoLevel("read committed") - ReadUncommitted = TxIsoLevel("read uncommitted") + Serializable TxIsoLevel = "serializable" + RepeatableRead TxIsoLevel = "repeatable read" + ReadCommitted TxIsoLevel = "read committed" + ReadUncommitted TxIsoLevel = "read uncommitted" ) +// TxAccessMode is the transaction access mode (read write or read only) type TxAccessMode string // Transaction access modes const ( - ReadWrite = TxAccessMode("read write") - ReadOnly = TxAccessMode("read only") + ReadWrite TxAccessMode = "read write" + ReadOnly TxAccessMode = "read only" ) +// TxDeferrableMode is the transaction deferrable mode (deferrable or not deferrable) type TxDeferrableMode string // Transaction deferrable modes const ( - Deferrable = TxDeferrableMode("deferrable") - NotDeferrable = TxDeferrableMode("not deferrable") + Deferrable TxDeferrableMode = "deferrable" + NotDeferrable TxDeferrableMode = "not deferrable" ) +// TxOptions are transaction modes within a transaction block type TxOptions struct { IsoLevel TxIsoLevel AccessMode TxAccessMode @@ -109,7 +113,7 @@ func (c *Conn) BeginTxFunc(ctx context.Context, txOptions TxOptions, f func(Tx) } defer func() { rollbackErr := tx.Rollback(ctx) - if !(rollbackErr == nil || errors.Is(rollbackErr, ErrTxClosed)) { + if rollbackErr != nil && !errors.Is(rollbackErr, ErrTxClosed) { err = rollbackErr } }() @@ -188,7 +192,7 @@ func (tx *dbTx) Begin(ctx context.Context) (Tx, error) { return nil, err } - return &dbSavepoint{tx: tx, savepointNum: tx.savepointNum}, nil + return &dbSimulatedNestedTx{tx: tx, savepointNum: tx.savepointNum}, nil } func (tx *dbTx) BeginFunc(ctx context.Context, f func(Tx) error) (err error) { @@ -203,7 +207,7 @@ func (tx *dbTx) BeginFunc(ctx context.Context, f func(Tx) error) (err error) { } defer func() { rollbackErr := savepoint.Rollback(ctx) - if !(rollbackErr == nil || errors.Is(rollbackErr, ErrTxClosed)) { + if rollbackErr != nil && !errors.Is(rollbackErr, ErrTxClosed) { err = rollbackErr } }() @@ -325,15 +329,15 @@ func (tx *dbTx) Conn() *Conn { return tx.conn } -// dbSavepoint represents a nested transaction implemented by a savepoint. -type dbSavepoint struct { +// dbSimulatedNestedTx represents a simulated nested transaction implemented by a savepoint. +type dbSimulatedNestedTx struct { tx Tx savepointNum int64 closed bool } // Begin starts a pseudo nested transaction implemented with a savepoint. -func (sp *dbSavepoint) Begin(ctx context.Context) (Tx, error) { +func (sp *dbSimulatedNestedTx) Begin(ctx context.Context) (Tx, error) { if sp.closed { return nil, ErrTxClosed } @@ -341,7 +345,7 @@ func (sp *dbSavepoint) Begin(ctx context.Context) (Tx, error) { return sp.tx.Begin(ctx) } -func (sp *dbSavepoint) BeginFunc(ctx context.Context, f func(Tx) error) (err error) { +func (sp *dbSimulatedNestedTx) BeginFunc(ctx context.Context, f func(Tx) error) (err error) { if sp.closed { return ErrTxClosed } @@ -350,7 +354,7 @@ func (sp *dbSavepoint) BeginFunc(ctx context.Context, f func(Tx) error) (err err } // Commit releases the savepoint essentially committing the pseudo nested transaction. -func (sp *dbSavepoint) Commit(ctx context.Context) error { +func (sp *dbSimulatedNestedTx) Commit(ctx context.Context) error { if sp.closed { return ErrTxClosed } @@ -363,7 +367,7 @@ func (sp *dbSavepoint) Commit(ctx context.Context) error { // Rollback rolls back to the savepoint essentially rolling back the pseudo nested transaction. Rollback will return // ErrTxClosed if the dbSavepoint is already closed, but is otherwise safe to call multiple times. Hence, a defer sp.Rollback() // is safe even if sp.Commit() will be called first in a non-error condition. -func (sp *dbSavepoint) Rollback(ctx context.Context) error { +func (sp *dbSimulatedNestedTx) Rollback(ctx context.Context) error { if sp.closed { return ErrTxClosed } @@ -374,7 +378,7 @@ func (sp *dbSavepoint) Rollback(ctx context.Context) error { } // Exec delegates to the underlying Tx -func (sp *dbSavepoint) Exec(ctx context.Context, sql string, arguments ...interface{}) (commandTag pgconn.CommandTag, err error) { +func (sp *dbSimulatedNestedTx) Exec(ctx context.Context, sql string, arguments ...interface{}) (commandTag pgconn.CommandTag, err error) { if sp.closed { return nil, ErrTxClosed } @@ -383,7 +387,7 @@ func (sp *dbSavepoint) Exec(ctx context.Context, sql string, arguments ...interf } // Prepare delegates to the underlying Tx -func (sp *dbSavepoint) Prepare(ctx context.Context, name, sql string) (*pgconn.StatementDescription, error) { +func (sp *dbSimulatedNestedTx) Prepare(ctx context.Context, name, sql string) (*pgconn.StatementDescription, error) { if sp.closed { return nil, ErrTxClosed } @@ -392,7 +396,7 @@ func (sp *dbSavepoint) Prepare(ctx context.Context, name, sql string) (*pgconn.S } // Query delegates to the underlying Tx -func (sp *dbSavepoint) Query(ctx context.Context, sql string, args ...interface{}) (Rows, error) { +func (sp *dbSimulatedNestedTx) Query(ctx context.Context, sql string, args ...interface{}) (Rows, error) { if sp.closed { // Because checking for errors can be deferred to the *Rows, build one with the error err := ErrTxClosed @@ -403,13 +407,13 @@ func (sp *dbSavepoint) Query(ctx context.Context, sql string, args ...interface{ } // QueryRow delegates to the underlying Tx -func (sp *dbSavepoint) QueryRow(ctx context.Context, sql string, args ...interface{}) Row { +func (sp *dbSimulatedNestedTx) QueryRow(ctx context.Context, sql string, args ...interface{}) Row { rows, _ := sp.Query(ctx, sql, args...) return (*connRow)(rows.(*connRows)) } // QueryFunc delegates to the underlying Tx. -func (sp *dbSavepoint) QueryFunc(ctx context.Context, sql string, args []interface{}, scans []interface{}, f func(QueryFuncRow) error) (pgconn.CommandTag, error) { +func (sp *dbSimulatedNestedTx) QueryFunc(ctx context.Context, sql string, args []interface{}, scans []interface{}, f func(QueryFuncRow) error) (pgconn.CommandTag, error) { if sp.closed { return nil, ErrTxClosed } @@ -418,7 +422,7 @@ func (sp *dbSavepoint) QueryFunc(ctx context.Context, sql string, args []interfa } // CopyFrom delegates to the underlying *Conn -func (sp *dbSavepoint) CopyFrom(ctx context.Context, tableName Identifier, columnNames []string, rowSrc CopyFromSource) (int64, error) { +func (sp *dbSimulatedNestedTx) CopyFrom(ctx context.Context, tableName Identifier, columnNames []string, rowSrc CopyFromSource) (int64, error) { if sp.closed { return 0, ErrTxClosed } @@ -427,7 +431,7 @@ func (sp *dbSavepoint) CopyFrom(ctx context.Context, tableName Identifier, colum } // SendBatch delegates to the underlying *Conn -func (sp *dbSavepoint) SendBatch(ctx context.Context, b *Batch) BatchResults { +func (sp *dbSimulatedNestedTx) SendBatch(ctx context.Context, b *Batch) BatchResults { if sp.closed { return &batchResults{err: ErrTxClosed} } @@ -435,10 +439,10 @@ func (sp *dbSavepoint) SendBatch(ctx context.Context, b *Batch) BatchResults { return sp.tx.SendBatch(ctx, b) } -func (sp *dbSavepoint) LargeObjects() LargeObjects { +func (sp *dbSimulatedNestedTx) LargeObjects() LargeObjects { return LargeObjects{tx: sp} } -func (sp *dbSavepoint) Conn() *Conn { +func (sp *dbSimulatedNestedTx) Conn() *Conn { return sp.tx.Conn() } diff --git a/src/vendor/github.com/jackc/pgx/v4/values.go b/src/vendor/github.com/jackc/pgx/v4/values.go index 45d8ff8399b..1a94547537e 100644 --- a/src/vendor/github.com/jackc/pgx/v4/values.go +++ b/src/vendor/github.com/jackc/pgx/v4/values.go @@ -78,6 +78,8 @@ func convertSimpleArgument(ci *pgtype.ConnInfo, arg interface{}) (interface{}, e return arg, nil case bool: return arg, nil + case time.Duration: + return fmt.Sprintf("%d microsecond", int64(arg)/1000), nil case time.Time: return arg, nil case string: diff --git a/src/vendor/github.com/sirupsen/logrus/README.md b/src/vendor/github.com/sirupsen/logrus/README.md index b042c896f25..d1d4a85fd75 100644 --- a/src/vendor/github.com/sirupsen/logrus/README.md +++ b/src/vendor/github.com/sirupsen/logrus/README.md @@ -9,7 +9,7 @@ the last thing you want from your Logging library (again...). This does not mean Logrus is dead. Logrus will continue to be maintained for security, (backwards compatible) bug fixes, and performance (where we are -limited by the interface). +limited by the interface). I believe Logrus' biggest contribution is to have played a part in today's widespread use of structured logging in Golang. There doesn't seem to be a @@ -43,7 +43,7 @@ plain text): With `log.SetFormatter(&log.JSONFormatter{})`, for easy parsing by logstash or Splunk: -```json +```text {"animal":"walrus","level":"info","msg":"A group of walrus emerges from the ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"} @@ -99,7 +99,7 @@ time="2015-03-26T01:27:38-04:00" level=fatal method=github.com/sirupsen/arcticcr ``` Note that this does add measurable overhead - the cost will depend on the version of Go, but is between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your -environment via benchmarks: +environment via benchmarks: ``` go test -bench=.*CallerTracing ``` @@ -317,6 +317,8 @@ log.SetLevel(log.InfoLevel) It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose environment if your application has that. +Note: If you want different log levels for global (`log.SetLevel(...)`) and syslog logging, please check the [syslog hook README](hooks/syslog/README.md#different-log-levels-for-local-and-remote-logging). + #### Entries Besides the fields added with `WithField` or `WithFields` some fields are diff --git a/src/vendor/golang.org/x/crypto/acme/rfc8555.go b/src/vendor/golang.org/x/crypto/acme/rfc8555.go index ee24dfdec69..3152e531b65 100644 --- a/src/vendor/golang.org/x/crypto/acme/rfc8555.go +++ b/src/vendor/golang.org/x/crypto/acme/rfc8555.go @@ -117,7 +117,7 @@ func (c *Client) updateRegRFC(ctx context.Context, a *Account) (*Account, error) return responseAccount(res) } -// getGegRFC is equivalent to c.GetReg but for CAs implementing RFC 8555. +// getRegRFC is equivalent to c.GetReg but for CAs implementing RFC 8555. // It expects c.Discover to have already been called. func (c *Client) getRegRFC(ctx context.Context) (*Account, error) { req := json.RawMessage(`{"onlyReturnExisting": true}`) diff --git a/src/vendor/golang.org/x/net/http2/transport.go b/src/vendor/golang.org/x/net/http2/transport.go index f965579f7d5..ac90a2631c9 100644 --- a/src/vendor/golang.org/x/net/http2/transport.go +++ b/src/vendor/golang.org/x/net/http2/transport.go @@ -1266,6 +1266,27 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) { return res, nil } + cancelRequest := func(cs *clientStream, err error) error { + cs.cc.mu.Lock() + defer cs.cc.mu.Unlock() + cs.abortStreamLocked(err) + if cs.ID != 0 { + // This request may have failed because of a problem with the connection, + // or for some unrelated reason. (For example, the user might have canceled + // the request without waiting for a response.) Mark the connection as + // not reusable, since trying to reuse a dead connection is worse than + // unnecessarily creating a new one. + // + // If cs.ID is 0, then the request was never allocated a stream ID and + // whatever went wrong was unrelated to the connection. We might have + // timed out waiting for a stream slot when StrictMaxConcurrentStreams + // is set, for example, in which case retrying on a different connection + // will not help. + cs.cc.doNotReuse = true + } + return err + } + for { select { case <-cs.respHeaderRecv: @@ -1280,15 +1301,12 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) { return handleResponseHeaders() default: waitDone() - return nil, cs.abortErr + return nil, cancelRequest(cs, cs.abortErr) } case <-ctx.Done(): - err := ctx.Err() - cs.abortStream(err) - return nil, err + return nil, cancelRequest(cs, ctx.Err()) case <-cs.reqCancel: - cs.abortStream(errRequestCanceled) - return nil, errRequestCanceled + return nil, cancelRequest(cs, errRequestCanceled) } } } diff --git a/src/vendor/golang.org/x/sys/unix/mkerrors.sh b/src/vendor/golang.org/x/sys/unix/mkerrors.sh index 2045d3dadb8..be0423e6856 100644 --- a/src/vendor/golang.org/x/sys/unix/mkerrors.sh +++ b/src/vendor/golang.org/x/sys/unix/mkerrors.sh @@ -204,6 +204,7 @@ struct ltchars { #include #include #include +#include #include #include #include @@ -518,7 +519,7 @@ ccflags="$@" $2 ~ /^LOCK_(SH|EX|NB|UN)$/ || $2 ~ /^LO_(KEY|NAME)_SIZE$/ || $2 ~ /^LOOP_(CLR|CTL|GET|SET)_/ || - $2 ~ /^(AF|SOCK|SO|SOL|IPPROTO|IP|IPV6|TCP|MCAST|EVFILT|NOTE|SHUT|PROT|MAP|MFD|T?PACKET|MSG|SCM|MCL|DT|MADV|PR|LOCAL|TCPOPT)_/ || + $2 ~ /^(AF|SOCK|SO|SOL|IPPROTO|IP|IPV6|TCP|MCAST|EVFILT|NOTE|SHUT|PROT|MAP|MFD|T?PACKET|MSG|SCM|MCL|DT|MADV|PR|LOCAL|TCPOPT|UDP)_/ || $2 ~ /^NFC_(GENL|PROTO|COMM|RF|SE|DIRECTION|LLCP|SOCKPROTO)_/ || $2 ~ /^NFC_.*_(MAX)?SIZE$/ || $2 ~ /^RAW_PAYLOAD_/ || diff --git a/src/vendor/golang.org/x/sys/unix/zerrors_linux.go b/src/vendor/golang.org/x/sys/unix/zerrors_linux.go index 398c37e52d6..de936b677b6 100644 --- a/src/vendor/golang.org/x/sys/unix/zerrors_linux.go +++ b/src/vendor/golang.org/x/sys/unix/zerrors_linux.go @@ -2967,6 +2967,7 @@ const ( SOL_TCP = 0x6 SOL_TIPC = 0x10f SOL_TLS = 0x11a + SOL_UDP = 0x11 SOL_X25 = 0x106 SOL_XDP = 0x11b SOMAXCONN = 0x1000 @@ -3251,6 +3252,19 @@ const ( TRACEFS_MAGIC = 0x74726163 TS_COMM_LEN = 0x20 UDF_SUPER_MAGIC = 0x15013346 + UDP_CORK = 0x1 + UDP_ENCAP = 0x64 + UDP_ENCAP_ESPINUDP = 0x2 + UDP_ENCAP_ESPINUDP_NON_IKE = 0x1 + UDP_ENCAP_GTP0 = 0x4 + UDP_ENCAP_GTP1U = 0x5 + UDP_ENCAP_L2TPINUDP = 0x3 + UDP_GRO = 0x68 + UDP_NO_CHECK6_RX = 0x66 + UDP_NO_CHECK6_TX = 0x65 + UDP_SEGMENT = 0x67 + UDP_V4_FLOW = 0x2 + UDP_V6_FLOW = 0x6 UMOUNT_NOFOLLOW = 0x8 USBDEVICE_SUPER_MAGIC = 0x9fa2 UTIME_NOW = 0x3fffffff diff --git a/src/vendor/golang.org/x/sys/windows/env_windows.go b/src/vendor/golang.org/x/sys/windows/env_windows.go index 92ac05ff4ea..b8ad1925068 100644 --- a/src/vendor/golang.org/x/sys/windows/env_windows.go +++ b/src/vendor/golang.org/x/sys/windows/env_windows.go @@ -37,14 +37,14 @@ func (token Token) Environ(inheritExisting bool) (env []string, err error) { return nil, err } defer DestroyEnvironmentBlock(block) - blockp := uintptr(unsafe.Pointer(block)) + blockp := unsafe.Pointer(block) for { - entry := UTF16PtrToString((*uint16)(unsafe.Pointer(blockp))) + entry := UTF16PtrToString((*uint16)(blockp)) if len(entry) == 0 { break } env = append(env, entry) - blockp += 2 * (uintptr(len(entry)) + 1) + blockp = unsafe.Add(blockp, 2*(len(entry)+1)) } return env, nil } diff --git a/src/vendor/golang.org/x/sys/windows/exec_windows.go b/src/vendor/golang.org/x/sys/windows/exec_windows.go index 75980fd44ad..a52e0331d8b 100644 --- a/src/vendor/golang.org/x/sys/windows/exec_windows.go +++ b/src/vendor/golang.org/x/sys/windows/exec_windows.go @@ -95,12 +95,17 @@ func ComposeCommandLine(args []string) string { // DecomposeCommandLine breaks apart its argument command line into unescaped parts using CommandLineToArgv, // as gathered from GetCommandLine, QUERY_SERVICE_CONFIG's BinaryPathName argument, or elsewhere that // command lines are passed around. +// DecomposeCommandLine returns error if commandLine contains NUL. func DecomposeCommandLine(commandLine string) ([]string, error) { if len(commandLine) == 0 { return []string{}, nil } + utf16CommandLine, err := UTF16FromString(commandLine) + if err != nil { + return nil, errorspkg.New("string with NUL passed to DecomposeCommandLine") + } var argc int32 - argv, err := CommandLineToArgv(StringToUTF16Ptr(commandLine), &argc) + argv, err := CommandLineToArgv(&utf16CommandLine[0], &argc) if err != nil { return nil, err } diff --git a/src/vendor/golang.org/x/sys/windows/service.go b/src/vendor/golang.org/x/sys/windows/service.go index f8deca8397a..c964b6848d4 100644 --- a/src/vendor/golang.org/x/sys/windows/service.go +++ b/src/vendor/golang.org/x/sys/windows/service.go @@ -141,6 +141,12 @@ const ( SERVICE_DYNAMIC_INFORMATION_LEVEL_START_REASON = 1 ) +type ENUM_SERVICE_STATUS struct { + ServiceName *uint16 + DisplayName *uint16 + ServiceStatus SERVICE_STATUS +} + type SERVICE_STATUS struct { ServiceType uint32 CurrentState uint32 @@ -245,3 +251,4 @@ type QUERY_SERVICE_LOCK_STATUS struct { //sys UnsubscribeServiceChangeNotifications(subscription uintptr) = sechost.UnsubscribeServiceChangeNotifications? //sys RegisterServiceCtrlHandlerEx(serviceName *uint16, handlerProc uintptr, context uintptr) (handle Handle, err error) = advapi32.RegisterServiceCtrlHandlerExW //sys QueryServiceDynamicInformation(service Handle, infoLevel uint32, dynamicInfo unsafe.Pointer) (err error) = advapi32.QueryServiceDynamicInformation? +//sys EnumDependentServices(service Handle, activityState uint32, services *ENUM_SERVICE_STATUS, buffSize uint32, bytesNeeded *uint32, servicesReturned *uint32) (err error) = advapi32.EnumDependentServicesW diff --git a/src/vendor/golang.org/x/sys/windows/types_windows.go b/src/vendor/golang.org/x/sys/windows/types_windows.go index 0dbb2084117..88e62a63851 100644 --- a/src/vendor/golang.org/x/sys/windows/types_windows.go +++ b/src/vendor/golang.org/x/sys/windows/types_windows.go @@ -2220,15 +2220,19 @@ type JOBOBJECT_BASIC_UI_RESTRICTIONS struct { } const ( - // JobObjectInformationClass + // JobObjectInformationClass for QueryInformationJobObject and SetInformationJobObject JobObjectAssociateCompletionPortInformation = 7 + JobObjectBasicAccountingInformation = 1 + JobObjectBasicAndIoAccountingInformation = 8 JobObjectBasicLimitInformation = 2 + JobObjectBasicProcessIdList = 3 JobObjectBasicUIRestrictions = 4 JobObjectCpuRateControlInformation = 15 JobObjectEndOfJobTimeInformation = 6 JobObjectExtendedLimitInformation = 9 JobObjectGroupInformation = 11 JobObjectGroupInformationEx = 14 + JobObjectLimitViolationInformation = 13 JobObjectLimitViolationInformation2 = 34 JobObjectNetRateControlInformation = 32 JobObjectNotificationLimitInformation = 12 diff --git a/src/vendor/golang.org/x/sys/windows/zsyscall_windows.go b/src/vendor/golang.org/x/sys/windows/zsyscall_windows.go index 6d2a268534d..a81ea2c7001 100644 --- a/src/vendor/golang.org/x/sys/windows/zsyscall_windows.go +++ b/src/vendor/golang.org/x/sys/windows/zsyscall_windows.go @@ -86,6 +86,7 @@ var ( procDeleteService = modadvapi32.NewProc("DeleteService") procDeregisterEventSource = modadvapi32.NewProc("DeregisterEventSource") procDuplicateTokenEx = modadvapi32.NewProc("DuplicateTokenEx") + procEnumDependentServicesW = modadvapi32.NewProc("EnumDependentServicesW") procEnumServicesStatusExW = modadvapi32.NewProc("EnumServicesStatusExW") procEqualSid = modadvapi32.NewProc("EqualSid") procFreeSid = modadvapi32.NewProc("FreeSid") @@ -734,6 +735,14 @@ func DuplicateTokenEx(existingToken Token, desiredAccess uint32, tokenAttributes return } +func EnumDependentServices(service Handle, activityState uint32, services *ENUM_SERVICE_STATUS, buffSize uint32, bytesNeeded *uint32, servicesReturned *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procEnumDependentServicesW.Addr(), 6, uintptr(service), uintptr(activityState), uintptr(unsafe.Pointer(services)), uintptr(buffSize), uintptr(unsafe.Pointer(bytesNeeded)), uintptr(unsafe.Pointer(servicesReturned))) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func EnumServicesStatusEx(mgr Handle, infoLevel uint32, serviceType uint32, serviceState uint32, services *byte, bufSize uint32, bytesNeeded *uint32, servicesReturned *uint32, resumeHandle *uint32, groupName *uint16) (err error) { r1, _, e1 := syscall.Syscall12(procEnumServicesStatusExW.Addr(), 10, uintptr(mgr), uintptr(infoLevel), uintptr(serviceType), uintptr(serviceState), uintptr(unsafe.Pointer(services)), uintptr(bufSize), uintptr(unsafe.Pointer(bytesNeeded)), uintptr(unsafe.Pointer(servicesReturned)), uintptr(unsafe.Pointer(resumeHandle)), uintptr(unsafe.Pointer(groupName)), 0, 0) if r1 == 0 { diff --git a/src/vendor/modules.txt b/src/vendor/modules.txt index 61be24ea266..fc9dca4e78c 100644 --- a/src/vendor/modules.txt +++ b/src/vendor/modules.txt @@ -44,8 +44,6 @@ github.com/Masterminds/semver # github.com/Masterminds/semver/v3 v3.2.0 ## explicit; go 1.18 github.com/Masterminds/semver/v3 -# github.com/Microsoft/go-winio v0.5.1 -## explicit; go 1.12 # github.com/Unknwon/goconfig v0.0.0-20160216183935-5f601ca6ef4d ## explicit github.com/Unknwon/goconfig @@ -196,7 +194,7 @@ github.com/dghubble/sling github.com/dgryski/go-rendezvous # github.com/dnaeon/go-vcr v1.2.0 ## explicit; go 1.15 -# github.com/docker/distribution v2.8.1+incompatible => github.com/distribution/distribution v2.8.2+incompatible +# github.com/docker/distribution v2.8.2+incompatible => github.com/distribution/distribution v2.8.2+incompatible ## explicit github.com/docker/distribution github.com/docker/distribution/configuration @@ -319,11 +317,11 @@ github.com/gocraft/work ## explicit; go 1.15 github.com/gogo/protobuf/proto github.com/gogo/protobuf/sortkeys -# github.com/golang-jwt/jwt/v4 v4.2.0 -## explicit; go 1.15 -github.com/golang-jwt/jwt/v4 -# github.com/golang-migrate/migrate/v4 v4.15.1 +# github.com/golang-jwt/jwt/v4 v4.4.2 ## explicit; go 1.16 +github.com/golang-jwt/jwt/v4 +# github.com/golang-migrate/migrate/v4 v4.16.2 +## explicit; go 1.18 github.com/golang-migrate/migrate/v4 github.com/golang-migrate/migrate/v4/database github.com/golang-migrate/migrate/v4/database/multistmt @@ -346,8 +344,8 @@ github.com/golang/protobuf/ptypes/wrappers # github.com/gomodule/redigo v2.0.0+incompatible => github.com/gomodule/redigo v1.8.8 ## explicit; go 1.16 github.com/gomodule/redigo/redis -# github.com/google/go-querystring v1.0.0 -## explicit +# github.com/google/go-querystring v1.1.0 +## explicit; go 1.10 github.com/google/go-querystring/query # github.com/google/gofuzz v1.2.0 ## explicit; go 1.12 @@ -404,12 +402,12 @@ github.com/hashicorp/hcl/json/token # github.com/jackc/chunkreader/v2 v2.0.1 ## explicit; go 1.12 github.com/jackc/chunkreader/v2 -# github.com/jackc/pgconn v1.9.0 +# github.com/jackc/pgconn v1.14.0 ## explicit; go 1.12 github.com/jackc/pgconn github.com/jackc/pgconn/internal/ctxwatch github.com/jackc/pgconn/stmtcache -# github.com/jackc/pgerrcode v0.0.0-20201024163028-a0d42d470451 +# github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa ## explicit; go 1.12 github.com/jackc/pgerrcode # github.com/jackc/pgio v1.0.0 @@ -418,16 +416,16 @@ github.com/jackc/pgio # github.com/jackc/pgpassfile v1.0.0 ## explicit; go 1.12 github.com/jackc/pgpassfile -# github.com/jackc/pgproto3/v2 v2.1.1 +# github.com/jackc/pgproto3/v2 v2.3.2 ## explicit; go 1.12 github.com/jackc/pgproto3/v2 -# github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b +# github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a ## explicit; go 1.14 github.com/jackc/pgservicefile -# github.com/jackc/pgtype v1.8.0 +# github.com/jackc/pgtype v1.14.0 ## explicit; go 1.13 github.com/jackc/pgtype -# github.com/jackc/pgx/v4 v4.12.0 +# github.com/jackc/pgx/v4 v4.18.1 ## explicit; go 1.13 github.com/jackc/pgx/v4 github.com/jackc/pgx/v4/internal/sanitize @@ -526,7 +524,7 @@ github.com/satori/go.uuid # github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 ## explicit github.com/shiena/ansicolor -# github.com/sirupsen/logrus v1.9.0 +# github.com/sirupsen/logrus v1.9.2 ## explicit; go 1.13 github.com/sirupsen/logrus # github.com/spf13/afero v1.6.0 @@ -574,7 +572,7 @@ github.com/vmihailenco/msgpack/v5/msgpcode github.com/vmihailenco/tagparser github.com/vmihailenco/tagparser/internal github.com/vmihailenco/tagparser/internal/parser -# go.mongodb.org/mongo-driver v1.7.0 +# go.mongodb.org/mongo-driver v1.7.5 ## explicit; go 1.10 go.mongodb.org/mongo-driver/bson go.mongodb.org/mongo-driver/bson/bsoncodec @@ -667,7 +665,7 @@ go.uber.org/zap/internal/bufferpool go.uber.org/zap/internal/color go.uber.org/zap/internal/exit go.uber.org/zap/zapcore -# golang.org/x/crypto v0.5.0 +# golang.org/x/crypto v0.7.0 ## explicit; go 1.17 golang.org/x/crypto/acme golang.org/x/crypto/acme/autocert @@ -676,7 +674,7 @@ golang.org/x/crypto/md4 golang.org/x/crypto/pbkdf2 golang.org/x/crypto/pkcs12 golang.org/x/crypto/pkcs12/internal/rc2 -# golang.org/x/net v0.9.0 +# golang.org/x/net v0.10.0 ## explicit; go 1.17 golang.org/x/net/context golang.org/x/net/context/ctxhttp @@ -700,14 +698,14 @@ golang.org/x/oauth2/jwt ## explicit; go 1.17 golang.org/x/sync/errgroup golang.org/x/sync/singleflight -# golang.org/x/sys v0.7.0 +# golang.org/x/sys v0.8.0 ## explicit; go 1.17 golang.org/x/sys/internal/unsafeheader golang.org/x/sys/plan9 golang.org/x/sys/unix golang.org/x/sys/windows golang.org/x/sys/windows/registry -# golang.org/x/term v0.7.0 +# golang.org/x/term v0.8.0 ## explicit; go 1.17 golang.org/x/term # golang.org/x/text v0.9.0 From c707106ef78963386860e139c38ff7dbffb4502e Mon Sep 17 00:00:00 2001 From: Yang Jiao <72076317+YangJiao0817@users.noreply.github.com> Date: Tue, 11 Jul 2023 15:37:15 +0800 Subject: [PATCH 35/38] Add OIDC filter group testcase (#18914) Fix #17950 Signed-off-by: Yang Jiao --- tests/resources/Util.robot | 4 +- tests/robot-cases/Group1-Nightly/OIDC.robot | 42 ++++++++++++++++++++- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/tests/resources/Util.robot b/tests/resources/Util.robot index cc916519751..5f1567ccdcd 100644 --- a/tests/resources/Util.robot +++ b/tests/resources/Util.robot @@ -226,9 +226,9 @@ Text Input Clear Field Of Characters [Arguments] ${field} ${character count} - [Documentation] This keyword pushes the delete key (ascii: \8) a specified number of times in a specified field. + [Documentation] This keyword pushes the BACKSPACE key a specified number of times in a specified field. FOR ${index} IN RANGE ${character count} - Press Keys ${field} \\8 + Press Keys ${field} BACKSPACE END Wait Unitl Command Success diff --git a/tests/robot-cases/Group1-Nightly/OIDC.robot b/tests/robot-cases/Group1-Nightly/OIDC.robot index b683c2d0181..b1c9ad30f68 100644 --- a/tests/robot-cases/Group1-Nightly/OIDC.robot +++ b/tests/robot-cases/Group1-Nightly/OIDC.robot @@ -117,7 +117,6 @@ Test Case - OIDC Group User Should Be Equal As Strings '${output[0]}' 'FAIL' Close Browser - Test Case - Delete An OIDC User In Local DB Init Chrome Driver # sign in with admin role @@ -132,3 +131,44 @@ Test Case - Delete An OIDC User In Local DB Sign In Harbor With OIDC User ${HARBOR_URL} username=${admin_user} password=${admin_pwd} login_with_provider=ldap Should Contain Target User Close Browser + +Test Case - OIDC Group Filter + [Tags] group_filter + Init Chrome Driver + ${oidc_user}= Set Variable mike02 + ${oidc_pwd}= Set Variable zhu88jie + Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} is_oidc=${true} + Retry Element Click //clr-vertical-nav//span[contains(.,'Groups')] + Retry Wait Until Page Contains Element //clr-dg-pagination//div[contains(@class, 'pagination-description')] + ${total}= Get Text //clr-dg-pagination//div[contains(@class, 'pagination-description')] + # Delete all groups + Run Keyword If '${total}' != '0 items' Run Keywords Retry Element Click //div[@class='clr-checkbox-wrapper']//label AND Retry Button Click //button[contains(.,'Delete')] AND Retry Button Click //button[contains(.,'DELETE')] + # Set OIDCGroupFilter to .*users + Switch To Configuration Authentication + Retry Text Input //*[@id='OIDCGroupFilter'] .*users + Retry Element Click ${config_auth_save_button_xpath} + Logout Harbor + # Login to the Harbor using OIDC user + Sign In Harbor With OIDC User ${HARBOR_URL} username=${oidc_user} password=${oidc_pwd} login_with_provider=ldap + Logout Harbor + # Check that there is only one harbor_users group + Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} is_oidc=${true} + Retry Element Click //clr-vertical-nav//span[contains(.,'Groups')] + Retry Wait Until Page Contains Element //app-group//clr-dg-row//clr-dg-cell[text()='harbor_users'] + ${count}= Get Element Count //app-group//clr-dg-row + Should Be Equal As Integers ${count} 1 + # Reset OIDCGroupFilter + Switch To Configuration Authentication + Clear Field Of Characters //*[@id='OIDCGroupFilter'] 7 + Retry Element Click ${config_auth_save_button_xpath} + Logout Harbor + # Login to the Harbor using OIDC user + Sign In Harbor With OIDC User ${HARBOR_URL} username=${oidc_user} password=${oidc_pwd} login_with_provider=ldap + Logout Harbor + # Check that there are more than one groups + Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} is_oidc=${true} + Retry Element Click //clr-vertical-nav//span[contains(.,'Groups')] + Retry Wait Until Page Contains Element //app-group//clr-dg-row//clr-dg-cell[text()='harbor_users'] + ${count}= Get Element Count //app-group//clr-dg-row + Should Be True ${count} > 1 + Close Browser From df4dc3c00b604c10ce7d0d786c3a1b3ea94c9192 Mon Sep 17 00:00:00 2001 From: Shengwen YU Date: Wed, 12 Jul 2023 17:04:50 +0800 Subject: [PATCH 36/38] fix: add password/secret length check to be <= 128 (#18916) Signed-off-by: Shengwen Yu --- src/controller/robot/controller.go | 2 +- src/controller/robot/controller_test.go | 6 ++++++ src/server/v2.0/handler/robot.go | 2 +- src/server/v2.0/handler/user.go | 4 ++-- src/server/v2.0/handler/user_test.go | 8 ++++++-- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/controller/robot/controller.go b/src/controller/robot/controller.go index 45757704b1c..88b16612c12 100644 --- a/src/controller/robot/controller.go +++ b/src/controller/robot/controller.go @@ -416,5 +416,5 @@ var ( ) func IsValidSec(secret string) bool { - return len(secret) >= 8 && hasLower.MatchString(secret) && hasUpper.MatchString(secret) && hasNumber.MatchString(secret) + return len(secret) >= 8 && len(secret) <= 128 && hasLower.MatchString(secret) && hasUpper.MatchString(secret) && hasNumber.MatchString(secret) } diff --git a/src/controller/robot/controller_test.go b/src/controller/robot/controller_test.go index 596fbef7ad9..2a97d059288 100644 --- a/src/controller/robot/controller_test.go +++ b/src/controller/robot/controller_test.go @@ -301,6 +301,12 @@ func (suite *ControllerTestSuite) TestIsValidSec() { suite.False(IsValidSec(sec)) sec = "123abc" suite.False(IsValidSec(sec)) + // secret of length 128 characters long should be ok + sec = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcd" + suite.True(IsValidSec(sec)) + // secret of length larger than 128 characters long, such as 129 characters long, should return false + sec = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcde" + suite.False(IsValidSec(sec)) } func (suite *ControllerTestSuite) TestCreateSec() { diff --git a/src/server/v2.0/handler/robot.go b/src/server/v2.0/handler/robot.go index b839009ff48..2a5acda1ecd 100644 --- a/src/server/v2.0/handler/robot.go +++ b/src/server/v2.0/handler/robot.go @@ -242,7 +242,7 @@ func (rAPI *robotAPI) RefreshSec(ctx context.Context, params operation.RefreshSe robotSec := &models.RobotSec{} if params.RobotSec.Secret != "" { if !robot.IsValidSec(params.RobotSec.Secret) { - return rAPI.SendError(ctx, errors.New("the secret must longer than 8 chars with at least 1 uppercase letter, 1 lowercase letter and 1 number").WithCode(errors.BadRequestCode)) + return rAPI.SendError(ctx, errors.New("the secret must be 8-128, inclusively, characters long with at least 1 uppercase letter, 1 lowercase letter and 1 number").WithCode(errors.BadRequestCode)) } secret = utils.Encrypt(params.RobotSec.Secret, r.Salt, utils.SHA256) robotSec.Secret = "" diff --git a/src/server/v2.0/handler/user.go b/src/server/v2.0/handler/user.go index 6df2073f67c..1b81ae0eba6 100644 --- a/src/server/v2.0/handler/user.go +++ b/src/server/v2.0/handler/user.go @@ -456,10 +456,10 @@ func requireValidSecret(in string) error { hasLower := regexp.MustCompile(`[a-z]`) hasUpper := regexp.MustCompile(`[A-Z]`) hasNumber := regexp.MustCompile(`[0-9]`) - if len(in) >= 8 && hasLower.MatchString(in) && hasUpper.MatchString(in) && hasNumber.MatchString(in) { + if len(in) >= 8 && len(in) <= 128 && hasLower.MatchString(in) && hasUpper.MatchString(in) && hasNumber.MatchString(in) { return nil } - return errors.BadRequestError(nil).WithMessage("the password or secret must be longer than 8 chars with at least 1 uppercase letter, 1 lowercase letter and 1 number") + return errors.BadRequestError(nil).WithMessage("the password or secret must be 8-128, inclusively, characters long with at least 1 uppercase letter, 1 lowercase letter and 1 number") } func getRandomSecret() (string, error) { diff --git a/src/server/v2.0/handler/user_test.go b/src/server/v2.0/handler/user_test.go index f4b5cfb9f39..65cfc2475e3 100644 --- a/src/server/v2.0/handler/user_test.go +++ b/src/server/v2.0/handler/user_test.go @@ -28,6 +28,10 @@ func TestRequireValidSecret(t *testing.T) { {"Sh0rt", true}, {"Passw0rd", false}, {"Thisis1Valid_password", false}, + // secret of length 128 characters long should be ok, no error returned + {"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcd", false}, + // secret of length larger than 128 characters long, such as 129 characters long, should return error + {"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcde", true}, } for _, c := range cases { e := requireValidSecret(c.in) @@ -44,8 +48,8 @@ type UserTestSuite struct { func (uts *UserTestSuite) SetupSuite() { uts.user = &commonmodels.User{ - UserID: 1, - Username: "admin", + UserID: 1, + Username: "admin", } uts.uCtl = &usertesting.Controller{} From 90259f3c804111f9bb987076b43bc115ccd26837 Mon Sep 17 00:00:00 2001 From: Yang Jiao <72076317+YangJiao0817@users.noreply.github.com> Date: Wed, 12 Jul 2023 17:32:40 +0800 Subject: [PATCH 37/38] Add CVE Allowlist expires Test Cases (#18921) Fix #18920 Signed-off-by: Yang Jiao --- .../Harbor-Pages/Configuration.robot | 7 ++ .../Harbor-Pages/Configuration_Elements.robot | 3 + tests/resources/TestCaseBody.robot | 102 +++++++++++------- tests/robot-cases/Group1-Nightly/Trivy.robot | 2 + 4 files changed, 74 insertions(+), 40 deletions(-) diff --git a/tests/resources/Harbor-Pages/Configuration.robot b/tests/resources/Harbor-Pages/Configuration.robot index a2a9edb0a6d..a7c9b32f083 100644 --- a/tests/resources/Harbor-Pages/Configuration.robot +++ b/tests/resources/Harbor-Pages/Configuration.robot @@ -277,6 +277,13 @@ Delete Top Item In System CVE Allowlist END Retry Element Click ${config_security_save_button_xpath} +Set CVE Allowlist Expires + [Arguments] ${expired} + Retry Button Click ${cve_allowlist_expires_btn} + ${element}= Set Variable If ${expired} ${cve_allowlist_expires_yesterday} ${cve_allowlist_expires_tomorrow} + Retry Element Click ${element} + Retry Element Click //button[contains(.,'SAVE')] + Get Project Count Quota Text From Project Quotas List [Arguments] ${project_name} Switch To Project Quotas diff --git a/tests/resources/Harbor-Pages/Configuration_Elements.robot b/tests/resources/Harbor-Pages/Configuration_Elements.robot index b84a4ee4fef..05a9bbb03a9 100644 --- a/tests/resources/Harbor-Pages/Configuration_Elements.robot +++ b/tests/resources/Harbor-Pages/Configuration_Elements.robot @@ -34,6 +34,9 @@ ${configuration_system_wl_textarea} //*[@id='allowlist-textarea'] ${configuration_system_wl_add_confirm_btn} //*[@id='add-to-system'] ${configuration_system_wl_delete_a_cve_id_icon} //app-security//form/section//ul/li[1]/a[2]/clr-icon ${configuration_sys_repo_readonly_chb_id} //*[@id='repo_read_only_lbl'] +${cve_allowlist_expires_btn} //clr-date-container[.//div[@class='clr-input-group' and not(@hidden)]]//button +${cve_allowlist_expires_yesterday} //td[.//button[@class='day-btn is-today']]/preceding-sibling::td[1] +${cve_allowlist_expires_tomorrow} //td[.//button[@class='day-btn is-today']]/following-sibling::td[1] ${cfg_auth_automatic_onboarding_checkbox} //clr-checkbox-wrapper//label[contains(@for,'oidcAutoOnboard')] ${cfg_auth_user_name_claim_input} //*[@id='oidcUserClaim'] diff --git a/tests/resources/TestCaseBody.robot b/tests/resources/TestCaseBody.robot index fda86c095df..be20b4cc033 100644 --- a/tests/resources/TestCaseBody.robot +++ b/tests/resources/TestCaseBody.robot @@ -171,37 +171,44 @@ Helm CLI Work Flow Retry File Should Exist ./${harbor_helm_package} Helm Registry Logout ${ip} -#Important Note: All CVE IDs in CVE Allowlist cases must unique! Body Of Verfiy System Level CVE Allowlist [Arguments] ${image_argument} ${sha256_argument} ${most_cve_list} ${single_cve} Init Chrome Driver ${d}= Get Current Date result_format=%m%s ${image}= Set Variable ${image_argument} ${sha256}= Set Variable ${sha256_argument} - ${signin_user}= Set Variable user025 - ${signin_pwd}= Set Variable Test1@34 - Sign In Harbor ${HARBOR_URL} ${signin_user} ${signin_pwd} - Create An New Project And Go Into Project project${d} - Push Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} sha256=${sha256} + ${signin_user}= Set Variable user025 + ${signin_pwd}= Set Variable Test1@34 + Sign In Harbor ${HARBOR_URL} ${signin_user} ${signin_pwd} + Create An New Project And Go Into Project project${d} + Push Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} sha256=${sha256} Go Into Project project${d} Set Vulnerabilty Serverity 2 - Cannot Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} err_msg=cannot be pulled due to configured policy + Cannot Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} err_msg=cannot be pulled due to configured policy Go Into Repo project${d} ${image} Scan Repo ${sha256} Succeed Logout Harbor - - Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} + Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} Check Listed In CVE Allowlist project${d} ${image} ${sha256} ${single_cve} is_in=No Switch To Configuration Security + Retry Wait Element Visible //li[text()=' None '] # Add Items To System CVE Allowlist CVE-2021-36222\nCVE-2021-43527 \nCVE-2021-4044 \nCVE-2021-36084 \nCVE-2021-36085 \nCVE-2021-36086 \nCVE-2021-37750 \nCVE-2021-40528 - Add Items To System CVE Allowlist ${most_cve_list} - Cannot Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} err_msg=cannot be pulled due to configured policy + Add Items To System CVE Allowlist ${most_cve_list} + Cannot Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} err_msg=cannot be pulled due to configured policy # Add Items To System CVE Allowlist CVE-2021-43519 - Add Items To System CVE Allowlist ${single_cve} - Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} - Delete Top Item In System CVE Allowlist count=9 - Cannot Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} err_msg=cannot be pulled due to configured policy + Add Items To System CVE Allowlist ${single_cve} + Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} + # Set System CVE Allowlist expires to expired + Set CVE Allowlist Expires ${True} + Retry Wait Until Page Contains The system CVE allowlist has expired. You can enable the allowlist by extending the expiration date. + Cannot Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} err_msg=cannot be pulled due to configured policy + # Set System CVE Allowlist expires to not expired + Set CVE Allowlist Expires ${False} + Retry Wait Until Page Does Not Contains The system CVE allowlist has expired. You can enable the allowlist by extending the expiration date. + Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} + Delete Top Item In System CVE Allowlist count=9 + Cannot Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} err_msg=cannot be pulled due to configured policy Check Listed In CVE Allowlist project${d} ${image} ${sha256} ${single_cve} Close Browser @@ -209,55 +216,70 @@ Body Of Verfiy Project Level CVE Allowlist [Arguments] ${image_argument} ${sha256_argument} ${most_cve_list} ${single_cve} [Tags] run-once Init Chrome Driver - ${d}= Get Current Date result_format=%m%s - ${image}= Set Variable ${image_argument} + ${d}= Get Current Date result_format=%m%s + ${image}= Set Variable ${image_argument} ${sha256}= Set Variable ${sha256_argument} - ${signin_user}= Set Variable user025 - ${signin_pwd}= Set Variable Test1@34 - Sign In Harbor ${HARBOR_URL} ${signin_user} ${signin_pwd} - Create An New Project And Go Into Project project${d} - Push Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} sha256=${sha256} - Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} + ${signin_user}= Set Variable user025 + ${signin_pwd}= Set Variable Test1@34 + Sign In Harbor ${HARBOR_URL} ${signin_user} ${signin_pwd} + Create An New Project And Go Into Project project${d} + Push Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} sha256=${sha256} + Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} Go Into Project project${d} Set Vulnerabilty Serverity 2 - Cannot Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} + Cannot Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} Go Into Repo project${d} ${image} Scan Repo ${sha256} Succeed Go Into Project project${d} - Add Items to Project CVE Allowlist ${most_cve_list} - Cannot Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} - Add Items to Project CVE Allowlist ${single_cve} - Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} + Add Items to Project CVE Allowlist ${most_cve_list} + Cannot Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} + Add Items to Project CVE Allowlist ${single_cve} + Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} + # Set System CVE Allowlist expires to expired + Set CVE Allowlist Expires ${True} + Retry Wait Until Page Contains The project CVE allowlist has expired. You can enable the allowlist by extending the expiration date. + Cannot Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} err_msg=cannot be pulled due to configured policy + # Set System CVE Allowlist expires to not expired + Set CVE Allowlist Expires ${False} + Retry Wait Until Page Does Not Contains The project CVE allowlist has expired. You can enable the allowlist by extending the expiration date. + Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} Delete Top Item In Project CVE Allowlist - Cannot Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} + Cannot Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} Close Browser Body Of Verfiy Project Level CVE Allowlist By Quick Way of Add System [Arguments] ${image_argument} ${sha256_argument} ${cve_list} [Tags] run-once Init Chrome Driver - ${d}= Get Current Date result_format=%m%s - ${image}= Set Variable ${image_argument} + ${d}= Get Current Date result_format=%m%s + ${image}= Set Variable ${image_argument} ${sha256}= Set Variable ${sha256_argument} - ${signin_user}= Set Variable user025 - ${signin_pwd}= Set Variable Test1@34 - Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} + ${signin_user}= Set Variable user025 + ${signin_pwd}= Set Variable Test1@34 + Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} Switch To Configuration Security Add Items To System CVE Allowlist ${cve_list} Logout Harbor - Sign In Harbor ${HARBOR_URL} ${signin_user} ${signin_pwd} - Create An New Project And Go Into Project project${d} - Push Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} sha256=${sha256} + Sign In Harbor ${HARBOR_URL} ${signin_user} ${signin_pwd} + Create An New Project And Go Into Project project${d} + Push Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} sha256=${sha256} Go Into Project project${d} Set Vulnerabilty Serverity 2 Go Into Repo project${d} ${image} Scan Repo ${sha256} Succeed - Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} + Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} Go Into Project project${d} Set Project To Project Level CVE Allowlist - Cannot Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} + Cannot Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} Add System CVE Allowlist to Project CVE Allowlist By Add System Button Click - Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} + Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} + # Set System CVE Allowlist expires to expired + Set CVE Allowlist Expires ${True} + Retry Wait Until Page Contains The project CVE allowlist has expired. You can enable the allowlist by extending the expiration date. + Cannot Pull Image ${ip} ${signin_user} ${signin_pwd} project${d} ${image} tag=${sha256} err_msg=cannot be pulled due to configured policy + # Set System CVE Allowlist expires to not expired + Set CVE Allowlist Expires ${False} + Retry Wait Until Page Does Not Contains The project CVE allowlist has expired. You can enable the allowlist by extending the expiration date. Close Browser Body Of Replication Of Push Images to Registry Triggered By Event diff --git a/tests/robot-cases/Group1-Nightly/Trivy.robot b/tests/robot-cases/Group1-Nightly/Trivy.robot index c05584bf548..0c4d0c69750 100644 --- a/tests/robot-cases/Group1-Nightly/Trivy.robot +++ b/tests/robot-cases/Group1-Nightly/Trivy.robot @@ -95,9 +95,11 @@ Test Case - Verfiy System Level CVE Allowlist Body Of Verfiy System Level CVE Allowlist goharbor/harbor-portal 55d776fc7f431cdd008c3d8fc3e090c81c1368ed9ed85335f4664df71f864f0d CVE-2021-36222\nCVE-2021-43527 \nCVE-2021-4044 \nCVE-2021-36084 \nCVE-2021-36085 \nCVE-2021-36086 \nCVE-2021-37750 \nCVE-2021-40528 CVE-2021-43519 Test Case - Verfiy Project Level CVE Allowlist + [Tags] proj_cve Body Of Verfiy Project Level CVE Allowlist goharbor/harbor-portal 55d776fc7f431cdd008c3d8fc3e090c81c1368ed9ed85335f4664df71f864f0d CVE-2021-36222\nCVE-2021-43527 \nCVE-2021-4044 \nCVE-2021-36084 \nCVE-2021-36085 \nCVE-2021-36086 \nCVE-2021-37750 \nCVE-2021-40528 CVE-2021-43519 Test Case - Verfiy Project Level CVE Allowlist By Quick Way of Add System + [Tags] proj_cve_quick_add_sys Body Of Verfiy Project Level CVE Allowlist By Quick Way of Add System goharbor/harbor-portal 55d776fc7f431cdd008c3d8fc3e090c81c1368ed9ed85335f4664df71f864f0d CVE-2021-36222\nCVE-2021-43527 \nCVE-2021-4044 \nCVE-2021-36084 \nCVE-2021-36085 \nCVE-2021-36086 \nCVE-2021-37750 \nCVE-2021-40528 \nCVE-2021-43519 Test Case - Stop Scan And Stop Scan All From 93e428d0d2bf45d3e19e4cff580eaadc24ac9ea6 Mon Sep 17 00:00:00 2001 From: "stonezdj(Daojun Zhang)" Date: Wed, 12 Jul 2023 19:18:08 +0800 Subject: [PATCH 38/38] Add security hub summary API (#18872) include WithCVE, WithArtifact option Signed-off-by: stonezdj --- api/v2.0/swagger.yaml | 148 ++++++++++++++++- src/common/rbac/const.go | 1 + src/common/rbac/system/policies.go | 2 + src/controller/securityhub/controller.go | 138 +++++++++++++++ src/controller/securityhub/controller_test.go | 157 ++++++++++++++++++ .../scan/postprocessors/report_converters.go | 4 + src/pkg/securityhub/dao/security.go | 133 +++++++++++++++ src/pkg/securityhub/dao/security_test.go | 104 ++++++++++++ src/pkg/securityhub/manager.go | 69 ++++++++ src/pkg/securityhub/model/model.go | 44 +++++ src/server/v2.0/handler/handler.go | 1 + src/server/v2.0/handler/security.go | 98 +++++++++++ src/testing/controller/controller.go | 1 + .../controller/securityhub/controller.go | 65 ++++++++ src/testing/pkg/pkg.go | 1 + src/testing/pkg/securityhub/manager.go | 136 +++++++++++++++ 16 files changed, 1101 insertions(+), 1 deletion(-) create mode 100644 src/controller/securityhub/controller.go create mode 100644 src/controller/securityhub/controller_test.go create mode 100644 src/pkg/securityhub/dao/security.go create mode 100644 src/pkg/securityhub/dao/security_test.go create mode 100644 src/pkg/securityhub/manager.go create mode 100644 src/pkg/securityhub/model/model.go create mode 100644 src/server/v2.0/handler/security.go create mode 100644 src/testing/controller/securityhub/controller.go create mode 100644 src/testing/pkg/securityhub/manager.go diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index 46d97702840..7f6f36f484b 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -6052,7 +6052,40 @@ paths: $ref: '#/responses/404' '500': $ref: '#/responses/500' - + /security/summary: + get: + summary: Get vulnerability system summary + description: Retrieve the vulnerability summary of the system + tags: + - securityhub + operationId: getSecuritySummary + parameters: + - $ref: '#/parameters/requestId' + - name: with_dangerous_cve + in: query + description: Specify whether the dangerous CVE is include in the security summary + type: boolean + required: false + default: false + - name: with_dangerous_artifact + in: query + description: Specify whether the dangerous artifacts is include in the security summary + type: boolean + required: false + default: false + responses: + '200': + description: Success + schema: + $ref: '#/definitions/SecuritySummary' + '401': + $ref: '#/responses/401' + '403': + $ref: '#/responses/403' + '404': + $ref: '#/responses/404' + '500': + $ref: '#/responses/500' parameters: query: name: q @@ -9614,3 +9647,116 @@ definitions: type: boolean description: if the scheduler is paused x-omitempty: false + SecuritySummary: + type: object + description: the security summary + properties: + critical_cnt: + type: integer + format: int64 + x-omitempty: false + description: the count of critical vulnerabilities + high_cnt: + type: integer + format: int64 + description: the count of high vulnerabilities + medium_cnt: + type: integer + format: int64 + x-omitempty: false + description: the count of medium vulnerabilities + low_cnt: + type: integer + format: int64 + x-omitempty: false + description: the count of low vulnerabilities + none_cnt: + type: integer + format: int64 + description: the count of none vulnerabilities + unknown_cnt: + type: integer + format: int64 + description: the count of unknown vulnerabilities + total_vuls: + type: integer + format: int64 + x-omitempty: false + description: the count of total vulnerabilities + scanned_cnt: + type: integer + format: int64 + x-omitempty: false + description: the count of scanned artifacts + total_artifact: + type: integer + format: int64 + x-omitempty: false + description: the total count of artifacts + fixable_cnt: + type: integer + format: int64 + x-omitempty: false + description: the count of fixable vulnerabilities + dangerous_cves: + type: array + x-omitempty: true + description: the list of dangerous CVEs + items: + $ref: '#/definitions/DangerousCVE' + dangerous_artifacts: + type: array + x-omitempty: true + description: the list of dangerous artifacts + items: + $ref: '#/definitions/DangerousArtifact' + DangerousCVE: + type: object + description: the dangerous CVE information + properties: + cve_id: + type: string + description: the cve id + severity: + type: string + description: the severity of the CVE + cvss_score_v3: + type: number + format: float64 + description: the cvss score v3 + desc: + type: string + description: the description of the CVE + package: + type: string + description: the package of the CVE + version: + type: string + description: the version of the package + DangerousArtifact: + type: object + description: the dangerous artifact information + properties: + project_id: + type: integer + format: int64 + description: the project id of the artifact + repository_name: + type: string + description: the repository name of the artifact + digest: + type: string + description: the digest of the artifact + critical_cnt: + type: integer + x-omitempty: false + description: the count of critical vulnerabilities + high_cnt: + type: integer + format: int64 + x-omitempty: false + description: the count of high vulnerabilities + medium_cnt: + type: integer + x-omitempty: false + description: the count of medium vulnerabilities diff --git a/src/common/rbac/const.go b/src/common/rbac/const.go index b085eeb6cdb..282779c4905 100644 --- a/src/common/rbac/const.go +++ b/src/common/rbac/const.go @@ -75,4 +75,5 @@ const ( ResourcePurgeAuditLog = Resource("purge-audit") ResourceExportCVE = Resource("export-cve") ResourceJobServiceMonitor = Resource("jobservice-monitor") + ResourceSecurityHub = Resource("security-hub") ) diff --git a/src/common/rbac/system/policies.go b/src/common/rbac/system/policies.go index f3e6a6a81d6..8fd769380f9 100644 --- a/src/common/rbac/system/policies.go +++ b/src/common/rbac/system/policies.go @@ -84,5 +84,7 @@ var ( {Resource: rbac.ResourceJobServiceMonitor, Action: rbac.ActionRead}, {Resource: rbac.ResourceJobServiceMonitor, Action: rbac.ActionList}, {Resource: rbac.ResourceJobServiceMonitor, Action: rbac.ActionStop}, + + {Resource: rbac.ResourceSecurityHub, Action: rbac.ActionRead}, } ) diff --git a/src/controller/securityhub/controller.go b/src/controller/securityhub/controller.go new file mode 100644 index 00000000000..f83af4956c0 --- /dev/null +++ b/src/controller/securityhub/controller.go @@ -0,0 +1,138 @@ +// 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 securityhub + +import ( + "context" + + "github.com/goharbor/harbor/src/lib/q" + "github.com/goharbor/harbor/src/pkg" + "github.com/goharbor/harbor/src/pkg/artifact" + "github.com/goharbor/harbor/src/pkg/scan/scanner" + "github.com/goharbor/harbor/src/pkg/securityhub" + secHubModel "github.com/goharbor/harbor/src/pkg/securityhub/model" +) + +// Ctl is the global controller for security hub +var Ctl = NewController() + +// Options define the option to query summary info +type Options struct { + WithCVE bool + WithArtifact bool +} + +// Option define the func to build options +type Option func(*Options) + +func newOptions(options ...Option) *Options { + opts := &Options{} + for _, f := range options { + f(opts) + } + return opts +} + +// WithCVE enable CVE info in summary +func WithCVE(enable bool) Option { + return func(o *Options) { + o.WithCVE = enable + } +} + +// WithArtifact enable artifact info in summary +func WithArtifact(enable bool) Option { + return func(o *Options) { + o.WithArtifact = enable + } +} + +// Controller controller of security hub +type Controller interface { + // SecuritySummary returns the security summary of the specified project. + SecuritySummary(ctx context.Context, projectID int64, options ...Option) (*secHubModel.Summary, error) +} + +type controller struct { + artifactMgr artifact.Manager + scannerMgr scanner.Manager + secHubMgr securityhub.Manager +} + +// NewController ... +func NewController() Controller { + return &controller{ + artifactMgr: pkg.ArtifactMgr, + scannerMgr: scanner.New(), + secHubMgr: securityhub.Mgr, + } +} + +func (c *controller) SecuritySummary(ctx context.Context, projectID int64, options ...Option) (*secHubModel.Summary, error) { + opts := newOptions(options...) + scannerUUID, err := c.defaultScannerUUID(ctx) + if err != nil { + return nil, err + } + sum, err := c.secHubMgr.Summary(ctx, scannerUUID, projectID, nil) + if err != nil { + return nil, err + } + sum.TotalArtifactCnt, err = c.totalArtifactCount(ctx, projectID) + if err != nil { + return nil, err + } + sum.ScannedCnt, err = c.secHubMgr.ScannedArtifactsCount(ctx, scannerUUID, projectID, nil) + if err != nil { + return nil, err + } + if opts.WithCVE { + sum.DangerousCVEs, err = c.secHubMgr.DangerousCVEs(ctx, scannerUUID, projectID, nil) + if err != nil { + return nil, err + } + } + if opts.WithArtifact { + sum.DangerousArtifacts, err = c.secHubMgr.DangerousArtifacts(ctx, scannerUUID, projectID, nil) + if err != nil { + return nil, err + } + } + return sum, nil +} + +func (c *controller) scannedArtifactCount(ctx context.Context, projectID int64) (int64, error) { + scannerUUID, err := c.defaultScannerUUID(ctx) + if err != nil { + return 0, err + } + return c.secHubMgr.ScannedArtifactsCount(ctx, scannerUUID, projectID, nil) +} + +func (c *controller) totalArtifactCount(ctx context.Context, projectID int64) (int64, error) { + if projectID == 0 { + return c.artifactMgr.Count(ctx, nil) + } + return c.artifactMgr.Count(ctx, q.New(q.KeyWords{"project_id": projectID})) +} + +// defaultScannerUUID returns the default scanner uuid. +func (c *controller) defaultScannerUUID(ctx context.Context) (string, error) { + reg, err := c.scannerMgr.GetDefault(ctx) + if err != nil { + return "", err + } + return reg.UUID, nil +} diff --git a/src/controller/securityhub/controller_test.go b/src/controller/securityhub/controller_test.go new file mode 100644 index 00000000000..987a7619da8 --- /dev/null +++ b/src/controller/securityhub/controller_test.go @@ -0,0 +1,157 @@ +// 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 securityhub + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/goharbor/harbor/src/pkg/scan/dao/scan" + "github.com/goharbor/harbor/src/pkg/scan/dao/scanner" + "github.com/goharbor/harbor/src/pkg/securityhub/model" + htesting "github.com/goharbor/harbor/src/testing" + "github.com/goharbor/harbor/src/testing/mock" + artifactMock "github.com/goharbor/harbor/src/testing/pkg/artifact" + scannerMock "github.com/goharbor/harbor/src/testing/pkg/scan/scanner" + securityMock "github.com/goharbor/harbor/src/testing/pkg/securityhub" +) + +var sum = &model.Summary{ + CriticalCnt: 50, + HighCnt: 40, + MediumCnt: 30, + LowCnt: 20, + NoneCnt: 10, + FixableCnt: 90, +} + +type ControllerTestSuite struct { + htesting.Suite + c *controller + artifactMgr *artifactMock.Manager + scannerMgr *scannerMock.Manager + secHubMgr *securityMock.Manager +} + +// TestController is the entry of controller test suite +func TestController(t *testing.T) { + suite.Run(t, new(ControllerTestSuite)) +} + +// SetupTest prepares env for the controller test suite +func (suite *ControllerTestSuite) SetupTest() { + suite.artifactMgr = &artifactMock.Manager{} + suite.secHubMgr = &securityMock.Manager{} + suite.scannerMgr = &scannerMock.Manager{} + suite.c = &controller{ + artifactMgr: suite.artifactMgr, + secHubMgr: suite.secHubMgr, + scannerMgr: suite.scannerMgr, + } +} + +func (suite *ControllerTestSuite) TearDownTest() { +} + +// TestSecuritySummary tests the security summary +func (suite *ControllerTestSuite) TestSecuritySummary() { + ctx := suite.Context() + + mock.OnAnything(suite.artifactMgr, "Count").Return(int64(1234), nil) + mock.OnAnything(suite.secHubMgr, "ScannedArtifactsCount").Return(int64(1000), nil) + mock.OnAnything(suite.secHubMgr, "Summary").Return(sum, nil).Twice() + mock.OnAnything(suite.scannerMgr, "GetDefault").Return(&scanner.Registration{UUID: "ruuid"}, nil) + summary, err := suite.c.SecuritySummary(ctx, 0, WithArtifact(false), WithCVE(false)) + suite.NoError(err) + suite.NotNil(summary) + suite.Equal(int64(1234), summary.TotalArtifactCnt) + suite.Equal(int64(1000), summary.ScannedCnt) + suite.Equal(int64(50), summary.CriticalCnt) + suite.Equal(int64(40), summary.HighCnt) + suite.Equal(int64(30), summary.MediumCnt) + suite.Equal(int64(20), summary.LowCnt) + suite.Equal(int64(10), summary.NoneCnt) + suite.Equal(int64(90), summary.FixableCnt) + sum.DangerousCVEs = []*scan.VulnerabilityRecord{ + {CVEID: "CVE-2020-1234", Severity: "CRITICAL"}, + {CVEID: "CVE-2020-1235", Severity: "HIGH"}, + {CVEID: "CVE-2020-1236", Severity: "MEDIUM"}, + {CVEID: "CVE-2020-1237", Severity: "LOW"}, + {CVEID: "CVE-2020-1238", Severity: "NONE"}, + } + sum.DangerousArtifacts = []*model.DangerousArtifact{ + {Project: 1, Repository: "library/busybox"}, + {Project: 1, Repository: "library/nginx"}, + {Project: 1, Repository: "library/hello-world"}, + {Project: 1, Repository: "library/harbor-jobservice"}, + {Project: 1, Repository: "library/harbor-core"}, + } + mock.OnAnything(suite.secHubMgr, "Summary").Return(sum, nil).Once() + mock.OnAnything(suite.secHubMgr, "DangerousCVEs").Return(sum.DangerousCVEs, nil).Once() + mock.OnAnything(suite.secHubMgr, "DangerousArtifacts").Return(sum.DangerousArtifacts, nil).Once() + sum2, err := suite.c.SecuritySummary(ctx, 0, WithCVE(false), WithArtifact(false)) + suite.NoError(err) + suite.NotNil(sum2) + suite.NotNil(sum2.DangerousCVEs) + suite.NotNil(sum2.DangerousArtifacts) + + sum3, err := suite.c.SecuritySummary(ctx, 0, WithCVE(true), WithArtifact(true)) + suite.NoError(err) + suite.NotNil(sum3) + suite.True(len(sum3.DangerousCVEs) > 0) + suite.True(len(sum3.DangerousArtifacts) > 0) +} + +// TestSecuritySummaryError tests the security summary with error +func (suite *ControllerTestSuite) TestSecuritySummaryError() { + ctx := suite.Context() + mock.OnAnything(suite.scannerMgr, "GetDefault").Return(&scanner.Registration{UUID: "ruuid"}, nil) + mock.OnAnything(suite.secHubMgr, "ScannedArtifactsCount").Return(int64(1000), nil) + mock.OnAnything(suite.secHubMgr, "Summary").Return(nil, errors.New("invalid project")).Once() + summary, err := suite.c.SecuritySummary(ctx, 0, WithCVE(false), WithArtifact(false)) + suite.Error(err) + suite.Nil(summary) + mock.OnAnything(suite.artifactMgr, "Count").Return(int64(0), errors.New("failed to connect db")).Once() + mock.OnAnything(suite.secHubMgr, "Summary").Return(sum, nil).Once() + summary, err = suite.c.SecuritySummary(ctx, 0, WithCVE(false), WithArtifact(false)) + suite.Error(err) + suite.Nil(summary) + +} + +// TestGetDefaultScanner tests the get default scanner +func (suite *ControllerTestSuite) TestGetDefaultScanner() { + ctx := suite.Context() + mock.OnAnything(suite.scannerMgr, "GetDefault").Return(&scanner.Registration{UUID: ""}, nil).Once() + scanner, err := suite.c.defaultScannerUUID(ctx) + suite.NoError(err) + suite.Equal("", scanner) + + mock.OnAnything(suite.scannerMgr, "GetDefault").Return(nil, errors.New("failed to get scanner")).Once() + scanner, err = suite.c.defaultScannerUUID(ctx) + suite.Error(err) + suite.Equal("", scanner) +} + +func (suite *ControllerTestSuite) TestScannedArtifact() { + ctx := suite.Context() + mock.OnAnything(suite.scannerMgr, "GetDefault").Return(&scanner.Registration{UUID: "ruuid"}, nil) + mock.OnAnything(suite.secHubMgr, "ScannedArtifactsCount").Return(int64(1000), nil) + scanned, err := suite.c.scannedArtifactCount(ctx, 0) + suite.NoError(err) + suite.Equal(int64(1000), scanned) +} diff --git a/src/pkg/scan/postprocessors/report_converters.go b/src/pkg/scan/postprocessors/report_converters.go index c3bb51363b2..3acea71dd03 100644 --- a/src/pkg/scan/postprocessors/report_converters.go +++ b/src/pkg/scan/postprocessors/report_converters.go @@ -71,6 +71,10 @@ func (c *nativeToRelationalSchemaConverter) ToRelationalSchema(ctx context.Conte return "", "", errors.Wrap(err, "Error when converting vulnerability report") } + if err := c.updateReport(ctx, rawReport.Vulnerabilities, reportUUID); err != nil { + return "", "", errors.Wrap(err, "Error when updating report") + } + rawReport.Vulnerabilities = nil data, err := json.Marshal(rawReport) if err != nil { diff --git a/src/pkg/securityhub/dao/security.go b/src/pkg/securityhub/dao/security.go new file mode 100644 index 00000000000..8adc67f0ac2 --- /dev/null +++ b/src/pkg/securityhub/dao/security.go @@ -0,0 +1,133 @@ +// 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 dao + +import ( + "context" + + "github.com/goharbor/harbor/src/lib/orm" + "github.com/goharbor/harbor/src/lib/q" + "github.com/goharbor/harbor/src/pkg/scan/dao/scan" + "github.com/goharbor/harbor/src/pkg/securityhub/model" +) + +const ( + summarySQL = `select sum(s.critical_cnt) critical_cnt, + sum(s.high_cnt) high_cnt, + sum(s.medium_cnt) medium_cnt, + sum(s.low_cnt) low_cnt, + sum(s.none_cnt) none_cnt, + sum(s.unknown_cnt) unknown_cnt, + sum(s.fixable_cnt) fixable_cnt +from artifact a + left join scan_report s on a.digest = s.digest + where s.registration_uuid = ?` + + dangerousArtifactSQL = `select a.project_id project, a.repository_name repository, a.digest, s.critical_cnt, s.high_cnt, s.medium_cnt, s.low_cnt +from artifact a, + scan_report s +where a.digest = s.digest + and s.registration_uuid = ? +order by s.critical_cnt desc, s.high_cnt desc, s.medium_cnt desc, s.low_cnt desc +limit 5` + + scannedArtifactCountSQL = `select count(1) + from artifact a + left join scan_report s on a.digest = s.digest + where s.registration_uuid= ? and s.uuid is not null` + + dangerousCVESQL = `select vr.* +from vulnerability_record vr +where vr.cvss_score_v3 is not null +and vr.registration_uuid = ? +order by vr.cvss_score_v3 desc +limit 5` +) + +// SecurityHubDao defines the interface to access security hub data. +type SecurityHubDao interface { + // Summary returns the summary of the scan cve reports. + Summary(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) (*model.Summary, error) + // DangerousCVEs get the top 5 most dangerous CVEs, return top 5 result + DangerousCVEs(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) ([]*scan.VulnerabilityRecord, error) + // DangerousArtifacts returns top 5 dangerous artifact for the given scanner. return top 5 result + DangerousArtifacts(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) ([]*model.DangerousArtifact, error) + // ScannedArtifactsCount return the count of scanned artifacts. + ScannedArtifactsCount(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) (int64, error) +} + +// New creates a new SecurityHubDao instance. +func New() SecurityHubDao { + return &dao{} +} + +type dao struct { +} + +func (d *dao) Summary(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) (*model.Summary, error) { + if len(scannerUUID) == 0 || projectID != 0 { + return nil, nil + } + o, err := orm.FromContext(ctx) + if err != nil { + return nil, err + } + var sum model.Summary + err = o.Raw(summarySQL, scannerUUID).QueryRow(&sum.CriticalCnt, + &sum.HighCnt, + &sum.MediumCnt, + &sum.LowCnt, + &sum.NoneCnt, + &sum.UnknownCnt, + &sum.FixableCnt) + return &sum, err +} +func (d *dao) DangerousArtifacts(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) ([]*model.DangerousArtifact, error) { + if len(scannerUUID) == 0 || projectID != 0 { + return nil, nil + } + o, err := orm.FromContext(ctx) + if err != nil { + return nil, err + } + var artifacts []*model.DangerousArtifact + _, err = o.Raw(dangerousArtifactSQL, scannerUUID).QueryRows(&artifacts) + return artifacts, err +} + +func (d *dao) ScannedArtifactsCount(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) (int64, error) { + if len(scannerUUID) == 0 || projectID != 0 { + return 0, nil + } + var cnt int64 + o, err := orm.FromContext(ctx) + if err != nil { + return cnt, err + } + err = o.Raw(scannedArtifactCountSQL, scannerUUID).QueryRow(&cnt) + return cnt, err +} +func (d *dao) DangerousCVEs(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) ([]*scan.VulnerabilityRecord, error) { + if len(scannerUUID) == 0 || projectID != 0 { + return nil, nil + } + cves := make([]*scan.VulnerabilityRecord, 0) + o, err := orm.FromContext(ctx) + if err != nil { + return nil, err + } + _, err = o.Raw(dangerousCVESQL, scannerUUID).QueryRows(&cves) + return cves, err +} diff --git a/src/pkg/securityhub/dao/security_test.go b/src/pkg/securityhub/dao/security_test.go new file mode 100644 index 00000000000..7cab1ab4a78 --- /dev/null +++ b/src/pkg/securityhub/dao/security_test.go @@ -0,0 +1,104 @@ +// 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 dao + +import ( + "testing" + + "github.com/stretchr/testify/suite" + + testDao "github.com/goharbor/harbor/src/common/dao" + "github.com/goharbor/harbor/src/lib/orm" + htesting "github.com/goharbor/harbor/src/testing" +) + +func TestDao(t *testing.T) { + suite.Run(t, &SecurityDaoTestSuite{}) +} + +type SecurityDaoTestSuite struct { + htesting.Suite + dao SecurityHubDao +} + +// SetupSuite prepares env for test suite. +func (suite *SecurityDaoTestSuite) SetupSuite() { + suite.Suite.SetupSuite() + suite.dao = New() +} + +// SetupTest prepares env for test case. +func (suite *SecurityDaoTestSuite) SetupTest() { + testDao.ExecuteBatchSQL([]string{ + `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 (project_id, repository_name, digest, type, pull_time, push_time, repository_id, media_type, manifest_media_type, size, extra_attrs, annotations, icon) +values (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 scanner_registration (name, url, uuid, auth) values('trivy', 'https://www.vmware.com', 'ruuid', 'empty')`, + `insert into vulnerability_record (id, cve_id, registration_uuid, cvss_score_v3) values (1, '2023-4567-12345', 'ruuid', 9.8)`, + `insert into report_vulnerability_record (report_uuid, vuln_record_id) VALUES ('uuid', 1)`, + }) + + testDao.ExecuteBatchSQL([]string{ + `INSERT INTO scanner_registration (name, url, uuid, auth) values('trivy2', 'https://www.trivy.com', 'uuid2', 'empty')`, + `INSERT INTO vulnerability_record(cve_id, registration_uuid, cvss_score_v3, package) VALUES ('CVE-2021-44228', 'uuid2', 10, 'org.apache.logging.log4j:log4j-core'); + INSERT INTO vulnerability_record(cve_id, registration_uuid, cvss_score_v3, package) VALUES ('CVE-2021-21345', 'uuid2', 9.9, 'com.thoughtworks.xstream:xstream'); + INSERT INTO vulnerability_record(cve_id, registration_uuid, cvss_score_v3, package) VALUES ('CVE-2016-1585', 'uuid2', 9.8, 'libapparmor1'); + INSERT INTO vulnerability_record(cve_id, registration_uuid, cvss_score_v3, package) VALUES ('CVE-2023-0950', 'uuid2', 9.8, 'ure'); + INSERT INTO vulnerability_record(cve_id, registration_uuid, cvss_score_v3, package) VALUES ('CVE-2022-47629', 'uuid2', 9.8, 'libksba8'); + `, + }) +} + +// TearDownTest clears enf for test case. +func (suite *SecurityDaoTestSuite) TearDownTest() { + testDao.ExecuteBatchSQL([]string{ + `delete from scan_report where uuid = 'uuid'`, + `delete from artifact where digest = 'digest1001'`, + `delete from scanner_registration where uuid='ruuid'`, + `delete from vulnerability_record where cve_id='2023-4567-12345'`, + `delete from report_vulnerability_record where report_uuid='ruuid'`, + `delete from vulnerability_record where registration_uuid ='uuid2'`, + }) +} + +func (suite *SecurityDaoTestSuite) TestGetSummary() { + s, err := suite.dao.Summary(suite.Context(), "ruuid", 0, nil) + suite.Require().NoError(err) + suite.Equal(int64(50), s.CriticalCnt) + suite.Equal(int64(50), s.HighCnt) + suite.Equal(int64(50), s.MediumCnt) + suite.Equal(int64(20), s.FixableCnt) +} +func (suite *SecurityDaoTestSuite) TestGetMostDangerousArtifact() { + aList, err := suite.dao.DangerousArtifacts(orm.Context(), "ruuid", 0, nil) + suite.Require().NoError(err) + suite.Equal(1, len(aList)) + suite.Equal(int64(50), aList[0].CriticalCnt) + suite.Equal(int64(50), aList[0].HighCnt) + suite.Equal(int64(50), aList[0].MediumCnt) + suite.Equal(int64(0), aList[0].LowCnt) +} + +func (suite *SecurityDaoTestSuite) TestGetScannedArtifactCount() { + count, err := suite.dao.ScannedArtifactsCount(orm.Context(), "ruuid", 0, nil) + suite.Require().NoError(err) + suite.Equal(int64(1), count) +} + +func (suite *SecurityDaoTestSuite) TestGetDangerousCVEs() { + records, err := suite.dao.DangerousCVEs(suite.Context(), `uuid2`, 0, nil) + suite.NoError(err, "Error when fetching most dangerous artifact") + suite.Equal(5, len(records)) +} diff --git a/src/pkg/securityhub/manager.go b/src/pkg/securityhub/manager.go new file mode 100644 index 00000000000..a71ebdf3976 --- /dev/null +++ b/src/pkg/securityhub/manager.go @@ -0,0 +1,69 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package securityhub + +import ( + "context" + + "github.com/goharbor/harbor/src/lib/q" + "github.com/goharbor/harbor/src/pkg/scan/dao/scan" + "github.com/goharbor/harbor/src/pkg/securityhub/dao" + "github.com/goharbor/harbor/src/pkg/securityhub/model" +) + +var ( + // Mgr is the global security manager + Mgr = NewManager() +) + +// Manager is used to manage the security manager. +type Manager interface { + // Summary returns the summary of the scan cve reports. + Summary(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) (*model.Summary, error) + // DangerousArtifacts returns the most dangerous artifact for the given scanner. + DangerousArtifacts(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) ([]*model.DangerousArtifact, error) + // ScannedArtifactsCount return the count of scanned artifacts. + ScannedArtifactsCount(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) (int64, error) + // DangerousCVEs returns the most dangerous CVEs for the given scanner. + DangerousCVEs(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) ([]*scan.VulnerabilityRecord, error) +} + +// NewManager news security manager. +func NewManager() Manager { + return &securityManager{ + dao: dao.New(), + } +} + +// securityManager is a default implementation of security manager. +type securityManager struct { + dao dao.SecurityHubDao +} + +func (s *securityManager) Summary(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) (*model.Summary, error) { + return s.dao.Summary(ctx, scannerUUID, projectID, query) +} + +func (s *securityManager) DangerousArtifacts(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) ([]*model.DangerousArtifact, error) { + return s.dao.DangerousArtifacts(ctx, scannerUUID, projectID, query) +} + +func (s *securityManager) ScannedArtifactsCount(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) (int64, error) { + return s.dao.ScannedArtifactsCount(ctx, scannerUUID, projectID, query) +} + +func (s *securityManager) DangerousCVEs(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) ([]*scan.VulnerabilityRecord, error) { + return s.dao.DangerousCVEs(ctx, scannerUUID, projectID, query) +} diff --git a/src/pkg/securityhub/model/model.go b/src/pkg/securityhub/model/model.go new file mode 100644 index 00000000000..2bb85cb5b27 --- /dev/null +++ b/src/pkg/securityhub/model/model.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 model + +import "github.com/goharbor/harbor/src/pkg/scan/dao/scan" + +// Summary is the summary of scan result +type Summary struct { + CriticalCnt int64 `json:"critical_cnt"` + HighCnt int64 `json:"high_cnt"` + MediumCnt int64 `json:"medium_cnt"` + LowCnt int64 `json:"low_cnt"` + NoneCnt int64 `json:"none_cnt"` + UnknownCnt int64 `json:"unknown_cnt"` + FixableCnt int64 `json:"fixable_cnt"` + ScannedCnt int64 `json:"scanned_cnt"` + NotScanCnt int64 `json:"not_scan_cnt"` + TotalArtifactCnt int64 `json:"total_artifact_cnt"` + DangerousCVEs []*scan.VulnerabilityRecord `json:"dangerous_cves"` + DangerousArtifacts []*DangerousArtifact `json:"dangerous_artifacts"` +} + +// DangerousArtifact define the most dangerous artifact +type DangerousArtifact struct { + Project int64 `json:"project" orm:"column(project)"` + Repository string `json:"repository" orm:"column(repository)"` + Digest string `json:"digest" orm:"column(digest)"` + CriticalCnt int64 `json:"critical_cnt" orm:"column(critical_cnt)"` + HighCnt int64 `json:"high_cnt" orm:"column(high_cnt)"` + MediumCnt int64 `json:"medium_cnt" orm:"column(medium_cnt)"` + LowCnt int64 `json:"low_cnt" orm:"column(low_cnt)"` +} diff --git a/src/server/v2.0/handler/handler.go b/src/server/v2.0/handler/handler.go index 597756d3748..7784b5d9b94 100644 --- a/src/server/v2.0/handler/handler.go +++ b/src/server/v2.0/handler/handler.go @@ -69,6 +69,7 @@ func New() http.Handler { ScanDataExportAPI: newScanDataExportAPI(), JobserviceAPI: newJobServiceAPI(), ScheduleAPI: newScheduleAPI(), + SecurityhubAPI: newSecurityAPI(), }) if err != nil { log.Fatal(err) diff --git a/src/server/v2.0/handler/security.go b/src/server/v2.0/handler/security.go new file mode 100644 index 00000000000..5865874518c --- /dev/null +++ b/src/server/v2.0/handler/security.go @@ -0,0 +1,98 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package handler + +import ( + "context" + + "github.com/go-openapi/runtime/middleware" + + "github.com/goharbor/harbor/src/common/rbac" + "github.com/goharbor/harbor/src/server/v2.0/models" + securityModel "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/securityhub" + + "github.com/goharbor/harbor/src/controller/securityhub" + "github.com/goharbor/harbor/src/pkg/scan/dao/scan" + secHubModel "github.com/goharbor/harbor/src/pkg/securityhub/model" +) + +func newSecurityAPI() *securityAPI { + return &securityAPI{ + controller: securityhub.Ctl, + } +} + +type securityAPI struct { + BaseAPI + controller securityhub.Controller +} + +func (s *securityAPI) GetSecuritySummary(ctx context.Context, + params securityModel.GetSecuritySummaryParams) middleware.Responder { + if err := s.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourceSecurityHub); err != nil { + return s.SendError(ctx, err) + } + summary, err := s.controller.SecuritySummary(ctx, 0, securityhub.WithCVE(*params.WithDangerousCVE), securityhub.WithArtifact(*params.WithDangerousArtifact)) + if err != nil { + return s.SendError(ctx, err) + } + sum := toSecuritySummaryModel(summary) + return securityModel.NewGetSecuritySummaryOK().WithPayload(sum) +} + +func toSecuritySummaryModel(summary *secHubModel.Summary) *models.SecuritySummary { + return &models.SecuritySummary{ + CriticalCnt: summary.CriticalCnt, + HighCnt: summary.HighCnt, + MediumCnt: summary.MediumCnt, + LowCnt: summary.LowCnt, + NoneCnt: summary.NoneCnt, + UnknownCnt: summary.UnknownCnt, + FixableCnt: summary.FixableCnt, + TotalVuls: summary.CriticalCnt + summary.HighCnt + summary.MediumCnt + summary.LowCnt + summary.NoneCnt + summary.UnknownCnt, + TotalArtifact: summary.TotalArtifactCnt, + ScannedCnt: summary.ScannedCnt, + DangerousCves: toDangerousCves(summary.DangerousCVEs), + DangerousArtifacts: toDangerousArtifacts(summary.DangerousArtifacts), + } +} +func toDangerousArtifacts(artifacts []*secHubModel.DangerousArtifact) []*models.DangerousArtifact { + var result []*models.DangerousArtifact + for _, artifact := range artifacts { + result = append(result, &models.DangerousArtifact{ + ProjectID: artifact.Project, + RepositoryName: artifact.Repository, + Digest: artifact.Digest, + CriticalCnt: artifact.CriticalCnt, + HighCnt: artifact.HighCnt, + MediumCnt: artifact.MediumCnt, + }) + } + return result +} + +func toDangerousCves(cves []*scan.VulnerabilityRecord) []*models.DangerousCVE { + var result []*models.DangerousCVE + for _, vul := range cves { + result = append(result, &models.DangerousCVE{ + CVEID: vul.CVEID, + Package: vul.Package, + Version: vul.PackageVersion, + Severity: vul.Severity, + CvssScoreV3: *vul.CVE3Score, + }) + } + return result +} diff --git a/src/testing/controller/controller.go b/src/testing/controller/controller.go index c982d9edb01..2daf186ad2b 100644 --- a/src/testing/controller/controller.go +++ b/src/testing/controller/controller.go @@ -35,3 +35,4 @@ package controller //go:generate mockery --case snake --dir ../../controller/task --name Controller --output ./task --outpkg task //go:generate mockery --case snake --dir ../../controller/task --name ExecutionController --output ./task --outpkg task //go:generate mockery --case snake --dir ../../controller/webhook --name Controller --output ./webhook --outpkg webhook +//go:generate mockery --case snake --dir ../../controller/securityhub --name Controller --output ./securityhub --outpkg securityhub diff --git a/src/testing/controller/securityhub/controller.go b/src/testing/controller/securityhub/controller.go new file mode 100644 index 00000000000..6c2f5cd8390 --- /dev/null +++ b/src/testing/controller/securityhub/controller.go @@ -0,0 +1,65 @@ +// Code generated by mockery v2.22.1. DO NOT EDIT. + +package securityhub + +import ( + context "context" + + model "github.com/goharbor/harbor/src/pkg/securityhub/model" + mock "github.com/stretchr/testify/mock" + + securityhub "github.com/goharbor/harbor/src/controller/securityhub" +) + +// Controller is an autogenerated mock type for the Controller type +type Controller struct { + mock.Mock +} + +// SecuritySummary provides a mock function with given fields: ctx, projectID, options +func (_m *Controller) SecuritySummary(ctx context.Context, projectID int64, options ...securityhub.Option) (*model.Summary, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, projectID) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *model.Summary + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64, ...securityhub.Option) (*model.Summary, error)); ok { + return rf(ctx, projectID, options...) + } + if rf, ok := ret.Get(0).(func(context.Context, int64, ...securityhub.Option) *model.Summary); ok { + r0 = rf(ctx, projectID, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.Summary) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int64, ...securityhub.Option) error); ok { + r1 = rf(ctx, projectID, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewController interface { + mock.TestingT + Cleanup(func()) +} + +// NewController creates a new instance of Controller. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewController(t mockConstructorTestingTNewController) *Controller { + mock := &Controller{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/src/testing/pkg/pkg.go b/src/testing/pkg/pkg.go index 08b92e82972..efef9799723 100644 --- a/src/testing/pkg/pkg.go +++ b/src/testing/pkg/pkg.go @@ -73,3 +73,4 @@ package pkg //go:generate mockery --case snake --dir ../../pkg/jobmonitor --name QueueManager --output ./jobmonitor --outpkg jobmonitor //go:generate mockery --case snake --dir ../../pkg/jobmonitor --name RedisClient --output ./jobmonitor --outpkg jobmonitor //go:generate mockery --case snake --dir ../../pkg/queuestatus --name Manager --output ./queuestatus --outpkg queuestatus +//go:generate mockery --case snake --dir ../../pkg/securityhub --name Manager --output ./securityhub --outpkg securityhub diff --git a/src/testing/pkg/securityhub/manager.go b/src/testing/pkg/securityhub/manager.go new file mode 100644 index 00000000000..cb06f31dfdb --- /dev/null +++ b/src/testing/pkg/securityhub/manager.go @@ -0,0 +1,136 @@ +// Code generated by mockery v2.22.1. DO NOT EDIT. + +package securityhub + +import ( + context "context" + + model "github.com/goharbor/harbor/src/pkg/securityhub/model" + mock "github.com/stretchr/testify/mock" + + q "github.com/goharbor/harbor/src/lib/q" + + scan "github.com/goharbor/harbor/src/pkg/scan/dao/scan" +) + +// Manager is an autogenerated mock type for the Manager type +type Manager struct { + mock.Mock +} + +// DangerousArtifacts provides a mock function with given fields: ctx, scannerUUID, projectID, query +func (_m *Manager) DangerousArtifacts(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) ([]*model.DangerousArtifact, error) { + ret := _m.Called(ctx, scannerUUID, projectID, query) + + var r0 []*model.DangerousArtifact + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, int64, *q.Query) ([]*model.DangerousArtifact, error)); ok { + return rf(ctx, scannerUUID, projectID, query) + } + if rf, ok := ret.Get(0).(func(context.Context, string, int64, *q.Query) []*model.DangerousArtifact); ok { + r0 = rf(ctx, scannerUUID, projectID, query) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.DangerousArtifact) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, int64, *q.Query) error); ok { + r1 = rf(ctx, scannerUUID, projectID, query) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DangerousCVEs provides a mock function with given fields: ctx, scannerUUID, projectID, query +func (_m *Manager) DangerousCVEs(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) ([]*scan.VulnerabilityRecord, error) { + ret := _m.Called(ctx, scannerUUID, projectID, query) + + var r0 []*scan.VulnerabilityRecord + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, int64, *q.Query) ([]*scan.VulnerabilityRecord, error)); ok { + return rf(ctx, scannerUUID, projectID, query) + } + if rf, ok := ret.Get(0).(func(context.Context, string, int64, *q.Query) []*scan.VulnerabilityRecord); ok { + r0 = rf(ctx, scannerUUID, projectID, query) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*scan.VulnerabilityRecord) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, int64, *q.Query) error); ok { + r1 = rf(ctx, scannerUUID, projectID, query) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ScannedArtifactsCount provides a mock function with given fields: ctx, scannerUUID, projectID, query +func (_m *Manager) ScannedArtifactsCount(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) (int64, error) { + ret := _m.Called(ctx, scannerUUID, projectID, query) + + var r0 int64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, int64, *q.Query) (int64, error)); ok { + return rf(ctx, scannerUUID, projectID, query) + } + if rf, ok := ret.Get(0).(func(context.Context, string, int64, *q.Query) int64); ok { + r0 = rf(ctx, scannerUUID, projectID, query) + } else { + r0 = ret.Get(0).(int64) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, int64, *q.Query) error); ok { + r1 = rf(ctx, scannerUUID, projectID, query) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Summary provides a mock function with given fields: ctx, scannerUUID, projectID, query +func (_m *Manager) Summary(ctx context.Context, scannerUUID string, projectID int64, query *q.Query) (*model.Summary, error) { + ret := _m.Called(ctx, scannerUUID, projectID, query) + + var r0 *model.Summary + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, int64, *q.Query) (*model.Summary, error)); ok { + return rf(ctx, scannerUUID, projectID, query) + } + if rf, ok := ret.Get(0).(func(context.Context, string, int64, *q.Query) *model.Summary); ok { + r0 = rf(ctx, scannerUUID, projectID, query) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.Summary) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, int64, *q.Query) error); ok { + r1 = rf(ctx, scannerUUID, projectID, query) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewManager interface { + mock.TestingT + Cleanup(func()) +} + +// NewManager creates a new instance of Manager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewManager(t mockConstructorTestingTNewManager) *Manager { + mock := &Manager{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +}