diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go index 88e21f17b..068f31565 100644 --- a/api/cmd/portainer/main.go +++ b/api/cmd/portainer/main.go @@ -49,6 +49,7 @@ import ( "github.com/portainer/portainer/pkg/build" "github.com/portainer/portainer/pkg/featureflags" "github.com/portainer/portainer/pkg/libhelm" + libhelmtypes "github.com/portainer/portainer/pkg/libhelm/types" "github.com/portainer/portainer/pkg/libstack/compose" "github.com/gofrs/uuid" @@ -169,8 +170,8 @@ func initKubernetesDeployer(kubernetesTokenCacheManager *kubeproxy.TokenCacheMan return exec.NewKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, signatureService, proxyManager, assetsPath) } -func initHelmPackageManager(assetsPath string) (libhelm.HelmPackageManager, error) { - return libhelm.NewHelmPackageManager(libhelm.HelmConfig{BinaryPath: assetsPath}) +func initHelmPackageManager() (libhelmtypes.HelmPackageManager, error) { + return libhelm.NewHelmPackageManager() } func initAPIKeyService(datastore dataservices.DataStore) apikey.APIKeyService { @@ -437,7 +438,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server { proxyManager.NewProxyFactory(dataStore, signatureService, reverseTunnelService, dockerClientFactory, kubernetesClientFactory, kubernetesTokenCacheManager, gitService, snapshotService) - helmPackageManager, err := initHelmPackageManager(*flags.Assets) + helmPackageManager, err := initHelmPackageManager() if err != nil { log.Fatal().Err(err).Msg("failed initializing helm package manager") } diff --git a/api/database/boltdb/json_test.go b/api/database/boltdb/json_test.go index ba0863efd..577aa2cfd 100644 --- a/api/database/boltdb/json_test.go +++ b/api/database/boltdb/json_test.go @@ -10,7 +10,7 @@ import ( ) const ( - jsonobject = `{"LogoURL":"","BlackListedLabels":[],"AuthenticationMethod":1,"InternalAuthSettings": {"RequiredPasswordLength": 12}"LDAPSettings":{"AnonymousMode":true,"ReaderDN":"","URL":"","TLSConfig":{"TLS":false,"TLSSkipVerify":false},"StartTLS":false,"SearchSettings":[{"BaseDN":"","Filter":"","UserNameAttribute":""}],"GroupSearchSettings":[{"GroupBaseDN":"","GroupFilter":"","GroupAttribute":""}],"AutoCreateUsers":true},"OAuthSettings":{"ClientID":"","AccessTokenURI":"","AuthorizationURI":"","ResourceURI":"","RedirectURI":"","UserIdentifier":"","Scopes":"","OAuthAutoCreateUsers":false,"DefaultTeamID":0,"SSO":true,"LogoutURI":"","KubeSecretKey":"j0zLVtY/lAWBk62ByyF0uP80SOXaitsABP0TTJX8MhI="},"OpenAMTConfiguration":{"Enabled":false,"MPSServer":"","MPSUser":"","MPSPassword":"","MPSToken":"","CertFileContent":"","CertFileName":"","CertFilePassword":"","DomainName":""},"FeatureFlagSettings":{},"SnapshotInterval":"5m","TemplatesURL":"https://raw.githubusercontent.com/portainer/templates/master/templates-2.0.json","EdgeAgentCheckinInterval":5,"EnableEdgeComputeFeatures":false,"UserSessionTimeout":"8h","KubeconfigExpiry":"0","EnableTelemetry":true,"HelmRepositoryURL":"https://kubernetes.github.io/ingress-nginx","KubectlShellImage":"portainer/kubectl-shell","DisplayDonationHeader":false,"DisplayExternalContributors":false,"EnableHostManagementFeatures":false,"AllowVolumeBrowserForRegularUsers":false,"AllowBindMountsForRegularUsers":false,"AllowPrivilegedModeForRegularUsers":false,"AllowHostNamespaceForRegularUsers":false,"AllowStackManagementForRegularUsers":false,"AllowDeviceMappingForRegularUsers":false,"AllowContainerCapabilitiesForRegularUsers":false}` + jsonobject = `{"LogoURL":"","BlackListedLabels":[],"AuthenticationMethod":1,"InternalAuthSettings": {"RequiredPasswordLength": 12}"LDAPSettings":{"AnonymousMode":true,"ReaderDN":"","URL":"","TLSConfig":{"TLS":false,"TLSSkipVerify":false},"StartTLS":false,"SearchSettings":[{"BaseDN":"","Filter":"","UserNameAttribute":""}],"GroupSearchSettings":[{"GroupBaseDN":"","GroupFilter":"","GroupAttribute":""}],"AutoCreateUsers":true},"OAuthSettings":{"ClientID":"","AccessTokenURI":"","AuthorizationURI":"","ResourceURI":"","RedirectURI":"","UserIdentifier":"","Scopes":"","OAuthAutoCreateUsers":false,"DefaultTeamID":0,"SSO":true,"LogoutURI":"","KubeSecretKey":"j0zLVtY/lAWBk62ByyF0uP80SOXaitsABP0TTJX8MhI="},"OpenAMTConfiguration":{"Enabled":false,"MPSServer":"","MPSUser":"","MPSPassword":"","MPSToken":"","CertFileContent":"","CertFileName":"","CertFilePassword":"","DomainName":""},"FeatureFlagSettings":{},"SnapshotInterval":"5m","TemplatesURL":"https://raw.githubusercontent.com/portainer/templates/master/templates-2.0.json","EdgeAgentCheckinInterval":5,"EnableEdgeComputeFeatures":false,"UserSessionTimeout":"8h","KubeconfigExpiry":"0","EnableTelemetry":true,"HelmRepositoryURL":"https://charts.bitnami.com/bitnami","KubectlShellImage":"portainer/kubectl-shell","DisplayDonationHeader":false,"DisplayExternalContributors":false,"EnableHostManagementFeatures":false,"AllowVolumeBrowserForRegularUsers":false,"AllowBindMountsForRegularUsers":false,"AllowPrivilegedModeForRegularUsers":false,"AllowHostNamespaceForRegularUsers":false,"AllowStackManagementForRegularUsers":false,"AllowDeviceMappingForRegularUsers":false,"AllowContainerCapabilitiesForRegularUsers":false}` passphrase = "my secret key" ) diff --git a/api/datastore/test_data/output_24_to_latest.json b/api/datastore/test_data/output_24_to_latest.json index 8c5dc01e7..89d781169 100644 --- a/api/datastore/test_data/output_24_to_latest.json +++ b/api/datastore/test_data/output_24_to_latest.json @@ -605,7 +605,7 @@ "GlobalDeploymentOptions": { "hideStacksFunctionality": false }, - "HelmRepositoryURL": "", + "HelmRepositoryURL": "https://charts.bitnami.com/bitnami", "InternalAuthSettings": { "RequiredPasswordLength": 12 }, diff --git a/api/http/handler/helm/handler.go b/api/http/handler/helm/handler.go index fb941940b..b776fe168 100644 --- a/api/http/handler/helm/handler.go +++ b/api/http/handler/helm/handler.go @@ -1,6 +1,7 @@ package helm import ( + "fmt" "net/http" portainer "github.com/portainer/portainer/api" @@ -8,8 +9,8 @@ import ( "github.com/portainer/portainer/api/http/middlewares" "github.com/portainer/portainer/api/http/security" "github.com/portainer/portainer/api/kubernetes" - "github.com/portainer/portainer/pkg/libhelm" "github.com/portainer/portainer/pkg/libhelm/options" + libhelmtypes "github.com/portainer/portainer/pkg/libhelm/types" httperror "github.com/portainer/portainer/pkg/libhttp/error" "github.com/gorilla/mux" @@ -23,11 +24,11 @@ type Handler struct { jwtService portainer.JWTService kubeClusterAccessService kubernetes.KubeClusterAccessService kubernetesDeployer portainer.KubernetesDeployer - helmPackageManager libhelm.HelmPackageManager + helmPackageManager libhelmtypes.HelmPackageManager } // NewHandler creates a handler to manage endpoint group operations. -func NewHandler(bouncer security.BouncerService, dataStore dataservices.DataStore, jwtService portainer.JWTService, kubernetesDeployer portainer.KubernetesDeployer, helmPackageManager libhelm.HelmPackageManager, kubeClusterAccessService kubernetes.KubeClusterAccessService) *Handler { +func NewHandler(bouncer security.BouncerService, dataStore dataservices.DataStore, jwtService portainer.JWTService, kubernetesDeployer portainer.KubernetesDeployer, helmPackageManager libhelmtypes.HelmPackageManager, kubeClusterAccessService kubernetes.KubeClusterAccessService) *Handler { h := &Handler{ Router: mux.NewRouter(), requestBouncer: bouncer, @@ -57,7 +58,7 @@ func NewHandler(bouncer security.BouncerService, dataStore dataservices.DataStor } // NewTemplateHandler creates a template handler to manage environment(endpoint) group operations. -func NewTemplateHandler(bouncer security.BouncerService, helmPackageManager libhelm.HelmPackageManager) *Handler { +func NewTemplateHandler(bouncer security.BouncerService, helmPackageManager libhelmtypes.HelmPackageManager) *Handler { h := &Handler{ Router: mux.NewRouter(), helmPackageManager: helmPackageManager, @@ -78,7 +79,7 @@ func NewTemplateHandler(bouncer security.BouncerService, helmPackageManager libh // getHelmClusterAccess obtains the core k8s cluster access details from request. // The cluster access includes the cluster server url, the user's bearer token and the tls certificate. -// The cluster access is passed in as kube config CLI params to helm binary. +// The cluster access is passed in as kube config CLI params to helm. func (handler *Handler) getHelmClusterAccess(r *http.Request) (*options.KubernetesClusterAccess, *httperror.HandlerError) { endpoint, err := middlewares.FetchEndpoint(r) if err != nil { @@ -107,6 +108,9 @@ func (handler *Handler) getHelmClusterAccess(r *http.Request) (*options.Kubernet kubeConfigInternal := handler.kubeClusterAccessService.GetClusterDetails(hostURL, endpoint.ID, true) return &options.KubernetesClusterAccess{ + ClusterName: fmt.Sprintf("%s-%s", "portainer-cluster", endpoint.Name), + ContextName: fmt.Sprintf("%s-%s", "portainer-ctx", endpoint.Name), + UserName: fmt.Sprintf("%s-%s", "portainer-sa-user", tokenData.Username), ClusterServerURL: kubeConfigInternal.ClusterServerURL, CertificateAuthorityFile: kubeConfigInternal.CertificateAuthorityFile, AuthToken: bearerToken, diff --git a/api/http/handler/helm/helm_delete_test.go b/api/http/handler/helm/helm_delete_test.go index 7d676fab9..ed77abdd2 100644 --- a/api/http/handler/helm/helm_delete_test.go +++ b/api/http/handler/helm/helm_delete_test.go @@ -13,8 +13,8 @@ import ( helper "github.com/portainer/portainer/api/internal/testhelpers" "github.com/portainer/portainer/api/jwt" "github.com/portainer/portainer/api/kubernetes" - "github.com/portainer/portainer/pkg/libhelm/binary/test" "github.com/portainer/portainer/pkg/libhelm/options" + "github.com/portainer/portainer/pkg/libhelm/test" "github.com/stretchr/testify/assert" ) @@ -34,7 +34,7 @@ func Test_helmDelete(t *testing.T) { is.NoError(err, "Error initiating jwt service") kubernetesDeployer := exectest.NewKubernetesDeployer() - helmPackageManager := test.NewMockHelmBinaryPackageManager("") + helmPackageManager := test.NewMockHelmPackageManager() kubeClusterAccessService := kubernetes.NewKubeClusterAccessService("", "", "") h := NewHandler(helper.NewTestRequestBouncer(), store, jwtService, kubernetesDeployer, helmPackageManager, kubeClusterAccessService) diff --git a/api/http/handler/helm/helm_install.go b/api/http/handler/helm/helm_install.go index dd1365f82..fffdf21bb 100644 --- a/api/http/handler/helm/helm_install.go +++ b/api/http/handler/helm/helm_install.go @@ -99,15 +99,11 @@ func (handler *Handler) installChart(r *http.Request, p installChartPayload) (*r } installOpts := options.InstallOptions{ - Name: p.Name, - Chart: p.Chart, - Namespace: p.Namespace, - Repo: p.Repo, - KubernetesClusterAccess: &options.KubernetesClusterAccess{ - ClusterServerURL: clusterAccess.ClusterServerURL, - CertificateAuthorityFile: clusterAccess.CertificateAuthorityFile, - AuthToken: clusterAccess.AuthToken, - }, + Name: p.Name, + Chart: p.Chart, + Namespace: p.Namespace, + Repo: p.Repo, + KubernetesClusterAccess: clusterAccess, } if p.Values != "" { diff --git a/api/http/handler/helm/helm_install_test.go b/api/http/handler/helm/helm_install_test.go index 0b87d3a23..ab2da85e6 100644 --- a/api/http/handler/helm/helm_install_test.go +++ b/api/http/handler/helm/helm_install_test.go @@ -15,9 +15,9 @@ import ( helper "github.com/portainer/portainer/api/internal/testhelpers" "github.com/portainer/portainer/api/jwt" "github.com/portainer/portainer/api/kubernetes" - "github.com/portainer/portainer/pkg/libhelm/binary/test" "github.com/portainer/portainer/pkg/libhelm/options" "github.com/portainer/portainer/pkg/libhelm/release" + "github.com/portainer/portainer/pkg/libhelm/test" "github.com/segmentio/encoding/json" "github.com/stretchr/testify/assert" @@ -38,14 +38,14 @@ func Test_helmInstall(t *testing.T) { is.NoError(err, "Error initiating jwt service") kubernetesDeployer := exectest.NewKubernetesDeployer() - helmPackageManager := test.NewMockHelmBinaryPackageManager("") + helmPackageManager := test.NewMockHelmPackageManager() kubeClusterAccessService := kubernetes.NewKubeClusterAccessService("", "", "") h := NewHandler(helper.NewTestRequestBouncer(), store, jwtService, kubernetesDeployer, helmPackageManager, kubeClusterAccessService) is.NotNil(h, "Handler should not fail") // Install a single chart. We expect to get these values back - options := options.InstallOptions{Name: "nginx-1", Chart: "nginx", Namespace: "default", Repo: "https://kubernetes.github.io/ingress-nginx"} + options := options.InstallOptions{Name: "nginx-1", Chart: "nginx", Namespace: "default", Repo: "https://charts.bitnami.com/bitnami"} optdata, err := json.Marshal(options) is.NoError(err) diff --git a/api/http/handler/helm/helm_list_test.go b/api/http/handler/helm/helm_list_test.go index 8ef51b324..b8dd40354 100644 --- a/api/http/handler/helm/helm_list_test.go +++ b/api/http/handler/helm/helm_list_test.go @@ -14,9 +14,9 @@ import ( helper "github.com/portainer/portainer/api/internal/testhelpers" "github.com/portainer/portainer/api/jwt" "github.com/portainer/portainer/api/kubernetes" - "github.com/portainer/portainer/pkg/libhelm/binary/test" "github.com/portainer/portainer/pkg/libhelm/options" "github.com/portainer/portainer/pkg/libhelm/release" + "github.com/portainer/portainer/pkg/libhelm/test" "github.com/segmentio/encoding/json" "github.com/stretchr/testify/assert" @@ -37,7 +37,7 @@ func Test_helmList(t *testing.T) { is.NoError(err, "Error initialising jwt service") kubernetesDeployer := exectest.NewKubernetesDeployer() - helmPackageManager := test.NewMockHelmBinaryPackageManager("") + helmPackageManager := test.NewMockHelmPackageManager() kubeClusterAccessService := kubernetes.NewKubeClusterAccessService("", "", "") h := NewHandler(helper.NewTestRequestBouncer(), store, jwtService, kubernetesDeployer, helmPackageManager, kubeClusterAccessService) diff --git a/api/http/handler/helm/helm_repo_search_test.go b/api/http/handler/helm/helm_repo_search_test.go index a556908b9..63dac43b5 100644 --- a/api/http/handler/helm/helm_repo_search_test.go +++ b/api/http/handler/helm/helm_repo_search_test.go @@ -8,19 +8,19 @@ import ( "testing" helper "github.com/portainer/portainer/api/internal/testhelpers" - "github.com/portainer/portainer/pkg/libhelm/binary/test" + "github.com/portainer/portainer/pkg/libhelm/test" "github.com/stretchr/testify/assert" ) func Test_helmRepoSearch(t *testing.T) { is := assert.New(t) - helmPackageManager := test.NewMockHelmBinaryPackageManager("") + helmPackageManager := test.NewMockHelmPackageManager() h := NewTemplateHandler(helper.NewTestRequestBouncer(), helmPackageManager) assert.NotNil(t, h, "Handler should not fail") - repos := []string{"https://kubernetes.github.io/ingress-nginx", "https://portainer.github.io/k8s"} + repos := []string{"https://charts.bitnami.com/bitnami", "https://portainer.github.io/k8s"} for _, repo := range repos { t.Run(repo, func(t *testing.T) { diff --git a/api/http/handler/helm/helm_show_test.go b/api/http/handler/helm/helm_show_test.go index fed59388d..385843b65 100644 --- a/api/http/handler/helm/helm_show_test.go +++ b/api/http/handler/helm/helm_show_test.go @@ -9,14 +9,14 @@ import ( "testing" helper "github.com/portainer/portainer/api/internal/testhelpers" - "github.com/portainer/portainer/pkg/libhelm/binary/test" + "github.com/portainer/portainer/pkg/libhelm/test" "github.com/stretchr/testify/assert" ) func Test_helmShow(t *testing.T) { is := assert.New(t) - helmPackageManager := test.NewMockHelmBinaryPackageManager("") + helmPackageManager := test.NewMockHelmPackageManager() h := NewTemplateHandler(helper.NewTestRequestBouncer(), helmPackageManager) is.NotNil(h, "Handler should not fail") @@ -31,7 +31,7 @@ func Test_helmShow(t *testing.T) { t.Run(cmd, func(t *testing.T) { is.NotNil(h, "Handler should not fail") - repoUrlEncoded := url.QueryEscape("https://kubernetes.github.io/ingress-nginx") + repoUrlEncoded := url.QueryEscape("https://charts.bitnami.com/bitnami") chart := "nginx" req := httptest.NewRequest("GET", fmt.Sprintf("/templates/helm/%s?repo=%s&chart=%s", cmd, repoUrlEncoded, chart), nil) rr := httptest.NewRecorder() diff --git a/api/http/handler/settings/settings_update.go b/api/http/handler/settings/settings_update.go index 895da489c..0b36dbc62 100644 --- a/api/http/handler/settings/settings_update.go +++ b/api/http/handler/settings/settings_update.go @@ -46,7 +46,7 @@ type settingsUpdatePayload struct { // Whether telemetry is enabled EnableTelemetry *bool `example:"false"` // Helm repository URL - HelmRepositoryURL *string `example:"https://kubernetes.github.io/ingress-nginx"` + HelmRepositoryURL *string `example:"https://charts.bitnami.com/bitnami"` // Kubectl Shell Image KubectlShellImage *string `example:"portainer/kubectl-shell:latest"` // TrustOnFirstConnect makes Portainer accepting edge agent connection by default diff --git a/api/http/server.go b/api/http/server.go index c5bb0d40f..3dcc84d2d 100644 --- a/api/http/server.go +++ b/api/http/server.go @@ -67,7 +67,7 @@ import ( "github.com/portainer/portainer/api/platform" "github.com/portainer/portainer/api/scheduler" "github.com/portainer/portainer/api/stacks/deployments" - "github.com/portainer/portainer/pkg/libhelm" + libhelmtypes "github.com/portainer/portainer/pkg/libhelm/types" "github.com/rs/zerolog/log" ) @@ -103,7 +103,7 @@ type Server struct { DockerClientFactory *dockerclient.ClientFactory KubernetesClientFactory *cli.ClientFactory KubernetesDeployer portainer.KubernetesDeployer - HelmPackageManager libhelm.HelmPackageManager + HelmPackageManager libhelmtypes.HelmPackageManager Scheduler *scheduler.Scheduler ShutdownCtx context.Context ShutdownTrigger context.CancelFunc diff --git a/api/portainer.go b/api/portainer.go index 990747813..9e1451633 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -588,7 +588,7 @@ type ( // User identifier UserID UserID `json:"UserId" example:"1"` // Helm repository URL - URL string `json:"URL" example:"https://kubernetes.github.io/ingress-nginx"` + URL string `json:"URL" example:"https://charts.bitnami.com/bitnami"` } // QuayRegistryData represents data required for Quay registry to work @@ -984,8 +984,8 @@ type ( KubeconfigExpiry string `json:"KubeconfigExpiry" example:"24h"` // Whether telemetry is enabled EnableTelemetry bool `json:"EnableTelemetry" example:"false"` - // Helm repository URL, defaults to "" - HelmRepositoryURL string `json:"HelmRepositoryURL"` + // Helm repository URL, defaults to "https://charts.bitnami.com/bitnami" + HelmRepositoryURL string `json:"HelmRepositoryURL" example:"https://charts.bitnami.com/bitnami"` // KubectlImage, defaults to portainer/kubectl-shell KubectlShellImage string `json:"KubectlShellImage" example:"portainer/kubectl-shell"` // TrustOnFirstConnect makes Portainer accepting edge agent connection by default @@ -1673,8 +1673,8 @@ const ( DefaultEdgeAgentCheckinIntervalInSeconds = 5 // DefaultTemplatesURL represents the URL to the official templates supported by Portainer DefaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/v3/templates.json" - // DefaultHelmrepositoryURL set to empty string until oci support is added - DefaultHelmRepositoryURL = "" + // DefaultHelmrepositoryURL represents the URL to the official templates supported by Bitnami + DefaultHelmRepositoryURL = "https://charts.bitnami.com/bitnami" // DefaultUserSessionTimeout represents the default timeout after which the user session is cleared DefaultUserSessionTimeout = "8h" // DefaultUserSessionTimeout represents the default timeout after which the user session is cleared diff --git a/app/react/kubernetes/helm/HelmApplicationView/HelmApplicationView.test.tsx b/app/react/kubernetes/helm/HelmApplicationView/HelmApplicationView.test.tsx index 296b17ff5..63b0468eb 100644 --- a/app/react/kubernetes/helm/HelmApplicationView/HelmApplicationView.test.tsx +++ b/app/react/kubernetes/helm/HelmApplicationView/HelmApplicationView.test.tsx @@ -62,19 +62,19 @@ describe('HelmApplicationView', () => { expect(await screen.findByText('Helm details')).toBeInTheDocument(); // Check for the release details - expect(screen.getByText('Release')).toBeInTheDocument(); + expect(await screen.findByText('Release')).toBeInTheDocument(); // Check for the table content - expect(screen.getByText('Name')).toBeInTheDocument(); - expect(screen.getByText('Chart')).toBeInTheDocument(); - expect(screen.getByText('App version')).toBeInTheDocument(); + expect(await screen.findByText('Name')).toBeInTheDocument(); + expect(await screen.findByText('Chart')).toBeInTheDocument(); + expect(await screen.findByText('App version')).toBeInTheDocument(); // Check for the actual values - expect(screen.getByTestId('k8sAppDetail-appName')).toHaveTextContent( + expect(await screen.findByTestId('k8sAppDetail-appName')).toHaveTextContent( 'test-release' ); - expect(screen.getByText('test-chart-1.0.0')).toBeInTheDocument(); - expect(screen.getByText('1.0.0')).toBeInTheDocument(); + expect(await screen.findByText('test-chart-1.0.0')).toBeInTheDocument(); + expect(await screen.findByText('1.0.0')).toBeInTheDocument(); }); it('should display error message when API request fails', async () => { diff --git a/app/react/kubernetes/helm/HelmApplicationView/HelmApplicationView.tsx b/app/react/kubernetes/helm/HelmApplicationView/HelmApplicationView.tsx index f50b59154..fa02574cc 100644 --- a/app/react/kubernetes/helm/HelmApplicationView/HelmApplicationView.tsx +++ b/app/react/kubernetes/helm/HelmApplicationView/HelmApplicationView.tsx @@ -1,37 +1,13 @@ import { useCurrentStateAndParams } from '@uirouter/react'; import { PageHeader } from '@/react/components/PageHeader'; -import { Widget, WidgetBody, WidgetTitle } from '@/react/components/Widget'; -import helm from '@/assets/ico/vendor/helm.svg?c'; -import { useEnvironmentId } from '@/react/hooks/useEnvironmentId'; -import { ViewLoading } from '@@/ViewLoading'; -import { Alert } from '@@/Alert'; - -import { useHelmRelease } from './queries/useHelmRelease'; +import { HelmDetailsWidget } from './HelmDetailsWidget'; export function HelmApplicationView() { const { params } = useCurrentStateAndParams(); - const environmentId = useEnvironmentId(); - const name = params.name as string; - const namespace = params.namespace as string; - - const { - data: release, - isLoading, - error, - } = useHelmRelease(environmentId, name, namespace); - - if (isLoading) { - return ; - } - - if (error || !release) { - return ( - - ); - } + const { name, namespace } = params; return ( <> @@ -46,32 +22,7 @@ export function HelmApplicationView() {
- - - - - - - - - - - - - - - - - - -
Name - {release.name} -
Chart{release.chart}
App version{release.app_version}
-
-
+
diff --git a/app/react/kubernetes/helm/HelmApplicationView/HelmDetailsWidget.tsx b/app/react/kubernetes/helm/HelmApplicationView/HelmDetailsWidget.tsx new file mode 100644 index 000000000..97703e0ff --- /dev/null +++ b/app/react/kubernetes/helm/HelmApplicationView/HelmDetailsWidget.tsx @@ -0,0 +1,67 @@ +import { + Loading, + Widget, + WidgetBody, + WidgetTitle, +} from '@/react/components/Widget'; +import helm from '@/assets/ico/vendor/helm.svg?c'; +import { useEnvironmentId } from '@/react/hooks/useEnvironmentId'; + +import { Alert } from '@@/Alert'; + +import { useHelmRelease } from './queries/useHelmRelease'; + +interface HelmDetailsWidgetProps { + name: string; + namespace: string; +} + +export function HelmDetailsWidget({ name, namespace }: HelmDetailsWidgetProps) { + const environmentId = useEnvironmentId(); + + const { + data: release, + isInitialLoading, + isError, + } = useHelmRelease(environmentId, name, namespace); + + return ( + + + + {isInitialLoading && } + + {isError && ( + + )} + + {!isInitialLoading && !isError && release && ( + + + + + + + + + + + + + + + +
Name + {release.name} +
Chart{release.chart}
App version{release.app_version}
+ )} +
+
+ ); +} diff --git a/build/build_binary.sh b/build/build_binary.sh index 6f87cd939..9051c76d2 100755 --- a/build/build_binary.sh +++ b/build/build_binary.sh @@ -23,7 +23,6 @@ GIT_COMMIT_HASH=${GIT_COMMIT_HASH:-$(git rev-parse --short HEAD)} # populate dependencies versions DOCKER_VERSION=$(jq -r '.docker' < "${BINARY_VERSION_FILE}") -HELM_VERSION=$(jq -r '.helm' < "${BINARY_VERSION_FILE}") KUBECTL_VERSION=$(jq -r '.kubectl' < "${BINARY_VERSION_FILE}") COMPOSE_VERSION=$(go list -m -f '{{.Version}}' github.com/docker/compose/v2) @@ -52,7 +51,6 @@ ldflags="-s -X 'github.com/portainer/liblicense.LicenseServerBaseURL=https://api -X 'github.com/portainer/portainer/pkg/build.GoVersion=${GO_VERSION}' \ -X 'github.com/portainer/portainer/pkg/build.DepComposeVersion=${COMPOSE_VERSION}' \ -X 'github.com/portainer/portainer/pkg/build.DepDockerVersion=${DOCKER_VERSION}' \ --X 'github.com/portainer/portainer/pkg/build.DepHelmVersion=${HELM_VERSION}' \ -X 'github.com/portainer/portainer/pkg/build.DepKubectlVersion=${KUBECTL_VERSION}'" echo "$ldflags" diff --git a/build/download_binaries.sh b/build/download_binaries.sh index 75e571a20..05c2c780b 100755 --- a/build/download_binaries.sh +++ b/build/download_binaries.sh @@ -17,12 +17,10 @@ echo "Checking and downloading binaries for docker ${dockerVersion}, helm ${helm # Determine the binary file names based on the platform dockerBinary="dist/docker" -helmBinary="dist/helm" kubectlBinary="dist/kubectl" if [ "$PLATFORM" == "windows" ]; then dockerBinary="dist/docker.exe" - helmBinary="dist/helm.exe" kubectlBinary="dist/kubectl.exe" fi @@ -34,14 +32,6 @@ else echo "Docker binary already exists, skipping download." fi -# Check and download helm binary -if [ ! -f "$helmBinary" ]; then - echo "Downloading helm binary..." - /usr/bin/env bash ./build/download_helm_binary.sh "$PLATFORM" "$ARCH" "$helmVersion" -else - echo "Helm binary already exists, skipping download." -fi - # Check and download kubectl binary if [ ! -f "$kubectlBinary" ]; then echo "Downloading kubectl binary..." diff --git a/build/download_helm_binary.sh b/build/download_helm_binary.sh deleted file mode 100755 index ae3de93e7..000000000 --- a/build/download_helm_binary.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -if [[ $# -ne 3 ]]; then - echo "Illegal number of parameters" >&2 - exit 1 -fi - -PLATFORM=$1 -ARCH=$2 -HELM_VERSION=$3 -HELM_DIST="helm-$HELM_VERSION-$PLATFORM-$ARCH" - - -if [[ ${PLATFORM} == "windows" ]]; then - wget --tries=3 --waitretry=30 --quiet -O tmp.zip "https://get.helm.sh/${HELM_DIST}.zip" && unzip -o -j tmp.zip "${PLATFORM}-${ARCH}/helm.exe" -d dist && rm -f tmp.zip -else - wget -qO- "https://get.helm.sh/${HELM_DIST}.tar.gz" | tar -x -z --strip-components 1 "${PLATFORM}-${ARCH}/helm" - mv "helm" "dist/helm" - chmod +x "dist/helm" -fi diff --git a/build/linux/Dockerfile b/build/linux/Dockerfile index f7a921220..eeded41c1 100644 --- a/build/linux/Dockerfile +++ b/build/linux/Dockerfile @@ -11,7 +11,6 @@ LABEL org.opencontainers.image.title="Portainer" \ com.docker.extension.additional-urls="[{\"title\":\"Website\",\"url\":\"https://www.portainer.io?utm_campaign=DockerCon&utm_source=DockerDesktop\"},{\"title\":\"Documentation\",\"url\":\"https://docs.portainer.io\"},{\"title\":\"Support\",\"url\":\"https://join.slack.com/t/portainer/shared_invite/zt-txh3ljab-52QHTyjCqbe5RibC2lcjKA\"}]" COPY dist/docker / -COPY dist/helm / COPY dist/kubectl / COPY dist/mustache-templates /mustache-templates/ COPY dist/portainer / diff --git a/build/linux/alpine.Dockerfile b/build/linux/alpine.Dockerfile index 0f7b29145..e2ced7d3e 100644 --- a/build/linux/alpine.Dockerfile +++ b/build/linux/alpine.Dockerfile @@ -11,7 +11,6 @@ LABEL org.opencontainers.image.title="Portainer" \ com.docker.extension.additional-urls="[{\"title\":\"Website\",\"url\":\"https://www.portainer.io?utm_campaign=DockerCon&utm_source=DockerDesktop\"},{\"title\":\"Documentation\",\"url\":\"https://docs.portainer.io\"},{\"title\":\"Support\",\"url\":\"https://join.slack.com/t/portainer/shared_invite/zt-txh3ljab-52QHTyjCqbe5RibC2lcjKA\"}]" COPY dist/docker / -COPY dist/helm / COPY dist/kubectl / COPY dist/mustache-templates /mustache-templates/ COPY dist/portainer / diff --git a/build/windows/Dockerfile b/build/windows/Dockerfile index bf9f0f41b..324e68f04 100644 --- a/build/windows/Dockerfile +++ b/build/windows/Dockerfile @@ -10,7 +10,6 @@ USER ContainerAdministrator COPY dist/mingit/ mingit/ COPY dist/docker.exe / -COPY dist/helm.exe / COPY dist/kubectl.exe / COPY dist/mustache-templates /mustache-templates/ COPY dist/portainer.exe / diff --git a/go.mod b/go.mod index 1749b8c20..78f3a8adb 100644 --- a/go.mod +++ b/go.mod @@ -55,10 +55,11 @@ require ( golang.org/x/sync v0.10.0 gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.29.2 - k8s.io/apimachinery v0.29.2 - k8s.io/client-go v0.29.2 - k8s.io/metrics v0.27.4 + helm.sh/helm/v3 v3.17.1 + k8s.io/api v0.32.1 + k8s.io/apimachinery v0.32.1 + k8s.io/client-go v0.32.1 + k8s.io/metrics v0.32.1 software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78 ) @@ -70,8 +71,12 @@ require ( github.com/AlecAivazis/survey/v2 v2.3.7 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c // indirect - github.com/BurntSushi/toml v1.3.2 // indirect - github.com/Masterminds/semver/v3 v3.2.1 // indirect + github.com/BurntSushi/toml v1.4.0 // 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 + github.com/Masterminds/sprig/v3 v3.3.0 // indirect + github.com/Masterminds/squirrel v1.5.4 // indirect github.com/ProtonMail/go-crypto v1.1.3 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect @@ -89,9 +94,11 @@ require ( 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/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect github.com/buger/goterm v1.0.4 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/chai2010/gettext-go v1.0.2 // indirect github.com/cloudflare/cfssl v1.6.4 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/containerd/console v1.0.4 // indirect @@ -106,8 +113,8 @@ require ( github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect github.com/containers/ocicrypt v1.1.10 // indirect github.com/containers/storage v1.53.0 // indirect - github.com/cyphar/filepath-securejoin v0.2.5 // indirect - github.com/davecgh/go-spew v1.1.1 // 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 @@ -120,39 +127,49 @@ require ( github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect - github.com/evanphx/json-patch v4.12.0+incompatible // indirect + github.com/evanphx/json-patch v5.9.0+incompatible // indirect + github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect + 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/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 github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.6.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-gorp/gorp/v3 v3.1.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.10 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-viper/mapstructure/v2 v2.0.0 // indirect + 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/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/btree v1.0.1 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // 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/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/imdario/mergo v0.3.16 // 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/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/josharian/intern v1.0.0 // indirect github.com/jpillora/ansi v1.0.3 // indirect @@ -162,22 +179,28 @@ require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/pgzip v1.2.6 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/leodido/go-urn v1.2.2 // indirect + github.com/lib/pq v1.10.9 // indirect + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // 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-shellwords v1.0.12 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/miekg/pkcs11 v1.1.1 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect 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/docker-image-spec v1.3.1 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/patternmatcher v0.6.0 // indirect - github.com/moby/spdystream v0.2.0 // indirect + github.com/moby/spdystream v0.5.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 @@ -188,28 +211,34 @@ require ( github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/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/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.0 // indirect - github.com/prometheus/client_golang v1.17.0 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.44.0 // 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_model v0.6.1 // indirect + github.com/prometheus/common v0.55.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 github.com/secure-systems-lab/go-securesystemslib v0.8.0 // indirect github.com/segmentio/asm v1.1.3 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect + github.com/shopspring/decimal v1.4.0 // indirect 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/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect @@ -223,44 +252,55 @@ require ( 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/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 - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // 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.50.0 // indirect - go.opentelemetry.io/otel v1.25.0 // 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.25.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.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.25.0 // indirect - go.opentelemetry.io/otel/sdk v1.25.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.21.0 // indirect - go.opentelemetry.io/otel/trace v1.25.0 // indirect - go.opentelemetry.io/proto/otlp v1.1.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.33.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/term v0.27.0 // indirect golang.org/x/text v0.21.0 // indirect - golang.org/x/time v0.6.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 + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/klog/v2 v2.110.1 // indirect - k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect - k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect - sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect + k8s.io/apiextensions-apiserver v0.32.1 // indirect + k8s.io/apiserver v0.32.1 // indirect + k8s.io/cli-runtime v0.32.1 // indirect + k8s.io/component-base v0.32.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect + k8s.io/kubectl v0.32.1 // indirect + k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect + oras.land/oras-go v1.2.5 // indirect + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + sigs.k8s.io/kustomize/api v0.18.0 // indirect + 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 ) diff --git a/go.sum b/go.sum index 6427b69de..278c4cdd3 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,7 @@ -cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= -cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= -cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= -cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= 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= @@ -15,12 +13,22 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg6 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= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +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/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= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= -github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= 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= @@ -90,7 +98,11 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bitly/go-hostpool v0.1.0/go.mod h1:4gOCgp6+NZnVqlKyZ/iBZFTAJKembaVENUpMkpg42fw= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY= github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE= github.com/bugsnag/bugsnag-go v1.0.5-0.20150529004307-13fd6b8acda0 h1:s7+5BfS4WFJoVF9pnB8kBk03S7pZXRdKamnV0FOl5Sc= @@ -106,13 +118,13 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= +github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= github.com/cloudflare/cfssl v1.6.4 h1:NMOvfrEjFfC63K3SGXgAnFdsgkmiq4kATme5BfcqrO8= github.com/cloudflare/cfssl v1.6.4/go.mod h1:8b3CQMxfWPAeom3zBnGJ6sd+G1NkL5TXqmDXacb+1J0= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= -github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI= -github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= 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= @@ -161,14 +173,17 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 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/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo= -github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +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= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 h1:RAV05c0xOkJ3dZGS0JFybxFKZ2WMLabgx3uXnd7rpGs= github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= +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= @@ -209,13 +224,19 @@ github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxER github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= -github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= -github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= -github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= +github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= +github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= +github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 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= @@ -223,12 +244,16 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 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= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/g07cha/defender v0.0.0-20180505193036-5665c627c814 h1:gWvniJ4GbFfkf700kykAImbLiEMU0Q3QN9hQ26Js1pU= github.com/g07cha/defender v0.0.0-20180505193036-5665c627c814/go.mod h1:secRm32Ro77eD23BmPVbgLbWN+JWDw7pJszenjxI4bI= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8= @@ -237,24 +262,26 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E= github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw= +github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= +github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-ldap/ldap/v3 v3.4.1 h1:fU/0xli6HY02ocbMuozHAYsaHLcnkLjvho2r5a34BUU= github.com/go-ldap/ldap/v3 v3.4.1/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.22.10 h1:4y86NVn7Z2yYd6pfS4Z+Nyh3aAUL3Nul+LMbhFKy0gA= -github.com/go-openapi/swag v0.22.10/go.mod h1:Cnn8BYtRlx6BNE3DPN86f/xkapGIcLWzh3CLEb4C1jI= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -264,13 +291,15 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91 github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI= github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA= github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc= github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= @@ -293,6 +322,10 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= +github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/certificate-transparency-go v1.1.4 h1:hCyXHDbtqlr/lMXU0D4WgbalXL0Zk4dSWWMbPV8VrqY= github.com/google/certificate-transparency-go v1.1.4/go.mod h1:D6lvbfwckhNrbM9WVl1EVeMOyzC19mpIjMOI4nxBHtQ= @@ -305,24 +338,29 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN 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= -github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= -github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI= github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/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/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= @@ -339,8 +377,8 @@ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= -github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU= github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -357,8 +395,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jmoiron/sqlx v1.3.3 h1:j82X0bf7oQ27XeqxicSZsTU5suPwKElg3oyxNn43iTk= -github.com/jmoiron/sqlx v1.3.3/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= +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= @@ -402,9 +440,17 @@ 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/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= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4= github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ= github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/magiconair/properties v1.5.3 h1:C8fxWnhYyME3n0klPOhVM7PtYUB3eV1W3DeFmN3j53Y= github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -423,19 +469,27 @@ github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh 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= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= +github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/mitchellh/mapstructure v0.0.0-20150613213606-2caf8efc9366/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 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/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= @@ -444,8 +498,8 @@ 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.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +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/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= @@ -469,6 +523,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= @@ -480,12 +536,12 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= -github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= -github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= +github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= -github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= +github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -505,6 +561,10 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= +github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -513,24 +573,27 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= +github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 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.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= -github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +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_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= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.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.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= -github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.0.0-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= @@ -544,11 +607,14 @@ 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.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +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/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= +github.com/rubenv/sql-migrate v1.7.1 h1:f/o0WgfO/GqNuVg+6801K/KW3WdDSupzSjDYODmiUq4= +github.com/rubenv/sql-migrate v1.7.1/go.mod h1:Ob2Psprc0/3ggbM6wCzyYVFFuc6FyZrb2AS+ezLDFb4= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ= github.com/secure-systems-lab/go-securesystemslib v0.8.0 h1:mr5An6X45Kb2nddcFlbmfHkLguCE9laoZCUzEEpIZXA= @@ -563,6 +629,8 @@ github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b h1:h+3JX2VoWTFuyQ github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc= github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= @@ -575,8 +643,9 @@ github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EE github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/spdx/tools-golang v0.5.3 h1:ialnHeEYUC4+hkm5vJm4qz2x+oEJbS0mAMFrNXdQraY= github.com/spdx/tools-golang v0.5.3/go.mod h1:/ETOahiAo96Ob0/RAIBmFZw6XN0yTnyr/uFZm2NTMhI= -github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94 h1:JmfC365KywYwHB946TTiQWEb8kqPY+pybPLoGE9GgVk= github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= +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= @@ -592,6 +661,8 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -632,6 +703,8 @@ github.com/viney-shih/go-lock v1.1.1 h1:SwzDPPAiHpcwGCr5k8xD15d2gQSo8d4roRYd7TDV 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= github.com/weppos/publicsuffix-go v0.15.1-0.20210511084619-b1f36a2d6c0b/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -641,9 +714,17 @@ 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/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= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= +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/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= @@ -652,34 +733,34 @@ go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= 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.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= +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.50.0 h1:cEPbyTSEHlQR89XVlyo78gqluF8Y3oMeBkXGWzQsfXY= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.50.0/go.mod h1:DKdbWcT4GH1D0Y3Sqt/PFXt2naRKDWtU+eE6oLdFNA8= -go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k= -go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg= +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.25.0 h1:dT33yIHtmsqpixFsSQPwNeY5drM9wTcoL8h0FWF4oGM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0/go.mod h1:h95q0LBGh7hlAC08X2DhSeyIG02YQ0UyioTCVAqRPmc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0= +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.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA= -go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s= -go.opentelemetry.io/otel/sdk v1.25.0 h1:PDryEJPC8YJZQSyLY5eqLeafHtG+X7FWnf3aXMtxbqo= -go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw= -go.opentelemetry.io/otel/sdk/metric v1.21.0 h1:smhI5oD714d6jHE6Tie36fPx4WDFIg+Y6RfAY4ICcR0= -go.opentelemetry.io/otel/sdk/metric v1.21.0/go.mod h1:FJ8RAsoPGv/wYMgBdUJXOm+6pzFY3YdljnXtv1SBE8Q= -go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM= -go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I= -go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= -go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.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.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= @@ -769,15 +850,15 @@ 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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= -golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +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/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.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= -golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= +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/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= @@ -805,6 +886,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= @@ -826,26 +909,44 @@ 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= -k8s.io/api v0.29.2 h1:hBC7B9+MU+ptchxEqTNW2DkUosJpp1P+Wn6YncZ474A= -k8s.io/api v0.29.2/go.mod h1:sdIaaKuU7P44aoyyLlikSLayT6Vb7bvJNCX105xZXY0= -k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8= -k8s.io/apimachinery v0.29.2/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= -k8s.io/client-go v0.29.2 h1:FEg85el1TeZp+/vYJM7hkDlSTFZ+c5nnK44DJ4FyoRg= -k8s.io/client-go v0.29.2/go.mod h1:knlvFZE58VpqbQpJNbCbctTVXcd35mMyAAwBdpt4jrA= -k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= -k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= -k8s.io/metrics v0.27.4 h1:2s04bods7rA507iouGbxD55YrKNlFjLYzm30noOl9Sk= -k8s.io/metrics v0.27.4/go.mod h1:kRvfhFC7wCQEFvu6H92uiV7v05z3Ty/vtluYT5D2Xpk= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +helm.sh/helm/v3 v3.17.1 h1:gzVoAD+qVuoJU6KDMSAeo0xRJ6N1znRxz3wyuXRmJDk= +helm.sh/helm/v3 v3.17.1/go.mod h1:nvreuhuR+j78NkQcLC3TYoprCKStLyw5P4T7E5itv2w= +k8s.io/api v0.32.1 h1:f562zw9cy+GvXzXf0CKlVQ7yHJVYzLfL6JAS4kOAaOc= +k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k= +k8s.io/apiextensions-apiserver v0.32.1 h1:hjkALhRUeCariC8DiVmb5jj0VjIc1N0DREP32+6UXZw= +k8s.io/apiextensions-apiserver v0.32.1/go.mod h1:sxWIGuGiYov7Io1fAS2X06NjMIk5CbRHc2StSmbaQto= +k8s.io/apimachinery v0.32.1 h1:683ENpaCBjma4CYqsmZyhEzrGz6cjn1MY/X2jB2hkZs= +k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/apiserver v0.32.1 h1:oo0OozRos66WFq87Zc5tclUX2r0mymoVHRq8JmR7Aak= +k8s.io/apiserver v0.32.1/go.mod h1:UcB9tWjBY7aryeI5zAgzVJB/6k7E97bkr1RgqDz0jPw= +k8s.io/cli-runtime v0.32.1 h1:19nwZPlYGJPUDbhAxDIS2/oydCikvKMHsxroKNGA2mM= +k8s.io/cli-runtime v0.32.1/go.mod h1:NJPbeadVFnV2E7B7vF+FvU09mpwYlZCu8PqjzfuOnkY= +k8s.io/client-go v0.32.1 h1:otM0AxdhdBIaQh7l1Q0jQpmo7WOFIk5FFa4bg6YMdUU= +k8s.io/client-go v0.32.1/go.mod h1:aTTKZY7MdxUaJ/KiUs8D+GssR9zJZi77ZqtzcGXIiDg= +k8s.io/component-base v0.32.1 h1:/5IfJ0dHIKBWysGV0yKTFfacZ5yNV1sulPh3ilJjRZk= +k8s.io/component-base v0.32.1/go.mod h1:j1iMMHi/sqAHeG5z+O9BFNCF698a1u0186zkjMZQ28w= +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= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= +k8s.io/kubectl v0.32.1 h1:/btLtXLQUU1rWx8AEvX9jrb9LaI6yeezt3sFALhB8M8= +k8s.io/kubectl v0.32.1/go.mod h1:sezNuyWi1STk4ZNPVRIFfgjqMI6XMf+oCVLjZen/pFQ= +k8s.io/metrics v0.32.1 h1:Ou4nrEtZS2vFf7OJCf9z3+2kr0A00kQzfoSwxg0gXps= +k8s.io/metrics v0.32.1/go.mod h1:cLnai9XKYby1tNMX+xe8p9VLzTqrxYPcmqfCBoWObcM= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo= +oras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +sigs.k8s.io/kustomize/api v0.18.0 h1:hTzp67k+3NEVInwz5BHyzc9rGxIauoXferXyjv5lWPo= +sigs.k8s.io/kustomize/api v0.18.0/go.mod h1:f8isXnX+8b+SGLHQ6yO4JG1rdkZlvhaCf/uZbLVMb0U= +sigs.k8s.io/kustomize/kyaml v0.18.1 h1:WvBo56Wzw3fjS+7vBjN6TeivvpbW9GmRaWZ9CIVmt4E= +sigs.k8s.io/kustomize/kyaml v0.18.1/go.mod h1:C3L2BFVU1jgcddNBE1TxuVLgS46TjObMwW5FT9FcjYo= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +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= diff --git a/pkg/build/info.go b/pkg/build/info.go index fac320b4f..026a317ef 100644 --- a/pkg/build/info.go +++ b/pkg/build/info.go @@ -42,9 +42,6 @@ var ( // DepDockerVersion is the version of the Docker binary shipped with the application. DepDockerVersion string - // DepHelmVersion is the version of the Helm binary shipped with the application. - DepHelmVersion string - // DepKubectlVersion is the version of the Kubectl binary shipped with the application. DepKubectlVersion string ) @@ -92,7 +89,6 @@ func GetBuildInfo() BuildInfo { func GetDependenciesInfo() DependenciesInfo { return DependenciesInfo{ DockerVersion: DepDockerVersion, - HelmVersion: DepHelmVersion, KubectlVersion: DepKubectlVersion, ComposeVersion: DepComposeVersion, } diff --git a/pkg/libhelm/binary/get.go b/pkg/libhelm/binary/get.go deleted file mode 100644 index 0df41bbc8..000000000 --- a/pkg/libhelm/binary/get.go +++ /dev/null @@ -1,29 +0,0 @@ -package binary - -import ( - "github.com/pkg/errors" - "github.com/portainer/portainer/pkg/libhelm/options" -) - -// Get runs `helm get` with specified get options. -// The get options translate to CLI arguments which are passed in to the helm binary when executing install. -func (hbpm *helmBinaryPackageManager) Get(getOpts options.GetOptions) ([]byte, error) { - if getOpts.Name == "" || getOpts.ReleaseResource == "" { - return nil, errors.New("release name and release resource are required") - } - - args := []string{ - string(getOpts.ReleaseResource), - getOpts.Name, - } - if getOpts.Namespace != "" { - args = append(args, "--namespace", getOpts.Namespace) - } - - result, err := hbpm.runWithKubeConfig("get", args, getOpts.KubernetesClusterAccess, getOpts.Env) - if err != nil { - return nil, errors.Wrap(err, "failed to run helm get on specified args") - } - - return result, nil -} diff --git a/pkg/libhelm/binary/helm_package.go b/pkg/libhelm/binary/helm_package.go deleted file mode 100644 index 2e64a8a2b..000000000 --- a/pkg/libhelm/binary/helm_package.go +++ /dev/null @@ -1,62 +0,0 @@ -package binary - -import ( - "bytes" - "os" - "os/exec" - "path" - "runtime" - - "github.com/pkg/errors" - "github.com/portainer/portainer/pkg/libhelm/options" -) - -// helmBinaryPackageManager is a wrapper for the helm binary which implements HelmPackageManager -type helmBinaryPackageManager struct { - binaryPath string -} - -// NewHelmBinaryPackageManager initializes a new HelmPackageManager service. -func NewHelmBinaryPackageManager(binaryPath string) *helmBinaryPackageManager { - return &helmBinaryPackageManager{binaryPath: binaryPath} -} - -// runWithKubeConfig will execute run against the provided Kubernetes cluster with kubeconfig as cli arguments. -func (hbpm *helmBinaryPackageManager) runWithKubeConfig(command string, args []string, kca *options.KubernetesClusterAccess, env []string) ([]byte, error) { - cmdArgs := make([]string, 0) - if kca != nil { - cmdArgs = append(cmdArgs, "--kube-apiserver", kca.ClusterServerURL) - cmdArgs = append(cmdArgs, "--kube-token", kca.AuthToken) - cmdArgs = append(cmdArgs, "--kube-ca-file", kca.CertificateAuthorityFile) - } - cmdArgs = append(cmdArgs, args...) - return hbpm.run(command, cmdArgs, env) -} - -// run will execute helm command against the provided Kubernetes cluster. -// The endpointId and authToken are dynamic params (based on the user) that allow helm to execute commands -// in the context of the current user against specified k8s cluster. -func (hbpm *helmBinaryPackageManager) run(command string, args []string, env []string) ([]byte, error) { - cmdArgs := make([]string, 0) - cmdArgs = append(cmdArgs, command) - cmdArgs = append(cmdArgs, args...) - - helmPath := path.Join(hbpm.binaryPath, "helm") - if runtime.GOOS == "windows" { - helmPath = path.Join(hbpm.binaryPath, "helm.exe") - } - - var stderr bytes.Buffer - cmd := exec.Command(helmPath, cmdArgs...) - cmd.Stderr = &stderr - - cmd.Env = os.Environ() - cmd.Env = append(cmd.Env, env...) - - output, err := cmd.Output() - if err != nil { - return nil, errors.Wrap(err, stderr.String()) - } - - return output, nil -} diff --git a/pkg/libhelm/binary/install.go b/pkg/libhelm/binary/install.go deleted file mode 100644 index 937df1500..000000000 --- a/pkg/libhelm/binary/install.go +++ /dev/null @@ -1,48 +0,0 @@ -package binary - -import ( - "github.com/portainer/portainer/pkg/libhelm/options" - "github.com/portainer/portainer/pkg/libhelm/release" - - "github.com/pkg/errors" - "github.com/segmentio/encoding/json" -) - -// Install runs `helm install` with specified install options. -// The install options translate to CLI arguments which are passed in to the helm binary when executing install. -func (hbpm *helmBinaryPackageManager) Install(installOpts options.InstallOptions) (*release.Release, error) { - if installOpts.Name == "" { - installOpts.Name = "--generate-name" - } - args := []string{ - installOpts.Name, - installOpts.Chart, - "--repo", installOpts.Repo, - "--output", "json", - } - if installOpts.Namespace != "" { - args = append(args, "--namespace", installOpts.Namespace) - } - if installOpts.ValuesFile != "" { - args = append(args, "--values", installOpts.ValuesFile) - } - if installOpts.Wait { - args = append(args, "--wait") - } - if installOpts.PostRenderer != "" { - args = append(args, "--post-renderer", installOpts.PostRenderer) - } - - result, err := hbpm.runWithKubeConfig("install", args, installOpts.KubernetesClusterAccess, installOpts.Env) - if err != nil { - return nil, errors.Wrap(err, "failed to run helm install on specified args") - } - - response := &release.Release{} - err = json.Unmarshal(result, &response) - if err != nil { - return nil, errors.Wrap(err, "failed to unmarshal helm install response to Release struct") - } - - return response, nil -} diff --git a/pkg/libhelm/binary/install_test.go b/pkg/libhelm/binary/install_test.go deleted file mode 100644 index 3d6c74707..000000000 --- a/pkg/libhelm/binary/install_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package binary - -import ( - "os" - "os/exec" - "path/filepath" - "testing" - - "github.com/portainer/portainer/pkg/libhelm/options" - "github.com/stretchr/testify/assert" -) - -func createValuesFile(values string) (string, error) { - file, err := os.CreateTemp("", "helm-values") - if err != nil { - return "", err - } - - _, err = file.WriteString(values) - if err != nil { - file.Close() - return "", err - } - - err = file.Close() - if err != nil { - return "", err - } - - return file.Name(), nil -} - -// getHelmBinaryPath is helper function to get local helm binary path (if helm is in path) -func getHelmBinaryPath() (string, error) { - path, err := exec.LookPath("helm") - if err != nil { - return "", err - } - dir, err := filepath.Abs(filepath.Dir(path)) - if err != nil { - return "", err - } - return dir, nil -} - -func Test_Install(t *testing.T) { - ensureIntegrationTest(t) - is := assert.New(t) - - path, err := getHelmBinaryPath() - is.NoError(err, "helm binary must exist in path to run tests") - - hbpm := NewHelmBinaryPackageManager(path) - - t.Run("successfully installs nginx chart with name test-nginx", func(t *testing.T) { - // helm install test-nginx --repo https://kubernetes.github.io/ingress-nginx nginx - installOpts := options.InstallOptions{ - Name: "test-nginx", - Chart: "nginx", - Repo: "https://kubernetes.github.io/ingress-nginx", - } - - release, err := hbpm.Install(installOpts) - defer hbpm.run("uninstall", []string{"test-nginx"}, nil) - - is.NoError(err, "should successfully install release", release) - }) - - t.Run("successfully installs nginx chart with generated name", func(t *testing.T) { - // helm install --generate-name --repo https://kubernetes.github.io/ingress-nginx nginx - installOpts := options.InstallOptions{ - Chart: "nginx", - Repo: "https://kubernetes.github.io/ingress-nginx", - } - release, err := hbpm.Install(installOpts) - defer hbpm.run("uninstall", []string{release.Name}, nil) - - is.NoError(err, "should successfully install release", release) - }) - - t.Run("successfully installs nginx with values", func(t *testing.T) { - // helm install test-nginx-2 --repo https://kubernetes.github.io/ingress-nginx nginx --values /tmp/helm-values3161785816 - values, err := createValuesFile("service:\n port: 8081") - is.NoError(err, "should create a values file") - - defer os.Remove(values) - - installOpts := options.InstallOptions{ - Name: "test-nginx-2", - Chart: "nginx", - Repo: "https://kubernetes.github.io/ingress-nginx", - ValuesFile: values, - } - release, err := hbpm.Install(installOpts) - defer hbpm.run("uninstall", []string{"test-nginx-2"}, nil) - - is.NoError(err, "should successfully install release", release) - }) - - t.Run("successfully installs portainer chart with name portainer-test", func(t *testing.T) { - // helm install portainer-test portainer --repo https://portainer.github.io/k8s/ - installOpts := options.InstallOptions{ - Name: "portainer-test", - Chart: "portainer", - Repo: "https://portainer.github.io/k8s/", - } - release, err := hbpm.Install(installOpts) - defer hbpm.run("uninstall", []string{installOpts.Name}, nil) - - is.NoError(err, "should successfully install release", release) - }) -} - -func ensureIntegrationTest(t *testing.T) { - if _, ok := os.LookupEnv("INTEGRATION_TEST"); !ok { - t.Skip("skip an integration test") - } -} diff --git a/pkg/libhelm/binary/list.go b/pkg/libhelm/binary/list.go deleted file mode 100644 index 886d4c5ea..000000000 --- a/pkg/libhelm/binary/list.go +++ /dev/null @@ -1,38 +0,0 @@ -package binary - -import ( - "github.com/portainer/portainer/pkg/libhelm/options" - "github.com/portainer/portainer/pkg/libhelm/release" - - "github.com/pkg/errors" - "github.com/segmentio/encoding/json" -) - -// List runs `helm list --output json --filter --selector --namespace ` with specified list options. -// The list options translate to CLI args the helm binary -func (hbpm *helmBinaryPackageManager) List(listOpts options.ListOptions) ([]release.ReleaseElement, error) { - args := []string{"--output", "json"} - - if listOpts.Filter != "" { - args = append(args, "--filter", listOpts.Filter) - } - if listOpts.Selector != "" { - args = append(args, "--selector", listOpts.Selector) - } - if listOpts.Namespace != "" { - args = append(args, "--namespace", listOpts.Namespace) - } - - result, err := hbpm.runWithKubeConfig("list", args, listOpts.KubernetesClusterAccess, listOpts.Env) - if err != nil { - return []release.ReleaseElement{}, errors.Wrap(err, "failed to run helm list on specified args") - } - - response := []release.ReleaseElement{} - err = json.Unmarshal(result, &response) - if err != nil { - return []release.ReleaseElement{}, errors.Wrap(err, "failed to unmarshal helm list response to releastElement list") - } - - return response, nil -} diff --git a/pkg/libhelm/binary/search_repo.go b/pkg/libhelm/binary/search_repo.go deleted file mode 100644 index f875589cb..000000000 --- a/pkg/libhelm/binary/search_repo.go +++ /dev/null @@ -1,118 +0,0 @@ -package binary - -// Package common implements common functionality for the helm. -// The functionality does not rely on the implementation of `HelmPackageManager` - -import ( - "net/http" - "net/url" - "path" - "time" - - "github.com/portainer/portainer/pkg/libhelm/options" - - "github.com/pkg/errors" - "github.com/segmentio/encoding/json" - "gopkg.in/yaml.v3" -) - -var errRequiredSearchOptions = errors.New("repo is required") -var errInvalidRepoURL = errors.New("the request failed since either the Helm repository was not found or the index.yaml is not valid") - -type File struct { - APIVersion string `yaml:"apiVersion" json:"apiVersion"` - Entries map[string][]Entry `yaml:"entries" json:"entries"` - Generated string `yaml:"generated" json:"generated"` -} - -type Annotations struct { - Category string `yaml:"category" json:"category"` -} - -type Entry struct { - Annotations *Annotations `yaml:"annotations" json:"annotations,omitempty"` - Created string `yaml:"created" json:"created"` - Deprecated bool `yaml:"deprecated" json:"deprecated"` - Description string `yaml:"description" json:"description"` - Digest string `yaml:"digest" json:"digest"` - Home string `yaml:"home" json:"home"` - Name string `yaml:"name" json:"name"` - Sources []string `yaml:"sources" json:"sources"` - Urls []string `yaml:"urls" json:"urls"` - Version string `yaml:"version" json:"version"` - Icon string `yaml:"icon" json:"icon,omitempty"` -} - -// SearchRepo downloads the `index.yaml` file for specified repo, parses it and returns JSON to caller. -// The functionality is similar to that of what `helm search repo [chart] --repo ` CLI runs; -// this approach is used instead since the `helm search repo` requires a repo to be added to the global helm cache -func (hbpm *helmBinaryPackageManager) SearchRepo(searchRepoOpts options.SearchRepoOptions) ([]byte, error) { - if searchRepoOpts.Repo == "" { - return nil, errRequiredSearchOptions - } - - client := searchRepoOpts.Client - - if client == nil { - // The current index.yaml is ~9MB on bitnami. - // At a slow @2mbit download = 40s. @100bit = ~1s. - // I'm seeing 3 - 4s over wifi. - // Give ample time but timeout for now. Can be improved in the future - client = &http.Client{ - Timeout: 300 * time.Second, - Transport: http.DefaultTransport, - } - } - - // Allow redirect behavior to be overridden if specified. - if client.CheckRedirect == nil { - client.CheckRedirect = defaultCheckRedirect - } - - url, err := url.ParseRequestURI(searchRepoOpts.Repo) - if err != nil { - return nil, errors.Wrap(err, "invalid helm chart URL: "+searchRepoOpts.Repo) - } - - url.Path = path.Join(url.Path, "index.yaml") - resp, err := client.Get(url.String()) - if err != nil { - return nil, errInvalidRepoURL - } - defer resp.Body.Close() - - var file File - err = yaml.NewDecoder(resp.Body).Decode(&file) - if err != nil { - return nil, errInvalidRepoURL - } - - // Validate index.yaml - if file.APIVersion == "" || file.Entries == nil { - return nil, errInvalidRepoURL - } - - result, err := json.Marshal(file) - if err != nil { - return nil, errInvalidRepoURL - } - - return result, nil -} - -// defaultCheckRedirect is a default CheckRedirect for helm -// We don't allow redirects to URLs not ending in index.yaml -// After that we follow the go http client behavior which is to stop -// after a maximum of 10 redirects -func defaultCheckRedirect(req *http.Request, via []*http.Request) error { - // The request url must end in index.yaml - if path.Base(req.URL.Path) != "index.yaml" { - return errors.New("the request URL must end in index.yaml") - } - - // default behavior below - if len(via) >= 10 { - return errors.New("stopped after 10 redirects") - } - return nil -} diff --git a/pkg/libhelm/binary/search_repo_test.go b/pkg/libhelm/binary/search_repo_test.go deleted file mode 100644 index 48786a0d7..000000000 --- a/pkg/libhelm/binary/search_repo_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package binary - -import ( - "testing" - - "github.com/portainer/portainer/pkg/libhelm/libhelmtest" - "github.com/portainer/portainer/pkg/libhelm/options" - "github.com/stretchr/testify/assert" -) - -func Test_SearchRepo(t *testing.T) { - libhelmtest.EnsureIntegrationTest(t) - is := assert.New(t) - - hpm := NewHelmBinaryPackageManager("") - - type testCase struct { - name string - url string - invalid bool - } - - tests := []testCase{ - {"not a helm repo", "https://portainer.io", true}, - {"ingress helm repo", "https://kubernetes.github.io/ingress-nginx", false}, - {"portainer helm repo", "https://portainer.github.io/k8s/", false}, - {"gitlap helm repo with trailing slash", "https://charts.gitlab.io/", false}, - {"elastic helm repo with trailing slash", "https://helm.elastic.co/", false}, - {"fabric8.io helm repo with trailing slash", "https://fabric8.io/helm/", false}, - {"lensesio helm repo without trailing slash", "https://lensesio.github.io/kafka-helm-charts", false}, - } - - for _, test := range tests { - func(tc testCase) { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - response, err := hpm.SearchRepo(options.SearchRepoOptions{Repo: tc.url}) - if tc.invalid { - is.Errorf(err, "error expected: %s", tc.url) - } else { - is.NoError(err, "no error expected: %s", tc.url) - } - - if err == nil { - is.NotEmpty(response, "response expected") - } - }) - }(test) - } -} diff --git a/pkg/libhelm/binary/show.go b/pkg/libhelm/binary/show.go deleted file mode 100644 index 848e8ab26..000000000 --- a/pkg/libhelm/binary/show.go +++ /dev/null @@ -1,29 +0,0 @@ -package binary - -import ( - "github.com/pkg/errors" - "github.com/portainer/portainer/pkg/libhelm/options" -) - -var errRequiredShowOptions = errors.New("chart, repo and output format are required") - -// Show runs `helm show --repo ` with specified show options. -// The show options translate to CLI arguments which are passed in to the helm binary when executing install. -func (hbpm *helmBinaryPackageManager) Show(showOpts options.ShowOptions) ([]byte, error) { - if showOpts.Chart == "" || showOpts.Repo == "" || showOpts.OutputFormat == "" { - return nil, errRequiredShowOptions - } - - args := []string{ - string(showOpts.OutputFormat), - showOpts.Chart, - "--repo", showOpts.Repo, - } - - result, err := hbpm.run("show", args, showOpts.Env) - if err != nil { - return nil, errors.New("the request failed since either the Helm repository was not found or the chart does not exist") - } - - return result, nil -} diff --git a/pkg/libhelm/binary/uninstall.go b/pkg/libhelm/binary/uninstall.go deleted file mode 100644 index b926e8ee5..000000000 --- a/pkg/libhelm/binary/uninstall.go +++ /dev/null @@ -1,29 +0,0 @@ -package binary - -import ( - "github.com/pkg/errors" - "github.com/portainer/portainer/pkg/libhelm/options" -) - -var errRequiredUninstallOptions = errors.New("release name is required") - -// Uninstall runs `helm uninstall --namespace ` with specified uninstall options. -// The uninstall options translate to CLI arguments which are passed in to the helm binary when executing uninstall. -func (hbpm *helmBinaryPackageManager) Uninstall(uninstallOpts options.UninstallOptions) error { - if uninstallOpts.Name == "" { - return errRequiredUninstallOptions - } - - args := []string{uninstallOpts.Name} - - if uninstallOpts.Namespace != "" { - args = append(args, "--namespace", uninstallOpts.Namespace) - } - - _, err := hbpm.runWithKubeConfig("uninstall", args, uninstallOpts.KubernetesClusterAccess, uninstallOpts.Env) - if err != nil { - return errors.Wrap(err, "failed to run helm uninstall on specified args") - } - - return nil -} diff --git a/pkg/libhelm/manager.go b/pkg/libhelm/manager.go index f378b2e55..1396195bd 100644 --- a/pkg/libhelm/manager.go +++ b/pkg/libhelm/manager.go @@ -1,22 +1,11 @@ package libhelm import ( - "errors" - - "github.com/portainer/portainer/pkg/libhelm/binary" + "github.com/portainer/portainer/pkg/libhelm/sdk" + "github.com/portainer/portainer/pkg/libhelm/types" ) -// HelmConfig is a struct that holds the configuration for the Helm package manager -type HelmConfig struct { - BinaryPath string `example:"/portainer/dist"` -} - -var errBinaryPathNotSpecified = errors.New("binary path not specified") - // NewHelmPackageManager returns a new instance of HelmPackageManager based on HelmConfig -func NewHelmPackageManager(config HelmConfig) (HelmPackageManager, error) { - if config.BinaryPath != "" { - return binary.NewHelmBinaryPackageManager(config.BinaryPath), nil - } - return nil, errBinaryPathNotSpecified +func NewHelmPackageManager() (types.HelmPackageManager, error) { + return sdk.NewHelmSDKPackageManager(), nil } diff --git a/pkg/libhelm/options/cluster_access.go b/pkg/libhelm/options/cluster_access.go index a66acdc5c..1fe079c12 100644 --- a/pkg/libhelm/options/cluster_access.go +++ b/pkg/libhelm/options/cluster_access.go @@ -2,6 +2,9 @@ package options // KubernetesClusterAccess represents core details which can be used to generate KubeConfig file/data type KubernetesClusterAccess struct { + ClusterName string `example:"portainer-cluster-endpoint-1"` + ContextName string `example:"portainer-ctx-endpoint-1"` + UserName string `example:"portainer-user-endpoint-1"` ClusterServerURL string `example:"https://mycompany.k8s.com"` CertificateAuthorityFile string `example:"/data/tls/localhost.crt"` AuthToken string `example:"ey..."` diff --git a/pkg/libhelm/sdk/client.go b/pkg/libhelm/sdk/client.go new file mode 100644 index 000000000..7989e4558 --- /dev/null +++ b/pkg/libhelm/sdk/client.go @@ -0,0 +1,165 @@ +package sdk + +import ( + "github.com/pkg/errors" + "github.com/portainer/portainer/pkg/libhelm/options" + "github.com/rs/zerolog/log" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chartutil" + "helm.sh/helm/v3/pkg/cli" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/client-go/discovery" + "k8s.io/client-go/discovery/cached/memory" + "k8s.io/client-go/rest" + "k8s.io/client-go/restmapper" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/clientcmd/api" +) + +// newRESTClientGetter creates a custom RESTClientGetter using the provided client config +type clientConfigGetter struct { + clientConfig clientcmd.ClientConfig + namespace string +} + +// initActionConfig initializes the action configuration with kubernetes config +func (hspm *HelmSDKPackageManager) initActionConfig(actionConfig *action.Configuration, namespace string, k8sAccess *options.KubernetesClusterAccess) error { + // If namespace is not provided, use the default namespace + if namespace == "" { + namespace = "default" + } + + if k8sAccess == nil { + // Use default kubeconfig + settings := cli.New() + clientGetter := settings.RESTClientGetter() + return actionConfig.Init(clientGetter, namespace, "secret", hspm.logf) + } + + // Create client config + configAPI := generateConfigAPI(namespace, k8sAccess) + clientConfig := clientcmd.NewDefaultClientConfig(*configAPI, &clientcmd.ConfigOverrides{}) + + // Create a custom RESTClientGetter that uses our in-memory config + clientGetter, err := newRESTClientGetter(clientConfig, namespace) + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Str("cluster_name", k8sAccess.ClusterName). + Str("cluster_url", k8sAccess.ClusterServerURL). + Str("user_name", k8sAccess.UserName). + Err(err). + Msg("failed to create client getter") + return err + } + + return actionConfig.Init(clientGetter, namespace, "secret", hspm.logf) +} + +// generateConfigAPI generates a new kubeconfig configuration +func generateConfigAPI(namespace string, k8sAccess *options.KubernetesClusterAccess) *api.Config { + // Create in-memory kubeconfig configuration + configAPI := api.NewConfig() + + // Create cluster + cluster := api.NewCluster() + cluster.Server = k8sAccess.ClusterServerURL + + if k8sAccess.CertificateAuthorityFile != "" { + // If we have a CA file, use it + cluster.CertificateAuthority = k8sAccess.CertificateAuthorityFile + } else { + // Otherwise skip TLS verification + cluster.InsecureSkipTLSVerify = true + } + + // Create auth info with token + authInfo := api.NewAuthInfo() + authInfo.Token = k8sAccess.AuthToken + + // Create context + context := api.NewContext() + context.Cluster = k8sAccess.ClusterName + context.AuthInfo = k8sAccess.UserName + context.Namespace = namespace + + // Add to config + configAPI.Clusters[k8sAccess.ClusterName] = cluster + configAPI.AuthInfos[k8sAccess.UserName] = authInfo + configAPI.Contexts[k8sAccess.ContextName] = context + configAPI.CurrentContext = k8sAccess.ContextName + + return configAPI +} + +func newRESTClientGetter(clientConfig clientcmd.ClientConfig, namespace string) (*clientConfigGetter, error) { + if clientConfig == nil { + log.Error(). + Str("context", "HelmClient"). + Msg("client config is nil") + + return nil, errors.New("client config provided during the helm client initialization was nil. Check the kubernetes cluster access configuration") + } + + return &clientConfigGetter{ + clientConfig: clientConfig, + namespace: namespace, + }, nil +} + +func (c *clientConfigGetter) ToRESTConfig() (*rest.Config, error) { + if c.clientConfig == nil { + log.Error(). + Str("context", "HelmClient"). + Msg("client config is nil") + + return nil, errors.New("client config provided during the helm client initialization was nil. Check the kubernetes cluster access configuration") + } + + return c.clientConfig.ClientConfig() +} + +func (c *clientConfigGetter) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) { + config, err := c.ToRESTConfig() + if err != nil { + return nil, err + } + + // Create the discovery client + discoveryClient, err := discovery.NewDiscoveryClientForConfig(config) + if err != nil { + return nil, err + } + + // Wrap the discovery client with a cached discovery client + return memory.NewMemCacheClient(discoveryClient), nil +} + +func (c *clientConfigGetter) ToRESTMapper() (meta.RESTMapper, error) { + discoveryClient, err := c.ToDiscoveryClient() + if err != nil { + return nil, err + } + + // Create a REST mapper from the discovery client + return restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient), nil +} + +func (c *clientConfigGetter) ToRawKubeConfigLoader() clientcmd.ClientConfig { + return c.clientConfig +} + +// parseValues parses YAML values data into a map +func (hspm *HelmSDKPackageManager) parseValues(data []byte) (map[string]any, error) { + // Use Helm's built-in chartutil.ReadValues which properly handles the conversion + // from map[interface{}]interface{} to map[string]interface{} + return chartutil.ReadValues(data) +} + +// logf is a log helper function for Helm +func (hspm *HelmSDKPackageManager) logf(format string, v ...any) { + // Use zerolog for structured logging + log.Debug(). + Str("context", "HelmClient"). + Msgf(format, v...) +} diff --git a/pkg/libhelm/sdk/client_test.go b/pkg/libhelm/sdk/client_test.go new file mode 100644 index 000000000..9dd7afffe --- /dev/null +++ b/pkg/libhelm/sdk/client_test.go @@ -0,0 +1,156 @@ +package sdk + +import ( + "testing" + + "github.com/portainer/portainer/pkg/libhelm/options" + "github.com/stretchr/testify/assert" + "helm.sh/helm/v3/pkg/action" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/clientcmd/api" +) + +func Test_InitActionConfig(t *testing.T) { + is := assert.New(t) + hspm := NewHelmSDKPackageManager() + + t.Run("with nil k8sAccess should use default kubeconfig", func(t *testing.T) { + actionConfig := new(action.Configuration) + err := hspm.(*HelmSDKPackageManager).initActionConfig(actionConfig, "default", nil) + + // The function should not fail by design, even when not running in a k8s environment + is.NoError(err, "should not return error when not in k8s environment") + }) + + t.Run("with k8sAccess should create in-memory config", func(t *testing.T) { + actionConfig := new(action.Configuration) + k8sAccess := &options.KubernetesClusterAccess{ + ClusterServerURL: "https://kubernetes.default.svc", + AuthToken: "test-token", + } + + // The function should not fail by design + err := hspm.(*HelmSDKPackageManager).initActionConfig(actionConfig, "default", k8sAccess) + is.NoError(err, "should not return error when using in-memory config") + }) + + t.Run("with k8sAccess and CA file should create config with CA", func(t *testing.T) { + actionConfig := new(action.Configuration) + k8sAccess := &options.KubernetesClusterAccess{ + ClusterServerURL: "https://kubernetes.default.svc", + AuthToken: "test-token", + CertificateAuthorityFile: "/path/to/ca.crt", + } + + // The function should not fail by design + err := hspm.(*HelmSDKPackageManager).initActionConfig(actionConfig, "default", k8sAccess) + is.NoError(err, "should not return error when using in-memory config with CA") + }) +} + +func Test_ClientConfigGetter(t *testing.T) { + is := assert.New(t) + + // Create a mock client config + configAPI := api.NewConfig() + + // Create cluster + cluster := api.NewCluster() + cluster.Server = "https://kubernetes.default.svc" + cluster.InsecureSkipTLSVerify = true + + // Create auth info + authInfo := api.NewAuthInfo() + authInfo.Token = "test-token" + + // Create context + context := api.NewContext() + context.Cluster = "test-cluster" + context.AuthInfo = "test-user" + context.Namespace = "default" + + // Add to config + configAPI.Clusters["test-cluster"] = cluster + configAPI.AuthInfos["test-user"] = authInfo + configAPI.Contexts["test-context"] = context + configAPI.CurrentContext = "test-context" + + clientConfig := clientcmd.NewDefaultClientConfig(*configAPI, &clientcmd.ConfigOverrides{}) + + // Create client config getter + clientGetter, err := newRESTClientGetter(clientConfig, "default") + is.NoError(err, "should not return error when creating client getter") + + // Test ToRESTConfig + restConfig, err := clientGetter.ToRESTConfig() + is.NoError(err, "should not return error when creating REST config") + is.NotNil(restConfig, "should return non-nil REST config") + is.Equal("https://kubernetes.default.svc", restConfig.Host, "host should be https://kubernetes.default.svc") + is.Equal("test-token", restConfig.BearerToken, "bearer token should be test-token") + + // Test ToDiscoveryClient + discoveryClient, err := clientGetter.ToDiscoveryClient() + is.NoError(err, "should not return error when creating discovery client") + is.NotNil(discoveryClient, "should return non-nil discovery client") + + // Test ToRESTMapper + restMapper, err := clientGetter.ToRESTMapper() + is.NoError(err, "should not return error when creating REST mapper") + is.NotNil(restMapper, "should return non-nil REST mapper") + + // Test ToRawKubeConfigLoader + config := clientGetter.ToRawKubeConfigLoader() + is.NotNil(config, "should return non-nil config loader") +} + +func Test_ParseValues(t *testing.T) { + is := assert.New(t) + hspm := NewHelmSDKPackageManager() + + t.Run("should parse valid YAML values", func(t *testing.T) { + yamlData := []byte(` +service: + type: ClusterIP + port: 80 +resources: + limits: + cpu: 100m + memory: 128Mi +`) + values, err := hspm.(*HelmSDKPackageManager).parseValues(yamlData) + is.NoError(err, "should parse valid YAML without error") + is.NotNil(values, "should return non-nil values") + + // Verify structure + service, ok := values["service"].(map[string]interface{}) + is.True(ok, "service should be a map") + is.Equal("ClusterIP", service["type"], "service type should be ClusterIP") + is.Equal(float64(80), service["port"], "service port should be 80") + + resources, ok := values["resources"].(map[string]interface{}) + is.True(ok, "resources should be a map") + limits, ok := resources["limits"].(map[string]interface{}) + is.True(ok, "limits should be a map") + is.Equal("100m", limits["cpu"], "cpu limit should be 100m") + is.Equal("128Mi", limits["memory"], "memory limit should be 128Mi") + }) + + t.Run("should handle invalid YAML", func(t *testing.T) { + yamlData := []byte(` +service: + type: ClusterIP + port: 80 + invalid yaml +`) + _, err := hspm.(*HelmSDKPackageManager).parseValues(yamlData) + is.Error(err, "should return error for invalid YAML") + }) + + t.Run("should handle empty YAML", func(t *testing.T) { + yamlData := []byte(``) + values, err := hspm.(*HelmSDKPackageManager).parseValues(yamlData) + is.NoError(err, "should not return error for empty YAML") + is.NotNil(values, "should return non-nil values for empty YAML") + is.Len(values, 0, "should return empty map for empty YAML") + }) +} diff --git a/pkg/libhelm/sdk/install.go b/pkg/libhelm/sdk/install.go new file mode 100644 index 000000000..26b50927b --- /dev/null +++ b/pkg/libhelm/sdk/install.go @@ -0,0 +1,210 @@ +package sdk + +import ( + "os" + + "github.com/pkg/errors" + "github.com/portainer/portainer/pkg/libhelm/options" + "github.com/portainer/portainer/pkg/libhelm/release" + "github.com/rs/zerolog/log" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/downloader" + "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v3/pkg/postrender" +) + +// Install implements the HelmPackageManager interface by using the Helm SDK to install a chart. +func (hspm *HelmSDKPackageManager) Install(installOpts options.InstallOptions) (*release.Release, error) { + log.Debug(). + Str("context", "HelmClient"). + Str("chart", installOpts.Chart). + Str("name", installOpts.Name). + Str("namespace", installOpts.Namespace). + Str("repo", installOpts.Repo). + Bool("wait", installOpts.Wait). + Msg("Installing Helm chart") + + if installOpts.Name == "" { + log.Error(). + Str("context", "HelmClient"). + Str("chart", installOpts.Chart). + Str("name", installOpts.Name). + Str("namespace", installOpts.Namespace). + Str("repo", installOpts.Repo). + Bool("wait", installOpts.Wait). + Msg("Name is required for helm release installation") + return nil, errors.New("name is required for helm release installation") + } + + // Initialize action configuration with kubernetes config + actionConfig := new(action.Configuration) + err := hspm.initActionConfig(actionConfig, installOpts.Namespace, installOpts.KubernetesClusterAccess) + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Str("chart", installOpts.Chart). + Str("namespace", installOpts.Namespace). + Err(err). + Msg("Failed to initialize helm configuration for helm release installation") + return nil, errors.Wrap(err, "failed to initialize helm configuration for helm release installation") + } + + installClient, err := initInstallClient(actionConfig, installOpts) + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Err(err). + Msg("Failed to initialize helm install client for helm release installation") + return nil, errors.Wrap(err, "failed to initialize helm install client for helm release installation") + } + + values, err := hspm.GetHelmValuesFromFile(installOpts.ValuesFile) + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Err(err). + Msg("Failed to get Helm values from file for helm release installation") + return nil, errors.Wrap(err, "failed to get Helm values from file for helm release installation") + } + + chart, err := hspm.loadAndValidateChart(installClient, installOpts) + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Err(err). + Msg("Failed to load and validate chart for helm release installation") + return nil, errors.Wrap(err, "failed to load and validate chart for helm release installation") + } + + // Run the installation + log.Info(). + Str("context", "HelmClient"). + Str("chart", installOpts.Chart). + Str("name", installOpts.Name). + Str("namespace", installOpts.Namespace). + Msg("Running chart installation for helm release") + + helmRelease, err := installClient.Run(chart, values) + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Str("chart", installOpts.Chart). + Str("name", installOpts.Name). + Str("namespace", installOpts.Namespace). + Err(err). + Msg("Failed to install helm chart for helm release installation") + return nil, errors.Wrap(err, "helm was not able to install the chart for helm release installation") + } + + return &release.Release{ + Name: helmRelease.Name, + Namespace: helmRelease.Namespace, + Chart: release.Chart{ + Metadata: &release.Metadata{ + Name: helmRelease.Chart.Metadata.Name, + Version: helmRelease.Chart.Metadata.Version, + AppVersion: helmRelease.Chart.Metadata.AppVersion, + }, + }, + Labels: helmRelease.Labels, + Version: helmRelease.Version, + Manifest: helmRelease.Manifest, + }, nil +} + +// loadAndValidateChart locates and loads the chart, and validates it. +// it also checks for chart dependencies and updates them if necessary. +// it returns the chart information. +func (hspm *HelmSDKPackageManager) loadAndValidateChart(installClient *action.Install, installOpts options.InstallOptions) (*chart.Chart, error) { + // Locate and load the chart + chartPath, err := installClient.ChartPathOptions.LocateChart(installOpts.Chart, hspm.settings) + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Str("chart", installOpts.Chart). + Err(err). + Msg("Failed to locate chart for helm release installation") + return nil, errors.Wrapf(err, "failed to find the helm chart at the path: %s/%s", installOpts.Repo, installOpts.Chart) + } + + chartReq, err := loader.Load(chartPath) + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Str("chart_path", chartPath). + Err(err). + Msg("Failed to load chart for helm release installation") + return nil, errors.Wrap(err, "failed to load chart for helm release installation") + } + + // Check chart dependencies to make sure all are present in /charts + if chartDependencies := chartReq.Metadata.Dependencies; chartDependencies != nil { + if err := action.CheckDependencies(chartReq, chartDependencies); err != nil { + err = errors.Wrap(err, "failed to check chart dependencies for helm release installation") + if !installClient.DependencyUpdate { + return nil, err + } + + log.Debug(). + Str("context", "HelmClient"). + Str("chart", installOpts.Chart). + Msg("Updating chart dependencies for helm release installation") + + providers := getter.All(hspm.settings) + manager := &downloader.Manager{ + Out: os.Stdout, + ChartPath: chartPath, + Keyring: installClient.ChartPathOptions.Keyring, + SkipUpdate: false, + Getters: providers, + RepositoryConfig: hspm.settings.RepositoryConfig, + RepositoryCache: hspm.settings.RepositoryCache, + Debug: hspm.settings.Debug, + } + if err := manager.Update(); err != nil { + log.Error(). + Str("context", "HelmClient"). + Str("chart", installOpts.Chart). + Err(err). + Msg("Failed to update chart dependencies for helm release installation") + return nil, errors.Wrap(err, "failed to update chart dependencies for helm release installation") + } + + // Reload the chart with the updated Chart.lock file. + if chartReq, err = loader.Load(chartPath); err != nil { + log.Error(). + Str("context", "HelmClient"). + Str("chart_path", chartPath). + Err(err). + Msg("Failed to reload chart after dependency update for helm release installation") + return nil, errors.Wrap(err, "failed to reload chart after dependency update for helm release installation") + } + } + } + + return chartReq, nil +} + +// initInstallClient initializes the install client with the given options +// and return the install client. +func initInstallClient(actionConfig *action.Configuration, installOpts options.InstallOptions) (*action.Install, error) { + installClient := action.NewInstall(actionConfig) + installClient.CreateNamespace = true + installClient.DependencyUpdate = true + + installClient.ReleaseName = installOpts.Name + installClient.Namespace = installOpts.Namespace + installClient.ChartPathOptions.RepoURL = installOpts.Repo + installClient.Wait = installOpts.Wait + if installOpts.PostRenderer != "" { + postRenderer, err := postrender.NewExec(installOpts.PostRenderer) + if err != nil { + return nil, errors.Wrap(err, "failed to create post renderer") + } + installClient.PostRenderer = postRenderer + } + + return installClient, nil +} diff --git a/pkg/libhelm/sdk/install_test.go b/pkg/libhelm/sdk/install_test.go new file mode 100644 index 000000000..9bd0d04f3 --- /dev/null +++ b/pkg/libhelm/sdk/install_test.go @@ -0,0 +1,190 @@ +package sdk + +import ( + "os" + "testing" + + "github.com/portainer/portainer/pkg/libhelm/options" + "github.com/portainer/portainer/pkg/libhelm/test" + "github.com/stretchr/testify/assert" +) + +func createValuesFile(values string) (string, error) { + file, err := os.CreateTemp("", "helm-values") + if err != nil { + return "", err + } + + _, err = file.WriteString(values) + if err != nil { + file.Close() + return "", err + } + + err = file.Close() + if err != nil { + return "", err + } + + return file.Name(), nil +} + +func Test_Install(t *testing.T) { + test.EnsureIntegrationTest(t) + is := assert.New(t) + + // Create a new SDK package manager + hspm := NewHelmSDKPackageManager() + + t.Run("successfully installs nginx chart with name test-nginx", func(t *testing.T) { + // SDK equivalent of: helm install test-nginx --repo https://kubernetes.github.io/ingress-nginx nginx + installOpts := options.InstallOptions{ + Name: "test-nginx", + Chart: "ingress-nginx", + Repo: "https://kubernetes.github.io/ingress-nginx", + } + + release, err := hspm.Install(installOpts) + if release != nil { + defer hspm.Uninstall(options.UninstallOptions{ + Name: "test-nginx", + }) + } + + is.NoError(err, "should successfully install release") + is.NotNil(release, "should return non-nil release") + is.Equal("test-nginx", release.Name, "release name should match") + is.Equal(1, release.Version, "release version should be 1") + is.NotEmpty(release.Manifest, "release manifest should not be empty") + }) + + t.Run("successfully installs nginx with values", func(t *testing.T) { + // SDK equivalent of: helm install test-nginx-2 --repo https://kubernetes.github.io/ingress-nginx nginx --values /tmp/helm-values3161785816 + values, err := createValuesFile("service:\n port: 8081") + is.NoError(err, "should create a values file") + + defer os.Remove(values) + + installOpts := options.InstallOptions{ + Name: "test-nginx-2", + Chart: "ingress-nginx", + Repo: "https://kubernetes.github.io/ingress-nginx", + ValuesFile: values, + } + release, err := hspm.Install(installOpts) + if release != nil { + defer hspm.Uninstall(options.UninstallOptions{ + Name: "test-nginx-2", + }) + } + + is.NoError(err, "should successfully install release") + is.NotNil(release, "should return non-nil release") + is.Equal("test-nginx-2", release.Name, "release name should match") + is.Equal(1, release.Version, "release version should be 1") + is.NotEmpty(release.Manifest, "release manifest should not be empty") + }) + + t.Run("successfully installs portainer chart with name portainer-test", func(t *testing.T) { + // SDK equivalent of: helm install portainer-test portainer --repo https://portainer.github.io/k8s/ + installOpts := options.InstallOptions{ + Name: "portainer-test", + Chart: "portainer", + Repo: "https://portainer.github.io/k8s/", + } + release, err := hspm.Install(installOpts) + if release != nil { + defer hspm.Uninstall(options.UninstallOptions{ + Name: installOpts.Name, + }) + } + + is.NoError(err, "should successfully install release") + is.NotNil(release, "should return non-nil release") + is.Equal("portainer-test", release.Name, "release name should match") + is.Equal(1, release.Version, "release version should be 1") + is.NotEmpty(release.Manifest, "release manifest should not be empty") + }) + + t.Run("install with values as string", func(t *testing.T) { + // First create a values file since InstallOptions doesn't support values as string directly + values, err := createValuesFile("service:\n port: 8082") + is.NoError(err, "should create a values file") + + defer os.Remove(values) + + // Install with values file + installOpts := options.InstallOptions{ + Name: "test-nginx-3", + Chart: "ingress-nginx", + Repo: "https://kubernetes.github.io/ingress-nginx", + ValuesFile: values, + } + release, err := hspm.Install(installOpts) + if release != nil { + defer hspm.Uninstall(options.UninstallOptions{ + Name: "test-nginx-3", + }) + } + + is.NoError(err, "should successfully install release") + is.NotNil(release, "should return non-nil release") + is.Equal("test-nginx-3", release.Name, "release name should match") + }) + + t.Run("install with namespace", func(t *testing.T) { + // Install with namespace + installOpts := options.InstallOptions{ + Name: "test-nginx-4", + Chart: "ingress-nginx", + Repo: "https://kubernetes.github.io/ingress-nginx", + Namespace: "default", + } + release, err := hspm.Install(installOpts) + if release != nil { + defer hspm.Uninstall(options.UninstallOptions{ + Name: "test-nginx-4", + Namespace: "default", + }) + } + + is.NoError(err, "should successfully install release") + is.NotNil(release, "should return non-nil release") + is.Equal("test-nginx-4", release.Name, "release name should match") + is.Equal("default", release.Namespace, "release namespace should match") + }) + + t.Run("returns an error when name is not provided", func(t *testing.T) { + installOpts := options.InstallOptions{ + Chart: "ingress-nginx", + Repo: "https://kubernetes.github.io/ingress-nginx", + } + _, err := hspm.Install(installOpts) + + is.Error(err, "should return an error when name is not provided") + is.Equal(err.Error(), "name is required") + }) + + t.Run("install with invalid chart", func(t *testing.T) { + // Install with invalid chart + installOpts := options.InstallOptions{ + Name: "test-invalid", + Chart: "non-existent-chart", + Repo: "https://kubernetes.github.io/ingress-nginx", + } + _, err := hspm.Install(installOpts) + is.Error(err, "should return error when chart doesn't exist") + is.Equal(err.Error(), "failed to find the helm chart at the path: https://kubernetes.github.io/ingress-nginx/non-existent-chart") + }) + + t.Run("install with invalid repo", func(t *testing.T) { + // Install with invalid repo + installOpts := options.InstallOptions{ + Name: "test-invalid-repo", + Chart: "nginx", + Repo: "https://non-existent-repo.example.com", + } + _, err := hspm.Install(installOpts) + is.Error(err, "should return error when repo doesn't exist") + }) +} diff --git a/pkg/libhelm/sdk/list.go b/pkg/libhelm/sdk/list.go new file mode 100644 index 000000000..494298a91 --- /dev/null +++ b/pkg/libhelm/sdk/list.go @@ -0,0 +1,108 @@ +package sdk + +import ( + "fmt" + "strconv" + + "github.com/pkg/errors" + "github.com/portainer/portainer/pkg/libhelm/options" + "github.com/portainer/portainer/pkg/libhelm/release" + "github.com/rs/zerolog/log" + "helm.sh/helm/v3/pkg/action" + sdkrelease "helm.sh/helm/v3/pkg/release" +) + +// List implements the HelmPackageManager interface by using the Helm SDK to list releases. +// It returns a slice of ReleaseElement. +func (hspm *HelmSDKPackageManager) List(listOpts options.ListOptions) ([]release.ReleaseElement, error) { + log.Debug(). + Str("context", "HelmClient"). + Str("namespace", listOpts.Namespace). + Str("filter", listOpts.Filter). + Str("selector", listOpts.Selector). + Msg("Listing Helm releases") + + // Initialize action configuration with kubernetes config + actionConfig := new(action.Configuration) + err := hspm.initActionConfig(actionConfig, listOpts.Namespace, listOpts.KubernetesClusterAccess) + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Str("namespace", listOpts.Namespace). + Err(err). + Msg("Failed to initialize helm configuration") + return nil, errors.Wrap(err, "failed to initialize helm configuration") + } + + listClient, err := initListClient(actionConfig, listOpts) + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Err(err). + Msg("Failed to initialize helm list client") + } + + // Run the list operation + releases, err := listClient.Run() + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Err(err). + Msg("Failed to list helm releases") + return []release.ReleaseElement{}, errors.Wrap(err, "failed to list helm releases") + } + + // Convert from SDK release type to our release element type and return + return convertToReleaseElements(releases), nil +} + +// convertToReleaseElements converts from the SDK release type to our release element type +func convertToReleaseElements(releases []*sdkrelease.Release) []release.ReleaseElement { + elements := make([]release.ReleaseElement, len(releases)) + + for i, rel := range releases { + chartName := fmt.Sprintf("%s-%s", rel.Chart.Metadata.Name, rel.Chart.Metadata.Version) + + elements[i] = release.ReleaseElement{ + Name: rel.Name, + Namespace: rel.Namespace, + Revision: strconv.Itoa(rel.Version), + Updated: rel.Info.LastDeployed.String(), + Status: string(rel.Info.Status), + Chart: chartName, + AppVersion: rel.Chart.Metadata.AppVersion, + } + } + + return elements +} + +// initListClient initializes the list client with the given options +// and return the list client. +func initListClient(actionConfig *action.Configuration, listOpts options.ListOptions) (*action.List, error) { + listClient := action.NewList(actionConfig) + + // Configure list options + if listOpts.Filter != "" { + listClient.Filter = listOpts.Filter + } + + if listOpts.Selector != "" { + listClient.Selector = listOpts.Selector + } + + // If no namespace is specified in options, list across all namespaces + if listOpts.Namespace == "" { + listClient.AllNamespaces = true + } + + // No limit by default + listClient.Limit = 0 + // Show all releases, even if in a pending or failed state + listClient.All = true + + // Set state mask to ensure proper filtering by status + listClient.SetStateMask() + + return listClient, nil +} diff --git a/pkg/libhelm/sdk/list_test.go b/pkg/libhelm/sdk/list_test.go new file mode 100644 index 000000000..c2f0ac43c --- /dev/null +++ b/pkg/libhelm/sdk/list_test.go @@ -0,0 +1,72 @@ +package sdk + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v3/pkg/time" +) + +func Test_ConvertToReleaseElements(t *testing.T) { + is := assert.New(t) + + // Create mock releases + releases := []*release.Release{ + { + Name: "release1", + Namespace: "default", + Version: 1, + Info: &release.Info{ + Status: release.StatusDeployed, + LastDeployed: time.Now(), + }, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "chart1", + Version: "1.0.0", + AppVersion: "1.0.0", + }, + }, + }, + { + Name: "release2", + Namespace: "kube-system", + Version: 2, + Info: &release.Info{ + Status: release.StatusFailed, + LastDeployed: time.Now(), + }, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "chart2", + Version: "2.0.0", + AppVersion: "2.0.0", + }, + }, + }, + } + + // Convert to release elements + elements := convertToReleaseElements(releases) + + // Verify conversion + is.Len(elements, 2, "should return 2 release elements") + + // Verify first release + is.Equal("release1", elements[0].Name, "first release name should be release1") + is.Equal("default", elements[0].Namespace, "first release namespace should be default") + is.Equal("1", elements[0].Revision, "first release revision should be 1") + is.Equal(string(release.StatusDeployed), elements[0].Status, "first release status should be deployed") + is.Equal("chart1-1.0.0", elements[0].Chart, "first release chart should be chart1-1.0.0") + is.Equal("1.0.0", elements[0].AppVersion, "first release app version should be 1.0.0") + + // Verify second release + is.Equal("release2", elements[1].Name, "second release name should be release2") + is.Equal("kube-system", elements[1].Namespace, "second release namespace should be kube-system") + is.Equal("2", elements[1].Revision, "second release revision should be 2") + is.Equal(string(release.StatusFailed), elements[1].Status, "second release status should be failed") + is.Equal("chart2-2.0.0", elements[1].Chart, "second release chart should be chart2-2.0.0") + is.Equal("2.0.0", elements[1].AppVersion, "second release app version should be 2.0.0") +} diff --git a/pkg/libhelm/sdk/manager.go b/pkg/libhelm/sdk/manager.go new file mode 100644 index 000000000..8652dd724 --- /dev/null +++ b/pkg/libhelm/sdk/manager.go @@ -0,0 +1,23 @@ +package sdk + +import ( + "time" + + "github.com/portainer/portainer/pkg/libhelm/types" + "helm.sh/helm/v3/pkg/cli" +) + +// HelmSDKPackageManager is a wrapper for the helm SDK which implements HelmPackageManager +type HelmSDKPackageManager struct { + settings *cli.EnvSettings + timeout time.Duration +} + +// NewHelmSDKPackageManager initializes a new HelmPackageManager service using the Helm SDK +func NewHelmSDKPackageManager() types.HelmPackageManager { + settings := cli.New() + return &HelmSDKPackageManager{ + settings: settings, + timeout: 300 * time.Second, // 5 minutes default timeout + } +} diff --git a/pkg/libhelm/sdk/manager_test.go b/pkg/libhelm/sdk/manager_test.go new file mode 100644 index 000000000..018794501 --- /dev/null +++ b/pkg/libhelm/sdk/manager_test.go @@ -0,0 +1,29 @@ +package sdk + +import ( + "testing" + "time" + + "github.com/portainer/portainer/pkg/libhelm/types" + "github.com/stretchr/testify/assert" +) + +func Test_NewHelmSDKPackageManager(t *testing.T) { + is := assert.New(t) + + // Test that NewHelmSDKPackageManager returns a non-nil HelmPackageManager + manager := NewHelmSDKPackageManager() + is.NotNil(manager, "should return non-nil HelmPackageManager") + + // Test that the returned manager is of the correct type + _, ok := manager.(*HelmSDKPackageManager) + is.True(ok, "should return a *HelmSDKPackageManager") + + // Test that the manager has the expected fields + sdkManager := manager.(*HelmSDKPackageManager) + is.NotNil(sdkManager.settings, "should have non-nil settings") + is.Equal(300*time.Second, sdkManager.timeout, "should have 5 minute timeout") + + // Test that the manager implements the HelmPackageManager interface + var _ types.HelmPackageManager = manager +} diff --git a/pkg/libhelm/sdk/search_repo.go b/pkg/libhelm/sdk/search_repo.go new file mode 100644 index 000000000..90dd98529 --- /dev/null +++ b/pkg/libhelm/sdk/search_repo.go @@ -0,0 +1,351 @@ +package sdk + +import ( + "net/url" + "os" + "path/filepath" + + "github.com/pkg/errors" + "github.com/portainer/portainer/pkg/libhelm/options" + "github.com/rs/zerolog/log" + "github.com/segmentio/encoding/json" + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v3/pkg/repo" +) + +var ( + errRequiredSearchOptions = errors.New("repo is required") + errInvalidRepoURL = errors.New("the request failed since either the Helm repository was not found or the index.yaml is not valid") +) + +type RepoIndex struct { + APIVersion string `json:"apiVersion"` + Entries map[string][]ChartInfo `json:"entries"` + Generated string `json:"generated"` +} + +// 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 + if err := validateSearchRepoOptions(searchRepoOpts); err != nil { + log.Error(). + Str("context", "HelmClient"). + Str("repo", searchRepoOpts.Repo). + Err(err). + Msg("Missing required search repo options") + return nil, err + } + + log.Debug(). + Str("context", "HelmClient"). + Str("repo", searchRepoOpts.Repo). + Msg("Searching repository") + + // Parse and validate the repository URL + repoURL, err := parseRepoURL(searchRepoOpts.Repo) + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Str("repo", searchRepoOpts.Repo). + Err(err). + Msg("Invalid repository URL") + return nil, err + } + + // Set up Helm CLI environment + repoSettings := cli.New() + + // Ensure all required Helm directories exist + if err := ensureHelmDirectoriesExist(repoSettings); err != nil { + log.Error(). + Str("context", "HelmClient"). + Err(err). + Msg("Failed to ensure Helm directories exist") + return nil, errors.Wrap(err, "failed to ensure Helm directories exist") + } + + // Download the index file and update repository configuration + indexPath, err := downloadRepoIndex(repoURL.String(), repoSettings, searchRepoOpts.Repo) + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Str("repo_url", repoURL.String()). + Err(err). + Msg("Failed to download repository index") + return nil, err + } + + // Load and parse the index file + log.Debug(). + Str("context", "HelmClient"). + Str("index_path", indexPath). + Msg("Loading index file") + + indexFile, err := loadIndexFile(indexPath) + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Str("index_path", indexPath). + Err(err). + Msg("Failed to load index file") + 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") + } + + log.Debug(). + Str("context", "HelmClient"). + Str("repo", searchRepoOpts.Repo). + Int("entries_count", len(indexFile.Entries)). + Msg("Successfully searched repository") + + return json.Marshal(result) +} + +// validateSearchRepoOptions validates the required search repository options. +func validateSearchRepoOptions(opts options.SearchRepoOptions) error { + if opts.Repo == "" { + return errRequiredSearchOptions + } + return nil +} + +// parseRepoURL parses and validates the repository URL. +func parseRepoURL(repoURL string) (*url.URL, error) { + parsedURL, err := url.ParseRequestURI(repoURL) + if err != nil { + return nil, errors.Wrap(err, "invalid helm chart URL: "+repoURL) + } + return parsedURL, nil +} + +// downloadRepoIndex downloads the index.yaml file from the repository and updates +// the repository configuration. +func downloadRepoIndex(repoURLString string, repoSettings *cli.EnvSettings, repoName string) (string, error) { + log.Debug(). + Str("context", "helm_sdk_repo_index"). + Str("repo_url", repoURLString). + Str("repo_name", repoName). + Msg("Creating chart repository object") + + // Create chart repository object + rep, err := repo.NewChartRepository( + &repo.Entry{ + URL: repoURLString, + }, + getter.All(repoSettings), + ) + if err != nil { + log.Error(). + Str("context", "helm_sdk_repo_index"). + Str("repo_url", repoURLString). + Err(err). + Msg("Failed to create chart repository object") + return "", errInvalidRepoURL + } + + // Load repository configuration file + f, err := repo.LoadFile(repoSettings.RepositoryConfig) + if err != nil { + log.Error(). + Str("context", "helm_sdk_repo_index"). + Str("repo_config", repoSettings.RepositoryConfig). + Err(err). + Msg("Failed to load repo config") + return "", errors.Wrap(err, "failed to load repo config") + } + + // Download the index file + log.Debug(). + Str("context", "helm_sdk_repo_index"). + Str("repo_url", repoURLString). + Msg("Downloading index file") + + indexPath, err := rep.DownloadIndexFile() + if err != nil { + log.Error(). + Str("context", "helm_sdk_repo_index"). + Str("repo_url", repoURLString). + Err(err). + Msg("Failed to download index file") + return "", errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", repoURLString) + } + + // Update repository configuration + c := repo.Entry{ + Name: repoName, + URL: repoURLString, + } + f.Update(&c) + + // Write updated configuration + repoFile := repoSettings.RepositoryConfig + if err := f.WriteFile(repoFile, 0644); err != nil { + log.Error(). + Str("context", "helm_sdk_repo_index"). + Str("repo_file", repoSettings.RepositoryConfig). + Err(err). + Msg("Failed to write repository configuration") + return "", errors.Wrap(err, "failed to write repository configuration") + } + + log.Debug(). + Str("context", "helm_sdk_repo_index"). + Str("index_path", indexPath). + Msg("Successfully downloaded index file") + + return indexPath, nil +} + +// loadIndexFile loads the index file from the given path. +func loadIndexFile(indexPath string) (*repo.IndexFile, error) { + indexFile, err := repo.LoadIndexFile(indexPath) + if err != nil { + return nil, errors.Wrapf(err, "failed to load downloaded index file: %s", indexPath) + } + return indexFile, nil +} + +// convertIndexToResponse converts the Helm index file to our response format. +func convertIndexToResponse(indexFile *repo.IndexFile) (RepoIndex, error) { + result := RepoIndex{ + APIVersion: indexFile.APIVersion, + Entries: make(map[string][]ChartInfo), + Generated: indexFile.Generated.String(), + } + + // Convert Helm SDK types to our response types + for name, charts := range indexFile.Entries { + result.Entries[name] = convertChartsToChartInfo(charts) + } + + return result, nil +} + +// convertChartsToChartInfo converts Helm chart entries to ChartInfo objects. +func convertChartsToChartInfo(charts []*repo.ChartVersion) []ChartInfo { + chartInfos := make([]ChartInfo, len(charts)) + for i, chart := range charts { + chartInfos[i] = ChartInfo{ + Name: chart.Name, + Version: chart.Version, + AppVersion: chart.AppVersion, + Description: chart.Description, + Deprecated: chart.Deprecated, + Created: chart.Created.String(), + Digest: chart.Digest, + Home: chart.Home, + Sources: chart.Sources, + URLs: chart.URLs, + Icon: chart.Icon, + Annotations: chart.Annotations, + } + } + return chartInfos +} + +// ChartInfo represents a Helm chart in the repository index +type ChartInfo struct { + Name string `json:"name"` + Version string `json:"version"` + AppVersion string `json:"appVersion"` + Description string `json:"description"` + Deprecated bool `json:"deprecated"` + Created string `json:"created"` + Digest string `json:"digest"` + Home string `json:"home"` + Sources []string `json:"sources"` + URLs []string `json:"urls"` + Icon string `json:"icon,omitempty"` + Annotations any `json:"annotations,omitempty"` +} + +// ensureHelmDirectoriesExist checks and creates required Helm directories if they don't exist +func ensureHelmDirectoriesExist(settings *cli.EnvSettings) error { + log.Debug(). + Str("context", "helm_sdk_dirs"). + Msg("Ensuring Helm directories exist") + + // List of directories to ensure exist + directories := []string{ + filepath.Dir(settings.RepositoryConfig), // Repository config directory + settings.RepositoryCache, // Repository cache directory + filepath.Dir(settings.RegistryConfig), // Registry config directory + settings.PluginsDirectory, // Plugins directory + } + + // Create each directory if it doesn't exist + for _, dir := range directories { + if dir == "" { + continue // Skip empty paths + } + + if _, err := os.Stat(dir); os.IsNotExist(err) { + if err := os.MkdirAll(dir, 0700); err != nil { + log.Error(). + Str("context", "helm_sdk_dirs"). + Str("directory", dir). + Err(err). + Msg("Failed to create directory") + return errors.Wrapf(err, "failed to create directory: %s", dir) + } + } + } + + // Ensure registry config file exists + if settings.RegistryConfig != "" { + if _, err := os.Stat(settings.RegistryConfig); os.IsNotExist(err) { + // Create the directory if it doesn't exist + dir := filepath.Dir(settings.RegistryConfig) + if err := os.MkdirAll(dir, 0700); err != nil { + log.Error(). + Str("context", "helm_sdk_dirs"). + Str("directory", dir). + Err(err). + Msg("Failed to create directory") + return errors.Wrapf(err, "failed to create directory: %s", dir) + } + + // Create an empty registry config file + if _, err := os.Create(settings.RegistryConfig); err != nil { + log.Error(). + Str("context", "helm_sdk_dirs"). + Str("file", settings.RegistryConfig). + Err(err). + Msg("Failed to create registry config file") + return errors.Wrapf(err, "failed to create registry config file: %s", settings.RegistryConfig) + } + } + } + + // Ensure repository config file exists + if settings.RepositoryConfig != "" { + if _, err := os.Stat(settings.RepositoryConfig); os.IsNotExist(err) { + // Create an empty repository config file with default yaml structure + f := repo.NewFile() + if err := f.WriteFile(settings.RepositoryConfig, 0644); err != nil { + log.Error(). + Str("context", "helm_sdk_dirs"). + Str("file", settings.RepositoryConfig). + Err(err). + Msg("Failed to create repository config file") + return errors.Wrapf(err, "failed to create repository config file: %s", settings.RepositoryConfig) + } + } + } + + log.Debug(). + Str("context", "helm_sdk_dirs"). + Msg("Successfully ensured all Helm directories exist") + + return nil +} diff --git a/pkg/libhelm/sdk/search_repo_test.go b/pkg/libhelm/sdk/search_repo_test.go new file mode 100644 index 000000000..d4cfd9b46 --- /dev/null +++ b/pkg/libhelm/sdk/search_repo_test.go @@ -0,0 +1,84 @@ +package sdk + +import ( + "encoding/json" + "testing" + + "github.com/portainer/portainer/pkg/libhelm/options" + "github.com/stretchr/testify/assert" +) + +type testCase struct { + name string + url string + invalid bool +} + +var tests = []testCase{ + {"not a helm repo", "https://portainer.io", true}, + {"ingress helm repo", "https://kubernetes.github.io/ingress-nginx", false}, + {"portainer helm repo", "https://portainer.github.io/k8s/", false}, + {"elastic helm repo with trailing slash", "https://helm.elastic.co/", false}, + {"lensesio helm repo without trailing slash", "https://lensesio.github.io/kafka-helm-charts", false}, +} + +func Test_SearchRepo(t *testing.T) { + is := assert.New(t) + + // Create a new SDK package manager + hspm := NewHelmSDKPackageManager() + + for _, test := range tests { + func(tc testCase) { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + response, err := hspm.SearchRepo(options.SearchRepoOptions{Repo: tc.url}) + if tc.invalid { + is.Errorf(err, "error expected: %s", tc.url) + } else { + is.NoError(err, "no error expected: %s", tc.url) + } + + if err == nil { + is.NotEmpty(response, "response expected") + } + }) + }(test) + } + + t.Run("search repo with keyword", func(t *testing.T) { + // Search for charts with keyword + searchOpts := options.SearchRepoOptions{ + Repo: "https://kubernetes.github.io/ingress-nginx", + } + responseBytes, err := hspm.SearchRepo(searchOpts) + + // The function should not fail by design, even when not running in a k8s environment + is.NoError(err, "should not return error when not in k8s environment") + is.NotNil(responseBytes, "should return non-nil response") + is.NotEmpty(responseBytes, "should return non-empty response") + + // Parse the ext response + var repoIndex RepoIndex + err = json.Unmarshal(responseBytes, &repoIndex) + is.NoError(err, "should parse JSON response without error") + is.NotEmpty(repoIndex, "should have at least one chart") + + // Verify charts structure apiVersion, entries, generated + is.Equal("v1", repoIndex.APIVersion, "apiVersion should be v1") + is.NotEmpty(repoIndex.Entries, "entries should not be empty") + is.NotEmpty(repoIndex.Generated, "generated should not be empty") + + // there should be at least one chart + is.Greater(len(repoIndex.Entries), 0, "should have at least one chart") + }) + + t.Run("search repo with empty repo URL", func(t *testing.T) { + // Search with empty repo URL + searchOpts := options.SearchRepoOptions{ + Repo: "", + } + _, err := hspm.SearchRepo(searchOpts) + is.Error(err, "should return error when repo URL is empty") + }) +} diff --git a/pkg/libhelm/sdk/show.go b/pkg/libhelm/sdk/show.go new file mode 100644 index 000000000..aa674a8c0 --- /dev/null +++ b/pkg/libhelm/sdk/show.go @@ -0,0 +1,131 @@ +package sdk + +import ( + "fmt" + "os" + + "github.com/pkg/errors" + "github.com/portainer/portainer/pkg/libhelm/options" + "github.com/rs/zerolog/log" + "helm.sh/helm/v3/pkg/action" +) + +var errRequiredShowOptions = errors.New("chart, repo and output format are required") + +// Show implements the HelmPackageManager interface by using the Helm SDK to show chart information. +// It supports showing chart values, readme, and chart details based on the provided ShowOptions. +func (hspm *HelmSDKPackageManager) Show(showOpts options.ShowOptions) ([]byte, error) { + if showOpts.Chart == "" || showOpts.Repo == "" || showOpts.OutputFormat == "" { + log.Error(). + Str("context", "HelmClient"). + Str("chart", showOpts.Chart). + Str("repo", showOpts.Repo). + Str("output_format", string(showOpts.OutputFormat)). + Msg("Missing required show options") + return nil, errRequiredShowOptions + } + + log.Debug(). + Str("context", "HelmClient"). + Str("chart", showOpts.Chart). + Str("repo", showOpts.Repo). + Str("output_format", string(showOpts.OutputFormat)). + Msg("Showing chart information") + + // Initialize action configuration + actionConfig := new(action.Configuration) + if err := actionConfig.Init(nil, "", "", func(format string, v ...interface{}) {}); err != nil { + log.Error(). + Str("context", "HelmClient"). + Err(err). + Msg("Failed to initialize helm configuration") + return nil, fmt.Errorf("failed to initialize helm configuration: %w", err) + } + + // Create temporary directory for chart download + tempDir, err := os.MkdirTemp("", "helm-show-*") + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Err(err). + Msg("Failed to create temp directory") + return nil, fmt.Errorf("failed to create temp directory: %w", err) + } + defer os.RemoveAll(tempDir) + + // Create showClient action + showClient, err := initShowClient(actionConfig, showOpts) + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Err(err). + Msg("Failed to initialize helm show client") + return nil, fmt.Errorf("failed to initialize helm show client: %w", err) + } + + // Locate and load the chart + log.Debug(). + Str("context", "HelmClient"). + Str("chart", showOpts.Chart). + Str("repo", showOpts.Repo). + Msg("Locating chart") + + chartPath, err := showClient.ChartPathOptions.LocateChart(showOpts.Chart, hspm.settings) + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Str("chart", showOpts.Chart). + Str("repo", showOpts.Repo). + Err(err). + Msg("Failed to locate chart") + return nil, fmt.Errorf("failed to locate chart: %w", err) + } + + // Get the output based on the requested format + output, err := showClient.Run(chartPath) + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Str("chart_path", chartPath). + Str("output_format", string(showOpts.OutputFormat)). + Err(err). + Msg("Failed to show chart info") + return nil, fmt.Errorf("failed to show chart info: %w", err) + } + + log.Debug(). + Str("context", "HelmClient"). + Str("chart", showOpts.Chart). + Int("output_size", len(output)). + Msg("Successfully retrieved chart information") + + return []byte(output), nil +} + +// initShowClient initializes the show client with the given options +// and return the show client. +func initShowClient(actionConfig *action.Configuration, showOpts options.ShowOptions) (*action.Show, error) { + showClient := action.NewShowWithConfig(action.ShowAll, actionConfig) + showClient.ChartPathOptions.RepoURL = showOpts.Repo + showClient.ChartPathOptions.Version = "" // Latest version + + // Set output type based on ShowOptions + switch showOpts.OutputFormat { + case options.ShowAll: + showClient.OutputFormat = action.ShowAll + case options.ShowChart: + showClient.OutputFormat = action.ShowChart + case options.ShowValues: + showClient.OutputFormat = action.ShowValues + case options.ShowReadme: + showClient.OutputFormat = action.ShowReadme + default: + log.Error(). + Str("context", "HelmClient"). + Str("output_format", string(showOpts.OutputFormat)). + Msg("Unsupported output format") + return nil, fmt.Errorf("unsupported output format: %s", showOpts.OutputFormat) + } + + return showClient, nil +} diff --git a/pkg/libhelm/sdk/show_test.go b/pkg/libhelm/sdk/show_test.go new file mode 100644 index 000000000..124aac02d --- /dev/null +++ b/pkg/libhelm/sdk/show_test.go @@ -0,0 +1,102 @@ +package sdk + +import ( + "testing" + + "github.com/portainer/portainer/pkg/libhelm/options" + "github.com/portainer/portainer/pkg/libhelm/test" + "github.com/stretchr/testify/assert" +) + +func Test_Show(t *testing.T) { + test.EnsureIntegrationTest(t) + is := assert.New(t) + + // Create a new SDK package manager + hspm := NewHelmSDKPackageManager() + + // install the ingress-nginx chart to test the show command + installOpts := options.InstallOptions{ + Name: "ingress-nginx", + Chart: "ingress-nginx", + Repo: "https://kubernetes.github.io/ingress-nginx", + } + release, err := hspm.Install(installOpts) + if release != nil || err != nil { + defer hspm.Uninstall(options.UninstallOptions{ + Name: "ingress-nginx", + }) + } + + t.Run("show requires chart, repo and output format", func(t *testing.T) { + showOpts := options.ShowOptions{ + Chart: "", + Repo: "", + OutputFormat: "", + } + _, err := hspm.Show(showOpts) + is.Error(err, "should return error when required options are missing") + is.Contains(err.Error(), "chart, repo and output format are required", "error message should indicate required options") + }) + + t.Run("show chart values", func(t *testing.T) { + showOpts := options.ShowOptions{ + Chart: "ingress-nginx", + Repo: "https://kubernetes.github.io/ingress-nginx", + OutputFormat: options.ShowValues, + } + values, err := hspm.Show(showOpts) + + is.NoError(err, "should not return error when not in k8s environment") + is.NotEmpty(values, "should return non-empty values") + }) + + t.Run("show chart readme", func(t *testing.T) { + showOpts := options.ShowOptions{ + Chart: "ingress-nginx", + Repo: "https://kubernetes.github.io/ingress-nginx", + OutputFormat: options.ShowReadme, + } + readme, err := hspm.Show(showOpts) + + is.NoError(err, "should not return error when not in k8s environment") + is.NotEmpty(readme, "should return non-empty readme") + }) + + t.Run("show chart definition", func(t *testing.T) { + showOpts := options.ShowOptions{ + Chart: "ingress-nginx", + Repo: "https://kubernetes.github.io/ingress-nginx", + OutputFormat: options.ShowChart, + } + chart, err := hspm.Show(showOpts) + + is.NoError(err, "should not return error when not in k8s environment") + is.NotNil(chart, "should return non-nil chart definition") + }) + + t.Run("show all chart info", func(t *testing.T) { + showOpts := options.ShowOptions{ + Chart: "ingress-nginx", + Repo: "https://kubernetes.github.io/ingress-nginx", + OutputFormat: options.ShowAll, + } + info, err := hspm.Show(showOpts) + + is.NoError(err, "should not return error when not in k8s environment") + is.NotEmpty(info, "should return non-empty chart info") + }) + + t.Run("show with invalid output format", func(t *testing.T) { + // Show with invalid output format + showOpts := options.ShowOptions{ + Chart: "ingress-nginx", + Repo: "https://kubernetes.github.io/ingress-nginx", + OutputFormat: "invalid", + } + _, err := hspm.Show(showOpts) + + is.Error(err, "should return error with invalid output format") + is.Contains(err.Error(), "unsupported output format", "error message should indicate invalid output format") + }) +} diff --git a/pkg/libhelm/sdk/uninstall.go b/pkg/libhelm/sdk/uninstall.go new file mode 100644 index 000000000..5459b9e84 --- /dev/null +++ b/pkg/libhelm/sdk/uninstall.go @@ -0,0 +1,70 @@ +package sdk + +import ( + "github.com/pkg/errors" + "github.com/portainer/portainer/pkg/libhelm/options" + "github.com/rs/zerolog/log" + "helm.sh/helm/v3/pkg/action" +) + +// Uninstall implements the HelmPackageManager interface by using the Helm SDK to uninstall a release. +func (hspm *HelmSDKPackageManager) Uninstall(uninstallOpts options.UninstallOptions) error { + if uninstallOpts.Name == "" { + log.Error(). + Str("context", "HelmClient"). + Msg("Release name is required") + return errors.New("release name is required") + } + + log.Debug(). + Str("context", "HelmClient"). + Str("release", uninstallOpts.Name). + Str("namespace", uninstallOpts.Namespace). + Msg("Uninstalling Helm release") + + // Initialize action configuration with kubernetes config + actionConfig := new(action.Configuration) + err := hspm.initActionConfig(actionConfig, uninstallOpts.Namespace, uninstallOpts.KubernetesClusterAccess) + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Str("release", uninstallOpts.Name). + Str("namespace", uninstallOpts.Namespace). + Err(err). + Msg("Failed to initialize helm configuration") + return errors.Wrap(err, "failed to initialize helm configuration") + } + + // Create uninstallClient action + uninstallClient := action.NewUninstall(actionConfig) + // 'foreground' means the parent object remains in a "terminating" state until all of its children are deleted. This ensures that all dependent resources are completely removed before finalizing the deletion of the parent resource. + uninstallClient.DeletionPropagation = "foreground" // "background" or "orphan" + + // Run the uninstallation + log.Info(). + Str("context", "HelmClient"). + Str("release", uninstallOpts.Name). + Str("namespace", uninstallOpts.Namespace). + Msg("Running uninstallation") + + result, err := uninstallClient.Run(uninstallOpts.Name) + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Str("release", uninstallOpts.Name). + Str("namespace", uninstallOpts.Namespace). + Err(err). + Msg("Failed to uninstall helm release") + return errors.Wrap(err, "failed to uninstall helm release") + } + + if result != nil { + log.Debug(). + Str("context", "HelmClient"). + Str("release", uninstallOpts.Name). + Str("release_info", result.Release.Info.Description). + Msg("Uninstall result details") + } + + return nil +} diff --git a/pkg/libhelm/sdk/uninstall_test.go b/pkg/libhelm/sdk/uninstall_test.go new file mode 100644 index 000000000..28ef1cb21 --- /dev/null +++ b/pkg/libhelm/sdk/uninstall_test.go @@ -0,0 +1,65 @@ +package sdk + +import ( + "testing" + + "github.com/portainer/portainer/pkg/libhelm/options" + "github.com/portainer/portainer/pkg/libhelm/test" + "github.com/stretchr/testify/assert" +) + +func Test_Uninstall(t *testing.T) { + test.EnsureIntegrationTest(t) + is := assert.New(t) + + // Create a new SDK package manager + hspm := NewHelmSDKPackageManager() + + t.Run("uninstall requires a release name", func(t *testing.T) { + // Try to uninstall without a release name + uninstallOpts := options.UninstallOptions{ + Name: "", + } + err := hspm.Uninstall(uninstallOpts) + is.Error(err, "should return error when release name is empty") + is.Contains(err.Error(), "release name is required", "error message should indicate release name is required") + }) + + t.Run("uninstall a non-existent release", func(t *testing.T) { + // Try to uninstall a release that doesn't exist + uninstallOpts := options.UninstallOptions{ + Name: "non-existent-release", + } + err := hspm.Uninstall(uninstallOpts) + + // The function should not fail by design, even when not running in a k8s environment + // However, it should return an error for a non-existent release + is.Error(err, "should return error when release doesn't exist") + is.Contains(err.Error(), "not found", "error message should indicate release not found") + }) + + // This test is commented out as it requires a real release to be installed first + t.Run("successfully uninstall an existing release", func(t *testing.T) { + // First install a release + installOpts := options.InstallOptions{ + Name: "test-uninstall", + Chart: "nginx", + Repo: "https://kubernetes.github.io/ingress-nginx", + } + + // Install the release + _, err := hspm.Install(installOpts) + if err != nil { + t.Logf("Error installing release: %v", err) + t.Skip("Skipping uninstall test because install failed") + return + } + + // Now uninstall it + uninstallOpts := options.UninstallOptions{ + Name: "test-uninstall", + } + err = hspm.Uninstall(uninstallOpts) + is.NoError(err, "should successfully uninstall release") + }) +} diff --git a/pkg/libhelm/sdk/values.go b/pkg/libhelm/sdk/values.go new file mode 100644 index 000000000..37ad46775 --- /dev/null +++ b/pkg/libhelm/sdk/values.go @@ -0,0 +1,42 @@ +package sdk + +import ( + "os" + + "github.com/pkg/errors" + "github.com/rs/zerolog/log" +) + +// GetHelmValuesFromFile reads the values file and parses it into a map[string]any +// and returns the map. +func (hspm *HelmSDKPackageManager) GetHelmValuesFromFile(valuesFile string) (map[string]any, error) { + var vals map[string]any + if valuesFile != "" { + log.Debug(). + Str("context", "HelmClient"). + Str("values_file", valuesFile). + Msg("Reading values file") + + valuesData, err := os.ReadFile(valuesFile) + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Str("values_file", valuesFile). + Err(err). + Msg("Failed to read values file") + return nil, errors.Wrap(err, "failed to read values file") + } + + vals, err = hspm.parseValues(valuesData) + if err != nil { + log.Error(). + Str("context", "HelmClient"). + Str("values_file", valuesFile). + Err(err). + Msg("Failed to parse values file") + return nil, errors.Wrap(err, "failed to parse values file") + } + } + + return vals, nil +} diff --git a/pkg/libhelm/libhelmtest/integration.go b/pkg/libhelm/test/integration.go similarity index 93% rename from pkg/libhelm/libhelmtest/integration.go rename to pkg/libhelm/test/integration.go index f8febab65..0939f460e 100644 --- a/pkg/libhelm/libhelmtest/integration.go +++ b/pkg/libhelm/test/integration.go @@ -1,4 +1,4 @@ -package libhelmtest +package test import ( "os" diff --git a/pkg/libhelm/binary/test/mock.go b/pkg/libhelm/test/mock.go similarity index 95% rename from pkg/libhelm/binary/test/mock.go rename to pkg/libhelm/test/mock.go index e019ee248..0b7712aab 100644 --- a/pkg/libhelm/binary/test/mock.go +++ b/pkg/libhelm/test/mock.go @@ -3,9 +3,9 @@ package test import ( "strings" - "github.com/portainer/portainer/pkg/libhelm" "github.com/portainer/portainer/pkg/libhelm/options" "github.com/portainer/portainer/pkg/libhelm/release" + "github.com/portainer/portainer/pkg/libhelm/types" "github.com/pkg/errors" "github.com/segmentio/encoding/json" @@ -31,8 +31,8 @@ const ( // Do not use this package for concurrent tests. type helmMockPackageManager struct{} -// NewMockHelmBinaryPackageManager initializes a new HelmPackageManager service (a mock instance) -func NewMockHelmBinaryPackageManager(binaryPath string) libhelm.HelmPackageManager { +// NewMockHelmPackageManager initializes a new HelmPackageManager service (a mock instance) +func NewMockHelmPackageManager() types.HelmPackageManager { return &helmMockPackageManager{} } diff --git a/pkg/libhelm/helm.go b/pkg/libhelm/types/types.go similarity index 72% rename from pkg/libhelm/helm.go rename to pkg/libhelm/types/types.go index 3f718e64f..91b777270 100644 --- a/pkg/libhelm/helm.go +++ b/pkg/libhelm/types/types.go @@ -1,16 +1,26 @@ -package libhelm +package types import ( "github.com/portainer/portainer/pkg/libhelm/options" "github.com/portainer/portainer/pkg/libhelm/release" + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/repo" ) // HelmPackageManager represents a service that interfaces with Helm type HelmPackageManager interface { Show(showOpts options.ShowOptions) ([]byte, error) SearchRepo(searchRepoOpts options.SearchRepoOptions) ([]byte, error) - Get(getOpts options.GetOptions) ([]byte, error) List(listOpts options.ListOptions) ([]release.ReleaseElement, error) Install(installOpts options.InstallOptions) (*release.Release, error) Uninstall(uninstallOpts options.UninstallOptions) error } + +type Repository interface { + Charts() (repo.ChartVersions, error) +} + +type HelmRepo struct { + Settings *cli.EnvSettings + Orig *repo.Entry +} diff --git a/pkg/libhelm/validate_repo.go b/pkg/libhelm/validate_repo.go index 872dad165..1b4de5dc5 100644 --- a/pkg/libhelm/validate_repo.go +++ b/pkg/libhelm/validate_repo.go @@ -15,6 +15,10 @@ func ValidateHelmRepositoryURL(repoUrl string, client *http.Client) error { return errors.New("URL is required") } + if strings.HasPrefix(repoUrl, "oci://") { + return errors.New("OCI repositories are not supported yet") + } + url, err := url.ParseRequestURI(repoUrl) if err != nil { return fmt.Errorf("invalid helm repository URL '%s': %w", repoUrl, err) diff --git a/pkg/libhelm/validate_repo_test.go b/pkg/libhelm/validate_repo_test.go index dce1257d0..55d9d4736 100644 --- a/pkg/libhelm/validate_repo_test.go +++ b/pkg/libhelm/validate_repo_test.go @@ -3,12 +3,12 @@ package libhelm import ( "testing" - "github.com/portainer/portainer/pkg/libhelm/libhelmtest" + "github.com/portainer/portainer/pkg/libhelm/test" "github.com/stretchr/testify/assert" ) func Test_ValidateHelmRepositoryURL(t *testing.T) { - libhelmtest.EnsureIntegrationTest(t) + test.EnsureIntegrationTest(t) is := assert.New(t) type testCase struct { @@ -26,7 +26,7 @@ func Test_ValidateHelmRepositoryURL(t *testing.T) { {"not helm repo", "http://google.com", true}, {"not valid repo with trailing slash", "http://google.com/", true}, {"not valid repo with trailing slashes", "http://google.com////", true}, - {"ingress helm repo", "https://kubernetes.github.io/ingress-nginx/", false}, + {"bitnami helm repo", "https://charts.bitnami.com/bitnami/", false}, {"gitlap helm repo", "https://charts.gitlab.io/", false}, {"portainer helm repo", "https://portainer.github.io/k8s/", false}, {"elastic helm repo", "https://helm.elastic.co/", false},