├── .github └── workflows │ └── publish.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── build └── build.go ├── cmd └── root.go ├── codecov.yml ├── exporter ├── exporter.go └── exporter_test.go ├── go.mod ├── go.sum └── main.go /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | branches: ['master'] 6 | 7 | jobs: 8 | publish: 9 | name: Publish 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/setup-go@v2 13 | with: 14 | go-version: 1.18 15 | - uses: actions/checkout@v2 16 | 17 | - uses: imjasonh/setup-ko@v0.6 18 | - run: ko build -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage.txt 2 | /dist 3 | /vendor 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - tip 5 | 6 | env: 7 | - GO111MODULE=on 8 | script: 9 | - make coverage 10 | 11 | after_success: 12 | - bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Daniel Milde 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME := disk_usage_exporter 2 | PACKAGE := github.com/dundee/$(NAME) 3 | VERSION := $(shell git describe --tags 2>/dev/null) 4 | GIT_SHA := $(shell git rev-parse HEAD 2>/dev/null) 5 | GOFLAGS ?= -buildmode=pie -trimpath -mod=readonly -modcacherw 6 | LDFLAGS := -s -w -extldflags '-static' \ 7 | -X '$(PACKAGE)/build.BuildVersion=$(VERSION)' \ 8 | -X '$(PACKAGE)/build.BuildCommitSha=$(GIT_SHA)' \ 9 | -X '$(PACKAGE)/build.BuildDate=$(shell LC_ALL=en_US.UTF-8 date)' 10 | 11 | all: clean build-all clean-uncompressed-dist shasums 12 | 13 | run: 14 | go run . 15 | 16 | build: 17 | @echo "Version: " $(VERSION) 18 | mkdir -p dist 19 | GOFLAGS="$(GOFLAGS)" CGO_ENABLED=0 go build -ldflags="$(LDFLAGS)" -o dist/$(NAME) . 20 | 21 | build-all: 22 | @echo "Version: " $(VERSION) 23 | -mkdir dist 24 | -CGO_ENABLED=0 gox \ 25 | -os="darwin" \ 26 | -arch="amd64 arm64" \ 27 | -output="dist/disk_usage_exporter_{{.OS}}_{{.Arch}}" \ 28 | -ldflags="$(LDFLAGS)" 29 | 30 | -CGO_ENABLED=0 gox \ 31 | -os="linux freebsd netbsd openbsd" \ 32 | -output="dist/disk_usage_exporter_{{.OS}}_{{.Arch}}" \ 33 | -ldflags="$(LDFLAGS)" 34 | 35 | cd dist; CGO_ENABLED=0 GOOS=linux GOARM=5 GOARCH=arm go build -ldflags="$(LDFLAGS)" -o disk_usage_exporter_linux_armv5l .. 36 | cd dist; CGO_ENABLED=0 GOOS=linux GOARM=6 GOARCH=arm go build -ldflags="$(LDFLAGS)" -o disk_usage_exporter_linux_armv6l .. 37 | cd dist; CGO_ENABLED=0 GOOS=linux GOARM=7 GOARCH=arm go build -ldflags="$(LDFLAGS)" -o disk_usage_exporter_linux_armv7l .. 38 | cd dist; GOFLAGS="$(GOFLAGS)" CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="$(LDFLAGS)" -o disk_usage_exporter_linux_amd64 .. 39 | cd dist; CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="$(LDFLAGS)" -o disk_usage_exporter_linux_arm64 .. 40 | 41 | cd dist; for file in disk_usage_exporter_linux_* disk_usage_exporter_darwin_* disk_usage_exporter_netbsd_* disk_usage_exporter_openbsd_* disk_usage_exporter_freebsd_*; do tar czf $$file.tgz $$file; done 42 | 43 | test: 44 | go test -v ./... 45 | 46 | coverage: 47 | go test -v -race -coverprofile=coverage.txt -covermode=atomic ./... 48 | 49 | coverage-html: coverage 50 | go tool cover -html=coverage.txt 51 | 52 | clean: 53 | -rm -r dist 54 | 55 | clean-uncompressed-dist: 56 | find dist -type f -not -name '*.tgz' -not -name '*.zip' -delete 57 | 58 | shasums: 59 | cd dist; sha256sum * > sha256sums.txt 60 | cd dist; gpg --sign --armor --detach-sign sha256sums.txt 61 | 62 | release: 63 | gh release create -t "$(VERSION)" $(VERSION) ./dist/* 64 | 65 | .PHONY: run build clean test coverage coverage-html 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Disk Usage Prometheus Exporter 2 | 3 | [![Build Status](https://travis-ci.com/dundee/disk_usage_exporter.svg?branch=master)](https://travis-ci.com/dundee/disk_usage_exporter) 4 | [![codecov](https://codecov.io/gh/dundee/disk_usage_exporter/branch/master/graph/badge.svg)](https://codecov.io/gh/dundee/disk_usage_exporter) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/dundee/disk_usage_exporter)](https://goreportcard.com/report/github.com/dundee/disk_usage_exporter) 6 | [![Maintainability](https://api.codeclimate.com/v1/badges/74d685f0c638e6109ab3/maintainability)](https://codeclimate.com/github/dundee/disk_usage_exporter/maintainability) 7 | [![CodeScene Code Health](https://codescene.io/projects/14689/status-badges/code-health)](https://codescene.io/projects/14689) 8 | 9 | Provides detailed info about disk usage of the selected filesystem path. 10 | 11 | Uses [gdu](https://github.com/dundee/gdu) under the hood for the disk usage analysis. 12 | 13 | ## Demo Grafana dashboard 14 | 15 | https://grafana.milde.cz/d/0TfJhs_Mz/disk-usage (credentials: grafana / gdu) 16 | 17 | ## Usage 18 | 19 | ``` 20 | Usage: 21 | disk_usage_exporter [flags] 22 | 23 | Flags: 24 | -p, --analyzed-path string Path where to analyze disk usage (default "/") 25 | --basic-auth-users stringToString Basic Auth users and their passwords as bcypt hashes (default []) 26 | -b, --bind-address string Address to bind to (default "0.0.0.0:9995") 27 | -c, --config string config file (default is $HOME/.disk_usage_exporter.yaml) 28 | -l, --dir-level int Directory nesting level to show (0 = only selected dir) (default 2) 29 | -L, --follow-symlinks Follow symlinks for files, i.e. show the size of the file to which symlink points to (symlinks to directories are not followed) 30 | -h, --help help for disk_usage_exporter 31 | -i, --ignore-dirs strings Absolute paths to ignore (separated by comma) (default [/proc,/dev,/sys,/run,/var/cache/rsnapshot]) 32 | -m, --mode string Expose method - either 'file' or 'http' (default "http") 33 | --multi-paths stringToString Multiple paths where to analyze disk usage, in format /path1=level1,/path2=level2,... (default []) 34 | -f, --output-file string Target file to store metrics in (default "./disk-usage-exporter.prom") 35 | ``` 36 | 37 | Either one path can be specified using `--analyzed-path` and `--dir-level` flags or multiple can be set 38 | using `--multi-paths` flag: 39 | 40 | ```bash 41 | disk_usage_exporter --multi-paths=/home=2,/var=3 42 | ``` 43 | 44 | ## Example output 45 | 46 | ``` 47 | # HELP node_disk_usage_bytes Disk usage of the directory/file 48 | # TYPE node_disk_usage_bytes gauge 49 | node_disk_usage_bytes{path="/var/cache"} 2.1766144e+09 50 | node_disk_usage_bytes{path="/var/db"} 20480 51 | node_disk_usage_bytes{path="/var/dpkg"} 8192 52 | node_disk_usage_bytes{path="/var/empty"} 4096 53 | node_disk_usage_bytes{path="/var/games"} 4096 54 | node_disk_usage_bytes{path="/var/lib"} 7.554709504e+09 55 | node_disk_usage_bytes{path="/var/local"} 4096 56 | node_disk_usage_bytes{path="/var/lock"} 0 57 | node_disk_usage_bytes{path="/var/log"} 4.247068672e+09 58 | node_disk_usage_bytes{path="/var/mail"} 0 59 | node_disk_usage_bytes{path="/var/named"} 4096 60 | node_disk_usage_bytes{path="/var/opt"} 4096 61 | node_disk_usage_bytes{path="/var/run"} 0 62 | node_disk_usage_bytes{path="/var/snap"} 1.11694848e+10 63 | node_disk_usage_bytes{path="/var/spool"} 16384 64 | node_disk_usage_bytes{path="/var/tmp"} 475136 65 | # HELP node_disk_usage_level_1_bytes Disk usage of the directory/file level 1 66 | # TYPE node_disk_usage_level_1_bytes gauge 67 | node_disk_usage_level_1_bytes{path="/bin"} 0 68 | node_disk_usage_level_1_bytes{path="/boot"} 1.29736704e+08 69 | node_disk_usage_level_1_bytes{path="/etc"} 1.3090816e+07 70 | node_disk_usage_level_1_bytes{path="/home"} 8.7081373696e+10 71 | node_disk_usage_level_1_bytes{path="/lib"} 0 72 | node_disk_usage_level_1_bytes{path="/lib64"} 0 73 | node_disk_usage_level_1_bytes{path="/lost+found"} 4096 74 | node_disk_usage_level_1_bytes{path="/mnt"} 4096 75 | node_disk_usage_level_1_bytes{path="/opt"} 2.979229696e+09 76 | node_disk_usage_level_1_bytes{path="/root"} 4096 77 | node_disk_usage_level_1_bytes{path="/sbin"} 0 78 | node_disk_usage_level_1_bytes{path="/snap"} 0 79 | node_disk_usage_level_1_bytes{path="/srv"} 4.988928e+06 80 | node_disk_usage_level_1_bytes{path="/tmp"} 1.3713408e+07 81 | node_disk_usage_level_1_bytes{path="/usr"} 1.8109427712e+10 82 | node_disk_usage_level_1_bytes{path="/var"} 2.5156793856e+10 83 | ``` 84 | 85 | ## Example Prometheus queries 86 | 87 | Disk usage of `/var` directory: 88 | 89 | ``` 90 | sum(node_disk_usage_bytes{path=~"/var.*"}) 91 | ``` 92 | 93 | ## Example config files 94 | 95 | `~/.disk_usage_exporter.yaml`: 96 | ```yaml 97 | analyzed-path: / 98 | bind-address: 0.0.0.0:9995 99 | dir-level: 2 100 | ignore-dirs: 101 | - /proc 102 | - /dev 103 | - /sys 104 | - /run 105 | ``` 106 | 107 | `~/.disk_usage_exporter.yaml`: 108 | ```yaml 109 | analyzed-path: / 110 | mode: file 111 | output-file: ./disk-usage-exporter.prom 112 | dir-level: 2 113 | ignore-dirs: 114 | - /proc 115 | - /dev 116 | - /sys 117 | - /run 118 | ``` 119 | 120 | `~/.disk_usage_exporter.yaml`: 121 | ```yaml 122 | multi-paths: 123 | /home: 2 124 | /var: 3 125 | /tmp: 1 126 | bind-address: 0.0.0.0:9995 127 | dir-level: 2 128 | ignore-dirs: 129 | - /proc 130 | - /dev 131 | - /sys 132 | - /run 133 | basic-auth-users: 134 | prom: $2b$12$MzUQjmLxPRM9WW6OI4ZzwuZHB7ubHiiSnngJxIufgZms27nw.5ZAq 135 | ``` 136 | 137 | ## Prometheus scrape config 138 | 139 | Disk usage analysis can be resource heavy. 140 | Set the `scrape_interval` and `scrape_timeout` according to the size of analyzed path. 141 | 142 | ```yaml 143 | scrape_configs: 144 | - job_name: 'disk-usage' 145 | scrape_interval: 5m 146 | scrape_timeout: 20s 147 | static_configs: 148 | - targets: ['localhost:9995'] 149 | ``` 150 | 151 | ## Dump to file 152 | 153 | The official `node-exporter` allows to specify a folder which contains additional metric files through a [textfile collection mechanism](https://github.com/prometheus/node_exporter#textfile-collector). 154 | In order to make use of this, one has to set up `node-exporter` according to the documentation and set the `output-file` 155 | of this exporter to any name ending in `.prom` within said folder (and of course also `mode` to `file`). 156 | 157 | A common use case for this is when the calculation of metrics takes particularly long and therefore can only be done 158 | once in a while. To automate the periodic update of the output file, simply set up a cronjob. 159 | 160 | ## Basic Auth 161 | 162 | You can enable HTTP Basic authorization by setting the `--basic-auth-users` flag (password is "test"): 163 | 164 | ``` 165 | disk_usage_exporter --basic-auth-users='admin=$2b$12$hNf2lSsxfm0.i4a.1kVpSOVyBCfIB51VRjgBUyv6kdnyTlgWj81Ay' 166 | ``` 167 | 168 | or by setting the key in config: 169 | 170 | ```yaml 171 | basic-auth-users: 172 | admin: $2b$12$hNf2lSsxfm0.i4a.1kVpSOVyBCfIB51VRjgBUyv6kdnyTlgWj81Ay 173 | ``` 174 | 175 | The password needs to be hashed by [bcrypt](https://bcrypt-generator.com/) in both cases. 176 | 177 | 178 | ## Example systemd unit file 179 | 180 | ``` 181 | [Unit] 182 | Description=Prometheus disk usage exporter 183 | Documentation=https://github.com/dundee/disk_usage_exporter 184 | 185 | [Service] 186 | Restart=always 187 | User=prometheus 188 | ExecStart=/usr/bin/disk_usage_exporter $ARGS 189 | ExecReload=/bin/kill -HUP $MAINPID 190 | TimeoutStopSec=20s 191 | SendSIGKILL=no 192 | 193 | [Install] 194 | WantedBy=multi-user.target 195 | ``` 196 | -------------------------------------------------------------------------------- /build/build.go: -------------------------------------------------------------------------------- 1 | package build 2 | 3 | var ( 4 | // BuildVersion is the tagged version of the binary 5 | BuildVersion = "<<< filled in by build >>>" 6 | // BuildDate is date and time of the binary build 7 | BuildDate = "<<< filled in by build >>>" 8 | // BuildCommitSha is git sha hash of the commit from which is the binary built 9 | BuildCommitSha = "<<< filled in by build >>>" 10 | ) 11 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "runtime" 8 | "strconv" 9 | 10 | "github.com/dundee/disk_usage_exporter/build" 11 | "github.com/dundee/disk_usage_exporter/exporter" 12 | log "github.com/sirupsen/logrus" 13 | "github.com/spf13/cobra" 14 | 15 | homedir "github.com/mitchellh/go-homedir" 16 | "github.com/spf13/viper" 17 | ) 18 | 19 | var ( 20 | cfgFile string 21 | ) 22 | 23 | var rootCmd = &cobra.Command{ 24 | Use: "disk_usage_exporter", 25 | Short: "Prometheus exporter for detailed disk usage info", 26 | Long: `Prometheus exporter analysing disk usage of the filesystem 27 | and reporting which directories consume what space.`, 28 | Run: func(cmd *cobra.Command, args []string) { 29 | printHeader() 30 | 31 | paths := transformMultipaths(viper.GetStringMapString("multi-paths")) 32 | if len(paths) == 0 { 33 | paths[filepath.Clean(viper.GetString("analyzed-path"))] = viper.GetInt("dir-level") 34 | } 35 | 36 | e := exporter.NewExporter( 37 | (paths), 38 | viper.GetBool("follow-symlinks"), 39 | ) 40 | e.SetIgnoreDirPaths(viper.GetStringSlice("ignore-dirs")) 41 | 42 | if viper.IsSet("basic-auth-users") { 43 | e.SetBasicAuth(viper.GetStringMapString("basic-auth-users")) 44 | } 45 | 46 | if viper.GetString("mode") == "file" { 47 | e.WriteToTextfile(viper.GetString("output-file")) 48 | log.Info("Done - exiting.") 49 | } else { 50 | e.RunServer(viper.GetString("bind-address")) 51 | } 52 | }, 53 | } 54 | 55 | // Execute runs the command 56 | func Execute() { 57 | if err := rootCmd.Execute(); err != nil { 58 | fmt.Println(err) 59 | os.Exit(1) 60 | } 61 | } 62 | 63 | func init() { 64 | cobra.OnInitialize(initConfig) 65 | flags := rootCmd.PersistentFlags() 66 | flags.StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.disk_usage_exporter.yaml)") 67 | flags.StringP("mode", "m", "http", "Expose method - either 'file' or 'http'") 68 | flags.StringP("bind-address", "b", "0.0.0.0:9995", "Address to bind to") 69 | flags.StringP("output-file", "f", "./disk-usage-exporter.prom", "Target file to store metrics in") 70 | flags.StringP("analyzed-path", "p", "/", "Path where to analyze disk usage") 71 | flags.IntP("dir-level", "l", 2, "Directory nesting level to show (0 = only selected dir)") 72 | flags.StringSliceP("ignore-dirs", "i", []string{"/proc", "/dev", "/sys", "/run", "/var/cache/rsnapshot"}, "Absolute paths to ignore (separated by comma)") 73 | flags.BoolP( 74 | "follow-symlinks", "L", false, 75 | "Follow symlinks for files, i.e. show the size of the file to which symlink points to (symlinks to directories are not followed)", 76 | ) 77 | flags.StringToString("multi-paths", map[string]string{}, "Multiple paths where to analyze disk usage, in format /path1=level1,/path2=level2,...") 78 | flags.StringToString("basic-auth-users", map[string]string{}, "Basic Auth users and their passwords as bcypt hashes") 79 | 80 | viper.BindPFlags(flags) 81 | } 82 | 83 | // initConfig reads in config file and ENV variables if set. 84 | func initConfig() { 85 | if cfgFile != "" { 86 | // Use config file from the flag. 87 | viper.SetConfigFile(cfgFile) 88 | } else { 89 | // Find home directory. 90 | home, err := homedir.Dir() 91 | if err != nil { 92 | fmt.Println(err) 93 | os.Exit(1) 94 | } 95 | 96 | // Search config in home directory with name ".disk_usage_exporter" (without extension). 97 | viper.AddConfigPath(home) 98 | viper.SetConfigName(".disk_usage_exporter") 99 | } 100 | 101 | viper.AutomaticEnv() // read in environment variables that match 102 | 103 | // If a config file is found, read it in. 104 | if err := viper.ReadInConfig(); err == nil { 105 | fmt.Println("Using config file:", viper.ConfigFileUsed()) 106 | } 107 | } 108 | 109 | func printHeader() { 110 | log.Printf("Disk Usage Prometheus Exporter %s build date: %s sha1: %s Go: %s GOOS: %s GOARCH: %s", 111 | build.BuildVersion, 112 | build.BuildDate, 113 | build.BuildCommitSha, 114 | runtime.Version(), 115 | runtime.GOOS, 116 | runtime.GOARCH, 117 | ) 118 | } 119 | 120 | func transformMultipaths(multiPaths map[string]string) map[string]int { 121 | paths := make(map[string]int, len(multiPaths)) 122 | for path, level := range multiPaths { 123 | l, err := strconv.Atoi(level) 124 | if err != nil { 125 | log.Fatalf("Invalid level for path %s: %s", path, level) 126 | } 127 | paths[filepath.Clean(path)] = l 128 | } 129 | return paths 130 | } 131 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | target: auto 6 | threshold: 2% 7 | informational: true 8 | patch: 9 | default: 10 | informational: true -------------------------------------------------------------------------------- /exporter/exporter.go: -------------------------------------------------------------------------------- 1 | package exporter 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "runtime/debug" 7 | 8 | "github.com/dundee/gdu/v5/pkg/analyze" 9 | "github.com/dundee/gdu/v5/pkg/fs" 10 | "github.com/prometheus/client_golang/prometheus" 11 | "github.com/prometheus/client_golang/prometheus/promhttp" 12 | log "github.com/sirupsen/logrus" 13 | "golang.org/x/crypto/bcrypt" 14 | ) 15 | 16 | var ( 17 | diskUsage = prometheus.NewGaugeVec( 18 | prometheus.GaugeOpts{ 19 | Name: "node_disk_usage_bytes", 20 | Help: "Disk usage of the directory/file", 21 | }, 22 | []string{"path"}, 23 | ) 24 | diskUsageLevel1 = prometheus.NewGaugeVec( 25 | prometheus.GaugeOpts{ 26 | Name: "node_disk_usage_level_1_bytes", 27 | Help: "Disk usage of the directory/file level 1", 28 | }, 29 | []string{"path"}, 30 | ) 31 | collectors = []prometheus.Collector{diskUsage, diskUsageLevel1} 32 | ) 33 | 34 | func init() { 35 | prometheus.MustRegister(collectors...) 36 | } 37 | 38 | // Exporter is the type to be used to start HTTP server and run the analysis 39 | type Exporter struct { 40 | paths map[string]int 41 | ignoreDirPaths map[string]struct{} 42 | followSymlinks bool 43 | basicAuth map[string]string 44 | } 45 | 46 | // NewExporter creates new Exporter 47 | func NewExporter(paths map[string]int, followSymlinks bool) *Exporter { 48 | return &Exporter{ 49 | followSymlinks: followSymlinks, 50 | paths: paths, 51 | } 52 | } 53 | 54 | func (e *Exporter) runAnalysis() { 55 | defer debug.FreeOSMemory() 56 | 57 | for path, level := range e.paths { 58 | analyzer := analyze.CreateAnalyzer() 59 | analyzer.SetFollowSymlinks(e.followSymlinks) 60 | dir := analyzer.AnalyzeDir(path, e.shouldDirBeIgnored, false) 61 | dir.UpdateStats(fs.HardLinkedItems{}) 62 | e.reportItem(dir, 0, level) 63 | } 64 | 65 | log.Info("Analysis done") 66 | } 67 | 68 | // SetIgnoreDirPaths sets paths to ignore 69 | func (e *Exporter) SetIgnoreDirPaths(paths []string) { 70 | e.ignoreDirPaths = make(map[string]struct{}, len(paths)) 71 | for _, path := range paths { 72 | e.ignoreDirPaths[path] = struct{}{} 73 | } 74 | } 75 | 76 | // SetBasicAuth sets Basic Auth credentials 77 | func (e *Exporter) SetBasicAuth(users map[string]string) { 78 | e.basicAuth = users 79 | } 80 | 81 | func (e *Exporter) shouldDirBeIgnored(_, path string) bool { 82 | _, ok := e.ignoreDirPaths[path] 83 | return ok 84 | } 85 | 86 | func (e *Exporter) reportItem(item fs.Item, level, maxLevel int) { 87 | if level == maxLevel { 88 | diskUsage.WithLabelValues(item.GetPath()).Set(float64(item.GetUsage())) 89 | } else if level == 1 { 90 | diskUsageLevel1.WithLabelValues(item.GetPath()).Set(float64(item.GetUsage())) 91 | } 92 | 93 | if item.IsDir() && level+1 <= maxLevel { 94 | for _, entry := range item.GetFiles() { 95 | e.reportItem(entry, level+1, maxLevel) 96 | } 97 | } 98 | } 99 | 100 | // WriteToTextfile writes the prometheus report to file 101 | func (e *Exporter) WriteToTextfile(name string) { 102 | e.runAnalysis() 103 | 104 | // Use a custom registry to drop go stats 105 | registry := prometheus.NewRegistry() 106 | registry.MustRegister(collectors...) 107 | 108 | err := prometheus.WriteToTextfile(name, registry) 109 | if err != nil { 110 | log.Fatalf("WriteToTextfile error: %v", err) 111 | } 112 | log.Printf("Stored stats in file %s", name) 113 | } 114 | 115 | func (e *Exporter) ServeHTTP(w http.ResponseWriter, req *http.Request) { 116 | if len(e.basicAuth) > 0 { 117 | if ok := e.authorizeReq(w, req); !ok { 118 | return 119 | } 120 | } 121 | 122 | e.runAnalysis() 123 | promhttp.Handler().ServeHTTP(w, req) 124 | } 125 | 126 | func (e *Exporter) authorizeReq(w http.ResponseWriter, req *http.Request) bool { 127 | user, pass, ok := req.BasicAuth() 128 | if ok { 129 | if hashed, found := e.basicAuth[user]; found { 130 | err := bcrypt.CompareHashAndPassword([]byte(hashed), []byte(pass)) 131 | if err == nil { 132 | return true 133 | } 134 | } 135 | } 136 | 137 | w.Header().Add("WWW-Authenticate", "Basic realm=\"Access to disk usage exporter\"") 138 | w.WriteHeader(401) 139 | return false 140 | } 141 | 142 | // RunServer starts HTTP server loop 143 | func (e *Exporter) RunServer(addr string) { 144 | http.Handle("/", http.HandlerFunc(ServeIndex)) 145 | http.Handle("/metrics", e) 146 | 147 | log.Printf("Providing metrics at http://%s/metrics", addr) 148 | err := http.ListenAndServe(addr, nil) 149 | if err != nil { 150 | log.Fatal("ListenAndServe:", err) 151 | } 152 | } 153 | 154 | // ServeIndex serves index page 155 | func ServeIndex(w http.ResponseWriter, req *http.Request) { 156 | w.Header().Set("Content-type", "text/html") 157 | res := ` 158 | 159 | 160 | 161 | 162 | Disk Usage Prometheus Exporter 163 | 164 | 165 |

Disk Usage Prometheus Exporter

166 |

167 | Metrics 168 |

169 |

170 | Homepage 171 |

172 | 173 | 174 | ` 175 | fmt.Fprint(w, res) 176 | } 177 | -------------------------------------------------------------------------------- /exporter/exporter_test.go: -------------------------------------------------------------------------------- 1 | package exporter_test 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | "testing" 7 | 8 | "github.com/dundee/disk_usage_exporter/exporter" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestRunAnalysis(t *testing.T) { 13 | fin := createTestDir() 14 | defer fin() 15 | 16 | w := &mockedResponseWriter{} 17 | 18 | paths := map[string]int{"test_dir": 2} 19 | e := exporter.NewExporter(paths, false) 20 | e.SetIgnoreDirPaths([]string{"/proc"}) 21 | e.ServeHTTP(w, &http.Request{ 22 | Header: make(http.Header), 23 | }) 24 | 25 | assert.NotContains(t, string(w.buff), "test_dir/test_dir") 26 | assert.Contains(t, string(w.buff), "node_disk_usage_bytes") 27 | } 28 | 29 | func TestIndex(t *testing.T) { 30 | w := &mockedResponseWriter{} 31 | exporter.ServeIndex(w, &http.Request{ 32 | Header: make(http.Header), 33 | }) 34 | assert.Contains(t, string(w.buff), "

Disk Usage Prometheus Exporter

") 35 | } 36 | 37 | type mockedResponseWriter struct { 38 | buff []byte 39 | } 40 | 41 | func (w *mockedResponseWriter) Header() http.Header { 42 | return make(http.Header) 43 | } 44 | 45 | func (w *mockedResponseWriter) Write(b []byte) (int, error) { 46 | w.buff = append(w.buff, b...) 47 | return len(b), nil 48 | } 49 | 50 | func (w *mockedResponseWriter) WriteHeader(statusCode int) { 51 | 52 | } 53 | 54 | func createTestDir() func() { 55 | os.MkdirAll("test_dir/nested/subnested", os.ModePerm) 56 | os.WriteFile("test_dir/nested/subnested/file", []byte("hello"), 0644) 57 | os.WriteFile("test_dir/nested/file2", []byte("go"), 0644) 58 | return func() { 59 | err := os.RemoveAll("test_dir") 60 | if err != nil { 61 | panic(err) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dundee/disk_usage_exporter 2 | 3 | go 1.21 4 | 5 | toolchain go1.22.0 6 | 7 | require ( 8 | github.com/dundee/gdu/v5 v5.27.0 9 | github.com/mitchellh/go-homedir v1.1.0 10 | github.com/prometheus/client_golang v1.19.0 11 | github.com/sirupsen/logrus v1.9.3 12 | github.com/spf13/cobra v1.8.0 13 | github.com/spf13/viper v1.18.2 14 | github.com/stretchr/testify v1.8.4 15 | golang.org/x/crypto v0.20.0 16 | ) 17 | 18 | require ( 19 | github.com/beorn7/perks v1.0.1 // indirect 20 | github.com/cespare/xxhash v1.1.0 // indirect 21 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 22 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 23 | github.com/dgraph-io/badger/v3 v3.2103.5 // indirect 24 | github.com/dgraph-io/ristretto v0.1.1 // indirect 25 | github.com/dustin/go-humanize v1.0.1 // indirect 26 | github.com/fsnotify/fsnotify v1.7.0 // indirect 27 | github.com/gdamore/encoding v1.0.0 // indirect 28 | github.com/gdamore/tcell/v2 v2.7.1 // indirect 29 | github.com/gogo/protobuf v1.3.2 // indirect 30 | github.com/golang/glog v1.2.0 // indirect 31 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 32 | github.com/golang/protobuf v1.5.3 // indirect 33 | github.com/golang/snappy v0.0.4 // indirect 34 | github.com/google/flatbuffers v23.5.26+incompatible // indirect 35 | github.com/hashicorp/hcl v1.0.0 // indirect 36 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 37 | github.com/klauspost/compress v1.17.7 // indirect 38 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 39 | github.com/magiconair/properties v1.8.7 // indirect 40 | github.com/maruel/natural v1.1.1 // indirect 41 | github.com/mattn/go-runewidth v0.0.15 // indirect 42 | github.com/mitchellh/mapstructure v1.5.0 // indirect 43 | github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect 44 | github.com/pelletier/go-toml/v2 v2.1.1 // indirect 45 | github.com/pkg/errors v0.9.1 // indirect 46 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 47 | github.com/prometheus/client_model v0.6.0 // indirect 48 | github.com/prometheus/common v0.48.0 // indirect 49 | github.com/prometheus/procfs v0.12.0 // indirect 50 | github.com/rivo/tview v0.0.0-20240225120200-5605142ca62e // indirect 51 | github.com/rivo/uniseg v0.4.7 // indirect 52 | github.com/sagikazarmark/locafero v0.4.0 // indirect 53 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect 54 | github.com/sourcegraph/conc v0.3.0 // indirect 55 | github.com/spf13/afero v1.11.0 // indirect 56 | github.com/spf13/cast v1.6.0 // indirect 57 | github.com/spf13/pflag v1.0.5 // indirect 58 | github.com/subosito/gotenv v1.6.0 // indirect 59 | go.opencensus.io v0.24.0 // indirect 60 | go.uber.org/multierr v1.11.0 // indirect 61 | golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect 62 | golang.org/x/net v0.21.0 // indirect 63 | golang.org/x/sys v0.17.0 // indirect 64 | golang.org/x/term v0.17.0 // indirect 65 | golang.org/x/text v0.14.0 // indirect 66 | google.golang.org/protobuf v1.32.0 // indirect 67 | gopkg.in/ini.v1 v1.67.0 // indirect 68 | gopkg.in/yaml.v3 v3.0.1 // indirect 69 | ) 70 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= 4 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 5 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 6 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 7 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 8 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 9 | github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= 10 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 11 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 12 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 13 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 14 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 15 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 16 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 17 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= 18 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 19 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 20 | github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 21 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 22 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 23 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 24 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 25 | github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg= 26 | github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw= 27 | github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= 28 | github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= 29 | github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= 30 | github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= 31 | github.com/dundee/gdu/v5 v5.27.0 h1:J2FfAKXp/xXI1lqtE0zfYIaZhZq8BP4HwBQekgm+xEg= 32 | github.com/dundee/gdu/v5 v5.27.0/go.mod h1:60V8EUwaPQC0KvOcLyIi3R95WAl4QzRrflFsRzumKKs= 33 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 34 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 35 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 36 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 37 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 38 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 39 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 40 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 41 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 42 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 43 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 44 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 45 | github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= 46 | github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= 47 | github.com/gdamore/tcell/v2 v2.7.1 h1:TiCcmpWHiAU7F0rA2I3S2Y4mmLmO9KHxJ7E1QhYzQbc= 48 | github.com/gdamore/tcell/v2 v2.7.1/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg= 49 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 50 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 51 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 52 | github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= 53 | github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= 54 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 55 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 56 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 57 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 58 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 59 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 60 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 61 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 62 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 63 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 64 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 65 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 66 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 67 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 68 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 69 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 70 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 71 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 72 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 73 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 74 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 75 | github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= 76 | github.com/google/flatbuffers v23.5.26+incompatible h1:M9dgRyhJemaM4Sw8+66GHBu8ioaQmyPLg1b8VwK5WJg= 77 | github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= 78 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 79 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 80 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 81 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 82 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 83 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 84 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 85 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 86 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 87 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 88 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 89 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 90 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 91 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 92 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 93 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 94 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 95 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 96 | github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= 97 | github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= 98 | github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= 99 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 100 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 101 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 102 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 103 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 104 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 105 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 106 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 107 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 108 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 109 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= 110 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 111 | github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= 112 | github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= 113 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= 114 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 115 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 116 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 117 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 118 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 119 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 120 | github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= 121 | github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= 122 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 123 | github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= 124 | github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= 125 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 126 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 127 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 128 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 129 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 130 | github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= 131 | github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= 132 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 133 | github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= 134 | github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= 135 | github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= 136 | github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= 137 | github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= 138 | github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= 139 | github.com/rivo/tview v0.0.0-20240225120200-5605142ca62e h1:7ubTieBkl4KCz5ABZzh0zPkBYWPguSOHUundUsorIzQ= 140 | github.com/rivo/tview v0.0.0-20240225120200-5605142ca62e/go.mod h1:02iFIz7K/A9jGCvrizLPvoqr4cEIx7q54RH5Qudkrss= 141 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 142 | github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 143 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 144 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 145 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 146 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 147 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 148 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 149 | github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= 150 | github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= 151 | github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= 152 | github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= 153 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 154 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 155 | github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= 156 | github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= 157 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 158 | github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 159 | github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 160 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 161 | github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= 162 | github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= 163 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 164 | github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= 165 | github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 166 | github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= 167 | github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= 168 | github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= 169 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 170 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 171 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 172 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 173 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 174 | github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= 175 | github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= 176 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 177 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 178 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 179 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 180 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 181 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 182 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 183 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 184 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 185 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 186 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 187 | github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= 188 | github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= 189 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 190 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 191 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 192 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 193 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 194 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= 195 | go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= 196 | go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 197 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 198 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 199 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 200 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 201 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 202 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 203 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 204 | golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= 205 | golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= 206 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 207 | golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= 208 | golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= 209 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 210 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 211 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 212 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 213 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 214 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 215 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 216 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 217 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 218 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 219 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 220 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 221 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 222 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 223 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 224 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 225 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 226 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 227 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 228 | golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= 229 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 230 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 231 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 232 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 233 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 234 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 235 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 236 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 237 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 238 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 239 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 240 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 241 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 242 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 243 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 244 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 245 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 246 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 247 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 248 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 249 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 250 | golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 251 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 252 | golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= 253 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 254 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 255 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 256 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 257 | golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= 258 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 259 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 260 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 261 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 262 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 263 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 264 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 265 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 266 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 267 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 268 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 269 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 270 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 271 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 272 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 273 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 274 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 275 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 276 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 277 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 278 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 279 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 280 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 281 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 282 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 283 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 284 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 285 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 286 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 287 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 288 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 289 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 290 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 291 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 292 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 293 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 294 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 295 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 296 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 297 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 298 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 299 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 300 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 301 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 302 | google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= 303 | google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 304 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 305 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 306 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 307 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 308 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 309 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 310 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 311 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 312 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 313 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 314 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 315 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 316 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/dundee/disk_usage_exporter/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | --------------------------------------------------------------------------------