Cron job clean (#1862)

* created roadmap and yaml claude agent

* Update roadmap.md

* chore(deps): bump sigstore/cosign-installer from 3.9.2 to 3.10.0 (#1857)

Bumps [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) from 3.9.2 to 3.10.0.
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](https://github.com/sigstore/cosign-installer/compare/v3.9.2...v3.10.0)

---
updated-dependencies:
- dependency-name: sigstore/cosign-installer
  dependency-version: 3.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump the security group with 2 updates (#1858)

Bumps the security group with 2 updates: [github.com/vmware-tanzu/velero](https://github.com/vmware-tanzu/velero) and [helm.sh/helm/v3](https://github.com/helm/helm).


Updates `github.com/vmware-tanzu/velero` from 1.16.2 to 1.17.0
- [Release notes](https://github.com/vmware-tanzu/velero/releases)
- [Changelog](https://github.com/vmware-tanzu/velero/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vmware-tanzu/velero/compare/v1.16.2...v1.17.0)

Updates `helm.sh/helm/v3` from 3.18.6 to 3.19.0
- [Release notes](https://github.com/helm/helm/releases)
- [Commits](https://github.com/helm/helm/compare/v3.18.6...v3.19.0)

---
updated-dependencies:
- dependency-name: github.com/vmware-tanzu/velero
  dependency-version: 1.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
- dependency-name: helm.sh/helm/v3
  dependency-version: 3.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump helm.sh/helm/v3 from 3.18.6 to 3.19.0 in /examples/sdk/helm-template in the security group (#1859)

chore(deps): bump helm.sh/helm/v3

Bumps the security group in /examples/sdk/helm-template with 1 update: [helm.sh/helm/v3](https://github.com/helm/helm).


Updates `helm.sh/helm/v3` from 3.18.6 to 3.19.0
- [Release notes](https://github.com/helm/helm/releases)
- [Commits](https://github.com/helm/helm/compare/v3.18.6...v3.19.0)

---
updated-dependencies:
- dependency-name: helm.sh/helm/v3
  dependency-version: 3.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: security
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Add cron job support bundle scheduler

Complete implementation with K8s integration:
- pkg/schedule/job.go: Job management and persistence
- pkg/schedule/daemon.go: Real-time scheduler daemon
- pkg/schedule/cli.go: CLI commands (create, list, delete, daemon)
- pkg/schedule/schedule_test.go: Comprehensive unit tests
- cmd/troubleshoot/cli/root.go: CLI integration

* fixing bugbot

* Fix all bugbot errors: auto-update stability, job cooldown timing, and daemon execution

* Deleting Agent

* removed unused flags

* fixing auto-upload

* fixing markdown files

* namespace not required flag for auto collectors to work

* loosened cron job validation

* writes logs to logfile

* fix: resolve autoFromEnv variable scoping issue for CI

- Ensure autoFromEnv variable and its usage are in correct scope
- Fix build errors: declared and not used / undefined variable
- All functionality preserved and tested locally
- Force add to override gitignore

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Noah Campbell <noah.edward.campbell@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
Benjamin Yang
2025-09-30 14:11:15 -05:00
committed by GitHub
parent 84dc8157b9
commit 83e6cffe5f
14 changed files with 3481 additions and 188 deletions

7
.gitignore vendored
View File

@@ -48,3 +48,10 @@ sbom/
# Ignore generated support bundles
*.tar.gz
!testdata/supportbundle/*.tar.gz
# Ignore built binaries
troubleshoot
troubleshoot-test
cmd/troubleshoot/troubleshoot
cmd/*/troubleshoot
support-bundle

File diff suppressed because it is too large Load Diff

View File

@@ -43,21 +43,25 @@ If no arguments are provided, specs are automatically loaded from the cluster by
}
// Auto-update support-bundle unless disabled by flag or env
envAuto := os.Getenv("TROUBLESHOOT_AUTO_UPDATE")
autoFromEnv := true
if envAuto != "" {
if strings.EqualFold(envAuto, "0") || strings.EqualFold(envAuto, "false") {
autoFromEnv = false
// Only run auto-update for the root support-bundle command, not subcommands
if cmd.Name() == "support-bundle" && !cmd.HasParent() {
envAuto := os.Getenv("TROUBLESHOOT_AUTO_UPDATE")
autoFromEnv := true
if envAuto != "" {
if strings.EqualFold(envAuto, "0") || strings.EqualFold(envAuto, "false") {
autoFromEnv = false
}
}
}
if v.GetBool("auto-update") && autoFromEnv {
exe, err := os.Executable()
if err == nil {
_ = updater.CheckAndUpdate(cmd.Context(), updater.Options{
BinaryName: "support-bundle",
CurrentPath: exe,
Printf: func(f string, a ...interface{}) { fmt.Fprintf(os.Stderr, f, a...) },
})
if v.GetBool("auto-update") && autoFromEnv {
exe, err := os.Executable()
if err == nil {
_ = updater.CheckAndUpdate(cmd.Context(), updater.Options{
BinaryName: "support-bundle",
CurrentPath: exe,
Printf: func(f string, a ...interface{}) { fmt.Fprintf(os.Stderr, f, a...) },
})
}
}
}
},
@@ -103,11 +107,13 @@ If no arguments are provided, specs are automatically loaded from the cluster by
cmd.AddCommand(Analyze())
cmd.AddCommand(Redact())
cmd.AddCommand(Diff())
cmd.AddCommand(Schedule())
cmd.AddCommand(UploadCmd())
cmd.AddCommand(util.VersionCmd())
cmd.Flags().StringSlice("redactors", []string{}, "names of the additional redactors to use")
cmd.Flags().Bool("redact", true, "enable/disable default redactions")
cmd.Flags().Bool("interactive", true, "enable/disable interactive mode")
cmd.Flags().Bool("collect-without-permissions", true, "always generate a support bundle, even if it some require additional permissions")
cmd.Flags().StringSliceP("selector", "l", []string{"troubleshoot.sh/kind=support-bundle"}, "selector to filter on for loading additional support bundle specs found in secrets within the cluster")

View File

@@ -0,0 +1,11 @@
package cli
import (
"github.com/replicatedhq/troubleshoot/pkg/schedule"
"github.com/spf13/cobra"
)
// Schedule returns the schedule command for managing scheduled support bundle jobs
func Schedule() *cobra.Command {
return schedule.CLI()
}

View File

@@ -9,7 +9,7 @@ replace github.com/replicatedhq/troubleshoot v0.0.0 => ../../../
require (
github.com/replicatedhq/troubleshoot v0.0.0
helm.sh/helm/v3 v3.18.6
helm.sh/helm/v3 v3.19.0
sigs.k8s.io/yaml v1.6.0
)
@@ -17,20 +17,19 @@ require (
dario.cat/mergo v1.0.2 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
@@ -40,35 +39,35 @@ require (
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.3 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/net v0.43.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.42.0 // indirect
golang.org/x/net v0.44.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/term v0.34.0 // indirect
golang.org/x/text v0.28.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/sys v0.36.0 // indirect
golang.org/x/term v0.35.0 // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/time v0.12.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.33.4 // indirect
k8s.io/apiextensions-apiserver v0.33.4 // indirect
k8s.io/apimachinery v0.33.4 // indirect
k8s.io/client-go v0.33.4 // indirect
k8s.io/api v0.34.1 // indirect
k8s.io/apiextensions-apiserver v0.34.1 // indirect
k8s.io/apimachinery v0.34.1 // indirect
k8s.io/client-go v0.34.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
sigs.k8s.io/controller-runtime v0.21.0 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
sigs.k8s.io/controller-runtime v0.22.1 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
)

View File

@@ -6,8 +6,8 @@ github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
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.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
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/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
@@ -18,12 +18,12 @@ 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/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
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/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
@@ -38,9 +38,8 @@ 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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -71,8 +70,9 @@ github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx
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=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/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/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/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
@@ -90,37 +90,37 @@ github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEV
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -129,22 +129,22 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -162,31 +162,29 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
helm.sh/helm/v3 v3.18.6 h1:S/2CqcYnNfLckkHLI0VgQbxgcDaU3N4A/46E3n9wSNY=
helm.sh/helm/v3 v3.18.6/go.mod h1:L/dXDR2r539oPlFP1PJqKAC1CUgqHJDLkxKpDGrWnyg=
k8s.io/api v0.33.4 h1:oTzrFVNPXBjMu0IlpA2eDDIU49jsuEorGHB4cvKupkk=
k8s.io/api v0.33.4/go.mod h1:VHQZ4cuxQ9sCUMESJV5+Fe8bGnqAARZ08tSTdHWfeAc=
k8s.io/apiextensions-apiserver v0.33.4 h1:rtq5SeXiDbXmSwxsF0MLe2Mtv3SwprA6wp+5qh/CrOU=
k8s.io/apiextensions-apiserver v0.33.4/go.mod h1:mWXcZQkQV1GQyxeIjYApuqsn/081hhXPZwZ2URuJeSs=
k8s.io/apimachinery v0.33.4 h1:SOf/JW33TP0eppJMkIgQ+L6atlDiP/090oaX0y9pd9s=
k8s.io/apimachinery v0.33.4/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/client-go v0.33.4 h1:TNH+CSu8EmXfitntjUPwaKVPN0AYMbc9F1bBS8/ABpw=
k8s.io/client-go v0.33.4/go.mod h1:LsA0+hBG2DPwovjd931L/AoaezMPX9CmBgyVyBZmbCY=
helm.sh/helm/v3 v3.19.0 h1:krVyCGa8fa/wzTZgqw0DUiXuRT5BPdeqE/sQXujQ22k=
helm.sh/helm/v3 v3.19.0/go.mod h1:Lk/SfzN0w3a3C3o+TdAKrLwJ0wcZ//t1/SDXAvfgDdc=
k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM=
k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk=
k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI=
k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc=
k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4=
k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY=
k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8=
sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.22.1 h1:Ah1T7I+0A7ize291nJZdS1CabF/lB4E++WizgV24Eqg=
sigs.k8s.io/controller-runtime v0.22.1/go.mod h1:FwiwRjkRPbiN+zp2QRp7wlTCzbUXxZ/D4OzuQUDwBHY=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
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/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

56
go.mod
View File

@@ -41,7 +41,7 @@ require (
github.com/tj/go-spin v1.1.0
github.com/vishvananda/netlink v1.3.1
github.com/vishvananda/netns v0.0.5
github.com/vmware-tanzu/velero v1.16.2
github.com/vmware-tanzu/velero v1.17.0
go.opentelemetry.io/otel v1.38.0
go.opentelemetry.io/otel/sdk v1.38.0
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67
@@ -63,19 +63,19 @@ require (
require (
cel.dev/expr v0.24.0 // indirect
cloud.google.com/go/auth v0.14.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/monitoring v1.21.2 // indirect
cloud.google.com/go/auth v0.16.2 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.7.0 // indirect
cloud.google.com/go/monitoring v1.24.2 // indirect
dario.cat/mergo v1.0.2 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/Masterminds/squirrel v1.5.4 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect
@@ -97,7 +97,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/sts v1.33.20 // indirect
github.com/aws/smithy-go v1.22.3 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f // 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
@@ -122,7 +122,7 @@ require (
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/go-containerregistry v0.20.3 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/gosuri/uitable v0.0.4 // indirect
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 // indirect
@@ -159,29 +159,29 @@ require (
github.com/x448/float16 v0.8.4 // indirect
github.com/zeebo/errs v1.4.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/tools v0.36.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
k8s.io/component-base v0.34.1 // indirect
k8s.io/kubectl v0.33.3 // indirect
k8s.io/kubectl v0.34.0 // indirect
oras.land/oras-go/v2 v2.6.0 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
)
require (
cloud.google.com/go v0.116.0 // indirect
cloud.google.com/go/iam v1.2.2 // indirect
cloud.google.com/go/storage v1.50.0 // indirect
cloud.google.com/go v0.121.1 // indirect
cloud.google.com/go/iam v1.5.2 // indirect
cloud.google.com/go/storage v1.55.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
@@ -191,7 +191,7 @@ require (
github.com/c9s/goprocinfo v0.0.0-20170724085704-0010a05ce49f // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/containerd/containerd v1.7.27 // indirect
github.com/containerd/containerd v1.7.28 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect
github.com/containers/ocicrypt v1.2.1 // indirect
@@ -217,7 +217,7 @@ require (
github.com/google/btree v1.1.3 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-intervals v0.0.2 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/googleapis/gax-go/v2 v2.14.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
@@ -252,7 +252,7 @@ require (
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
github.com/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/afero v1.15.0 // indirect
@@ -270,14 +270,14 @@ require (
golang.org/x/sys v0.36.0
golang.org/x/term v0.35.0 // indirect
golang.org/x/text v0.29.0
golang.org/x/time v0.11.0 // indirect
google.golang.org/api v0.218.0 // indirect
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect
google.golang.org/grpc v1.72.2 // indirect
golang.org/x/time v0.12.0 // indirect
google.golang.org/api v0.241.0 // indirect
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect
google.golang.org/grpc v1.73.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
helm.sh/helm/v3 v3.18.6
helm.sh/helm/v3 v3.19.0
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
k8s.io/kubelet v0.34.1
k8s.io/metrics v0.34.1

136
go.sum
View File

@@ -1,34 +1,34 @@
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
cloud.google.com/go/auth v0.14.0 h1:A5C4dKV/Spdvxcl0ggWwWEzzP7AZMJSEIgrkngwhGYM=
cloud.google.com/go/auth v0.14.0/go.mod h1:CYsoRL1PdiDuqeQpZE0bP2pnPrGqFcOkI0nldEQis+A=
cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M=
cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc=
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA=
cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY=
cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk=
cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM=
cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc=
cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI=
cloud.google.com/go/monitoring v1.21.2 h1:FChwVtClH19E7pJ+e0xUhJPGksctZNVOk2UhMmblmdU=
cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU=
cloud.google.com/go/storage v1.50.0 h1:3TbVkzTooBvnZsk7WaAQfOsNrdoM8QHusXA1cpk6QJs=
cloud.google.com/go/storage v1.50.0/go.mod h1:l7XeiD//vx5lfqE3RavfmU9yvk5Pp0Zhcv482poyafY=
cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI=
cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io=
cloud.google.com/go v0.121.1 h1:S3kTQSydxmu1JfLRLpKtxRPA7rSrYPRPEUmL/PavVUw=
cloud.google.com/go v0.121.1/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw=
cloud.google.com/go/auth v0.16.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4=
cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=
cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=
cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0=
cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY=
cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4=
cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 h1:Wc1ml6QlJs2BHQ/9Bqu1jiyggbsSjramq2oUmp5WeIo=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
@@ -46,20 +46,20 @@ github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 h1:f2Qw/Ehhimh5uO1fayV0QIW7DShEQqhtUfhYc+cBPlw=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0/go.mod h1:2bIszWvQRlJVmJLiuLhukLImRjKPcYdzzsx6darK02A=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 h1:UQ0AhxogsIRZDkElkblfnwjc3IaltCm2HUMvezQaL7s=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1 h1:oTX4vsorBZo/Zdum6OKPA4o7544hm6smoRv1QjpTwGo=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1/go.mod h1:0wEl7vrAD8mehJyohS9HZy+WyEOaQO2mJx86Cvh93kM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 h1:8nn+rsCvTq9axyEh382S0PFLBeaFwNsT43IrPWzctRU=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1/go.mod h1:viRWSEhtMZqz1rhwmOVKkWl6SwmVowfL9O2YR5gI2PE=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0=
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.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
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/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=
@@ -148,12 +148,12 @@ github.com/cilium/ebpf v0.19.0 h1:Ro/rE64RmFBeA9FGjcTc+KmCeY6jXmryu6FfnzPRIao=
github.com/cilium/ebpf v0.19.0/go.mod h1:fLCgMo3l8tZmAdM3B2XqdFzXBpwkcSTroaVqN08OWVY=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk=
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k=
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
github.com/containerd/containerd v1.7.27 h1:yFyEyojddO3MIGVER2xJLWoCIn+Up4GaHFquP7hsFII=
github.com/containerd/containerd v1.7.27/go.mod h1:xZmPnl75Vc+BLGt4MIfu6bp+fy03gdHAn9bz+FreFR0=
github.com/containerd/containerd v1.7.28 h1:Nsgm1AtcmEh4AHAJ4gGlNSaKgXiNccU270Dnf81FQ3c=
github.com/containerd/containerd v1.7.28/go.mod h1:azUkWcOvHrWvaiUjSQH0fjzuHIwSPg1WL5PshGP4Szs=
github.com/containerd/continuity v0.4.4 h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34PlCpMKII=
github.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
@@ -345,10 +345,10 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0=
github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w=
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.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
@@ -559,8 +559,8 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
@@ -653,8 +653,8 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/vladimirvivien/gexe v0.4.1 h1:W9gWkp8vSPjDoXDu04Yp4KljpVMaSt8IQuHswLDd5LY=
github.com/vladimirvivien/gexe v0.4.1/go.mod h1:3gjgTqE2c0VyHnU5UOIwk7gyNzZDGulPb/DJPgcw64E=
github.com/vmware-tanzu/velero v1.16.2 h1:Zhve1mKtX4n0oVhHwbEOsgB9fjKKwm96HJK4WaV/28o=
github.com/vmware-tanzu/velero v1.16.2/go.mod h1:rGIxqbeVHne/47AMtA8vV0ebeQOzyF7VEullayyTEto=
github.com/vmware-tanzu/velero v1.17.0 h1:b+KLlBG+v1YKogP81nAFix2pgJBTmUrnVlXg+OfB5ao=
github.com/vmware-tanzu/velero v1.17.0/go.mod h1:BJRFKei89hSqrazQKiwv5YhhX871X1W1qPyo5OP09zw=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
@@ -671,14 +671,14 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGhfnFe5lwB0ic1JBVjzhk0w=
go.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk=
go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao=
go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo=
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4=
go.opentelemetry.io/contrib/exporters/autoexport v0.57.0/go.mod h1:EJBheUMttD/lABFyLXhce47Wr6DPWYReCzaZiXadH7g=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls=
@@ -699,8 +699,8 @@ go.opentelemetry.io/otel/exporters/prometheus v0.54.0 h1:rFwzp68QMgtzu9PgP3jm9Xa
go.opentelemetry.io/otel/exporters/prometheus v0.54.0/go.mod h1:QyjcV9qDP6VeK5qPyKETvNjmaaEc7+gqjh4SS0ZYzDU=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 h1:CHXNXwfKWfzS65yrlB2PVds1IBZcdsX8Vepy9of0iRU=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0/go.mod h1:zKU4zUgKiaRxrdovSS2amdM5gOc59slmo/zJwGX+YBg=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0 h1:SZmDnHcgp3zwlPBS2JX2urGYe/jBKEIT6ZedHRUyCz8=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0/go.mod h1:fdWW0HtZJ7+jNpTKUR0GpMEDP69nR8YBJQxNiVCE3jk=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 h1:cC2yDI3IQd0Udsux7Qmq8ToKAx1XCilTQECZ0KDZyTw=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0/go.mod h1:2PD5Ex6z8CFzDbTdOlwyNIUywRr1DN0ospafJM1wJ+s=
go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk=
@@ -797,8 +797,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -813,26 +813,26 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.218.0 h1:x6JCjEWeZ9PFCRe9z0FBrNwj7pB7DOAqT35N+IPnAUA=
google.golang.org/api v0.218.0/go.mod h1:5VGHBAkxrA/8EFjLVEYmMUJ8/8+gWWQ3s4cFH0FxG2M=
google.golang.org/api v0.241.0 h1:QKwqWQlkc6O895LchPEDUSYr22Xp3NCxpQRiWTB6avE=
google.golang.org/api v0.241.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk=
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -867,8 +867,8 @@ 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.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
helm.sh/helm/v3 v3.18.6 h1:S/2CqcYnNfLckkHLI0VgQbxgcDaU3N4A/46E3n9wSNY=
helm.sh/helm/v3 v3.18.6/go.mod h1:L/dXDR2r539oPlFP1PJqKAC1CUgqHJDLkxKpDGrWnyg=
helm.sh/helm/v3 v3.19.0 h1:krVyCGa8fa/wzTZgqw0DUiXuRT5BPdeqE/sQXujQ22k=
helm.sh/helm/v3 v3.19.0/go.mod h1:Lk/SfzN0w3a3C3o+TdAKrLwJ0wcZ//t1/SDXAvfgDdc=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM=
@@ -889,8 +889,8 @@ k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
k8s.io/kubectl v0.33.3 h1:r/phHvH1iU7gO/l7tTjQk2K01ER7/OAJi8uFHHyWSac=
k8s.io/kubectl v0.33.3/go.mod h1:euj2bG56L6kUGOE/ckZbCoudPwuj4Kud7BR0GzyNiT0=
k8s.io/kubectl v0.34.0 h1:NcXz4TPTaUwhiX4LU+6r6udrlm0NsVnSkP3R9t0dmxs=
k8s.io/kubectl v0.34.0/go.mod h1:bmd0W5i+HuG7/p5sqicr0Li0rR2iIhXL0oUyLF3OjR4=
k8s.io/kubelet v0.34.1 h1:doAaTA9/Yfzbdq/u/LveZeONp96CwX9giW6b+oHn4m4=
k8s.io/kubelet v0.34.1/go.mod h1:PtV3Ese8iOM19gSooFoQT9iyRisbmJdAPuDImuccbbA=
k8s.io/kubernetes v1.34.1 h1:F3p8dtpv+i8zQoebZeK5zBqM1g9x1aIdnA5vthvcuUk=

172
pkg/schedule/cli.go Normal file
View File

@@ -0,0 +1,172 @@
package schedule
import (
"fmt"
"os"
"text/tabwriter"
"github.com/spf13/cobra"
)
// CLI creates the schedule command
func CLI() *cobra.Command {
cmd := &cobra.Command{
Use: "schedule",
Short: "Manage scheduled support bundle jobs",
Long: `Create and manage scheduled support bundle collection jobs.
This allows customers to schedule support bundle collection to run automatically
at specified times using standard cron syntax.`,
}
cmd.AddCommand(
createCommand(),
listCommand(),
deleteCommand(),
daemonCommand(),
)
return cmd
}
// createCommand creates the create subcommand
func createCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "create [job-name] --cron [schedule] [--namespace ns]",
Short: "Create a scheduled support bundle job",
Long: `Create a new scheduled job to automatically collect support bundles.
Examples:
# Daily at 2 AM
support-bundle schedule create daily-check --cron "0 2 * * *" --namespace production
# Every 6 hours with auto-discovery and auto-upload to vendor portal
support-bundle schedule create frequent --cron "0 */6 * * *" --namespace app --auto --upload enabled`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
cronSchedule, _ := cmd.Flags().GetString("cron")
namespace, _ := cmd.Flags().GetString("namespace")
auto, _ := cmd.Flags().GetBool("auto")
upload, _ := cmd.Flags().GetString("upload")
if cronSchedule == "" {
return fmt.Errorf("--cron is required")
}
manager, err := NewManager()
if err != nil {
return err
}
job, err := manager.CreateJob(args[0], cronSchedule, namespace, auto, upload)
if err != nil {
return err
}
fmt.Printf("✓ Created scheduled job '%s' (ID: %s)\n", job.Name, job.ID)
fmt.Printf(" Schedule: %s\n", job.Schedule)
fmt.Printf(" Namespace: %s\n", job.Namespace)
if auto {
fmt.Printf(" Auto-discovery: enabled\n")
}
if upload != "" {
fmt.Printf(" Auto-upload: enabled (uploads to vendor portal)\n")
}
fmt.Printf("\n💡 To activate, start the daemon:\n")
fmt.Printf(" support-bundle schedule daemon start\n")
return nil
},
}
cmd.Flags().StringP("cron", "c", "", "Cron expression (required)")
cmd.Flags().StringP("namespace", "n", "", "Kubernetes namespace (optional)")
cmd.Flags().Bool("auto", false, "Enable auto-discovery")
cmd.Flags().String("upload", "", "Enable auto-upload to vendor portal (any non-empty value enables auto-upload)")
cmd.MarkFlagRequired("cron")
return cmd
}
// listCommand creates the list subcommand
func listCommand() *cobra.Command {
return &cobra.Command{
Use: "list",
Short: "List all scheduled jobs",
RunE: func(cmd *cobra.Command, args []string) error {
manager, err := NewManager()
if err != nil {
return err
}
jobs, err := manager.ListJobs()
if err != nil {
return err
}
if len(jobs) == 0 {
fmt.Println("No scheduled jobs found")
return nil
}
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
fmt.Fprintln(w, "NAME\tSCHEDULE\tNAMESPACE\tAUTO\tAUTO-UPLOAD\tRUNS")
for _, job := range jobs {
upload := "none"
if job.Upload != "" {
upload = "enabled"
}
fmt.Fprintf(w, "%s\t%s\t%s\t%t\t%s\t%d\n",
job.Name, job.Schedule, job.Namespace, job.Auto, upload, job.RunCount)
}
return w.Flush()
},
}
}
// deleteCommand creates the delete subcommand
func deleteCommand() *cobra.Command {
return &cobra.Command{
Use: "delete [job-name]",
Short: "Delete a scheduled job",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
manager, err := NewManager()
if err != nil {
return err
}
if err := manager.DeleteJob(args[0]); err != nil {
return err
}
fmt.Printf("✓ Deleted job: %s\n", args[0])
return nil
},
}
}
// daemonCommand creates the daemon subcommand
func daemonCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "daemon",
Short: "Manage scheduler daemon",
}
start := &cobra.Command{
Use: "start",
Short: "Start the scheduler daemon",
Long: "Start the daemon to automatically execute scheduled jobs",
RunE: func(cmd *cobra.Command, args []string) error {
daemon, err := NewDaemon()
if err != nil {
return err
}
return daemon.Start()
},
}
cmd.AddCommand(start)
return cmd
}

342
pkg/schedule/daemon.go Normal file
View File

@@ -0,0 +1,342 @@
package schedule
import (
"fmt"
"log"
"os"
"os/exec"
"os/signal"
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
"time"
)
// Daemon runs scheduled jobs
type Daemon struct {
manager *Manager
running bool
jobMutex sync.Mutex
runningJobs map[string]bool // Track running jobs to prevent concurrent execution
logger *log.Logger
logFile *os.File
}
// NewDaemon creates a new daemon
func NewDaemon() (*Daemon, error) {
manager, err := NewManager()
if err != nil {
return nil, fmt.Errorf("failed to create job manager: %w", err)
}
// Setup persistent logging
homeDir, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("failed to get user home directory: %w", err)
}
logDir := filepath.Join(homeDir, ".troubleshoot")
if err := os.MkdirAll(logDir, 0755); err != nil {
return nil, fmt.Errorf("failed to create log directory %s: %w", logDir, err)
}
logPath := filepath.Join(logDir, "scheduler.log")
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return nil, fmt.Errorf("failed to open log file %s: %w", logPath, err)
}
logger := log.New(logFile, "", log.LstdFlags)
return &Daemon{
manager: manager,
running: false,
runningJobs: make(map[string]bool),
logger: logger,
logFile: logFile,
}, nil
}
// Start starts the daemon to monitor and execute jobs
func (d *Daemon) Start() error {
d.running = true
// Setup signal handling
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
// Ensure signal handling is cleaned up and close log file
defer func() {
signal.Stop(sigChan)
if d.logFile != nil {
d.logFile.Close()
}
}()
d.logInfo("Scheduler daemon started")
d.logInfo("Monitoring scheduled jobs every minute...")
ticker := time.NewTicker(1 * time.Minute)
defer ticker.Stop()
for d.running {
select {
case <-ticker.C:
d.checkAndExecuteJobs()
case sig := <-sigChan:
d.logInfo(fmt.Sprintf("Received signal %v, shutting down...", sig))
d.running = false
}
}
d.logInfo("Scheduler daemon stopped")
return nil
}
// Stop stops the daemon
func (d *Daemon) Stop() {
d.running = false
}
// checkAndExecuteJobs checks for jobs that should run now
func (d *Daemon) checkAndExecuteJobs() {
jobs, err := d.manager.ListJobs()
if err != nil {
d.logError(fmt.Sprintf("Error loading jobs: %v", err))
return
}
now := time.Now()
for _, job := range jobs {
if job == nil {
continue // Skip nil jobs
}
if job.Enabled && d.shouldJobRun(job, now) {
// Check if job is already running to prevent concurrent execution
d.jobMutex.Lock()
if d.runningJobs[job.ID] {
d.jobMutex.Unlock()
continue // Skip if already running
}
d.runningJobs[job.ID] = true
d.jobMutex.Unlock()
go d.executeJob(job)
}
}
}
// shouldJobRun checks if a job should run based on its schedule
func (d *Daemon) shouldJobRun(job *Job, now time.Time) bool {
if job == nil {
return false
}
// Prevent running multiple times in the same minute (avoid duplicates)
// Use 90-second cooldown to ensure we don't run more than once per minute
// even with slight timing variations in the daemon's check cycle
if !job.LastRun.IsZero() && now.Sub(job.LastRun) < 90*time.Second {
return false
}
// Parse cron schedule (minute hour day-of-month month day-of-week)
parts := strings.Fields(job.Schedule)
if len(parts) != 5 {
return false
}
minute := parts[0]
hour := parts[1]
dayOfMonth := parts[2]
month := parts[3]
dayOfWeek := parts[4]
// Check if current time matches all cron fields
if !matchesCronField(minute, now.Minute()) {
return false
}
if !matchesCronField(hour, now.Hour()) {
return false
}
if !matchesCronField(dayOfMonth, now.Day()) {
return false
}
if !matchesCronField(month, int(now.Month())) {
return false
}
// Day of week: Sunday = 0, Monday = 1, etc.
if !matchesCronField(dayOfWeek, int(now.Weekday())) {
return false
}
return true
}
// matchesCronField checks if a cron field matches the current time value
func matchesCronField(field string, currentValue int) bool {
if field == "*" {
return true
}
// Handle */N syntax (e.g., */2 for every 2 minutes)
if strings.HasPrefix(field, "*/") {
intervalStr := strings.TrimPrefix(field, "*/")
if interval, err := strconv.Atoi(intervalStr); err == nil && interval > 0 {
return currentValue%interval == 0
}
return false // Invalid interval format
}
// Handle comma-separated lists (e.g., "1,15,30")
values := strings.Split(field, ",")
for _, val := range values {
val = strings.TrimSpace(val)
if fieldValue, err := strconv.Atoi(val); err == nil {
if currentValue == fieldValue {
return true
}
}
}
return false
}
// findSupportBundleBinary finds the support-bundle binary path
func findSupportBundleBinary() (string, error) {
// First try current directory
if _, err := os.Stat("./support-bundle"); err == nil {
abs, _ := filepath.Abs("./support-bundle")
return abs, nil
}
// Try relative to current binary location
if execPath, err := os.Executable(); err == nil {
supportBundlePath := filepath.Join(filepath.Dir(execPath), "support-bundle")
if _, err := os.Stat(supportBundlePath); err == nil {
return supportBundlePath, nil
}
}
// Try PATH
if path, err := exec.LookPath("support-bundle"); err == nil {
return path, nil
}
return "", fmt.Errorf("support-bundle binary not found")
}
// executeJob runs a support bundle collection
func (d *Daemon) executeJob(job *Job) {
if job == nil {
return
}
// Ensure we mark the job as not running when done
defer func() {
d.jobMutex.Lock()
delete(d.runningJobs, job.ID)
d.jobMutex.Unlock()
}()
d.logInfo(fmt.Sprintf("Executing job: %s", job.Name))
// Build command arguments (no subcommand needed - binary IS support-bundle)
args := []string{}
if job.Namespace != "" {
args = append(args, "--namespace", job.Namespace)
}
if job.Auto {
args = append(args, "--auto")
}
if job.Upload != "" {
args = append(args, "--auto-upload")
// Add license and app flags if available in the future
// if job.LicenseID != "" {
// args = append(args, "--license-id", job.LicenseID)
// }
// if job.AppSlug != "" {
// args = append(args, "--app-slug", job.AppSlug)
// }
}
// Disable auto-update for scheduled jobs
args = append(args, "--auto-update=false")
// Find support-bundle binary
supportBundleBinary, err := findSupportBundleBinary()
if err != nil {
d.logError(fmt.Sprintf("Job failed: %s - cannot find support-bundle binary: %v", job.Name, err))
return
}
// Execute support-bundle command directly with output capture
cmd := exec.Command(supportBundleBinary, args...)
// Capture both stdout and stderr
output, err := cmd.CombinedOutput()
if err != nil {
d.logError(fmt.Sprintf("Job failed: %s - %v", job.Name, err))
if len(output) > 0 {
d.logError(fmt.Sprintf("Command output for %s:\n%s", job.Name, string(output)))
}
return
}
d.logInfo(fmt.Sprintf("Job completed: %s", job.Name))
// Log key information but skip verbose JSON output
if len(output) > 0 {
outputStr := string(output)
// Extract and log only the important parts
if strings.Contains(outputStr, "Successfully uploaded support bundle") {
d.logInfo(fmt.Sprintf("Upload successful for job: %s", job.Name))
}
if strings.Contains(outputStr, "Auto-upload failed:") {
// Log upload failures in detail
lines := strings.Split(outputStr, "\n")
for _, line := range lines {
if strings.Contains(line, "Auto-upload failed:") {
d.logError(fmt.Sprintf("Upload failed for job %s: %s", job.Name, strings.TrimSpace(line)))
}
}
}
if strings.Contains(outputStr, "archivePath") {
// Extract just the archive name
lines := strings.Split(outputStr, "\n")
for _, line := range lines {
if strings.Contains(line, "archivePath") {
d.logInfo(fmt.Sprintf("Archive created for job %s: %s", job.Name, strings.TrimSpace(line)))
break
}
}
}
}
// Update job stats only on success
job.RunCount++
job.LastRun = time.Now()
if err := d.manager.saveJob(job); err != nil {
d.logError(fmt.Sprintf("Warning: Failed to save job statistics for %s: %v", job.Name, err))
}
}
// logInfo logs an info message to both console and file
func (d *Daemon) logInfo(message string) {
fmt.Printf("✓ %s\n", message)
if d.logger != nil {
d.logger.Printf("INFO: %s", message)
}
}
// logError logs an error message to both console and file
func (d *Daemon) logError(message string) {
fmt.Printf("❌ %s\n", message)
if d.logger != nil {
d.logger.Printf("ERROR: %s", message)
}
}

212
pkg/schedule/job.go Normal file
View File

@@ -0,0 +1,212 @@
package schedule
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
// Job represents a scheduled support bundle collection job
type Job struct {
ID string `json:"id"`
Name string `json:"name"`
Schedule string `json:"schedule"` // Cron expression
Namespace string `json:"namespace"`
Auto bool `json:"auto"` // Auto-discovery
Upload string `json:"upload,omitempty"`
Enabled bool `json:"enabled"`
RunCount int `json:"runCount"`
LastRun time.Time `json:"lastRun,omitempty"`
Created time.Time `json:"created"`
}
// Manager handles job operations
type Manager struct {
storageDir string
}
// NewManager creates a new job manager
func NewManager() (*Manager, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("failed to get user home directory: %w", err)
}
storageDir := filepath.Join(homeDir, ".troubleshoot", "scheduled-jobs")
if err := os.MkdirAll(storageDir, 0755); err != nil {
return nil, fmt.Errorf("failed to create storage directory %s: %w", storageDir, err)
}
return &Manager{storageDir: storageDir}, nil
}
// CreateJob creates a new scheduled job
func (m *Manager) CreateJob(name, schedule, namespace string, auto bool, upload string) (*Job, error) {
// Input validation
if strings.TrimSpace(name) == "" {
return nil, fmt.Errorf("job name cannot be empty")
}
// Sanitize job name for filesystem safety
name = strings.TrimSpace(name)
if len(name) > 100 {
return nil, fmt.Errorf("job name too long, maximum 100 characters")
}
// Check for invalid filename characters
invalidChars := []string{"/", "\\", ":", "*", "?", "\"", "<", ">", "|", "\x00"}
for _, char := range invalidChars {
if strings.Contains(name, char) {
return nil, fmt.Errorf("job name contains invalid character: %s", char)
}
}
// Cron validation - check it has 5 parts and basic field validation
if err := validateCronSchedule(schedule); err != nil {
return nil, fmt.Errorf("invalid cron schedule: %w", err)
}
job := &Job{
ID: generateJobID(),
Name: name,
Schedule: schedule,
Namespace: namespace,
Auto: auto,
Upload: upload,
Enabled: true,
Created: time.Now(),
}
if err := m.saveJob(job); err != nil {
return nil, err
}
return job, nil
}
// ListJobs returns all saved jobs
func (m *Manager) ListJobs() ([]*Job, error) {
files, err := filepath.Glob(filepath.Join(m.storageDir, "*.json"))
if err != nil {
return nil, err
}
var jobs []*Job
for _, file := range files {
job, err := m.loadJobFromFile(file)
if err != nil {
continue // Skip invalid files
}
jobs = append(jobs, job)
}
return jobs, nil
}
// GetJob retrieves a job by name or ID
func (m *Manager) GetJob(nameOrID string) (*Job, error) {
jobs, err := m.ListJobs()
if err != nil {
return nil, err
}
for _, job := range jobs {
if job.Name == nameOrID || job.ID == nameOrID {
return job, nil
}
}
return nil, fmt.Errorf("job not found: %s", nameOrID)
}
// DeleteJob removes a job
func (m *Manager) DeleteJob(nameOrID string) error {
job, err := m.GetJob(nameOrID)
if err != nil {
return err
}
jobFile := filepath.Join(m.storageDir, job.ID+".json")
return os.Remove(jobFile)
}
// saveJob saves a job to a JSON file
func (m *Manager) saveJob(job *Job) error {
data, err := json.MarshalIndent(job, "", " ")
if err != nil {
return err
}
jobFile := filepath.Join(m.storageDir, job.ID+".json")
return os.WriteFile(jobFile, data, 0644)
}
// loadJobFromFile loads a job from a JSON file
func (m *Manager) loadJobFromFile(filename string) (*Job, error) {
data, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
var job Job
err = json.Unmarshal(data, &job)
return &job, err
}
// validateCronSchedule performs basic cron schedule validation
func validateCronSchedule(schedule string) error {
parts := strings.Fields(schedule)
if len(parts) != 5 {
return fmt.Errorf("expected 5 fields (minute hour day-of-month month day-of-week), got %d", len(parts))
}
// Validate each field has reasonable values
fieldNames := []string{"minute", "hour", "day-of-month", "month", "day-of-week"}
fieldRanges := [][2]int{{0, 59}, {0, 23}, {1, 31}, {1, 12}, {0, 6}}
for i, field := range parts {
if err := validateCronField(field, fieldRanges[i][0], fieldRanges[i][1], fieldNames[i]); err != nil {
return err
}
}
return nil
}
// validateCronField validates a single cron field
func validateCronField(field string, min, max int, fieldName string) error {
if field == "*" {
return nil
}
// Handle */N syntax
if strings.HasPrefix(field, "*/") {
intervalStr := strings.TrimPrefix(field, "*/")
if interval, err := strconv.Atoi(intervalStr); err != nil || interval <= 0 {
return fmt.Errorf("invalid %s interval: %s", fieldName, intervalStr)
}
return nil
}
// Handle exact values (including comma-separated lists)
values := strings.Split(field, ",")
for _, val := range values {
val = strings.TrimSpace(val)
if fieldValue, err := strconv.Atoi(val); err != nil {
return fmt.Errorf("invalid %s value: %s", fieldName, val)
} else if fieldValue < min || fieldValue > max {
return fmt.Errorf("%s value %d out of range [%d-%d]", fieldName, fieldValue, min, max)
}
}
return nil
}
// generateJobID generates a simple job ID
func generateJobID() string {
return fmt.Sprintf("job-%d", time.Now().UnixNano())
}

View File

@@ -0,0 +1,124 @@
package schedule
import (
"fmt"
"os"
"testing"
"time"
)
func TestManager_CreateJob(t *testing.T) {
// Use temporary directory for testing
tempDir, err := os.MkdirTemp("", "schedule-test")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(tempDir)
manager := &Manager{storageDir: tempDir}
// Test job creation
job, err := manager.CreateJob("test-job", "0 2 * * *", "default", true, "s3://bucket")
if err != nil {
t.Fatalf("CreateJob failed: %v", err)
}
if job.Name != "test-job" {
t.Errorf("Job name = %s, want test-job", job.Name)
}
if job.Schedule != "0 2 * * *" {
t.Errorf("Schedule = %s, want 0 2 * * *", job.Schedule)
}
if !job.Enabled {
t.Error("Job should be enabled by default")
}
}
func TestManager_ListJobs(t *testing.T) {
tempDir, err := os.MkdirTemp("", "schedule-test")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(tempDir)
manager := &Manager{storageDir: tempDir}
// Create test jobs
_, err = manager.CreateJob("job1", "0 1 * * *", "ns1", false, "")
if err != nil {
t.Fatalf("CreateJob failed: %v", err)
}
_, err = manager.CreateJob("job2", "0 2 * * *", "ns2", true, "s3://bucket")
if err != nil {
t.Fatalf("CreateJob failed: %v", err)
}
// List jobs
jobs, err := manager.ListJobs()
if err != nil {
t.Fatalf("ListJobs failed: %v", err)
}
if len(jobs) != 2 {
t.Errorf("Expected 2 jobs, got %d", len(jobs))
}
}
func TestManager_DeleteJob(t *testing.T) {
tempDir, err := os.MkdirTemp("", "schedule-test")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(tempDir)
manager := &Manager{storageDir: tempDir}
// Create and delete job
job, err := manager.CreateJob("temp-job", "0 3 * * *", "default", false, "")
if err != nil {
t.Fatalf("CreateJob failed: %v", err)
}
err = manager.DeleteJob(job.Name)
if err != nil {
t.Fatalf("DeleteJob failed: %v", err)
}
// Verify deletion
jobs, err := manager.ListJobs()
if err != nil {
t.Fatalf("ListJobs failed: %v", err)
}
if len(jobs) != 0 {
t.Errorf("Expected 0 jobs after deletion, got %d", len(jobs))
}
}
func TestDaemon_ScheduleMatching(t *testing.T) {
daemon, err := NewDaemon()
if err != nil {
t.Fatalf("NewDaemon failed: %v", err)
}
// Test job that should run at current minute
now := time.Now()
job := &Job{
Schedule: fmt.Sprintf("%d %d * * *", now.Minute(), now.Hour()),
LastRun: time.Time{}, // Never run
Enabled: true,
}
if !daemon.shouldJobRun(job, now) {
t.Error("Job should run at current time")
}
// Test job that just ran
job.LastRun = now.Add(-25 * time.Second)
if daemon.shouldJobRun(job, now) {
t.Error("Job should not run again so soon")
}
}

621
roadmap.md Normal file
View File

@@ -0,0 +1,621 @@
### Phased execution plan (actionable)
1) Foundation & policy (cross-cutting)
• Goal: Establish non-negotiable engineering charters, error taxonomy, deterministic I/O, and output envelope.
• Do:
• Adopt items under “Cross-cutting engineering charters”.
• Implement centralized error codes (see “1) Error codes (centralized)”).
• Implement JSON output envelope (see “2) Output envelope (JSON mode)”).
• Add idempotency key helper (see “3) Idempotency key”).
• Ensure deterministic marshaling patterns (see “4) Deterministic marshaling”).
• Define config precedence and env aliases (see section E) Config precedence & env aliases).
• Add Make targets (see section F) Make targets).
• Acceptance:
• “Measurable add-on success criteria” items related to CLI output and determinism are satisfied.
2) Distribution & updates (installers, signing, updater)
• Goal: Stop krew; ship Homebrew and curl|bash installers; add secure update with rollback.
• Do:
• Remove/retire krew guidance; add Homebrew formulas and curl|bash script(s).
• Implement “C) Update system (secure + rollback)” including channels, rollback, tamper defense, delta updates (optional later).
• Implement “Reproducible, signed, attestable releases” (SBOM, cosign, SLSA, SOURCE_DATE_EPOCH).
• Add minimal packaging matrix validation for brew and curl|bash; expand later (see D) Packaging matrix validation (CI)).
• Acceptance:
• Users can install preflight and support-bundle via brew and curl|bash.
• Updater supports --channel, verify, rollback; signatures verified per roadmap details.
3) API v1beta3 schemas and libraries
• Goal: Define and own v1beta3 JSON Schemas and supporting defaulting/validation/conversion libraries within performance budgets.
• Do:
• Implement “API v1beta3 & schema work (deeper)” sections AD (JSON Schema strategy; defaulting; validation; performance budget).
• Add converters and fuzzers per “C) Converters robustness”.
• Benchmarks per “D) Performance budget”.
• Acceptance:
• Schemas published under schemas.troubleshoot.sh/v1beta3/* with $id, $schema, $defs.
• Validation/defaulting return structured errors; fuzz and perf budgets pass.
4) Preflight requirements disclosure command
• Goal: Let customers preview requirements offline; render table/json/yaml/md; support templating values.
• Do:
• Implement “Preflight requirements disclosure (new command)” (`preflight requirements`), including flags and behaviors.
• Implement templating from “Preflight CLI: Values and --set support (templating)”.
• Acceptance:
• Output validates against docs/preflight-requirements.schema.json and renders within width targets.
• Unit and golden tests for table/json/md; fuzz tests for extractor stability.
5) Docs generator and portal gate/override
• Goal: Generate preflight docs with rationale and support portal gate/override flow.
• Do:
• Implement “Preflight docs & portal flow (hardening)” sections AD (merge engine, docs generator, portal client contract, E2E tests).
• Ensure CLI prints requestId on error; implement backoff/idempotency per contract.
• Acceptance:
• E2E portal tests cover pass/fail/override/429/5xx with retries.
• Docs generator emits MD/HTML with i18n hooks and template slots.
6) Simplified spec model: intents, presets, imports
• Goal: Reduce authoring burden via intents for collect/analyze, redaction profiles with tokenize, and preset/import model.
• Do:
• Implement “Simplified spec model: intents, presets, imports”: intents.collect.auto; intents.analyze.requirements; redact.profile + tokenize; import/extends; selectors/filters; compatibility flags `--emit` and `--explain`.
• Provide examples and downgrade warnings for v1beta2 emit.
• Acceptance:
• Deterministic expansion demonstrated; explain output shows generated low-level spec; downgrade warnings reported where applicable.
7) Public packages & ecosystem factoring
• Goal: Establish stable package boundaries to support reuse and avoid logging in libs.
• Do:
• Create packages listed under “Public packages & ecosystem” (pkg/cli/contract, update, schema, specs/*, docs/render, portal/client).
• Export minimal, stable APIs; return structured errors.
• Acceptance:
• api-diff green or change proposal attached.
8) CI/CD reinforcement
• Goal: End-to-end pipelines for verification, install matrix, benchmarks, supply-chain, and releases.
• Do:
• Implement pipeline stages listed under “CI/CD reinforcement → Pipelines 15”.
• Add static checks (revive/golangci-lint, api-diff rules) per roadmap.
• Acceptance:
• Pipelines green; supply chain artifacts (SBOM, cosign, SLSA) produced; release flow notarizes and publishes.
9) Testing strategy, determinism and performance harness, artifacts layout
• Goal: Comprehensive unit/contract/fuzz/integration tests, deterministic outputs, and curated fixtures.
• Do:
• Implement “Testing strategy (Dev 1 scope)” (unit, contract/golden, fuzz/property, integration/matrix tests).
• Implement “Determinism & performance” harness and budgets.
• Organize artifacts per “Artifacts & layout” and add Make targets for test/fuzz/contracts/e2e/bench.
• Acceptance:
• Golden tests stable; determinism harness passes under SOURCE_DATE_EPOCH; benchmarks within budgets.
10) Packaging matrix expansion (optional later)
• Goal: Expand beyond brew/curl to scoop and deb/rpm when desired.
• Do:
• Extend “D) Packaging matrix validation (CI)” to include scoop and deb/rpm installers and tests across OSes.
• Acceptance:
• Installers validated on ubuntu/macos/windows with smoke commands; macOS notarization verified.
Notes
• Each phase references detailed specifications below. Implement phases in order; parallelize sub-items where safe.
• If scope for an initial milestone is narrower (e.g., brew/curl only), mark the remaining items as deferred but keep tests/docs ready to expand.
### Cross-cutting engineering charters
1) Contract/stability policy (one pager, checked into repo)
• SemVer & windows: major.minor.patch; flags/commands stable for ≥2 minors; deprecations carry --explain-deprecations.
• Breaking-change gate: PR must include contracts/CHANGE_PROPOSAL.md + updated goldens + migration notes.
• Determinism: Same inputs ⇒ byte-identical outputs (normalized map ordering, sorted slices, stable timestamps with SOURCE_DATE_EPOCH).
2) Observability & diagnostics
• Structured logs (zerolog/zap): --log-format {text,json}, --log-level {info,debug,trace}.
• Exit code taxonomy: 0 ok, 1 generic, 2 usage, 3 network, 4 schema, 5 incompatible-api, 6 update-failed, 7 permission, 8 partial-success.
• OTel hooks (behind TROUBLESHOOT_OTEL_ENDPOINT): span “loadSpec”, “mergeSpec”, “runPreflight”, “uploadPortal”.
3) Reproducible, signed, attestable releases
• SBOM (cyclonedx/spdx) emitted by GoReleaser.
• cosign: sign archives + checksums.txt; produce SLSA provenance attestation.
• SOURCE_DATE_EPOCH set in CI to pin archive mtimes.
CLI contracts & packaging (more depth)
A) Machine-readable CLI spec
• Generate docs/cli-contracts.json from Cobra tree (name, synopsis, flags, defaults, env aliases, deprecation).
• Validate at runtime when TROUBLESHOOT_DEBUG_CONTRACT=1 to catch drift in dev builds.
• Use that JSON to:
• Autogenerate shell completions for bash/zsh/fish/pwsh.
• Render the --help text (single source of truth).
B) UX hardening
• TTY detection: progress bars only on TTY; --no-progress to force off.
• Color policy: --color {auto,always,never} + NO_COLOR env respected.
• Output mode: --output {human,json,yaml} for all read commands. For json, include a top-level "schemaVersion": "cli.v1".
C) Update system (secure + rollback)
• Channel support: --channel {stable,rc,nightly} (maps to tags: vX.Y.Z, vX.Y.Z-rc.N, nightly-YYYYMMDD).
• Rollback: keep N=2 previous binaries under ~/.troubleshoot/bin/versions/…; preflight update --rollback.
• Tamper defense: verify cosign sig for checksums.txt; verify SHA256 of selected asset; fail closed with error code 6.
• Delta updates (optional later): if asset .patch exists and base version matches, apply bsdiff; fallback to full.
D) Packaging matrix validation (CI)
• Matrix test on ubuntu-latest, macos-latest, windows-latest:
• Install via brew, scoop, deb/rpm, curl|bash; then run preflight --version and a sample command.
• Gatekeeper: spctl -a -v on macOS; print notarization ticket.
E) Config precedence & env aliases
• Per-binary config paths (defaults):
• macOS/Linux:
• preflight: ~/.config/preflight/config.yaml
• support-bundle: ~/.config/support-bundle/config.yaml
• Windows:
• preflight: %APPDATA%\Troubleshoot\Preflight\config.yaml
• support-bundle: %APPDATA%\Troubleshoot\SupportBundle\config.yaml
• Optional global fallback (lower precedence): ~/.config/troubleshoot/config.yaml
• Precedence: flag > binary env > global env > binary config > global config > default
• --config <path> overrides discovery; respects XDG_CONFIG_HOME (Unix) and APPDATA (Windows)
• Env aliases:
• Global: TROUBLESHOOT_PORTAL_URL, TROUBLESHOOT_API_TOKEN
• Binary-scoped: PREFLIGHT_* and SUPPORT_BUNDLE_* (take precedence over TROUBLESHOOT_*)
F) Make targets
make contracts # regen CLI JSON + goldens
make sbom # build SBOMs
make release-dryrun # goreleaser --skip-publish
make e2e-install # spins a container farm to test deb/rpm
API v1beta3 & schema work (deeper)
A) JSON Schema strategy
• Give every schema an $id and $schema; publish at schemas.troubleshoot.sh/v1beta3/*.json.
• Use $defs for shared primitives (Quantity, Duration, CPUSet, Selector).
• Add x-kubernetes-validations parity constraints where applicable (even if not applying as CRD).
B) Defaulting & validation library
• pkg/validation/validate.go: returns []FieldError with JSONPointer paths and machine codes.
• pkg/defaults/defaults.go: idempotent defaulting; fuzz tests prove no oscillation (fuzz: in -> default -> default == default).
C) Converters robustness
• Fuzzers (go1.20+): generate random v1beta1/2 structs, convert→internal→v1beta3→internal and assert invariants (lossless roundtrips where representable).
• Report downgrade loss: if v1beta3→v1beta2 drops info, print warning list to stderr and annotate output with x-downgrade-warnings.
D) Performance budget
• Load+validate 1MB spec ≤ 150ms p95, 10MB ≤ 800ms p95 on GOARCH=amd64 GitHub runner.
• Benchmarks in pkg/apis/bench_test.go enforce budgets.
E) Simplified spec model: intents, presets, imports
• Problem: vendors handwrite verbose collector/analyzer lists. Goal: smaller, intent-driven specs that expand deterministically.
• Tenets:
• Additive, backwards-compatible; loader can expand intents into concrete v1beta2-equivalent structures.
• Deterministic expansion (same inputs ⇒ same expansion) with --explain to show the generated low-level spec.
• Shorthand over raw lists: “what” not “how”.
• Top-level additions (v1beta3):
• intents.collect.auto: namespace, profiles, includeKinds, excludeKinds, selectors, size caps.
• intents.analyze.requirements: high-level checks (k8sVersion, nodes.cpu/memory, podsReady, storageClass, CRDsPresent…).
• redact.profile + tokenize: standard|strict; optional token map emission.
• import: versioned presets (preset://k8s/basic@v1) with local vendoring.
• extends: URL or preset to inherit from, with override blocks.
• Selectors & filters:
• labelSelector, fieldSelector, name/glob filters; include/exclude precedence clarified in schema docs.
• Compatibility:
• --emit v1beta2 to produce a concrete legacy spec; downgrade warnings if some intent cant fully map.
• --explain prints the expanded collectors/analyzers to aid review and vendoring.
• Example: Preflight with requirements + docs
```yaml
apiVersion: troubleshoot.sh/v1beta3
kind: Preflight
metadata:
name: example
requirements:
- name: Baseline
docString: "Core Kubernetes and cluster requirements."
checks:
- clusterVersion:
checkName: Kubernetes version
outcomes:
- fail:
when: "< 1.20.0"
message: This application requires at least Kubernetes 1.20.0, and recommends 1.22.0.
uri: https://kubernetes.io
- warn:
when: "< 1.22.0"
message: Your cluster meets the minimum version of Kubernetes, but we recommend you update to 1.22.0 or later.
uri: https://kubernetes.io
- pass:
when: ">= 1.22.0"
message: Your cluster meets the recommended and required versions of Kubernetes.
- customResourceDefinition:
checkName: Ingress
customResourceDefinitionName: ingressroutes.contour.heptio.com
outcomes:
- fail:
message: Contour ingress not found!
- pass:
message: Contour ingress found!
- containerRuntime:
outcomes:
- pass:
when: "== containerd"
message: containerd container runtime was found.
- fail:
message: Did not find containerd container runtime.
- storageClass:
checkName: Required storage classes
storageClassName: "default"
outcomes:
- fail:
message: Could not find a storage class called default.
- pass:
message: All good on storage classes
- distribution:
outcomes:
- fail:
when: "== docker-desktop"
message: The application does not support Docker Desktop Clusters
- fail:
when: "== microk8s"
message: The application does not support Microk8s Clusters
- fail:
when: "== minikube"
message: The application does not support Minikube Clusters
- pass:
when: "== eks"
message: EKS is a supported distribution
- pass:
when: "== gke"
message: GKE is a supported distribution
- pass:
when: "== aks"
message: AKS is a supported distribution
- pass:
when: "== kurl"
message: KURL is a supported distribution
- pass:
when: "== digitalocean"
message: DigitalOcean is a supported distribution
- pass:
when: "== rke2"
message: RKE2 is a supported distribution
- pass:
when: "== k3s"
message: K3S is a supported distribution
- pass:
when: "== oke"
message: OKE is a supported distribution
- pass:
when: "== kind"
message: Kind is a supported distribution
- warn:
message: Unable to determine the distribution of Kubernetes
- nodeResources:
checkName: Must have at least 3 nodes in the cluster, with 5 recommended
outcomes:
- fail:
when: "count() < 3"
message: This application requires at least 3 nodes.
uri: https://kurl.sh/docs/install-with-kurl/adding-nodes
- warn:
when: "count() < 5"
message: This application recommends at last 5 nodes.
uri: https://kurl.sh/docs/install-with-kurl/adding-nodes
- pass:
message: This cluster has enough nodes.
- nodeResources:
checkName: Every node in the cluster must have at least 8 GB of memory, with 32 GB recommended
outcomes:
- fail:
when: "min(memoryCapacity) < 8Gi"
message: All nodes must have at least 8 GB of memory.
uri: https://kurl.sh/docs/install-with-kurl/system-requirements
- warn:
when: "min(memoryCapacity) < 32Gi"
message: All nodes are recommended to have at least 32 GB of memory.
uri: https://kurl.sh/docs/install-with-kurl/system-requirements
- pass:
message: All nodes have at least 32 GB of memory.
- nodeResources:
checkName: Total CPU Cores in the cluster is 4 or greater
outcomes:
- fail:
when: "sum(cpuCapacity) < 4"
message: The cluster must contain at least 4 cores
uri: https://kurl.sh/docs/install-with-kurl/system-requirements
- pass:
message: There are at least 4 cores in the cluster
- nodeResources:
checkName: Every node in the cluster must have at least 40 GB of ephemeral storage, with 100 GB recommended
outcomes:
- fail:
when: "min(ephemeralStorageCapacity) < 40Gi"
message: All nodes must have at least 40 GB of ephemeral storage.
uri: https://kurl.sh/docs/install-with-kurl/system-requirements
- warn:
when: "min(ephemeralStorageCapacity) < 100Gi"
message: All nodes are recommended to have at least 100 GB of ephemeral storage.
uri: https://kurl.sh/docs/install-with-kurl/system-requirements
- pass:
message: All nodes have at least 100 GB of ephemeral storage.
{{- if eq .Values.postgres.enabled true }}
- name: Postgres
docString: "Postgres needs a storage class and sufficient memory."
checks:
- storageClass:
checkName: Postgres storage class
name: "{{ .Values.postgres.storageClassName | default \"default\" }}"
required: true
- nodeResources:
checkName: Postgres memory guidance
outcomes:
- fail:
when: "min(memoryCapacity) < 8Gi"
message: All nodes must have at least 8 GB of memory for Postgres.
- warn:
when: "min(memoryCapacity) < 32Gi"
message: Nodes are recommended to have at least 32 GB of memory for Postgres.
- pass:
message: Nodes have sufficient memory for Postgres.
{{- end }}
{{- if eq .Values.redis.enabled true }}
- name: Redis
docString: "Redis needs a storage class and adequate ephemeral storage."
checks:
- storageClass:
checkName: Redis storage class
name: "{{ .Values.redis.storageClassName | default \"default\" }}"
required: true
- nodeResources:
checkName: Redis ephemeral storage
outcomes:
- fail:
when: "min(ephemeralStorageCapacity) < 40Gi"
message: All nodes must have at least 40 GB of ephemeral storage for Redis.
- warn:
when: "min(ephemeralStorageCapacity) < 100Gi"
message: Nodes are recommended to have at least 100 GB of ephemeral storage for Redis.
- pass:
message: Nodes have sufficient ephemeral storage for Redis.
{{- end }}
```
• Presets library:
• Versioned URIs (e.g., preset://k8s/basic@v1, preset://app/logs@v1) maintained in-repo and publishable.
• "troubleshoot vendor --import" downloads presets to ./vendor/troubleshoot/ for offline builds.
Preflight docs & portal flow (hardening)
A) Merge engine details
• Stable key = GroupKind/Name[/Namespace] (e.g., NodeResource/CPU, FilePermission//etc/hosts).
• Conflict detection emits a list with reasons: “same key, differing fields: thresholds.min, description”.
• Provenance captured on each merged node:
• troubleshoot.sh/provenance: vendor|replicated|merged
• troubleshoot.sh/merge-conflict: "thresholds.min, description"
B) Docs generator upgrades
• Template slots: why, riskLevel {low,med,high}, owner, runbookURL, estimatedTime.
• i18n hooks: template lookup by locale --locale es-ES falls back to en-US.
• Output MD + self-contained HTML (inline CSS) when --html. --toc adds a nav sidebar.
C) Portal client contract
• Auth: Bearer <token>; optional mTLS later.
• Idempotency: Idempotency-Key header derived from spec SHA256.
• Backoff: exponential jitter (100ms → 3s, 6 tries) on 429/5xx; code 3 on exhaustion.
• Response model:
{
"requestId": "r_abc123",
"decision": "pass|override|fail",
"reason": "text",
"policyVersion": "2025-09-01"
}
• CLI prints requestId on error for support.
D) E2E tests (httptest.Server)
• Scenarios: pass, fail, override, 429 with retry-after, 5xx flake, invalid JSON.
• Golden transcripts of HTTP exchanges under testdata/e2e/portal.
Public packages & ecosystem
A) Package boundaries
pkg/
cli/contract # cobra->json exporter (no cobra import cycles)
update/ # channel, verify, rollback
schema/ # embed.FS of JSON Schemas + helpers
specs/loader # version sniffing, load any -> internal
specs/convert # converters
specs/validate # validation library
docs/render # md/html generation
portal/client # http client + types
• No logging in libs; return structured errors with codes; callers log.
B) SARIF export (nice-to-have)
• --output sarif for preflight results so CI systems ingest findings.
C) Back-compat façade
• For integrators, add tiny shim: pkg/legacy/v1beta2loader that calls new loader + converter; mark with Deprecated: GoDoc but stable for a window.
CI/CD reinforcement
Pipelines
1. verify: lint, unit, fuzz (short), contracts, schemas → required.
2. matrix-install: brew/scoop/deb/rpm/curl on 3 OSes.
3. bench: enforce perf budgets.
4. supply-chain: build SBOM, cosign sign/verify, slsa attestation.
5. release (tagged): goreleaser publish, notarize, bump brew/scoop, attach SBOM, cosign attest.
Static checks
• revive/golangci-lint with a rule to forbid time.Now() in pure functions; must use injected clock.
• api-diff: compare exported pkg/** against last tag; fails on breaking changes without contracts/CHANGE_PROPOSAL.md.
1) Error codes (centralized)
package xerr
type Code int
const (
OK Code = iota
Usage
Network
Schema
IncompatibleAPI
UpdateFailed
Permission
Partial
)
type E struct { Code Code; Op, Msg string; Err error }
func (e *E) Error() string { return e.Msg }
func CodeOf(err error) Code { /* unwrap */ }
2) Output envelope (JSON mode)
{
"schemaVersion": "cli.v1",
"tool": "preflight",
"version": "1.12.0",
"timestamp": "2025-09-09T17:02:33Z",
"result": { /* command-specific */ },
"warnings": [],
"errors": []
}
3) Idempotency key
func idemKey(spec []byte) string {
sum := sha256.Sum256(spec)
return hex.EncodeToString(sum[:])
}
4) Deterministic marshaling
enc := json.NewEncoder(w)
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
sort.SliceStable(obj.Items, func(i,j int) bool { return obj.Items[i].Name < obj.Items[j].Name })
Measurable add-on success criteria
• preflight --help --output json validates against docs/cli-contracts.schema.json.
• make bench passes with stated p95 budgets.
• cosign verify-blob succeeds for checksums.txt in CI and on dev machines (docd).
• E2E portal tests cover all decision branches and 429/5xx paths with retries observed.
• api-diff is green or has an attached change proposal.
Testing strategy (Dev 1 scope)
Unit tests
• CLI arg parsing: Cobra ExecuteC with table-driven flag sets for both binaries.
• Config precedence resolver: tmp dirs + OS-specific cases (XDG_CONFIG_HOME/APPDATA).
• Validation/defaulting libraries: happy/edge cases; structured []FieldError assertions.
• Portal client: httptest.Server scenarios (pass/fail/override/429/5xx) with retry/backoff checks.
• Updater: mock release index; cosign verify using test keys; rollback success/failure paths.
Contract/golden tests
• CLI contracts: generate docs/cli-contracts.json and compare to goldens; update via make contracts.
• --help rendering snapshots (normalized width/colors) for core commands.
• Schemas: validate example specs against v1beta3 JSON Schemas; store fixtures in testdata/schemas/.
• Docs generator: preflight-docs.md/HTML goldens for sample merged specs with provenance.
Fuzz/property tests
• Converters: v1beta1/2→internal→v1beta3→internal round-trip fuzz; invariants enforced.
• Defaulting idempotence: default(default(x)) == default(x).
Integration/matrix tests
• Installers: brew/scoop/deb/rpm/curl on ubuntu/macos/windows; run preflight/support-bundle --version and a smoke command.
• macOS notarization: spctl -a -v on built binaries.
• Updater E2E: start mock release server, switch channels, rollback, tamper-detection failure.
Determinism & performance
• Deterministic outputs under SOURCE_DATE_EPOCH; byte-for-byte stable archives in a test harness.
• Benchmarks: load+validate budgets (latency + RSS) enforced via go test -bench and thresholds.
Artifacts & layout
• Fixtures under testdata/: schemas/, cli/, docs/, portal/, updater/ with README explaining regeneration.
• Make targets: make test, make fuzz-short, make contracts, make e2e-install, make bench.
Preflight CLI: Values and --set support (templating)
• Goal: Let end customers pass Values at runtime to drive a single modular YAML with conditionals.
• Scope: `preflight` gains `--values` (repeatable) and `--set key=value` (repeatable), rendered over the input YAML before loading specs.
• Template engine: Go text/template + Sprig, with `.Values` bound. Standard delimiters `{{` `}}`.
• Precedence:
`--set` overrides everything (last one wins when repeated)
• Later `--values` files override earlier ones (left-to-right deep merge)
• Defaults embedded in the YAML are lowest precedence
• Merge:
• Maps: deep-merge
• Slices: replace (whole list)
• Types:
`true|false` parsed as bool, numbers as float/int when unquoted, everything else as string
• Use quotes to force string: `--set image.tag="1.2.3"`
Example usage
```bash
# combine file values with inline overrides
preflight ./some-preflight-checks.yaml \
--values ./values.yaml \
--values ./values-prod.yaml \
--set postgres.enabled=true \
--set redis.enabled=false
```
Minimal Values schema (illustrative)
```yaml
postgres:
enabled: false
storageClassName: default
redis:
enabled: true
storageClassName: default
```
Single-file modular YAML authoring pattern
```yaml
apiVersion: troubleshoot.sh/v1beta3
kind: Preflight
metadata:
name: example
requirements:
- name: Baseline
docString: "Core Kubernetes requirements."
checks:
- k8sVersion: ">=1.22"
- distribution:
allow: [eks, gke, aks, kurl, digitalocean, rke2, k3s, oke, kind]
deny: [docker-desktop, microk8s, minikube]
- storageClass:
name: "default"
required: true
{{- if eq .Values.postgres.enabled true }}
- name: Postgres
docString: "Postgres needs a storage class and sufficient memory."
checks:
- storageClass:
name: "{{ .Values.postgres.storageClassName | default \"default\" }}"
required: true
- nodes:
memoryPerNode: ">=8Gi"
recommendMemoryPerNode: ">=32Gi"
{{- end }}
{{- if eq .Values.redis.enabled true }}
- name: Redis
docString: "Redis needs a storage class and adequate ephemeral storage."
checks:
- storageClass:
name: "{{ .Values.redis.storageClassName | default \"default\" }}"
required: true
- nodes:
ephemeralPerNode: ">=40Gi"
recommendEphemeralPerNode: ">=100Gi"
{{- end }}
```
Notes
• Keep everything in one YAML; conditionals gate entire requirement blocks.
• Authors can still drop down to raw analyzers; the renderer runs before spec parsing, so both styles work.
• Add `--dry-run` to print the rendered spec without executing checks.

View File

@@ -0,0 +1,106 @@
# Scheduled Jobs + Daemon: How They Work Together
## The Complete Picture
```
You create scheduled jobs → Daemon watches jobs → Jobs run automatically
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Scheduled Job │ │ Daemon Process │ │ Job Execution │
│ │ │ │ │ │
│ Name: daily │───▶│ ⏰ Checks time │───▶│ ▶ Collect bundle│
│ Schedule: 2 AM │ │ 📋 Reads jobs │ │ ▶ Upload to S3 │
│ Task: collect │ │ 🔄 Runs loop │ │ ▶ Send alerts │
└─────────────────┘ └──────────────────┘ └─────────────────┘
```
## Step-by-Step Example
### 1. You Create a Scheduled Job (One Time Setup)
```bash
support-bundle schedule create daily-health-check \
--cron "0 2 * * *" \
--namespace production \
--auto \
--upload enabled
```
**What this creates:**
- A job definition stored on disk
- Schedule: "Run daily at 2:00 AM"
- Task: "Collect support bundle from production namespace with auto-discovery and auto-upload to vendor portal"
### 2. You Start the Daemon (One Time Setup)
```bash
support-bundle schedule daemon start
```
**What the daemon does:**
```go
// Simplified daemon logic
for {
currentTime := time.Now()
// Check all scheduled jobs
for _, job := range scheduledJobs {
if job.NextRunTime <= currentTime && job.Enabled {
go runSupportBundleCollection(job) // Run in background
job.NextRunTime = calculateNextRun(job.Schedule)
}
}
time.Sleep(60 * time.Second) // Wait 1 minute, then check again
}
```
### 3. Automatic Execution (Happens Forever)
```
Day 1, 2:00 AM → Daemon sees it's time → Runs: support-bundle --namespace production
Day 2, 2:00 AM → Daemon sees it's time → Runs: support-bundle --namespace production
Day 3, 2:00 AM → Daemon sees it's time → Runs: support-bundle --namespace production
... continues forever ...
```
## Key Benefits
### Without Scheduling (Manual)
```bash
# You have to remember to run this every day
support-bundle --namespace production
# Upload manually
# Check results manually
# Easy to forget!
```
### With Scheduling (Automatic)
```bash
# Set it up once
support-bundle schedule create daily-check --cron "0 2 * * *" --namespace production --auto --upload enabled
# Start daemon once
support-bundle schedule daemon start
# Now it happens automatically forever:
# ✓ Collects support bundle daily at 2 AM with auto-discovery
# ✓ Auto-uploads to vendor portal automatically
# ✓ Never forgets
# ✓ You can sleep peacefully!
```
## Real-World Comparison
### Scheduled Job = Appointment in Calendar
- **Job Definition**: "Doctor appointment every 6 months"
- **Schedule**: "Next Tuesday at 3 PM"
- **Task**: "Go to doctor for checkup"
### Daemon = Personal Assistant
- **Always watching**: Checks your calendar continuously
- **Reminds you**: "It's time for your doctor appointment!"
- **Manages conflicts**: "You have 3 appointments at once, let me reschedule"
- **Never sleeps**: Works 24/7 even when you're busy
### In Troubleshoot Terms
- **Scheduled Job**: "Collect diagnostics every 6 hours from namespace 'webapp'"
- **Daemon**: Background service that watches the clock and runs collections automatically
- **Result**: Continuous monitoring without manual intervention