├── .gitignore ├── docs └── demo.gif ├── .github ├── workflows │ ├── build.yml │ ├── ci.yml │ └── release.yml └── dependabot.yml ├── internal ├── textviewer.go └── flashmsg.go ├── LICENSE ├── .golangci.yml ├── Makefile ├── cmd ├── options.go ├── main.go └── cardinality.go ├── README.md ├── pkg └── scrape │ ├── series.go │ ├── series_test.go │ └── scraper.go ├── go.mod └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | prom-scrape-analyzer* -------------------------------------------------------------------------------- /docs/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pedro-stanaka/prom-scrape-analyzer/HEAD/docs/demo.gif -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Check build 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build: 9 | name: Build 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | goos: [ linux, darwin ] 14 | goarch: [ amd64, arm64 ] 15 | steps: 16 | - uses: actions/checkout@v6 17 | 18 | - name: Set up Go 19 | uses: actions/setup-go@v6 20 | with: 21 | go-version-file: 'go.mod' 22 | 23 | - name: Build 24 | env: 25 | GOOS: ${{ matrix.goos }} 26 | GOARCH: ${{ matrix.goarch }} 27 | run: | 28 | make build GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} 29 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | day: "monday" 8 | open-pull-requests-limit: 10 9 | target-branch: "main" 10 | labels: 11 | - "dependencies" 12 | - "go" 13 | commit-message: 14 | prefix: "chore" 15 | include: "scope" 16 | - package-ecosystem: "github-actions" 17 | directory: "/" 18 | schedule: 19 | interval: "weekly" 20 | day: "monday" 21 | open-pull-requests-limit: 10 22 | target-branch: "main" 23 | labels: 24 | - "dependencies" 25 | - "github-actions" 26 | commit-message: 27 | prefix: "chore" 28 | include: "scope" 29 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Test and lint 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | 7 | jobs: 8 | test: 9 | name: Test 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v6 13 | - name: Set up Go 14 | uses: actions/setup-go@v6 15 | with: 16 | go-version-file: 'go.mod' 17 | - name: Test 18 | run: make test 19 | 20 | lint: 21 | name: lint 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v6 25 | - uses: actions/setup-go@v6 26 | with: 27 | go-version-file: 'go.mod' 28 | - name: golangci-lint 29 | uses: golangci/golangci-lint-action@v9 30 | with: 31 | version: v2.4 32 | -------------------------------------------------------------------------------- /internal/textviewer.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | ) 7 | 8 | // CreateTempFileWithContent creates a temporary file with the given content and returns its path. 9 | // The caller is responsible for removing the file when done. 10 | func CreateTempFileWithContent(content string) string { 11 | tmpfile, err := os.CreateTemp("", "prom-scrape-analyzer-*.txt") 12 | if err != nil { 13 | return "" 14 | } 15 | 16 | if _, err := tmpfile.WriteString(content); err != nil { 17 | tmpfile.Close() 18 | os.Remove(tmpfile.Name()) 19 | return "" 20 | } 21 | 22 | if err := tmpfile.Sync(); err != nil { 23 | tmpfile.Close() 24 | os.Remove(tmpfile.Name()) 25 | return "" 26 | } 27 | 28 | if err := tmpfile.Close(); err != nil { 29 | os.Remove(tmpfile.Name()) 30 | return "" 31 | } 32 | 33 | return filepath.Clean(tmpfile.Name()) 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Pedro Tanaka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | issues-exit-code: 1 4 | output: 5 | formats: 6 | text: 7 | path: stdout 8 | print-linter-name: true 9 | print-issued-lines: true 10 | linters: 11 | enable: 12 | - goconst 13 | - godot 14 | - lll 15 | - misspell 16 | - promlinter 17 | - unparam 18 | settings: 19 | errcheck: 20 | exclude-functions: 21 | - (github.com/go-kit/log.Logger).Log 22 | - fmt.Fprintln 23 | - fmt.Fprint 24 | goconst: 25 | min-occurrences: 5 26 | misspell: 27 | locale: US 28 | exclusions: 29 | generated: lax 30 | presets: 31 | - comments 32 | - common-false-positives 33 | - legacy 34 | - std-error-handling 35 | paths: 36 | - third_party$ 37 | - builtin$ 38 | - examples$ 39 | formatters: 40 | enable: 41 | - gci 42 | - gofmt 43 | - goimports 44 | settings: 45 | gci: 46 | sections: 47 | - standard 48 | - default 49 | - prefix(github.com/pedro-stanaka/prom-scrape-analyzer) 50 | exclusions: 51 | generated: lax 52 | paths: 53 | - third_party$ 54 | - builtin$ 55 | - examples$ 56 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Go parameters 2 | GOCMD=go 3 | GOBUILD=$(GOCMD) build 4 | GOCLEAN=$(GOCMD) clean 5 | GOTEST=$(GOCMD) test 6 | GOGET=$(GOCMD) get 7 | GOMOD=$(GOCMD) mod 8 | BINARY_NAME=prom-scrape-analyzer-$(GOOS)-$(GOARCH) 9 | VERSION=0.1.0 10 | BUILD_FLAGS=-ldflags "-X main.version=$(VERSION) -s -w" 11 | GOARCH=$(shell go env GOARCH) 12 | GOOS=$(shell go env GOOS) 13 | CGO_ENABLED=0 14 | 15 | all: test build 16 | 17 | build: 18 | GOARCH=$(GOARCH) GOOS=$(GOOS) CGO_ENABLED=$(CGO_ENABLED) $(GOBUILD) $(BUILD_FLAGS) -o $(BINARY_NAME) ./cmd/... 19 | 20 | test: 21 | $(GOTEST) ./... 22 | 23 | clean: 24 | $(GOCLEAN) 25 | rm -f $(BINARY_NAME) 26 | 27 | run: 28 | $(GOBUILD) -o $(BINARY_NAME) -v ./cmd/main.go 29 | ./$(BINARY_NAME) 30 | 31 | deps: 32 | $(GOGET) ./... 33 | $(GOMOD) tidy 34 | 35 | lint: deps 36 | go tool github.com/golangci/golangci-lint/v2/cmd/golangci-lint run --fix --verbose ./... 37 | 38 | # Cross compilation 39 | build-linux: 40 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BINARY_NAME)_linux -v ./cmd/main.go 41 | 42 | build-windows: 43 | CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GOBUILD) -o $(BINARY_NAME).exe -v ./cmd/main.go 44 | 45 | build-mac: 46 | CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 $(GOBUILD) -o $(BINARY_NAME)_mac -v ./cmd/main.go 47 | 48 | .PHONY: all build test clean run deps build-linux build-windows build-mac 49 | -------------------------------------------------------------------------------- /cmd/options.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/docker/go-units" 7 | "github.com/pkg/errors" 8 | "github.com/thanos-io/thanos/pkg/extkingpin" 9 | ) 10 | 11 | type Options struct { 12 | ScrapeURL string 13 | ScrapeFile string 14 | OutputHeight int 15 | MaxScrapeSize string 16 | Timeout time.Duration 17 | HttpConfigFile string 18 | } 19 | 20 | func (o *Options) MaxScrapeSizeBytes() (int64, error) { 21 | size, err := units.FromHumanSize(o.MaxScrapeSize) 22 | if err != nil { 23 | return 0, errors.Wrap(err, "invalid max scrape size") 24 | } 25 | return size, nil 26 | } 27 | 28 | func (o *Options) AddFlags(app extkingpin.AppClause) { 29 | app.Flag("scrape.url", "URL to scrape metrics from"). 30 | Default(""). 31 | StringVar(&o.ScrapeURL) 32 | 33 | app.Flag("scrape.file", "File to scrape metrics from"). 34 | Default(""). 35 | StringVar(&o.ScrapeFile) 36 | 37 | app.Flag("timeout", "Timeout for the scrape request"). 38 | Default("10s"). 39 | DurationVar(&o.Timeout) 40 | 41 | app.Flag("output-height", "Height of the output table"). 42 | Default("40"). 43 | IntVar(&o.OutputHeight) 44 | 45 | app.Flag("max-scrape-size", "Maximum size of the scrape response body (e.g. 10MB, 1GB)"). 46 | Default("100MB"). 47 | StringVar(&o.MaxScrapeSize) 48 | 49 | app.Flag("http.config", "Path to file to use for HTTP client config options like basic auth and TLS."). 50 | Default(""). 51 | StringVar(&o.HttpConfigFile) 52 | } 53 | -------------------------------------------------------------------------------- /internal/flashmsg.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "time" 5 | 6 | tea "github.com/charmbracelet/bubbletea" 7 | "github.com/charmbracelet/lipgloss" 8 | ) 9 | 10 | type Level int 11 | 12 | const ( 13 | Info Level = iota 14 | Error 15 | ) 16 | 17 | // TextFlash is used to temporarily display a message to the user. 18 | type TextFlash struct { 19 | text string 20 | level Level 21 | } 22 | 23 | type UpdateTextFlashMsg struct { 24 | text string 25 | level Level 26 | duration time.Duration 27 | 28 | isResetMsg bool 29 | } 30 | 31 | func (t TextFlash) Init() tea.Cmd { 32 | return nil 33 | } 34 | 35 | func (t TextFlash) Update(msg tea.Msg) (TextFlash, tea.Cmd) { 36 | switch msg := msg.(type) { 37 | case UpdateTextFlashMsg: 38 | if msg.isResetMsg { 39 | t.text = "" 40 | t.level = Info 41 | return t, nil 42 | } 43 | 44 | // Workaround so that existing error messages take priority over new info messages, 45 | // which is needed since tea.Batch(infoCmd, errorCmd) creates a race condition 46 | if t.level == Error && msg.level == Info { 47 | return t, nil 48 | } 49 | 50 | t.text = msg.text 51 | t.level = msg.level 52 | cmd := tea.Tick(msg.duration, func(time.Time) tea.Msg { 53 | return UpdateTextFlashMsg{ 54 | isResetMsg: true, 55 | } 56 | }) 57 | return t, cmd 58 | } 59 | return t, nil 60 | } 61 | 62 | func (t TextFlash) Flash(text string, level Level, duration time.Duration) tea.Cmd { 63 | return func() tea.Msg { 64 | return UpdateTextFlashMsg{ 65 | text: text, 66 | level: level, 67 | duration: duration, 68 | } 69 | } 70 | } 71 | 72 | func (t TextFlash) View() string { 73 | if t.text == "" { 74 | return "" 75 | } 76 | 77 | style := lipgloss.NewStyle(). 78 | Bold(true). 79 | MarginLeft(1) 80 | 81 | switch t.level { 82 | case Info: 83 | return style.Render("ℹ️ " + t.text) 84 | case Error: 85 | return style.Render("⚠️ " + t.text + " ⚠️") 86 | default: 87 | return style.Render(t.text) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Prom Scrape Analyzer 2 | 3 | Prom Scrape Analyzer is a powerful companion tool for Observability professionals, 4 | designed to inspect Prometheus scrape endpoints and provide valuable insights into your monitoring setup. 5 | 6 | ![Demo GIF](docs/demo.gif) 7 | 8 | ## Motivation 9 | 10 | This project was born out of two main desires: 11 | 12 | 1. **Endpoint Visibility**: Working in the Observability space, I was frustrated by the lack of tools to easily view metrics 13 | being exported in Prometheus' protobuf endpoints. This tool aims to bridge that gap, providing clear visibility into the 14 | actual metrics being scraped. 15 | 16 | 2. **Exploring Bubble Tea TUI library**: I wanted to experiment with and showcase the capabilities of Bubble Tea, 17 | a powerful library for building Text User Interface (TUI) applications. This project serves as a playground 18 | for creating an intuitive and interactive command-line interface. 19 | 20 | ## Features 21 | 22 | - [x] Scrape and analyze cardinality for a given Prometheus scrape endpoint (supports Protobuf format) 23 | - [x] Support for latest features like Created Timestamps and Native Histograms, showing them as separate columns. 24 | - [x] Allow to select a specific metric to inspect, and show its series. 25 | - [x] Metric search with fuzzy search. 26 | - [x] [HTTP configuration file](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#http_config) to support custom HTTP client options like basic auth, custom headers, proxy configs, etc. 27 | - [x] Support reading metrics from local files, to help analyze metrics that have already been collected. 28 | 29 | ## Planned Features 30 | 31 | - [ ] Improve TUI with filtering, sorting and other features. 32 | - [ ] Watch mode, to see how cardinality and churn evolve over time. 33 | - [ ] For native histograms, show the bucket boundaries and counts (if possible, chart it). 34 | 35 | ## Getting Started 36 | 37 | This project is currently under development. Stay tuned for updates and installation instructions. 38 | 39 | ## Contributing 40 | 41 | We welcome contributions! Please check back soon for guidelines on how to contribute to this project. 42 | 43 | ## License 44 | 45 | [MIT](LICENSE) 46 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build an release binaries 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | build: 10 | name: Build binaries 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | goos: [ linux, windows, darwin ] 15 | goarch: [ amd64, arm64 ] 16 | exclude: 17 | - goos: windows 18 | goarch: arm64 19 | steps: 20 | - uses: actions/checkout@v6 21 | 22 | - name: Set up Go 23 | uses: actions/setup-go@v6 24 | with: 25 | go-version-file: 'go.mod' 26 | 27 | - name: Build 28 | run: make build GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} VERSION=${{ github.ref_name }} 29 | 30 | - name: Upload build artifacts 31 | uses: actions/upload-artifact@v6 32 | with: 33 | name: build-artifacts-${{ matrix.goos }}-${{ matrix.goarch }} 34 | path: ./prom-scrape-analyzer-* 35 | 36 | publish: 37 | name: Publish release artifacts 38 | runs-on: ubuntu-latest 39 | needs: build 40 | permissions: 41 | contents: write 42 | packages: write 43 | attestations: write 44 | steps: 45 | - uses: actions/checkout@v6 46 | - uses: actions/download-artifact@v7 47 | name: Download build artifacts 48 | with: 49 | pattern: build-artifacts* 50 | merge-multiple: true 51 | - name: Zip final release files 52 | env: 53 | VERSION: ${{ github.ref_name }} 54 | run: | 55 | for file in prom-scrape-analyzer-*; do 56 | if [[ $file == *windows* ]]; then 57 | ext=".exe" 58 | else 59 | ext="" 60 | fi 61 | os_arch=$(echo $file | sed -E 's/prom-scrape-analyzer-(.+)/\1/') 62 | mkdir -p "release-$os_arch" 63 | cp "$file" "release-$os_arch/prom-scrape-analyzer$ext" 64 | cp README.md "release-$os_arch/" 65 | echo "$VERSION" > "release-$os_arch/VERSION" 66 | zip -r "prom-scrape-analyzer-$VERSION-$os_arch.zip" "release-$os_arch" 67 | done 68 | - name: Upload Release Asset 69 | uses: AButler/upload-release-assets@v3.0 70 | with: 71 | repo-token: ${{ secrets.GITHUB_TOKEN }} 72 | files: ./prom-scrape-analyzer-*.zip 73 | release-tag: ${{ github.ref_name }} 74 | -------------------------------------------------------------------------------- /pkg/scrape/series.go: -------------------------------------------------------------------------------- 1 | package scrape 2 | 3 | import ( 4 | "fmt" 5 | "slices" 6 | "strings" 7 | "time" 8 | 9 | "github.com/prometheus/prometheus/model/labels" 10 | ) 11 | 12 | type Exemplar struct { 13 | Labels labels.Labels 14 | Value float64 15 | Ts int64 16 | HasTs bool 17 | } 18 | 19 | func (e Exemplar) String() string { 20 | var ts string 21 | if e.HasTs { 22 | ts = fmt.Sprintf(" @ %s", time.UnixMilli(e.Ts).Format(time.RFC3339)) 23 | } 24 | return fmt.Sprintf("%s=%g%s", e.Labels.String(), e.Value, ts) 25 | } 26 | 27 | type Series struct { 28 | Name string 29 | Labels labels.Labels 30 | Type string 31 | CreatedTimestamp int64 32 | Exemplars []Exemplar 33 | } 34 | 35 | type SeriesSet map[uint64]Series 36 | type SeriesScrapeText map[string]string 37 | 38 | func (s SeriesSet) Cardinality() int { 39 | return len(s) 40 | } 41 | 42 | func (s SeriesSet) MetricTypeString() string { 43 | if len(s) == 0 { 44 | return "" 45 | } 46 | typeStr := "" 47 | lastType := "" 48 | for _, v := range s { 49 | if v.Type == "" { 50 | v.Type = "unknown" 51 | } 52 | if lastType != v.Type { 53 | if typeStr != "" { 54 | typeStr += "|" 55 | } 56 | typeStr += v.Type 57 | lastType = v.Type 58 | } 59 | } 60 | return typeStr 61 | } 62 | 63 | func (s SeriesSet) CreatedTS() int64 { 64 | for _, v := range s { 65 | return v.CreatedTimestamp 66 | } 67 | return 0 68 | } 69 | 70 | func (s SeriesSet) LabelNames() string { 71 | if len(s) == 0 { 72 | return "" 73 | } 74 | labelSet := make(map[string]struct{}) 75 | for _, v := range s { 76 | v.Labels.Range(func(l labels.Label) { 77 | if l.Name != "__name__" { 78 | labelSet[l.Name] = struct{}{} 79 | } 80 | }) 81 | } 82 | lbls := make([]string, 0, len(labelSet)) 83 | for label := range labelSet { 84 | lbls = append(lbls, label) 85 | } 86 | return strings.Join(lbls, "|") 87 | } 88 | 89 | func (s SeriesSet) LabelStats() LabelStatsSlice { 90 | if len(s) == 0 { 91 | return nil 92 | } 93 | labelValueSet := make(map[string]map[string]struct{}) 94 | 95 | for _, v := range s { 96 | v.Labels.Range(func(l labels.Label) { 97 | if l.Name != "__name__" { 98 | // Initialize the inner map if it doesn't exist 99 | if _, exists := labelValueSet[l.Name]; !exists { 100 | labelValueSet[l.Name] = make(map[string]struct{}) 101 | } 102 | // Add the value to the set 103 | labelValueSet[l.Name][l.Value] = struct{}{} 104 | } 105 | }) 106 | } 107 | 108 | var stats []LabelStats 109 | for label, valueSet := range labelValueSet { 110 | stats = append(stats, LabelStats{ 111 | Name: label, 112 | DistinctValues: uint(len(valueSet)), // Count unique values 113 | }) 114 | } 115 | return stats 116 | } 117 | 118 | type LabelStats struct { 119 | Name string 120 | DistinctValues uint 121 | } 122 | 123 | func (l LabelStats) String() string { 124 | return fmt.Sprintf("%s(%d)", l.Name, l.DistinctValues) 125 | } 126 | 127 | type LabelStatsSlice []LabelStats 128 | 129 | func (l LabelStatsSlice) String() string { 130 | var strBuf strings.Builder 131 | for i, ls := range l { 132 | if i > 0 { 133 | strBuf.WriteString("|") 134 | } 135 | strBuf.WriteString(ls.String()) 136 | } 137 | return strBuf.String() 138 | } 139 | 140 | type SeriesMap map[string]SeriesSet 141 | 142 | type Result struct { 143 | Series SeriesMap 144 | UsedContentType string 145 | SeriesScrapeText 146 | } 147 | 148 | type SeriesInfo struct { 149 | Name string 150 | Cardinality int 151 | Type string 152 | Labels string 153 | CreatedTS string 154 | } 155 | 156 | func (s SeriesMap) AsRows() []SeriesInfo { 157 | var rows []SeriesInfo 158 | for name, s := range s { 159 | createdTs := int64(0) 160 | if len(s) > 0 { 161 | createdTs = int64(int(s.CreatedTS())) 162 | } 163 | createdTsStr := "_empty_" 164 | if createdTs > 0 { 165 | createdTsStr = time.UnixMilli(createdTs).String() 166 | } 167 | lblStats := s.LabelStats() 168 | slices.SortFunc(lblStats, func(i, j LabelStats) int { 169 | if d := (int(i.DistinctValues) - int(j.DistinctValues)) * -1; d != 0 { 170 | return d 171 | } 172 | // Consistent sorting to avoid labels moving around after filtering on the same values 173 | return strings.Compare(i.Name, j.Name) 174 | }) 175 | rows = append(rows, SeriesInfo{ 176 | Name: name, 177 | Cardinality: s.Cardinality(), 178 | Type: s.MetricTypeString(), 179 | Labels: lblStats.String(), 180 | CreatedTS: createdTsStr, 181 | }) 182 | } 183 | 184 | slices.SortFunc(rows, func(i, j SeriesInfo) int { 185 | if c := (i.Cardinality - j.Cardinality) * -1; c != 0 { 186 | return c 187 | } 188 | // Consistent sorting to avoid rows moving around after filtering on the same values 189 | return strings.Compare(i.Name, j.Name) 190 | }) 191 | 192 | return rows 193 | } 194 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "os/signal" 8 | "path/filepath" 9 | "regexp" 10 | "syscall" 11 | 12 | "github.com/go-kit/log" 13 | "github.com/go-kit/log/level" 14 | "github.com/oklog/run" 15 | "github.com/opentracing/opentracing-go" 16 | "github.com/pkg/errors" 17 | "github.com/prometheus/client_golang/prometheus" 18 | "github.com/prometheus/client_golang/prometheus/collectors" 19 | versioncollector "github.com/prometheus/client_golang/prometheus/collectors/version" 20 | "github.com/thanos-io/thanos/pkg/extkingpin" 21 | "github.com/thanos-io/thanos/pkg/logging" 22 | "gopkg.in/alecthomas/kingpin.v2" 23 | ) 24 | 25 | func main() { 26 | app := extkingpin.NewApp(kingpin.New(filepath.Base(os.Args[0]), "A tool to analyze Prometheus scrape data.")) 27 | logLevel := app.Flag("log.level", "Log filtering level."). 28 | Default("info").Enum("error", "warn", "info", "debug") 29 | logFormat := app.Flag("log.format", "Log format to use. Possible options: logfmt or json."). 30 | Default(logging.LogFormatLogfmt).Enum(logging.LogFormatLogfmt, logging.LogFormatJSON) 31 | logFile := app.Flag("log.file", "Log file to write to, if empty will log to stderr.").Default("").String() 32 | 33 | registerCardinalityCommand(app) 34 | 35 | cmd, setup := app.Parse() 36 | 37 | metrics := prometheus.NewRegistry() 38 | metrics.MustRegister( 39 | versioncollector.NewCollector("prom_scrape_analyzer"), 40 | collectors.NewGoCollector( 41 | collectors.WithGoCollectorRuntimeMetrics(collectors.GoRuntimeMetricsRule{Matcher: regexp.MustCompile("/.*")}), 42 | ), 43 | collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}), 44 | ) 45 | 46 | logger, err := setupLogging(*logLevel, *logFormat, *logFile) 47 | if err != nil { 48 | fmt.Fprintf(os.Stderr, "Error setting up logging: %v\n", err) 49 | os.Exit(1) 50 | } 51 | 52 | // Create a signal channel to dispatch reload events to sub-commands. 53 | reloadCh := make(chan struct{}, 1) 54 | 55 | var g run.Group 56 | var tracer opentracing.Tracer 57 | if err := setup(&g, logger, metrics, tracer, reloadCh, *logLevel == "debug"); err != nil { 58 | // Use %+v for github.com/pkg/errors error to print with stack. 59 | level.Error(logger).Log("err", fmt.Sprintf("%+v", errors.Wrapf(err, "preparing %s command failed", cmd))) 60 | os.Exit(1) 61 | } 62 | 63 | // Listen for termination signals. 64 | { 65 | cancel := make(chan struct{}) 66 | g.Add(func() error { 67 | return interrupt(logger, cancel) 68 | }, func(error) { 69 | close(cancel) 70 | }) 71 | } 72 | 73 | // Listen for reload signals. 74 | { 75 | cancel := make(chan struct{}) 76 | g.Add(func() error { 77 | return reload(logger, cancel, reloadCh) 78 | }, func(error) { 79 | close(cancel) 80 | }) 81 | } 82 | 83 | if err := g.Run(); err != nil { 84 | // Use %+v for github.com/pkg/errors error to print with stack. 85 | level.Error(logger).Log("err", fmt.Sprintf("%+v", errors.Wrapf(err, "%s command failed", cmd))) 86 | os.Exit(1) 87 | } 88 | level.Info(logger).Log("msg", "exiting") 89 | } 90 | 91 | func setupLogging(logLevel, logFormat, file string) (log.Logger, error) { 92 | var ( 93 | logger log.Logger 94 | lvl level.Option 95 | writer io.Writer 96 | ) 97 | 98 | switch logLevel { 99 | case "error": 100 | lvl = level.AllowError() 101 | case "warn": 102 | lvl = level.AllowWarn() 103 | case "info": 104 | lvl = level.AllowInfo() 105 | case "debug": 106 | lvl = level.AllowDebug() 107 | default: 108 | // This enum is already checked and enforced by flag validations, so 109 | // this should never happen. 110 | panic("unexpected log level") 111 | } 112 | 113 | if file != "" { 114 | f, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) 115 | if err != nil { 116 | return nil, err 117 | } 118 | writer = f 119 | } else { 120 | writer = os.Stderr 121 | } 122 | 123 | logger = log.NewLogfmtLogger(log.NewSyncWriter(writer)) 124 | if logFormat == logging.LogFormatJSON { 125 | logger = log.NewJSONLogger(log.NewSyncWriter(writer)) 126 | } 127 | 128 | // Sort the logger chain to avoid expensive log.Valuer evaluation for disallowed level. 129 | // Ref: https://github.com/go-kit/log/issues/14#issuecomment-945038252 130 | logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.Caller(5)) 131 | logger = level.NewFilter(logger, lvl) 132 | 133 | return logger, nil 134 | } 135 | 136 | func interrupt(logger log.Logger, cancel <-chan struct{}) error { 137 | c := make(chan os.Signal, 1) 138 | signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) 139 | select { 140 | case s := <-c: 141 | level.Info(logger).Log("msg", "caught signal. Exiting.", "signal", s) 142 | return nil 143 | case <-cancel: 144 | return errors.New("canceled") 145 | } 146 | } 147 | 148 | func reload(logger log.Logger, cancel <-chan struct{}, r chan<- struct{}) error { 149 | c := make(chan os.Signal, 1) 150 | signal.Notify(c, syscall.SIGHUP) 151 | for { 152 | select { 153 | case s := <-c: 154 | level.Info(logger).Log("msg", "caught signal. Reloading.", "signal", s) 155 | select { 156 | case r <- struct{}{}: 157 | level.Info(logger).Log("msg", "reload dispatched.") 158 | default: 159 | } 160 | case <-cancel: 161 | return errors.New("canceled") 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /pkg/scrape/series_test.go: -------------------------------------------------------------------------------- 1 | package scrape_test 2 | 3 | import ( 4 | "sort" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/prometheus/prometheus/model/labels" 9 | "github.com/stretchr/testify/require" 10 | 11 | "github.com/pedro-stanaka/prom-scrape-analyzer/pkg/scrape" 12 | ) 13 | 14 | func TestSeriesSet_Cardinality(t *testing.T) { 15 | t.Parallel() 16 | seriesSet := scrape.SeriesSet{ 17 | 1: {Name: "series1"}, 18 | 2: {Name: "series2"}, 19 | } 20 | 21 | expected := 2 22 | require.Equal(t, expected, seriesSet.Cardinality(), "Cardinality() should return the correct number of series") 23 | } 24 | 25 | func TestSeriesSet_MetricTypeString(t *testing.T) { 26 | t.Parallel() 27 | seriesSet := scrape.SeriesSet{ 28 | 1: {Name: "series1", Type: "gauge"}, 29 | 2: {Name: "series2", Type: "counter"}, 30 | } 31 | 32 | // Get actual result and sort it 33 | actual := seriesSet.MetricTypeString() 34 | actualParts := strings.Split(actual, "|") 35 | sort.Strings(actualParts) 36 | 37 | // Sort expected parts the same way 38 | expectedParts := []string{"gauge", "counter"} 39 | sort.Strings(expectedParts) 40 | 41 | require.Equal(t, strings.Join(expectedParts, "|"), strings.Join(actualParts, "|"), 42 | "MetricTypeString() should return the correct metric types") 43 | } 44 | 45 | func TestSeriesSet_CreatedTS(t *testing.T) { 46 | t.Parallel() 47 | seriesSet := scrape.SeriesSet{ 48 | 1: {Name: "series1", CreatedTimestamp: 1620000000}, 49 | 2: {Name: "series2", CreatedTimestamp: 1620000001}, 50 | } 51 | 52 | createdTS := seriesSet.CreatedTS() 53 | require.Condition(t, func() bool { 54 | return createdTS == int64(1620000000) || createdTS == int64(1620000001) 55 | }, "CreatedTS() should return either 1620000000 or 1620000001") 56 | } 57 | 58 | func TestSeriesSet_LabelNames(t *testing.T) { 59 | t.Parallel() 60 | seriesSet := scrape.SeriesSet{ 61 | 1: {Name: "series1", Labels: labels.FromStrings("label1", "", "label2", "")}, 62 | 2: {Name: "series2", Labels: labels.FromStrings("label2", "", "label3", "")}, 63 | } 64 | 65 | expected := "label1|label2|label3" 66 | actual := seriesSet.LabelNames() 67 | require.ElementsMatch( 68 | t, 69 | strings.Split(expected, "|"), 70 | strings.Split(actual, "|"), 71 | "LabelNames() should return the correct label names", 72 | ) 73 | } 74 | 75 | func TestSeriesSet_LabelStats(t *testing.T) { 76 | t.Parallel() 77 | seriesSet := scrape.SeriesSet{ 78 | 1: {Name: "series1", Labels: labels.FromStrings("label1", "foo", "label2", "bar")}, 79 | 2: {Name: "series2", Labels: labels.FromStrings("label2", "baz", "label3", "qux")}, 80 | 3: {Name: "series3", Labels: labels.FromStrings("label2", "baz", "label3", "qua")}, 81 | } 82 | 83 | expected := scrape.LabelStatsSlice{ 84 | {Name: "label1", DistinctValues: 1}, 85 | {Name: "label2", DistinctValues: 2}, 86 | {Name: "label3", DistinctValues: 2}, 87 | } 88 | got := seriesSet.LabelStats() 89 | 90 | require.Len(t, got, len(expected), "LabelStats() should return the correct number of label stats") 91 | // Sort both slices by Name before comparison 92 | sort.Slice(expected, func(i, j int) bool { return expected[i].Name < expected[j].Name }) 93 | sort.Slice(got, func(i, j int) bool { return got[i].Name < got[j].Name }) 94 | require.EqualValues(t, expected, got, "LabelStats() should return the correct label stats") 95 | } 96 | 97 | func TestSeriesSet_AsRowOrdering(t *testing.T) { 98 | t.Parallel() 99 | var seriesMap scrape.SeriesMap = make(map[string]scrape.SeriesSet) 100 | seriesMap["series1"] = scrape.SeriesSet{ 101 | 1: {Name: "series1", Labels: labels.FromStrings("label1", "foo")}, 102 | } 103 | seriesMap["series2"] = scrape.SeriesSet{ 104 | 1: {Name: "series2", Labels: labels.FromStrings("label1", "foo")}, 105 | 2: {Name: "series2", Labels: labels.FromStrings("label1", "bar")}, 106 | } 107 | seriesMap["series3"] = scrape.SeriesSet{ 108 | 1: {Name: "series3", Labels: labels.FromStrings("label1", "foo")}, 109 | 2: {Name: "series3", Labels: labels.FromStrings("label1", "bar")}, 110 | } 111 | 112 | rows := seriesMap.AsRows() 113 | 114 | require.Len(t, rows, 3, "AsRows() should return the correct number of rows") 115 | require.Equal(t, "series2", rows[0].Name) 116 | require.Equal(t, "series3", rows[1].Name) 117 | require.Equal(t, "series1", rows[2].Name) 118 | } 119 | 120 | func TestSeries_Exemplars(t *testing.T) { 121 | t.Parallel() 122 | 123 | // Test series with exemplars 124 | series := scrape.Series{ 125 | Name: "test_metric", 126 | Labels: labels.FromStrings("method", "GET", "status", "200"), 127 | Type: "counter", 128 | Exemplars: []scrape.Exemplar{ 129 | { 130 | Labels: labels.FromStrings("trace_id", "abc123"), 131 | Value: 42.0, 132 | Ts: 1620000000000, 133 | HasTs: true, 134 | }, 135 | { 136 | Labels: labels.FromStrings("span_id", "xyz789"), 137 | Value: 100.5, 138 | Ts: 0, 139 | HasTs: false, 140 | }, 141 | }, 142 | } 143 | 144 | require.Len(t, series.Exemplars, 2, "Series should have 2 exemplars") 145 | 146 | // Test first exemplar 147 | ex1 := series.Exemplars[0] 148 | require.Equal(t, 42.0, ex1.Value) 149 | require.Equal(t, int64(1620000000000), ex1.Ts) 150 | require.True(t, ex1.HasTs) 151 | require.Equal(t, "abc123", ex1.Labels.Get("trace_id")) 152 | 153 | // Test second exemplar 154 | ex2 := series.Exemplars[1] 155 | require.Equal(t, 100.5, ex2.Value) 156 | require.False(t, ex2.HasTs) 157 | require.Equal(t, "xyz789", ex2.Labels.Get("span_id")) 158 | 159 | // Test String() method 160 | ex1Str := ex1.String() 161 | require.Contains(t, ex1Str, "trace_id=\"abc123\"") 162 | require.Contains(t, ex1Str, "42") 163 | require.Contains(t, ex1Str, "2021-05-03") // Check timestamp is formatted 164 | 165 | ex2Str := ex2.String() 166 | require.Contains(t, ex2Str, "span_id=\"xyz789\"") 167 | require.Contains(t, ex2Str, "100.5") 168 | require.NotContains(t, ex2Str, "@") // No timestamp should be shown 169 | } 170 | 171 | func TestSeriesSet_WithExemplars(t *testing.T) { 172 | t.Parallel() 173 | 174 | seriesSet := scrape.SeriesSet{ 175 | 1: { 176 | Name: "http_requests_total", 177 | Labels: labels.FromStrings("method", "GET"), 178 | Type: "counter", 179 | Exemplars: []scrape.Exemplar{ 180 | { 181 | Labels: labels.FromStrings("trace_id", "trace1"), 182 | Value: 123.0, 183 | Ts: 1620000000000, 184 | HasTs: true, 185 | }, 186 | }, 187 | }, 188 | 2: { 189 | Name: "http_requests_total", 190 | Labels: labels.FromStrings("method", "POST"), 191 | Type: "counter", 192 | // No exemplars for this series 193 | }, 194 | } 195 | 196 | // Check that we can access exemplars correctly 197 | series1 := seriesSet[1] 198 | require.Len(t, series1.Exemplars, 1) 199 | require.Equal(t, "trace1", series1.Exemplars[0].Labels.Get("trace_id")) 200 | 201 | series2 := seriesSet[2] 202 | require.Len(t, series2.Exemplars, 0) 203 | } 204 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pedro-stanaka/prom-scrape-analyzer 2 | 3 | go 1.25.0 4 | 5 | require ( 6 | github.com/charmbracelet/bubbles v0.21.0 7 | github.com/charmbracelet/bubbletea v1.3.9 8 | github.com/charmbracelet/lipgloss v1.1.0 9 | github.com/docker/go-units v0.5.0 10 | github.com/go-kit/log v0.2.1 11 | github.com/lithammer/fuzzysearch v1.1.8 12 | github.com/oklog/run v1.2.0 13 | github.com/opentracing/opentracing-go v1.2.0 14 | github.com/pkg/errors v0.9.1 15 | github.com/prometheus/client_golang v1.23.0 16 | github.com/prometheus/common v0.65.0 17 | github.com/prometheus/prometheus v0.305.0 18 | github.com/stretchr/testify v1.10.0 19 | github.com/thanos-io/thanos v0.37.3-0.20250221064218-f230915c1c13 20 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 21 | ) 22 | 23 | require ( 24 | 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect 25 | 4d63.com/gochecknoglobals v0.2.2 // indirect 26 | cloud.google.com/go/auth v0.16.2 // indirect 27 | cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect 28 | cloud.google.com/go/compute/metadata v0.7.0 // indirect 29 | codeberg.org/chavacava/garif v0.2.0 // indirect 30 | dev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect 31 | dev.gaijin.team/go/golib v0.6.0 // indirect 32 | github.com/4meepo/tagalign v1.4.3 // indirect 33 | github.com/Abirdcfly/dupword v0.1.6 // indirect 34 | github.com/AlwxSin/noinlineerr v1.0.5 // indirect 35 | github.com/Antonboom/errname v1.1.0 // indirect 36 | github.com/Antonboom/nilnil v1.1.0 // indirect 37 | github.com/Antonboom/testifylint v1.6.1 // indirect 38 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect 39 | github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 // indirect 40 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect 41 | github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect 42 | github.com/BurntSushi/toml v1.5.0 // indirect 43 | github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect 44 | github.com/Masterminds/semver/v3 v3.3.1 // indirect 45 | github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect 46 | github.com/alecthomas/chroma/v2 v2.20.0 // indirect 47 | github.com/alecthomas/go-check-sumtype v0.3.1 // indirect 48 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect 49 | github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect 50 | github.com/alexkohler/nakedret/v2 v2.0.6 // indirect 51 | github.com/alexkohler/prealloc v1.0.0 // indirect 52 | github.com/alfatraining/structtag v1.0.0 // indirect 53 | github.com/alingse/asasalint v0.0.11 // indirect 54 | github.com/alingse/nilnesserr v0.2.0 // indirect 55 | github.com/ashanbrown/forbidigo/v2 v2.1.0 // indirect 56 | github.com/ashanbrown/makezero/v2 v2.0.1 // indirect 57 | github.com/atotto/clipboard v0.1.4 // indirect 58 | github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect 59 | github.com/aws/aws-sdk-go-v2/config v1.29.14 // indirect 60 | github.com/aws/aws-sdk-go-v2/credentials v1.17.67 // indirect 61 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect 62 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect 63 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect 64 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect 65 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect 66 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect 67 | github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect 68 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect 69 | github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect 70 | github.com/aws/smithy-go v1.22.2 // indirect 71 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 72 | github.com/beorn7/perks v1.0.1 // indirect 73 | github.com/bkielbasa/cyclop v1.2.3 // indirect 74 | github.com/blizzy78/varnamelen v0.8.0 // indirect 75 | github.com/bombsimon/wsl/v4 v4.7.0 // indirect 76 | github.com/bombsimon/wsl/v5 v5.1.1 // indirect 77 | github.com/breml/bidichk v0.3.3 // indirect 78 | github.com/breml/errchkjson v0.4.1 // indirect 79 | github.com/butuzov/ireturn v0.4.0 // indirect 80 | github.com/butuzov/mirror v1.3.0 // indirect 81 | github.com/catenacyber/perfsprint v0.9.1 // indirect 82 | github.com/ccojocar/zxcvbn-go v1.0.4 // indirect 83 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 84 | github.com/charithe/durationcheck v0.0.10 // indirect 85 | github.com/charmbracelet/colorprofile v0.3.0 // indirect 86 | github.com/charmbracelet/x/ansi v0.10.1 // indirect 87 | github.com/charmbracelet/x/cellbuf v0.0.13 // indirect 88 | github.com/charmbracelet/x/term v0.2.1 // indirect 89 | github.com/ckaznocha/intrange v0.3.1 // indirect 90 | github.com/curioswitch/go-reassign v0.3.0 // indirect 91 | github.com/daixiang0/gci v0.13.7 // indirect 92 | github.com/dave/dst v0.27.3 // indirect 93 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 94 | github.com/denis-tingaikin/go-header v0.5.0 // indirect 95 | github.com/dlclark/regexp2 v1.11.5 // indirect 96 | github.com/efficientgo/tools/extkingpin v0.0.0-20220817170617-6c25e3b627dd // indirect 97 | github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect 98 | github.com/ettle/strcase v0.2.0 // indirect 99 | github.com/fatih/color v1.18.0 // indirect 100 | github.com/fatih/structtag v1.2.0 // indirect 101 | github.com/felixge/httpsnoop v1.0.4 // indirect 102 | github.com/firefart/nonamedreturns v1.0.6 // indirect 103 | github.com/fsnotify/fsnotify v1.8.0 // indirect 104 | github.com/fzipp/gocyclo v0.6.0 // indirect 105 | github.com/ghostiam/protogetter v0.3.15 // indirect 106 | github.com/go-critic/go-critic v0.13.0 // indirect 107 | github.com/go-logfmt/logfmt v0.6.0 // indirect 108 | github.com/go-logr/logr v1.4.3 // indirect 109 | github.com/go-logr/stdr v1.2.2 // indirect 110 | github.com/go-toolsmith/astcast v1.1.0 // indirect 111 | github.com/go-toolsmith/astcopy v1.1.0 // indirect 112 | github.com/go-toolsmith/astequal v1.2.0 // indirect 113 | github.com/go-toolsmith/astfmt v1.1.0 // indirect 114 | github.com/go-toolsmith/astp v1.1.0 // indirect 115 | github.com/go-toolsmith/strparse v1.1.0 // indirect 116 | github.com/go-toolsmith/typep v1.1.0 // indirect 117 | github.com/go-viper/mapstructure/v2 v2.4.0 // indirect 118 | github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect 119 | github.com/gobwas/glob v0.2.3 // indirect 120 | github.com/gofrs/flock v0.12.1 // indirect 121 | github.com/gogo/protobuf v1.3.2 // indirect 122 | github.com/golang-jwt/jwt/v5 v5.2.2 // indirect 123 | github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect 124 | github.com/golangci/go-printf-func-name v0.1.0 // indirect 125 | github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect 126 | github.com/golangci/golangci-lint/v2 v2.4.0 // indirect 127 | github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95 // indirect 128 | github.com/golangci/misspell v0.7.0 // indirect 129 | github.com/golangci/plugin-module-register v0.1.2 // indirect 130 | github.com/golangci/revgrep v0.8.0 // indirect 131 | github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e // indirect 132 | github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect 133 | github.com/google/go-cmp v0.7.0 // indirect 134 | github.com/google/s2a-go v0.1.9 // indirect 135 | github.com/google/uuid v1.6.0 // indirect 136 | github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect 137 | github.com/googleapis/gax-go/v2 v2.14.2 // indirect 138 | github.com/gordonklaus/ineffassign v0.1.0 // indirect 139 | github.com/gostaticanalysis/analysisutil v0.7.1 // indirect 140 | github.com/gostaticanalysis/comment v1.5.0 // indirect 141 | github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect 142 | github.com/gostaticanalysis/nilerr v0.1.1 // indirect 143 | github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect 144 | github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect 145 | github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect 146 | github.com/hashicorp/go-version v1.7.0 // indirect 147 | github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 148 | github.com/hashicorp/hcl v1.0.0 // indirect 149 | github.com/hexops/gotextdiff v1.0.3 // indirect 150 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 151 | github.com/jgautheron/goconst v1.8.2 // indirect 152 | github.com/jingyugao/rowserrcheck v1.1.1 // indirect 153 | github.com/jjti/go-spancheck v0.6.5 // indirect 154 | github.com/jpillora/backoff v1.0.0 // indirect 155 | github.com/julz/importas v0.2.0 // indirect 156 | github.com/karamaru-alpha/copyloopvar v1.2.1 // indirect 157 | github.com/kisielk/errcheck v1.9.0 // indirect 158 | github.com/kkHAIKE/contextcheck v1.1.6 // indirect 159 | github.com/kulti/thelper v0.6.3 // indirect 160 | github.com/kunwardeep/paralleltest v1.0.14 // indirect 161 | github.com/kylelemons/godebug v1.1.0 // indirect 162 | github.com/lasiar/canonicalheader v1.1.2 // indirect 163 | github.com/ldez/exptostd v0.4.4 // indirect 164 | github.com/ldez/gomoddirectives v0.7.0 // indirect 165 | github.com/ldez/grignotin v0.10.0 // indirect 166 | github.com/ldez/tagliatelle v0.7.1 // indirect 167 | github.com/ldez/usetesting v0.5.0 // indirect 168 | github.com/leonklingele/grouper v1.1.2 // indirect 169 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 170 | github.com/macabu/inamedparam v0.2.0 // indirect 171 | github.com/magiconair/properties v1.8.6 // indirect 172 | github.com/manuelarte/embeddedstructfieldcheck v0.3.0 // indirect 173 | github.com/manuelarte/funcorder v0.5.0 // indirect 174 | github.com/maratori/testableexamples v1.0.0 // indirect 175 | github.com/maratori/testpackage v1.1.1 // indirect 176 | github.com/matoous/godox v1.1.0 // indirect 177 | github.com/mattn/go-colorable v0.1.14 // indirect 178 | github.com/mattn/go-isatty v0.0.20 // indirect 179 | github.com/mattn/go-localereader v0.0.1 // indirect 180 | github.com/mattn/go-runewidth v0.0.16 // indirect 181 | github.com/mgechev/revive v1.11.0 // indirect 182 | github.com/mitchellh/go-homedir v1.1.0 // indirect 183 | github.com/mitchellh/mapstructure v1.5.0 // indirect 184 | github.com/moricho/tparallel v0.3.2 // indirect 185 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect 186 | github.com/muesli/cancelreader v0.2.2 // indirect 187 | github.com/muesli/termenv v0.16.0 // indirect 188 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 189 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect 190 | github.com/nakabonne/nestif v0.3.1 // indirect 191 | github.com/nishanths/exhaustive v0.12.0 // indirect 192 | github.com/nishanths/predeclared v0.2.2 // indirect 193 | github.com/nunnatsa/ginkgolinter v0.20.0 // indirect 194 | github.com/oklog/ulid v1.3.1 // indirect 195 | github.com/pelletier/go-toml v1.9.5 // indirect 196 | github.com/pelletier/go-toml/v2 v2.2.4 // indirect 197 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect 198 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 199 | github.com/polyfloyd/go-errorlint v1.8.0 // indirect 200 | github.com/prometheus/client_model v0.6.2 // indirect 201 | github.com/prometheus/procfs v0.16.1 // indirect 202 | github.com/prometheus/sigv4 v0.2.0 // indirect 203 | github.com/quasilyte/go-ruleguard v0.4.4 // indirect 204 | github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect 205 | github.com/quasilyte/gogrep v0.5.0 // indirect 206 | github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect 207 | github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect 208 | github.com/raeperd/recvcheck v0.2.0 // indirect 209 | github.com/rivo/uniseg v0.4.7 // indirect 210 | github.com/rogpeppe/go-internal v1.14.1 // indirect 211 | github.com/ryancurrah/gomodguard v1.4.1 // indirect 212 | github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect 213 | github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect 214 | github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect 215 | github.com/sashamelentyev/interfacebloat v1.1.0 // indirect 216 | github.com/sashamelentyev/usestdlibvars v1.29.0 // indirect 217 | github.com/securego/gosec/v2 v2.22.7 // indirect 218 | github.com/sirupsen/logrus v1.9.3 // indirect 219 | github.com/sivchari/containedctx v1.0.3 // indirect 220 | github.com/sonatard/noctx v0.4.0 // indirect 221 | github.com/sourcegraph/go-diff v0.7.0 // indirect 222 | github.com/spf13/afero v1.14.0 // indirect 223 | github.com/spf13/cast v1.5.0 // indirect 224 | github.com/spf13/cobra v1.9.1 // indirect 225 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 226 | github.com/spf13/pflag v1.0.7 // indirect 227 | github.com/spf13/viper v1.12.0 // indirect 228 | github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect 229 | github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect 230 | github.com/stretchr/objx v0.5.2 // indirect 231 | github.com/subosito/gotenv v1.4.1 // indirect 232 | github.com/tdakkota/asciicheck v0.4.1 // indirect 233 | github.com/tetafro/godot v1.5.1 // indirect 234 | github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect 235 | github.com/timonwong/loggercheck v0.11.0 // indirect 236 | github.com/tomarrell/wrapcheck/v2 v2.11.0 // indirect 237 | github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect 238 | github.com/ultraware/funlen v0.2.0 // indirect 239 | github.com/ultraware/whitespace v0.2.0 // indirect 240 | github.com/uudashr/gocognit v1.2.0 // indirect 241 | github.com/uudashr/iface v1.4.1 // indirect 242 | github.com/xen0n/gosmopolitan v1.3.0 // indirect 243 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 244 | github.com/yagipy/maintidx v1.0.0 // indirect 245 | github.com/yeya24/promlinter v0.3.0 // indirect 246 | github.com/ykadowak/zerologlint v0.1.5 // indirect 247 | gitlab.com/bosi/decorder v0.4.2 // indirect 248 | go-simpler.org/musttag v0.13.1 // indirect 249 | go-simpler.org/sloglint v0.11.1 // indirect 250 | go.augendre.info/arangolint v0.2.0 // indirect 251 | go.augendre.info/fatcontext v0.8.0 // indirect 252 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 253 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect 254 | go.opentelemetry.io/contrib/propagators/autoprop v0.54.0 // indirect 255 | go.opentelemetry.io/contrib/propagators/aws v1.29.0 // indirect 256 | go.opentelemetry.io/contrib/propagators/b3 v1.29.0 // indirect 257 | go.opentelemetry.io/contrib/propagators/jaeger v1.29.0 // indirect 258 | go.opentelemetry.io/contrib/propagators/ot v1.29.0 // indirect 259 | go.opentelemetry.io/otel v1.36.0 // indirect 260 | go.opentelemetry.io/otel/bridge/opentracing v1.31.0 // indirect 261 | go.opentelemetry.io/otel/metric v1.36.0 // indirect 262 | go.opentelemetry.io/otel/sdk v1.36.0 // indirect 263 | go.opentelemetry.io/otel/trace v1.36.0 // indirect 264 | go.uber.org/automaxprocs v1.6.0 // indirect 265 | go.uber.org/multierr v1.11.0 // indirect 266 | go.uber.org/zap v1.27.0 // indirect 267 | golang.org/x/crypto v0.41.0 // indirect 268 | golang.org/x/exp/typeparams v0.0.0-20250620022241-b7579e27df2b // indirect 269 | golang.org/x/mod v0.27.0 // indirect 270 | golang.org/x/net v0.43.0 // indirect 271 | golang.org/x/oauth2 v0.30.0 // indirect 272 | golang.org/x/sync v0.16.0 // indirect 273 | golang.org/x/sys v0.36.0 // indirect 274 | golang.org/x/text v0.28.0 // indirect 275 | golang.org/x/time v0.12.0 // indirect 276 | golang.org/x/tools v0.36.0 // indirect 277 | google.golang.org/api v0.242.0 // indirect 278 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect 279 | google.golang.org/grpc v1.73.0 // indirect 280 | google.golang.org/protobuf v1.36.6 // indirect 281 | gopkg.in/ini.v1 v1.67.0 // indirect 282 | gopkg.in/yaml.v2 v2.4.0 // indirect 283 | gopkg.in/yaml.v3 v3.0.1 // indirect 284 | honnef.co/go/tools v0.6.1 // indirect 285 | k8s.io/apimachinery v0.32.3 // indirect 286 | k8s.io/client-go v0.32.3 // indirect 287 | k8s.io/klog/v2 v2.130.1 // indirect 288 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect 289 | mvdan.cc/gofumpt v0.8.0 // indirect 290 | mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 // indirect 291 | ) 292 | 293 | // Overriding to use latest commit. 294 | replace gopkg.in/alecthomas/kingpin.v2 => github.com/alecthomas/kingpin v1.3.8-0.20210301060133-17f40c25f497 295 | 296 | tool github.com/golangci/golangci-lint/v2/cmd/golangci-lint 297 | -------------------------------------------------------------------------------- /pkg/scrape/scraper.go: -------------------------------------------------------------------------------- 1 | package scrape 2 | 3 | import ( 4 | "compress/gzip" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "os" 9 | "regexp" 10 | "strconv" 11 | "strings" 12 | "sync" 13 | "time" 14 | 15 | "github.com/go-kit/log" 16 | "github.com/go-kit/log/level" 17 | config_util "github.com/prometheus/common/config" 18 | "github.com/prometheus/prometheus/config" 19 | "github.com/prometheus/prometheus/model/exemplar" 20 | "github.com/prometheus/prometheus/model/labels" 21 | "github.com/prometheus/prometheus/model/textparse" 22 | "github.com/prometheus/prometheus/model/timestamp" 23 | ) 24 | 25 | type PromScraper struct { 26 | httpConfigFile string 27 | scrapeURL string 28 | scrapeFilePath string 29 | timeout time.Duration 30 | logger log.Logger 31 | series map[string]SeriesSet 32 | lastScrapeContentType string 33 | maxBodySize int64 34 | } 35 | 36 | type scrapeOpts struct { 37 | httpConfigFile string 38 | timeout time.Duration 39 | maxBodySize int64 40 | } 41 | 42 | type ScraperOption func(*scrapeOpts) 43 | 44 | func WithTimeout(timeout time.Duration) ScraperOption { 45 | return func(opts *scrapeOpts) { 46 | opts.timeout = timeout 47 | } 48 | } 49 | 50 | func WithMaxBodySize(maxBodySize int64) ScraperOption { 51 | return func(opts *scrapeOpts) { 52 | opts.maxBodySize = maxBodySize 53 | } 54 | } 55 | 56 | func WithHttpConfigFile(file string) ScraperOption { 57 | return func(opts *scrapeOpts) { 58 | opts.httpConfigFile = file 59 | } 60 | } 61 | 62 | func NewPromScraper(scrapeURL string, scrapeFile string, logger log.Logger, opts ...ScraperOption) *PromScraper { 63 | scOpts := &scrapeOpts{ 64 | timeout: 10 * time.Second, 65 | maxBodySize: 10 * 1024 * 1024, 66 | httpConfigFile: "", 67 | } 68 | 69 | for _, opt := range opts { 70 | opt(scOpts) 71 | } 72 | 73 | return &PromScraper{ 74 | scrapeURL: scrapeURL, 75 | scrapeFilePath: scrapeFile, 76 | logger: logger, 77 | timeout: scOpts.timeout, 78 | maxBodySize: scOpts.maxBodySize, 79 | httpConfigFile: scOpts.httpConfigFile, 80 | 81 | series: make(map[string]SeriesSet), 82 | } 83 | } 84 | 85 | func (ps *PromScraper) Scrape() (*Result, error) { 86 | if ps.scrapeFilePath != "" { 87 | return ps.scrapeFile() 88 | } 89 | 90 | return ps.scrapeHTTP() 91 | } 92 | 93 | func (ps *PromScraper) scrapeFile() (*Result, error) { 94 | var ( 95 | seriesSet map[string]SeriesSet 96 | seriesScrapeText SeriesScrapeText 97 | ) 98 | 99 | // Don't use os.ReadFile(); manually open the file so we can create an 100 | // io.LimitReader from the file to enforce max body size. 101 | f, err := os.Open(ps.scrapeFilePath) 102 | if err != nil { 103 | return &Result{}, fmt.Errorf("failed to open file %s to scrape metrics: %w", ps.scrapeFilePath, err) 104 | } 105 | defer f.Close() 106 | 107 | body, err := io.ReadAll(io.LimitReader(f, ps.maxBodySize)) 108 | if err != nil { 109 | return &Result{}, fmt.Errorf("failed reading file %s to scrape metrics: %w", ps.scrapeFilePath, err) 110 | } 111 | 112 | if int64(len(body)) >= ps.maxBodySize { 113 | level.Warn(ps.logger).Log( 114 | "msg", "metric file body size limit exceeded", 115 | "limit_bytes", ps.maxBodySize, 116 | "body_size", len(body), 117 | ) 118 | return &Result{}, fmt.Errorf("metric file body size exceeded limit of %d bytes", ps.maxBodySize) 119 | } 120 | 121 | // assume that scraping metrics from a file implies they're in text format. 122 | contentType := "text/plain" 123 | ps.lastScrapeContentType = contentType 124 | seriesSet, scrapeErr := ps.extractMetrics(body, contentType) 125 | if scrapeErr != nil { 126 | return &Result{}, fmt.Errorf("failed to extract metrics from file: %w", scrapeErr) 127 | } 128 | seriesScrapeText = ps.extractMetricSeriesText(body) 129 | 130 | return &Result{ 131 | Series: seriesSet, 132 | UsedContentType: contentType, 133 | SeriesScrapeText: seriesScrapeText, 134 | }, nil 135 | } 136 | 137 | func (ps *PromScraper) scrapeHTTP() (*Result, error) { 138 | var ( 139 | seriesSet map[string]SeriesSet 140 | scrapeErr error 141 | seriesScrapeText SeriesScrapeText 142 | textScrapeErr error 143 | wg sync.WaitGroup 144 | ) 145 | 146 | httpClient := http.DefaultClient 147 | if ps.httpConfigFile != "" { 148 | httpCfg, _, err := config_util.LoadHTTPConfigFile(ps.httpConfigFile) 149 | if err != nil { 150 | return &Result{}, fmt.Errorf("failed to load HTTP configuration file %s: %w", ps.httpConfigFile, err) 151 | } 152 | 153 | if err = httpCfg.Validate(); err != nil { 154 | return &Result{}, fmt.Errorf("failed to validate HTTP configuration file %s: %w", ps.httpConfigFile, err) 155 | } 156 | 157 | httpClient, err = config_util.NewClientFromConfig(*httpCfg, "prom-scrape-analyzer") 158 | if err != nil { 159 | return &Result{}, fmt.Errorf("failed to create HTTP client from configuration file %s: %w", ps.httpConfigFile, err) 160 | } 161 | } 162 | 163 | // First prioritize scraping PrometheusProto format for access to data about created timestamps and native histograms 164 | wg.Add(1) 165 | go func() { 166 | defer wg.Done() 167 | 168 | req, err := ps.setupRequest([]config.ScrapeProtocol{ 169 | config.PrometheusProto, 170 | config.OpenMetricsText1_0_0, 171 | config.PrometheusText0_0_4, 172 | config.OpenMetricsText0_0_1, 173 | }) 174 | if err != nil { 175 | return 176 | } 177 | 178 | resp, err := httpClient.Do(req) 179 | if err != nil { 180 | scrapeErr = err 181 | return 182 | } 183 | defer resp.Body.Close() 184 | 185 | contentType, body, err := ps.readResponse(resp) 186 | if err != nil { 187 | scrapeErr = err 188 | return 189 | } 190 | ps.lastScrapeContentType = contentType 191 | 192 | seriesSet, scrapeErr = ps.extractMetrics(body, contentType) 193 | }() 194 | 195 | // If the above response is in proto format then it isn't in a human-readable format, 196 | // so request a format known to be readable in case the user wants to view the series. 197 | wg.Add(1) 198 | go func() { 199 | defer wg.Done() 200 | 201 | textReq, err := ps.setupRequest([]config.ScrapeProtocol{ 202 | config.OpenMetricsText1_0_0, 203 | config.PrometheusText0_0_4, 204 | config.OpenMetricsText0_0_1, 205 | }) 206 | if err != nil { 207 | textScrapeErr = err 208 | return 209 | } 210 | 211 | textResp, err := httpClient.Do(textReq) 212 | if err != nil { 213 | textScrapeErr = err 214 | return 215 | } 216 | defer textResp.Body.Close() 217 | _, textBody, err := ps.readResponse(textResp) 218 | if err != nil { 219 | textScrapeErr = err 220 | return 221 | } 222 | 223 | seriesScrapeText = ps.extractMetricSeriesText(textBody) 224 | }() 225 | 226 | wg.Wait() 227 | 228 | if scrapeErr != nil { 229 | return nil, scrapeErr 230 | } 231 | if textScrapeErr != nil { 232 | return nil, textScrapeErr 233 | } 234 | 235 | return &Result{ 236 | Series: seriesSet, 237 | UsedContentType: ps.lastScrapeContentType, 238 | SeriesScrapeText: seriesScrapeText, 239 | }, nil 240 | } 241 | 242 | func (ps *PromScraper) LastScrapeContentType() string { 243 | return ps.lastScrapeContentType 244 | } 245 | 246 | func (ps *PromScraper) setupRequest(accept []config.ScrapeProtocol) (*http.Request, error) { 247 | // Scrape the URL and analyze the cardinality. 248 | req, err := http.NewRequest("GET", ps.scrapeURL, nil) 249 | if err != nil { 250 | return nil, err 251 | } 252 | 253 | acceptHeader := acceptHeader(accept) 254 | req.Header.Set("Accept", acceptHeader) 255 | req.Header.Set("Accept-Encoding", "gzip") 256 | req.Header.Set("X-Prometheus-Scrape-Timeout-Seconds", strconv.FormatInt(int64(ps.timeout.Seconds()), 10)) 257 | return req, nil 258 | } 259 | 260 | func (ps *PromScraper) readResponse(resp *http.Response) (string, []byte, error) { 261 | defer func() { 262 | _, _ = io.Copy(io.Discard, resp.Body) 263 | _ = resp.Body.Close() 264 | }() 265 | 266 | if resp.StatusCode != http.StatusOK { 267 | return "", nil, fmt.Errorf("server returned HTTP status %s", resp.Status) 268 | } 269 | 270 | var reader io.Reader = resp.Body 271 | 272 | if resp.Header.Get("Content-Encoding") == "gzip" { 273 | var err error 274 | reader, err = gzip.NewReader(resp.Body) 275 | if err != nil { 276 | return "", nil, err 277 | } 278 | defer reader.(*gzip.Reader).Close() 279 | } 280 | 281 | body, err := io.ReadAll(io.LimitReader(reader, ps.maxBodySize)) 282 | if err != nil { 283 | return "", nil, err 284 | } 285 | 286 | if int64(len(body)) >= ps.maxBodySize { 287 | level.Warn(ps.logger).Log( 288 | "msg", "response body size limit exceeded", 289 | "limit_bytes", ps.maxBodySize, 290 | "body_size", len(body), 291 | ) 292 | return "", nil, fmt.Errorf("response body size exceeded limit of %d bytes", ps.maxBodySize) 293 | } 294 | 295 | return resp.Header.Get("Content-Type"), body, nil 296 | } 297 | 298 | func (ps *PromScraper) extractMetrics(body []byte, contentType string) (map[string]SeriesSet, error) { 299 | metrics := make(map[string]SeriesSet) 300 | parser, err := textparse.New(body, contentType, "", false, false, false, nil) 301 | if err != nil { 302 | return nil, fmt.Errorf("failed to create parser: %w", err) 303 | } 304 | 305 | var ( 306 | lset labels.Labels 307 | currentType string 308 | baseMetricName string 309 | defTime = timestamp.FromTime(time.Now()) 310 | ) 311 | 312 | for { 313 | entry, err := parser.Next() 314 | if err == io.EOF { 315 | break 316 | } 317 | if err != nil { 318 | level.Debug(ps.logger).Log("msg", "failed to parse entry", "err", err) 319 | continue 320 | } 321 | 322 | switch entry { 323 | case textparse.EntryType: 324 | metricName, metricType := parser.Type() 325 | currentType = string(metricType) 326 | baseMetricName = string(metricName) 327 | 328 | continue // Skip to next iteration as we don't need to process this entry further 329 | 330 | case textparse.EntrySeries: 331 | parser.Labels(&lset) 332 | metricName := lset.Get(labels.MetricName) 333 | if metricName == "" { 334 | level.Debug(ps.logger).Log("msg", "metric name not found in labels", "labels", lset.String()) 335 | continue 336 | } 337 | 338 | // Combine series belonging to the same classic histogram or summary metric 339 | // ex: the series elapsed_seconds_bucket, elapsed_seconds_count, elapsed_seconds_sum are tracked as elapsed_seconds 340 | if currentType == "histogram" || currentType == "summary" { 341 | metricName = baseMetricName 342 | } 343 | 344 | if _, ok := metrics[metricName]; !ok { 345 | metrics[metricName] = make(SeriesSet) 346 | } 347 | 348 | hash := lset.Hash() 349 | series := Series{ 350 | Name: metricName, 351 | Labels: lset.Copy(), 352 | Type: currentType, // clone type string 353 | } 354 | 355 | _, ts, _ := parser.Series() 356 | t := defTime 357 | if ts != nil { 358 | t = *ts 359 | } 360 | 361 | ctMs := parser.CreatedTimestamp() 362 | if ctMs != 0 { 363 | series.CreatedTimestamp = ctMs 364 | level.Debug(ps.logger).Log("msg", "found CT zero sample", "metric", metricName, "ct", ctMs) 365 | } 366 | 367 | // Collect exemplars for this series 368 | var exemplars []Exemplar 369 | ex := &exemplar.Exemplar{} 370 | for parser.Exemplar(ex) { 371 | exemplars = append(exemplars, Exemplar{ 372 | Labels: ex.Labels.Copy(), 373 | Value: ex.Value, 374 | Ts: ex.Ts, 375 | HasTs: ex.HasTs, 376 | }) 377 | ex = &exemplar.Exemplar{} 378 | } 379 | series.Exemplars = exemplars 380 | 381 | metrics[metricName][hash] = series 382 | 383 | level.Debug(ps.logger).Log( 384 | "msg", "found series", 385 | "metric", metricName, 386 | "labels", lset.String(), 387 | "type", currentType, 388 | "timestamp", t, 389 | "has_ct_zero", series.CreatedTimestamp != 0, 390 | "exemplar_count", len(exemplars), 391 | ) 392 | case textparse.EntryHistogram: 393 | // Processing solely for native histograms 394 | parser.Labels(&lset) 395 | metricName := lset.Get(labels.MetricName) 396 | if metricName == "" { 397 | level.Debug(ps.logger).Log("msg", "histogram metric name not found in labels", "labels", lset.String()) 398 | continue 399 | } 400 | 401 | if _, ok := metrics[metricName]; !ok { 402 | metrics[metricName] = make(SeriesSet) 403 | } 404 | 405 | hash := lset.Hash() 406 | series := Series{ 407 | Name: metricName, 408 | Labels: lset.Copy(), 409 | Type: "native_histogram", 410 | } 411 | 412 | _, ts, h, fh := parser.Histogram() 413 | t := defTime 414 | if ts != nil { 415 | t = *ts 416 | } 417 | 418 | ctMs := parser.CreatedTimestamp() 419 | if ctMs != 0 { 420 | series.CreatedTimestamp = ctMs 421 | level.Debug(ps.logger).Log( 422 | "msg", "found CT zero sample for histogram", 423 | "metric", metricName, 424 | "ct", ctMs, 425 | ) 426 | } 427 | 428 | // Collect exemplars for this histogram 429 | var exemplars []Exemplar 430 | ex := &exemplar.Exemplar{} 431 | for parser.Exemplar(ex) { 432 | exemplars = append(exemplars, Exemplar{ 433 | Labels: ex.Labels.Copy(), 434 | Value: ex.Value, 435 | Ts: ex.Ts, 436 | HasTs: ex.HasTs, 437 | }) 438 | ex = &exemplar.Exemplar{} 439 | } 440 | series.Exemplars = exemplars 441 | 442 | metrics[metricName][hash] = series 443 | 444 | if h != nil { 445 | level.Debug(ps.logger).Log( 446 | "msg", "found histogram", 447 | "metric", metricName, 448 | "labels", lset.String(), 449 | "type", "histogram", 450 | "timestamp", t, 451 | "has_ct_zero", series.CreatedTimestamp != 0, 452 | "exemplar_count", len(exemplars), 453 | ) 454 | } else if fh != nil { 455 | level.Debug(ps.logger).Log( 456 | "msg", "found float histogram", 457 | "metric", metricName, 458 | "labels", lset.String(), 459 | "type", "float_histogram", 460 | "timestamp", t, 461 | "has_ct_zero", series.CreatedTimestamp != 0, 462 | "exemplar_count", len(exemplars), 463 | ) 464 | } 465 | 466 | default: 467 | level.Debug(ps.logger).Log("msg", "unknown entry type", "type", entry) 468 | } 469 | } 470 | 471 | return metrics, nil 472 | } 473 | 474 | func (ps *PromScraper) extractMetricSeriesText(textScrapeResponse []byte) SeriesScrapeText { 475 | seriesScrapeText := make(map[string]string) 476 | metricNamePattern := regexp.MustCompile(`^[^{\s]+`) 477 | lines := strings.Split(string(textScrapeResponse), "\n") 478 | // a metric's series are not on consecutive lines for histogram and summary metrics 479 | // so a strings.Builder is kept in memory for each metric 480 | metricLines := make(map[string]*strings.Builder) 481 | 482 | // For histograms and summaries, we need to map the base metric name to all its suffixes 483 | baseMetrics := make(map[string]bool) 484 | 485 | // First pass: identify histogram and summary base metrics 486 | for _, line := range lines { 487 | if strings.HasPrefix(line, "# TYPE") { 488 | parts := strings.Fields(line) 489 | if len(parts) >= 4 && (parts[3] == "histogram" || parts[3] == "summary") { 490 | baseMetrics[parts[2]] = true 491 | } 492 | } 493 | } 494 | 495 | // Second pass: collect all lines 496 | for _, line := range lines { 497 | if len(line) == 0 { 498 | continue 499 | } 500 | 501 | var parsedMetric string 502 | if strings.HasPrefix(line, "#") { 503 | parts := strings.Split(line, " ") 504 | if len(parts) >= 3 { 505 | parsedMetric = parts[2] 506 | } 507 | } else { 508 | parsedMetric = metricNamePattern.FindString(line) 509 | } 510 | if parsedMetric == "" { 511 | _ = level.Debug(ps.logger).Log("msg", "failed to parse metric name from line", "line", line) 512 | continue 513 | } 514 | 515 | // For histogram and summary metrics, also add the line to the base metric 516 | baseMetric := parsedMetric 517 | for prefix := range baseMetrics { 518 | if strings.HasPrefix(parsedMetric, prefix+"_") { 519 | baseMetric = prefix 520 | break 521 | } 522 | } 523 | 524 | sb, ok := metricLines[baseMetric] 525 | if !ok { 526 | sb = &strings.Builder{} 527 | metricLines[baseMetric] = sb 528 | } 529 | 530 | sb.WriteString(line) 531 | sb.WriteString("\n") 532 | } 533 | 534 | for metric, sb := range metricLines { 535 | seriesScrapeText[metric] = sb.String() 536 | } 537 | return seriesScrapeText 538 | } 539 | 540 | // acceptHeader transforms preference from the options into specific header values as 541 | // https://www.rfc-editor.org/rfc/rfc9110.html#name-accept defines. 542 | // No validation is here, we expect scrape protocols to be validated already. 543 | func acceptHeader(sps []config.ScrapeProtocol) string { 544 | var vals []string 545 | weight := len(config.ScrapeProtocolsHeaders) + 1 546 | for _, sp := range sps { 547 | vals = append(vals, fmt.Sprintf("%s;q=0.%d", config.ScrapeProtocolsHeaders[sp], weight)) 548 | weight-- 549 | } 550 | // Default match anything. 551 | vals = append(vals, fmt.Sprintf("*/*;q=0.%d", weight)) 552 | return strings.Join(vals, ",") 553 | } 554 | -------------------------------------------------------------------------------- /cmd/cardinality.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "strconv" 8 | "strings" 9 | "time" 10 | 11 | "github.com/charmbracelet/bubbles/cursor" 12 | "github.com/charmbracelet/bubbles/help" 13 | "github.com/charmbracelet/bubbles/key" 14 | "github.com/charmbracelet/bubbles/spinner" 15 | "github.com/charmbracelet/bubbles/table" 16 | "github.com/charmbracelet/bubbles/textinput" 17 | tea "github.com/charmbracelet/bubbletea" 18 | "github.com/charmbracelet/lipgloss" 19 | "github.com/go-kit/log" 20 | "github.com/go-kit/log/level" 21 | "github.com/lithammer/fuzzysearch/fuzzy" 22 | "github.com/oklog/run" 23 | "github.com/opentracing/opentracing-go" 24 | "github.com/pkg/errors" 25 | "github.com/prometheus/client_golang/prometheus" 26 | "github.com/thanos-io/thanos/pkg/extkingpin" 27 | 28 | "github.com/pedro-stanaka/prom-scrape-analyzer/internal" 29 | "github.com/pedro-stanaka/prom-scrape-analyzer/pkg/scrape" 30 | ) 31 | 32 | type cardinalityOptions struct { 33 | Options 34 | } 35 | 36 | func (o *cardinalityOptions) addFlags(app extkingpin.AppClause) { 37 | o.AddFlags(app) 38 | } 39 | 40 | var baseStyle = lipgloss.NewStyle(). 41 | BorderStyle(lipgloss.NormalBorder()). 42 | BorderForeground(lipgloss.Color("240")) 43 | 44 | var tableHelp = help.New().ShortHelpView([]key.Binding{ 45 | key.NewBinding( 46 | key.WithKeys("up", "k"), 47 | key.WithHelp("↑/k", "up"), 48 | ), 49 | key.NewBinding( 50 | key.WithKeys("down", "j"), 51 | key.WithHelp("↓/j", "down"), 52 | ), 53 | key.NewBinding( 54 | key.WithKeys("m"), 55 | key.WithHelp("m", "search metrics"), 56 | ), 57 | key.NewBinding( 58 | key.WithKeys("l"), 59 | key.WithHelp("l", "search label names"), 60 | ), 61 | key.NewBinding( 62 | key.WithKeys("/"), 63 | key.WithHelp("/", "search metric and label names"), 64 | ), 65 | key.NewBinding( 66 | key.WithKeys("enter", "v"), 67 | key.WithHelp("v/↵", "view series in text editor"), 68 | ), 69 | key.NewBinding( 70 | key.WithKeys("e"), 71 | key.WithHelp("e", "view exemplars"), 72 | ), 73 | }) 74 | var searchHelp = help.New().ShortHelpView([]key.Binding{ 75 | key.NewBinding( 76 | key.WithKeys("enter"), 77 | key.WithHelp("↵", "explore table"), 78 | ), 79 | key.NewBinding( 80 | key.WithKeys("esc"), 81 | key.WithHelp("esc:", "clear search"), 82 | ), 83 | }) 84 | 85 | var noFiltering func(info scrape.SeriesInfo) bool = nil 86 | 87 | var flashDuration = 5 * time.Second 88 | 89 | type searchType int 90 | 91 | const ( 92 | searchNone searchType = iota 93 | searchMetrics 94 | searchLabels 95 | searchAll 96 | ) 97 | 98 | type seriesTable struct { 99 | table table.Model 100 | spinner spinner.Model 101 | searchInput textinput.Model 102 | seriesMap scrape.SeriesMap 103 | seriesScrapeText scrape.SeriesScrapeText 104 | loading bool 105 | search searchType 106 | err error 107 | infoTitle string 108 | flashMsg internal.TextFlash 109 | program *tea.Program 110 | logger log.Logger 111 | } 112 | 113 | func newModel(sm map[string]scrape.SeriesSet, height int, logger log.Logger) *seriesTable { 114 | tbl := table.New( 115 | table.WithColumns([]table.Column{ 116 | {Title: "Name", Width: 60}, 117 | {Title: "Cardinality", Width: 16}, 118 | {Title: "Type", Width: 10}, 119 | {Title: "Labels", Width: 80}, 120 | {Title: "Created TS", Width: 50}, 121 | }), 122 | table.WithFocused(true), 123 | table.WithHeight(height), 124 | ) 125 | 126 | tblStyle := table.DefaultStyles() 127 | tblStyle.Header = tblStyle.Header. 128 | BorderStyle(lipgloss.NormalBorder()). 129 | BorderForeground(lipgloss.Color("240")). 130 | BorderBottom(true). 131 | Bold(false) 132 | tblStyle.Selected = tblStyle.Selected. 133 | Foreground(lipgloss.Color("229")). 134 | Background(lipgloss.Color("57")). 135 | Bold(false) 136 | tbl.SetStyles(tblStyle) 137 | 138 | sp := spinner.New() 139 | sp.Spinner = spinner.Dot 140 | sp.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205")) 141 | 142 | ti := textinput.New() 143 | ti.Placeholder = "Search value" 144 | 145 | m := &seriesTable{ 146 | table: tbl, 147 | seriesMap: sm, 148 | spinner: sp, 149 | searchInput: ti, 150 | loading: true, 151 | search: searchNone, 152 | flashMsg: internal.TextFlash{}, 153 | logger: logger, 154 | } 155 | 156 | return m 157 | } 158 | 159 | func (m *seriesTable) setTableRows(filter func(info scrape.SeriesInfo) bool) { 160 | var rows []table.Row 161 | for _, r := range m.seriesMap.AsRows() { 162 | if filter == nil || filter(r) { 163 | rows = append(rows, table.Row{ 164 | r.Name, 165 | strconv.Itoa(r.Cardinality), 166 | r.Type, 167 | r.Labels, 168 | r.CreatedTS, 169 | }) 170 | } 171 | } 172 | 173 | m.table.SetRows(rows) 174 | } 175 | 176 | func (m *seriesTable) View() string { 177 | if m.loading { 178 | return m.spinner.View() + "\nLoading..." 179 | } 180 | if m.err != nil { 181 | return baseStyle.Render("Exiting with error: " + m.err.Error()) 182 | } 183 | 184 | var view strings.Builder 185 | 186 | if m.search != searchNone { 187 | view.WriteString(baseStyle.Render(m.searchInput.View())) 188 | } 189 | 190 | flashText := m.flashMsg.View() 191 | if flashText != "" { 192 | if m.search != searchNone { 193 | view.WriteString("\n") 194 | } 195 | view.WriteString(flashText) 196 | } 197 | 198 | view.WriteString("\n") 199 | view.WriteString(baseStyle.Render(m.table.View())) 200 | 201 | view.WriteString("\n") 202 | if m.searchInput.Focused() { 203 | view.WriteString(searchHelp) 204 | } else { 205 | view.WriteString(tableHelp) 206 | } 207 | 208 | if m.search != searchNone { 209 | total := len(m.seriesMap) 210 | filtered := len(m.table.Rows()) 211 | view.WriteString("\n") 212 | view.WriteString(fmt.Sprintf("Showing %d out of %d metrics", filtered, total)) 213 | } else { 214 | total := len(m.seriesMap) 215 | view.WriteString("\n") 216 | view.WriteString(fmt.Sprintf("Total metrics: %d", total)) 217 | view.WriteString("\n") 218 | view.WriteString(m.infoTitle) 219 | } 220 | 221 | return view.String() 222 | } 223 | 224 | func (m *seriesTable) Init() tea.Cmd { 225 | return m.spinner.Tick 226 | } 227 | 228 | func (m *seriesTable) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 229 | var cmd tea.Cmd 230 | switch msg := msg.(type) { 231 | case tea.KeyMsg: 232 | switch msg.String() { 233 | case "ctrl+c": 234 | return m, tea.Quit 235 | } 236 | case spinner.TickMsg: 237 | if m.loading { 238 | m.spinner, cmd = m.spinner.Update(msg) 239 | return m, cmd 240 | } 241 | case internal.UpdateTextFlashMsg: 242 | m.flashMsg, cmd = m.flashMsg.Update(msg) 243 | return m, cmd 244 | case error: 245 | m.loading = false 246 | m.err = msg 247 | return m, tea.Quit 248 | case *scrape.Result: 249 | m.loading = false 250 | m.seriesMap = msg.Series 251 | m.seriesScrapeText = msg.SeriesScrapeText 252 | m.infoTitle = m.formatInfoTitle(msg) 253 | m.setTableRows(noFiltering) 254 | return m, nil 255 | } 256 | 257 | if m.search != searchNone { 258 | return m.updateWhileSearchingMetrics(msg) 259 | } else { 260 | return m.updateWhileBrowsingTable(msg) 261 | } 262 | } 263 | 264 | func (m *seriesTable) updateWhileBrowsingTable(msg tea.Msg) (tea.Model, tea.Cmd) { 265 | var cmd tea.Cmd 266 | switch msg := msg.(type) { 267 | case tea.KeyMsg: 268 | switch msg.String() { 269 | case "q": 270 | return m, tea.Quit 271 | case "esc": 272 | if m.table.Focused() { 273 | m.table.Blur() 274 | } else { 275 | m.table.Focus() 276 | } 277 | case "down": 278 | if m.table.Cursor() < len(m.table.Rows())-1 { 279 | m.table, cmd = m.table.Update(msg) 280 | } 281 | return m, cmd 282 | case "up": 283 | m.table, cmd = m.table.Update(msg) 284 | return m, cmd 285 | case "e": 286 | selectedRow := m.table.SelectedRow() 287 | if len(selectedRow) == 0 { 288 | return m, m.flashMsg.Flash("No series available to view exemplars", internal.Error, flashDuration) 289 | } 290 | 291 | metricName := selectedRow[0] 292 | seriesSet, exists := m.seriesMap[metricName] 293 | if !exists { 294 | return m, m.flashMsg.Flash("Metric not found", internal.Error, flashDuration) 295 | } 296 | 297 | // Collect all exemplars from all series of this metric 298 | var exemplarText strings.Builder 299 | exemplarText.WriteString(fmt.Sprintf("# Exemplars for metric: %s\n\n", metricName)) 300 | 301 | hasExemplars := false 302 | for _, series := range seriesSet { 303 | if len(series.Exemplars) > 0 { 304 | hasExemplars = true 305 | exemplarText.WriteString(fmt.Sprintf("## Series: %s\n", series.Labels.String())) 306 | for i, ex := range series.Exemplars { 307 | exemplarText.WriteString(fmt.Sprintf(" [%d] %s\n", i+1, ex.String())) 308 | } 309 | exemplarText.WriteString("\n") 310 | } 311 | } 312 | 313 | if !hasExemplars { 314 | return m, m.flashMsg.Flash("No exemplars found for this metric", internal.Info, flashDuration) 315 | } 316 | 317 | // Create temp file and open in editor 318 | tmpFile := internal.CreateTempFileWithContent(exemplarText.String()) 319 | if tmpFile == "" { 320 | return m, m.flashMsg.Flash("Failed to create temporary file", internal.Error, flashDuration) 321 | } 322 | 323 | editor := os.Getenv("EDITOR") 324 | if editor == "" { 325 | os.Remove(tmpFile) 326 | return m, m.flashMsg.Flash("Please set the EDITOR environment variable", internal.Error, flashDuration) 327 | } 328 | 329 | // Run the editor 330 | cmd := exec.Command(editor, tmpFile) 331 | cmd.Stdin = os.Stdin 332 | cmd.Stdout = os.Stdout 333 | cmd.Stderr = os.Stderr 334 | 335 | err := m.program.ReleaseTerminal() 336 | if err != nil { 337 | return m, m.flashMsg.Flash("Error preparing to view exemplars: "+err.Error(), internal.Error, flashDuration) 338 | } 339 | 340 | err = cmd.Run() 341 | 342 | // Restore terminal after editor closes 343 | restoreErr := m.program.RestoreTerminal() 344 | if restoreErr != nil { 345 | _ = level.Warn(m.logger).Log("msg", "Failed to restore terminal", "err", restoreErr) 346 | } 347 | 348 | if err != nil { 349 | return m, m.flashMsg.Flash("Failed to run editor: "+err.Error(), internal.Error, flashDuration) 350 | } 351 | return m, nil 352 | case "enter", "v": 353 | selectedRow := m.table.SelectedRow() 354 | if len(selectedRow) == 0 { 355 | return m, m.flashMsg.Flash("No series available to open", internal.Error, flashDuration) 356 | } 357 | 358 | metricName := selectedRow[0] 359 | seriesText := m.seriesScrapeText[metricName] 360 | 361 | tmpFile := internal.CreateTempFileWithContent(seriesText) 362 | if tmpFile == "" { 363 | return m, m.flashMsg.Flash("Failed to create temporary file", internal.Error, flashDuration) 364 | } 365 | 366 | editor := os.Getenv("EDITOR") 367 | if editor == "" { 368 | os.Remove(tmpFile) 369 | return m, m.flashMsg.Flash("Please set the EDITOR environment variable", internal.Error, flashDuration) 370 | } 371 | 372 | // Run the editor and wait for it to complete 373 | cmd := exec.Command(editor, tmpFile) 374 | cmd.Stdin = os.Stdin 375 | cmd.Stdout = os.Stdout 376 | cmd.Stderr = os.Stderr 377 | 378 | // Pause the program to allow the editor to run without interference 379 | err := m.program.ReleaseTerminal() 380 | if err != nil { 381 | return m, m.flashMsg.Flash("Error preparing to view series: "+err.Error(), internal.Error, flashDuration) 382 | } 383 | 384 | // Display the editor 385 | err = cmd.Run() 386 | 387 | // Restore terminal after editor closes 388 | restoreErr := m.program.RestoreTerminal() 389 | if restoreErr != nil { 390 | _ = level.Warn(m.logger).Log("msg", "Failed to restore terminal", "err", restoreErr) 391 | } 392 | 393 | // Ideally the temp file would be removed here but that causes issues with editors like vscode 394 | if err != nil { 395 | return m, m.flashMsg.Flash("Failed to run editor: "+err.Error(), internal.Error, flashDuration) 396 | } 397 | return m, nil 398 | case "/": 399 | m.search = searchAll 400 | m.searchInput.SetCursor(int(cursor.CursorBlink)) 401 | m.searchInput.CursorEnd() 402 | return m, m.searchInput.Focus() 403 | case "m": 404 | m.search = searchMetrics 405 | m.searchInput.SetCursor(int(cursor.CursorBlink)) 406 | m.searchInput.CursorEnd() 407 | return m, m.searchInput.Focus() 408 | case "l": 409 | m.search = searchLabels 410 | m.searchInput.SetCursor(int(cursor.CursorBlink)) 411 | m.searchInput.CursorEnd() 412 | return m, m.searchInput.Focus() 413 | } 414 | } 415 | 416 | m.table, cmd = m.table.Update(msg) 417 | return m, cmd 418 | } 419 | 420 | func (m *seriesTable) updateWhileSearchingMetrics(msg tea.Msg) (tea.Model, tea.Cmd) { 421 | var cmd tea.Cmd 422 | 423 | switch msg := msg.(type) { 424 | case tea.KeyMsg: 425 | switch msg.String() { 426 | case "enter": 427 | if !m.searchInput.Focused() { 428 | // enter should allow viewing metrics for the filtered row that's selected 429 | return m.updateWhileBrowsingTable(msg) 430 | } 431 | 432 | // Allow exploring the filtered table 433 | m.searchInput.SetCursor(int(cursor.CursorHide)) 434 | m.searchInput.Blur() 435 | m.table.Focus() 436 | return m, cmd 437 | case "esc": 438 | // Reset the search input and table back to their initial state 439 | m.searchInput.Reset() 440 | m.searchInput.Blur() 441 | m.setTableRows(noFiltering) 442 | 443 | // Hide the search input and restore control to the table 444 | m.search = searchNone 445 | m.table.Focus() 446 | return m, cmd 447 | default: 448 | if m.searchInput.Focused() { 449 | // Update the search value with the key press from this msg 450 | m.searchInput, cmd = m.searchInput.Update(msg) 451 | 452 | oldRowCount := len(m.table.Rows()) 453 | switch m.search { 454 | case searchNone: 455 | // Show all rows 456 | m.setTableRows(noFiltering) 457 | case searchMetrics: 458 | m.setTableRows(func(info scrape.SeriesInfo) bool { 459 | return fuzzy.MatchFold(m.searchInput.Value(), info.Name) 460 | }) 461 | case searchLabels: 462 | m.setTableRows(func(info scrape.SeriesInfo) bool { 463 | return fuzzy.MatchFold(m.searchInput.Value(), info.Labels) 464 | }) 465 | case searchAll: 466 | m.setTableRows(func(info scrape.SeriesInfo) bool { 467 | metricMatch := fuzzy.MatchFold(m.searchInput.Value(), info.Name) 468 | labelMatch := fuzzy.MatchFold(m.searchInput.Value(), info.Labels) 469 | return metricMatch || labelMatch 470 | }) 471 | } 472 | 473 | if oldRowCount != len(m.table.Rows()) { 474 | //Reset the selected row since the current index might exceed the filtered count 475 | m.table.SetCursor(0) 476 | } 477 | 478 | return m, cmd 479 | } 480 | } 481 | } 482 | 483 | if m.table.Focused() { 484 | // Allow navigating the filtered table 485 | return m.updateWhileBrowsingTable(msg) 486 | } 487 | 488 | m.searchInput, cmd = m.searchInput.Update(msg) 489 | return m, cmd 490 | } 491 | 492 | func (m *seriesTable) formatInfoTitle(sr *scrape.Result) string { 493 | return "Scrape used content type: " + sr.UsedContentType 494 | } 495 | 496 | func registerCardinalityCommand(app *extkingpin.App) { 497 | cmd := app.Command("cardinality", "Analyze the cardinality of a Prometheus scrape job.") 498 | opts := &cardinalityOptions{} 499 | opts.addFlags(cmd) 500 | cmd.Setup(func( 501 | g *run.Group, 502 | logger log.Logger, 503 | reg *prometheus.Registry, 504 | _ opentracing.Tracer, 505 | _ <-chan struct{}, 506 | _ bool, 507 | ) error { 508 | scrapeURL := opts.ScrapeURL 509 | scrapeFile := opts.ScrapeFile 510 | timeoutDuration := opts.Timeout 511 | httpConfigFile := opts.HttpConfigFile 512 | 513 | if scrapeURL == "" && scrapeFile == "" { 514 | return errors.New("No URL or file provided to scrape metrics. " + 515 | "Please supply a target to scrape via `--scrape.url` or `--scrape.file` flags.") 516 | } 517 | 518 | if scrapeURL != "" && scrapeFile != "" { 519 | return errors.New("The flags `--scrape.url` and `--scrape.file` are mutually exclusive.") 520 | } 521 | 522 | metricTable := newModel(nil, opts.OutputHeight, logger) 523 | p := tea.NewProgram(metricTable) 524 | metricTable.program = p 525 | 526 | // Create a channel to signal when scraping is complete 527 | scrapeDone := make(chan struct{}) 528 | 529 | g.Add(func() error { 530 | _, err := p.Run() 531 | return err 532 | }, func(error) { 533 | close(scrapeDone) 534 | }) 535 | 536 | g.Add(func() error { 537 | maxSize, err := opts.MaxScrapeSizeBytes() 538 | if err != nil { 539 | err = errors.Wrapf(err, "failed to parse max scrape size") 540 | p.Send(err) 541 | return err 542 | } 543 | 544 | level.Info(logger).Log( 545 | "msg", "scraping", 546 | "scrape_url", scrapeURL, 547 | "scrape_file", scrapeFile, 548 | "timeout", timeoutDuration, 549 | "max_size", maxSize, 550 | "http_config_file", httpConfigFile, 551 | ) 552 | 553 | t0 := time.Now() 554 | scraper := scrape.NewPromScraper( 555 | scrapeURL, 556 | scrapeFile, 557 | logger, 558 | scrape.WithTimeout(timeoutDuration), 559 | scrape.WithMaxBodySize(maxSize), 560 | scrape.WithHttpConfigFile(httpConfigFile), 561 | ) 562 | metrics, err := scraper.Scrape() 563 | if err != nil { 564 | p.Send(err) 565 | return err 566 | } 567 | 568 | // Send the scraped data to the UI 569 | level.Info(logger).Log("msg", "scraping complete", "duration", time.Since(t0)) 570 | p.Send(metrics) 571 | return nil 572 | }, func(error) {}) 573 | 574 | return nil 575 | }) 576 | } 577 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | 4d63.com/gocheckcompilerdirectives v1.3.0 h1:Ew5y5CtcAAQeTVKUVFrE7EwHMrTO6BggtEj8BZSjZ3A= 2 | 4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY= 3 | 4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= 4 | 4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= 5 | cloud.google.com/go/auth v0.16.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4= 6 | cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA= 7 | cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= 8 | cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= 9 | cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= 10 | cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= 11 | codeberg.org/chavacava/garif v0.2.0 h1:F0tVjhYbuOCnvNcU3YSpO6b3Waw6Bimy4K0mM8y6MfY= 12 | codeberg.org/chavacava/garif v0.2.0/go.mod h1:P2BPbVbT4QcvLZrORc2T29szK3xEOlnl0GiPTJmEqBQ= 13 | dev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKfwODoI1Y= 14 | dev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88YdiB0Ai4fXOzPI= 15 | dev.gaijin.team/go/golib v0.6.0 h1:v6nnznFTs4bppib/NyU1PQxobwDHwCXXl15P7DV5Zgo= 16 | dev.gaijin.team/go/golib v0.6.0/go.mod h1:uY1mShx8Z/aNHWDyAkZTkX+uCi5PdX7KsG1eDQa2AVE= 17 | github.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8= 18 | github.com/4meepo/tagalign v1.4.3/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c= 19 | github.com/Abirdcfly/dupword v0.1.6 h1:qeL6u0442RPRe3mcaLcbaCi2/Y/hOcdtw6DE9odjz9c= 20 | github.com/Abirdcfly/dupword v0.1.6/go.mod h1:s+BFMuL/I4YSiFv29snqyjwzDp4b65W2Kvy+PKzZ6cw= 21 | github.com/AlwxSin/noinlineerr v1.0.5 h1:RUjt63wk1AYWTXtVXbSqemlbVTb23JOSRiNsshj7TbY= 22 | github.com/AlwxSin/noinlineerr v1.0.5/go.mod h1:+QgkkoYrMH7RHvcdxdlI7vYYEdgeoFOVjU9sUhw/rQc= 23 | github.com/Antonboom/errname v1.1.0 h1:A+ucvdpMwlo/myWrkHEUEBWc/xuXdud23S8tmTb/oAE= 24 | github.com/Antonboom/errname v1.1.0/go.mod h1:O1NMrzgUcVBGIfi3xlVuvX8Q/VP/73sseCaAppfjqZw= 25 | github.com/Antonboom/nilnil v1.1.0 h1:jGxJxjgYS3VUUtOTNk8Z1icwT5ESpLH/426fjmQG+ng= 26 | github.com/Antonboom/nilnil v1.1.0/go.mod h1:b7sAlogQjFa1wV8jUW3o4PMzDVFLbTux+xnQdvzdcIE= 27 | github.com/Antonboom/testifylint v1.6.1 h1:6ZSytkFWatT8mwZlmRCHkWz1gPi+q6UBSbieji2Gj/o= 28 | github.com/Antonboom/testifylint v1.6.1/go.mod h1:k+nEkathI2NFjKO6HvwmSrbzUcQ6FAnbZV+ZRrnXPLI= 29 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U= 30 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= 31 | github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= 32 | github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= 33 | github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= 34 | github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= 35 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= 36 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= 37 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.7.0 h1:LkHbJbgF3YyvC53aqYGR+wWQDn2Rdp9AQdGndf9QvY4= 38 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.7.0/go.mod h1:QyiQdW4f4/BIfB8ZutZ2s+28RAgfa/pT+zS++ZHyM1I= 39 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4 v4.3.0 h1:bXwSugBiSbgtz7rOtbfGf+woewp4f06orW9OP5BjHLA= 40 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4 v4.3.0/go.mod h1:Y/HgrePTmGy9HjdSGTqZNa+apUpTVIEVKXJyARP2lrk= 41 | github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= 42 | github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= 43 | github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= 44 | github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= 45 | github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= 46 | github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 47 | github.com/Code-Hex/go-generics-cache v1.5.1 h1:6vhZGc5M7Y/YD8cIUcY8kcuQLB4cHR7U+0KMqAA0KcU= 48 | github.com/Code-Hex/go-generics-cache v1.5.1/go.mod h1:qxcC9kRVrct9rHeiYpFWSoW1vxyillCVzX13KZG8dl4= 49 | github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= 50 | github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= 51 | github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= 52 | github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= 53 | github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= 54 | github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= 55 | github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4= 56 | github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo= 57 | github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= 58 | github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= 59 | github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw= 60 | github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA= 61 | github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU= 62 | github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E= 63 | github.com/alecthomas/kingpin v1.3.8-0.20210301060133-17f40c25f497 h1:aDITxVUQ/3KBhpVWX57Vo9ntGTxoRw1F0T6/x/tRzNU= 64 | github.com/alecthomas/kingpin v1.3.8-0.20210301060133-17f40c25f497/go.mod h1:b6br6/pDFSfMkBgC96TbpOji05q5pa+v5rIlS0Y6XtI= 65 | github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg= 66 | github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= 67 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 68 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= 69 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 70 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 71 | github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= 72 | github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= 73 | github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= 74 | github.com/alexkohler/nakedret/v2 v2.0.6 h1:ME3Qef1/KIKr3kWX3nti3hhgNxw6aqN5pZmQiFSsuzQ= 75 | github.com/alexkohler/nakedret/v2 v2.0.6/go.mod h1:l3RKju/IzOMQHmsEvXwkqMDzHHvurNQfAgE1eVmT40Q= 76 | github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= 77 | github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= 78 | github.com/alfatraining/structtag v1.0.0 h1:2qmcUqNcCoyVJ0up879K614L9PazjBSFruTB0GOFjCc= 79 | github.com/alfatraining/structtag v1.0.0/go.mod h1:p3Xi5SwzTi+Ryj64DqjLWz7XurHxbGsq6y3ubePJPus= 80 | github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= 81 | github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= 82 | github.com/alingse/nilnesserr v0.2.0 h1:raLem5KG7EFVb4UIDAXgrv3N2JIaffeKNtcEXkEWd/w= 83 | github.com/alingse/nilnesserr v0.2.0/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= 84 | github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= 85 | github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= 86 | github.com/ashanbrown/forbidigo/v2 v2.1.0 h1:NAxZrWqNUQiDz19FKScQ/xvwzmij6BiOw3S0+QUQ+Hs= 87 | github.com/ashanbrown/forbidigo/v2 v2.1.0/go.mod h1:0zZfdNAuZIL7rSComLGthgc/9/n2FqspBOH90xlCHdA= 88 | github.com/ashanbrown/makezero/v2 v2.0.1 h1:r8GtKetWOgoJ4sLyUx97UTwyt2dO7WkGFHizn/Lo8TY= 89 | github.com/ashanbrown/makezero/v2 v2.0.1/go.mod h1:kKU4IMxmYW1M4fiEHMb2vc5SFoPzXvgbMR9gIp5pjSw= 90 | github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= 91 | github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= 92 | github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE= 93 | github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= 94 | github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM= 95 | github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= 96 | github.com/aws/aws-sdk-go-v2/config v1.29.14 h1:f+eEi/2cKCg9pqKBoAIwRGzVb70MRKqWX4dg1BDcSJM= 97 | github.com/aws/aws-sdk-go-v2/config v1.29.14/go.mod h1:wVPHWcIFv3WO89w0rE10gzf17ZYy+UVS1Geq8Iei34g= 98 | github.com/aws/aws-sdk-go-v2/credentials v1.17.67 h1:9KxtdcIA/5xPNQyZRgUSpYOE6j9Bc4+D7nZua0KGYOM= 99 | github.com/aws/aws-sdk-go-v2/credentials v1.17.67/go.mod h1:p3C44m+cfnbv763s52gCqrjaqyPikj9Sg47kUVaNZQQ= 100 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw= 101 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M= 102 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q= 103 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY= 104 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0= 105 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q= 106 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= 107 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= 108 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE= 109 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA= 110 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM= 111 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY= 112 | github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 h1:1Gw+9ajCV1jogloEv1RRnvfRFia2cL6c9cuKV2Ps+G8= 113 | github.com/aws/aws-sdk-go-v2/service/sso v1.25.3/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI= 114 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 h1:hXmVKytPfTy5axZ+fYbR5d0cFmC3JvwLm5kM83luako= 115 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs= 116 | github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 h1:1XuUZ8mYJw9B6lzAkXhqHlJd/XvaX32evhproijJEZY= 117 | github.com/aws/aws-sdk-go-v2/service/sts v1.33.19/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4= 118 | github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= 119 | github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= 120 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 121 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 122 | github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= 123 | github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= 124 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 125 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 126 | github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w= 127 | github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo= 128 | github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= 129 | github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= 130 | github.com/bombsimon/wsl/v4 v4.7.0 h1:1Ilm9JBPRczjyUs6hvOPKvd7VL1Q++PL8M0SXBDf+jQ= 131 | github.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg= 132 | github.com/bombsimon/wsl/v5 v5.1.1 h1:cQg5KJf9FlctAH4cpL9vLKnziYknoCMCdqXl0wjl72Q= 133 | github.com/bombsimon/wsl/v5 v5.1.1/go.mod h1:Gp8lD04z27wm3FANIUPZycXp+8huVsn0oxc+n4qfV9I= 134 | github.com/breml/bidichk v0.3.3 h1:WSM67ztRusf1sMoqH6/c4OBCUlRVTKq+CbSeo0R17sE= 135 | github.com/breml/bidichk v0.3.3/go.mod h1:ISbsut8OnjB367j5NseXEGGgO/th206dVa427kR8YTE= 136 | github.com/breml/errchkjson v0.4.1 h1:keFSS8D7A2T0haP9kzZTi7o26r7kE3vymjZNeNDRDwg= 137 | github.com/breml/errchkjson v0.4.1/go.mod h1:a23OvR6Qvcl7DG/Z4o0el6BRAjKnaReoPQFciAl9U3s= 138 | github.com/butuzov/ireturn v0.4.0 h1:+s76bF/PfeKEdbG8b54aCocxXmi0wvYdOVsWxVO7n8E= 139 | github.com/butuzov/ireturn v0.4.0/go.mod h1:ghI0FrCmap8pDWZwfPisFD1vEc56VKH4NpQUxDHta70= 140 | github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc= 141 | github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= 142 | github.com/catenacyber/perfsprint v0.9.1 h1:5LlTp4RwTooQjJCvGEFV6XksZvWE7wCOUvjD2z0vls0= 143 | github.com/catenacyber/perfsprint v0.9.1/go.mod h1:q//VWC2fWbcdSLEY1R3l8n0zQCDPdE4IjZwyY1HMunM= 144 | github.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc= 145 | github.com/ccojocar/zxcvbn-go v1.0.4/go.mod h1:3GxGX+rHmueTUMvm5ium7irpyjmm7ikxYFOSJB21Das= 146 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 147 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 148 | github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= 149 | github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= 150 | github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= 151 | github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= 152 | github.com/charmbracelet/bubbletea v1.3.9 h1:OBYdfRo6QnlIcXNmcoI2n1NNS65Nk6kI2L2FO1puS/4= 153 | github.com/charmbracelet/bubbletea v1.3.9/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= 154 | github.com/charmbracelet/colorprofile v0.3.0 h1:KtLh9uuu1RCt+Hml4s6Hz+kB1PfV3wi++1h5ia65yKQ= 155 | github.com/charmbracelet/colorprofile v0.3.0/go.mod h1:oHJ340RS2nmG1zRGPmhJKJ/jf4FPNNk0P39/wBPA1G0= 156 | github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= 157 | github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= 158 | github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= 159 | github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= 160 | github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= 161 | github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= 162 | github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= 163 | github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= 164 | github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= 165 | github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= 166 | github.com/ckaznocha/intrange v0.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7LspvJs= 167 | github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk= 168 | github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k= 169 | github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= 170 | github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= 171 | github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= 172 | github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= 173 | github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= 174 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 175 | github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= 176 | github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= 177 | github.com/daixiang0/gci v0.13.7 h1:+0bG5eK9vlI08J+J/NWGbWPTNiXPG4WhNLJOkSxWITQ= 178 | github.com/daixiang0/gci v0.13.7/go.mod h1:812WVN6JLFY9S6Tv76twqmNqevN0pa3SX3nih0brVzQ= 179 | github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY= 180 | github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc= 181 | github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo= 182 | github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= 183 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 184 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 185 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 186 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 187 | github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= 188 | github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= 189 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 190 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 191 | github.com/digitalocean/godo v1.152.0 h1:WRgkPMogZSXEJK70IkZKTB/PsMn16hMQ+NI3wCIQdzA= 192 | github.com/digitalocean/godo v1.152.0/go.mod h1:tYeiWY5ZXVpU48YaFv0M5irUFHXGorZpDNm7zzdWMzM= 193 | github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= 194 | github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= 195 | github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= 196 | github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= 197 | github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw= 198 | github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 199 | github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= 200 | github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= 201 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 202 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 203 | github.com/efficientgo/core v1.0.0-rc.3 h1:X6CdgycYWDcbYiJr1H1+lQGzx13o7bq3EUkbB9DsSPc= 204 | github.com/efficientgo/core v1.0.0-rc.3/go.mod h1:FfGdkzWarkuzOlY04VY+bGfb1lWrjaL6x/GLcQ4vJps= 205 | github.com/efficientgo/tools/extkingpin v0.0.0-20220817170617-6c25e3b627dd h1:VaYzzXeUbC5fVheskcKVNOyJMEYD+HgrJNzIAg/mRIM= 206 | github.com/efficientgo/tools/extkingpin v0.0.0-20220817170617-6c25e3b627dd/go.mod h1:ZV0utlglOczUWv3ih2AbqPSoLoFzdplUYxwV62eZi6Q= 207 | github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= 208 | github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 209 | github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= 210 | github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= 211 | github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= 212 | github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= 213 | github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= 214 | github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= 215 | github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= 216 | github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= 217 | github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= 218 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 219 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 220 | github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= 221 | github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= 222 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 223 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 224 | github.com/firefart/nonamedreturns v1.0.6 h1:vmiBcKV/3EqKY3ZiPxCINmpS431OcE1S47AQUwhrg8E= 225 | github.com/firefart/nonamedreturns v1.0.6/go.mod h1:R8NisJnSIpvPWheCq0mNRXJok6D8h7fagJTF8EMEwCo= 226 | github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= 227 | github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= 228 | github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= 229 | github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 230 | github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= 231 | github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 232 | github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= 233 | github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= 234 | github.com/ghostiam/protogetter v0.3.15 h1:1KF5sXel0HE48zh1/vn0Loiw25A9ApyseLzQuif1mLY= 235 | github.com/ghostiam/protogetter v0.3.15/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA= 236 | github.com/go-critic/go-critic v0.13.0 h1:kJzM7wzltQasSUXtYyTl6UaPVySO6GkaR1thFnJ6afY= 237 | github.com/go-critic/go-critic v0.13.0/go.mod h1:M/YeuJ3vOCQDnP2SU+ZhjgRzwzcBW87JqLpMJLrZDLI= 238 | github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= 239 | github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= 240 | github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= 241 | github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= 242 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 243 | github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= 244 | github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 245 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 246 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 247 | github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= 248 | github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 249 | github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= 250 | github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= 251 | github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= 252 | github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 253 | github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= 254 | github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= 255 | github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM= 256 | github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= 257 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 258 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 259 | github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= 260 | github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= 261 | github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= 262 | github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw= 263 | github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= 264 | github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ= 265 | github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw= 266 | github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY= 267 | github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco= 268 | github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4= 269 | github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= 270 | github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA= 271 | github.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk= 272 | github.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus= 273 | github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= 274 | github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw= 275 | github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= 276 | github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= 277 | github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= 278 | github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= 279 | github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= 280 | github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY= 281 | github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= 282 | github.com/go-zookeeper/zk v1.0.4 h1:DPzxraQx7OrPyXq2phlGlNSIyWEsAox0RJmjTseMV6I= 283 | github.com/go-zookeeper/zk v1.0.4/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= 284 | github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= 285 | github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= 286 | github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= 287 | github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= 288 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 289 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 290 | github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= 291 | github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 292 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 293 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 294 | github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw= 295 | github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E= 296 | github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU= 297 | github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s= 298 | github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE= 299 | github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY= 300 | github.com/golangci/golangci-lint/v2 v2.4.0 h1:qz6O6vr7kVzXJqyvHjHSz5fA3D+PM8v96QU5gxZCNWM= 301 | github.com/golangci/golangci-lint/v2 v2.4.0/go.mod h1:Oq7vuAf6L1iNL34uHDcsIF6Mnc0amOPdsT3/GlpHD+I= 302 | github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95 h1:AkK+w9FZBXlU/xUmBtSJN1+tAI4FIvy5WtnUnY8e4p8= 303 | github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95/go.mod h1:k9mmcyWKSTMcPPvQUCfRWWQ9VHJ1U9Dc0R7kaXAgtnQ= 304 | github.com/golangci/misspell v0.7.0 h1:4GOHr/T1lTW0hhR4tgaaV1WS/lJ+ncvYCoFKmqJsj0c= 305 | github.com/golangci/misspell v0.7.0/go.mod h1:WZyyI2P3hxPY2UVHs3cS8YcllAeyfquQcKfdeE9AFVg= 306 | github.com/golangci/plugin-module-register v0.1.2 h1:e5WM6PO6NIAEcij3B053CohVp3HIYbzSuP53UAYgOpg= 307 | github.com/golangci/plugin-module-register v0.1.2/go.mod h1:1+QGTsKBvAIvPvoY/os+G5eoqxWn70HYDm2uvUyGuVw= 308 | github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s= 309 | github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= 310 | github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e h1:ai0EfmVYE2bRA5htgAG9r7s3tHsfjIhN98WshBTJ9jM= 311 | github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e/go.mod h1:Vrn4B5oR9qRwM+f54koyeH3yzphlecwERs0el27Fr/s= 312 | github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqtt0ssnqSJNNndxe69DOQ24A5h7+i3KpM= 313 | github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e/go.mod h1:h+wZwLjUTJnm/P2rwlbJdRPZXOzaT36/FwnPnY2inzc= 314 | github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= 315 | github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= 316 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 317 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 318 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 319 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 320 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 321 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 322 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 323 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 324 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 325 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 326 | github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a h1://KbezygeMJZCSHH+HgUZiTeSoiuFspbMg1ge+eFj18= 327 | github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= 328 | github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= 329 | github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= 330 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 331 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 332 | github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= 333 | github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= 334 | github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0= 335 | github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w= 336 | github.com/gophercloud/gophercloud/v2 v2.7.0 h1:o0m4kgVcPgHlcXiWAjoVxGd8QCmvM5VU+YM71pFbn0E= 337 | github.com/gophercloud/gophercloud/v2 v2.7.0/go.mod h1:Ki/ILhYZr/5EPebrPL9Ej+tUg4lqx71/YH2JWVeU+Qk= 338 | github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= 339 | github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= 340 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 341 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 342 | github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= 343 | github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= 344 | github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= 345 | github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= 346 | github.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8= 347 | github.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc= 348 | github.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk= 349 | github.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY= 350 | github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk= 351 | github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= 352 | github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= 353 | github.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+3Zh0sUGqw8= 354 | github.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs= 355 | github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= 356 | github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= 357 | github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= 358 | github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= 359 | github.com/hashicorp/consul/api v1.32.0 h1:5wp5u780Gri7c4OedGEPzmlUEzi0g2KyiPphSr6zjVg= 360 | github.com/hashicorp/consul/api v1.32.0/go.mod h1:Z8YgY0eVPukT/17ejW+l+C7zJmKwgPHtjU1q16v/Y40= 361 | github.com/hashicorp/cronexpr v1.1.2 h1:wG/ZYIKT+RT3QkOdgYc+xsKWVRgnxJ1OJtjjy84fJ9A= 362 | github.com/hashicorp/cronexpr v1.1.2/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= 363 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 364 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 365 | github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 366 | github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 367 | github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= 368 | github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= 369 | github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= 370 | github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 371 | github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo= 372 | github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw= 373 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 374 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 375 | github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= 376 | github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= 377 | github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= 378 | github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= 379 | github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= 380 | github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 381 | github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 382 | github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= 383 | github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 384 | github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o8jga4= 385 | github.com/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 386 | github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= 387 | github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 388 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 389 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 390 | github.com/hashicorp/nomad/api v0.0.0-20241218080744-e3ac00f30eec h1:+YBzb977VrmffaCX/OBm17dEVJUcWn5dW+eqs3aIJ/A= 391 | github.com/hashicorp/nomad/api v0.0.0-20241218080744-e3ac00f30eec/go.mod h1:svtxn6QnrQ69P23VvIWMR34tg3vmwLz4UdUzm1dSCgE= 392 | github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= 393 | github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= 394 | github.com/hetznercloud/hcloud-go/v2 v2.21.1 h1:IH3liW8/cCRjfJ4cyqYvw3s1ek+KWP8dl1roa0lD8JM= 395 | github.com/hetznercloud/hcloud-go/v2 v2.21.1/go.mod h1:XOaYycZJ3XKMVWzmqQ24/+1V7ormJHmPdck/kxrNnQA= 396 | github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= 397 | github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= 398 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 399 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 400 | github.com/ionos-cloud/sdk-go/v6 v6.3.4 h1:jTvGl4LOF8v8OYoEIBNVwbFoqSGAFqn6vGE7sp7/BqQ= 401 | github.com/ionos-cloud/sdk-go/v6 v6.3.4/go.mod h1:wCVwNJ/21W29FWFUv+fNawOTMlFoP1dS3L+ZuztFW48= 402 | github.com/jgautheron/goconst v1.8.2 h1:y0XF7X8CikZ93fSNT6WBTb/NElBu9IjaY7CCYQrCMX4= 403 | github.com/jgautheron/goconst v1.8.2/go.mod h1:A0oxgBCHy55NQn6sYpO7UdnA9p+h7cPtoOZUmvNIako= 404 | github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= 405 | github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= 406 | github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8= 407 | github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU= 408 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 409 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 410 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 411 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 412 | github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= 413 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 414 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 415 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 416 | github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ= 417 | github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY= 418 | github.com/karamaru-alpha/copyloopvar v1.2.1 h1:wmZaZYIjnJ0b5UoKDjUHrikcV0zuPyyxI4SVplLd2CI= 419 | github.com/karamaru-alpha/copyloopvar v1.2.1/go.mod h1:nFmMlFNlClC2BPvNaHMdkirmTJxVCY0lhxBtlfOypMM= 420 | github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= 421 | github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= 422 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 423 | github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M= 424 | github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= 425 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 426 | github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE= 427 | github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg= 428 | github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= 429 | github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= 430 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 431 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 432 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 433 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 434 | github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= 435 | github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= 436 | github.com/kunwardeep/paralleltest v1.0.14 h1:wAkMoMeGX/kGfhQBPODT/BL8XhK23ol/nuQ3SwFaUw8= 437 | github.com/kunwardeep/paralleltest v1.0.14/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk= 438 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 439 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 440 | github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= 441 | github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= 442 | github.com/ldez/exptostd v0.4.4 h1:58AtQjnLcT/tI5W/1KU7xE/O7zW9RAWB6c/ScQAnfus= 443 | github.com/ldez/exptostd v0.4.4/go.mod h1:QfdzPw6oHjFVdNV7ILoPu5sw3OZ3OG1JS0I5JN3J4Js= 444 | github.com/ldez/gomoddirectives v0.7.0 h1:EOx8Dd56BZYSez11LVgdj025lKwlP0/E5OLSl9HDwsY= 445 | github.com/ldez/gomoddirectives v0.7.0/go.mod h1:wR4v8MN9J8kcwvrkzrx6sC9xe9Cp68gWYCsda5xvyGc= 446 | github.com/ldez/grignotin v0.10.0 h1:NQPeh1E/Eza4F0exCeC1WkpnLvgUcQDT8MQ1vOLML0E= 447 | github.com/ldez/grignotin v0.10.0/go.mod h1:oR4iCKUP9fwoeO6vCQeD7M5SMxCT6xdVas4vg0h1LaI= 448 | github.com/ldez/tagliatelle v0.7.1 h1:bTgKjjc2sQcsgPiT902+aadvMjCeMHrY7ly2XKFORIk= 449 | github.com/ldez/tagliatelle v0.7.1/go.mod h1:3zjxUpsNB2aEZScWiZTHrAXOl1x25t3cRmzfK1mlo2I= 450 | github.com/ldez/usetesting v0.5.0 h1:3/QtzZObBKLy1F4F8jLuKJiKBjjVFi1IavpoWbmqLwc= 451 | github.com/ldez/usetesting v0.5.0/go.mod h1:Spnb4Qppf8JTuRgblLrEWb7IE6rDmUpGvxY3iRrzvDQ= 452 | github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= 453 | github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= 454 | github.com/linode/linodego v1.52.1 h1:HJ1cz1n9n3chRP9UrtqmP91+xTi0Q5l+H/4z4tpkwgQ= 455 | github.com/linode/linodego v1.52.1/go.mod h1:zEN2sX+cSdp67EuRY1HJiyuLujoa7HqvVwNEcJv3iXw= 456 | github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= 457 | github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= 458 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 459 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 460 | github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE= 461 | github.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U= 462 | github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= 463 | github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= 464 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 465 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 466 | github.com/manuelarte/embeddedstructfieldcheck v0.3.0 h1:VhGqK8gANDvFYDxQkjPbv7/gDJtsGU9k6qj/hC2hgso= 467 | github.com/manuelarte/embeddedstructfieldcheck v0.3.0/go.mod h1:LSo/IQpPfx1dXMcX4ibZCYA7Yy6ayZHIaOGM70+1Wy8= 468 | github.com/manuelarte/funcorder v0.5.0 h1:llMuHXXbg7tD0i/LNw8vGnkDTHFpTnWqKPI85Rknc+8= 469 | github.com/manuelarte/funcorder v0.5.0/go.mod h1:Yt3CiUQthSBMBxjShjdXMexmzpP8YGvGLjrxJNkO2hA= 470 | github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= 471 | github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= 472 | github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= 473 | github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc= 474 | github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4= 475 | github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs= 476 | github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= 477 | github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= 478 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 479 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 480 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 481 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 482 | github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= 483 | github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= 484 | github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= 485 | github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 486 | github.com/mgechev/revive v1.11.0 h1:b/gLLpBE427o+Xmd8G58gSA+KtBwxWinH/A565Awh0w= 487 | github.com/mgechev/revive v1.11.0/go.mod h1:tI0oLF/2uj+InHCBLrrqfTKfjtFTBCFFfG05auyzgdw= 488 | github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE= 489 | github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE= 490 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 491 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 492 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 493 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 494 | github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= 495 | github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= 496 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 497 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 498 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 499 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 500 | github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= 501 | github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= 502 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= 503 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= 504 | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= 505 | github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 506 | github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= 507 | github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= 508 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 509 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 510 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= 511 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 512 | github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= 513 | github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= 514 | github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= 515 | github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= 516 | github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= 517 | github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= 518 | github.com/nunnatsa/ginkgolinter v0.20.0 h1:OmWLkAFO2HUTYcU6mprnKud1Ey5pVdiVNYGO5HVicx8= 519 | github.com/nunnatsa/ginkgolinter v0.20.0/go.mod h1:dCIuFlTPfQerXgGUju3VygfAFPdC5aE1mdacCDKDJcQ= 520 | github.com/oklog/run v1.2.0 h1:O8x3yXwah4A73hJdlrwo/2X6J62gE5qTMusH0dvz60E= 521 | github.com/oklog/run v1.2.0/go.mod h1:mgDbKRSwPhJfesJ4PntqFUbKQRZ50NgmZTSPlFA0YFk= 522 | github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= 523 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 524 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= 525 | github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= 526 | github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= 527 | github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= 528 | github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= 529 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 530 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 531 | github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= 532 | github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= 533 | github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= 534 | github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 535 | github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= 536 | github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= 537 | github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= 538 | github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= 539 | github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= 540 | github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= 541 | github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= 542 | github.com/ovh/go-ovh v1.8.0 h1:eQ5TAAFZvZAVarQir62oaTL+8a503pIBuOWVn72iGtY= 543 | github.com/ovh/go-ovh v1.8.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c= 544 | github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= 545 | github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 546 | github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= 547 | github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 548 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= 549 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= 550 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 551 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 552 | github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= 553 | github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= 554 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 555 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 556 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 557 | github.com/polyfloyd/go-errorlint v1.8.0 h1:DL4RestQqRLr8U4LygLw8g2DX6RN1eBJOpa2mzsrl1Q= 558 | github.com/polyfloyd/go-errorlint v1.8.0/go.mod h1:G2W0Q5roxbLCt0ZQbdoxQxXktTjwNyDbEaj3n7jvl4s= 559 | github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= 560 | github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= 561 | github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= 562 | github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= 563 | github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= 564 | github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= 565 | github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= 566 | github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= 567 | github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= 568 | github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= 569 | github.com/prometheus/prometheus v0.305.0 h1:UO/LsM32/E9yBDtvQj8tN+WwhbyWKR10lO35vmFLx0U= 570 | github.com/prometheus/prometheus v0.305.0/go.mod h1:JG+jKIDUJ9Bn97anZiCjwCxRyAx+lpcEQ0QnZlUlbwY= 571 | github.com/prometheus/sigv4 v0.2.0 h1:qDFKnHYFswJxdzGeRP63c4HlH3Vbn1Yf/Ao2zabtVXk= 572 | github.com/prometheus/sigv4 v0.2.0/go.mod h1:D04rqmAaPPEUkjRQxGqjoxdyJuyCh6E0M18fZr0zBiE= 573 | github.com/quasilyte/go-ruleguard v0.4.4 h1:53DncefIeLX3qEpjzlS1lyUmQoUEeOWPFWqaTJq9eAQ= 574 | github.com/quasilyte/go-ruleguard v0.4.4/go.mod h1:Vl05zJ538vcEEwu16V/Hdu7IYZWyKSwIy4c88Ro1kRE= 575 | github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= 576 | github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= 577 | github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= 578 | github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= 579 | github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= 580 | github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= 581 | github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= 582 | github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= 583 | github.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI= 584 | github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= 585 | github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= 586 | github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= 587 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 588 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 589 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 590 | github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= 591 | github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 592 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 593 | github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g= 594 | github.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I= 595 | github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= 596 | github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= 597 | github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0= 598 | github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4= 599 | github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= 600 | github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= 601 | github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= 602 | github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= 603 | github.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ= 604 | github.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8= 605 | github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33 h1:KhF0WejiUTDbL5X55nXowP7zNopwpowa6qaMAWyIE+0= 606 | github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33/go.mod h1:792k1RTU+5JeMXm35/e2Wgp71qPH/DmDoZrRc+EFZDk= 607 | github.com/securego/gosec/v2 v2.22.7 h1:8/9P+oTYI4yIpAzccQKVsg1/90Po+JzGtAhqoHImDeM= 608 | github.com/securego/gosec/v2 v2.22.7/go.mod h1:510TFNDMrIPytokyHQAVLvPeDr41Yihn2ak8P+XQfNE= 609 | github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= 610 | github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 611 | github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= 612 | github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= 613 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 614 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 615 | github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= 616 | github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= 617 | github.com/sonatard/noctx v0.4.0 h1:7MC/5Gg4SQ4lhLYR6mvOP6mQVSxCrdyiExo7atBs27o= 618 | github.com/sonatard/noctx v0.4.0/go.mod h1:64XdbzFb18XL4LporKXp8poqZtPKbCrqQ402CV+kJas= 619 | github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= 620 | github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= 621 | github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= 622 | github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= 623 | github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= 624 | github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= 625 | github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= 626 | github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= 627 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= 628 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 629 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 630 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 631 | github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= 632 | github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 633 | github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= 634 | github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= 635 | github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= 636 | github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= 637 | github.com/stackitcloud/stackit-sdk-go/core v0.17.2 h1:jPyn+i8rkp2hM80+hOg0B/1EVRbMt778Tr5RWyK1m2E= 638 | github.com/stackitcloud/stackit-sdk-go/core v0.17.2/go.mod h1:8KIw3czdNJ9sdil9QQimxjR6vHjeINFrRv0iZ67wfn0= 639 | github.com/stbenjam/no-sprintf-host-port v0.2.0 h1:i8pxvGrt1+4G0czLr/WnmyH7zbZ8Bg8etvARQ1rpyl4= 640 | github.com/stbenjam/no-sprintf-host-port v0.2.0/go.mod h1:eL0bQ9PasS0hsyTyfTjjG+E80QIyPnBVQbYZyv20Jfk= 641 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 642 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 643 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 644 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 645 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 646 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 647 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 648 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 649 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 650 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 651 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 652 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 653 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 654 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 655 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 656 | github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= 657 | github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= 658 | github.com/tdakkota/asciicheck v0.4.1 h1:bm0tbcmi0jezRA2b5kg4ozmMuGAFotKI3RZfrhfovg8= 659 | github.com/tdakkota/asciicheck v0.4.1/go.mod h1:0k7M3rCfRXb0Z6bwgvkEIMleKH3kXNz9UqJ9Xuqopr8= 660 | github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= 661 | github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= 662 | github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= 663 | github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= 664 | github.com/tetafro/godot v1.5.1 h1:PZnjCol4+FqaEzvZg5+O8IY2P3hfY9JzRBNPv1pEDS4= 665 | github.com/tetafro/godot v1.5.1/go.mod h1:cCdPtEndkmqqrhiCfkmxDodMQJ/f3L1BCNskCUZdTwk= 666 | github.com/thanos-io/thanos v0.37.3-0.20250221064218-f230915c1c13 h1:LXVl2kJzwlGeVzqCE1dQP1Owa2luzwm0DqOqO3qI3Ns= 667 | github.com/thanos-io/thanos v0.37.3-0.20250221064218-f230915c1c13/go.mod h1:qdKuuBXavtJIiyeqn3Ie2o2ZOEkqLQelPUroxQIBnBs= 668 | github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 h1:9LPGD+jzxMlnk5r6+hJnar67cgpDIz/iyD+rfl5r2Vk= 669 | github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= 670 | github.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M= 671 | github.com/timonwong/loggercheck v0.11.0/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= 672 | github.com/tomarrell/wrapcheck/v2 v2.11.0 h1:BJSt36snX9+4WTIXeJ7nvHBQBcm1h2SjQMSlmQ6aFSU= 673 | github.com/tomarrell/wrapcheck/v2 v2.11.0/go.mod h1:wFL9pDWDAbXhhPZZt+nG8Fu+h29TtnZ2MW6Lx4BRXIU= 674 | github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= 675 | github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= 676 | github.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI= 677 | github.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA= 678 | github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g= 679 | github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= 680 | github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA= 681 | github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU= 682 | github.com/uudashr/iface v1.4.1 h1:J16Xl1wyNX9ofhpHmQ9h9gk5rnv2A6lX/2+APLTo0zU= 683 | github.com/uudashr/iface v1.4.1/go.mod h1:pbeBPlbuU2qkNDn0mmfrxP2X+wjPMIQAy+r1MBXSXtg= 684 | github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs= 685 | github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI= 686 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 687 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 688 | github.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM= 689 | github.com/xen0n/gosmopolitan v1.3.0/go.mod h1:rckfr5T6o4lBtM1ga7mLGKZmLxswUoH1zxHgNXOsEt4= 690 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 691 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 692 | github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= 693 | github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= 694 | github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs= 695 | github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4= 696 | github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= 697 | github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= 698 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 699 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 700 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 701 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 702 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 703 | github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 704 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 705 | gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= 706 | gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= 707 | go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= 708 | go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= 709 | go-simpler.org/musttag v0.13.1 h1:lw2sJyu7S1X8lc8zWUAdH42y+afdcCnHhWpnkWvd6vU= 710 | go-simpler.org/musttag v0.13.1/go.mod h1:8r450ehpMLQgvpb6sg+hV5Ur47eH6olp/3yEanfG97k= 711 | go-simpler.org/sloglint v0.11.1 h1:xRbPepLT/MHPTCA6TS/wNfZrDzkGvCCqUv4Bdwc3H7s= 712 | go-simpler.org/sloglint v0.11.1/go.mod h1:2PowwiCOK8mjiF+0KGifVOT8ZsCNiFzvfyJeJOIt8MQ= 713 | go.augendre.info/arangolint v0.2.0 h1:2NP/XudpPmfBhQKX4rMk+zDYIj//qbt4hfZmSSTcpj8= 714 | go.augendre.info/arangolint v0.2.0/go.mod h1:Vx4KSJwu48tkE+8uxuf0cbBnAPgnt8O1KWiT7bljq7w= 715 | go.augendre.info/fatcontext v0.8.0 h1:2dfk6CQbDGeu1YocF59Za5Pia7ULeAM6friJ3LP7lmk= 716 | go.augendre.info/fatcontext v0.8.0/go.mod h1:oVJfMgwngMsHO+KB2MdgzcO+RvtNdiCEOlWvSFtax/s= 717 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 718 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 719 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= 720 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= 721 | go.opentelemetry.io/contrib/propagators/autoprop v0.54.0 h1:h/O1OcNbqrFilsMKfG6MJWWpx8gzCDfn9D+1W7lU3lE= 722 | go.opentelemetry.io/contrib/propagators/autoprop v0.54.0/go.mod h1:VIaPlErTgbng1UhrMA4N6Yy+f94PLA/qRPOCMATdoCs= 723 | go.opentelemetry.io/contrib/propagators/aws v1.29.0 h1:mqadbdNBhn/MVOcNx0dEZAaOaomKKdnsM0QNBmFegiI= 724 | go.opentelemetry.io/contrib/propagators/aws v1.29.0/go.mod h1:3RCUqtGbLbVr6REZv3pQbtqql9GNEpvyB7GiTJhP/nk= 725 | go.opentelemetry.io/contrib/propagators/b3 v1.29.0 h1:hNjyoRsAACnhoOLWupItUjABzeYmX3GTTZLzwJluJlk= 726 | go.opentelemetry.io/contrib/propagators/b3 v1.29.0/go.mod h1:E76MTitU1Niwo5NSN+mVxkyLu4h4h7Dp/yh38F2WuIU= 727 | go.opentelemetry.io/contrib/propagators/jaeger v1.29.0 h1:+YPiqF5rR6PqHBlmEFLPumbSP0gY0WmCGFayXRcCLvs= 728 | go.opentelemetry.io/contrib/propagators/jaeger v1.29.0/go.mod h1:6PD7q7qquWSp3Z4HeM3e/2ipRubaY1rXZO8NIHVDZjs= 729 | go.opentelemetry.io/contrib/propagators/ot v1.29.0 h1:CaJU78FvXrA6ajjp1dOdcABBEjh529+hl396RTqc2LQ= 730 | go.opentelemetry.io/contrib/propagators/ot v1.29.0/go.mod h1:Sc0omwLb4eptUhwOAfYXfmPmErHPu2HV6vkeDge/3sY= 731 | go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= 732 | go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= 733 | go.opentelemetry.io/otel/bridge/opentracing v1.31.0 h1:S6SA1IQdNHgfZfgkaWBKQqNIlMNiPoyQDACii2uKQ9k= 734 | go.opentelemetry.io/otel/bridge/opentracing v1.31.0/go.mod h1:DnEoPjq3eNCtnB41TqlUQYtarWe8PqJNWRt37daADe4= 735 | go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= 736 | go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= 737 | go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= 738 | go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= 739 | go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= 740 | go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= 741 | go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= 742 | go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= 743 | go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 744 | go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 745 | go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= 746 | go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= 747 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 748 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 749 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 750 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 751 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 752 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 753 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 754 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 755 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 756 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 757 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 758 | golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= 759 | golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= 760 | golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= 761 | golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= 762 | golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= 763 | golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= 764 | golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= 765 | golang.org/x/exp/typeparams v0.0.0-20250620022241-b7579e27df2b h1:KdrhdYPDUvJTvrDK9gdjfFd6JTk8vA1WJoldYSi0kHo= 766 | golang.org/x/exp/typeparams v0.0.0-20250620022241-b7579e27df2b/go.mod h1:LKZHyeOpPuZcMgxeHjJp4p5yvxrCX1xDvH10zYHhjjQ= 767 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 768 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 769 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 770 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 771 | golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= 772 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 773 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 774 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 775 | golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 776 | golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= 777 | golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= 778 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 779 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 780 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 781 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 782 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 783 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 784 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 785 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 786 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 787 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 788 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 789 | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 790 | golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 791 | golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= 792 | golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= 793 | golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= 794 | golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= 795 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 796 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 797 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 798 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 799 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 800 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 801 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 802 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 803 | golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 804 | golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= 805 | golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 806 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 807 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 808 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 809 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 810 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 811 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 812 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 813 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 814 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 815 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 816 | golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 817 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 818 | golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 819 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 820 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 821 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 822 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 823 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 824 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 825 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 826 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 827 | golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 828 | golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= 829 | golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 830 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 831 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 832 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 833 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 834 | golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= 835 | golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= 836 | golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= 837 | golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= 838 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 839 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 840 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 841 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 842 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 843 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 844 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 845 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 846 | golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= 847 | golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= 848 | golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= 849 | golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 850 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 851 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 852 | golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 853 | golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 854 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 855 | golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 856 | golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 857 | golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 858 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 859 | golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= 860 | golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= 861 | golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 862 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 863 | golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= 864 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 865 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 866 | golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 867 | golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= 868 | golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= 869 | golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= 870 | golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= 871 | golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= 872 | golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= 873 | golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= 874 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 875 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 876 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 877 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 878 | google.golang.org/api v0.242.0 h1:7Lnb1nfnpvbkCiZek6IXKdJ0MFuAZNAJKQfA1ws62xg= 879 | google.golang.org/api v0.242.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= 880 | google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78= 881 | google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= 882 | google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= 883 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= 884 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= 885 | google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= 886 | google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= 887 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 888 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 889 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 890 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 891 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 892 | gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= 893 | gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= 894 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 895 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 896 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 897 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 898 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 899 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 900 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 901 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 902 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 903 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 904 | honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= 905 | honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= 906 | k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= 907 | k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= 908 | k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= 909 | k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= 910 | k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= 911 | k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= 912 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 913 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 914 | k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= 915 | k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= 916 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= 917 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 918 | mvdan.cc/gofumpt v0.8.0 h1:nZUCeC2ViFaerTcYKstMmfysj6uhQrA2vJe+2vwGU6k= 919 | mvdan.cc/gofumpt v0.8.0/go.mod h1:vEYnSzyGPmjvFkqJWtXkh79UwPWP9/HMxQdGEXZHjpg= 920 | mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 h1:WjUu4yQoT5BHT1w8Zu56SP8367OuBV5jvo+4Ulppyf8= 921 | mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4/go.mod h1:rthT7OuvRbaGcd5ginj6dA2oLE7YNlta9qhBNNdCaLE= 922 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= 923 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= 924 | sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= 925 | sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= 926 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 927 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 928 | --------------------------------------------------------------------------------