From ed744299fc6a62bed33d334ae053bca60cdaf7d7 Mon Sep 17 00:00:00 2001 From: YanamiNeko <63277113+YanamiNeko@users.noreply.github.com> Date: Sun, 26 Jul 2026 18:14:27 +0800 Subject: [PATCH] feat: support installing Helm charts from all sources in app store The app store could only deploy a single image extracted from a Sealos template, so Helm charts listed there could never actually be installed. Add first-class Helm support: - browse charts via ArtifactHub server-side search, any HTTP(S) repository index.yaml, or a direct oci:// reference - prefill the chart's real values.yaml instead of an empty object - run install/upgrade/uninstall as background tasks so long installs no longer block the HTTP request, and stream Helm logs to the UI - install atomically so failed installs roll back instead of leaving partial resources behind - manage existing releases from a new Helm Releases page - require admin for every Helm write endpoint and refuse to create the target namespace implicitly --- controllers/app.go | 11 +- controllers/app_artifacthub.go | 94 ++++++ controllers/apptemplate.go | 117 ++++++- controllers/helm.go | 246 ++++++++++++++ controllers/helm_task.go | 188 +++++++++++ go.mod | 85 +++-- go.sum | 215 ++++++++++-- object/helm.go | 587 +++++++++++++++++++++++++++++++++ object/helm_repo.go | 327 ++++++++++++++++++ object/helm_test.go | 210 ++++++++++++ routers/router.go | 9 + web/src/AppStorePage.js | 417 ++++++++++++++++++----- web/src/DeployAppModal.js | 328 +++++++++++++++--- web/src/HelmReleaseListPage.js | 306 +++++++++++++++++ web/src/ManagementPage.js | 4 + web/src/backend/AppBackend.js | 87 +++++ web/src/locales/en/data.json | 71 +++- web/src/locales/zh/data.json | 71 +++- 18 files changed, 3179 insertions(+), 194 deletions(-) create mode 100644 controllers/app_artifacthub.go create mode 100644 controllers/helm.go create mode 100644 controllers/helm_task.go create mode 100644 object/helm.go create mode 100644 object/helm_repo.go create mode 100644 object/helm_test.go create mode 100644 web/src/HelmReleaseListPage.js diff --git a/controllers/app.go b/controllers/app.go index 3773c8ef..da244905 100644 --- a/controllers/app.go +++ b/controllers/app.go @@ -27,13 +27,17 @@ type deployAppRequest struct { } type deployAppResult struct { - Deployment deploymentSummary `json:"deployment"` - Service *serviceSummary `json:"service,omitempty"` + Deployment *deploymentSummary `json:"deployment,omitempty"` + Service *serviceSummary `json:"service,omitempty"` } // DeployApp creates a Deployment and a matching ClusterIP/NodePort Service in one call. +// Helm charts are installed through /api/install-helm-chart instead. // @router /api/deploy-app [post] func (c *ApiController) DeployApp() { + if c.RequireAdmin() { + return + } cfg := getAdminRestConfig() if cfg == nil { c.ResponseError("apiserver not ready") @@ -82,7 +86,8 @@ func (c *ApiController) DeployApp() { return } - result := deployAppResult{Deployment: toDeploymentSummary(*createdDepl)} + deplSummary := toDeploymentSummary(*createdDepl) + result := deployAppResult{Deployment: &deplSummary} if len(req.Ports) > 0 { svcPorts := make([]portRequest, 0, len(req.Ports)) diff --git a/controllers/app_artifacthub.go b/controllers/app_artifacthub.go new file mode 100644 index 00000000..0ee8a741 --- /dev/null +++ b/controllers/app_artifacthub.go @@ -0,0 +1,94 @@ +package controllers + +import ( + "fmt" + "sort" + "strings" + + "github.com/casosorg/casos/object" +) + +// fetchArtifactHubTemplates returns a curated first page of popular Helm charts +// for the app store landing view. Full discovery goes through +// /api/search-helm-charts, which queries ArtifactHub server-side. +func fetchArtifactHubTemplates() ([]AppTemplate, error) { + result, err := object.SearchArtifactHubCharts("", 1, 48) + if err != nil { + return nil, err + } + + templates := make([]AppTemplate, 0, len(result.Items)) + seen := map[string]bool{} + for _, item := range result.Items { + key := item.RepoURL + "/" + item.ChartName + if seen[key] { + continue + } + seen[key] = true + templates = append(templates, toAppTemplate(item)) + } + + sort.SliceStable(templates, func(i, j int) bool { + return strings.ToLower(templates[i].Title) < strings.ToLower(templates[j].Title) + }) + return templates, nil +} + +func toAppTemplate(item object.HelmChartListItem) AppTemplate { + categories := append([]string{"Helm"}, item.Categories...) + if item.Official { + categories = append(categories, "Official") + } else if item.Verified { + categories = append(categories, "Verified") + } + + return AppTemplate{ + Name: safeAppTemplateName(item.ChartName), + Title: item.Title, + Description: item.Description, + Icon: item.Icon, + Categories: categories, + Source: item.Source, + PackageType: item.PackageType, + RepoName: item.RepoName, + RepoURL: item.RepoURL, + ChartName: item.ChartName, + Version: item.Version, + } +} + +func safeAppTemplateName(name string) string { + name = strings.ToLower(strings.TrimSpace(name)) + var b strings.Builder + lastDash := false + for _, r := range name { + if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') { + b.WriteRune(r) + lastDash = false + continue + } + if !lastDash { + b.WriteByte('-') + lastDash = true + } + } + result := strings.Trim(b.String(), "-") + if result == "" { + return "app" + } + return result +} + +func helmChartRefFromRequest(repoURL, chartName, version, username, password string) (object.HelmChartRef, error) { + ref := object.HelmChartRef{ + RepoURL: strings.TrimSpace(repoURL), + Chart: strings.TrimSpace(chartName), + Version: strings.TrimSpace(version), + Username: username, + Password: password, + } + if _, _, err := ref.Normalize(); err != nil { + return object.HelmChartRef{}, fmt.Errorf("invalid chart reference: %w", err) + } + return ref, nil +} diff --git a/controllers/apptemplate.go b/controllers/apptemplate.go index 1f4a63d8..eb4a1ab7 100644 --- a/controllers/apptemplate.go +++ b/controllers/apptemplate.go @@ -1,6 +1,7 @@ package controllers import ( + "context" "encoding/json" "fmt" "io" @@ -15,9 +16,17 @@ import ( ) const ( - githubTreeURL = "https://api.github.com/repos/labring-actions/templates/git/trees/main?recursive=1" - rawContentBase = "https://raw.githubusercontent.com/labring-actions/templates/main/" - templateCacheTTL = time.Hour + githubTreeURL = "https://api.github.com/repos/labring-actions/templates/git/trees/main?recursive=1" + rawContentBase = "https://raw.githubusercontent.com/labring-actions/templates/main/" + artifactHubSearchURL = "https://artifacthub.io/api/v1/packages/search?kind=0&limit=48&offset=0&sort=relevance" + templateCacheTTL = time.Hour +) + +const ( + appSourceSealos = "sealos" + appSourceArtifactHub = "artifacthub" + appPackageSealos = "sealos-template" + appPackageHelm = "helm" ) type TemplateInput struct { @@ -37,6 +46,12 @@ type AppTemplate struct { Image string `json:"image"` Ports []int32 `json:"ports"` Inputs []TemplateInput `json:"inputs"` + Source string `json:"source"` + PackageType string `json:"packageType"` + RepoName string `json:"repoName,omitempty"` + RepoURL string `json:"repoUrl,omitempty"` + ChartName string `json:"chartName,omitempty"` + Version string `json:"version,omitempty"` } var ( @@ -45,7 +60,7 @@ var ( templateCacheMu sync.RWMutex ) -// GetAppTemplates returns the Sealos app template catalog from GitHub. +// GetAppTemplates returns app templates from the configured app store sources. // Results are cached in memory for 1 hour. // @router /api/get-app-templates [get] func (c *ApiController) GetAppTemplates() { @@ -66,19 +81,101 @@ func getCachedTemplates() ([]AppTemplate, error) { } templateCacheMu.RUnlock() - templates, err := fetchAllTemplates() + templates, partial, err := fetchAllTemplateSources() if err != nil { return nil, err } - templateCacheMu.Lock() - templateCache = templates - templateCacheAt = time.Now() - templateCacheMu.Unlock() + if !partial { + templateCacheMu.Lock() + templateCache = templates + templateCacheAt = time.Now() + templateCacheMu.Unlock() + } return templates, nil } +type templateSourceResult struct { + source string + templates []AppTemplate + err error +} + +func fetchAllTemplateSources() ([]AppTemplate, bool, error) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + resultCh := make(chan templateSourceResult, 2) + sources := []struct { + name string + fetch func() ([]AppTemplate, error) + }{ + {name: appSourceSealos, fetch: fetchAllTemplates}, + {name: appSourceArtifactHub, fetch: fetchArtifactHubTemplates}, + } + + for _, source := range sources { + source := source + go func() { + templates, err := source.fetch() + select { + case resultCh <- templateSourceResult{source: source.name, templates: templates, err: err}: + case <-ctx.Done(): + } + }() + } + + hardTimer := time.NewTimer(20 * time.Second) + defer hardTimer.Stop() + softTimer := time.NewTimer(5 * time.Second) + defer softTimer.Stop() + softTimeout := softTimer.C + hardTimeout := hardTimer.C + + sourceTemplates := map[string][]AppTemplate{} + var errs []string + completed := 0 + templateCount := 0 + partial := false + for completed < len(sources) { + select { + case result := <-resultCh: + completed++ + if result.err != nil { + errs = append(errs, fmt.Sprintf("%s: %s", result.source, result.err.Error())) + continue + } + sourceTemplates[result.source] = result.templates + templateCount += len(result.templates) + case <-softTimeout: + if templateCount > 0 { + partial = completed < len(sources) + completed = len(sources) + } else { + softTimeout = nil + } + case <-hardTimeout: + errs = append(errs, "app template source timeout") + partial = completed < len(sources) + completed = len(sources) + } + } + + var templates []AppTemplate + for _, source := range sources { + templates = append(templates, sourceTemplates[source.name]...) + } + + if len(templates) == 0 { + if len(errs) == 0 { + return nil, false, fmt.Errorf("no app templates found") + } + return nil, false, fmt.Errorf("fetch app templates: %s", strings.Join(errs, "; ")) + } + return templates, partial || len(errs) > 0, nil +} + type githubTree struct { Tree []struct { Path string `json:"path"` @@ -279,5 +376,7 @@ func fetchAndParseTemplate(path string) (AppTemplate, error) { Image: image, Ports: ports, Inputs: inputs, + Source: appSourceSealos, + PackageType: appPackageSealos, }, nil } diff --git a/controllers/helm.go b/controllers/helm.go new file mode 100644 index 00000000..1fe40d92 --- /dev/null +++ b/controllers/helm.go @@ -0,0 +1,246 @@ +package controllers + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + + "github.com/casosorg/casos/object" + "k8s.io/client-go/rest" +) + +type helmChartRequest struct { + Namespace string `json:"namespace"` + Release string `json:"release"` + RepoURL string `json:"repoUrl"` + Chart string `json:"chart"` + Version string `json:"version"` + Username string `json:"username"` + Password string `json:"password"` + Values string `json:"values"` +} + +// SearchHelmCharts performs a server-side chart search on ArtifactHub. +// @router /api/search-helm-charts [get] +func (c *ApiController) SearchHelmCharts() { + if c.RequireSignedIn() { + return + } + + page, _ := strconv.Atoi(c.GetString("page")) + pageSize, _ := strconv.Atoi(c.GetString("pageSize")) + result, err := object.SearchArtifactHubCharts(c.GetString("q"), page, pageSize) + if err != nil { + c.ResponseError(err.Error()) + return + } + c.ResponseOk(result) +} + +// GetHelmRepoCharts lists all charts published by an arbitrary Helm repository. +// @router /api/get-helm-repo-charts [get] +func (c *ApiController) GetHelmRepoCharts() { + if c.RequireSignedIn() { + return + } + + items, err := object.ListHelmRepoCharts(c.GetString("repoUrl")) + if err != nil { + c.ResponseError(err.Error()) + return + } + c.ResponseOk(items) +} + +// GetHelmChartInfo returns chart metadata, default values and values schema. +// @router /api/get-helm-chart-info [get] +func (c *ApiController) GetHelmChartInfo() { + if c.RequireSignedIn() { + return + } + + ref, err := helmChartRefFromRequest( + c.GetString("repoUrl"), c.GetString("chart"), c.GetString("version"), + c.GetString("username"), c.GetString("password"), + ) + if err != nil { + c.ResponseError(err.Error()) + return + } + + info, err := object.HelmShowChart(ref) + if err != nil { + c.ResponseError(err.Error()) + return + } + + versions := helmChartVersions(c.GetString("source"), c.GetString("repoName"), ref) + c.ResponseOk(map[string]interface{}{ + "info": info, + "availableVersions": versions, + }) +} + +// helmChartVersions resolves the selectable chart versions, preferring the +// repository index and falling back to ArtifactHub metadata. +func helmChartVersions(source, repoName string, ref object.HelmChartRef) []string { + if ref.IsOCI() { + return nil + } + if versions, err := object.GetHelmRepoChartVersions(ref.RepoURL, ref.Chart); err == nil && len(versions) > 0 { + return versions + } + if source == "artifacthub" && repoName != "" { + if versions, err := object.GetArtifactHubChartVersions(repoName, ref.Chart); err == nil { + return versions + } + } + return nil +} + +// GetHelmReleases lists Helm releases in a namespace (or all namespaces). +// @router /api/get-helm-releases [get] +func (c *ApiController) GetHelmReleases() { + if c.RequireSignedIn() { + return + } + cfg := getAdminRestConfig() + if cfg == nil { + c.ResponseError("apiserver not ready") + return + } + + releases, err := object.HelmListReleases(cfg, c.GetString("namespace")) + if err != nil { + c.ResponseError(err.Error()) + return + } + c.ResponseOk(releases) +} + +// GetHelmTask returns the state of an asynchronous Helm operation. +// @router /api/get-helm-task [get] +func (c *ApiController) GetHelmTask() { + if c.RequireSignedIn() { + return + } + + task := getHelmTask(c.GetString("id")) + if task == nil { + c.ResponseError("helm task not found") + return + } + c.ResponseOk(task) +} + +// InstallHelmChart starts an asynchronous Helm install and returns a task id. +// @router /api/install-helm-chart [post] +func (c *ApiController) InstallHelmChart() { + c.runHelmChartTask("install") +} + +// UpgradeHelmRelease starts an asynchronous Helm upgrade and returns a task id. +// @router /api/upgrade-helm-release [post] +func (c *ApiController) UpgradeHelmRelease() { + c.runHelmChartTask("upgrade") +} + +func (c *ApiController) runHelmChartTask(action string) { + if c.RequireAdmin() { + return + } + cfg := getAdminRestConfig() + if cfg == nil { + c.ResponseError("apiserver not ready") + return + } + + var req helmChartRequest + if err := json.Unmarshal(c.Ctx.Input.RequestBody, &req); err != nil { + c.ResponseError("invalid request body: " + err.Error()) + return + } + + namespace := strings.TrimSpace(req.Namespace) + release := strings.TrimSpace(req.Release) + if err := validateHelmTarget(cfg, namespace, release); err != nil { + c.ResponseError(err.Error()) + return + } + + ref, err := helmChartRefFromRequest(req.RepoURL, req.Chart, req.Version, req.Username, req.Password) + if err != nil { + c.ResponseError(err.Error()) + return + } + + values, err := object.ParseHelmValues(req.Values) + if err != nil { + c.ResponseError(err.Error()) + return + } + + task, err := newHelmTask(action, namespace, release, ref.Chart, ref.Version) + if err != nil { + c.ResponseError(err.Error()) + return + } + + if action == "upgrade" { + go runHelmUpgradeTask(cfg, task, ref, values) + } else { + go runHelmInstallTask(cfg, task, ref, values) + } + c.ResponseOk(map[string]string{"taskId": task.Id, "status": task.Status}) +} + +// UninstallHelmRelease starts an asynchronous Helm uninstall. +// @router /api/uninstall-helm-release [post] +func (c *ApiController) UninstallHelmRelease() { + if c.RequireAdmin() { + return + } + cfg := getAdminRestConfig() + if cfg == nil { + c.ResponseError("apiserver not ready") + return + } + + var req helmChartRequest + if err := json.Unmarshal(c.Ctx.Input.RequestBody, &req); err != nil { + c.ResponseError("invalid request body: " + err.Error()) + return + } + + namespace := strings.TrimSpace(req.Namespace) + release := strings.TrimSpace(req.Release) + if err := validateHelmTarget(cfg, namespace, release); err != nil { + c.ResponseError(err.Error()) + return + } + + task, err := newHelmTask("uninstall", namespace, release, "", "") + if err != nil { + c.ResponseError(err.Error()) + return + } + + go runHelmUninstallTask(cfg, task) + c.ResponseOk(map[string]string{"taskId": task.Id, "status": task.Status}) +} + +// validateHelmTarget verifies the release name and that the target namespace +// already exists. Namespaces are never created implicitly. +func validateHelmTarget(cfg *rest.Config, namespace, release string) error { + if namespace == "" { + return fmt.Errorf("namespace is required") + } + if err := object.ValidateHelmReleaseName(release); err != nil { + return err + } + if _, err := object.GetNamespace(cfg, namespace); err != nil { + return fmt.Errorf("namespace %q is not available: %w", namespace, err) + } + return nil +} diff --git a/controllers/helm_task.go b/controllers/helm_task.go new file mode 100644 index 00000000..fe54a4e3 --- /dev/null +++ b/controllers/helm_task.go @@ -0,0 +1,188 @@ +package controllers + +import ( + "fmt" + "sort" + "sync" + "time" + + "github.com/casosorg/casos/object" + "k8s.io/client-go/rest" +) + +const ( + helmTaskMaxLogLines = 500 + helmTaskMaxEntries = 200 + helmTaskTTL = 2 * time.Hour +) + +// helmTask tracks one asynchronous Helm operation so the UI can poll progress +// instead of holding a long HTTP request open. +type helmTask struct { + Id string `json:"id"` + Action string `json:"action"` // install | upgrade | uninstall + Namespace string `json:"namespace"` + Release string `json:"release"` + Chart string `json:"chart,omitempty"` + Version string `json:"version,omitempty"` + Status string `json:"status"` // pending | running | succeeded | failed + Error string `json:"error,omitempty"` + Logs []string `json:"logs"` + Result *object.HelmReleaseSummary `json:"result,omitempty"` + StartedAt string `json:"startedAt"` + EndedAt string `json:"endedAt,omitempty"` + + startedAtTime time.Time +} + +var ( + helmTaskMu sync.RWMutex + helmTasks = map[string]*helmTask{} + helmTaskSeq uint64 +) + +func helmTaskKey(namespace, release string) string { + return namespace + "/" + release +} + +func newHelmTask(action, namespace, release, chart, version string) (*helmTask, error) { + helmTaskMu.Lock() + defer helmTaskMu.Unlock() + + key := helmTaskKey(namespace, release) + for _, task := range helmTasks { + if helmTaskKey(task.Namespace, task.Release) == key && + (task.Status == "pending" || task.Status == "running") { + return nil, fmt.Errorf("another %s operation is already running for release %q", task.Action, release) + } + } + + pruneHelmTasksLocked() + + helmTaskSeq++ + now := time.Now() + task := &helmTask{ + Id: fmt.Sprintf("helm-%d-%d", now.UnixNano(), helmTaskSeq), + Action: action, + Namespace: namespace, + Release: release, + Chart: chart, + Version: version, + Status: "pending", + Logs: []string{}, + StartedAt: now.UTC().Format("2006-01-02 15:04:05"), + startedAtTime: now, + } + helmTasks[task.Id] = task + return task, nil +} + +// pruneHelmTasksLocked drops finished tasks that are past the TTL, and trims +// the oldest entries when the map grows beyond the cap. Caller holds the lock. +func pruneHelmTasksLocked() { + now := time.Now() + for id, task := range helmTasks { + if task.Status == "pending" || task.Status == "running" { + continue + } + if now.Sub(task.startedAtTime) > helmTaskTTL { + delete(helmTasks, id) + } + } + + if len(helmTasks) < helmTaskMaxEntries { + return + } + finished := make([]*helmTask, 0, len(helmTasks)) + for _, task := range helmTasks { + if task.Status == "succeeded" || task.Status == "failed" { + finished = append(finished, task) + } + } + sort.Slice(finished, func(i, j int) bool { + return finished[i].startedAtTime.Before(finished[j].startedAtTime) + }) + for _, task := range finished { + if len(helmTasks) < helmTaskMaxEntries { + break + } + delete(helmTasks, task.Id) + } +} + +func getHelmTask(id string) *helmTask { + helmTaskMu.RLock() + defer helmTaskMu.RUnlock() + + task, ok := helmTasks[id] + if !ok { + return nil + } + snapshot := *task + snapshot.Logs = append([]string{}, task.Logs...) + return &snapshot +} + +func updateHelmTask(id string, fn func(task *helmTask)) { + helmTaskMu.Lock() + defer helmTaskMu.Unlock() + + if task, ok := helmTasks[id]; ok { + fn(task) + } +} + +func appendHelmTaskLog(id, line string) { + updateHelmTask(id, func(task *helmTask) { + task.Logs = append(task.Logs, line) + if len(task.Logs) > helmTaskMaxLogLines { + task.Logs = task.Logs[len(task.Logs)-helmTaskMaxLogLines:] + } + }) +} + +func helmTaskLogger(id string) object.HelmLogger { + return func(format string, v ...interface{}) { + appendHelmTaskLog(id, fmt.Sprintf(format, v...)) + } +} + +func finishHelmTask(id string, result *object.HelmReleaseSummary, err error) { + updateHelmTask(id, func(task *helmTask) { + task.EndedAt = time.Now().UTC().Format("2006-01-02 15:04:05") + if err != nil { + task.Status = "failed" + task.Error = err.Error() + return + } + task.Status = "succeeded" + task.Result = result + }) +} + +// runHelmInstallTask performs an install in the background. +func runHelmInstallTask(cfg *rest.Config, task *helmTask, ref object.HelmChartRef, values map[string]interface{}) { + updateHelmTask(task.Id, func(t *helmTask) { t.Status = "running" }) + appendHelmTaskLog(task.Id, fmt.Sprintf("installing chart %s into namespace %s", ref.Chart, task.Namespace)) + + result, err := object.HelmInstall(cfg, ref, task.Namespace, task.Release, values, helmTaskLogger(task.Id)) + finishHelmTask(task.Id, result, err) +} + +// runHelmUpgradeTask performs an upgrade in the background. +func runHelmUpgradeTask(cfg *rest.Config, task *helmTask, ref object.HelmChartRef, values map[string]interface{}) { + updateHelmTask(task.Id, func(t *helmTask) { t.Status = "running" }) + appendHelmTaskLog(task.Id, fmt.Sprintf("upgrading release %s in namespace %s", task.Release, task.Namespace)) + + result, err := object.HelmUpgrade(cfg, ref, task.Namespace, task.Release, values, helmTaskLogger(task.Id)) + finishHelmTask(task.Id, result, err) +} + +// runHelmUninstallTask performs an uninstall in the background. +func runHelmUninstallTask(cfg *rest.Config, task *helmTask) { + updateHelmTask(task.Id, func(t *helmTask) { t.Status = "running" }) + appendHelmTaskLog(task.Id, fmt.Sprintf("uninstalling release %s from namespace %s", task.Release, task.Namespace)) + + err := object.HelmUninstall(cfg, task.Namespace, task.Release, helmTaskLogger(task.Id)) + finishHelmTask(task.Id, nil, err) +} diff --git a/go.mod b/go.mod index b7527410..991eecbd 100644 --- a/go.mod +++ b/go.mod @@ -80,9 +80,12 @@ require ( require ( github.com/casbin/casbin/v2 v2.135.0 + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 github.com/spf13/pflag v1.0.10 - golang.org/x/net v0.53.0 + golang.org/x/crypto v0.53.0 + golang.org/x/net v0.55.0 gopkg.in/yaml.v3 v3.0.1 + helm.sh/helm/v3 v3.21.2 k8s.io/api v1.36.1-k3s1 k8s.io/component-base v1.36.1-k3s1 ) @@ -90,21 +93,31 @@ require ( require ( cel.dev/expr v0.25.1 // indirect cyphar.com/go-pathrs v0.2.2 // indirect + dario.cat/mergo v1.0.1 // indirect filippo.io/edwards25519 v1.2.0 // indirect - github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect + github.com/BurntSushi/toml v1.6.0 // indirect github.com/DataDog/zstd v1.4.5 // indirect github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab // indirect + github.com/MakeNowJust/heredoc v1.0.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.5.0 // indirect + github.com/Masterminds/sprig/v3 v3.3.0 // indirect + github.com/Masterminds/squirrel v1.5.4 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/Rican7/retry v0.3.1 // indirect github.com/antithesishq/antithesis-sdk-go v0.7.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect github.com/casbin/govaluate v1.3.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/chai2010/gettext-go v1.0.2 // indirect + github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/cockroachdb/errors v1.11.3 // indirect github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect @@ -112,10 +125,12 @@ require ( github.com/cockroachdb/redact v1.1.5 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/container-storage-interface/spec v1.9.0 // indirect + github.com/containerd/containerd v1.7.32 // indirect github.com/containerd/containerd/api v1.10.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect github.com/containerd/ttrpc v1.2.7 // indirect github.com/containerd/typeurl/v2 v2.2.3 // indirect github.com/coreos/go-oidc v2.5.0+incompatible // indirect @@ -128,17 +143,23 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/euank/go-kmsg-parser v2.0.0+incompatible // indirect + github.com/evanphx/json-patch v5.9.11+incompatible // indirect + github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/expr-lang/expr v1.17.8 // indirect + github.com/fatih/color v1.15.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/getsentry/sentry-go v0.27.0 // indirect + github.com/go-errors/errors v1.4.2 // indirect + github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // 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.23.0 // indirect + github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/godbus/dbus/v5 v5.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -153,17 +174,21 @@ require ( github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-tpm v0.9.8 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect + github.com/gosuri/uitable v0.0.4 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgx/v5 v5.9.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jmoiron/sqlx v1.4.0 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -173,20 +198,30 @@ require ( github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/lib/pq v1.12.3 // 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.20 // indirect + github.com/mattn/go-runewidth v0.0.23 // indirect github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect github.com/minio/crc64nvme v1.1.1 // indirect github.com/minio/highwayhash v1.0.4 // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/minio-go/v7 v7.1.0 // indirect github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/spdystream v0.5.1 // indirect github.com/moby/sys/mountinfo v0.7.2 // indirect github.com/moby/sys/userns v0.1.0 // indirect - github.com/moby/term v0.5.0 // indirect + github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/nats-io/jsm.go v0.4.1 // indirect @@ -201,6 +236,7 @@ require ( github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opencontainers/runtime-spec v1.3.0 // indirect github.com/opencontainers/selinux v1.13.1 // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/philhofer/fwd v1.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -213,9 +249,14 @@ require ( github.com/robfig/cron/v3 v3.0.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/xid v1.6.0 // indirect + github.com/rubenv/sql-migrate v1.8.1 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/shengdoushi/base58 v1.0.0 // indirect github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect + github.com/shopspring/decimal v1.4.0 // indirect github.com/soheilhy/cmux v0.1.5 // indirect + github.com/spf13/cast v1.7.0 // indirect github.com/spf13/cobra v1.10.2 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect @@ -225,6 +266,7 @@ require ( github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 // indirect + github.com/xlab/treeprint v1.2.0 // indirect github.com/zeebo/xxh3 v1.1.0 // indirect go.etcd.io/bbolt v1.4.3 // indirect go.etcd.io/etcd/api/v3 v3.6.12 // indirect @@ -235,29 +277,28 @@ require ( go.etcd.io/raft/v3 v3.6.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect go.opentelemetry.io/otel v1.43.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 // indirect go.opentelemetry.io/otel/metric v1.43.0 // indirect go.opentelemetry.io/otel/sdk v1.43.0 // indirect go.opentelemetry.io/otel/trace v1.43.0 // indirect - go.opentelemetry.io/proto/otlp v1.9.0 // indirect + go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.50.0 // indirect golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.43.0 // indirect - golang.org/x/term v0.42.0 // indirect - golang.org/x/text v0.36.0 // indirect + golang.org/x/sync v0.21.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/term v0.44.0 // indirect + golang.org/x/text v0.38.0 // indirect golang.org/x/time v0.15.0 // indirect - golang.org/x/tools v0.44.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 // indirect + golang.org/x/tools v0.45.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d // indirect google.golang.org/grpc v1.81.1 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect @@ -265,13 +306,14 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/apiextensions-apiserver v0.0.0 // indirect + k8s.io/apiextensions-apiserver v0.36.2 // indirect k8s.io/apiserver v1.36.1-k3s1 // indirect + k8s.io/cli-runtime v0.36.2 // indirect k8s.io/cloud-provider v0.0.0 // indirect k8s.io/cluster-bootstrap v0.0.0 // indirect k8s.io/component-helpers v0.0.0 // indirect k8s.io/controller-manager v0.0.0 // indirect - k8s.io/cri-api v0.0.0 // indirect + k8s.io/cri-api v0.27.1 // indirect k8s.io/cri-client v0.0.0 // indirect k8s.io/csi-translation-lib v0.0.0 // indirect k8s.io/dynamic-resource-allocation v0.0.0 // indirect @@ -284,7 +326,7 @@ require ( k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect k8s.io/kube-proxy v0.0.0 // indirect k8s.io/kube-scheduler v0.0.0 // indirect - k8s.io/kubectl v0.0.0 // indirect + k8s.io/kubectl v0.36.2 // indirect k8s.io/kubelet v0.0.0 // indirect k8s.io/metrics v0.0.0 // indirect k8s.io/mount-utils v0.0.0 // indirect @@ -295,8 +337,11 @@ require ( modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect modernc.org/sqlite v1.51.0 // indirect + oras.land/oras-go/v2 v2.6.1 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/kustomize/api v0.21.1 // indirect + sigs.k8s.io/kustomize/kyaml v0.21.1 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/go.sum b/go.sum index 7ad0f4fc..1b3592db 100644 --- a/go.sum +++ b/go.sum @@ -2598,10 +2598,13 @@ codeberg.org/go-pdf/fpdf v0.10.0/go.mod h1:Y0DGRAdZ0OmnZPvjbMp/1bYxmIPxm0ws4tfoP contrib.go.opencensus.io/exporter/stackdriver v0.13.15-0.20230702191903-2de6d2748484/go.mod h1:uxw+4/0SiKbbVSD/F2tk5pJTdVcfIBBcsQ8gwcu4X+E= cyphar.com/go-pathrs v0.2.2 h1:y9w7hxbkr3zEL78Fjzeg4HEhs2xNy+fbwHiHGJJY2Xo= cyphar.com/go-pathrs v0.2.2/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20221208032759-85de2813cf6b/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= @@ -2621,10 +2624,16 @@ git.sr.ht/~sbinet/gg v0.6.0/go.mod h1:uucygbfC9wVPQIfrmwM2et0imr8L7KQWywX0xpFMm9 git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0/go.mod h1:+axXBRUTIDlCeE73IKeD/os7LoEnTKdkp8/gQOFjqyo= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/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/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= +github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +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/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0= @@ -2648,9 +2657,17 @@ github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab/go.mod h1:3VYc5 github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +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/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= -github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= +github.com/Masterminds/semver/v3 v3.5.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.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= @@ -2705,6 +2722,8 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= @@ -2764,6 +2783,8 @@ github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTS github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= +github.com/bshuster-repo/logrus-logstash-hook v1.1.0 h1:o2FzZifLg+z/DN1OFmzTWzZZx/roaqt8IPZCIVco8r4= +github.com/bshuster-repo/logrus-logstash-hook v1.1.0/go.mod h1:Q2aXOe7rNuPgbBtPCOzYyWDvKX7+FpxE5sRdvcPoui0= github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= @@ -2786,6 +2807,8 @@ github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/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/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -2793,6 +2816,8 @@ github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86c github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= @@ -2834,6 +2859,8 @@ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1: github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/container-storage-interface/spec v1.9.0 h1:zKtX4STsq31Knz3gciCYCi1SXtO2HJDecIjDVboYavY= github.com/container-storage-interface/spec v1.9.0/go.mod h1:ZfDu+3ZRyeVqxZM0Ds19MVLkN2d1XJ5MAfi1L3VjlT0= +github.com/containerd/containerd v1.7.32 h1:S54xuVcPxeLaYgaRABtpJ2VyVUVsy0IGf7qHBs+sbY8= +github.com/containerd/containerd v1.7.32/go.mod h1:jdwD6s/BhV4XVJGrvtziNPVA+83n66TwptVaPKprq4E= github.com/containerd/containerd/api v1.10.0 h1:5n0oHYVBwN4VhoX9fFykCV9dF1/BvAXeg2F8W6UYq1o= github.com/containerd/containerd/api v1.10.0/go.mod h1:NBm1OAk8ZL+LG8R0ceObGxT5hbUYj7CzTmR3xh0DlMM= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= @@ -2842,6 +2869,8 @@ github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151X github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ= github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= @@ -2875,10 +2904,22 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/distribution/distribution/v3 v3.1.1 h1:KUbk7C8CfaLXy8kbf/hGq9cad/wCoLB6dbWH6DMbmX0= +github.com/distribution/distribution/v3 v3.1.1/go.mod h1:d7lXwZpph0bVcOj4Aqn0nMrWHIwRQGdiV5TLeI+/w6Y= 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/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY= +github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/go-events v0.0.0-20250808211157-605354379745 h1:yOn6Ze6IbYI/KAw2lw/83ELYvZh6hvsygTVkD0dzMC4= +github.com/docker/go-events v0.0.0-20250808211157-605354379745/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= @@ -2926,18 +2967,27 @@ github.com/esiqveland/notify v0.11.0/go.mod h1:63UbVSaeJwF0LVJARHFuPgUAoM7o1BEvC github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= github.com/euank/go-kmsg-parser v2.0.0+incompatible h1:cHD53+PLQuuQyLZeriD1V/esuG4MuU0Pjs5y6iknohY= github.com/euank/go-kmsg-parser v2.0.0+incompatible/go.mod h1:MhmAMZ8V4CYH4ybgdRwPr2TU5ThnS43puaKEMpja1uw= +github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= +github.com/evanphx/json-patch v5.9.11+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/expr-lang/expr v1.17.8 h1:W1loDTT+0PQf5YteHSTpju2qfUfNoBt4yw9+wOEU9VM= github.com/expr-lang/expr v1.17.8/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +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/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0= +github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= @@ -2967,6 +3017,8 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20231223183121-56fa3ac82ce7/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +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-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= @@ -3020,6 +3072,7 @@ github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8w github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw= github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -3027,6 +3080,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k= github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= +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/goccmack/gocc v0.0.0-20230228185258-2292f9e40198/go.mod h1:DTh/Y2+NbnOVVoypCCQrovMPDKUGp4yZpSbWg5D0XIM= github.com/goccy/go-json v0.7.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= @@ -3218,12 +3273,18 @@ github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+ github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= +github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= +github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20= @@ -3236,16 +3297,20 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4Zs github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/hamba/avro/v2 v2.17.2/go.mod h1:Q9YK+qxAhtVrNqOhwlZTATLgLA8qxG2vtvkhK8fJ7Jo= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= @@ -3257,6 +3322,8 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw= +github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= @@ -3265,6 +3332,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +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/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= @@ -3336,6 +3405,8 @@ github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFl github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= @@ -3378,6 +3449,8 @@ github.com/k3s-io/kubernetes/staging/src/k8s.io/apimachinery v1.36.1-k3s1 h1:whT github.com/k3s-io/kubernetes/staging/src/k8s.io/apimachinery v1.36.1-k3s1/go.mod h1:+d1oaR9LEzkN+LW0f+oZabFYFsshU2C+rvgnyyAwcFY= github.com/k3s-io/kubernetes/staging/src/k8s.io/apiserver v1.36.1-k3s1 h1:UiEW6FF+yA9Ng/c5BZk+gJOfRfeFAuI6gTaadGYOnk8= github.com/k3s-io/kubernetes/staging/src/k8s.io/apiserver v1.36.1-k3s1/go.mod h1:nUUwRE1XM4kb1QNawZFw3D7CIpQ7paqWtCO564yPvkM= +github.com/k3s-io/kubernetes/staging/src/k8s.io/cli-runtime v1.36.1-k3s1 h1:KTWNljoGt2K0VS/Vuu4Dk+GbZDwv9R0icbf1J1iaDK8= +github.com/k3s-io/kubernetes/staging/src/k8s.io/cli-runtime v1.36.1-k3s1/go.mod h1:VrHkbjBrl6EYGx6s42nG321PPbkuVa3xq70iApaVkfI= github.com/k3s-io/kubernetes/staging/src/k8s.io/client-go v1.36.1-k3s1 h1:rqgs6bqh1g0ViOp7i6Nq8x7yfbtTEOhrNEM+Dor6MkA= github.com/k3s-io/kubernetes/staging/src/k8s.io/client-go v1.36.1-k3s1/go.mod h1:sSQ7Q8PXufsUFVyMXAnD/TK4fBb0mrhTI/IHkcxAQ+I= github.com/k3s-io/kubernetes/staging/src/k8s.io/cloud-provider v1.36.1-k3s1 h1:lAIS1QWxaWT8CCAJ+7YFIhncCeL+uZHpkuZSJ9omlM0= @@ -3461,6 +3534,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -3468,6 +3545,11 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= +github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +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/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= @@ -3483,6 +3565,7 @@ github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -3498,12 +3581,16 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw= +github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24/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/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +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/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI= @@ -3517,13 +3604,19 @@ github.com/minio/minio-go/v7 v7.1.0/go.mod h1:Dm7WS1AgLmBa0NcQD6SeJnJf+K/EUW3GR7 github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible h1:aKW/4cBs+yK6gpqU3K/oIwk9Q/XICqd3zOX/UFuvqmk= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/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-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 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/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/moby/api v1.52.0 h1:00BtlJY4MXkkt84WhUZPRqt5TvPbgig2FZvTbe3igYg= @@ -3536,8 +3629,8 @@ github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9Kou github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -3546,6 +3639,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -3615,7 +3710,11 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +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/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= +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/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= @@ -3640,6 +3739,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= +github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc= github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -3690,6 +3791,8 @@ github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkq github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos= +github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -3706,6 +3809,12 @@ github.com/prometheus/procfs v0.16.0/go.mod h1:8veyXUu3nGP7oaCxhX6yeaM5u4stL2FeM github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= +github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= +github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= +github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= +github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= +github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= @@ -3727,14 +3836,21 @@ github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM= +github.com/rubenv/sql-migrate v1.8.1 h1:EPNwCvjAowHI3TnZ+4fQu3a915OpnQoPAjTXCGOy2U0= +github.com/rubenv/sql-migrate v1.8.1/go.mod h1:BTIKBORjzyxZDS6dzoiw6eAFYJ1iNlGAtjn4LGeVjS8= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +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/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shengdoushi/base58 v1.0.0 h1:tGe4o6TmdXFJWoI31VoSWvuaKxf0Px3gqa3sUWhAxBs= github.com/shengdoushi/base58 v1.0.0/go.mod h1:m5uIILfzcKMw6238iWAhP4l3s5+uXyF3+bJKUNhAL9I= github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg= @@ -3743,6 +3859,8 @@ github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18/go.mod h1:nkxAfR/ github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKzU3qCuvj/tPnimWSsZZzvk9qvkvrIL5naVBPh5s= @@ -3765,6 +3883,8 @@ github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY52 github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +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.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= @@ -3832,6 +3952,8 @@ github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtX github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk= github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +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.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -3871,12 +3993,16 @@ go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 h1:dkBzNEAIKADEaFnuESzcXvpd09vxvDZsOjx11gjUqLk= +go.opentelemetry.io/contrib/bridges/prometheus v0.67.0/go.mod h1:Z5RIwRkZgauOIfnG5IpidvLpERjhTninpP1dTG2jTl4= go.opentelemetry.io/contrib/detectors/gcp v1.29.0/go.mod h1:GW2aWZNwR2ZxDLdv8OyC2G8zkRoQBuURgV7RPQgcPoU= go.opentelemetry.io/contrib/detectors/gcp v1.31.0/go.mod h1:tzQL6E1l+iV44YFTkcAeNQqzXUiekSYP9jjJjXwEd00= go.opentelemetry.io/contrib/detectors/gcp v1.33.0/go.mod h1:ZHrLmr4ikK2AwRj9QL+c9s2SOlgoSRyMpNVzUj2fZqI= go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= go.opentelemetry.io/contrib/detectors/gcp v1.35.0/go.mod h1:qGWP8/+ILwMRIUf9uIVLloR1uo5ZYAslM4O6OqUi1DA= go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk= +go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 h1:4fnRcNpc6YFtG3zsFw9achKn3XgmxPxuMuqIL5rE8e8= +go.opentelemetry.io/contrib/exporters/autoexport v0.67.0/go.mod h1:qTvIHMFKoxW7HXg02gm6/Wofhq5p3Ib/A/NNt1EoBSQ= 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.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0/go.mod h1:tIKj3DbO8N9Y2xo52og3irLsPI4GW02DSMtrVgNMgxg= @@ -3897,8 +4023,8 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1: go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= @@ -3918,12 +4044,32 @@ go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwEx go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 h1:Dn8rkudDzY6KV9dr/D/bTUuWgqDf9xe0rr4G2elrn0Y= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0/go.mod h1:gMk9F0xDgyN9M/3Ed5Y1wKcx/9mlU91NXY2SNq7RQuU= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0/go.mod h1:ji9vId85hMxqfvICA0Jt8JqEdrXaAkcpkI9HPXya0ro= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 h1:8UQVDcZxOJLtX6gxtDt3vY2WTgvZqMQRzjsqiIHQdkc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0/go.mod h1:2lmweYCiHYpEjQ/lSJBYhj9jP1zvCvQW4BqL9dnT7FQ= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 h1:w1K+pCJoPpQifuVpsKamUdn9U0zM3xUziVOqsGksUrY= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0/go.mod h1:HBy4BjzgVE8139ieRI75oXm3EcDN+6GhD88JT1Kjvxg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= go.opentelemetry.io/otel/exporters/prometheus v0.57.0/go.mod h1:QpFWz1QxqevfjwzYdbMb4Y1NnlJvqSGwyuU0B4iuc9c= +go.opentelemetry.io/otel/exporters/prometheus v0.65.0 h1:jOveH/b4lU9HT7y+Gfamf18BqlOuz2PWEvs8yM7Q6XE= +go.opentelemetry.io/otel/exporters/prometheus v0.65.0/go.mod h1:i1P8pcumauPtUI4YNopea1dhzEMuEqWP1xoUZDylLHo= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0 h1:GJkybS+crDMdExT/BUNCEgfrmfboztcS6PhvSo88HKM= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0/go.mod h1:NuAyxRYIG2lKX3YQkB+83StTxM7s52PUUkRRiC0wnYI= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 h1:TC+BewnDpeiAmcscXbGMfxkO+mwYUwE/VySwvw88PfA= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0/go.mod h1:J/ZyF4vfPwsSr9xJSPyQ4LqtcTPULFR64KwTikGLe+A= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0 h1:mS47AX77OtFfKG4vtp+84kuGSFZHTyxtXIN269vChY0= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0/go.mod h1:PJnsC41lAGncJlPUniSwM81gc80GkgWJWr3cu2nKEtU= +go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4= +go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= @@ -3960,6 +4106,8 @@ go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOg go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko= +go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg= go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0= go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= @@ -3995,8 +4143,8 @@ go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= -go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= -go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= +go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= +go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -4134,8 +4282,8 @@ golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -4222,8 +4370,9 @@ golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= @@ -4237,8 +4386,8 @@ golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc/go.mod h1:hKdjCMrbv9sk golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2/go.mod h1:b7fPSJ0pKZ3ccUh8gnTONJxhn3c/PS6tyzQvyqw4iA8= golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4/go.mod h1:g5NllXBEermZrmR51cJDQxmJUHUOfRAaNyWBM+R+548= golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= -golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= -golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -4272,8 +4421,8 @@ golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -4388,8 +4537,8 @@ golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -4794,8 +4943,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go. google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0/go.mod h1:8ytArBbtOy2xfht+y2fqKd5DRDJRUQhqbyEnQ4bDChs= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= -google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 h1:41r6JMbpzBMen0R/4TZeeAmGXSJC7DftGINUodzTkPI= -google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y= +google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= +google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230807174057-1744710a1577/go.mod h1:NjCQG/D8JandXxM57PZbAJL1DCNL6EypA0vPPwfsc7c= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:+34luvCflYKiKylNwGJfn9cFBbcL/WrkciMmDmsTQ/A= @@ -4926,8 +5075,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250721164621-a45f3dfb1074/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 h1:ndE4FoJqsIceKP2oYSnUZqhTdYufCYYkqwtFzfrhI7w= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d h1:wT2n40TBqFY6wiwazVK9/iTWbsQrgk5ZfCSVFLO9LQA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= @@ -5013,6 +5162,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +helm.sh/helm/v3 v3.21.2 h1:O8ktw30zQZm4tNRavKRN44rotcHICdbSU3e6gS7CCbQ= +helm.sh/helm/v3 v3.21.2/go.mod h1:Da+9m67Mzg42rQNrWAj0RvUd4tDT4dL8mrxUwvRgYWo= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -5123,6 +5274,8 @@ modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA= modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ= +oras.land/oras-go/v2 v2.6.1 h1:bonOEkjLfp8tt6qXWRRWP6p1F+9octchOf2EqnWB4Zs= +oras.land/oras-go/v2 v2.6.1/go.mod h1:dhtFrFOuZuDtAVeZ9FUnaa5zfzplG3ZnFX9/uH1J/Yk= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= @@ -5131,6 +5284,10 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs= +sigs.k8s.io/kustomize/api v0.21.1/go.mod h1:f3wkKByTrgpgltLgySCntrYoq5d3q7aaxveSagwTlwI= +sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI= +sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= diff --git a/object/helm.go b/object/helm.go new file mode 100644 index 00000000..69905754 --- /dev/null +++ b/object/helm.go @@ -0,0 +1,587 @@ +package object + +import ( + "fmt" + "os" + "path/filepath" + "sort" + "strings" + "time" + + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/chartutil" + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/downloader" + helmgetter "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v3/pkg/registry" + "helm.sh/helm/v3/pkg/release" + "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" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + "sigs.k8s.io/yaml" +) + +const ( + helmDefaultTimeout = 10 * time.Minute + helmDriver = "secrets" +) + +// HelmChartRef identifies a chart in a Helm repository (classic HTTP or OCI). +type HelmChartRef struct { + RepoURL string `json:"repoUrl"` + Chart string `json:"chart"` + Version string `json:"version"` + Username string `json:"username"` + Password string `json:"password"` +} + +// IsOCI reports whether the chart is referenced through an OCI registry. +func (r HelmChartRef) IsOCI() bool { + return strings.HasPrefix(strings.ToLower(strings.TrimSpace(r.RepoURL)), "oci://") || + strings.HasPrefix(strings.ToLower(strings.TrimSpace(r.Chart)), "oci://") +} + +// Normalize validates the reference and returns the chart argument plus the +// repository URL to pass to Helm. OCI charts are addressed by a single ref and +// must not carry a separate repo URL. +func (r HelmChartRef) Normalize() (chartRef string, repoURL string, err error) { + repo := strings.TrimSpace(r.RepoURL) + name := strings.TrimSpace(r.Chart) + + if r.IsOCI() { + ref := name + if !strings.HasPrefix(strings.ToLower(ref), "oci://") { + ref = strings.TrimRight(repo, "/") + if name != "" && !strings.HasSuffix(strings.ToLower(ref), "/"+strings.ToLower(name)) { + ref = ref + "/" + name + } + } + if ref == "" { + return "", "", fmt.Errorf("oci chart reference is required") + } + return strings.TrimRight(ref, "/"), "", nil + } + + if repo == "" { + return "", "", fmt.Errorf("chart repository url is required") + } + if name == "" { + return "", "", fmt.Errorf("chart name is required") + } + return name, repo, nil +} + +// HelmReleaseSummary is the API-facing view of a Helm release. +type HelmReleaseSummary struct { + Name string `json:"name"` + Namespace string `json:"namespace"` + Chart string `json:"chart"` + Version string `json:"version"` + AppVersion string `json:"appVersion"` + Status string `json:"status"` + Revision int `json:"revision"` + Updated string `json:"updated,omitempty"` + Notes string `json:"notes,omitempty"` +} + +// HelmChartInfo describes a chart and the inputs needed to install it. +type HelmChartInfo struct { + Name string `json:"name"` + Version string `json:"version"` + AppVersion string `json:"appVersion"` + Description string `json:"description"` + Home string `json:"home"` + Icon string `json:"icon"` + Deprecated bool `json:"deprecated"` + DefaultValues string `json:"defaultValues"` + ValuesSchema string `json:"valuesSchema,omitempty"` + Dependencies []string `json:"dependencies,omitempty"` +} + +// HelmLogger receives Helm's debug output during an operation. +type HelmLogger func(format string, v ...interface{}) + +type helmRESTClientGetter struct { + cfg *rest.Config + namespace string +} + +func (g *helmRESTClientGetter) ToRESTConfig() (*rest.Config, error) { + if g.cfg == nil { + return nil, fmt.Errorf("kubernetes rest config is nil") + } + return rest.CopyConfig(g.cfg), nil +} + +func (g *helmRESTClientGetter) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) { + cfg, err := g.ToRESTConfig() + if err != nil { + return nil, err + } + client, err := discovery.NewDiscoveryClientForConfig(cfg) + if err != nil { + return nil, err + } + return memory.NewMemCacheClient(client), nil +} + +func (g *helmRESTClientGetter) ToRESTMapper() (meta.RESTMapper, error) { + discoveryClient, err := g.ToDiscoveryClient() + if err != nil { + return nil, err + } + return restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient), nil +} + +func (g *helmRESTClientGetter) ToRawKubeConfigLoader() clientcmd.ClientConfig { + return &helmClientConfig{cfg: g.cfg, namespace: g.namespace} +} + +type helmClientConfig struct { + cfg *rest.Config + namespace string +} + +func (c *helmClientConfig) RawConfig() (clientcmdapi.Config, error) { + return clientcmdapi.Config{ + CurrentContext: "casos", + Contexts: map[string]*clientcmdapi.Context{"casos": {Namespace: c.namespace}}, + }, nil +} + +func (c *helmClientConfig) ClientConfig() (*rest.Config, error) { + if c.cfg == nil { + return nil, fmt.Errorf("kubernetes rest config is nil") + } + return rest.CopyConfig(c.cfg), nil +} + +func (c *helmClientConfig) Namespace() (string, bool, error) { + if c.namespace == "" { + return "default", false, nil + } + return c.namespace, true, nil +} + +func (c *helmClientConfig) ConfigAccess() clientcmd.ConfigAccess { + return nil +} + +// helmEnv bundles the per-operation Helm environment. Everything lives in a +// temporary directory so the server never reads or writes the user's ~/.helm. +type helmEnv struct { + settings *cli.EnvSettings + registryClient *registry.Client + cleanup func() +} + +func newHelmEnv(namespace string) (*helmEnv, error) { + workDir, err := os.MkdirTemp("", "casos-helm-*") + if err != nil { + return nil, fmt.Errorf("create helm temp dir: %w", err) + } + cleanup := func() { _ = os.RemoveAll(workDir) } + + settings := cli.New() + settings.SetNamespace(namespace) + settings.RepositoryCache = filepath.Join(workDir, "repository") + settings.RepositoryConfig = filepath.Join(workDir, "repositories.yaml") + settings.RegistryConfig = filepath.Join(workDir, "registry.json") + if err := os.MkdirAll(settings.RepositoryCache, 0o755); err != nil { + cleanup() + return nil, fmt.Errorf("create helm cache dir: %w", err) + } + + registryClient, err := registry.NewClient(registry.ClientOptCredentialsFile(settings.RegistryConfig)) + if err != nil { + cleanup() + return nil, fmt.Errorf("create helm registry client: %w", err) + } + + return &helmEnv{settings: settings, registryClient: registryClient, cleanup: cleanup}, nil +} + +func newHelmActionConfig(cfg *rest.Config, namespace string, env *helmEnv, log HelmLogger) (*action.Configuration, error) { + if log == nil { + log = func(string, ...interface{}) {} + } + actionConfig := new(action.Configuration) + getter := &helmRESTClientGetter{cfg: cfg, namespace: namespace} + if err := actionConfig.Init(getter, namespace, helmDriver, action.DebugLog(log)); err != nil { + return nil, fmt.Errorf("init helm action config: %w", err) + } + actionConfig.RegistryClient = env.registryClient + return actionConfig, nil +} + +func applyChartPathOptions(opts *action.ChartPathOptions, ref HelmChartRef, repoURL string) { + opts.RepoURL = repoURL + opts.Version = strings.TrimSpace(ref.Version) + opts.Username = ref.Username + opts.Password = ref.Password +} + +// loadChart downloads (if needed) and loads a chart, updating dependencies when +// the chart declares ones that are not vendored in the archive. +func loadChart(opts *action.ChartPathOptions, ref HelmChartRef, env *helmEnv) (*chart.Chart, error) { + chartRef, repoURL, err := ref.Normalize() + if err != nil { + return nil, err + } + applyChartPathOptions(opts, ref, repoURL) + + chartPath, err := opts.LocateChart(chartRef, env.settings) + if err != nil { + return nil, fmt.Errorf("locate helm chart: %w", err) + } + + loaded, err := loader.Load(chartPath) + if err != nil { + return nil, fmt.Errorf("load helm chart: %w", err) + } + + if loaded.Metadata != nil && len(loaded.Metadata.Dependencies) > 0 { + if depErr := action.CheckDependencies(loaded, loaded.Metadata.Dependencies); depErr != nil { + manager := &downloader.Manager{ + ChartPath: chartPath, + Keyring: opts.Keyring, + Getters: helmgetter.All(env.settings), + RepositoryConfig: env.settings.RepositoryConfig, + RepositoryCache: env.settings.RepositoryCache, + RegistryClient: env.registryClient, + } + if updateErr := manager.Update(); updateErr != nil { + return nil, fmt.Errorf("update chart dependencies: %w", updateErr) + } + loaded, err = loader.Load(chartPath) + if err != nil { + return nil, fmt.Errorf("reload helm chart after dependency update: %w", err) + } + } + } + return loaded, nil +} + +// ParseHelmValues parses user supplied values. YAML is accepted, and because +// JSON is a subset of YAML, JSON input works too. An empty input yields an +// empty (non-nil) map. +func ParseHelmValues(raw string) (map[string]interface{}, error) { + trimmed := strings.TrimSpace(raw) + if trimmed == "" { + return map[string]interface{}{}, nil + } + values := map[string]interface{}{} + if err := yaml.Unmarshal([]byte(trimmed), &values); err != nil { + return nil, fmt.Errorf("values must be a valid YAML or JSON object: %w", err) + } + if values == nil { + return map[string]interface{}{}, nil + } + return values, nil +} + +// ValidateHelmReleaseName checks the release name against Helm's own rules. +func ValidateHelmReleaseName(name string) error { + if strings.TrimSpace(name) == "" { + return fmt.Errorf("release name is required") + } + if err := chartutil.ValidateReleaseName(name); err != nil { + return fmt.Errorf("invalid release name: %w", err) + } + return nil +} + +// HelmShowChart loads a chart without touching the cluster and reports the +// metadata, default values and values schema needed to render an install form. +func HelmShowChart(ref HelmChartRef) (*HelmChartInfo, error) { + env, err := newHelmEnv("default") + if err != nil { + return nil, err + } + defer env.cleanup() + + install := action.NewInstall(new(action.Configuration)) + install.SetRegistryClient(env.registryClient) + + loaded, err := loadChart(&install.ChartPathOptions, ref, env) + if err != nil { + return nil, err + } + + info := &HelmChartInfo{} + if loaded.Metadata != nil { + info.Name = loaded.Metadata.Name + info.Version = loaded.Metadata.Version + info.AppVersion = loaded.Metadata.AppVersion + info.Description = loaded.Metadata.Description + info.Home = loaded.Metadata.Home + info.Icon = loaded.Metadata.Icon + info.Deprecated = loaded.Metadata.Deprecated + for _, dep := range loaded.Metadata.Dependencies { + info.Dependencies = append(info.Dependencies, dep.Name) + } + } + if len(loaded.Schema) > 0 { + info.ValuesSchema = string(loaded.Schema) + } + for _, file := range loaded.Raw { + if file.Name == chartutil.ValuesfileName { + info.DefaultValues = string(file.Data) + break + } + } + if info.DefaultValues == "" && len(loaded.Values) > 0 { + if data, marshalErr := yaml.Marshal(loaded.Values); marshalErr == nil { + info.DefaultValues = string(data) + } + } + return info, nil +} + +// HelmRenderChart renders a chart locally (no cluster access) and returns the +// generated manifests. It is used for validation and smoke tests. +func HelmRenderChart(ref HelmChartRef, namespace, releaseName string, values map[string]interface{}) (string, error) { + if err := ValidateHelmReleaseName(releaseName); err != nil { + return "", err + } + env, err := newHelmEnv(namespace) + if err != nil { + return "", err + } + defer env.cleanup() + + actionConfig := new(action.Configuration) + install := action.NewInstall(actionConfig) + install.SetRegistryClient(env.registryClient) + install.ReleaseName = releaseName + install.Namespace = namespace + install.DryRun = true + install.ClientOnly = true + install.IncludeCRDs = true + // ClientOnly rendering defaults to a very old Kubernetes version; charts + // with a kubeVersion constraint would fail. Match the embedded control + // plane version instead. + install.KubeVersion = &chartutil.KubeVersion{Version: "v1.36.0", Major: "1", Minor: "36"} + + loaded, err := loadChart(&install.ChartPathOptions, ref, env) + if err != nil { + return "", err + } + if values == nil { + values = map[string]interface{}{} + } + rel, err := install.Run(loaded, values) + if err != nil { + return "", fmt.Errorf("render helm chart: %w", err) + } + return rel.Manifest, nil +} + +// HelmInstall installs a chart into the cluster. Installation is atomic: a +// failed install is rolled back so no partial resources are left behind. +func HelmInstall(cfg *rest.Config, ref HelmChartRef, namespace, releaseName string, values map[string]interface{}, log HelmLogger) (*HelmReleaseSummary, error) { + if err := ValidateHelmReleaseName(releaseName); err != nil { + return nil, err + } + if namespace == "" { + return nil, fmt.Errorf("namespace is required") + } + + env, err := newHelmEnv(namespace) + if err != nil { + return nil, err + } + defer env.cleanup() + + actionConfig, err := newHelmActionConfig(cfg, namespace, env, log) + if err != nil { + return nil, err + } + + install := action.NewInstall(actionConfig) + install.SetRegistryClient(env.registryClient) + install.ReleaseName = releaseName + install.Namespace = namespace + install.CreateNamespace = false + install.Atomic = true + install.Wait = true + install.Timeout = helmDefaultTimeout + + loaded, err := loadChart(&install.ChartPathOptions, ref, env) + if err != nil { + return nil, err + } + if values == nil { + values = map[string]interface{}{} + } + + rel, err := install.Run(loaded, values) + if err != nil { + return nil, fmt.Errorf("install helm chart: %w", err) + } + return toHelmReleaseSummary(rel), nil +} + +// HelmUpgrade upgrades an existing release, rolling back on failure. +func HelmUpgrade(cfg *rest.Config, ref HelmChartRef, namespace, releaseName string, values map[string]interface{}, log HelmLogger) (*HelmReleaseSummary, error) { + if err := ValidateHelmReleaseName(releaseName); err != nil { + return nil, err + } + if namespace == "" { + return nil, fmt.Errorf("namespace is required") + } + + env, err := newHelmEnv(namespace) + if err != nil { + return nil, err + } + defer env.cleanup() + + actionConfig, err := newHelmActionConfig(cfg, namespace, env, log) + if err != nil { + return nil, err + } + + upgrade := action.NewUpgrade(actionConfig) + upgrade.SetRegistryClient(env.registryClient) + upgrade.Namespace = namespace + upgrade.Atomic = true + upgrade.Wait = true + upgrade.Timeout = helmDefaultTimeout + + loaded, err := loadChart(&upgrade.ChartPathOptions, ref, env) + if err != nil { + return nil, err + } + if values == nil { + values = map[string]interface{}{} + } + + rel, err := upgrade.Run(releaseName, loaded, values) + if err != nil { + return nil, fmt.Errorf("upgrade helm release: %w", err) + } + return toHelmReleaseSummary(rel), nil +} + +// HelmUninstall removes a release and its resources. +func HelmUninstall(cfg *rest.Config, namespace, releaseName string, log HelmLogger) error { + if err := ValidateHelmReleaseName(releaseName); err != nil { + return err + } + if namespace == "" { + return fmt.Errorf("namespace is required") + } + + env, err := newHelmEnv(namespace) + if err != nil { + return err + } + defer env.cleanup() + + actionConfig, err := newHelmActionConfig(cfg, namespace, env, log) + if err != nil { + return err + } + + uninstall := action.NewUninstall(actionConfig) + uninstall.Wait = false + uninstall.Timeout = helmDefaultTimeout + if _, err := uninstall.Run(releaseName); err != nil { + return fmt.Errorf("uninstall helm release: %w", err) + } + return nil +} + +// HelmListReleases lists releases in a namespace, or in all namespaces when +// namespace is empty. +func HelmListReleases(cfg *rest.Config, namespace string) ([]HelmReleaseSummary, error) { + env, err := newHelmEnv(namespace) + if err != nil { + return nil, err + } + defer env.cleanup() + + actionConfig, err := newHelmActionConfig(cfg, namespace, env, nil) + if err != nil { + return nil, err + } + + list := action.NewList(actionConfig) + list.All = true + list.SetStateMask() + if namespace == "" { + list.AllNamespaces = true + } + + releases, err := list.Run() + if err != nil { + return nil, fmt.Errorf("list helm releases: %w", err) + } + + result := make([]HelmReleaseSummary, 0, len(releases)) + for _, rel := range releases { + if summary := toHelmReleaseSummary(rel); summary != nil { + result = append(result, *summary) + } + } + sort.SliceStable(result, func(i, j int) bool { + if result[i].Namespace != result[j].Namespace { + return result[i].Namespace < result[j].Namespace + } + return result[i].Name < result[j].Name + }) + return result, nil +} + +// HelmGetRelease returns a single release. +func HelmGetRelease(cfg *rest.Config, namespace, releaseName string) (*HelmReleaseSummary, error) { + env, err := newHelmEnv(namespace) + if err != nil { + return nil, err + } + defer env.cleanup() + + actionConfig, err := newHelmActionConfig(cfg, namespace, env, nil) + if err != nil { + return nil, err + } + + rel, err := action.NewGet(actionConfig).Run(releaseName) + if err != nil { + return nil, fmt.Errorf("get helm release: %w", err) + } + return toHelmReleaseSummary(rel), nil +} + +func toHelmReleaseSummary(rel *release.Release) *HelmReleaseSummary { + if rel == nil { + return nil + } + summary := &HelmReleaseSummary{ + Name: rel.Name, + Namespace: rel.Namespace, + Revision: rel.Version, + } + if rel.Chart != nil && rel.Chart.Metadata != nil { + summary.Chart = rel.Chart.Metadata.Name + summary.Version = rel.Chart.Metadata.Version + summary.AppVersion = rel.Chart.Metadata.AppVersion + } + if rel.Info != nil { + summary.Status = rel.Info.Status.String() + summary.Notes = rel.Info.Notes + if !rel.Info.LastDeployed.IsZero() { + summary.Updated = rel.Info.LastDeployed.UTC().Format("2006-01-02 15:04:05") + } else if !rel.Info.FirstDeployed.IsZero() { + summary.Updated = rel.Info.FirstDeployed.UTC().Format("2006-01-02 15:04:05") + } + } + return summary +} diff --git a/object/helm_repo.go b/object/helm_repo.go new file mode 100644 index 00000000..3cd1b5d9 --- /dev/null +++ b/object/helm_repo.go @@ -0,0 +1,327 @@ +package object + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "sort" + "strconv" + "strings" + "time" + + "helm.sh/helm/v3/pkg/repo" + "sigs.k8s.io/yaml" +) + +const ( + artifactHubAPIBase = "https://artifacthub.io/api/v1" + artifactHubTimeout = 20 * time.Second + helmRepoTimeout = 30 * time.Second + helmUserAgent = "casos-app/1.0" +) + +// HelmChartListItem is a single chart entry shown in the app store. +type HelmChartListItem struct { + Name string `json:"name"` + Title string `json:"title"` + Description string `json:"description"` + Icon string `json:"icon"` + Version string `json:"version"` + AppVersion string `json:"appVersion"` + Deprecated bool `json:"deprecated"` + Source string `json:"source"` + PackageType string `json:"packageType"` + RepoName string `json:"repoName"` + RepoURL string `json:"repoUrl"` + ChartName string `json:"chartName"` + Categories []string `json:"categories,omitempty"` + Stars int `json:"stars,omitempty"` + Official bool `json:"official,omitempty"` + Verified bool `json:"verified,omitempty"` +} + +// HelmChartSearchResult is a paginated chart listing. +type HelmChartSearchResult struct { + Items []HelmChartListItem `json:"items"` + Total int `json:"total"` + Page int `json:"page"` + PageSize int `json:"pageSize"` +} + +type artifactHubSearchResponse struct { + Packages []artifactHubPackage `json:"packages"` +} + +type artifactHubPackage struct { + Name string `json:"name"` + Description string `json:"description"` + Version string `json:"version"` + AppVersion string `json:"app_version"` + Deprecated bool `json:"deprecated"` + LogoImageID string `json:"logo_image_id"` + Stars int `json:"stars"` + Repository artifactHubRepository `json:"repository"` +} + +type artifactHubRepository struct { + URL string `json:"url"` + Kind int `json:"kind"` + Name string `json:"name"` + DisplayName string `json:"display_name"` + Official bool `json:"official"` + VerifiedPublisher bool `json:"verified_publisher"` + OrganizationName string `json:"organization_name"` +} + +type artifactHubVersion struct { + Version string `json:"version"` + AppVersion string `json:"app_version"` + Prerelease bool `json:"prerelease"` +} + +type artifactHubPackageDetail struct { + artifactHubPackage + AvailableVersions []artifactHubVersion `json:"available_versions"` +} + +func httpGet(rawURL string, timeout time.Duration, accept string) ([]byte, http.Header, error) { + req, err := http.NewRequest(http.MethodGet, rawURL, nil) + if err != nil { + return nil, nil, err + } + req.Header.Set("User-Agent", helmUserAgent) + if accept != "" { + req.Header.Set("Accept", accept) + } + + client := &http.Client{Timeout: timeout} + resp, err := client.Do(req) + if err != nil { + return nil, nil, err + } + defer resp.Body.Close() + + body, err := io.ReadAll(io.LimitReader(resp.Body, 64<<20)) + if err != nil { + return nil, nil, err + } + if resp.StatusCode != http.StatusOK { + return nil, nil, fmt.Errorf("GET %s returned %d", rawURL, resp.StatusCode) + } + return body, resp.Header, nil +} + +// SearchArtifactHubCharts performs a server-side chart search against +// ArtifactHub so the UI is not limited to a locally cached subset. +func SearchArtifactHubCharts(query string, page, pageSize int) (*HelmChartSearchResult, error) { + if page < 1 { + page = 1 + } + if pageSize < 1 || pageSize > 60 { + pageSize = 24 + } + + params := url.Values{} + params.Set("kind", "0") // Helm charts + params.Set("limit", strconv.Itoa(pageSize)) + params.Set("offset", strconv.Itoa((page-1)*pageSize)) + params.Set("facets", "false") + params.Set("deprecated", "false") + if q := strings.TrimSpace(query); q != "" { + params.Set("ts_query_web", q) + params.Set("sort", "relevance") + } else { + params.Set("sort", "stars") + } + + body, header, err := httpGet(artifactHubAPIBase+"/packages/search?"+params.Encode(), artifactHubTimeout, "application/json") + if err != nil { + return nil, err + } + + var parsed artifactHubSearchResponse + if err := json.Unmarshal(body, &parsed); err != nil { + return nil, fmt.Errorf("parse artifacthub response: %w", err) + } + + items := make([]HelmChartListItem, 0, len(parsed.Packages)) + for _, pkg := range parsed.Packages { + if item, ok := toArtifactHubItem(pkg); ok { + items = append(items, item) + } + } + + total := len(items) + if raw := header.Get("Pagination-Total-Count"); raw != "" { + if parsedTotal, convErr := strconv.Atoi(raw); convErr == nil { + total = parsedTotal + } + } + + return &HelmChartSearchResult{Items: items, Total: total, Page: page, PageSize: pageSize}, nil +} + +func toArtifactHubItem(pkg artifactHubPackage) (HelmChartListItem, bool) { + if pkg.Name == "" || pkg.Repository.URL == "" { + return HelmChartListItem{}, false + } + repoName := pkg.Repository.DisplayName + if repoName == "" { + repoName = pkg.Repository.Name + } + icon := "" + if pkg.LogoImageID != "" { + icon = "https://artifacthub.io/image/" + pkg.LogoImageID + } + title := pkg.Name + if repoName != "" && !strings.EqualFold(repoName, pkg.Name) { + title = repoName + " / " + pkg.Name + } + return HelmChartListItem{ + Name: pkg.Name, + Title: title, + Description: pkg.Description, + Icon: icon, + Version: pkg.Version, + AppVersion: pkg.AppVersion, + Deprecated: pkg.Deprecated, + Source: "artifacthub", + PackageType: "helm", + RepoName: repoName, + RepoURL: pkg.Repository.URL, + ChartName: pkg.Name, + Stars: pkg.Stars, + Official: pkg.Repository.Official, + Verified: pkg.Repository.VerifiedPublisher, + }, true +} + +// GetArtifactHubChartVersions returns the published versions of a chart hosted +// on ArtifactHub. repoName is the ArtifactHub repository name. +func GetArtifactHubChartVersions(repoName, chartName string) ([]string, error) { + repoName = strings.TrimSpace(repoName) + chartName = strings.TrimSpace(chartName) + if repoName == "" || chartName == "" { + return nil, fmt.Errorf("repoName and chartName are required") + } + + endpoint := fmt.Sprintf("%s/packages/helm/%s/%s", artifactHubAPIBase, url.PathEscape(repoName), url.PathEscape(chartName)) + body, _, err := httpGet(endpoint, artifactHubTimeout, "application/json") + if err != nil { + return nil, err + } + + var detail artifactHubPackageDetail + if err := json.Unmarshal(body, &detail); err != nil { + return nil, fmt.Errorf("parse artifacthub package: %w", err) + } + + versions := make([]string, 0, len(detail.AvailableVersions)) + for _, v := range detail.AvailableVersions { + if v.Version != "" && !v.Prerelease { + versions = append(versions, v.Version) + } + } + return versions, nil +} + +// ListHelmRepoCharts fetches and parses index.yaml of an arbitrary Helm +// repository, so users can install charts from any source. +func ListHelmRepoCharts(repoURL string) ([]HelmChartListItem, error) { + trimmed := strings.TrimRight(strings.TrimSpace(repoURL), "/") + if trimmed == "" { + return nil, fmt.Errorf("repoUrl is required") + } + if strings.HasPrefix(strings.ToLower(trimmed), "oci://") { + return nil, fmt.Errorf("oci repositories cannot be listed; install by full chart reference instead") + } + parsed, err := url.Parse(trimmed) + if err != nil { + return nil, fmt.Errorf("invalid repoUrl: %w", err) + } + if parsed.Scheme != "http" && parsed.Scheme != "https" { + return nil, fmt.Errorf("repoUrl must use http or https") + } + + body, _, err := httpGet(trimmed+"/index.yaml", helmRepoTimeout, "application/yaml") + if err != nil { + return nil, err + } + + index := &repo.IndexFile{} + if err := yaml.Unmarshal(body, index); err != nil { + return nil, fmt.Errorf("parse repository index: %w", err) + } + + items := make([]HelmChartListItem, 0, len(index.Entries)) + for name, versions := range index.Entries { + if len(versions) == 0 { + continue + } + latest := versions[0] + for _, v := range versions { + if v != nil && v.Metadata != nil && latest != nil && latest.Metadata != nil { + if v.Created.After(latest.Created) { + latest = v + } + } + } + if latest == nil || latest.Metadata == nil { + continue + } + items = append(items, HelmChartListItem{ + Name: name, + Title: name, + Description: latest.Description, + Icon: latest.Icon, + Version: latest.Version, + AppVersion: latest.AppVersion, + Deprecated: latest.Deprecated, + Source: "repository", + PackageType: "helm", + RepoName: parsed.Host, + RepoURL: trimmed, + ChartName: name, + Categories: latest.Keywords, + }) + } + + sort.SliceStable(items, func(i, j int) bool { + return strings.ToLower(items[i].Name) < strings.ToLower(items[j].Name) + }) + return items, nil +} + +// GetHelmRepoChartVersions returns all versions of one chart in a repository. +func GetHelmRepoChartVersions(repoURL, chartName string) ([]string, error) { + trimmed := strings.TrimRight(strings.TrimSpace(repoURL), "/") + chartName = strings.TrimSpace(chartName) + if trimmed == "" || chartName == "" { + return nil, fmt.Errorf("repoUrl and chart are required") + } + + body, _, err := httpGet(trimmed+"/index.yaml", helmRepoTimeout, "application/yaml") + if err != nil { + return nil, err + } + + index := &repo.IndexFile{} + if err := yaml.Unmarshal(body, index); err != nil { + return nil, fmt.Errorf("parse repository index: %w", err) + } + + entries, ok := index.Entries[chartName] + if !ok { + return nil, fmt.Errorf("chart %q not found in repository", chartName) + } + + versions := make([]string, 0, len(entries)) + for _, entry := range entries { + if entry != nil && entry.Metadata != nil && entry.Version != "" { + versions = append(versions, entry.Version) + } + } + return versions, nil +} diff --git a/object/helm_test.go b/object/helm_test.go new file mode 100644 index 00000000..03818687 --- /dev/null +++ b/object/helm_test.go @@ -0,0 +1,210 @@ +package object + +import ( + "os" + "strings" + "testing" +) + +func TestParseHelmValues(t *testing.T) { + cases := []struct { + name string + input string + wantErr bool + check func(t *testing.T, values map[string]interface{}) + }{ + { + name: "empty input yields empty map", + input: " ", + check: func(t *testing.T, values map[string]interface{}) { + if len(values) != 0 { + t.Fatalf("expected empty map, got %v", values) + } + }, + }, + { + name: "json object", + input: `{"replicaCount": 2, "image": {"tag": "1.2.3"}}`, + check: func(t *testing.T, values map[string]interface{}) { + image, ok := values["image"].(map[string]interface{}) + if !ok { + t.Fatalf("expected nested image map, got %T", values["image"]) + } + if image["tag"] != "1.2.3" { + t.Fatalf("expected image.tag=1.2.3, got %v", image["tag"]) + } + }, + }, + { + name: "yaml object", + input: "replicaCount: 3\nservice:\n type: NodePort\n", + check: func(t *testing.T, values map[string]interface{}) { + service, ok := values["service"].(map[string]interface{}) + if !ok { + t.Fatalf("expected nested service map, got %T", values["service"]) + } + if service["type"] != "NodePort" { + t.Fatalf("expected service.type=NodePort, got %v", service["type"]) + } + }, + }, + { + name: "list is rejected", + input: "- a\n- b\n", + wantErr: true, + }, + { + name: "scalar is rejected", + input: "just-a-string", + wantErr: true, + }, + { + name: "malformed yaml is rejected", + input: "foo: [unclosed", + wantErr: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + values, err := ParseHelmValues(tc.input) + if tc.wantErr { + if err == nil { + t.Fatalf("expected error for input %q", tc.input) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if values == nil { + t.Fatal("expected non-nil values map") + } + if tc.check != nil { + tc.check(t, values) + } + }) + } +} + +func TestHelmChartRefNormalize(t *testing.T) { + cases := []struct { + name string + ref HelmChartRef + wantChart string + wantRepo string + wantOCI bool + wantErr bool + }{ + { + name: "classic http repository", + ref: HelmChartRef{RepoURL: "https://charts.bitnami.com/bitnami", Chart: "redis"}, + wantChart: "redis", + wantRepo: "https://charts.bitnami.com/bitnami", + }, + { + name: "oci repo url with chart name already included", + ref: HelmChartRef{RepoURL: "oci://registry-1.docker.io/cloudpirates/nginx", Chart: "nginx"}, + wantChart: "oci://registry-1.docker.io/cloudpirates/nginx", + wantOCI: true, + }, + { + name: "oci repo url without chart suffix", + ref: HelmChartRef{RepoURL: "oci://registry-1.docker.io/bitnamicharts", Chart: "redis"}, + wantChart: "oci://registry-1.docker.io/bitnamicharts/redis", + wantOCI: true, + }, + { + name: "oci passed through chart field", + ref: HelmChartRef{Chart: "oci://ghcr.io/some/chart"}, + wantChart: "oci://ghcr.io/some/chart", + wantOCI: true, + }, + { + name: "missing repo url", + ref: HelmChartRef{Chart: "redis"}, + wantErr: true, + }, + { + name: "missing chart name", + ref: HelmChartRef{RepoURL: "https://charts.bitnami.com/bitnami"}, + wantErr: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + if got := tc.ref.IsOCI(); got != tc.wantOCI { + t.Fatalf("IsOCI() = %v, want %v", got, tc.wantOCI) + } + chartRef, repoURL, err := tc.ref.Normalize() + if tc.wantErr { + if err == nil { + t.Fatal("expected error") + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if chartRef != tc.wantChart { + t.Fatalf("chartRef = %q, want %q", chartRef, tc.wantChart) + } + if repoURL != tc.wantRepo { + t.Fatalf("repoURL = %q, want %q", repoURL, tc.wantRepo) + } + }) + } +} + +func TestValidateHelmReleaseName(t *testing.T) { + if err := ValidateHelmReleaseName("my-release"); err != nil { + t.Fatalf("expected valid release name, got %v", err) + } + for _, name := range []string{"", " ", "Invalid_Name", strings.Repeat("a", 100)} { + if err := ValidateHelmReleaseName(name); err == nil { + t.Fatalf("expected error for release name %q", name) + } + } +} + +// TestHelmRenderChartE2E downloads a real chart and renders it locally with +// Helm's dry-run engine. It needs network access but no Kubernetes cluster. +// Enable with CASOS_HELM_E2E=1. +func TestHelmRenderChartE2E(t *testing.T) { + if os.Getenv("CASOS_HELM_E2E") != "1" { + t.Skip("set CASOS_HELM_E2E=1 to run the Helm chart rendering smoke test") + } + + // charts.jetstack.io serves chart archives from the same host as its + // index.yaml, which keeps the smoke test independent from GitHub and + // Docker Hub connectivity. + ref := HelmChartRef{ + RepoURL: "https://charts.jetstack.io", + Chart: "cert-manager", + } + + info, err := HelmShowChart(ref) + if err != nil { + t.Fatalf("HelmShowChart failed: %v", err) + } + if info.Name == "" || info.Version == "" { + t.Fatalf("expected chart metadata, got %+v", info) + } + if strings.TrimSpace(info.DefaultValues) == "" { + t.Fatal("expected non-empty default values") + } + + values, err := ParseHelmValues(info.DefaultValues) + if err != nil { + t.Fatalf("default values are not parseable: %v", err) + } + + manifest, err := HelmRenderChart(ref, "default", "casos-smoke", values) + if err != nil { + t.Fatalf("HelmRenderChart failed: %v", err) + } + if !strings.Contains(manifest, "kind: Service") { + t.Fatalf("rendered manifest looks wrong, first 200 chars: %.200s", manifest) + } +} diff --git a/routers/router.go b/routers/router.go index 9463389d..4c91afe2 100644 --- a/routers/router.go +++ b/routers/router.go @@ -125,6 +125,15 @@ func InitAPI() { beego.Router("/api/deploy-app", &controllers.ApiController{}, "POST:DeployApp") beego.Router("/api/get-app-templates", &controllers.ApiController{}, "GET:GetAppTemplates") + beego.Router("/api/search-helm-charts", &controllers.ApiController{}, "GET:SearchHelmCharts") + beego.Router("/api/get-helm-repo-charts", &controllers.ApiController{}, "GET:GetHelmRepoCharts") + beego.Router("/api/get-helm-chart-info", &controllers.ApiController{}, "GET:GetHelmChartInfo") + beego.Router("/api/get-helm-releases", &controllers.ApiController{}, "GET:GetHelmReleases") + beego.Router("/api/get-helm-task", &controllers.ApiController{}, "GET:GetHelmTask") + beego.Router("/api/install-helm-chart", &controllers.ApiController{}, "POST:InstallHelmChart") + beego.Router("/api/upgrade-helm-release", &controllers.ApiController{}, "POST:UpgradeHelmRelease") + beego.Router("/api/uninstall-helm-release", &controllers.ApiController{}, "POST:UninstallHelmRelease") + beego.Router("/api/get-networkpolicies", &controllers.ApiController{}, "GET:GetNetworkPolicies") beego.Router("/api/get-networkpolicy", &controllers.ApiController{}, "GET:GetNetworkPolicy") beego.Router("/api/add-networkpolicy", &controllers.ApiController{}, "POST:AddNetworkPolicy") diff --git a/web/src/AppStorePage.js b/web/src/AppStorePage.js index bc2b4d0e..e49b12c4 100644 --- a/web/src/AppStorePage.js +++ b/web/src/AppStorePage.js @@ -1,16 +1,29 @@ -import React, {useEffect, useState} from "react"; -import {Alert, Button, Card, Col, Input, Row, Spin, Tag, Typography} from "antd"; -import {ReloadOutlined, RocketOutlined} from "@ant-design/icons"; +import React, {useCallback, useEffect, useRef, useState} from "react"; +import {Alert, Button, Card, Col, Input, Pagination, Row, Spin, Tabs, Tag, Typography} from "antd"; +import {ReloadOutlined, RocketOutlined, SearchOutlined} from "@ant-design/icons"; import {useTranslation} from "react-i18next"; import * as AppBackend from "./backend/AppBackend"; import DeployAppModal from "./DeployAppModal"; const {Title, Paragraph, Text} = Typography; +function isHelmTemplate(template) { + return template?.packageType === "helm"; +} + +function getSourceLabel(template, t) { + if (template?.source === "artifacthub") {return "ArtifactHub";} + if (template?.source === "sealos") {return "Sealos";} + if (template?.source === "repository") {return t("appStore:Custom repository");} + return template?.source || ""; +} + function AppCard({template, onDeploy}) { const {t} = useTranslation(); const [imgErr, setImgErr] = useState(false); - const category = template.categories?.[0] ?? ""; + const category = template.categories?.find(c => c && c !== "Helm") ?? ""; + const sourceLabel = getSourceLabel(template, t); + const isHelm = isHelmTemplate(template); return (
{template.title} + {sourceLabel && {sourceLabel}} + {isHelm && {t("appStore:Helm")}} {category && {category}}
+ {isHelm && (template.repoName || template.chartName || template.version) && ( +
+ {template.repoName && {template.repoName}} + {template.chartName && {template.chartName}} + {template.version && v{template.version}} +
+ )} + {template.ports?.length > 0 && (
{template.ports.map(p => ( @@ -66,16 +89,56 @@ function AppCard({template, onDeploy}) { ); } -function AppStorePage() { +function templateKey(tpl) { + return `${tpl.source || "app"}-${tpl.repoUrl || ""}-${tpl.chartName || tpl.name}-${tpl.version || ""}`; +} + +function CardGrid({items, onDeploy, emptyText}) { + return ( + + {items.map(tpl => ( + + + + ))} + {items.length === 0 && ( + + + {emptyText} + + + )} + + ); +} + +// helmItemToTemplate converts a backend HelmChartListItem into the template +// shape shared with DeployAppModal. +function helmItemToTemplate(item) { + return { + name: item.chartName, + title: item.title || item.chartName, + description: item.description, + icon: item.icon, + categories: item.categories ?? [], + source: item.source, + packageType: item.packageType || "helm", + repoName: item.repoName, + repoUrl: item.repoUrl, + chartName: item.chartName, + version: item.version, + }; +} + +function FeaturedTab({onDeploy}) { const {t} = useTranslation(); const [templates, setTemplates] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [search, setSearch] = useState(""); const [activeCategory, setActiveCategory] = useState(null); - const [deployTarget, setDeployTarget] = useState(null); - const fetchTemplates = () => { + const fetchTemplates = useCallback(() => { setLoading(true); setError(null); AppBackend.getAppTemplates() @@ -88,111 +151,293 @@ function AppStorePage() { }) .catch(e => setError(e.message)) .finally(() => setLoading(false)); - }; + }, []); useEffect(() => { fetchTemplates(); - }, []); + }, [fetchTemplates]); const allCategories = [...new Set( - templates.flatMap(t => t.categories ?? []).filter(Boolean) + templates.flatMap(tp => tp.categories ?? []).filter(Boolean) )].sort(); const filtered = templates.filter(tpl => { + const keyword = search.toLowerCase(); const matchSearch = !search - || tpl.title?.toLowerCase().includes(search.toLowerCase()) - || tpl.description?.toLowerCase().includes(search.toLowerCase()) - || tpl.name?.toLowerCase().includes(search.toLowerCase()); + || tpl.title?.toLowerCase().includes(keyword) + || tpl.description?.toLowerCase().includes(keyword) + || tpl.name?.toLowerCase().includes(keyword) + || tpl.chartName?.toLowerCase().includes(keyword) + || tpl.repoName?.toLowerCase().includes(keyword); const matchCat = !activeCategory || (tpl.categories ?? []).includes(activeCategory); return matchSearch && matchCat; }); - return ( -
-
- {t("general:App Store")} - - {t("appStore:App Store desc")} - {!loading && templates.length > 0 && ( - {t("appStore:app count", {count: templates.length})} - )} - -
+ if (error) { + return ( + } onClick={fetchTemplates}> + {t("appStore:Retry")} + + } + /> + ); + } - {error && ( - } onClick={fetchTemplates}> - {t("appStore:Retry")} - - } + return ( + <> +
+ setSearch(e.target.value)} + style={{width: 220}} + allowClear /> - )} - - {!error && ( -
- setSearch(e.target.value)} - style={{width: 220}} - allowClear - /> - {allCategories.length > 0 && ( -
+ {allCategories.length > 0 && ( +
+ setActiveCategory(null)} + > + {t("appStore:All")} + + {allCategories.map(cat => ( setActiveCategory(null)} + onClick={() => setActiveCategory(activeCategory === cat ? null : cat)} > - {t("appStore:All")} + {cat} - {allCategories.map(cat => ( - setActiveCategory(activeCategory === cat ? null : cat)} - > - {cat} - - ))} -
- )} - -
- )} + ))} +
+ )} + +
{loading ? (
-
- {t("appStore:Loading")} -
+
{t("appStore:Loading")}
) : ( - - {filtered.map(tpl => ( - - setDeployTarget(t2)} /> - - ))} - {filtered.length === 0 && ( - - - {t("appStore:No results")} - - - )} - + + )} + + ); +} + +function ArtifactHubTab({onDeploy}) { + const {t} = useTranslation(); + const [items, setItems] = useState([]); + const [total, setTotal] = useState(0); + const [page, setPage] = useState(1); + const [pageSize] = useState(24); + const [query, setQuery] = useState(""); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const debounceRef = useRef(null); + + const fetchCharts = useCallback((q, p) => { + setLoading(true); + setError(null); + AppBackend.searchHelmCharts(q, p, pageSize) + .then(res => { + if (res.status === "ok") { + setItems(res.data?.items ?? []); + setTotal(res.data?.total ?? 0); + } else { + setError(res.msg); + } + }) + .catch(e => setError(e.message)) + .finally(() => setLoading(false)); + }, [pageSize]); + + useEffect(() => { + fetchCharts("", 1); + }, [fetchCharts]); + + const handleQueryChange = value => { + setQuery(value); + setPage(1); + if (debounceRef.current) { + clearTimeout(debounceRef.current); + } + debounceRef.current = setTimeout(() => fetchCharts(value, 1), 400); + }; + + const handlePageChange = p => { + setPage(p); + fetchCharts(query, p); + }; + + return ( + <> +
+ } + placeholder={t("appStore:Search all charts")} + value={query} + onChange={e => handleQueryChange(e.target.value)} + style={{width: 280}} + allowClear + /> + {total > 0 && ( + {t("appStore:chart count", {count: total})} + )} +
+ + {error && } + + + + + + {total > pageSize && ( +
+ +
)} + + ); +} + +function CustomRepoTab({onDeploy}) { + const {t} = useTranslation(); + const [repoUrl, setRepoUrl] = useState(""); + const [items, setItems] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [loaded, setLoaded] = useState(false); + const [search, setSearch] = useState(""); + + const fetchRepo = () => { + const trimmed = repoUrl.trim(); + if (!trimmed) {return;} + setLoading(true); + setError(null); + AppBackend.getHelmRepoCharts(trimmed) + .then(res => { + if (res.status === "ok") { + setItems(res.data ?? []); + setLoaded(true); + } else { + setError(res.msg); + } + }) + .catch(e => setError(e.message)) + .finally(() => setLoading(false)); + }; + + const filtered = items.filter(item => { + if (!search) {return true;} + const keyword = search.toLowerCase(); + return item.name?.toLowerCase().includes(keyword) + || item.description?.toLowerCase().includes(keyword); + }); + + return ( + <> + +
+ setRepoUrl(e.target.value)} + onPressEnter={fetchRepo} + style={{width: 360}} + allowClear + /> + + {loaded && ( + setSearch(e.target.value)} + style={{width: 200}} + allowClear + /> + )} +
+ + {error && } + + + {loaded && ( + + )} + + + ); +} + +function AppStorePage() { + const {t} = useTranslation(); + const [deployTarget, setDeployTarget] = useState(null); + + const onDeploy = tpl => setDeployTarget(tpl); + + return ( +
+
+ {t("general:App Store")} + + {t("appStore:App Store desc")} + +
+ + , + }, + { + key: "artifacthub", + label: "ArtifactHub", + children: , + }, + { + key: "custom", + label: t("appStore:Custom repository"), + children: , + }, + ]} + /> e.name).map(e => ({name: e.name, value: e.value ?? ""})); } @@ -65,15 +76,44 @@ class DeployAppModal extends React.Component { submitting: false, result: null, error: null, + // Helm-specific state + chartInfoLoading: false, + chartInfo: null, + availableVersions: [], + selectedVersion: "", + helmTask: null, + taskId: null, }; this.formRef = React.createRef(); + this.pollTimer = null; } componentDidUpdate(prevProps) { if (this.props.open && !prevProps.open && this.props.template) { - this.setState({result: null, error: null, envVars: []}); + this.setState({ + result: null, error: null, envVars: [], + chartInfo: null, availableVersions: [], selectedVersion: this.props.template?.version ?? "", + helmTask: null, taskId: null, + }); this.fetchNamespaces(); this.fetchNodeIP(); + if (isHelmTemplate(this.props.template)) { + this.fetchChartInfo(this.props.template.version); + } + } + if (!this.props.open && prevProps.open) { + this.stopPolling(); + } + } + + componentWillUnmount() { + this.stopPolling(); + } + + stopPolling() { + if (this.pollTimer) { + clearInterval(this.pollTimer); + this.pollTimer = null; } } @@ -117,10 +157,91 @@ class DeployAppModal extends React.Component { }).catch(() => {}); } + fetchChartInfo(version) { + const tpl = this.props.template; + if (!tpl) {return;} + this.setState({chartInfoLoading: true, error: null}); + AppBackend.getHelmChartInfo({ + repoUrl: tpl.repoUrl, + chart: tpl.chartName, + version: version ?? "", + source: tpl.source, + repoName: tpl.repoName, + }).then(res => { + if (res.status === "ok") { + const info = res.data?.info ?? null; + const versions = res.data?.availableVersions ?? []; + this.setState({ + chartInfo: info, + availableVersions: versions, + selectedVersion: version || info?.version || "", + }); + setTimeout(() => { + this.formRef.current?.setFieldsValue({helmValues: info?.defaultValues ?? ""}); + }, 0); + } else { + this.setState({error: res.msg}); + } + }).catch(e => this.setState({error: e.message})) + .finally(() => this.setState({chartInfoLoading: false})); + } + + handleVersionChange(version) { + this.setState({selectedVersion: version}); + this.fetchChartInfo(version); + } + + startPolling(taskId) { + this.stopPolling(); + this.pollTimer = setInterval(() => { + AppBackend.getHelmTask(taskId).then(res => { + if (res.status !== "ok") { + this.stopPolling(); + this.setState({error: res.msg, submitting: false}); + return; + } + const task = res.data; + this.setState({helmTask: task}); + if (task.status === "succeeded" || task.status === "failed") { + this.stopPolling(); + this.setState({ + submitting: false, + result: task.status === "succeeded" ? {helm: task.result ?? {}} : null, + error: task.status === "failed" ? task.error : null, + }); + } + }).catch(() => {}); + }, 2000); + } + handleSubmit() { this.formRef.current?.validateFields().then(values => { const tpl = this.props.template; + if (isHelmTemplate(tpl)) { + const payload = { + namespace: values.namespace, + release: values.name, + repoUrl: tpl?.repoUrl, + chart: tpl?.chartName, + version: this.state.selectedVersion || tpl?.version || "", + values: values.helmValues ?? "", + }; + this.setState({submitting: true, error: null, helmTask: null}); + AppBackend.installHelmChart(payload) + .then(res => { + if (res.status === "ok") { + const taskId = res.data?.taskId; + this.setState({taskId}); + this.startPolling(taskId); + } else { + this.setState({error: res.msg, submitting: false}); + } + }) + .catch(e => this.setState({error: e.message, submitting: false})); + return; + } + // Merge template inputs into env vars (inputs take precedence, empties are skipped) const inputEnvVars = Object.entries(values.inputs ?? {}) .filter(([, v]) => v !== "" && v !== null && v !== undefined) @@ -146,15 +267,65 @@ class DeployAppModal extends React.Component { } }) .catch(e => this.setState({error: e.message})) - .finally(() => this.setState({submitting: false})); + .finally(() => { + if (!isHelmTemplate(tpl)) { + this.setState({submitting: false}); + } + }); }); } handleClose() { - this.setState({result: null, error: null, envVars: []}); + this.stopPolling(); + this.setState({result: null, error: null, envVars: [], helmTask: null, taskId: null}); this.props.onClose?.(); } + renderHelmInfo() { + const {template} = this.props; + if (!isHelmTemplate(template)) {return null;} + const sourceLabel = getSourceLabel(template); + return ( + + {sourceLabel &&
{t("appStore:Source")}: {sourceLabel}
} + {template.repoName &&
{t("appStore:Repository")}: {template.repoName}
} + {template.repoUrl &&
{t("appStore:Repository URL")}: {template.repoUrl}
} + {template.chartName &&
{t("appStore:Chart")}: {template.chartName}
} +
+ )} + /> + ); + } + + renderHelmProgress() { + const {helmTask, submitting} = this.state; + if (!submitting || !helmTask) {return null;} + const logs = helmTask.logs ?? []; + return ( +
+ + + {t("appStore:Installing chart")} + {helmTask.status} + + {logs.length > 0 && ( +
+            {logs.slice(-40).join("\n")}
+          
+ )} +
+ ); + } + renderInputs() { const {template} = this.props; const inputs = template?.inputs ?? []; @@ -197,6 +368,38 @@ class DeployAppModal extends React.Component { renderResult() { const {result, nodeIP} = this.state; if (!result) {return null;} + if (result.helm) { + const helm = result.helm; + return ( + +
{t("appStore:Helm release")} {helm.name} {t("appStore:Helm release installed")}
+
{t("general:Namespaces")}: {helm.namespace}
+ {helm.chart &&
{t("appStore:Chart")}: {helm.chart}
} + {helm.version &&
{t("appStore:Chart version")}: {helm.version}
} + {helm.status &&
{t("general:Status")}: {helm.status}
} + {helm.notes && ( + {t("appStore:Release notes")}, + children: {helm.notes}, + }]} + /> + )} +
+ )} + style={{marginTop: 16}} + /> + ); + } + const svc = result.service; const accessUrls = svc && nodeIP ? (svc.ports ?? []).filter(p => p.nodePort).map(p => `http://${nodeIP}:${p.nodePort}`) @@ -209,7 +412,7 @@ class DeployAppModal extends React.Component { description={
- Deployment {result.deployment.name} {t("appStore:Deployment started")} + Deployment {result.deployment?.name} {t("appStore:Deployment started")}
{accessUrls.length > 0 && (
@@ -238,11 +441,13 @@ class DeployAppModal extends React.Component { render() { const {open, template} = this.props; - const {namespaces, envVars, submitting, result, error} = this.state; + const {namespaces, envVars, submitting, result, error, chartInfoLoading, availableVersions, selectedVersion} = this.state; if (!template) {return null;} const nsOptions = namespaces.map(ns => ({label: ns.name, value: ns.name})); const isDone = !!result; + const helmTemplate = isHelmTemplate(template); + const versionOptions = availableVersions.map(v => ({label: v, value: v})); return ( this.handleClose() : () => this.handleSubmit()} onCancel={() => this.handleClose()} - okText={isDone ? t("appStore:Done") : t("appStore:Deploy")} + okText={isDone ? t("appStore:Done") : (helmTemplate ? t("appStore:Deploy Helm chart") : t("appStore:Deploy"))} + okButtonProps={submitting ? {disabled: true} : {}} cancelButtonProps={isDone ? {style: {display: "none"}} : {}} confirmLoading={submitting} - width={620} + width={680} destroyOnHidden > {error && ( @@ -270,6 +476,7 @@ class DeployAppModal extends React.Component { {!isDone && (
+ {this.renderHelmInfo()} - - - - - - - - - - - {template.ports?.length > 0 && ( - - - {template.ports.map(p => :{p}/TCP)} - - - )} + {helmTemplate ? ( + + {versionOptions.length > 0 && ( + + + + + + + + this.setState({namespace: value}, () => this.fetchReleases())} + /> + +
+ + {error && } + + + `${record.namespace}/${record.name}`} + columns={columns} + dataSource={releases} + loading={loading} + size="middle" + pagination={{pageSize: 20, showSizeChanger: false}} + /> + + + {this.renderTaskLogs()} + + this.handleUpgrade()} + onCancel={() => this.setState({upgradeTarget: null})} + confirmLoading={submitting} + okText={t("helm:Upgrade")} + cancelText={t("general:Cancel")} + width={640} + destroyOnHidden + > + + + + + + + + + + + + + + + + {this.renderTaskLogs()} + + + ); + } +} + +export default HelmReleaseListPage; diff --git a/web/src/ManagementPage.js b/web/src/ManagementPage.js index f515582f..48f0b039 100644 --- a/web/src/ManagementPage.js +++ b/web/src/ManagementPage.js @@ -46,6 +46,7 @@ import SiteEditPage from "./SiteEditPage"; import MachineListPage from "./MachineListPage"; import MachineEditPage from "./MachineEditPage"; import AppStorePage from "./AppStorePage"; +import HelmReleaseListPage from "./HelmReleaseListPage"; import AdmissionPolicyPage from "./AdmissionPolicyPage"; import AuthorizationPolicyPage from "./AuthorizationPolicyPage"; import TrivyScanPage from "./TrivyScanPage"; @@ -58,6 +59,7 @@ function getMenuParentKey(uri) { if (!uri) {return null;} if (uri === "/dashboard" || uri === "/app-store") {return null;} if (uri.includes("/pods") || uri.includes("/deployments") || uri.includes("/statefulsets") || uri.includes("/cronjobs") || uri.includes("/hpas") || uri.includes("/log-search")) {return "/workloads";} + if (uri.includes("/helm-releases")) {return "/workloads";} if (uri.includes("/nodes") || uri.includes("/namespaces") || uri.includes("/serviceaccounts")) {return "/cluster";} if (uri.includes("/configmaps") || uri.includes("/secrets") || uri.includes("/pvcs") || uri.includes("/resourcequotas")) {return "/configuration";} if (uri.includes("/ingresses") || uri.includes("/networkpolicies")) {return "/networking";} @@ -206,6 +208,7 @@ function ManagementPage(props) { Setting.getItem({i18next.t("general:Pods")}, "/pods"), Setting.getItem({i18next.t("general:Cron Jobs")}, "/cronjobs"), Setting.getItem({i18next.t("general:Horizontal Pod Autoscaler")}, "/hpas"), + Setting.getItem({i18next.t("general:Helm Releases")}, "/helm-releases"), Setting.getItem( {i18next.t("general:Log Search")}, "/log-search"), ]), Setting.getItem({i18next.t("general:Cluster")}, "/cluster", , [ @@ -252,6 +255,7 @@ function ManagementPage(props) { } /> } /> } /> + } /> } /> } /> } /> diff --git a/web/src/backend/AppBackend.js b/web/src/backend/AppBackend.js index 22f0effa..a0d9856d 100644 --- a/web/src/backend/AppBackend.js +++ b/web/src/backend/AppBackend.js @@ -19,3 +19,90 @@ export function deployApp(req) { body: JSON.stringify(req), }).then(res => res.json()); } + +export function searchHelmCharts(q, page, pageSize) { + const params = new URLSearchParams({q: q ?? "", page: String(page ?? 1), pageSize: String(pageSize ?? 24)}); + return fetch(`${Setting.ServerUrl}/api/search-helm-charts?${params.toString()}`, { + method: "GET", + credentials: "include", + headers: {"Accept-Language": Setting.getAcceptLanguage()}, + }).then(res => res.json()); +} + +export function getHelmRepoCharts(repoUrl) { + const params = new URLSearchParams({repoUrl}); + return fetch(`${Setting.ServerUrl}/api/get-helm-repo-charts?${params.toString()}`, { + method: "GET", + credentials: "include", + headers: {"Accept-Language": Setting.getAcceptLanguage()}, + }).then(res => res.json()); +} + +export function getHelmChartInfo(req) { + const params = new URLSearchParams({ + repoUrl: req.repoUrl ?? "", + chart: req.chart ?? "", + version: req.version ?? "", + source: req.source ?? "", + repoName: req.repoName ?? "", + }); + return fetch(`${Setting.ServerUrl}/api/get-helm-chart-info?${params.toString()}`, { + method: "GET", + credentials: "include", + headers: {"Accept-Language": Setting.getAcceptLanguage()}, + }).then(res => res.json()); +} + +export function installHelmChart(req) { + return fetch(`${Setting.ServerUrl}/api/install-helm-chart`, { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/json", + "Accept-Language": Setting.getAcceptLanguage(), + }, + body: JSON.stringify(req), + }).then(res => res.json()); +} + +export function upgradeHelmRelease(req) { + return fetch(`${Setting.ServerUrl}/api/upgrade-helm-release`, { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/json", + "Accept-Language": Setting.getAcceptLanguage(), + }, + body: JSON.stringify(req), + }).then(res => res.json()); +} + +export function uninstallHelmRelease(req) { + return fetch(`${Setting.ServerUrl}/api/uninstall-helm-release`, { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/json", + "Accept-Language": Setting.getAcceptLanguage(), + }, + body: JSON.stringify(req), + }).then(res => res.json()); +} + +export function getHelmTask(id) { + const params = new URLSearchParams({id}); + return fetch(`${Setting.ServerUrl}/api/get-helm-task?${params.toString()}`, { + method: "GET", + credentials: "include", + headers: {"Accept-Language": Setting.getAcceptLanguage()}, + }).then(res => res.json()); +} + +export function getHelmReleases(namespace) { + const params = new URLSearchParams({namespace: namespace ?? ""}); + return fetch(`${Setting.ServerUrl}/api/get-helm-releases?${params.toString()}`, { + method: "GET", + credentials: "include", + headers: {"Accept-Language": Setting.getAcceptLanguage()}, + }).then(res => res.json()); +} diff --git a/web/src/locales/en/data.json b/web/src/locales/en/data.json index 05f536ae..46e0b8a7 100644 --- a/web/src/locales/en/data.json +++ b/web/src/locales/en/data.json @@ -4,7 +4,75 @@ "Sign Out": "Sign Out" }, "appStore": { - "Image required": "Image is required" + "All": "All", + "App Store desc": "Browse app templates and Helm charts, then deploy them to your cluster.", + "App config": "App configuration", + "App config desc": "Required template inputs are passed to the app as environment variables.", + "App name": "App name", + "App name pattern": "Use lowercase letters, numbers, and hyphens only", + "App name placeholder": "e.g. my-app", + "App name required": "App name is required", + "Chart": "Chart", + "Chart version": "Chart version", + "ClusterIP desc": "ClusterIP - accessible inside the cluster", + "Custom repository": "Custom repository", + "Custom repository desc": "Enter any Helm repository URL to list and install its charts. OCI charts can be installed directly by their oci:// reference.", + "Deploy": "Deploy", + "Deploy Helm chart": "Install chart", + "Deploy app title": "Deploy {{title}}", + "Deploy success": "Deployment started", + "Deployment started": "has been created.", + "Done": "Done", + "Env vars": "Environment variables", + "Featured": "Featured", + "Helm": "Helm", + "Helm chart": "Helm chart", + "Helm release": "Helm release", + "Helm release installed": "has been installed.", + "Helm values": "Values", + "Helm values desc": "YAML (or JSON) values passed to Helm. Prefilled with the chart defaults.", + "Image required": "Image is required", + "Installing chart": "Installing chart...", + "Latest version": "Latest version", + "Load failed": "Failed to load app templates", + "Load repository": "Load repository", + "Loading": "Loading app templates...", + "No results": "No apps found.", + "NodePort desc": "NodePort - expose on a node port", + "Optional settings": "Optional settings", + "Ports": "Ports", + "Release name": "Release name", + "Release name placeholder": "e.g. my-release", + "Release notes": "Release notes", + "Replicas": "Replicas", + "Repository": "Repository", + "Repository URL": "Repository URL", + "Retry": "Retry", + "Search all charts": "Search all Helm charts", + "Search placeholder": "Search apps", + "Sensitive value hint": "This value looks sensitive and will be entered as a password field.", + "Service info prefix": "Service", + "Service info suffix": "was created:", + "Service type": "Service type", + "Source": "Source", + "app count": "({{count}} apps)", + "chart count": "{{count}} charts found" + }, + "helm": { + "All namespaces": "All namespaces", + "App version": "App version", + "Chart required": "Chart name is required", + "Operation failed": "Helm operation failed", + "Operation succeeded": "Helm operation completed", + "Release": "Release", + "Repository URL required": "Repository URL is required", + "Revision": "Revision", + "Uninstall": "Uninstall", + "Uninstall confirm": "Uninstall release {{name}}? All of its resources will be removed.", + "Upgrade": "Upgrade", + "Upgrade desc": "Provide the chart source to upgrade from. Leave values empty to keep the chart defaults.", + "Upgrade release": "Upgrade {{name}}", + "Version empty hint": "Leave empty to use the latest version" }, "general": { "Access Control": "Access Control", @@ -43,6 +111,7 @@ "General Settings desc": "Basic site information", "HTML title": "HTML title", "HTML title - Tooltip": "Text shown in the browser tab and window title bar for this site", + "Helm Releases": "Helm Releases", "Horizontal Pod Autoscaler": "Horizontal Pod Autoscaler", "Image": "Image", "Image Scan": "Image Scan", diff --git a/web/src/locales/zh/data.json b/web/src/locales/zh/data.json index 592f5226..c3c823fd 100644 --- a/web/src/locales/zh/data.json +++ b/web/src/locales/zh/data.json @@ -4,7 +4,75 @@ "Sign Out": "退出登录" }, "appStore": { - "Image required": "镜像不能为空" + "All": "全部", + "App Store desc": "浏览应用模板与 Helm Chart,并部署到当前集群。", + "App config": "应用配置", + "App config desc": "必填模板参数会作为环境变量传递给应用。", + "App name": "应用名称", + "App name pattern": "只能使用小写字母、数字和连字符", + "App name placeholder": "例如:my-app", + "App name required": "应用名称不能为空", + "Chart": "Chart", + "Chart version": "Chart 版本", + "ClusterIP desc": "ClusterIP - 仅集群内访问", + "Custom repository": "自定义仓库", + "Custom repository desc": "输入任意 Helm 仓库地址即可列出并安装其中的 Chart;OCI Chart 可直接使用 oci:// 引用安装。", + "Deploy": "部署", + "Deploy Helm chart": "安装 Chart", + "Deploy app title": "部署 {{title}}", + "Deploy success": "部署已开始", + "Deployment started": "已创建。", + "Done": "完成", + "Env vars": "环境变量", + "Featured": "推荐", + "Helm": "Helm", + "Helm chart": "Helm Chart", + "Helm release": "Helm Release", + "Helm release installed": "已安装。", + "Helm values": "Values", + "Helm values desc": "传给 Helm 的 YAML(或 JSON)配置,已用 Chart 默认值预填。", + "Image required": "镜像不能为空", + "Installing chart": "正在安装 Chart...", + "Latest version": "最新版本", + "Load failed": "加载应用模板失败", + "Load repository": "加载仓库", + "Loading": "正在加载应用模板...", + "No results": "没有找到应用。", + "NodePort desc": "NodePort - 暴露到节点端口", + "Optional settings": "可选配置", + "Ports": "端口", + "Release name": "Release 名称", + "Release name placeholder": "例如:my-release", + "Release notes": "安装说明", + "Replicas": "副本数", + "Repository": "仓库", + "Repository URL": "仓库地址", + "Retry": "重试", + "Search all charts": "搜索全部 Helm Chart", + "Search placeholder": "搜索应用", + "Sensitive value hint": "该值看起来包含敏感信息,将使用密码输入框。", + "Service info prefix": "服务", + "Service info suffix": "已创建:", + "Service type": "服务类型", + "Source": "来源", + "app count": "({{count}} 个应用)", + "chart count": "共 {{count}} 个 Chart" + }, + "helm": { + "All namespaces": "全部命名空间", + "App version": "应用版本", + "Chart required": "Chart 名称不能为空", + "Operation failed": "Helm 操作失败", + "Operation succeeded": "Helm 操作已完成", + "Release": "Release", + "Repository URL required": "仓库地址不能为空", + "Revision": "版本号", + "Uninstall": "卸载", + "Uninstall confirm": "确认卸载 Release {{name}}?其所有资源都会被删除。", + "Upgrade": "升级", + "Upgrade desc": "填写要升级到的 Chart 来源。values 留空则使用 Chart 默认值。", + "Upgrade release": "升级 {{name}}", + "Version empty hint": "留空表示使用最新版本" }, "general": { "Access Control": "访问控制", @@ -43,6 +111,7 @@ "General Settings desc": "站点基本信息", "HTML title": "HTML标题", "HTML title - Tooltip": "显示在浏览器标签页和窗口标题栏中的文字", + "Helm Releases": "Helm Release", "Horizontal Pod Autoscaler": "水平自动伸缩", "Image": "镜像", "Image Scan": "镜像扫描",