From ee65223ee7fe444b22110874ac326eb7713f9b02 Mon Sep 17 00:00:00 2001 From: Ali <83188384+testA113@users.noreply.github.com> Date: Wed, 14 May 2025 17:35:05 +1200 Subject: [PATCH 01/68] chore: bump version to 2.30.0 (#735) --- api/datastore/test_data/output_24_to_latest.json | 4 ++-- api/http/handler/handler.go | 2 +- api/portainer.go | 2 +- package.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/datastore/test_data/output_24_to_latest.json b/api/datastore/test_data/output_24_to_latest.json index 21f3f8630..b0078c326 100644 --- a/api/datastore/test_data/output_24_to_latest.json +++ b/api/datastore/test_data/output_24_to_latest.json @@ -610,7 +610,7 @@ "RequiredPasswordLength": 12 }, "KubeconfigExpiry": "0", - "KubectlShellImage": "portainer/kubectl-shell:2.29.0", + "KubectlShellImage": "portainer/kubectl-shell:2.30.0", "LDAPSettings": { "AnonymousMode": true, "AutoCreateUsers": true, @@ -943,7 +943,7 @@ } ], "version": { - "VERSION": "{\"SchemaVersion\":\"2.29.0\",\"MigratorCount\":0,\"Edition\":1,\"InstanceID\":\"463d5c47-0ea5-4aca-85b1-405ceefee254\"}" + "VERSION": "{\"SchemaVersion\":\"2.30.0\",\"MigratorCount\":0,\"Edition\":1,\"InstanceID\":\"463d5c47-0ea5-4aca-85b1-405ceefee254\"}" }, "webhooks": null } \ No newline at end of file diff --git a/api/http/handler/handler.go b/api/http/handler/handler.go index 42c555905..72c7f7669 100644 --- a/api/http/handler/handler.go +++ b/api/http/handler/handler.go @@ -81,7 +81,7 @@ type Handler struct { } // @title PortainerCE API -// @version 2.29.0 +// @version 2.30.0 // @description.markdown api-description.md // @termsOfService diff --git a/api/portainer.go b/api/portainer.go index 1db7142d5..935def1f1 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -1638,7 +1638,7 @@ type ( const ( // APIVersion is the version number of the Portainer API - APIVersion = "2.29.0" + APIVersion = "2.30.0" // Support annotation for the API version ("STS" for Short-Term Support or "LTS" for Long-Term Support) APIVersionSupport = "STS" // Edition is what this edition of Portainer is called diff --git a/package.json b/package.json index 7d1c72b83..28859ab3e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Portainer.io", "name": "portainer", "homepage": "http://portainer.io", - "version": "2.29.0", + "version": "2.30.0", "repository": { "type": "git", "url": "git@github.com:portainer/portainer.git" From f60e13adbb1eeba62412963ae43ef1ec84124e20 Mon Sep 17 00:00:00 2001 From: Ali <83188384+testA113@users.noreply.github.com> Date: Wed, 14 May 2025 17:35:05 +1200 Subject: [PATCH 02/68] chore: bump version to 2.30.0 (#735) --- api/datastore/test_data/output_24_to_latest.json | 4 ++-- api/http/handler/handler.go | 2 +- api/portainer.go | 2 +- package.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/datastore/test_data/output_24_to_latest.json b/api/datastore/test_data/output_24_to_latest.json index 21f3f8630..b0078c326 100644 --- a/api/datastore/test_data/output_24_to_latest.json +++ b/api/datastore/test_data/output_24_to_latest.json @@ -610,7 +610,7 @@ "RequiredPasswordLength": 12 }, "KubeconfigExpiry": "0", - "KubectlShellImage": "portainer/kubectl-shell:2.29.0", + "KubectlShellImage": "portainer/kubectl-shell:2.30.0", "LDAPSettings": { "AnonymousMode": true, "AutoCreateUsers": true, @@ -943,7 +943,7 @@ } ], "version": { - "VERSION": "{\"SchemaVersion\":\"2.29.0\",\"MigratorCount\":0,\"Edition\":1,\"InstanceID\":\"463d5c47-0ea5-4aca-85b1-405ceefee254\"}" + "VERSION": "{\"SchemaVersion\":\"2.30.0\",\"MigratorCount\":0,\"Edition\":1,\"InstanceID\":\"463d5c47-0ea5-4aca-85b1-405ceefee254\"}" }, "webhooks": null } \ No newline at end of file diff --git a/api/http/handler/handler.go b/api/http/handler/handler.go index 42c555905..72c7f7669 100644 --- a/api/http/handler/handler.go +++ b/api/http/handler/handler.go @@ -81,7 +81,7 @@ type Handler struct { } // @title PortainerCE API -// @version 2.29.0 +// @version 2.30.0 // @description.markdown api-description.md // @termsOfService diff --git a/api/portainer.go b/api/portainer.go index 1db7142d5..935def1f1 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -1638,7 +1638,7 @@ type ( const ( // APIVersion is the version number of the Portainer API - APIVersion = "2.29.0" + APIVersion = "2.30.0" // Support annotation for the API version ("STS" for Short-Term Support or "LTS" for Long-Term Support) APIVersionSupport = "STS" // Edition is what this edition of Portainer is called diff --git a/package.json b/package.json index 7d1c72b83..28859ab3e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Portainer.io", "name": "portainer", "homepage": "http://portainer.io", - "version": "2.29.0", + "version": "2.30.0", "repository": { "type": "git", "url": "git@github.com:portainer/portainer.git" From 44daab04ac5c2d1469228ea5ce4a77fc3e7b91d6 Mon Sep 17 00:00:00 2001 From: Oscar Zhou <100548325+oscarzhou-portainer@users.noreply.github.com> Date: Thu, 15 May 2025 09:54:35 +1200 Subject: [PATCH 03/68] fix(libclient): option to disable external http request [BE-11696] (#719) --- api/http/handler/motd/motd.go | 8 +++++++ api/http/handler/system/version.go | 7 +++++- .../templates/utils_fetch_templates.go | 11 +++++++++- api/portainer.go | 5 +++++ pkg/libhttp/client/client.go | 22 +++++++++++++++++++ 5 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 pkg/libhttp/client/client.go diff --git a/api/http/handler/motd/motd.go b/api/http/handler/motd/motd.go index dd2112c16..4117de830 100644 --- a/api/http/handler/motd/motd.go +++ b/api/http/handler/motd/motd.go @@ -7,7 +7,9 @@ import ( portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/http/client" "github.com/portainer/portainer/pkg/libcrypto" + libclient "github.com/portainer/portainer/pkg/libhttp/client" "github.com/portainer/portainer/pkg/libhttp/response" + "github.com/rs/zerolog/log" "github.com/segmentio/encoding/json" ) @@ -37,6 +39,12 @@ type motdData struct { // @success 200 {object} motdResponse // @router /motd [get] func (handler *Handler) motd(w http.ResponseWriter, r *http.Request) { + if err := libclient.ExternalRequestDisabled(portainer.MessageOfTheDayURL); err != nil { + log.Debug().Err(err).Msg("External request disabled: MOTD") + response.JSON(w, &motdResponse{Message: ""}) + return + } + motd, err := client.Get(portainer.MessageOfTheDayURL, 0) if err != nil { response.JSON(w, &motdResponse{Message: ""}) diff --git a/api/http/handler/system/version.go b/api/http/handler/system/version.go index 9d80b88a9..52af5879c 100644 --- a/api/http/handler/system/version.go +++ b/api/http/handler/system/version.go @@ -7,6 +7,7 @@ import ( "github.com/portainer/portainer/api/http/client" "github.com/portainer/portainer/api/http/security" "github.com/portainer/portainer/pkg/build" + libclient "github.com/portainer/portainer/pkg/libhttp/client" httperror "github.com/portainer/portainer/pkg/libhttp/error" "github.com/portainer/portainer/pkg/libhttp/response" @@ -69,10 +70,14 @@ func (handler *Handler) version(w http.ResponseWriter, r *http.Request) *httperr } func GetLatestVersion() string { + if err := libclient.ExternalRequestDisabled(portainer.VersionCheckURL); err != nil { + log.Debug().Err(err).Msg("External request disabled: Version check") + return "" + } + motd, err := client.Get(portainer.VersionCheckURL, 5) if err != nil { log.Debug().Err(err).Msg("couldn't fetch latest Portainer release version") - return "" } diff --git a/api/http/handler/templates/utils_fetch_templates.go b/api/http/handler/templates/utils_fetch_templates.go index 6feb9edeb..73f9bad56 100644 --- a/api/http/handler/templates/utils_fetch_templates.go +++ b/api/http/handler/templates/utils_fetch_templates.go @@ -4,7 +4,9 @@ import ( "net/http" portainer "github.com/portainer/portainer/api" + libclient "github.com/portainer/portainer/pkg/libhttp/client" httperror "github.com/portainer/portainer/pkg/libhttp/error" + "github.com/rs/zerolog/log" "github.com/segmentio/encoding/json" ) @@ -24,13 +26,20 @@ func (handler *Handler) fetchTemplates() (*listResponse, *httperror.HandlerError templatesURL = portainer.DefaultTemplatesURL } + var body *listResponse + if err := libclient.ExternalRequestDisabled(templatesURL); err != nil { + if templatesURL == portainer.DefaultTemplatesURL { + log.Debug().Err(err).Msg("External request disabled: Default templates") + return body, nil + } + } + resp, err := http.Get(templatesURL) if err != nil { return nil, httperror.InternalServerError("Unable to retrieve templates via the network", err) } defer resp.Body.Close() - var body *listResponse err = json.NewDecoder(resp.Body).Decode(&body) if err != nil { return nil, httperror.InternalServerError("Unable to parse template file", err) diff --git a/api/portainer.go b/api/portainer.go index 935def1f1..60e5c7a1f 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -1692,6 +1692,11 @@ const ( KubectlShellImageEnvVar = "KUBECTL_SHELL_IMAGE" // PullLimitCheckDisabledEnvVar is the environment variable used to disable the pull limit check PullLimitCheckDisabledEnvVar = "PULL_LIMIT_CHECK_DISABLED" + // LicenseServerBaseURL represents the base URL of the API used to validate + // an extension license. + LicenseServerBaseURL = "https://api.portainer.io" + // URL to validate licenses along with system metadata. + LicenseCheckInURL = LicenseServerBaseURL + "/licenses/checkin" ) // List of supported features diff --git a/pkg/libhttp/client/client.go b/pkg/libhttp/client/client.go new file mode 100644 index 000000000..1e1f094a1 --- /dev/null +++ b/pkg/libhttp/client/client.go @@ -0,0 +1,22 @@ +package client + +import ( + "errors" + + "github.com/portainer/portainer/pkg/featureflags" +) + +var ( + ErrExternalRequestsBlocked = errors.New("external requests are blocked by feature flag") +) + +// DisableExternalRequest is the feature flag name for blocking outbound requests +const DisableExternalRequests = "disable-external-requests" + +func ExternalRequestDisabled(url string) error { + if featureflags.IsEnabled(DisableExternalRequests) { + return ErrExternalRequestsBlocked + } + + return nil +} From b540709e03781c689da17675afc56728685d03cc Mon Sep 17 00:00:00 2001 From: James Carppe <85850129+jamescarppe@users.noreply.github.com> Date: Thu, 15 May 2025 01:09:28 +0100 Subject: [PATCH 04/68] Update bug report template for 2.30.0 (#737) --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index b83e3792a..0c6416fef 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -94,6 +94,7 @@ body: description: We only provide support for current versions of Portainer as per the lifecycle policy linked above. If you are on an older version of Portainer we recommend [updating first](https://docs.portainer.io/start/upgrade) in case your bug has already been fixed. multiple: false options: + - '2.30.0' - '2.29.2' - '2.29.1' - '2.29.0' From 799325d9f8d51aef4fb11ba10ffdd39c00a4c4d8 Mon Sep 17 00:00:00 2001 From: James Carppe <85850129+jamescarppe@users.noreply.github.com> Date: Tue, 20 May 2025 03:40:43 +0100 Subject: [PATCH 05/68] Update bug report template for 2.30.1 (#749) --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 0c6416fef..4324bf5a8 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -94,6 +94,7 @@ body: description: We only provide support for current versions of Portainer as per the lifecycle policy linked above. If you are on an older version of Portainer we recommend [updating first](https://docs.portainer.io/start/upgrade) in case your bug has already been fixed. multiple: false options: + - '2.30.1' - '2.30.0' - '2.29.2' - '2.29.1' From 1bc91d0c7c29cbb69183b6b038a5f2f171cd2e49 Mon Sep 17 00:00:00 2001 From: Viktor Pettersson Date: Tue, 20 May 2025 08:28:40 +0200 Subject: [PATCH 06/68] fix(edge-update): set edge stack status to EdgeStackStatusError to avoid redeployment of portainer-updater [BE-11855] (#714) --- pkg/libstack/compose/composeplugin.go | 5 +++-- pkg/libstack/libstack.go | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/libstack/compose/composeplugin.go b/pkg/libstack/compose/composeplugin.go index 01a5134ea..394de356e 100644 --- a/pkg/libstack/compose/composeplugin.go +++ b/pkg/libstack/compose/composeplugin.go @@ -270,8 +270,9 @@ func (c *ComposeDeployer) GetExistingEdgeStacks(ctx context.Context) ([]libstack } m[id] = libstack.EdgeStack{ - ID: id, - Name: cs.Labels[api.ProjectLabel], + ID: id, + Name: cs.Labels[api.ProjectLabel], + ExitCode: cs.ExitCode, } } } diff --git a/pkg/libstack/libstack.go b/pkg/libstack/libstack.go index 4e7b184ea..e4125d3ca 100644 --- a/pkg/libstack/libstack.go +++ b/pkg/libstack/libstack.go @@ -88,6 +88,7 @@ type RemoveOptions struct { } type EdgeStack struct { - ID int - Name string + ID int + Name string + ExitCode int } From 45471ce86dbe3342c57fb99274e0a2e3b1c1f260 Mon Sep 17 00:00:00 2001 From: Devon Steenberg Date: Thu, 22 May 2025 14:27:14 +1200 Subject: [PATCH 07/68] fix(docker): check len of device capabilities [BE-11898] (#750) --- .../containers/edit/containerController.js | 2 +- pkg/snapshot/docker.go | 20 +++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/app/docker/views/containers/edit/containerController.js b/app/docker/views/containers/edit/containerController.js index 78f153e27..f1ff8f27c 100644 --- a/app/docker/views/containers/edit/containerController.js +++ b/app/docker/views/containers/edit/containerController.js @@ -54,7 +54,7 @@ angular.module('portainer.docker').controller('ContainerController', [ $scope.computeDockerGPUCommand = () => { const gpuOptions = _.find($scope.container.HostConfig.DeviceRequests, function (o) { - return o.Driver === 'nvidia' || o.Capabilities[0][0] === 'gpu'; + return o.Driver === 'nvidia' || (o.Capabilities && o.Capabilities.length > 0 && o.Capabilities[0] > 0 && o.Capabilities[0][0] === 'gpu'); }); if (!gpuOptions) { return 'No GPU config found'; diff --git a/pkg/snapshot/docker.go b/pkg/snapshot/docker.go index 3ea4dc0d5..810dbeaa9 100644 --- a/pkg/snapshot/docker.go +++ b/pkg/snapshot/docker.go @@ -14,8 +14,7 @@ import ( networkingutils "github.com/portainer/portainer/pkg/networking" "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - _container "github.com/docker/docker/api/types/container" + dockercontainer "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/volume" "github.com/docker/docker/client" @@ -128,7 +127,7 @@ func dockerSnapshotSwarmServices(snapshot *portainer.DockerSnapshot, cli *client } func dockerSnapshotContainers(snapshot *portainer.DockerSnapshot, cli *client.Client) error { - containers, err := cli.ContainerList(context.Background(), container.ListOptions{All: true}) + containers, err := cli.ContainerList(context.Background(), dockercontainer.ListOptions{All: true}) if err != nil { return err } @@ -170,11 +169,16 @@ func dockerSnapshotContainers(snapshot *portainer.DockerSnapshot, cli *client.Cl containerEnvs[container.ID] = response.Config.Env - var gpuOptions *_container.DeviceRequest + var gpuOptions *dockercontainer.DeviceRequest - for _, deviceRequest := range response.HostConfig.Resources.DeviceRequests { - if deviceRequest.Driver == "nvidia" || deviceRequest.Capabilities[0][0] == "gpu" { - gpuOptions = &deviceRequest + if response.HostConfig != nil { + for _, deviceRequest := range response.HostConfig.DeviceRequests { + if deviceRequest.Driver == "nvidia" || + (len(deviceRequest.Capabilities) > 0 && + len(deviceRequest.Capabilities[0]) > 0 && + deviceRequest.Capabilities[0][0] == "gpu") { + gpuOptions = &deviceRequest + } } } @@ -298,7 +302,7 @@ func dockerSnapshotContainerErrorLogs(snapshot *portainer.DockerSnapshot, cli *c return nil } - rd, err := cli.ContainerLogs(context.Background(), containerId, container.LogsOptions{ + rd, err := cli.ContainerLogs(context.Background(), containerId, dockercontainer.LogsOptions{ ShowStdout: false, ShowStderr: true, Tail: "5", From b96328e098c37c8c04647d430c510e7e5fb18298 Mon Sep 17 00:00:00 2001 From: Malcolm Lockyer Date: Fri, 23 May 2025 12:42:45 +1200 Subject: [PATCH 08/68] fix(async-perf): In async poll snapshot handling, reduce redundant json marshal [be-11861] (#726) --- api/dataservices/snapshot/snapshot.go | 17 +++++++++++++++++ api/dataservices/snapshot/tx.go | 16 ++++++++++++++++ api/portainer.go | 7 +++++++ 3 files changed, 40 insertions(+) diff --git a/api/dataservices/snapshot/snapshot.go b/api/dataservices/snapshot/snapshot.go index 155077677..c0066317d 100644 --- a/api/dataservices/snapshot/snapshot.go +++ b/api/dataservices/snapshot/snapshot.go @@ -51,3 +51,20 @@ func (service *Service) ReadWithoutSnapshotRaw(ID portainer.EndpointID) (*portai return snapshot, err } + +func (service *Service) ReadRawMessage(ID portainer.EndpointID) (*portainer.SnapshotRawMessage, error) { + var snapshot *portainer.SnapshotRawMessage + + err := service.Connection.ViewTx(func(tx portainer.Transaction) error { + var err error + snapshot, err = service.Tx(tx).ReadRawMessage(ID) + + return err + }) + + return snapshot, err +} + +func (service *Service) CreateRawMessage(snapshot *portainer.SnapshotRawMessage) error { + return service.Connection.CreateObjectWithId(BucketName, int(snapshot.EndpointID), snapshot) +} diff --git a/api/dataservices/snapshot/tx.go b/api/dataservices/snapshot/tx.go index 8a8dcc1c2..45d1df9fc 100644 --- a/api/dataservices/snapshot/tx.go +++ b/api/dataservices/snapshot/tx.go @@ -35,3 +35,19 @@ func (service ServiceTx) ReadWithoutSnapshotRaw(ID portainer.EndpointID) (*porta return &snapshot.Snapshot, nil } + +func (service ServiceTx) ReadRawMessage(ID portainer.EndpointID) (*portainer.SnapshotRawMessage, error) { + var snapshot = portainer.SnapshotRawMessage{} + + identifier := service.Connection.ConvertToKey(int(ID)) + + if err := service.Tx.GetObject(service.Bucket, identifier, &snapshot); err != nil { + return nil, err + } + + return &snapshot, nil +} + +func (service ServiceTx) CreateRawMessage(snapshot *portainer.SnapshotRawMessage) error { + return service.Tx.CreateObjectWithId(BucketName, int(snapshot.EndpointID), snapshot) +} diff --git a/api/portainer.go b/api/portainer.go index 60e5c7a1f..f61bb1af2 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -13,6 +13,7 @@ import ( gittypes "github.com/portainer/portainer/api/git/types" models "github.com/portainer/portainer/api/http/models/kubernetes" "github.com/portainer/portainer/pkg/featureflags" + "github.com/segmentio/encoding/json" "golang.org/x/oauth2" corev1 "k8s.io/api/core/v1" @@ -1374,6 +1375,12 @@ type ( Kubernetes *KubernetesSnapshot `json:"Kubernetes"` } + SnapshotRawMessage struct { + EndpointID EndpointID `json:"EndpointId"` + Docker json.RawMessage `json:"Docker"` + Kubernetes json.RawMessage `json:"Kubernetes"` + } + // CLIService represents a service for managing CLI CLIService interface { ParseFlags(version string) (*CLIFlags, error) From a80b185e1050cb811d722132db18f9df5409396a Mon Sep 17 00:00:00 2001 From: Cara Ryan Date: Mon, 26 May 2025 14:10:38 +1200 Subject: [PATCH 09/68] feat(helm): filter on chart versions at API level [R8S-324] (#747) --- api/http/handler/helm/helm_repo_search.go | 8 +- .../ChartActions/UpgradeButton.test.tsx | 10 +++ .../ChartActions/UpgradeButton.tsx | 87 +++++++++++++------ .../queries/useHelmRepositories.ts | 16 ++-- pkg/libhelm/options/search_repo_options.go | 6 +- pkg/libhelm/sdk/search_repo.go | 77 ++++++++++++---- 6 files changed, 153 insertions(+), 51 deletions(-) diff --git a/api/http/handler/helm/helm_repo_search.go b/api/http/handler/helm/helm_repo_search.go index aab9c523d..976558b9b 100644 --- a/api/http/handler/helm/helm_repo_search.go +++ b/api/http/handler/helm/helm_repo_search.go @@ -7,6 +7,7 @@ import ( "github.com/portainer/portainer/pkg/libhelm/options" httperror "github.com/portainer/portainer/pkg/libhttp/error" + "github.com/portainer/portainer/pkg/libhttp/request" "github.com/pkg/errors" ) @@ -32,13 +33,18 @@ func (handler *Handler) helmRepoSearch(w http.ResponseWriter, r *http.Request) * return httperror.BadRequest("Bad request", errors.New("missing `repo` query parameter")) } + chart, _ := request.RetrieveQueryParameter(r, "chart", false) + useCache, _ := request.RetrieveBooleanQueryParameter(r, "useCache", false) + _, err := url.ParseRequestURI(repo) if err != nil { return httperror.BadRequest("Bad request", errors.Wrap(err, fmt.Sprintf("provided URL %q is not valid", repo))) } searchOpts := options.SearchRepoOptions{ - Repo: repo, + Repo: repo, + Chart: chart, + UseCache: useCache, } result, err := handler.helmPackageManager.SearchRepo(searchOpts) diff --git a/app/react/kubernetes/helm/HelmApplicationView/ChartActions/UpgradeButton.test.tsx b/app/react/kubernetes/helm/HelmApplicationView/ChartActions/UpgradeButton.test.tsx index 4bea0bf47..b45a54e79 100644 --- a/app/react/kubernetes/helm/HelmApplicationView/ChartActions/UpgradeButton.test.tsx +++ b/app/react/kubernetes/helm/HelmApplicationView/ChartActions/UpgradeButton.test.tsx @@ -33,6 +33,8 @@ vi.mock('../queries/useHelmRepositories', () => ({ ], isInitialLoading: false, isError: false, + isFetching: false, + refetch: vi.fn(() => Promise.resolve([])), })), useHelmRepositories: vi.fn(() => ({ data: ['repo1', 'repo2'], @@ -81,6 +83,8 @@ describe('UpgradeButton', () => { data, isInitialLoading: false, isError: false, + isFetching: false, + refetch: vi.fn(() => Promise.resolve([])), }); renderButton(); @@ -94,6 +98,8 @@ describe('UpgradeButton', () => { data: [], isInitialLoading: true, isError: false, + isFetching: false, + refetch: vi.fn(() => Promise.resolve([])), }); renderButton(); @@ -109,6 +115,8 @@ describe('UpgradeButton', () => { data, isInitialLoading: false, isError: false, + isFetching: false, + refetch: vi.fn(() => Promise.resolve([])), }); renderButton(); @@ -139,6 +147,8 @@ describe('UpgradeButton', () => { ], isInitialLoading: false, isError: false, + isFetching: false, + refetch: vi.fn(() => Promise.resolve([])), }); renderButton({ release: mockRelease }); diff --git a/app/react/kubernetes/helm/HelmApplicationView/ChartActions/UpgradeButton.tsx b/app/react/kubernetes/helm/HelmApplicationView/ChartActions/UpgradeButton.tsx index 4c385c388..7376877e4 100644 --- a/app/react/kubernetes/helm/HelmApplicationView/ChartActions/UpgradeButton.tsx +++ b/app/react/kubernetes/helm/HelmApplicationView/ChartActions/UpgradeButton.tsx @@ -1,5 +1,6 @@ import { ArrowUp } from 'lucide-react'; import { useRouter } from '@uirouter/react'; +import { useState } from 'react'; import { EnvironmentId } from '@/react/portainer/environments/types'; import { notifySuccess } from '@/portainer/services/notifications'; @@ -41,16 +42,19 @@ export function UpgradeButton({ const updateHelmReleaseMutation = useUpdateHelmReleaseMutation(environmentId); const repositoriesQuery = useHelmRepositories(); + const [useCache, setUseCache] = useState(true); const helmRepoVersionsQuery = useHelmRepoVersions( release?.chart.metadata?.name || '', 60 * 60 * 1000, // 1 hour - repositoriesQuery.data + repositoriesQuery.data, + useCache ); const versions = helmRepoVersionsQuery.data; // Combined loading state const isInitialLoading = repositoriesQuery.isInitialLoading || + helmRepoVersionsQuery.isFetching || helmRepoVersionsQuery.isInitialLoading; const isError = repositoriesQuery.isError || helmRepoVersionsQuery.isError; @@ -58,9 +62,10 @@ export function UpgradeButton({ select: (data) => data.chart.metadata?.version, }); const latestVersionAvailable = versions[0]?.Version ?? ''; - const isNewVersionAvailable = + const isNewVersionAvailable = Boolean( latestVersion?.data && - semverCompare(latestVersionAvailable, latestVersion?.data) === 1; + semverCompare(latestVersionAvailable, latestVersion?.data) === 1 + ); const editableHelmRelease: UpdateHelmReleasePayload = { name: releaseName, @@ -70,6 +75,14 @@ export function UpgradeButton({ version: release?.chart.metadata?.version, }; + function handleRefreshVersions() { + if (useCache === false) { + helmRepoVersionsQuery.refetch(); + } else { + setUseCache(false); + } + } + return (
)} - {versions.length === 0 && !isInitialLoading && !isError && ( + {!isInitialLoading && !isError && ( - No versions available - - Portainer is unable to find any versions for this chart in the - repositories saved. Try adding a new repository which contains - the chart in the{' '} - - Helm repositories settings - -
- } - /> - - )} - {isNewVersionAvailable && ( - - New version available ({latestVersionAvailable}) + {getStatusMessage( + versions.length === 0, + latestVersionAvailable, + isNewVersionAvailable + )} + {versions.length === 0 && ( + + Portainer is unable to find any versions for this chart in the + repositories saved. Try adding a new repository which contains + the chart in the{' '} + + Helm repositories settings + + + } + /> + )} + )} @@ -164,4 +185,18 @@ export function UpgradeButton({ }, }); } + + function getStatusMessage( + hasNoAvailableVersions: boolean, + latestVersionAvailable: string, + isNewVersionAvailable: boolean + ): string { + if (hasNoAvailableVersions) { + return 'No versions available '; + } + if (isNewVersionAvailable) { + return `New version available (${latestVersionAvailable}) `; + } + return ''; + } } diff --git a/app/react/kubernetes/helm/HelmApplicationView/queries/useHelmRepositories.ts b/app/react/kubernetes/helm/HelmApplicationView/queries/useHelmRepositories.ts index 733b79dea..bf11eadb1 100644 --- a/app/react/kubernetes/helm/HelmApplicationView/queries/useHelmRepositories.ts +++ b/app/react/kubernetes/helm/HelmApplicationView/queries/useHelmRepositories.ts @@ -45,20 +45,21 @@ export function useHelmRepositories() { export function useHelmRepoVersions( chart: string, staleTime: number, - repositories: string[] = [] + repositories: string[] = [], + useCache: boolean = true ) { // Fetch versions from each repository in parallel as separate queries const versionQueries = useQueries({ queries: useMemo( () => repositories.map((repo) => ({ - queryKey: ['helm', 'repositories', chart, repo], - queryFn: () => getSearchHelmRepo(repo, chart), + queryKey: ['helm', 'repositories', chart, repo, useCache], + queryFn: () => getSearchHelmRepo(repo, chart, useCache), enabled: !!chart && repositories.length > 0, staleTime, ...withGlobalError(`Unable to retrieve versions from ${repo}`), })), - [repositories, chart, staleTime] + [repositories, chart, staleTime, useCache] ), }); @@ -72,6 +73,8 @@ export function useHelmRepoVersions( data: allVersions, isInitialLoading: versionQueries.some((q) => q.isLoading), isError: versionQueries.some((q) => q.isError), + isFetching: versionQueries.some((q) => q.isFetching), + refetch: () => Promise.all(versionQueries.map((q) => q.refetch())), }; } @@ -80,11 +83,12 @@ export function useHelmRepoVersions( */ async function getSearchHelmRepo( repo: string, - chart: string + chart: string, + useCache: boolean = true ): Promise { try { const { data } = await axios.get(`templates/helm`, { - params: { repo, chart }, + params: { repo, chart, useCache }, }); const versions = data.entries[chart]; return ( diff --git a/pkg/libhelm/options/search_repo_options.go b/pkg/libhelm/options/search_repo_options.go index 73acc5709..0b35c0bbd 100644 --- a/pkg/libhelm/options/search_repo_options.go +++ b/pkg/libhelm/options/search_repo_options.go @@ -3,6 +3,8 @@ package options import "net/http" type SearchRepoOptions struct { - Repo string `example:"https://charts.gitlab.io/"` - Client *http.Client `example:"&http.Client{Timeout: time.Second * 10}"` + Repo string `example:"https://charts.gitlab.io/"` + Client *http.Client `example:"&http.Client{Timeout: time.Second * 10}"` + Chart string `example:"my-chart"` + UseCache bool `example:"false"` } diff --git a/pkg/libhelm/sdk/search_repo.go b/pkg/libhelm/sdk/search_repo.go index 90dd98529..63015330c 100644 --- a/pkg/libhelm/sdk/search_repo.go +++ b/pkg/libhelm/sdk/search_repo.go @@ -4,6 +4,8 @@ import ( "net/url" "os" "path/filepath" + "sync" + "time" "github.com/pkg/errors" "github.com/portainer/portainer/pkg/libhelm/options" @@ -25,6 +27,17 @@ type RepoIndex struct { Generated string `json:"generated"` } +type RepoIndexCache struct { + Index *repo.IndexFile + Timestamp time.Time +} + +var ( + indexCache = make(map[string]RepoIndexCache) + cacheMutex sync.RWMutex + cacheDuration = 60 * time.Minute +) + // SearchRepo downloads the `index.yaml` file for specified repo, parses it and returns JSON to caller. func (hspm *HelmSDKPackageManager) SearchRepo(searchRepoOpts options.SearchRepoOptions) ([]byte, error) { // Validate input options @@ -53,6 +66,18 @@ func (hspm *HelmSDKPackageManager) SearchRepo(searchRepoOpts options.SearchRepoO return nil, err } + // Check cache first + if searchRepoOpts.UseCache { + cacheMutex.RLock() + if cached, exists := indexCache[repoURL.String()]; exists { + if time.Since(cached.Timestamp) < cacheDuration { + cacheMutex.RUnlock() + return convertAndMarshalIndex(cached.Index, searchRepoOpts.Chart) + } + } + cacheMutex.RUnlock() + } + // Set up Helm CLI environment repoSettings := cli.New() @@ -92,23 +117,21 @@ func (hspm *HelmSDKPackageManager) SearchRepo(searchRepoOpts options.SearchRepoO return nil, err } - // Convert the index file to our response format - result, err := convertIndexToResponse(indexFile) - if err != nil { - log.Error(). - Str("context", "HelmClient"). - Err(err). - Msg("Failed to convert index to response format") - return nil, errors.Wrap(err, "failed to convert index to response format") + // Update cache and remove old entries + cacheMutex.Lock() + indexCache[searchRepoOpts.Repo] = RepoIndexCache{ + Index: indexFile, + Timestamp: time.Now(), + } + for key, index := range indexCache { + if time.Since(index.Timestamp) > cacheDuration { + delete(indexCache, key) + } } - log.Debug(). - Str("context", "HelmClient"). - Str("repo", searchRepoOpts.Repo). - Int("entries_count", len(indexFile.Entries)). - Msg("Successfully searched repository") + cacheMutex.Unlock() - return json.Marshal(result) + return convertAndMarshalIndex(indexFile, searchRepoOpts.Chart) } // validateSearchRepoOptions validates the required search repository options. @@ -216,7 +239,7 @@ func loadIndexFile(indexPath string) (*repo.IndexFile, error) { } // convertIndexToResponse converts the Helm index file to our response format. -func convertIndexToResponse(indexFile *repo.IndexFile) (RepoIndex, error) { +func convertIndexToResponse(indexFile *repo.IndexFile, chartName string) (RepoIndex, error) { result := RepoIndex{ APIVersion: indexFile.APIVersion, Entries: make(map[string][]ChartInfo), @@ -225,7 +248,9 @@ func convertIndexToResponse(indexFile *repo.IndexFile) (RepoIndex, error) { // Convert Helm SDK types to our response types for name, charts := range indexFile.Entries { - result.Entries[name] = convertChartsToChartInfo(charts) + if chartName == "" || name == chartName { + result.Entries[name] = convertChartsToChartInfo(charts) + } } return result, nil @@ -349,3 +374,23 @@ func ensureHelmDirectoriesExist(settings *cli.EnvSettings) error { return nil } + +func convertAndMarshalIndex(indexFile *repo.IndexFile, chartName string) ([]byte, error) { + // Convert the index file to our response format + result, err := convertIndexToResponse(indexFile, chartName) + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Err(err). + Msg("Failed to convert index to response format") + return nil, errors.Wrap(err, "failed to convert index to response format") + } + + log.Debug(). + Str("context", "HelmClient"). + Str("repo", chartName). + Int("entries_count", len(indexFile.Entries)). + Msg("Successfully searched repository") + + return json.Marshal(result) +} From 32ef20827825f895a92f7340ff05d68a9a48d8e2 Mon Sep 17 00:00:00 2001 From: Cara Ryan Date: Mon, 26 May 2025 16:58:53 +1200 Subject: [PATCH 10/68] Revert "feat(helm): filter on chart versions at API level [R8S-324]" (#753) --- api/http/handler/helm/helm_repo_search.go | 8 +- .../ChartActions/UpgradeButton.test.tsx | 10 --- .../ChartActions/UpgradeButton.tsx | 87 ++++++------------- .../queries/useHelmRepositories.ts | 16 ++-- pkg/libhelm/options/search_repo_options.go | 6 +- pkg/libhelm/sdk/search_repo.go | 77 ++++------------ 6 files changed, 51 insertions(+), 153 deletions(-) diff --git a/api/http/handler/helm/helm_repo_search.go b/api/http/handler/helm/helm_repo_search.go index 976558b9b..aab9c523d 100644 --- a/api/http/handler/helm/helm_repo_search.go +++ b/api/http/handler/helm/helm_repo_search.go @@ -7,7 +7,6 @@ import ( "github.com/portainer/portainer/pkg/libhelm/options" httperror "github.com/portainer/portainer/pkg/libhttp/error" - "github.com/portainer/portainer/pkg/libhttp/request" "github.com/pkg/errors" ) @@ -33,18 +32,13 @@ func (handler *Handler) helmRepoSearch(w http.ResponseWriter, r *http.Request) * return httperror.BadRequest("Bad request", errors.New("missing `repo` query parameter")) } - chart, _ := request.RetrieveQueryParameter(r, "chart", false) - useCache, _ := request.RetrieveBooleanQueryParameter(r, "useCache", false) - _, err := url.ParseRequestURI(repo) if err != nil { return httperror.BadRequest("Bad request", errors.Wrap(err, fmt.Sprintf("provided URL %q is not valid", repo))) } searchOpts := options.SearchRepoOptions{ - Repo: repo, - Chart: chart, - UseCache: useCache, + Repo: repo, } result, err := handler.helmPackageManager.SearchRepo(searchOpts) diff --git a/app/react/kubernetes/helm/HelmApplicationView/ChartActions/UpgradeButton.test.tsx b/app/react/kubernetes/helm/HelmApplicationView/ChartActions/UpgradeButton.test.tsx index b45a54e79..4bea0bf47 100644 --- a/app/react/kubernetes/helm/HelmApplicationView/ChartActions/UpgradeButton.test.tsx +++ b/app/react/kubernetes/helm/HelmApplicationView/ChartActions/UpgradeButton.test.tsx @@ -33,8 +33,6 @@ vi.mock('../queries/useHelmRepositories', () => ({ ], isInitialLoading: false, isError: false, - isFetching: false, - refetch: vi.fn(() => Promise.resolve([])), })), useHelmRepositories: vi.fn(() => ({ data: ['repo1', 'repo2'], @@ -83,8 +81,6 @@ describe('UpgradeButton', () => { data, isInitialLoading: false, isError: false, - isFetching: false, - refetch: vi.fn(() => Promise.resolve([])), }); renderButton(); @@ -98,8 +94,6 @@ describe('UpgradeButton', () => { data: [], isInitialLoading: true, isError: false, - isFetching: false, - refetch: vi.fn(() => Promise.resolve([])), }); renderButton(); @@ -115,8 +109,6 @@ describe('UpgradeButton', () => { data, isInitialLoading: false, isError: false, - isFetching: false, - refetch: vi.fn(() => Promise.resolve([])), }); renderButton(); @@ -147,8 +139,6 @@ describe('UpgradeButton', () => { ], isInitialLoading: false, isError: false, - isFetching: false, - refetch: vi.fn(() => Promise.resolve([])), }); renderButton({ release: mockRelease }); diff --git a/app/react/kubernetes/helm/HelmApplicationView/ChartActions/UpgradeButton.tsx b/app/react/kubernetes/helm/HelmApplicationView/ChartActions/UpgradeButton.tsx index 7376877e4..4c385c388 100644 --- a/app/react/kubernetes/helm/HelmApplicationView/ChartActions/UpgradeButton.tsx +++ b/app/react/kubernetes/helm/HelmApplicationView/ChartActions/UpgradeButton.tsx @@ -1,6 +1,5 @@ import { ArrowUp } from 'lucide-react'; import { useRouter } from '@uirouter/react'; -import { useState } from 'react'; import { EnvironmentId } from '@/react/portainer/environments/types'; import { notifySuccess } from '@/portainer/services/notifications'; @@ -42,19 +41,16 @@ export function UpgradeButton({ const updateHelmReleaseMutation = useUpdateHelmReleaseMutation(environmentId); const repositoriesQuery = useHelmRepositories(); - const [useCache, setUseCache] = useState(true); const helmRepoVersionsQuery = useHelmRepoVersions( release?.chart.metadata?.name || '', 60 * 60 * 1000, // 1 hour - repositoriesQuery.data, - useCache + repositoriesQuery.data ); const versions = helmRepoVersionsQuery.data; // Combined loading state const isInitialLoading = repositoriesQuery.isInitialLoading || - helmRepoVersionsQuery.isFetching || helmRepoVersionsQuery.isInitialLoading; const isError = repositoriesQuery.isError || helmRepoVersionsQuery.isError; @@ -62,10 +58,9 @@ export function UpgradeButton({ select: (data) => data.chart.metadata?.version, }); const latestVersionAvailable = versions[0]?.Version ?? ''; - const isNewVersionAvailable = Boolean( + const isNewVersionAvailable = latestVersion?.data && - semverCompare(latestVersionAvailable, latestVersion?.data) === 1 - ); + semverCompare(latestVersionAvailable, latestVersion?.data) === 1; const editableHelmRelease: UpdateHelmReleasePayload = { name: releaseName, @@ -75,14 +70,6 @@ export function UpgradeButton({ version: release?.chart.metadata?.version, }; - function handleRefreshVersions() { - if (useCache === false) { - helmRepoVersionsQuery.refetch(); - } else { - setUseCache(false); - } - } - return (
)} - {!isInitialLoading && !isError && ( + {versions.length === 0 && !isInitialLoading && !isError && ( - {getStatusMessage( - versions.length === 0, - latestVersionAvailable, - isNewVersionAvailable - )} - {versions.length === 0 && ( - - Portainer is unable to find any versions for this chart in the - repositories saved. Try adding a new repository which contains - the chart in the{' '} - - Helm repositories settings - -
- } - /> - )} - + No versions available + + Portainer is unable to find any versions for this chart in the + repositories saved. Try adding a new repository which contains + the chart in the{' '} + + Helm repositories settings + + + } + /> + + )} + {isNewVersionAvailable && ( + + New version available ({latestVersionAvailable}) )} @@ -185,18 +164,4 @@ export function UpgradeButton({ }, }); } - - function getStatusMessage( - hasNoAvailableVersions: boolean, - latestVersionAvailable: string, - isNewVersionAvailable: boolean - ): string { - if (hasNoAvailableVersions) { - return 'No versions available '; - } - if (isNewVersionAvailable) { - return `New version available (${latestVersionAvailable}) `; - } - return ''; - } } diff --git a/app/react/kubernetes/helm/HelmApplicationView/queries/useHelmRepositories.ts b/app/react/kubernetes/helm/HelmApplicationView/queries/useHelmRepositories.ts index bf11eadb1..733b79dea 100644 --- a/app/react/kubernetes/helm/HelmApplicationView/queries/useHelmRepositories.ts +++ b/app/react/kubernetes/helm/HelmApplicationView/queries/useHelmRepositories.ts @@ -45,21 +45,20 @@ export function useHelmRepositories() { export function useHelmRepoVersions( chart: string, staleTime: number, - repositories: string[] = [], - useCache: boolean = true + repositories: string[] = [] ) { // Fetch versions from each repository in parallel as separate queries const versionQueries = useQueries({ queries: useMemo( () => repositories.map((repo) => ({ - queryKey: ['helm', 'repositories', chart, repo, useCache], - queryFn: () => getSearchHelmRepo(repo, chart, useCache), + queryKey: ['helm', 'repositories', chart, repo], + queryFn: () => getSearchHelmRepo(repo, chart), enabled: !!chart && repositories.length > 0, staleTime, ...withGlobalError(`Unable to retrieve versions from ${repo}`), })), - [repositories, chart, staleTime, useCache] + [repositories, chart, staleTime] ), }); @@ -73,8 +72,6 @@ export function useHelmRepoVersions( data: allVersions, isInitialLoading: versionQueries.some((q) => q.isLoading), isError: versionQueries.some((q) => q.isError), - isFetching: versionQueries.some((q) => q.isFetching), - refetch: () => Promise.all(versionQueries.map((q) => q.refetch())), }; } @@ -83,12 +80,11 @@ export function useHelmRepoVersions( */ async function getSearchHelmRepo( repo: string, - chart: string, - useCache: boolean = true + chart: string ): Promise { try { const { data } = await axios.get(`templates/helm`, { - params: { repo, chart, useCache }, + params: { repo, chart }, }); const versions = data.entries[chart]; return ( diff --git a/pkg/libhelm/options/search_repo_options.go b/pkg/libhelm/options/search_repo_options.go index 0b35c0bbd..73acc5709 100644 --- a/pkg/libhelm/options/search_repo_options.go +++ b/pkg/libhelm/options/search_repo_options.go @@ -3,8 +3,6 @@ package options import "net/http" type SearchRepoOptions struct { - Repo string `example:"https://charts.gitlab.io/"` - Client *http.Client `example:"&http.Client{Timeout: time.Second * 10}"` - Chart string `example:"my-chart"` - UseCache bool `example:"false"` + Repo string `example:"https://charts.gitlab.io/"` + Client *http.Client `example:"&http.Client{Timeout: time.Second * 10}"` } diff --git a/pkg/libhelm/sdk/search_repo.go b/pkg/libhelm/sdk/search_repo.go index 63015330c..90dd98529 100644 --- a/pkg/libhelm/sdk/search_repo.go +++ b/pkg/libhelm/sdk/search_repo.go @@ -4,8 +4,6 @@ import ( "net/url" "os" "path/filepath" - "sync" - "time" "github.com/pkg/errors" "github.com/portainer/portainer/pkg/libhelm/options" @@ -27,17 +25,6 @@ type RepoIndex struct { Generated string `json:"generated"` } -type RepoIndexCache struct { - Index *repo.IndexFile - Timestamp time.Time -} - -var ( - indexCache = make(map[string]RepoIndexCache) - cacheMutex sync.RWMutex - cacheDuration = 60 * time.Minute -) - // SearchRepo downloads the `index.yaml` file for specified repo, parses it and returns JSON to caller. func (hspm *HelmSDKPackageManager) SearchRepo(searchRepoOpts options.SearchRepoOptions) ([]byte, error) { // Validate input options @@ -66,18 +53,6 @@ func (hspm *HelmSDKPackageManager) SearchRepo(searchRepoOpts options.SearchRepoO return nil, err } - // Check cache first - if searchRepoOpts.UseCache { - cacheMutex.RLock() - if cached, exists := indexCache[repoURL.String()]; exists { - if time.Since(cached.Timestamp) < cacheDuration { - cacheMutex.RUnlock() - return convertAndMarshalIndex(cached.Index, searchRepoOpts.Chart) - } - } - cacheMutex.RUnlock() - } - // Set up Helm CLI environment repoSettings := cli.New() @@ -117,21 +92,23 @@ func (hspm *HelmSDKPackageManager) SearchRepo(searchRepoOpts options.SearchRepoO return nil, err } - // Update cache and remove old entries - cacheMutex.Lock() - indexCache[searchRepoOpts.Repo] = RepoIndexCache{ - Index: indexFile, - Timestamp: time.Now(), - } - for key, index := range indexCache { - if time.Since(index.Timestamp) > cacheDuration { - delete(indexCache, key) - } + // Convert the index file to our response format + result, err := convertIndexToResponse(indexFile) + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Err(err). + Msg("Failed to convert index to response format") + return nil, errors.Wrap(err, "failed to convert index to response format") } - cacheMutex.Unlock() + log.Debug(). + Str("context", "HelmClient"). + Str("repo", searchRepoOpts.Repo). + Int("entries_count", len(indexFile.Entries)). + Msg("Successfully searched repository") - return convertAndMarshalIndex(indexFile, searchRepoOpts.Chart) + return json.Marshal(result) } // validateSearchRepoOptions validates the required search repository options. @@ -239,7 +216,7 @@ func loadIndexFile(indexPath string) (*repo.IndexFile, error) { } // convertIndexToResponse converts the Helm index file to our response format. -func convertIndexToResponse(indexFile *repo.IndexFile, chartName string) (RepoIndex, error) { +func convertIndexToResponse(indexFile *repo.IndexFile) (RepoIndex, error) { result := RepoIndex{ APIVersion: indexFile.APIVersion, Entries: make(map[string][]ChartInfo), @@ -248,9 +225,7 @@ func convertIndexToResponse(indexFile *repo.IndexFile, chartName string) (RepoIn // Convert Helm SDK types to our response types for name, charts := range indexFile.Entries { - if chartName == "" || name == chartName { - result.Entries[name] = convertChartsToChartInfo(charts) - } + result.Entries[name] = convertChartsToChartInfo(charts) } return result, nil @@ -374,23 +349,3 @@ func ensureHelmDirectoriesExist(settings *cli.EnvSettings) error { return nil } - -func convertAndMarshalIndex(indexFile *repo.IndexFile, chartName string) ([]byte, error) { - // Convert the index file to our response format - result, err := convertIndexToResponse(indexFile, chartName) - if err != nil { - log.Error(). - Str("context", "HelmClient"). - Err(err). - Msg("Failed to convert index to response format") - return nil, errors.Wrap(err, "failed to convert index to response format") - } - - log.Debug(). - Str("context", "HelmClient"). - Str("repo", chartName). - Int("entries_count", len(indexFile.Entries)). - Msg("Successfully searched repository") - - return json.Marshal(result) -} From 07dfd981a21279518f3dbae2429c4e0f091e1648 Mon Sep 17 00:00:00 2001 From: Cara Ryan Date: Tue, 27 May 2025 13:55:31 +1200 Subject: [PATCH 11/68] fix(kubernetes): events api to call the backend [R8S-243] (#563) --- api/http/handler/kubernetes/client.go | 4 +- .../kubernetes/cluster_role_bindings.go | 2 +- api/http/handler/kubernetes/cluster_roles.go | 2 +- api/http/handler/kubernetes/event.go | 102 +++++++++ api/http/handler/kubernetes/event_test.go | 60 ++++++ api/http/handler/kubernetes/handler.go | 6 +- api/http/models/kubernetes/event.go | 25 +++ api/internal/testhelpers/kube_client.go | 19 ++ api/kubernetes/cli/access.go | 20 ++ api/kubernetes/cli/client.go | 4 + api/kubernetes/cli/event.go | 93 ++++++++ api/kubernetes/cli/event_test.go | 108 ++++++++++ api/portainer.go | 147 +++++++++---- .../configmap/edit/configMap.html | 2 +- .../configurations/secret/edit/secret.html | 2 +- .../EventsDatatable/EventsDatatable.test.tsx | 83 +++++++ .../EventsDatatable/EventsDatatable.tsx | 4 +- .../ResourceEventsDatatable.tsx | 4 +- .../EventsDatatable/columns/eventType.tsx | 3 +- .../EventsDatatable/columns/helper.ts | 3 +- .../EventsDatatable/columns/kind.tsx | 3 +- .../HelmApplicationView.test.tsx | 33 +-- .../HelmEventsDatatable.test.tsx | 202 +++++++----------- .../ReleaseDetails/HelmEventsDatatable.tsx | 2 +- app/react/kubernetes/queries/types.ts | 19 ++ app/react/kubernetes/queries/useEvents.ts | 15 +- 26 files changed, 750 insertions(+), 217 deletions(-) create mode 100644 api/http/handler/kubernetes/event.go create mode 100644 api/http/handler/kubernetes/event_test.go create mode 100644 api/http/models/kubernetes/event.go create mode 100644 api/internal/testhelpers/kube_client.go create mode 100644 api/kubernetes/cli/event.go create mode 100644 api/kubernetes/cli/event_test.go create mode 100644 app/react/kubernetes/components/EventsDatatable/EventsDatatable.test.tsx create mode 100644 app/react/kubernetes/queries/types.ts diff --git a/api/http/handler/kubernetes/client.go b/api/http/handler/kubernetes/client.go index a85f2cff9..6612ab7f4 100644 --- a/api/http/handler/kubernetes/client.go +++ b/api/http/handler/kubernetes/client.go @@ -30,8 +30,8 @@ func (handler *Handler) prepareKubeClient(r *http.Request) (*cli.KubeClient, *ht log.Error().Err(err).Str("context", "prepareKubeClient").Msg("Unable to get a privileged Kubernetes client for the user.") return nil, httperror.InternalServerError("Unable to get a privileged Kubernetes client for the user.", err) } - pcli.IsKubeAdmin = cli.IsKubeAdmin - pcli.NonAdminNamespaces = cli.NonAdminNamespaces + pcli.SetIsKubeAdmin(cli.GetIsKubeAdmin()) + pcli.SetClientNonAdminNamespaces(cli.GetClientNonAdminNamespaces()) return pcli, nil } diff --git a/api/http/handler/kubernetes/cluster_role_bindings.go b/api/http/handler/kubernetes/cluster_role_bindings.go index a5050c947..83621a900 100644 --- a/api/http/handler/kubernetes/cluster_role_bindings.go +++ b/api/http/handler/kubernetes/cluster_role_bindings.go @@ -32,7 +32,7 @@ func (handler *Handler) getAllKubernetesClusterRoleBindings(w http.ResponseWrite return httperror.Forbidden("User is not authorized to fetch cluster role bindings from the Kubernetes cluster.", httpErr) } - if !cli.IsKubeAdmin { + if !cli.GetIsKubeAdmin() { log.Error().Str("context", "getAllKubernetesClusterRoleBindings").Msg("user is not authorized to fetch cluster role bindings from the Kubernetes cluster.") return httperror.Forbidden("User is not authorized to fetch cluster role bindings from the Kubernetes cluster.", nil) } diff --git a/api/http/handler/kubernetes/cluster_roles.go b/api/http/handler/kubernetes/cluster_roles.go index 3fd2ca8aa..6d5d028be 100644 --- a/api/http/handler/kubernetes/cluster_roles.go +++ b/api/http/handler/kubernetes/cluster_roles.go @@ -32,7 +32,7 @@ func (handler *Handler) getAllKubernetesClusterRoles(w http.ResponseWriter, r *h return httperror.Forbidden("User is not authorized to fetch cluster roles from the Kubernetes cluster.", httpErr) } - if !cli.IsKubeAdmin { + if !cli.GetIsKubeAdmin() { log.Error().Str("context", "getAllKubernetesClusterRoles").Msg("user is not authorized to fetch cluster roles from the Kubernetes cluster.") return httperror.Forbidden("User is not authorized to fetch cluster roles from the Kubernetes cluster.", nil) } diff --git a/api/http/handler/kubernetes/event.go b/api/http/handler/kubernetes/event.go new file mode 100644 index 000000000..0e226d5ec --- /dev/null +++ b/api/http/handler/kubernetes/event.go @@ -0,0 +1,102 @@ +package kubernetes + +import ( + "net/http" + + httperror "github.com/portainer/portainer/pkg/libhttp/error" + "github.com/portainer/portainer/pkg/libhttp/request" + "github.com/portainer/portainer/pkg/libhttp/response" + "github.com/rs/zerolog/log" + k8serrors "k8s.io/apimachinery/pkg/api/errors" +) + +// @id getKubernetesEventsForNamespace +// @summary Gets kubernetes events for namespace +// @description Get events by optional query param resourceId for a given namespace. +// @description **Access policy**: Authenticated user. +// @tags kubernetes +// @security ApiKeyAuth || jwt +// @produce json +// @param id path int true "Environment identifier" +// @param namespace path string true "The namespace name the events are associated to" +// @param resourceId query string false "The resource id of the involved kubernetes object" example:"e5b021b6-4bce-4c06-bd3b-6cca906797aa" +// @success 200 {object} models.Event[] "Success" +// @failure 400 "Invalid request payload, such as missing required fields or fields not meeting validation criteria." +// @failure 401 "Unauthorized access - the user is not authenticated or does not have the necessary permissions. Ensure that you have provided a valid API key or JWT token, and that you have the required permissions." +// @failure 403 "Permission denied - the user is authenticated but does not have the necessary permissions to access the requested resource or perform the specified operation. Check your user roles and permissions." +// @failure 500 "Server error occurred while attempting to retrieve the events within the specified namespace." +// @router /kubernetes/{id}/namespaces/{namespace}/events [get] +func (handler *Handler) getKubernetesEventsForNamespace(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { + namespace, err := request.RetrieveRouteVariableValue(r, "namespace") + if err != nil { + log.Error().Err(err).Str("context", "getKubernetesEvents").Str("namespace", namespace).Msg("Unable to retrieve namespace identifier route variable") + return httperror.BadRequest("Unable to retrieve namespace identifier route variable", err) + } + + resourceId, err := request.RetrieveQueryParameter(r, "resourceId", true) + if err != nil { + log.Error().Err(err).Str("context", "getKubernetesEvents").Msg("Unable to retrieve resourceId query parameter") + return httperror.BadRequest("Unable to retrieve resourceId query parameter", err) + } + + cli, httpErr := handler.getProxyKubeClient(r) + if httpErr != nil { + log.Error().Err(httpErr).Str("context", "getKubernetesEvents").Str("resourceId", resourceId).Msg("Unable to get a Kubernetes client for the user") + return httperror.InternalServerError("Unable to get a Kubernetes client for the user", httpErr) + } + + events, err := cli.GetEvents(namespace, resourceId) + if err != nil { + if k8serrors.IsUnauthorized(err) || k8serrors.IsForbidden(err) { + log.Error().Err(err).Str("context", "getKubernetesEvents").Msg("Unauthorized access to the Kubernetes API") + return httperror.Forbidden("Unauthorized access to the Kubernetes API", err) + } + + log.Error().Err(err).Str("context", "getKubernetesEvents").Msg("Unable to retrieve events") + return httperror.InternalServerError("Unable to retrieve events", err) + } + + return response.JSON(w, events) +} + +// @id getAllKubernetesEvents +// @summary Gets kubernetes events +// @description Get events by query param resourceId +// @description **Access policy**: Authenticated user. +// @tags kubernetes +// @security ApiKeyAuth || jwt +// @produce json +// @param id path int true "Environment identifier" +// @param resourceId query string false "The resource id of the involved kubernetes object" example:"e5b021b6-4bce-4c06-bd3b-6cca906797aa" +// @success 200 {object} models.Event[] "Success" +// @failure 400 "Invalid request payload, such as missing required fields or fields not meeting validation criteria." +// @failure 401 "Unauthorized access - the user is not authenticated or does not have the necessary permissions. Ensure that you have provided a valid API key or JWT token, and that you have the required permissions." +// @failure 403 "Permission denied - the user is authenticated but does not have the necessary permissions to access the requested resource or perform the specified operation. Check your user roles and permissions." +// @failure 500 "Server error occurred while attempting to retrieve the events." +// @router /kubernetes/{id}/events [get] +func (handler *Handler) getAllKubernetesEvents(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { + resourceId, err := request.RetrieveQueryParameter(r, "resourceId", true) + if err != nil { + log.Error().Err(err).Str("context", "getKubernetesEvents").Msg("Unable to retrieve resourceId query parameter") + return httperror.BadRequest("Unable to retrieve resourceId query parameter", err) + } + + cli, httpErr := handler.getProxyKubeClient(r) + if httpErr != nil { + log.Error().Err(httpErr).Str("context", "getKubernetesEvents").Str("resourceId", resourceId).Msg("Unable to get a Kubernetes client for the user") + return httperror.InternalServerError("Unable to get a Kubernetes client for the user", httpErr) + } + + events, err := cli.GetEvents("", resourceId) + if err != nil { + if k8serrors.IsUnauthorized(err) || k8serrors.IsForbidden(err) { + log.Error().Err(err).Str("context", "getKubernetesEvents").Msg("Unauthorized access to the Kubernetes API") + return httperror.Forbidden("Unauthorized access to the Kubernetes API", err) + } + + log.Error().Err(err).Str("context", "getKubernetesEvents").Msg("Unable to retrieve events") + return httperror.InternalServerError("Unable to retrieve events", err) + } + + return response.JSON(w, events) +} diff --git a/api/http/handler/kubernetes/event_test.go b/api/http/handler/kubernetes/event_test.go new file mode 100644 index 000000000..77f38c511 --- /dev/null +++ b/api/http/handler/kubernetes/event_test.go @@ -0,0 +1,60 @@ +package kubernetes + +import ( + "net/http" + "net/http/httptest" + "testing" + + portainer "github.com/portainer/portainer/api" + "github.com/portainer/portainer/api/datastore" + "github.com/portainer/portainer/api/http/security" + "github.com/portainer/portainer/api/internal/authorization" + "github.com/portainer/portainer/api/internal/testhelpers" + "github.com/portainer/portainer/api/jwt" + "github.com/portainer/portainer/api/kubernetes" + kubeClient "github.com/portainer/portainer/api/kubernetes/cli" + "github.com/stretchr/testify/assert" +) + +// Currently this test just tests the HTTP Handler is setup correctly, in the future we should move the ClientFactory to a mock in order +// test the logic in event.go +func TestGetKubernetesEvents(t *testing.T) { + is := assert.New(t) + + _, store := datastore.MustNewTestStore(t, true, true) + + err := store.Endpoint().Create(&portainer.Endpoint{ + ID: 1, + Type: portainer.AgentOnKubernetesEnvironment, + }, + ) + is.NoError(err, "error creating environment") + + err = store.User().Create(&portainer.User{Username: "admin", Role: portainer.AdministratorRole}) + is.NoError(err, "error creating a user") + + jwtService, err := jwt.NewService("1h", store) + is.NoError(err, "Error initiating jwt service") + + tk, _, _ := jwtService.GenerateToken(&portainer.TokenData{ID: 1, Username: "admin", Role: portainer.AdministratorRole}) + + kubeClusterAccessService := kubernetes.NewKubeClusterAccessService("", "", "") + + cli := testhelpers.NewKubernetesClient() + factory, _ := kubeClient.NewClientFactory(nil, nil, store, "", "", "") + + authorizationService := authorization.NewService(store) + handler := NewHandler(testhelpers.NewTestRequestBouncer(), authorizationService, store, jwtService, kubeClusterAccessService, + factory, cli) + is.NotNil(handler, "Handler should not fail") + + req := httptest.NewRequest(http.MethodGet, "/kubernetes/1/events?resourceId=8", nil) + ctx := security.StoreTokenData(req, &portainer.TokenData{ID: 1, Username: "admin", Role: 1}) + req = req.WithContext(ctx) + testhelpers.AddTestSecurityCookie(req, tk) + + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + + is.Equal(http.StatusOK, rr.Code, "Status should be 200") +} diff --git a/api/http/handler/kubernetes/handler.go b/api/http/handler/kubernetes/handler.go index cc068e4a4..07f6bdf3f 100644 --- a/api/http/handler/kubernetes/handler.go +++ b/api/http/handler/kubernetes/handler.go @@ -58,6 +58,7 @@ func NewHandler(bouncer security.BouncerService, authorizationService *authoriza endpointRouter.Handle("/configmaps/count", httperror.LoggerHandler(h.getAllKubernetesConfigMapsCount)).Methods(http.MethodGet) endpointRouter.Handle("/cron_jobs", httperror.LoggerHandler(h.getAllKubernetesCronJobs)).Methods(http.MethodGet) endpointRouter.Handle("/cron_jobs/delete", httperror.LoggerHandler(h.deleteKubernetesCronJobs)).Methods(http.MethodPost) + endpointRouter.Handle("/events", httperror.LoggerHandler(h.getAllKubernetesEvents)).Methods(http.MethodGet) endpointRouter.Handle("/jobs", httperror.LoggerHandler(h.getAllKubernetesJobs)).Methods(http.MethodGet) endpointRouter.Handle("/jobs/delete", httperror.LoggerHandler(h.deleteKubernetesJobs)).Methods(http.MethodPost) endpointRouter.Handle("/cluster_roles", httperror.LoggerHandler(h.getAllKubernetesClusterRoles)).Methods(http.MethodGet) @@ -110,6 +111,7 @@ func NewHandler(bouncer security.BouncerService, authorizationService *authoriza // to keep it simple, we've decided to leave it like this. namespaceRouter := endpointRouter.PathPrefix("/namespaces/{namespace}").Subrouter() namespaceRouter.Handle("/configmaps/{configmap}", httperror.LoggerHandler(h.getKubernetesConfigMap)).Methods(http.MethodGet) + namespaceRouter.Handle("/events", httperror.LoggerHandler(h.getKubernetesEventsForNamespace)).Methods(http.MethodGet) namespaceRouter.Handle("/system", bouncer.RestrictedAccess(httperror.LoggerHandler(h.namespacesToggleSystem))).Methods(http.MethodPut) namespaceRouter.Handle("/ingresscontrollers", httperror.LoggerHandler(h.getKubernetesIngressControllersByNamespace)).Methods(http.MethodGet) namespaceRouter.Handle("/ingresscontrollers", httperror.LoggerHandler(h.updateKubernetesIngressControllersByNamespace)).Methods(http.MethodPut) @@ -133,7 +135,7 @@ func NewHandler(bouncer security.BouncerService, authorizationService *authoriza // getProxyKubeClient gets a kubeclient for the user. It's generally what you want as it retrieves the kubeclient // from the Authorization token of the currently logged in user. The kubeclient that is not from the proxy is actually using // admin permissions. If you're unsure which one to use, use this. -func (h *Handler) getProxyKubeClient(r *http.Request) (*cli.KubeClient, *httperror.HandlerError) { +func (h *Handler) getProxyKubeClient(r *http.Request) (portainer.KubeClient, *httperror.HandlerError) { endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") if err != nil { return nil, httperror.BadRequest(fmt.Sprintf("an error occurred during the getProxyKubeClient operation, the environment identifier route variable is invalid for /api/kubernetes/%d. Error: ", endpointID), err) @@ -253,7 +255,7 @@ func (handler *Handler) kubeClientMiddleware(next http.Handler) http.Handler { return } serverURL.Scheme = "https" - serverURL.Host = "localhost" + handler.KubernetesClientFactory.AddrHTTPS + serverURL.Host = "localhost" + handler.KubernetesClientFactory.GetAddrHTTPS() config.Clusters[0].Cluster.Server = serverURL.String() yaml, err := cli.GenerateYAML(config) diff --git a/api/http/models/kubernetes/event.go b/api/http/models/kubernetes/event.go new file mode 100644 index 000000000..be447b554 --- /dev/null +++ b/api/http/models/kubernetes/event.go @@ -0,0 +1,25 @@ +package kubernetes + +import "time" + +type K8sEvent struct { + Type string `json:"type"` + Name string `json:"name"` + Reason string `json:"reason"` + Message string `json:"message"` + Namespace string `json:"namespace"` + EventTime time.Time `json:"eventTime"` + Kind string `json:"kind,omitempty"` + Count int32 `json:"count"` + FirstTimestamp *time.Time `json:"firstTimestamp,omitempty"` + LastTimestamp *time.Time `json:"lastTimestamp,omitempty"` + UID string `json:"uid"` + InvolvedObjectKind K8sEventInvolvedObject `json:"involvedObject"` +} + +type K8sEventInvolvedObject struct { + Kind string `json:"kind,omitempty"` + UID string `json:"uid"` + Name string `json:"name"` + Namespace string `json:"namespace"` +} diff --git a/api/internal/testhelpers/kube_client.go b/api/internal/testhelpers/kube_client.go new file mode 100644 index 000000000..550e7ce92 --- /dev/null +++ b/api/internal/testhelpers/kube_client.go @@ -0,0 +1,19 @@ +package testhelpers + +import ( + portainer "github.com/portainer/portainer/api" + models "github.com/portainer/portainer/api/http/models/kubernetes" +) + +type testKubeClient struct { + portainer.KubeClient +} + +func NewKubernetesClient() portainer.KubeClient { + return &testKubeClient{} +} + +// Event +func (kcl *testKubeClient) GetEvents(namespace string, resourceId string) ([]models.K8sEvent, error) { + return nil, nil +} diff --git a/api/kubernetes/cli/access.go b/api/kubernetes/cli/access.go index 73f8d50af..6f254c296 100644 --- a/api/kubernetes/cli/access.go +++ b/api/kubernetes/cli/access.go @@ -143,3 +143,23 @@ func (kcl *KubeClient) GetNonAdminNamespaces(userID int, teamIDs []int, isRestri return nonAdminNamespaces, nil } + +// GetIsKubeAdmin retrieves true if client is admin +func (client *KubeClient) GetIsKubeAdmin() bool { + return client.IsKubeAdmin +} + +// UpdateIsKubeAdmin sets whether the kube client is admin +func (client *KubeClient) SetIsKubeAdmin(isKubeAdmin bool) { + client.IsKubeAdmin = isKubeAdmin +} + +// GetClientNonAdminNamespaces retrieves non-admin namespaces +func (client *KubeClient) GetClientNonAdminNamespaces() []string { + return client.NonAdminNamespaces +} + +// UpdateClientNonAdminNamespaces sets the client non admin namespace list +func (client *KubeClient) SetClientNonAdminNamespaces(nonAdminNamespaces []string) { + client.NonAdminNamespaces = nonAdminNamespaces +} diff --git a/api/kubernetes/cli/client.go b/api/kubernetes/cli/client.go index 6d2cc437c..a40a865f1 100644 --- a/api/kubernetes/cli/client.go +++ b/api/kubernetes/cli/client.go @@ -82,6 +82,10 @@ func (factory *ClientFactory) RemoveKubeClient(endpointID portainer.EndpointID) factory.endpointProxyClients.Delete(strconv.Itoa(int(endpointID))) } +func (factory *ClientFactory) GetAddrHTTPS() string { + return factory.AddrHTTPS +} + // GetPrivilegedKubeClient checks if an existing client is already registered for the environment(endpoint) and returns it if one is found. // If no client is registered, it will create a new client, register it, and returns it. func (factory *ClientFactory) GetPrivilegedKubeClient(endpoint *portainer.Endpoint) (*KubeClient, error) { diff --git a/api/kubernetes/cli/event.go b/api/kubernetes/cli/event.go new file mode 100644 index 000000000..03472fca6 --- /dev/null +++ b/api/kubernetes/cli/event.go @@ -0,0 +1,93 @@ +package cli + +import ( + "context" + + models "github.com/portainer/portainer/api/http/models/kubernetes" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// GetEvents gets all the Events for a given namespace and resource +// If the user is a kube admin, it returns all events in the namespace +// Otherwise, it returns only the events in the non-admin namespaces +func (kcl *KubeClient) GetEvents(namespace string, resourceId string) ([]models.K8sEvent, error) { + if kcl.IsKubeAdmin { + return kcl.fetchAllEvents(namespace, resourceId) + } + + return kcl.fetchEventsForNonAdmin(namespace, resourceId) +} + +// fetchEventsForNonAdmin returns all events in the given namespace and resource +// It returns only the events in the non-admin namespaces +func (kcl *KubeClient) fetchEventsForNonAdmin(namespace string, resourceId string) ([]models.K8sEvent, error) { + if len(kcl.NonAdminNamespaces) == 0 { + return nil, nil + } + + events, err := kcl.fetchAllEvents(namespace, resourceId) + if err != nil { + return nil, err + } + + nonAdminNamespaceSet := kcl.buildNonAdminNamespacesMap() + results := make([]models.K8sEvent, 0) + for _, event := range events { + if _, ok := nonAdminNamespaceSet[event.Namespace]; ok { + results = append(results, event) + } + } + + return results, nil +} + +// fetchEventsForNonAdmin returns all events in the given namespace and resource +// It returns all events in the namespace and resource +func (kcl *KubeClient) fetchAllEvents(namespace string, resourceId string) ([]models.K8sEvent, error) { + options := metav1.ListOptions{} + if resourceId != "" { + options.FieldSelector = "involvedObject.uid=" + resourceId + } + + list, err := kcl.cli.CoreV1().Events(namespace).List(context.TODO(), options) + if err != nil { + return nil, err + } + + results := make([]models.K8sEvent, 0) + for _, event := range list.Items { + results = append(results, parseEvent(&event)) + } + + return results, nil +} + +func parseEvent(event *corev1.Event) models.K8sEvent { + result := models.K8sEvent{ + Type: event.Type, + Name: event.Name, + Message: event.Message, + Reason: event.Reason, + Namespace: event.Namespace, + EventTime: event.EventTime.UTC(), + Kind: event.Kind, + Count: event.Count, + UID: string(event.ObjectMeta.GetUID()), + InvolvedObjectKind: models.K8sEventInvolvedObject{ + Kind: event.InvolvedObject.Kind, + UID: string(event.InvolvedObject.UID), + Name: event.InvolvedObject.Name, + Namespace: event.InvolvedObject.Namespace, + }, + } + + if !event.LastTimestamp.Time.IsZero() { + result.LastTimestamp = &event.LastTimestamp.Time + } + if !event.FirstTimestamp.Time.IsZero() { + result.FirstTimestamp = &event.FirstTimestamp.Time + } + + return result +} diff --git a/api/kubernetes/cli/event_test.go b/api/kubernetes/cli/event_test.go new file mode 100644 index 000000000..926928317 --- /dev/null +++ b/api/kubernetes/cli/event_test.go @@ -0,0 +1,108 @@ +package cli + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kfake "k8s.io/client-go/kubernetes/fake" +) + +// TestGetEvents tests the GetEvents method +// It creates a fake Kubernetes client and passes it to the GetEvents method +// It then logs the fetched events and validated the data returned +func TestGetEvents(t *testing.T) { + t.Run("can get events for resource id when admin", func(t *testing.T) { + kcl := &KubeClient{ + cli: kfake.NewSimpleClientset(), + instanceID: "instance", + IsKubeAdmin: true, + } + event := corev1.Event{ + InvolvedObject: corev1.ObjectReference{UID: "resourceId"}, + Action: "something", + ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "myEvent"}, + EventTime: metav1.NowMicro(), + Type: "warning", + Message: "This event has a very serious warning", + } + _, err := kcl.cli.CoreV1().Events("default").Create(context.TODO(), &event, metav1.CreateOptions{}) + if err != nil { + t.Fatalf("Failed to create Event: %v", err) + } + + events, err := kcl.GetEvents("default", "resourceId") + + if err != nil { + t.Fatalf("Failed to fetch Cron Jobs: %v", err) + } + t.Logf("Fetched Events: %v", events) + require.Equal(t, 1, len(events), "Expected to return 1 event") + assert.Equal(t, event.Message, events[0].Message, "Expected Message to be equal to event message created") + assert.Equal(t, event.Type, events[0].Type, "Expected Type to be equal to event type created") + assert.Equal(t, event.EventTime.UTC(), events[0].EventTime, "Expected EventTime to be saved as a string from event time created") + }) + t.Run("can get kubernetes events for non admin namespace when non admin", func(t *testing.T) { + kcl := &KubeClient{ + cli: kfake.NewSimpleClientset(), + instanceID: "instance", + IsKubeAdmin: false, + NonAdminNamespaces: []string{"nonAdmin"}, + } + event := corev1.Event{ + InvolvedObject: corev1.ObjectReference{UID: "resourceId"}, + Action: "something", + ObjectMeta: metav1.ObjectMeta{Namespace: "nonAdmin", Name: "myEvent"}, + EventTime: metav1.NowMicro(), + Type: "warning", + Message: "This event has a very serious warning", + } + _, err := kcl.cli.CoreV1().Events("nonAdmin").Create(context.TODO(), &event, metav1.CreateOptions{}) + if err != nil { + t.Fatalf("Failed to create Event: %v", err) + } + + events, err := kcl.GetEvents("nonAdmin", "resourceId") + + if err != nil { + t.Fatalf("Failed to fetch Cron Jobs: %v", err) + } + t.Logf("Fetched Events: %v", events) + require.Equal(t, 1, len(events), "Expected to return 1 event") + assert.Equal(t, event.Message, events[0].Message, "Expected Message to be equal to event message created") + assert.Equal(t, event.Type, events[0].Type, "Expected Type to be equal to event type created") + assert.Equal(t, event.EventTime.UTC(), events[0].EventTime, "Expected EventTime to be saved as a string from event time created") + }) + + t.Run("cannot get kubernetes events for admin namespace when non admin", func(t *testing.T) { + kcl := &KubeClient{ + cli: kfake.NewSimpleClientset(), + instanceID: "instance", + IsKubeAdmin: false, + NonAdminNamespaces: []string{"nonAdmin"}, + } + event := corev1.Event{ + InvolvedObject: corev1.ObjectReference{UID: "resourceId"}, + Action: "something", + ObjectMeta: metav1.ObjectMeta{Namespace: "admin", Name: "myEvent"}, + EventTime: metav1.NowMicro(), + Type: "warning", + Message: "This event has a very serious warning", + } + _, err := kcl.cli.CoreV1().Events("admin").Create(context.TODO(), &event, metav1.CreateOptions{}) + if err != nil { + t.Fatalf("Failed to create Event: %v", err) + } + + events, err := kcl.GetEvents("admin", "resourceId") + + if err != nil { + t.Fatalf("Failed to fetch Cron Jobs: %v", err) + } + t.Logf("Fetched Events: %v", events) + assert.Equal(t, 0, len(events), "Expected to return 0 events") + }) +} diff --git a/api/portainer.go b/api/portainer.go index f61bb1af2..f63d04ddb 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "net/http" "time" "github.com/docker/docker/api/types" @@ -13,6 +14,7 @@ import ( gittypes "github.com/portainer/portainer/api/git/types" models "github.com/portainer/portainer/api/http/models/kubernetes" "github.com/portainer/portainer/pkg/featureflags" + httperror "github.com/portainer/portainer/pkg/libhttp/error" "github.com/segmentio/encoding/json" "golang.org/x/oauth2" @@ -1531,56 +1533,127 @@ type ( // KubeClient represents a service used to query a Kubernetes environment(endpoint) KubeClient interface { - ServerVersion() (*version.Info, error) + // Access + GetIsKubeAdmin() bool + SetIsKubeAdmin(isKubeAdmin bool) + GetClientNonAdminNamespaces() []string + SetClientNonAdminNamespaces([]string) + NamespaceAccessPoliciesDeleteNamespace(ns string) error + UpdateNamespaceAccessPolicies(accessPolicies map[string]K8sNamespaceAccessPolicy) error + GetNamespaceAccessPolicies() (map[string]K8sNamespaceAccessPolicy, error) + GetNonAdminNamespaces(userID int, teamIDs []int, isRestrictDefaultNamespace bool) ([]string, error) - SetupUserServiceAccount(userID int, teamIDs []int, restrictDefaultNamespace bool) error - IsRBACEnabled() (bool, error) - GetPortainerUserServiceAccount(tokendata *TokenData) (*corev1.ServiceAccount, error) - GetServiceAccounts(namespace string) ([]models.K8sServiceAccount, error) - DeleteServiceAccounts(reqs models.K8sServiceAccountDeleteRequests) error - GetServiceAccountBearerToken(userID int) (string, error) - CreateUserShellPod(ctx context.Context, serviceAccountName, shellPodImage string) (*KubernetesShellPod, error) + // Applications + GetApplications(namespace, nodeName string) ([]models.K8sApplication, error) + GetApplicationsResource(namespace, node string) (models.K8sApplicationResource, error) + + // ClusterRole + GetClusterRoles() ([]models.K8sClusterRole, error) + DeleteClusterRoles(req models.K8sClusterRoleDeleteRequests) error + + // ConfigMap + GetConfigMap(namespace, configMapName string) (models.K8sConfigMap, error) + CombineConfigMapWithApplications(configMap models.K8sConfigMap) (models.K8sConfigMap, error) + + // CronJob + GetCronJobs(namespace string) ([]models.K8sCronJob, error) + DeleteCronJobs(payload models.K8sCronJobDeleteRequests) error + + // Event + GetEvents(namespace string, resourceId string) ([]models.K8sEvent, error) + + // Exec StartExecProcess(token string, useAdminToken bool, namespace, podName, containerName string, command []string, stdin io.Reader, stdout io.Writer, errChan chan error) + // ClusterRoleBinding + GetClusterRoleBindings() ([]models.K8sClusterRoleBinding, error) + DeleteClusterRoleBindings(reqs models.K8sClusterRoleBindingDeleteRequests) error + + // Dashboard + GetDashboard() (models.K8sDashboard, error) + + // Deployment HasStackName(namespace string, stackName string) (bool, error) - NamespaceAccessPoliciesDeleteNamespace(namespace string) error - CreateNamespace(info models.K8sNamespaceDetails) (*corev1.Namespace, error) - UpdateNamespace(info models.K8sNamespaceDetails) (*corev1.Namespace, error) - GetNamespaces() (map[string]K8sNamespaceInfo, error) - GetNamespace(string) (K8sNamespaceInfo, error) - DeleteNamespace(namespace string) (*corev1.Namespace, error) - GetConfigMaps(namespace string) ([]models.K8sConfigMap, error) - GetSecrets(namespace string) ([]models.K8sSecret, error) + + // Ingress GetIngressControllers() (models.K8sIngressControllers, error) - GetApplications(namespace, nodename string) ([]models.K8sApplication, error) - GetMetrics() (models.K8sMetrics, error) - GetStorage() ([]KubernetesStorageClassConfig, error) - CreateIngress(namespace string, info models.K8sIngressInfo, owner string) error - UpdateIngress(namespace string, info models.K8sIngressInfo) error + GetIngress(namespace, ingressName string) (models.K8sIngressInfo, error) GetIngresses(namespace string) ([]models.K8sIngressInfo, error) + CreateIngress(namespace string, info models.K8sIngressInfo, owner string) error DeleteIngresses(reqs models.K8sIngressDeleteRequests) error - CreateService(namespace string, service models.K8sServiceInfo) error - UpdateService(namespace string, service models.K8sServiceInfo) error - GetServices(namespace string) ([]models.K8sServiceInfo, error) - DeleteServices(reqs models.K8sServiceDeleteRequests) error + UpdateIngress(namespace string, info models.K8sIngressInfo) error + CombineIngressWithService(ingress models.K8sIngressInfo) (models.K8sIngressInfo, error) + CombineIngressesWithServices(ingresses []models.K8sIngressInfo) ([]models.K8sIngressInfo, error) + + // Job + GetJobs(namespace string, includeCronJobChildren bool) ([]models.K8sJob, error) + DeleteJobs(payload models.K8sJobDeleteRequests) error + + // Metrics + GetMetrics() (models.K8sMetrics, error) + + // Namespace + ToggleSystemState(namespaceName string, isSystem bool) error + UpdateNamespace(info models.K8sNamespaceDetails) (*corev1.Namespace, error) + GetNamespace(name string) (K8sNamespaceInfo, error) + CreateNamespace(info models.K8sNamespaceDetails) (*corev1.Namespace, error) + GetNamespaces() (map[string]K8sNamespaceInfo, error) + CombineNamespaceWithResourceQuota(namespace K8sNamespaceInfo, w http.ResponseWriter) *httperror.HandlerError + DeleteNamespace(namespaceName string) (*corev1.Namespace, error) + CombineNamespacesWithResourceQuotas(namespaces map[string]K8sNamespaceInfo, w http.ResponseWriter) *httperror.HandlerError + ConvertNamespaceMapToSlice(namespaces map[string]K8sNamespaceInfo) []K8sNamespaceInfo + + // NodeLimits GetNodesLimits() (K8sNodesLimits, error) - GetMaxResourceLimits(name string, overCommitEnabled bool, resourceOverCommitPercent int) (K8sNodeLimits, error) - GetNamespaceAccessPolicies() (map[string]K8sNamespaceAccessPolicy, error) - UpdateNamespaceAccessPolicies(accessPolicies map[string]K8sNamespaceAccessPolicy) error + GetMaxResourceLimits(skipNamespace string, overCommitEnabled bool, resourceOverCommitPercent int) (K8sNodeLimits, error) + + // Pod + CreateUserShellPod(ctx context.Context, serviceAccountName, shellPodImage string) (*KubernetesShellPod, error) + + // RBAC + IsRBACEnabled() (bool, error) + + // Registries DeleteRegistrySecret(registry RegistryID, namespace string) error CreateRegistrySecret(registry *Registry, namespace string) error IsRegistrySecret(namespace, secretName string) (bool, error) - ToggleSystemState(namespace string, isSystem bool) error - GetClusterRoles() ([]models.K8sClusterRole, error) - DeleteClusterRoles(models.K8sClusterRoleDeleteRequests) error - GetClusterRoleBindings() ([]models.K8sClusterRoleBinding, error) - DeleteClusterRoleBindings(models.K8sClusterRoleBindingDeleteRequests) error - - GetRoles(namespace string) ([]models.K8sRole, error) - DeleteRoles(models.K8sRoleDeleteRequests) error + // RoleBinding GetRoleBindings(namespace string) ([]models.K8sRoleBinding, error) - DeleteRoleBindings(models.K8sRoleBindingDeleteRequests) error + DeleteRoleBindings(reqs models.K8sRoleBindingDeleteRequests) error + + // Role + DeleteRoles(reqs models.K8sRoleDeleteRequests) error + + // Secret + GetSecrets(namespace string) ([]models.K8sSecret, error) + GetSecret(namespace string, secretName string) (models.K8sSecret, error) + CombineSecretWithApplications(secret models.K8sSecret) (models.K8sSecret, error) + + // ServiceAccount + GetServiceAccounts(namespace string) ([]models.K8sServiceAccount, error) + DeleteServiceAccounts(reqs models.K8sServiceAccountDeleteRequests) error + SetupUserServiceAccount(int, []int, bool) error + GetPortainerUserServiceAccount(tokendata *TokenData) (*corev1.ServiceAccount, error) + GetServiceAccountBearerToken(userID int) (string, error) + + // Service + GetServices(namespace string) ([]models.K8sServiceInfo, error) + CombineServicesWithApplications(services []models.K8sServiceInfo) ([]models.K8sServiceInfo, error) + CreateService(namespace string, info models.K8sServiceInfo) error + DeleteServices(reqs models.K8sServiceDeleteRequests) error + UpdateService(namespace string, info models.K8sServiceInfo) error + + // ServerVersion + ServerVersion() (*version.Info, error) + + // Storage + GetStorage() ([]KubernetesStorageClassConfig, error) + + // Volumes + GetVolumes(namespace string) ([]models.K8sVolumeInfo, error) + GetVolume(namespace, volumeName string) (*models.K8sVolumeInfo, error) + CombineVolumesWithApplications(volumes *[]models.K8sVolumeInfo) (*[]models.K8sVolumeInfo, error) } // KubernetesDeployer represents a service to deploy a manifest inside a Kubernetes environment(endpoint) diff --git a/app/kubernetes/views/configurations/configmap/edit/configMap.html b/app/kubernetes/views/configurations/configmap/edit/configMap.html index 89e1c38c5..70a2d4f1a 100644 --- a/app/kubernetes/views/configurations/configmap/edit/configMap.html +++ b/app/kubernetes/views/configurations/configmap/edit/configMap.html @@ -58,7 +58,7 @@ diff --git a/app/kubernetes/views/configurations/secret/edit/secret.html b/app/kubernetes/views/configurations/secret/edit/secret.html index 0309d356c..2e939c87e 100644 --- a/app/kubernetes/views/configurations/secret/edit/secret.html +++ b/app/kubernetes/views/configurations/secret/edit/secret.html @@ -65,7 +65,7 @@ diff --git a/app/react/kubernetes/components/EventsDatatable/EventsDatatable.test.tsx b/app/react/kubernetes/components/EventsDatatable/EventsDatatable.test.tsx new file mode 100644 index 000000000..c5dc49c16 --- /dev/null +++ b/app/react/kubernetes/components/EventsDatatable/EventsDatatable.test.tsx @@ -0,0 +1,83 @@ +import { render, screen } from '@testing-library/react'; + +import { withTestQueryProvider } from '@/react/test-utils/withTestQuery'; +import { withTestRouter } from '@/react/test-utils/withRouter'; +import { UserViewModel } from '@/portainer/models/user'; +import { withUserProvider } from '@/react/test-utils/withUserProvider'; +import { TableSettings } from '@/react/kubernetes/datatables/DefaultDatatableSettings'; + +import { TableState } from '@@/datatables/useTableState'; + +import { Event } from '../../queries/types'; + +import { EventsDatatable } from './EventsDatatable'; + +// Mock the necessary hooks and dependencies +const mockTableState: TableState = { + sortBy: { id: 'Date', desc: true }, + pageSize: 10, + search: '', + autoRefreshRate: 0, + showSystemResources: false, + setSortBy: vi.fn(), + setPageSize: vi.fn(), + setSearch: vi.fn(), + setAutoRefreshRate: vi.fn(), + setShowSystemResources: vi.fn(), +}; + +vi.mock('../../datatables/default-kube-datatable-store', () => ({ + useKubeStore: () => mockTableState, +})); + +function renderComponent() { + const user = new UserViewModel({ Username: 'user' }); + + const events: Event[] = [ + { + type: 'Warning', + name: 'name', + message: 'not sure if this what you want to do', + namespace: 'default', + reason: 'unknown', + count: 1, + eventTime: new Date('2025-01-02T15:04:05Z'), + uid: '4500fc9c-0cc8-4695-b4c4-989ac021d1d6', + involvedObject: { + kind: 'configMap', + uid: '35', + name: 'name', + namespace: 'default', + }, + }, + ]; + + const Wrapped = withTestQueryProvider( + withUserProvider( + withTestRouter(() => ( + + )), + user + ) + ); + return { ...render(), events }; +} + +describe('EventsDatatable', () => { + it('should display events when data is loaded', async () => { + const { events } = renderComponent(); + const event = events[0]; + + expect(screen.getByText(event.message || '')).toBeInTheDocument(); + expect(screen.getAllByText(event.type || '')).toHaveLength(2); + expect(screen.getAllByText(event.involvedObject.kind || '')).toHaveLength( + 2 + ); + }); +}); diff --git a/app/react/kubernetes/components/EventsDatatable/EventsDatatable.tsx b/app/react/kubernetes/components/EventsDatatable/EventsDatatable.tsx index ffb92e7bd..fd063535a 100644 --- a/app/react/kubernetes/components/EventsDatatable/EventsDatatable.tsx +++ b/app/react/kubernetes/components/EventsDatatable/EventsDatatable.tsx @@ -1,7 +1,7 @@ -import { Event } from 'kubernetes-types/core/v1'; import { History } from 'lucide-react'; import { ReactNode } from 'react'; +import { Event } from '@/react/kubernetes/queries/types'; import { IndexOptional } from '@/react/kubernetes/configs/types'; import { TableSettings } from '@/react/kubernetes/datatables/DefaultDatatableSettings'; @@ -38,7 +38,7 @@ export function EventsDatatable({ isLoading={isLoading} title={title} titleIcon={titleIcon} - getRowId={(row) => row.metadata?.uid || ''} + getRowId={(row) => row.uid || ''} disableSelect renderTableSettings={() => ( diff --git a/app/react/kubernetes/components/EventsDatatable/ResourceEventsDatatable.tsx b/app/react/kubernetes/components/EventsDatatable/ResourceEventsDatatable.tsx index f763043c9..bf04d00d9 100644 --- a/app/react/kubernetes/components/EventsDatatable/ResourceEventsDatatable.tsx +++ b/app/react/kubernetes/components/EventsDatatable/ResourceEventsDatatable.tsx @@ -29,9 +29,7 @@ export function ResourceEventsDatatable({ params: { endpointId }, } = useCurrentStateAndParams(); - const params = resourceId - ? { fieldSelector: `involvedObject.uid=${resourceId}` } - : {}; + const params = resourceId ? { resourceId: `${resourceId}` } : {}; const resourceEventsQuery = useEvents(endpointId, { namespace, params, diff --git a/app/react/kubernetes/components/EventsDatatable/columns/eventType.tsx b/app/react/kubernetes/components/EventsDatatable/columns/eventType.tsx index 0d381d682..d2f24e3ab 100644 --- a/app/react/kubernetes/components/EventsDatatable/columns/eventType.tsx +++ b/app/react/kubernetes/components/EventsDatatable/columns/eventType.tsx @@ -1,5 +1,6 @@ import { Row } from '@tanstack/react-table'; -import { Event } from 'kubernetes-types/core/v1'; + +import { Event } from '@/react/kubernetes/queries/types'; import { Badge, BadgeType } from '@@/Badge'; import { filterHOC } from '@@/datatables/Filter'; diff --git a/app/react/kubernetes/components/EventsDatatable/columns/helper.ts b/app/react/kubernetes/components/EventsDatatable/columns/helper.ts index 23a453238..dbb2a57d5 100644 --- a/app/react/kubernetes/components/EventsDatatable/columns/helper.ts +++ b/app/react/kubernetes/components/EventsDatatable/columns/helper.ts @@ -1,4 +1,5 @@ import { createColumnHelper } from '@tanstack/react-table'; -import { Event } from 'kubernetes-types/core/v1'; + +import { Event } from '@/react/kubernetes/queries/types'; export const columnHelper = createColumnHelper(); diff --git a/app/react/kubernetes/components/EventsDatatable/columns/kind.tsx b/app/react/kubernetes/components/EventsDatatable/columns/kind.tsx index 641662291..efc69fe4e 100644 --- a/app/react/kubernetes/components/EventsDatatable/columns/kind.tsx +++ b/app/react/kubernetes/components/EventsDatatable/columns/kind.tsx @@ -1,5 +1,6 @@ import { Row } from '@tanstack/react-table'; -import { Event } from 'kubernetes-types/core/v1'; + +import { Event } from '@/react/kubernetes/queries/types'; import { filterHOC } from '@@/datatables/Filter'; diff --git a/app/react/kubernetes/helm/HelmApplicationView/HelmApplicationView.test.tsx b/app/react/kubernetes/helm/HelmApplicationView/HelmApplicationView.test.tsx index 89f522d3a..9cad1d046 100644 --- a/app/react/kubernetes/helm/HelmApplicationView/HelmApplicationView.test.tsx +++ b/app/react/kubernetes/helm/HelmApplicationView/HelmApplicationView.test.tsx @@ -184,15 +184,8 @@ describe( http.get('/api/endpoints/3/kubernetes/helm/test-release/history', () => HttpResponse.json(helmReleaseHistory) ), - http.get( - '/api/endpoints/3/kubernetes/api/v1/namespaces/default/events', - () => - HttpResponse.json({ - kind: 'EventList', - apiVersion: 'v1', - metadata: { resourceVersion: '12345' }, - items: [], - }) + http.get('/api/kubernetes/3/namespaces/default/events', () => + HttpResponse.json([]) ) ); @@ -236,15 +229,8 @@ describe( HttpResponse.error() ), // Add mock for events endpoint - http.get( - '/api/endpoints/3/kubernetes/api/v1/namespaces/default/events', - () => - HttpResponse.json({ - kind: 'EventList', - apiVersion: 'v1', - metadata: { resourceVersion: '12345' }, - items: [], - }) + http.get('/api/kubernetes/3/namespaces/default/events', () => + HttpResponse.json([]) ) ); @@ -274,15 +260,8 @@ describe( http.get('/api/endpoints/3/kubernetes/helm/test-release/history', () => HttpResponse.json(helmReleaseHistory) ), - http.get( - '/api/endpoints/3/kubernetes/api/v1/namespaces/default/events', - () => - HttpResponse.json({ - kind: 'EventList', - apiVersion: 'v1', - metadata: { resourceVersion: '12345' }, - items: [], - }) + http.get('/api/kubernetes/3/namespaces/default/events', () => + HttpResponse.json([]) ) ); diff --git a/app/react/kubernetes/helm/HelmApplicationView/ReleaseDetails/HelmEventsDatatable.test.tsx b/app/react/kubernetes/helm/HelmApplicationView/ReleaseDetails/HelmEventsDatatable.test.tsx index 08c48488a..67ff7d10b 100644 --- a/app/react/kubernetes/helm/HelmApplicationView/ReleaseDetails/HelmEventsDatatable.test.tsx +++ b/app/react/kubernetes/helm/HelmApplicationView/ReleaseDetails/HelmEventsDatatable.test.tsx @@ -1,7 +1,7 @@ import { render, screen, waitFor } from '@testing-library/react'; import { HttpResponse } from 'msw'; -import { Event, EventList } from 'kubernetes-types/core/v1'; +import { Event } from '@/react/kubernetes/queries/types'; import { server, http } from '@/setup-tests/server'; import { withTestQueryProvider } from '@/react/test-utils/withTestQuery'; import { withTestRouter } from '@/react/test-utils/withRouter'; @@ -56,136 +56,84 @@ const testResources: GenericResource[] = [ }, ]; -const mockEventsResponse: EventList = { - kind: 'EventList', - apiVersion: 'v1', - metadata: { - resourceVersion: '12345', +const mockEventsResponse: Event[] = [ + { + name: 'test-deployment-123456', + namespace: 'default', + reason: 'CreatedLoadBalancer', + eventTime: new Date('2023-01-01T00:00:00Z'), + uid: 'event-uid-1', + involvedObject: { + kind: 'Deployment', + name: 'test-deployment', + uid: 'test-deployment-uid', + namespace: 'default', + }, + message: 'Scaled up replica set test-deployment-abc123 to 1', + firstTimestamp: new Date('2023-01-01T00:00:00Z'), + lastTimestamp: new Date('2023-01-01T00:00:00Z'), + count: 1, + type: 'Normal', }, - items: [ - { - metadata: { - name: 'test-deployment-123456', - namespace: 'default', - uid: 'event-uid-1', - resourceVersion: '1000', - creationTimestamp: '2023-01-01T00:00:00Z', - }, - involvedObject: { - kind: 'Deployment', - namespace: 'default', - name: 'test-deployment', - uid: 'test-deployment-uid', - apiVersion: 'apps/v1', - resourceVersion: '2000', - }, - reason: 'ScalingReplicaSet', - message: 'Scaled up replica set test-deployment-abc123 to 1', - source: { - component: 'deployment-controller', - }, - firstTimestamp: '2023-01-01T00:00:00Z', - lastTimestamp: '2023-01-01T00:00:00Z', - count: 1, - type: 'Normal', - reportingComponent: 'deployment-controller', - reportingInstance: '', + { + name: 'test-service-123456', + namespace: 'default', + uid: 'event-uid-2', + eventTime: new Date('2023-01-01T00:00:00Z'), + involvedObject: { + kind: 'Service', + namespace: 'default', + name: 'test-service', + uid: 'test-service-uid', }, - { - metadata: { - name: 'test-service-123456', - namespace: 'default', - uid: 'event-uid-2', - resourceVersion: '1001', - creationTimestamp: '2023-01-01T00:00:00Z', - }, - involvedObject: { - kind: 'Service', - namespace: 'default', - name: 'test-service', - uid: 'test-service-uid', - apiVersion: 'v1', - resourceVersion: '2001', - }, - reason: 'CreatedLoadBalancer', - message: 'Created load balancer', - source: { - component: 'service-controller', - }, - firstTimestamp: '2023-01-01T00:00:00Z', - lastTimestamp: '2023-01-01T00:00:00Z', - count: 1, - type: 'Normal', - reportingComponent: 'service-controller', - reportingInstance: '', - }, - ], -}; + reason: 'CreatedLoadBalancer', + message: 'Created load balancer', + firstTimestamp: new Date('2023-01-01T00:00:00Z'), + lastTimestamp: new Date('2023-01-01T00:00:00Z'), + count: 1, + type: 'Normal', + }, +]; -const mixedEventsResponse: EventList = { - kind: 'EventList', - apiVersion: 'v1', - metadata: { - resourceVersion: '12345', +const mixedEventsResponse: Event[] = [ + { + name: 'test-deployment-123456', + namespace: 'default', + uid: 'event-uid-1', + eventTime: new Date('2023-01-01T00:00:00Z'), + involvedObject: { + kind: 'Deployment', + namespace: 'default', + name: 'test-deployment', + uid: 'test-deployment-uid', // This matches a resource UID + }, + reason: 'ScalingReplicaSet', + message: 'Scaled up replica set test-deployment-abc123 to 1', + + firstTimestamp: new Date('2023-01-01T00:00:00Z'), + lastTimestamp: new Date('2023-01-01T00:00:00Z'), + count: 1, + type: 'Normal', }, - items: [ - { - metadata: { - name: 'test-deployment-123456', - namespace: 'default', - uid: 'event-uid-1', - resourceVersion: '1000', - creationTimestamp: '2023-01-01T00:00:00Z', - }, - involvedObject: { - kind: 'Deployment', - namespace: 'default', - name: 'test-deployment', - uid: 'test-deployment-uid', // This matches a resource UID - apiVersion: 'apps/v1', - resourceVersion: '2000', - }, - reason: 'ScalingReplicaSet', - message: 'Scaled up replica set test-deployment-abc123 to 1', - source: { - component: 'deployment-controller', - }, - firstTimestamp: '2023-01-01T00:00:00Z', - lastTimestamp: '2023-01-01T00:00:00Z', - count: 1, - type: 'Normal', - reportingComponent: 'deployment-controller', - reportingInstance: '', + { + name: 'unrelated-pod-123456', + namespace: 'default', + uid: 'event-uid-3', + eventTime: new Date('2023-01-01T00:00:00Z'), + involvedObject: { + kind: 'Pod', + namespace: 'default', + name: 'unrelated-pod', + uid: 'unrelated-pod-uid', // This does NOT match any resource UIDs }, - { - metadata: { - name: 'unrelated-pod-123456', - namespace: 'default', - uid: 'event-uid-3', - resourceVersion: '1002', - creationTimestamp: '2023-01-01T00:00:00Z', - }, - involvedObject: { - kind: 'Pod', - namespace: 'default', - name: 'unrelated-pod', - uid: 'unrelated-pod-uid', // This does NOT match any resource UIDs - apiVersion: 'v1', - resourceVersion: '2002', - }, - reason: 'Scheduled', - message: 'Successfully assigned unrelated-pod to node', - source: { - component: 'default-scheduler', - }, - firstTimestamp: '2023-01-01T00:00:00Z', - lastTimestamp: '2023-01-01T00:00:00Z', - count: 1, - reportingComponent: 'scheduler', - reportingInstance: '', - }, - ], -}; + reason: 'Scheduled', + message: 'Successfully assigned unrelated-pod to node', + type: 'Normal', + firstTimestamp: new Date('2023-01-01T00:00:00Z'), + lastTimestamp: new Date('2023-01-01T00:00:00Z'), + count: 1, + }, +]; function renderComponent() { const user = new UserViewModel({ Username: 'user' }); @@ -229,7 +177,7 @@ describe('HelmEventsDatatable', () => { it('should correctly filter related events using the filterRelatedEvents function', () => { const filteredEvents = filterRelatedEvents( - mixedEventsResponse.items as Event[], + mixedEventsResponse as Event[], testResources ); diff --git a/app/react/kubernetes/helm/HelmApplicationView/ReleaseDetails/HelmEventsDatatable.tsx b/app/react/kubernetes/helm/HelmApplicationView/ReleaseDetails/HelmEventsDatatable.tsx index ced859f54..9dd161f5b 100644 --- a/app/react/kubernetes/helm/HelmApplicationView/ReleaseDetails/HelmEventsDatatable.tsx +++ b/app/react/kubernetes/helm/HelmApplicationView/ReleaseDetails/HelmEventsDatatable.tsx @@ -1,6 +1,6 @@ import { compact } from 'lodash'; -import { Event } from 'kubernetes-types/core/v1'; +import { Event } from '@/react/kubernetes/queries/types'; import { createStore } from '@/react/kubernetes/datatables/default-kube-datatable-store'; import { EventsDatatable } from '@/react/kubernetes/components/EventsDatatable'; import { useEvents } from '@/react/kubernetes/queries/useEvents'; diff --git a/app/react/kubernetes/queries/types.ts b/app/react/kubernetes/queries/types.ts new file mode 100644 index 000000000..4c8269c28 --- /dev/null +++ b/app/react/kubernetes/queries/types.ts @@ -0,0 +1,19 @@ +export type Event = { + type: string; + name: string; + reason: string; + message: string; + namespace: string; + eventTime: Date; + kind?: string; + count: number; + lastTimestamp?: Date; + firstTimestamp?: Date; + uid: string; + involvedObject: { + uid: string; + kind?: string; + name: string; + namespace: string; + }; +}; diff --git a/app/react/kubernetes/queries/useEvents.ts b/app/react/kubernetes/queries/useEvents.ts index f25c255db..47ea5c2c5 100644 --- a/app/react/kubernetes/queries/useEvents.ts +++ b/app/react/kubernetes/queries/useEvents.ts @@ -1,6 +1,6 @@ -import { EventList, Event } from 'kubernetes-types/core/v1'; import { useQuery } from '@tanstack/react-query'; +import { Event } from '@/react/kubernetes/queries/types'; import { EnvironmentId } from '@/react/portainer/environments/types'; import axios from '@/portainer/services/axios'; import { withGlobalError } from '@/react-tools/react-query'; @@ -13,10 +13,7 @@ type RequestOptions = { /** if undefined, events are fetched at the cluster scope */ namespace?: string; params?: { - /** https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors */ - labelSelector?: string; - /** https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors */ - fieldSelector?: string; + resourceId?: string; }; }; @@ -44,13 +41,13 @@ async function getEvents( ): Promise { const { namespace, params } = options ?? {}; try { - const { data } = await axios.get( + const { data } = await axios.get( buildUrl(environmentId, namespace), { params, } ); - return data.items; + return data; } catch (e) { throw parseKubernetesAxiosError(e, 'Unable to retrieve events'); } @@ -96,6 +93,6 @@ export function useEventWarningsCount( function buildUrl(environmentId: EnvironmentId, namespace?: string) { return namespace - ? `/endpoints/${environmentId}/kubernetes/api/v1/namespaces/${namespace}/events` - : `/endpoints/${environmentId}/kubernetes/api/v1/events`; + ? `/kubernetes/${environmentId}/namespaces/${namespace}/events` + : `/kubernetes/${environmentId}/events`; } From 731afbee4697ef4eb55c075b6b851f5ab9414fa6 Mon Sep 17 00:00:00 2001 From: Cara Ryan Date: Tue, 27 May 2025 15:20:28 +1200 Subject: [PATCH 12/68] feat(helm): filter on chart versions at API level [R8S-324] (#754) --- api/http/handler/helm/helm_repo_search.go | 11 ++- .../ChartActions/UpgradeButton.test.tsx | 10 ++ .../ChartActions/UpgradeButton.tsx | 95 +++++++++++++------ .../queries/useHelmRepositories.ts | 16 ++-- pkg/libhelm/options/search_repo_options.go | 6 +- pkg/libhelm/sdk/search_repo.go | 77 +++++++++++---- 6 files changed, 159 insertions(+), 56 deletions(-) diff --git a/api/http/handler/helm/helm_repo_search.go b/api/http/handler/helm/helm_repo_search.go index aab9c523d..c29423fa9 100644 --- a/api/http/handler/helm/helm_repo_search.go +++ b/api/http/handler/helm/helm_repo_search.go @@ -7,6 +7,7 @@ import ( "github.com/portainer/portainer/pkg/libhelm/options" httperror "github.com/portainer/portainer/pkg/libhttp/error" + "github.com/portainer/portainer/pkg/libhttp/request" "github.com/pkg/errors" ) @@ -17,6 +18,8 @@ import ( // @description **Access policy**: authenticated // @tags helm // @param repo query string true "Helm repository URL" +// @param chart query string false "Helm chart name" +// @param useCache query string false "If true will use cache to search" // @security ApiKeyAuth // @security jwt // @produce json @@ -32,13 +35,19 @@ func (handler *Handler) helmRepoSearch(w http.ResponseWriter, r *http.Request) * return httperror.BadRequest("Bad request", errors.New("missing `repo` query parameter")) } + chart, _ := request.RetrieveQueryParameter(r, "chart", false) + // If true will useCache to search, will always add to cache after + useCache, _ := request.RetrieveBooleanQueryParameter(r, "useCache", false) + _, err := url.ParseRequestURI(repo) if err != nil { return httperror.BadRequest("Bad request", errors.Wrap(err, fmt.Sprintf("provided URL %q is not valid", repo))) } searchOpts := options.SearchRepoOptions{ - Repo: repo, + Repo: repo, + Chart: chart, + UseCache: useCache, } result, err := handler.helmPackageManager.SearchRepo(searchOpts) diff --git a/app/react/kubernetes/helm/HelmApplicationView/ChartActions/UpgradeButton.test.tsx b/app/react/kubernetes/helm/HelmApplicationView/ChartActions/UpgradeButton.test.tsx index 4bea0bf47..190fef7b1 100644 --- a/app/react/kubernetes/helm/HelmApplicationView/ChartActions/UpgradeButton.test.tsx +++ b/app/react/kubernetes/helm/HelmApplicationView/ChartActions/UpgradeButton.test.tsx @@ -33,6 +33,8 @@ vi.mock('../queries/useHelmRepositories', () => ({ ], isInitialLoading: false, isError: false, + isFetching: false, + refetch: vi.fn(() => Promise.resolve([])), })), useHelmRepositories: vi.fn(() => ({ data: ['repo1', 'repo2'], @@ -81,6 +83,8 @@ describe('UpgradeButton', () => { data, isInitialLoading: false, isError: false, + isFetching: false, + refetch: vi.fn(() => Promise.resolve([])), }); renderButton(); @@ -94,6 +98,8 @@ describe('UpgradeButton', () => { data: [], isInitialLoading: true, isError: false, + isFetching: true, + refetch: vi.fn(() => Promise.resolve([])), }); renderButton(); @@ -109,6 +115,8 @@ describe('UpgradeButton', () => { data, isInitialLoading: false, isError: false, + isFetching: false, + refetch: vi.fn(() => Promise.resolve([])), }); renderButton(); @@ -139,6 +147,8 @@ describe('UpgradeButton', () => { ], isInitialLoading: false, isError: false, + isFetching: false, + refetch: vi.fn(() => Promise.resolve([])), }); renderButton({ release: mockRelease }); diff --git a/app/react/kubernetes/helm/HelmApplicationView/ChartActions/UpgradeButton.tsx b/app/react/kubernetes/helm/HelmApplicationView/ChartActions/UpgradeButton.tsx index 4c385c388..2c32fb69d 100644 --- a/app/react/kubernetes/helm/HelmApplicationView/ChartActions/UpgradeButton.tsx +++ b/app/react/kubernetes/helm/HelmApplicationView/ChartActions/UpgradeButton.tsx @@ -1,5 +1,6 @@ import { ArrowUp } from 'lucide-react'; import { useRouter } from '@uirouter/react'; +import { useState } from 'react'; import { EnvironmentId } from '@/react/portainer/environments/types'; import { notifySuccess } from '@/portainer/services/notifications'; @@ -41,26 +42,28 @@ export function UpgradeButton({ const updateHelmReleaseMutation = useUpdateHelmReleaseMutation(environmentId); const repositoriesQuery = useHelmRepositories(); + const [useCache, setUseCache] = useState(true); const helmRepoVersionsQuery = useHelmRepoVersions( release?.chart.metadata?.name || '', 60 * 60 * 1000, // 1 hour - repositoriesQuery.data + repositoriesQuery.data, + useCache ); const versions = helmRepoVersionsQuery.data; // Combined loading state - const isInitialLoading = - repositoriesQuery.isInitialLoading || - helmRepoVersionsQuery.isInitialLoading; + const isLoading = + repositoriesQuery.isInitialLoading || helmRepoVersionsQuery.isFetching; const isError = repositoriesQuery.isError || helmRepoVersionsQuery.isError; const latestVersion = useHelmRelease(environmentId, releaseName, namespace, { select: (data) => data.chart.metadata?.version, }); const latestVersionAvailable = versions[0]?.Version ?? ''; - const isNewVersionAvailable = + const isNewVersionAvailable = Boolean( latestVersion?.data && - semverCompare(latestVersionAvailable, latestVersion?.data) === 1; + semverCompare(latestVersionAvailable, latestVersion?.data) === 1 + ); const editableHelmRelease: UpdateHelmReleasePayload = { name: releaseName, @@ -70,6 +73,14 @@ export function UpgradeButton({ version: release?.chart.metadata?.version, }; + function handleRefreshVersions() { + if (!useCache) { + helmRepoVersionsQuery.refetch(); + } else { + setUseCache(false); + } + } + return (
openUpgradeForm(versions, release)} disabled={ versions.length === 0 || - isInitialLoading || + isLoading || isError || release?.info?.status?.startsWith('pending') } @@ -89,7 +100,7 @@ export function UpgradeButton({ > Upgrade - {versions.length === 0 && isInitialLoading && ( + {isLoading && ( )} - {versions.length === 0 && !isInitialLoading && !isError && ( + {!isLoading && !isError && ( - No versions available - - Portainer is unable to find any versions for this chart in the - repositories saved. Try adding a new repository which contains - the chart in the{' '} - - Helm repositories settings - -
- } - /> - - )} - {isNewVersionAvailable && ( - - New version available ({latestVersionAvailable}) + {getStatusMessage( + versions.length === 0, + latestVersionAvailable, + isNewVersionAvailable + )} + {versions.length === 0 && ( + + Portainer is unable to find any versions for this chart in the + repositories saved. Try adding a new repository which contains + the chart in the{' '} + + Helm repositories settings + + + } + /> + )} + )} @@ -164,4 +183,18 @@ export function UpgradeButton({ }, }); } + + function getStatusMessage( + hasNoAvailableVersions: boolean, + latestVersionAvailable: string, + isNewVersionAvailable: boolean + ): string { + if (hasNoAvailableVersions) { + return 'No versions available '; + } + if (isNewVersionAvailable) { + return `New version available (${latestVersionAvailable}) `; + } + return ''; + } } diff --git a/app/react/kubernetes/helm/HelmApplicationView/queries/useHelmRepositories.ts b/app/react/kubernetes/helm/HelmApplicationView/queries/useHelmRepositories.ts index 733b79dea..bf11eadb1 100644 --- a/app/react/kubernetes/helm/HelmApplicationView/queries/useHelmRepositories.ts +++ b/app/react/kubernetes/helm/HelmApplicationView/queries/useHelmRepositories.ts @@ -45,20 +45,21 @@ export function useHelmRepositories() { export function useHelmRepoVersions( chart: string, staleTime: number, - repositories: string[] = [] + repositories: string[] = [], + useCache: boolean = true ) { // Fetch versions from each repository in parallel as separate queries const versionQueries = useQueries({ queries: useMemo( () => repositories.map((repo) => ({ - queryKey: ['helm', 'repositories', chart, repo], - queryFn: () => getSearchHelmRepo(repo, chart), + queryKey: ['helm', 'repositories', chart, repo, useCache], + queryFn: () => getSearchHelmRepo(repo, chart, useCache), enabled: !!chart && repositories.length > 0, staleTime, ...withGlobalError(`Unable to retrieve versions from ${repo}`), })), - [repositories, chart, staleTime] + [repositories, chart, staleTime, useCache] ), }); @@ -72,6 +73,8 @@ export function useHelmRepoVersions( data: allVersions, isInitialLoading: versionQueries.some((q) => q.isLoading), isError: versionQueries.some((q) => q.isError), + isFetching: versionQueries.some((q) => q.isFetching), + refetch: () => Promise.all(versionQueries.map((q) => q.refetch())), }; } @@ -80,11 +83,12 @@ export function useHelmRepoVersions( */ async function getSearchHelmRepo( repo: string, - chart: string + chart: string, + useCache: boolean = true ): Promise { try { const { data } = await axios.get(`templates/helm`, { - params: { repo, chart }, + params: { repo, chart, useCache }, }); const versions = data.entries[chart]; return ( diff --git a/pkg/libhelm/options/search_repo_options.go b/pkg/libhelm/options/search_repo_options.go index 73acc5709..0b35c0bbd 100644 --- a/pkg/libhelm/options/search_repo_options.go +++ b/pkg/libhelm/options/search_repo_options.go @@ -3,6 +3,8 @@ package options import "net/http" type SearchRepoOptions struct { - Repo string `example:"https://charts.gitlab.io/"` - Client *http.Client `example:"&http.Client{Timeout: time.Second * 10}"` + Repo string `example:"https://charts.gitlab.io/"` + Client *http.Client `example:"&http.Client{Timeout: time.Second * 10}"` + Chart string `example:"my-chart"` + UseCache bool `example:"false"` } diff --git a/pkg/libhelm/sdk/search_repo.go b/pkg/libhelm/sdk/search_repo.go index 90dd98529..63015330c 100644 --- a/pkg/libhelm/sdk/search_repo.go +++ b/pkg/libhelm/sdk/search_repo.go @@ -4,6 +4,8 @@ import ( "net/url" "os" "path/filepath" + "sync" + "time" "github.com/pkg/errors" "github.com/portainer/portainer/pkg/libhelm/options" @@ -25,6 +27,17 @@ type RepoIndex struct { Generated string `json:"generated"` } +type RepoIndexCache struct { + Index *repo.IndexFile + Timestamp time.Time +} + +var ( + indexCache = make(map[string]RepoIndexCache) + cacheMutex sync.RWMutex + cacheDuration = 60 * time.Minute +) + // SearchRepo downloads the `index.yaml` file for specified repo, parses it and returns JSON to caller. func (hspm *HelmSDKPackageManager) SearchRepo(searchRepoOpts options.SearchRepoOptions) ([]byte, error) { // Validate input options @@ -53,6 +66,18 @@ func (hspm *HelmSDKPackageManager) SearchRepo(searchRepoOpts options.SearchRepoO return nil, err } + // Check cache first + if searchRepoOpts.UseCache { + cacheMutex.RLock() + if cached, exists := indexCache[repoURL.String()]; exists { + if time.Since(cached.Timestamp) < cacheDuration { + cacheMutex.RUnlock() + return convertAndMarshalIndex(cached.Index, searchRepoOpts.Chart) + } + } + cacheMutex.RUnlock() + } + // Set up Helm CLI environment repoSettings := cli.New() @@ -92,23 +117,21 @@ func (hspm *HelmSDKPackageManager) SearchRepo(searchRepoOpts options.SearchRepoO return nil, err } - // Convert the index file to our response format - result, err := convertIndexToResponse(indexFile) - if err != nil { - log.Error(). - Str("context", "HelmClient"). - Err(err). - Msg("Failed to convert index to response format") - return nil, errors.Wrap(err, "failed to convert index to response format") + // Update cache and remove old entries + cacheMutex.Lock() + indexCache[searchRepoOpts.Repo] = RepoIndexCache{ + Index: indexFile, + Timestamp: time.Now(), + } + for key, index := range indexCache { + if time.Since(index.Timestamp) > cacheDuration { + delete(indexCache, key) + } } - log.Debug(). - Str("context", "HelmClient"). - Str("repo", searchRepoOpts.Repo). - Int("entries_count", len(indexFile.Entries)). - Msg("Successfully searched repository") + cacheMutex.Unlock() - return json.Marshal(result) + return convertAndMarshalIndex(indexFile, searchRepoOpts.Chart) } // validateSearchRepoOptions validates the required search repository options. @@ -216,7 +239,7 @@ func loadIndexFile(indexPath string) (*repo.IndexFile, error) { } // convertIndexToResponse converts the Helm index file to our response format. -func convertIndexToResponse(indexFile *repo.IndexFile) (RepoIndex, error) { +func convertIndexToResponse(indexFile *repo.IndexFile, chartName string) (RepoIndex, error) { result := RepoIndex{ APIVersion: indexFile.APIVersion, Entries: make(map[string][]ChartInfo), @@ -225,7 +248,9 @@ func convertIndexToResponse(indexFile *repo.IndexFile) (RepoIndex, error) { // Convert Helm SDK types to our response types for name, charts := range indexFile.Entries { - result.Entries[name] = convertChartsToChartInfo(charts) + if chartName == "" || name == chartName { + result.Entries[name] = convertChartsToChartInfo(charts) + } } return result, nil @@ -349,3 +374,23 @@ func ensureHelmDirectoriesExist(settings *cli.EnvSettings) error { return nil } + +func convertAndMarshalIndex(indexFile *repo.IndexFile, chartName string) ([]byte, error) { + // Convert the index file to our response format + result, err := convertIndexToResponse(indexFile, chartName) + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Err(err). + Msg("Failed to convert index to response format") + return nil, errors.Wrap(err, "failed to convert index to response format") + } + + log.Debug(). + Str("context", "HelmClient"). + Str("repo", chartName). + Int("entries_count", len(indexFile.Entries)). + Msg("Successfully searched repository") + + return json.Marshal(result) +} From b767dcb27ed253b423facd2e04ef971985950fd3 Mon Sep 17 00:00:00 2001 From: Devon Steenberg Date: Fri, 30 May 2025 11:49:23 +1200 Subject: [PATCH 13/68] fix(proxy): whitelist headers for proxy to forward [BE-11819] (#665) --- api/http/proxy/factory/reverse_proxy.go | 23 ++++- api/http/proxy/factory/reverse_proxy_test.go | 90 ++++++++++++++++++-- 2 files changed, 102 insertions(+), 11 deletions(-) diff --git a/api/http/proxy/factory/reverse_proxy.go b/api/http/proxy/factory/reverse_proxy.go index d583d75fe..c40e6c485 100644 --- a/api/http/proxy/factory/reverse_proxy.go +++ b/api/http/proxy/factory/reverse_proxy.go @@ -7,6 +7,21 @@ import ( "strings" ) +// Note that we discard any non-canonical headers by design +var allowedHeaders = map[string]struct{}{ + "Accept": {}, + "Accept-Encoding": {}, + "Accept-Language": {}, + "Cache-Control": {}, + "Content-Length": {}, + "Content-Type": {}, + "Private-Token": {}, + "User-Agent": {}, + "X-Portaineragent-Target": {}, + "X-Portainer-Volumename": {}, + "X-Registry-Auth": {}, +} + // newSingleHostReverseProxyWithHostHeader is based on NewSingleHostReverseProxy // from golang.org/src/net/http/httputil/reverseproxy.go and merely sets the Host // HTTP header, which NewSingleHostReverseProxy deliberately preserves. @@ -15,7 +30,6 @@ func NewSingleHostReverseProxyWithHostHeader(target *url.URL) *httputil.ReverseP } func createDirector(target *url.URL) func(*http.Request) { - sensitiveHeaders := []string{"Cookie", "X-Csrf-Token"} targetQuery := target.RawQuery return func(req *http.Request) { req.URL.Scheme = target.Scheme @@ -32,8 +46,11 @@ func createDirector(target *url.URL) func(*http.Request) { req.Header.Set("User-Agent", "") } - for _, header := range sensitiveHeaders { - delete(req.Header, header) + for k := range req.Header { + if _, ok := allowedHeaders[k]; !ok { + // We use delete here instead of req.Header.Del because we want to delete non canonical headers. + delete(req.Header, k) + } } } } diff --git a/api/http/proxy/factory/reverse_proxy_test.go b/api/http/proxy/factory/reverse_proxy_test.go index 1a3d88ba0..6f23d75ec 100644 --- a/api/http/proxy/factory/reverse_proxy_test.go +++ b/api/http/proxy/factory/reverse_proxy_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + portainer "github.com/portainer/portainer/api" ) func Test_createDirector(t *testing.T) { @@ -23,12 +24,14 @@ func Test_createDirector(t *testing.T) { "GET", "https://agent-portainer.io/test?c=7", map[string]string{"Accept-Encoding": "gzip", "Accept": "application/json", "User-Agent": "something"}, + true, ), expectedReq: createRequest( t, "GET", "https://portainer.io/api/docker/test?a=5&b=6&c=7", map[string]string{"Accept-Encoding": "gzip", "Accept": "application/json", "User-Agent": "something"}, + true, ), }, { @@ -39,12 +42,14 @@ func Test_createDirector(t *testing.T) { "GET", "https://agent-portainer.io/test?c=7", map[string]string{"Accept-Encoding": "gzip", "Accept": "application/json"}, + true, ), expectedReq: createRequest( t, "GET", "https://portainer.io/api/docker/test?a=5&b=6&c=7", map[string]string{"Accept-Encoding": "gzip", "Accept": "application/json", "User-Agent": ""}, + true, ), }, { @@ -55,18 +60,83 @@ func Test_createDirector(t *testing.T) { "GET", "https://agent-portainer.io/test?c=7", map[string]string{ - "Accept-Encoding": "gzip", - "Accept": "application/json", - "User-Agent": "something", - "Cookie": "junk", - "X-Csrf-Token": "junk", + "Authorization": "secret", + "Proxy-Authorization": "secret", + "Cookie": "secret", + "X-Csrf-Token": "secret", + "X-Api-Key": "secret", + "Accept": "application/json", + "Accept-Encoding": "gzip", + "Accept-Language": "en-GB", + "Cache-Control": "None", + "Content-Length": "100", + "Content-Type": "application/json", + "Private-Token": "test-private-token", + "User-Agent": "test-user-agent", + "X-Portaineragent-Target": "test-agent-1", + "X-Portainer-Volumename": "test-volume-1", + "X-Registry-Auth": "test-registry-auth", }, + true, ), expectedReq: createRequest( t, "GET", "https://portainer.io/api/docker/test?a=5&b=6&c=7", - map[string]string{"Accept-Encoding": "gzip", "Accept": "application/json", "User-Agent": "something"}, + map[string]string{ + "Accept": "application/json", + "Accept-Encoding": "gzip", + "Accept-Language": "en-GB", + "Cache-Control": "None", + "Content-Length": "100", + "Content-Type": "application/json", + "Private-Token": "test-private-token", + "User-Agent": "test-user-agent", + "X-Portaineragent-Target": "test-agent-1", + "X-Portainer-Volumename": "test-volume-1", + "X-Registry-Auth": "test-registry-auth", + }, + true, + ), + }, + { + name: "Non canonical Headers", + target: createURL(t, "https://portainer.io/api/docker?a=5&b=6"), + req: createRequest( + t, + "GET", + "https://agent-portainer.io/test?c=7", + map[string]string{ + "Accept": "application/json", + "Accept-Encoding": "gzip", + "Accept-Language": "en-GB", + "Cache-Control": "None", + "Content-Length": "100", + "Content-Type": "application/json", + "Private-Token": "test-private-token", + "User-Agent": "test-user-agent", + portainer.PortainerAgentTargetHeader: "test-agent-1", + "X-Portainer-VolumeName": "test-volume-1", + "X-Registry-Auth": "test-registry-auth", + }, + false, + ), + expectedReq: createRequest( + t, + "GET", + "https://portainer.io/api/docker/test?a=5&b=6&c=7", + map[string]string{ + "Accept": "application/json", + "Accept-Encoding": "gzip", + "Accept-Language": "en-GB", + "Cache-Control": "None", + "Content-Length": "100", + "Content-Type": "application/json", + "Private-Token": "test-private-token", + "User-Agent": "test-user-agent", + "X-Registry-Auth": "test-registry-auth", + }, + true, ), }, } @@ -92,13 +162,17 @@ func createURL(t *testing.T, urlString string) *url.URL { return parsedURL } -func createRequest(t *testing.T, method, url string, headers map[string]string) *http.Request { +func createRequest(t *testing.T, method, url string, headers map[string]string, canonicalHeaders bool) *http.Request { req, err := http.NewRequest(method, url, nil) if err != nil { t.Fatalf("Failed to create http request: %s", err) } else { for k, v := range headers { - req.Header.Add(k, v) + if canonicalHeaders { + req.Header.Add(k, v) + } else { + req.Header[k] = []string{v} + } } } From 24ff7a79112712e80ca2dedebc55f46f64510e16 Mon Sep 17 00:00:00 2001 From: LP B Date: Fri, 30 May 2025 09:12:27 +0200 Subject: [PATCH 14/68] chore(deps): upgrade docker/cli to v28.2.1 | docker/docker to v28.2.1 | docker/compose to v2.36.2 (#758) --- .../test_data/output_24_to_latest.json | 5 - api/http/handler/docker/dashboard.go | 5 +- api/http/proxy/factory/docker/networks.go | 4 +- api/portainer.go | 3 +- go.mod | 183 ++++---- go.sum | 417 +++++++++--------- pkg/snapshot/docker.go | 3 +- 7 files changed, 324 insertions(+), 296 deletions(-) diff --git a/api/datastore/test_data/output_24_to_latest.json b/api/datastore/test_data/output_24_to_latest.json index b0078c326..40971c519 100644 --- a/api/datastore/test_data/output_24_to_latest.json +++ b/api/datastore/test_data/output_24_to_latest.json @@ -678,14 +678,11 @@ "Images": null, "Info": { "Architecture": "", - "BridgeNfIp6tables": false, - "BridgeNfIptables": false, "CDISpecDirs": null, "CPUSet": false, "CPUShares": false, "CgroupDriver": "", "ContainerdCommit": { - "Expected": "", "ID": "" }, "Containers": 0, @@ -709,7 +706,6 @@ "IndexServerAddress": "", "InitBinary": "", "InitCommit": { - "Expected": "", "ID": "" }, "Isolation": "", @@ -738,7 +734,6 @@ }, "RegistryConfig": null, "RuncCommit": { - "Expected": "", "ID": "" }, "Runtimes": null, diff --git a/api/http/handler/docker/dashboard.go b/api/http/handler/docker/dashboard.go index ad0399569..97d40e069 100644 --- a/api/http/handler/docker/dashboard.go +++ b/api/http/handler/docker/dashboard.go @@ -6,6 +6,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/image" + "github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/volume" portainer "github.com/portainer/portainer/api" @@ -116,12 +117,12 @@ func (h *Handler) dashboard(w http.ResponseWriter, r *http.Request) *httperror.H return err } - networks, err := cli.NetworkList(r.Context(), types.NetworkListOptions{}) + networks, err := cli.NetworkList(r.Context(), network.ListOptions{}) if err != nil { return httperror.InternalServerError("Unable to retrieve Docker networks", err) } - networks, err = utils.FilterByResourceControl(tx, networks, portainer.NetworkResourceControl, context, func(c types.NetworkResource) string { + networks, err = utils.FilterByResourceControl(tx, networks, portainer.NetworkResourceControl, context, func(c network.Summary) string { return c.Name }) if err != nil { diff --git a/api/http/proxy/factory/docker/networks.go b/api/http/proxy/factory/docker/networks.go index 95c96df81..cd94478d2 100644 --- a/api/http/proxy/factory/docker/networks.go +++ b/api/http/proxy/factory/docker/networks.go @@ -6,7 +6,7 @@ import ( portainer "github.com/portainer/portainer/api" - "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/network" "github.com/docker/docker/client" @@ -20,7 +20,7 @@ const ( ) func getInheritedResourceControlFromNetworkLabels(dockerClient *client.Client, endpointID portainer.EndpointID, networkID string, resourceControls []portainer.ResourceControl) (*portainer.ResourceControl, error) { - network, err := dockerClient.NetworkInspect(context.Background(), networkID, types.NetworkInspectOptions{}) + network, err := dockerClient.NetworkInspect(context.Background(), networkID, network.InspectOptions{}) if err != nil { return nil, err } diff --git a/api/portainer.go b/api/portainer.go index f63d04ddb..bb383e44c 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -9,6 +9,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/image" + "github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/system" "github.com/docker/docker/api/types/volume" gittypes "github.com/portainer/portainer/api/git/types" @@ -245,7 +246,7 @@ type ( DockerSnapshotRaw struct { Containers []DockerContainerSnapshot `json:"Containers" swaggerignore:"true"` Volumes volume.ListResponse `json:"Volumes" swaggerignore:"true"` - Networks []types.NetworkResource `json:"Networks" swaggerignore:"true"` + Networks []network.Summary `json:"Networks" swaggerignore:"true"` Images []image.Summary `json:"Images" swaggerignore:"true"` Info system.Info `json:"Info" swaggerignore:"true"` Version types.Version `json:"Version" swaggerignore:"true"` diff --git a/go.mod b/go.mod index 3193c560c..3a2ba0bf5 100644 --- a/go.mod +++ b/go.mod @@ -6,25 +6,25 @@ require ( github.com/Masterminds/semver v1.5.0 github.com/Microsoft/go-winio v0.6.2 github.com/VictoriaMetrics/fastcache v1.12.0 - github.com/aws/aws-sdk-go-v2 v1.24.1 - github.com/aws/aws-sdk-go-v2/credentials v1.16.16 + github.com/aws/aws-sdk-go-v2 v1.30.3 + github.com/aws/aws-sdk-go-v2/credentials v1.17.27 github.com/aws/aws-sdk-go-v2/service/ecr v1.24.1 - github.com/aws/smithy-go v1.19.0 + github.com/aws/smithy-go v1.20.3 github.com/cbroglie/mustache v1.4.0 - github.com/compose-spec/compose-go/v2 v2.4.5 + github.com/compose-spec/compose-go/v2 v2.6.4 github.com/containers/image/v5 v5.30.1 github.com/coreos/go-semver v0.3.1 github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 - github.com/docker/cli v27.4.0+incompatible - github.com/docker/compose/v2 v2.31.0 - github.com/docker/docker v27.4.0+incompatible + github.com/docker/cli v28.2.1+incompatible + github.com/docker/compose/v2 v2.36.2 + github.com/docker/docker v28.2.1+incompatible github.com/fvbommel/sortorder v1.1.0 github.com/g07cha/defender v0.0.0-20180505193036-5665c627c814 github.com/go-git/go-git/v5 v5.13.0 github.com/go-ldap/ldap/v3 v3.4.1 github.com/gofrs/uuid v4.2.0+incompatible github.com/golang-jwt/jwt/v4 v4.5.2 - github.com/google/go-cmp v0.6.0 + github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 github.com/gorilla/csrf v1.7.3 github.com/gorilla/mux v1.8.1 @@ -33,7 +33,7 @@ require ( github.com/hashicorp/golang-lru v0.5.4 github.com/joho/godotenv v1.4.0 github.com/jpillora/chisel v1.10.0 - github.com/klauspost/compress v1.17.11 + github.com/klauspost/compress v1.18.0 github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c github.com/opencontainers/go-digest v1.0.0 github.com/orcaman/concurrent-map v1.0.0 @@ -46,20 +46,20 @@ require ( github.com/stretchr/testify v1.10.0 github.com/urfave/negroni v1.0.0 github.com/viney-shih/go-lock v1.1.1 - go.etcd.io/bbolt v1.3.11 - golang.org/x/crypto v0.36.0 - golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 - golang.org/x/mod v0.21.0 - golang.org/x/oauth2 v0.27.0 - golang.org/x/sync v0.12.0 + go.etcd.io/bbolt v1.4.0 + golang.org/x/crypto v0.37.0 + golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 + golang.org/x/mod v0.24.0 + golang.org/x/oauth2 v0.29.0 + golang.org/x/sync v0.14.0 gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.17.3 - k8s.io/api v0.32.2 - k8s.io/apimachinery v0.32.2 + k8s.io/api v0.32.3 + k8s.io/apimachinery v0.32.3 k8s.io/cli-runtime v0.32.2 - k8s.io/client-go v0.32.2 + k8s.io/client-go v0.32.3 k8s.io/kubectl v0.32.2 k8s.io/metrics v0.32.2 software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78 @@ -69,11 +69,12 @@ require github.com/gorilla/securecookie v1.1.2 // indirect require ( dario.cat/mergo v1.0.1 // indirect - github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect + github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect github.com/AlecAivazis/survey/v2 v2.3.7 // indirect - github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c // indirect github.com/BurntSushi/toml v1.4.0 // indirect + github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect @@ -84,18 +85,19 @@ require ( github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/aws/aws-sdk-go-v2/config v1.26.6 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect + github.com/aws/aws-sdk-go-v2/config v1.27.27 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/buger/goterm v1.0.4 // indirect @@ -106,23 +108,25 @@ require ( github.com/cloudflare/circl v1.3.7 // indirect github.com/containerd/console v1.0.4 // indirect github.com/containerd/containerd v1.7.24 // indirect - github.com/containerd/containerd/api v1.7.19 // indirect - github.com/containerd/continuity v0.4.4 // indirect - github.com/containerd/errdefs v0.3.0 // indirect + github.com/containerd/containerd/api v1.9.0 // indirect + github.com/containerd/containerd/v2 v2.1.1 // indirect + github.com/containerd/continuity v0.4.5 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/containerd/platforms v0.2.1 // indirect - github.com/containerd/ttrpc v1.2.5 // indirect - github.com/containerd/typeurl/v2 v2.2.0 // indirect + github.com/containerd/platforms v1.0.0-rc.1 // indirect + github.com/containerd/ttrpc v1.2.7 // indirect + github.com/containerd/typeurl/v2 v2.2.3 // indirect github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect - github.com/containers/ocicrypt v1.1.10 // indirect + github.com/containers/ocicrypt v1.2.1 // indirect github.com/containers/storage v1.53.0 // indirect github.com/cyphar/filepath-securejoin v0.3.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/buildx v0.18.0 // indirect - github.com/docker/cli-docs-tool v0.8.0 // indirect + github.com/docker/buildx v0.24.0 // indirect + github.com/docker/cli-docs-tool v0.9.0 // indirect github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker-credential-helpers v0.8.2 // indirect + github.com/docker/docker-credential-helpers v0.9.3 // indirect github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect @@ -136,7 +140,7 @@ require ( github.com/fatih/color v1.15.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsevents v0.2.0 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.1 // indirect github.com/go-errors/errors v1.4.2 // indirect @@ -152,6 +156,7 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect @@ -161,17 +166,18 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/in-toto/in-toto-golang v0.9.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmoiron/sqlx v1.4.0 // indirect - github.com/jonboulle/clockwork v0.4.0 // indirect + github.com/jonboulle/clockwork v0.5.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/jpillora/ansi v1.0.3 // indirect github.com/jpillora/requestlog v1.0.0 // indirect @@ -187,8 +193,8 @@ require ( github.com/lithammer/dedent v1.1.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-shellwords v1.0.12 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/miekg/pkcs11 v1.1.1 // indirect @@ -198,37 +204,38 @@ require ( github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/moby/buildkit v0.17.2 // indirect + github.com/moby/buildkit v0.22.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/go-archive v0.1.0 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/spdystream v0.5.0 // indirect + github.com/moby/sys/atomicwriter v0.1.0 // indirect github.com/moby/sys/capability v0.4.0 // indirect github.com/moby/sys/mountinfo v0.7.2 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/signal v0.7.1 // indirect github.com/moby/sys/symlink v0.3.0 // indirect - github.com/moby/sys/user v0.3.0 // indirect + github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect - github.com/moby/term v0.5.0 // indirect + github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/opencontainers/runtime-spec v1.2.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/opencontainers/runtime-spec v1.2.1 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/rubenv/sql-migrate v1.7.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect @@ -241,57 +248,59 @@ require ( github.com/skeema/knownhosts v1.3.0 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/spf13/cast v1.7.0 // indirect - github.com/spf13/cobra v1.8.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/cobra v1.9.1 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect github.com/theupdateframework/notary v0.7.0 // indirect github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 // indirect github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect - github.com/tonistiigi/dchapes-mode v0.0.0-20241001053921-ca0759fec205 // indirect - github.com/tonistiigi/fsutil v0.0.0-20241028165955-397af5306b5c // indirect + github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect + github.com/tonistiigi/fsutil v0.0.0-20250417144416-3f76f8130144 // indirect github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 // indirect github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect github.com/ulikunitz/xz v0.5.11 // indirect - github.com/vbatts/tar-split v0.11.5 // indirect + github.com/vbatts/tar-split v0.12.1 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect + github.com/xhit/go-str2duration/v2 v2.1.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.46.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect - go.opentelemetry.io/otel v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.25.0 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/sdk v1.28.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.28.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect - go.uber.org/mock v0.5.0 // indirect - golang.org/x/net v0.38.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/term v0.30.0 // indirect - golang.org/x/text v0.23.0 // indirect - golang.org/x/time v0.7.0 // indirect - google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/grpc v1.68.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect - gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect + github.com/zclconf/go-cty v1.16.2 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect + go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect + go.opentelemetry.io/otel/metric v1.35.0 // indirect + go.opentelemetry.io/otel/sdk v1.35.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect + go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect + go.uber.org/mock v0.5.2 // indirect + golang.org/x/net v0.39.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/term v0.31.0 // indirect + golang.org/x/text v0.24.0 // indirect + golang.org/x/time v0.11.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect + google.golang.org/grpc v1.72.1 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect k8s.io/apiextensions-apiserver v0.32.2 // indirect - k8s.io/apiserver v0.32.2 // indirect - k8s.io/component-base v0.32.2 // indirect + k8s.io/apiserver v0.32.3 // indirect + k8s.io/component-base v0.32.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect @@ -301,5 +310,5 @@ require ( sigs.k8s.io/kustomize/kyaml v0.18.1 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect sigs.k8s.io/yaml v1.4.0 // indirect - tags.cncf.io/container-device-interface v0.8.0 // indirect + tags.cncf.io/container-device-interface v1.0.1 // indirect ) diff --git a/go.sum b/go.sum index 3f4be510b..b79c1d644 100644 --- a/go.sum +++ b/go.sum @@ -2,14 +2,12 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= -github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 h1:59MxjQVfjXsBpLy+dbd2/ELV5ofnUkUZBvWSC85sheA= -github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0/go.mod h1:OahwfttHWG6eJ0clwcfBAHoDI6X/LV/15hx/wlMZSrU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -17,6 +15,8 @@ github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0 github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e h1:rd4bOvKmDIx0WeTv9Qz+hghsgyjikFiPrseXHlKepO0= +github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e/go.mod h1:blbwPQh4DTlCZEfk1BLU4oMIhLda2U+A840Uag9DsZw= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= @@ -32,8 +32,8 @@ github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA4 github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Microsoft/hcsshim v0.12.5 h1:bpTInLlDy/nDRWFVcefDZZ1+U8tS+rz3MxjKgu9boo0= -github.com/Microsoft/hcsshim v0.12.5/go.mod h1:tIUGego4G1EN5Hb6KC90aDYiUI2dqLSTTOCjVNpOgZ8= +github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA= +github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= @@ -59,38 +59,40 @@ github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 h1:axBiC50cNZ github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2/go.mod h1:jnzFpU88PccN/tPPhCpnNU8mZphvKxYM9lLNkd8e+os= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= -github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= -github.com/aws/aws-sdk-go-v2/config v1.26.6 h1:Z/7w9bUqlRI0FFQpetVuFYEsjzE3h7fpU6HuGmfPL/o= -github.com/aws/aws-sdk-go-v2/config v1.26.6/go.mod h1:uKU6cnDmYCvJ+pxO9S4cWDb2yWWIH5hra+32hVh1MI4= -github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8= -github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 h1:n3GDfwqF2tzEkXlv5cuy4iy7LpKDtqDMcNLfZDu9rls= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= +github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY= +github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= +github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90= +github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg= +github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI= +github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= github.com/aws/aws-sdk-go-v2/service/ecr v1.24.1 h1:zqXEIhuR7RcHob2gxB/Xf1X4XuMS0vapn7xr+wCPrpg= github.com/aws/aws-sdk-go-v2/service/ecr v1.24.1/go.mod h1:+rWYJfms9p+D/wUN599tx3FtWvxoXCP25b8Porlrxcc= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= -github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= -github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ= +github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= +github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/beorn7/perks v0.0.0-20150223135152-b965b613227f/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -127,52 +129,58 @@ github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vc github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= -github.com/compose-spec/compose-go/v2 v2.4.5 h1:p4ih4Jb6VgGPLPxh3fSFVKAjFHtZd+7HVLCSFzcFx9Y= -github.com/compose-spec/compose-go/v2 v2.4.5/go.mod h1:lFN0DrMxIncJGYAXTfWuajfwj5haBJqrBkarHcnjJKc= +github.com/compose-spec/compose-go/v2 v2.6.4 h1:Gjv6x8eAhqwwWvoXIo0oZ4bDQBh0OMwdU7LUL9PDLiM= +github.com/compose-spec/compose-go/v2 v2.6.4/go.mod h1:vPlkN0i+0LjLf9rv52lodNMUTJF5YHVfHVGLLIP67NA= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= -github.com/containerd/cgroups/v3 v3.0.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0= -github.com/containerd/cgroups/v3 v3.0.2/go.mod h1:JUgITrzdFqp42uI2ryGA+ge0ap/nxzYgkGmIcetmErE= +github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= +github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins= github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/containerd/containerd v1.7.24 h1:zxszGrGjrra1yYJW/6rhm9cJ1ZQ8rkKBR48brqsa7nA= github.com/containerd/containerd v1.7.24/go.mod h1:7QUzfURqZWCZV7RLNEn1XjUCQLEf0bkaK4GjUaZehxw= -github.com/containerd/containerd/api v1.7.19 h1:VWbJL+8Ap4Ju2mx9c9qS1uFSB1OVYr5JJrW2yT5vFoA= -github.com/containerd/containerd/api v1.7.19/go.mod h1:fwGavl3LNwAV5ilJ0sbrABL44AQxmNjDRcwheXDb6Ig= -github.com/containerd/continuity v0.4.4 h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34PlCpMKII= -github.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= -github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= -github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/containerd/api v1.9.0 h1:HZ/licowTRazus+wt9fM6r/9BQO7S0vD5lMcWspGIg0= +github.com/containerd/containerd/api v1.9.0/go.mod h1:GhghKFmTR3hNtyznBoQ0EMWr9ju5AqHjcZPsSpTKutI= +github.com/containerd/containerd/v2 v2.1.1 h1:znnkm7Ajz8lg8BcIPMhc/9yjBRN3B+OkNKqKisKfwwM= +github.com/containerd/containerd/v2 v2.1.1/go.mod h1:zIfkQj4RIodclYQkX7GSSswSwgP8d/XxDOtOAoSDIGU= +github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= +github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/nydus-snapshotter v0.14.0 h1:6/eAi6d7MjaeLLuMO8Udfe5GVsDudmrDNO4SGETMBco= -github.com/containerd/nydus-snapshotter v0.14.0/go.mod h1:TT4jv2SnIDxEBu4H2YOvWQHPOap031ydTaHTuvc5VQk= -github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= -github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= -github.com/containerd/stargz-snapshotter v0.15.1 h1:fpsP4kf/Z4n2EYnU0WT8ZCE3eiKDwikDhL6VwxIlgeA= -github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G/ZW/0kEe2oEKCdS/ZxIyoCU= -github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk= -github.com/containerd/ttrpc v1.2.5 h1:IFckT1EFQoFBMG4c3sMdT8EP3/aKfumK1msY+Ze4oLU= -github.com/containerd/ttrpc v1.2.5/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= -github.com/containerd/typeurl/v2 v2.2.0 h1:6NBDbQzr7I5LHgp34xAXYF5DOTQDn05X58lsPEmzLso= -github.com/containerd/typeurl/v2 v2.2.0/go.mod h1:8XOOxnyatxSWuG8OfsZXVnAF4iZfedjS/8UHSPJnX4g= +github.com/containerd/nydus-snapshotter v0.15.0 h1:RqZRs1GPeM6T3wmuxJV9u+2Rg4YETVMwTmiDeX+iWC8= +github.com/containerd/nydus-snapshotter v0.15.0/go.mod h1:biq0ijpeZe0I5yZFSJyHzFSjjRZQ7P7y/OuHyd7hYOw= +github.com/containerd/platforms v1.0.0-rc.1 h1:83KIq4yy1erSRgOVHNk1HYdPvzdJ5CnsWaRoJX4C41E= +github.com/containerd/platforms v1.0.0-rc.1/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4= +github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y= +github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8= +github.com/containerd/stargz-snapshotter v0.16.3 h1:zbQMm8dRuPHEOD4OqAYGajJJUwCeUzt4j7w9Iaw58u4= +github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8= +github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU= +github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ= +github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= +github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= +github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= github.com/containers/image/v5 v5.30.1 h1:AKrQMgOKI1oKx5FW5eoU2xoNyzACajHGx1O3qxobvFM= github.com/containers/image/v5 v5.30.1/go.mod h1:gSD8MVOyqBspc0ynLsuiMR9qmt8UQ4jpVImjmK0uXfk= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= -github.com/containers/ocicrypt v1.1.10 h1:r7UR6o8+lyhkEywetubUUgcKFjOWOaWz8cEBrCPX0ic= -github.com/containers/ocicrypt v1.1.10/go.mod h1:YfzSSr06PTHQwSTUKqDSjish9BeW1E4HUmreluQcMd8= +github.com/containers/ocicrypt v1.2.1 h1:0qIOTT9DoYwcKmxSt8QJt+VzMY18onl9jUXsxpVhSmM= +github.com/containers/ocicrypt v1.2.1/go.mod h1:aD0AAqfMp0MtwqWgHM1bUwe1anx0VazI108CRrSKINQ= github.com/containers/storage v1.53.0 h1:VSES3C/u1pxjTJIXvLrSmyP7OBtDky04oGu07UvdTEA= github.com/containers/storage v1.53.0/go.mod h1:pujcoOSc+upx15Jirdkebhtd8uJiLwbSd/mYT6zDJK8= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= -github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -186,21 +194,21 @@ github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aB github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/buildx v0.18.0 h1:rSauXHeJt90NvtXrLK5J992Eb0UPJZs2vV3u1zTf1nE= -github.com/docker/buildx v0.18.0/go.mod h1:JGNSshOhHs5FhG3u51jXUf4lLOeD2QBIlJ2vaRB67p4= -github.com/docker/cli v27.4.0+incompatible h1:/nJzWkcI1MDMN+U+px/YXnQWJqnu4J+QKGTfD6ptiTc= -github.com/docker/cli v27.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/cli-docs-tool v0.8.0 h1:YcDWl7rQJC3lJ7WVZRwSs3bc9nka97QLWfyJQli8yJU= -github.com/docker/cli-docs-tool v0.8.0/go.mod h1:8TQQ3E7mOXoYUs811LiPdUnAhXrcVsBIrW21a5pUbdk= -github.com/docker/compose/v2 v2.31.0 h1:8Sm0c4MjIhksguxIA5koYMXoTJDAp/CaZ1cdZrMvMdw= -github.com/docker/compose/v2 v2.31.0/go.mod h1:oQq3UDEdsnB3AUO72AxaoeLbkCgmUu1+8tLzvmphmXA= +github.com/docker/buildx v0.24.0 h1:qiD+xktY+Fs3R79oz8M+7pbhip78qGLx6LBuVmyb+64= +github.com/docker/buildx v0.24.0/go.mod h1:vYkdBUBjFo/i5vUE0mkajGlk03gE0T/HaGXXhgIxo8E= +github.com/docker/cli v28.2.1+incompatible h1:AYyTcuwvhl9dXdyCiXlOGXiIqSNYzTmaDNpxIISPGsM= +github.com/docker/cli v28.2.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli-docs-tool v0.9.0 h1:CVwQbE+ZziwlPqrJ7LRyUF6GvCA+6gj7MTCsayaK9t0= +github.com/docker/cli-docs-tool v0.9.0/go.mod h1:ClrwlNW+UioiRyH9GiAOe1o3J/TsY3Tr1ipoypjAUtc= +github.com/docker/compose/v2 v2.36.2 h1:rxk1PUUbhbAS6HkGsYo9xUmMBpKtVwFMNCQjE4+i5fk= +github.com/docker/compose/v2 v2.36.2/go.mod h1:mZygkne+MAMu/e1B28PBFmG0Z0WefbxZ/IpcjSFdrw8= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v27.4.0+incompatible h1:I9z7sQ5qyzO0BfAb9IMOawRkAGxhYsidKiTMcm0DU+A= -github.com/docker/docker v27.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= -github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= +github.com/docker/docker v28.2.1+incompatible h1:aTSWVTDStpHbnRu0xBcGoJEjRf5EQKt6nik6Vif8sWw= +github.com/docker/docker v28.2.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= +github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= @@ -242,8 +250,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsevents v0.2.0 h1:BRlvlqjvNTfogHfeBOFvSC9N0Ddy+wzQCQukyoD7o/c= github.com/fsnotify/fsevents v0.2.0/go.mod h1:B3eEk39i4hz8y1zaWS/wPrAP4O6wkIl7HQwKBr1qH/w= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= @@ -305,6 +313,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -327,8 +337,8 @@ github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvR github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.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= @@ -353,8 +363,8 @@ github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -378,6 +388,8 @@ github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf h1:FtEj8sfIcaaBfAKrE1Cwb61YDtYq9JxChK1c7AKce7s= +github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf/go.mod h1:yrqSXGoD/4EKfF26AOGzscPOgTTJcyAwM2rpixWT+t4= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8 h1:CZkYfurY6KGhVtlalI4QwQ6T0Cu6iuY3e0x5RLu96WE= @@ -393,8 +405,8 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= -github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= +github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= +github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= 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/ansi v1.0.3 h1:nn4Jzti0EmRfDxm7JtEs5LzCbNwd5sv+0aE+LdS9/ZQ= @@ -417,8 +429,8 @@ github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4 github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c h1:N7A4JCA2G+j5fuFxCsJqjFU/sZe0mj8H0sSoSwbaikw= @@ -434,6 +446,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= @@ -445,8 +459,9 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= -github.com/magiconair/properties v1.5.3 h1:C8fxWnhYyME3n0klPOhVM7PtYUB3eV1W3DeFmN3j53Y= github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= +github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -456,10 +471,10 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= @@ -486,16 +501,20 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/buildkit v0.17.2 h1:/jgk/MuXbA7jeXMkknOpHYB+Ct4aNvQHkBB7SxD3D4U= -github.com/moby/buildkit v0.17.2/go.mod h1:vr5vltV8wt4F2jThbNOChfbAklJ0DOW11w36v210hOg= +github.com/moby/buildkit v0.22.0 h1:aWN06w1YGSVN1XfeZbj2ZbgY+zi5xDAjEFI8Cy9fTjA= +github.com/moby/buildkit v0.22.0/go.mod h1:j4pP5hxiTWcz7xuTK2cyxQislHl/N2WWHzOy43DlLJw= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= +github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk= github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I= github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= @@ -506,12 +525,12 @@ github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0 github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8= github.com/moby/sys/symlink v0.3.0 h1:GZX89mEZ9u53f97npBy4Rc3vJKj7JBDj/PN2I22GrNU= github.com/moby/sys/symlink v0.3.0/go.mod h1:3eNdhduHmYPcgsJtZXW1W4XUJdZGBIkttZ8xKqPUJq0= -github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= -github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= 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= @@ -542,12 +561,12 @@ github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1 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 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= -github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU= -github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww= +github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8= +github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U= 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= @@ -578,8 +597,8 @@ github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -588,23 +607,21 @@ github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQy github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 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.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= 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.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc h1:zAsgcP8MhzAbhMnB1QQ2O7ZhWYVGYSR2iVcjzQuPV+o= -github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc/go.mod h1:S8xSOnV3CgpNrWd0GQ/OoQfMtlg2uPRSuTzcSGrzwK8= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w= github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= @@ -642,13 +659,13 @@ github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94/go.mod h1:r2rcYCSwa1IEx github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431 h1:XTHrT015sxHyJ5FnQ0AeemSspZWaDq7DoTRW0EVsDCE= github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.0/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/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c h1:2EejZtjFjKJGk71ANb+wtFK5EjUzUkEM3R0xnp559xg= github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -677,10 +694,10 @@ github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 h1:QB54BJwA6x8 github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375/go.mod h1:xRroudyp5iVtxKqZCrA6n2TLFRBf8bmnjr1UD4x+z7g= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= -github.com/tonistiigi/dchapes-mode v0.0.0-20241001053921-ca0759fec205 h1:eUk79E1w8yMtXeHSzjKorxuC8qJOnyXQnLaJehxpJaI= -github.com/tonistiigi/dchapes-mode v0.0.0-20241001053921-ca0759fec205/go.mod h1:3Iuxbr0P7D3zUzBMAZB+ois3h/et0shEz0qApgHYGpY= -github.com/tonistiigi/fsutil v0.0.0-20241028165955-397af5306b5c h1:bQLsfX+uEPZUjyR2qmoJs5F8Z57bPVmF3IsUf22Xo9Y= -github.com/tonistiigi/fsutil v0.0.0-20241028165955-397af5306b5c/go.mod h1:Dl/9oEjK7IqnjAm21Okx/XIxUCFJzvh+XdVHUlBwXTw= +github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 h1:r0p7fK56l8WPequOaR3i9LBqfPtEdXIQbUTzT55iqT4= +github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323/go.mod h1:3Iuxbr0P7D3zUzBMAZB+ois3h/et0shEz0qApgHYGpY= +github.com/tonistiigi/fsutil v0.0.0-20250417144416-3f76f8130144 h1:k9tdF32oJYwtjzMx+D26M6eYiCaAPdJ7tyN7tF1oU5Q= +github.com/tonistiigi/fsutil v0.0.0-20250417144416-3f76f8130144/go.mod h1:BKdcez7BiVtBvIcef90ZPc6ebqIWr4JWD7+EvLm6J98= github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 h1:7I5c2Ig/5FgqkYOh/N87NzoyI9U15qUPXhDD8uCupv8= github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE= github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0= @@ -691,8 +708,8 @@ github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= -github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts= -github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk= +github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo= +github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= github.com/viney-shih/go-lock v1.1.1 h1:SwzDPPAiHpcwGCr5k8xD15d2gQSo8d4roRYd7TDV2eI= github.com/viney-shih/go-lock v1.1.1/go.mod h1:Yijm78Ljteb3kRiJrbLAxVntkUukGu5uzSxq/xV7OO8= github.com/weppos/publicsuffix-go v0.15.1-0.20210511084619-b1f36a2d6c0b h1:FsyNrX12e5BkplJq7wKOLk0+C6LZ+KGXvuEcKUYm5ss= @@ -708,6 +725,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -719,46 +738,50 @@ github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMzt github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70= +github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zmap/zcrypto v0.0.0-20210511125630-18f1e0152cfc h1:zkGwegkOW709y0oiAraH/3D8njopUR/pARHv4tZZ6pw= github.com/zmap/zcrypto v0.0.0-20210511125630-18f1e0152cfc/go.mod h1:FM4U1E3NzlNMRnSUTU3P1UdukWhYGifqEsjk9fn7BCk= github.com/zmap/zlint/v3 v3.1.0 h1:WjVytZo79m/L1+/Mlphl09WBob6YTGljN5IGWZFpAv0= github.com/zmap/zlint/v3 v3.1.0/go.mod h1:L7t8s3sEKkb0A2BxGy1IWrxt1ZATa1R4QfJZaQOD3zU= -go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= -go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= +go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= +go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= -go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.46.1 h1:gbhw/u49SS3gkPWiYweQNJGm/uJN5GkI/FrosxSHT7A= -go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.46.1/go.mod h1:GnOaBaFQ2we3b9AGWJpsBa7v1S5RlQzlC3O7dRMxZhM= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 h1:jd0+5t/YynESZqsSyPz+7PAFdEop0dlN0+PkyHYo8oI= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0/go.mod h1:U707O40ee1FpQGyhvqnzmCJm1Wh6OX6GGBVn0E6Uyyk= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0 h1:bflGWrfYyuulcdxf14V6n9+CoQcu5SAAdHmDPAJnlps= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0/go.mod h1:qcTO4xHAxZLaLxPd60TdE88rxtItPHgHWqOhOGRr0as= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.25.0 h1:Mbi5PKN7u322woPa85d7ebZ+SOvEoPvoiBu+ryHWgfA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.25.0/go.mod h1:e7ciERRhZaOZXVjx5MiL8TK5+Xv7G5Gv5PA2ZDEJdL8= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= -go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= -go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= -go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0 h1:4BZHA+B1wXEQoGNHxW8mURaLhcdGwvRnmhGbm+odRbc= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0/go.mod h1:3qi2EEwMgB4xnKgPLqsDP3j9qxnHDZeHsnAxfjQqTko= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 h1:FZ6ei8GFW7kyPYdxJaV2rgI6M+4tvZzhYsQ2wgyVC08= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0/go.mod h1:MdEu/mC6j3D+tTEfvI15b5Ci2Fn7NneJ71YMoiS3tpI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0 h1:ZsXq73BERAiNuuFXYqP4MR5hBrjXfMGSO+Cx7qoOZiM= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0/go.mod h1:hg1zaDMpyZJuUzjFxFsRYBoccE86tM9Uf4IqNMUxvrY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= -go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= +go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/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-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -770,30 +793,29 @@ golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= +golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= 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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= -golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= +golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -802,8 +824,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -830,49 +852,46 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= +golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= 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= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0= +google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= -google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= +google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y= -gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UDAkHu8BrjI= gopkg.in/cenkalti/backoff.v2 v2.2.1 h1:eJ9UAg01/HIHG987TwxvnzK2MgxXq97YY6rYDpY9aII= gopkg.in/cenkalti/backoff.v2 v2.2.1/go.mod h1:S0QdOvT2AlerfSBkp0O+dk+bbIMaNbEmVk876gPCthU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -886,6 +905,8 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1 h1:d4KQkxAaAiRY2h5Zqis161Pv91A37uZyJOx73duwUwM= gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1/go.mod h1:WbjuEoo1oadwzQ4apSDU+JTvmllEHtsNHS6y7vFc7iw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -901,24 +922,24 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= helm.sh/helm/v3 v3.17.3 h1:3n5rW3D0ArjFl0p4/oWO8IbY/HKaNNwJtOQFdH2AZHg= helm.sh/helm/v3 v3.17.3/go.mod h1:+uJKMH/UiMzZQOALR3XUf3BLIoczI2RKKD6bMhPh4G8= -k8s.io/api v0.32.2 h1:bZrMLEkgizC24G9eViHGOPbW+aRo9duEISRIJKfdJuw= -k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y= +k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= +k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= k8s.io/apiextensions-apiserver v0.32.2 h1:2YMk285jWMk2188V2AERy5yDwBYrjgWYggscghPCvV4= k8s.io/apiextensions-apiserver v0.32.2/go.mod h1:GPwf8sph7YlJT3H6aKUWtd0E+oyShk/YHWQHf/OOgCA= -k8s.io/apimachinery v0.32.2 h1:yoQBR9ZGkA6Rgmhbp/yuT9/g+4lxtsGYwW6dR6BDPLQ= -k8s.io/apimachinery v0.32.2/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/apiserver v0.32.2 h1:WzyxAu4mvLkQxwD9hGa4ZfExo3yZZaYzoYvvVDlM6vw= -k8s.io/apiserver v0.32.2/go.mod h1:PEwREHiHNU2oFdte7BjzA1ZyjWjuckORLIK/wLV5goM= +k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= +k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/apiserver v0.32.3 h1:kOw2KBuHOA+wetX1MkmrxgBr648ksz653j26ESuWNY8= +k8s.io/apiserver v0.32.3/go.mod h1:q1x9B8E/WzShF49wh3ADOh6muSfpmFL0I2t+TG0Zdgc= k8s.io/cli-runtime v0.32.2 h1:aKQR4foh9qeyckKRkNXUccP9moxzffyndZAvr+IXMks= k8s.io/cli-runtime v0.32.2/go.mod h1:a/JpeMztz3xDa7GCyyShcwe55p8pbcCVQxvqZnIwXN8= -k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA= -k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94= -k8s.io/component-base v0.32.2 h1:1aUL5Vdmu7qNo4ZsE+569PV5zFatM9hl+lb3dEea2zU= -k8s.io/component-base v0.32.2/go.mod h1:PXJ61Vx9Lg+P5mS8TLd7bCIr+eMJRQTyXe8KvkrvJq0= +k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= +k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= +k8s.io/component-base v0.32.3 h1:98WJvvMs3QZ2LYHBzvltFSeJjEx7t5+8s71P7M74u8k= +k8s.io/component-base v0.32.3/go.mod h1:LWi9cR+yPAv7cu2X9rZanTiFKB2kHA+JjmhkKjCZRpI= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= @@ -943,5 +964,5 @@ sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78 h1:SqYE5+A2qvRhErbsXFfUEUmpWEKxxRSMgGLkvRAFOV4= software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78/go.mod h1:B7Wf0Ya4DHF9Yw+qfZuJijQYkWicqDa+79Ytmmq3Kjg= -tags.cncf.io/container-device-interface v0.8.0 h1:8bCFo/g9WODjWx3m6EYl3GfUG31eKJbaggyBDxEldRc= -tags.cncf.io/container-device-interface v0.8.0/go.mod h1:Apb7N4VdILW0EVdEMRYXIDVRZfNJZ+kmEUss2kRRQ6Y= +tags.cncf.io/container-device-interface v1.0.1 h1:KqQDr4vIlxwfYh0Ed/uJGVgX+CHAkahrgabg6Q8GYxc= +tags.cncf.io/container-device-interface v1.0.1/go.mod h1:JojJIOeW3hNbcnOH2q0NrWNha/JuHoDZcmYxAZwb2i0= diff --git a/pkg/snapshot/docker.go b/pkg/snapshot/docker.go index 810dbeaa9..deaabdb08 100644 --- a/pkg/snapshot/docker.go +++ b/pkg/snapshot/docker.go @@ -16,6 +16,7 @@ import ( "github.com/docker/docker/api/types" dockercontainer "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/image" + "github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/volume" "github.com/docker/docker/client" "github.com/docker/docker/pkg/stdcopy" @@ -244,7 +245,7 @@ func dockerSnapshotVolumes(snapshot *portainer.DockerSnapshot, cli *client.Clien } func dockerSnapshotNetworks(snapshot *portainer.DockerSnapshot, cli *client.Client) error { - networks, err := cli.NetworkList(context.Background(), types.NetworkListOptions{}) + networks, err := cli.NetworkList(context.Background(), network.ListOptions{}) if err != nil { return err } From caac45b834db853fc42cdf0517b0ee674f5ba08e Mon Sep 17 00:00:00 2001 From: James Player Date: Thu, 5 Jun 2025 10:14:39 +1200 Subject: [PATCH 15/68] feat(UI): Add repository url to Helm chart installation list items (#769) --- .../HelmTemplates/HelmTemplatesListItem.tsx | 15 ++++--------- .../HelmTemplatesSelectedItem.test.tsx | 2 +- .../HelmTemplatesSelectedItem.tsx | 22 +++++-------------- 3 files changed, 11 insertions(+), 28 deletions(-) diff --git a/app/react/kubernetes/helm/HelmTemplates/HelmTemplatesListItem.tsx b/app/react/kubernetes/helm/HelmTemplates/HelmTemplatesListItem.tsx index de8272bcb..a1ee6a444 100644 --- a/app/react/kubernetes/helm/HelmTemplates/HelmTemplatesListItem.tsx +++ b/app/react/kubernetes/helm/HelmTemplates/HelmTemplatesListItem.tsx @@ -2,8 +2,6 @@ import React from 'react'; import { FallbackImage } from '@/react/components/FallbackImage'; -import Svg from '@@/Svg'; - import { Chart } from '../types'; import { HelmIcon } from './HelmIcon'; @@ -40,15 +38,10 @@ export function HelmTemplatesListItem(props: HelmTemplatesListItemProps) {
- - {model.name} - - - - - Helm - - +
+
{model.name}
+
{model.repo}
+
{actions} diff --git a/app/react/kubernetes/helm/HelmTemplates/HelmTemplatesSelectedItem.test.tsx b/app/react/kubernetes/helm/HelmTemplates/HelmTemplatesSelectedItem.test.tsx index 215838ff8..f7328cc4d 100644 --- a/app/react/kubernetes/helm/HelmTemplates/HelmTemplatesSelectedItem.test.tsx +++ b/app/react/kubernetes/helm/HelmTemplates/HelmTemplatesSelectedItem.test.tsx @@ -99,7 +99,7 @@ describe('HelmTemplatesSelectedItem', () => { expect(screen.getByText('test-chart')).toBeInTheDocument(); expect(screen.getByText('Test Chart Description')).toBeInTheDocument(); expect(screen.getByText('Clear selection')).toBeInTheDocument(); - expect(screen.getByText('Helm')).toBeInTheDocument(); + expect(screen.getByText('https://example.com')).toBeInTheDocument(); }); it('should toggle custom values editor', async () => { diff --git a/app/react/kubernetes/helm/HelmTemplates/HelmTemplatesSelectedItem.tsx b/app/react/kubernetes/helm/HelmTemplates/HelmTemplatesSelectedItem.tsx index baffb9655..837659fcd 100644 --- a/app/react/kubernetes/helm/HelmTemplates/HelmTemplatesSelectedItem.tsx +++ b/app/react/kubernetes/helm/HelmTemplates/HelmTemplatesSelectedItem.tsx @@ -10,7 +10,6 @@ import { useCanExit } from '@/react/hooks/useCanExit'; import { Widget } from '@@/Widget'; import { Button } from '@@/buttons/Button'; import { FallbackImage } from '@@/FallbackImage'; -import Svg from '@@/Svg'; import { Icon } from '@@/Icon'; import { WebEditorForm } from '@@/WebEditorForm'; import { confirmGenericDiscard } from '@@/modals/confirm'; @@ -100,22 +99,13 @@ export function HelmTemplatesSelectedItem({ className="h-16 w-16" />
-
- - - {selectedChart.name} - - - - - {' '} - Helm - - -
-
- {selectedChart.description} +
+
{selectedChart.name}
+
+ {selectedChart.repo} +
+
{selectedChart.description}
From a9061e525892b981013c1d0f85680374aa71f471 Mon Sep 17 00:00:00 2001 From: Ali <83188384+testA113@users.noreply.github.com> Date: Thu, 5 Jun 2025 13:13:45 +1200 Subject: [PATCH 16/68] feat(helm): enhance helm chart install [r8s-341] (#766) --- api/http/handler/helm/helm_show.go | 7 + app/kubernetes/views/deploy/deploy.html | 10 +- .../custom-template-selector.html | 4 +- app/portainer/react/components/index.ts | 1 + .../CodeEditor/CodeEditor.module.css | 8 +- .../components/CodeEditor/CodeEditor.tsx | 60 +++--- .../CodeEditor/ShortcutsTooltip.tsx | 52 +++++ app/react/components/ExternalLink.tsx | 32 +++ app/react/components/WebEditorForm.tsx | 60 +----- app/react/components/modals/Modal/Modal.tsx | 3 +- .../DeployView/StackName/StackName.tsx | 2 +- .../ChartActions/UpgradeButton.test.tsx | 49 +++-- .../ChartActions/UpgradeButton.tsx | 154 ++++++++------- .../ChartActions/UpgradeHelmModal.tsx | 156 ++++++++------- .../HelmTemplates/HelmInstallForm.test.tsx | 174 ++++++++++++++++ .../helm/HelmTemplates/HelmInstallForm.tsx | 106 ++++++++++ .../HelmTemplates/HelmInstallInnerForm.tsx | 86 ++++++++ .../helm/HelmTemplates/HelmTemplates.tsx | 18 +- .../HelmTemplatesSelectedItem.test.tsx | 86 +------- .../HelmTemplatesSelectedItem.tsx | 185 +++--------------- .../queries/useHelmChartInstall.ts | 40 ---- .../kubernetes/helm/HelmTemplates/types.ts | 4 + .../helm/components/HelmValuesInput.tsx | 80 ++++++++ .../queries/useHelmChartValues.ts | 22 ++- .../queries/useHelmRepositories.ts | 2 +- .../queries/useUpdateHelmReleaseMutation.ts | 11 +- app/react/kubernetes/helm/types.ts | 11 ++ pkg/libhelm/options/show_options.go | 1 + pkg/libhelm/sdk/show.go | 2 +- 29 files changed, 864 insertions(+), 562 deletions(-) create mode 100644 app/react/components/CodeEditor/ShortcutsTooltip.tsx create mode 100644 app/react/components/ExternalLink.tsx create mode 100644 app/react/kubernetes/helm/HelmTemplates/HelmInstallForm.test.tsx create mode 100644 app/react/kubernetes/helm/HelmTemplates/HelmInstallForm.tsx create mode 100644 app/react/kubernetes/helm/HelmTemplates/HelmInstallInnerForm.tsx delete mode 100644 app/react/kubernetes/helm/HelmTemplates/queries/useHelmChartInstall.ts create mode 100644 app/react/kubernetes/helm/HelmTemplates/types.ts create mode 100644 app/react/kubernetes/helm/components/HelmValuesInput.tsx rename app/react/kubernetes/helm/{HelmTemplates => }/queries/useHelmChartValues.ts (54%) rename app/react/kubernetes/helm/{HelmApplicationView => }/queries/useHelmRepositories.ts (97%) rename app/react/kubernetes/helm/{HelmApplicationView => }/queries/useUpdateHelmReleaseMutation.ts (84%) diff --git a/api/http/handler/helm/helm_show.go b/api/http/handler/helm/helm_show.go index 591c57922..f139827b8 100644 --- a/api/http/handler/helm/helm_show.go +++ b/api/http/handler/helm/helm_show.go @@ -20,6 +20,7 @@ import ( // @tags helm // @param repo query string true "Helm repository URL" // @param chart query string true "Chart name" +// @param version query string true "Chart version" // @param command path string true "chart/values/readme" // @security ApiKeyAuth // @security jwt @@ -45,6 +46,11 @@ func (handler *Handler) helmShow(w http.ResponseWriter, r *http.Request) *httper return httperror.BadRequest("Bad request", errors.New("missing `chart` query parameter")) } + version, err := request.RetrieveQueryParameter(r, "version", true) + if err != nil { + return httperror.BadRequest("Bad request", errors.Wrap(err, fmt.Sprintf("provided version %q is not valid", version))) + } + cmd, err := request.RetrieveRouteVariableValue(r, "command") if err != nil { cmd = "all" @@ -55,6 +61,7 @@ func (handler *Handler) helmShow(w http.ResponseWriter, r *http.Request) *httper OutputFormat: options.ShowOutputFormat(cmd), Chart: chart, Repo: repo, + Version: version, } result, err := handler.helmPackageManager.Show(showOptions) if err != nil { diff --git a/app/kubernetes/views/deploy/deploy.html b/app/kubernetes/views/deploy/deploy.html index 5eda2d3a7..d57d0caa7 100644 --- a/app/kubernetes/views/deploy/deploy.html +++ b/app/kubernetes/views/deploy/deploy.html @@ -31,7 +31,7 @@ -
+