├── .github └── workflows │ └── release.yml ├── .gitignore ├── .goreleaser.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── alloc.go ├── app.go ├── config.sample.toml ├── dev ├── base.toml.tpl ├── deployment.nomad ├── nomad-vector-logger.toml.tpl └── static │ └── sink.toml.tpl ├── go.mod ├── go.sum ├── init.go ├── main.go ├── run.sh ├── sample └── nomad.toml ├── static └── sink.toml └── vector.toml.tmpl /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: goreleaser 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | goreleaser: 10 | runs-on: ubuntu-latest 11 | env: 12 | DOCKER_CLI_EXPERIMENTAL: "enabled" 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Set up Go 21 | uses: actions/setup-go@v3 22 | with: 23 | go-version: 1.18 24 | 25 | - name: Set up QEMU 26 | uses: docker/setup-qemu-action@v2 27 | 28 | - name: Set up Docker Buildx 29 | uses: docker/setup-buildx-action@v2 30 | 31 | - name: Login to GitHub Container Registry 32 | uses: docker/login-action@v2 33 | with: 34 | registry: ghcr.io 35 | username: ${{ github.repository_owner }} 36 | password: ${{ secrets.GITHUB_TOKEN }} 37 | 38 | - name: Run GoReleaser 39 | uses: goreleaser/goreleaser-action@v2 40 | with: 41 | version: latest 42 | args: release --rm-dist 43 | env: 44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | config.toml 18 | .env 19 | bin/ 20 | data/ 21 | dist/ 22 | .vscode/ 23 | vector.toml 24 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | env: 2 | - GO111MODULE=on 3 | - CGO_ENABLED=0 4 | 5 | builds: 6 | - binary: nomad-vector-logger.bin 7 | id: nomad-vector-logger 8 | goos: 9 | - linux 10 | - darwin 11 | goarch: 12 | - amd64 13 | - arm64 14 | ldflags: 15 | - -s -w -X "main.buildString={{ .Tag }} ({{ .ShortCommit }} {{ .Date }})" 16 | dir: . 17 | 18 | archives: 19 | - format: tar.gz 20 | files: 21 | - README.md 22 | - LICENSE 23 | - config.sample.toml 24 | 25 | dockers: 26 | - image_templates: ["ghcr.io/mr-karan/nomad-vector-logger:{{ .Tag }}-amd64"] 27 | goarch: amd64 28 | dockerfile: Dockerfile 29 | use: buildx 30 | build_flag_templates: 31 | - "--pull" 32 | - "--label=org.opencontainers.image.created={{.Date}}" 33 | - "--label=org.opencontainers.image.title={{.ProjectName}}" 34 | - "--label=org.opencontainers.image.revision={{.FullCommit}}" 35 | - "--label=org.opencontainers.image.version={{.Version}}" 36 | - "--platform=linux/amd64" 37 | extra_files: 38 | - config.sample.toml 39 | - image_templates: ["ghcr.io/mr-karan/nomad-vector-logger:{{ .Tag }}-arm64"] 40 | goarch: arm64 41 | dockerfile: Dockerfile 42 | use: buildx 43 | build_flag_templates: 44 | - "--pull" 45 | - "--label=org.opencontainers.image.created={{.Date}}" 46 | - "--label=org.opencontainers.image.title={{.ProjectName}}" 47 | - "--label=org.opencontainers.image.revision={{.FullCommit}}" 48 | - "--label=org.opencontainers.image.version={{.Version}}" 49 | - "--platform=linux/arm64" 50 | extra_files: 51 | - config.sample.toml 52 | 53 | docker_manifests: 54 | - name_template: ghcr.io/mr-karan/nomad-vector-logger:{{ .Tag }} 55 | image_templates: 56 | - ghcr.io/mr-karan/nomad-vector-logger:{{ .Tag }}-amd64 57 | - ghcr.io/mr-karan/nomad-vector-logger:{{ .Tag }}-arm64 58 | - name_template: ghcr.io/mr-karan/nomad-vector-logger:latest 59 | image_templates: 60 | - ghcr.io/mr-karan/nomad-vector-logger:{{ .Tag }}-amd64 61 | - ghcr.io/mr-karan/nomad-vector-logger:{{ .Tag }}-arm64 62 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | WORKDIR /app 3 | COPY nomad-vector-logger.bin . 4 | COPY config.sample.toml . 5 | CMD ["./nomad-vector-logger.bin"] 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Karan Sharma 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | APP-BIN := ./bin/nomad-vector-logger.bin 2 | 3 | LAST_COMMIT := $(shell git rev-parse --short HEAD) 4 | LAST_COMMIT_DATE := $(shell git show -s --format=%ci ${LAST_COMMIT}) 5 | VERSION := $(shell git describe --tags) 6 | BUILDSTR := ${VERSION} (Commit: ${LAST_COMMIT_DATE} (${LAST_COMMIT}), Build: $(shell date +"%Y-%m-%d% %H:%M:%S %z")) 7 | 8 | .PHONY: build 9 | build: ## Build binary. 10 | go build -o ${APP-BIN} -ldflags="-X 'main.buildString=${BUILDSTR}'" 11 | 12 | .PHONY: run 13 | run: ## Run binary. 14 | ./${APP-BIN} 15 | 16 | .PHONY: fresh 17 | fresh: build run 18 | 19 | .PHONY: lint 20 | lint: 21 | docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint:latest golangci-lint run -v 22 | 23 | .PHONY: dev 24 | dev: 25 | ./run.sh 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # nomad-vector-logger 4 | 5 | A daemon which continuously watches for deployments in a [Nomad](https://www.nomadproject.io/) cluster and generates a [Vector](https://vector.dev/) configuration file, which can be used to collect logs enriched with Nomad **metadata**. 6 | 7 | Each log event is annotated with the following metadata: 8 | 9 | - Namespace of the application 10 | - Node where the deployment is running 11 | - Job name 12 | - Group name 13 | - Task name 14 | - Allocation ID 15 | 16 | ## Why 17 | 18 | ### Problem 19 | 20 | Currently, Nomad stores all application logs inside `$NOMAD_DATA_DIR/$NOMAD_ALLOC_DIR/logs/` directory. The limitation is that these logs don't have any information about the task/job/allocation etc. Suppose there are multiple deployments on the same host. In that case, no central log collecting agent can distinguish and process these logs uniquely. 21 | 22 | For the `docker` driver, this is a non-issue since logging of tasks with the docker driver is configured with [`logging`](https://www.nomadproject.io/docs/drivers/docker#config-1) stanza. 23 | 24 | Users running deployments with `raw_exec` and `exec` as the task driver will find that no such configuration exists as mentioned in this [GitHub Issue](https://github.com/hashicorp/nomad/issues/10219). 25 | 26 | ### Solution 27 | 28 | - `nomad-vector-logger` is a daemon that runs in the background, periodically polling for `Allocations` on the node. 29 | - It then generates a `vector` configuration to collect logs from the allocation's log directory. It enriches the log event with relevant metadata. 30 | - `vector` is started with a [`--watch-config`](https://vector.dev/docs/administration/management/#reloading) flag, which automatically live-reloads `vector` whenever config changes. A config change can happen whenever an allocation is _created/stopped/restarted_. 31 | 32 | You can see a sample [config file](./sample/nomad.toml) that is generated by this daemon. This config file can be used in addition to other `vector` config files to provide the config for the rest of the pipeline (additional transformations, sinks etc.). 33 | 34 | #### Before 35 | 36 | Logs without any metdata on `/opt/nomad/data/alloc/$ALLOC_ID/alloc/logs`: 37 | 38 | ``` 39 | ==> proxy.stdout.0 <== 40 | 192.168.29.76 - - [03/Sep/2022:17:30:36 +0000] "GET / HTTP/1.1" 200 27 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:103.0) Gecko/20100101 Firefox/103.0" "-" 41 | ``` 42 | 43 | #### After 44 | 45 | This is an example JSON log collected from `nginx` task running with `raw_exec` task driver on Nomad, collected using `vector`: 46 | 47 | ```json 48 | { 49 | "file": "/opt/nomad/data/alloc/64a2f9fd-e003-0bb3-b5cd-838125283a06/alloc/logs/proxy.stdout.0", 50 | "host": "pop-os", 51 | "message": "192.168.29.76 - - [03/Sep/2022:17:30:36 +0000] \"GET / HTTP/1.1\" 200 27 \"-\" \"Mozilla/5.0 (X11; Linux x86_64; rv:103.0) Gecko/20100101 Firefox/103.0\" \"-\"", 52 | "nomad": { 53 | "alloc_id": "64a2f9fd-e003-0bb3-b5cd-838125283a06", 54 | "group_name": "nginx", 55 | "job_name": "nginx", 56 | "namespace": "default", 57 | "node_name": "pop-os", 58 | "task_name": "proxy" 59 | }, 60 | "source_type": "file", 61 | "timestamp": "2022-09-03T17:30:42.569487273Z" 62 | } 63 | ``` 64 | 65 | ## Dev Setup 66 | 67 | ``` 68 | make dev 69 | ``` 70 | 71 | You can refer to a local dev suite which runs this program in a Nomad cluster. The [jobspec](./dev/deployment.nomad) can also be used as a reference for production deployment. 72 | 73 | ## Deployment Notes 74 | 75 | - This program is meant to be run inside a Nomad cluster and should have proper ACL to fetch `Allocation:*` events. You can use this ACL policy to generate a token: 76 | 77 | ```hcl 78 | namespace "*" { 79 | policy = "read" 80 | } 81 | 82 | node { 83 | policy = "read" 84 | } 85 | 86 | agent { 87 | policy = "read" 88 | } 89 | ``` 90 | 91 | - It's preferable to run it as a `system` job. Each program allocation will be responsible for configuring `vector` to collect logs from that particular log directory on the host. 92 | 93 | You can choose one of the various deployment options: 94 | 95 | ### Binary 96 | 97 | Grab the latest release from [Releases](https://github.com/mr-karan/nomad-vector-logger/releases). 98 | 99 | To run: 100 | 101 | ``` 102 | $ ./nomad-vector-logger.bin --config config.toml 103 | ``` 104 | 105 | ### Nomad 106 | 107 | View a sample deployment file at [dev/deployment.nomad](./dev/deployment.nomad). 108 | 109 | ### Docker 110 | 111 | Docker images are available on [GitHub](https://github.com/mr-karan/nomad-vector-logger/pkgs/container/nomad-vector-logger). 112 | 113 | ## Configuration 114 | 115 | Refer to [config.sample.toml](./config.sample.toml) for a list of configurable values. 116 | 117 | ### Environment Variables 118 | 119 | All config variables can also be populated as env variables by prefixing `NOMAD_VECTOR_LOGGER_` and replacing `.` with `__`. 120 | 121 | For eg: `app.data_dir` becomes `NOMAD_VECTOR_LOGGER_app__data_dir`. 122 | 123 | ## Contribution 124 | 125 | Please feel free to open a new issue for bugs, feedback etc. 126 | 127 | ## LICENSE 128 | 129 | [LICENSE](./LICENSE) 130 | -------------------------------------------------------------------------------- /alloc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "path" 9 | "path/filepath" 10 | "text/template" 11 | 12 | "github.com/hashicorp/nomad/api" 13 | ) 14 | 15 | // fetchRunningAllocs fetches all the current allocations in the cluster. 16 | // It ignores the alloc which aren't running on the current node. 17 | func (app *App) fetchRunningAllocs() (map[string]*api.Allocation, error) { 18 | allocs := make(map[string]*api.Allocation, 0) 19 | 20 | // Only fetch the allocations running on this noe. 21 | params := map[string]string{} 22 | params["filter"] = fmt.Sprintf("NodeID==\"%s\"", app.nodeID) 23 | 24 | // Prepare params for listing alloc. 25 | query := &api.QueryOptions{ 26 | Params: params, 27 | Namespace: "*", 28 | } 29 | 30 | // Query list of allocs. 31 | currentAllocs, meta, err := app.nomadClient.Allocations().List(query) 32 | if err != nil { 33 | return nil, err 34 | } 35 | app.log.Debug("fetched existing allocs", "count", len(currentAllocs), "took", meta.RequestTime) 36 | 37 | // For each alloc, check if it's running and get the underlying alloc info. 38 | for _, allocStub := range currentAllocs { 39 | if allocStub.ClientStatus != "running" { 40 | app.log.Debug("ignoring alloc since it's not running", "name", allocStub.Name, "status", allocStub.ClientStatus) 41 | continue 42 | } 43 | // Skip the allocations which aren't running on this node. 44 | if allocStub.NodeID != app.nodeID { 45 | app.log.Debug("skipping alloc because it doesn't run on this node", "name", allocStub.Name, "alloc_node", allocStub.NodeID, "node", app.nodeID) 46 | continue 47 | } else { 48 | app.log.Debug("alloc belongs to the current node", "name", allocStub.Name, "alloc_node", allocStub.NodeID, "node", app.nodeID) 49 | } 50 | 51 | prefix := path.Join(app.opts.nomadDataDir, allocStub.ID) 52 | app.log.Debug("checking if alloc log dir exists", "name", allocStub.Name, "alloc_node", allocStub.NodeID, "node", app.nodeID) 53 | _, err := os.Stat(prefix) 54 | if errors.Is(err, os.ErrNotExist) { 55 | app.log.Debug("log dir doesn't exist", "dir", prefix, "name", allocStub.Name, "alloc_node", allocStub.NodeID, "node", app.nodeID) 56 | // Skip the allocation if it has been GC'ed from host but still the API returned. 57 | // Unlikely case to happen. 58 | continue 59 | } else if err != nil { 60 | app.log.Error("error checking if alloc dir exists on host", "error", err) 61 | continue 62 | } 63 | 64 | if alloc, _, err := app.nomadClient.Allocations().Info(allocStub.ID, &api.QueryOptions{Namespace: allocStub.Namespace}); err != nil { 65 | app.log.Error("unable to fetch alloc info", "error", err) 66 | continue 67 | } else { 68 | allocs[alloc.ID] = alloc 69 | } 70 | } 71 | 72 | // Return map of allocs. 73 | return allocs, nil 74 | } 75 | 76 | // generateConfig generates a vector config file by iterating on a 77 | // map of allocations in the cluster and adding some extra metadata about the alloc. 78 | // It creates a config file on the disk which vector is _live_ watching and reloading 79 | // whenever it changes. 80 | func (app *App) generateConfig(allocs map[string]*api.Allocation) error { 81 | // Create a config dir to store templates. 82 | if err := os.MkdirAll(app.opts.vectorConfigDir, os.ModePerm); err != nil { 83 | return fmt.Errorf("error creating dir %s: %v", app.opts.vectorConfigDir, err) 84 | } 85 | 86 | // Load the vector config template. 87 | tpl, err := template.ParseFS(vectorTmpl, "vector.toml.tmpl") 88 | if err != nil { 89 | return fmt.Errorf("unable to parse template: %v", err) 90 | } 91 | 92 | // Special case to handle where there are no allocs. 93 | // If this is the case, the user supplie config which relies on sources/transforms 94 | // as the input for the sink can fail as the generated `nomad.toml` will be empty. 95 | // To avoid this, remove all files inside the existing config dir and exit the function. 96 | if len(allocs) == 0 { 97 | app.log.Info("no current alloc is running, cleaning up config dir", "vector_dir", app.opts.vectorConfigDir) 98 | dir, err := ioutil.ReadDir(app.opts.vectorConfigDir) 99 | if err != nil { 100 | return fmt.Errorf("error reading vector config dir") 101 | } 102 | for _, d := range dir { 103 | if err := os.RemoveAll(path.Join([]string{app.opts.vectorConfigDir, d.Name()}...)); err != nil { 104 | return fmt.Errorf("error cleaning up config dir") 105 | } 106 | } 107 | return nil 108 | } 109 | 110 | data := make([]AllocMeta, 0) 111 | 112 | // Iterate on allocs in the map. 113 | for _, alloc := range allocs { 114 | // Add metadata for each task in the alloc. 115 | for task := range alloc.TaskResources { 116 | // Add task to the data. 117 | data = append(data, AllocMeta{ 118 | Key: fmt.Sprintf("nomad_alloc_%s_%s", alloc.ID, task), 119 | ID: alloc.ID, 120 | LogDir: filepath.Join(fmt.Sprintf("%s/%s", app.opts.nomadDataDir, alloc.ID), "alloc/logs/"+task+"*"), 121 | Namespace: alloc.Namespace, 122 | Group: alloc.TaskGroup, 123 | Node: alloc.NodeName, 124 | Task: task, 125 | Job: alloc.JobID, 126 | }) 127 | } 128 | } 129 | 130 | app.log.Info("generating config with total tasks", "count", len(data)) 131 | file, err := os.Create(filepath.Join(app.opts.vectorConfigDir, "nomad.toml")) 132 | if err != nil { 133 | return fmt.Errorf("error creating vector config file: %v", err) 134 | } 135 | defer file.Close() 136 | 137 | if err := tpl.Execute(file, data); err != nil { 138 | return fmt.Errorf("error executing template: %v", err) 139 | } 140 | 141 | // Load all user provided templates. 142 | if app.opts.extraTemplatesDir != "" { 143 | // Loop over all files mentioned in the templates dir. 144 | files, err := ioutil.ReadDir(app.opts.extraTemplatesDir) 145 | if err != nil { 146 | return fmt.Errorf("error opening extra template file: %v", err) 147 | } 148 | 149 | // For all files, template it out and store in vector config dir. 150 | for _, file := range files { 151 | // Load the vector config template. 152 | t, err := template.ParseFiles(filepath.Join(app.opts.extraTemplatesDir, file.Name())) 153 | if err != nil { 154 | return fmt.Errorf("unable to parse template: %v", err) 155 | } 156 | 157 | // Create the underlying file. 158 | f, err := os.Create(filepath.Join(app.opts.vectorConfigDir, file.Name())) 159 | if err != nil { 160 | return fmt.Errorf("error creating extra template file: %v", err) 161 | } 162 | defer f.Close() 163 | 164 | if err := t.Execute(f, data); err != nil { 165 | return fmt.Errorf("error executing extra template: %v", err) 166 | } 167 | } 168 | } 169 | 170 | return nil 171 | } 172 | -------------------------------------------------------------------------------- /app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "time" 7 | 8 | "github.com/hashicorp/nomad/api" 9 | "github.com/zerodha/logf" 10 | ) 11 | 12 | type Opts struct { 13 | refreshInterval time.Duration 14 | removeAllocInterval time.Duration 15 | nomadDataDir string 16 | vectorConfigDir string 17 | extraTemplatesDir string 18 | } 19 | 20 | // App is the global container that holds 21 | // objects of various routines that run on boot. 22 | type App struct { 23 | sync.RWMutex 24 | log logf.Logger 25 | opts Opts 26 | nomadClient *api.Client 27 | nodeID string // Self NodeID where this program is running. 28 | allocs map[string]*api.Allocation // Map of Alloc ID and Allocation object running in the cluster. 29 | expiredAllocs []string 30 | configUpdated chan bool 31 | } 32 | 33 | type AllocMeta struct { 34 | Key string 35 | ID string 36 | LogDir string 37 | Job string 38 | Namespace string 39 | Task string 40 | Node string 41 | Group string 42 | } 43 | 44 | // Start initialises the subscription stream in background and waits 45 | // for context to be cancelled to exit. 46 | func (app *App) Start(ctx context.Context) { 47 | wg := &sync.WaitGroup{} 48 | 49 | // Start a background worker for fetching allocs and generating template. 50 | wg.Add(1) 51 | go func() { 52 | defer wg.Done() 53 | app.UpdateAllocs(ctx, app.opts.refreshInterval) 54 | }() 55 | 56 | // Start a background worker for removing expired allocs. 57 | wg.Add(1) 58 | go func() { 59 | defer wg.Done() 60 | app.CleanupAllocs(ctx, app.opts.removeAllocInterval) 61 | }() 62 | 63 | // Start a background worker for updating config. 64 | wg.Add(1) 65 | go func() { 66 | defer wg.Done() 67 | app.ConfigUpdater(ctx) 68 | }() 69 | 70 | // Wait for all routines to finish. 71 | wg.Wait() 72 | } 73 | 74 | // UpdateAllocs fetches Nomad allocs from all the namespaces 75 | // at periodic interval and generates the templates. 76 | // This is a blocking function so the caller must invoke as a goroutine. 77 | func (app *App) UpdateAllocs(ctx context.Context, refreshInterval time.Duration) { 78 | var ( 79 | ticker = time.NewTicker(refreshInterval).C 80 | ) 81 | 82 | for { 83 | select { 84 | case <-ticker: 85 | // Fetch the list of allocs running on this node. 86 | runningAllocs, err := app.fetchRunningAllocs() 87 | if err != nil { 88 | app.log.Error("error fetching allocs", "error", err) 89 | continue 90 | } 91 | 92 | // Copy the map of allocs so we don't have to hold the lock longer. 93 | app.RLock() 94 | presentAllocs := app.allocs 95 | app.RUnlock() 96 | 97 | updateCnt := 0 98 | if len(presentAllocs) > 0 { 99 | for _, a := range runningAllocs { 100 | // If an alloc is present in the running list but missing in our map, that means we should add it to our map. 101 | if _, ok := presentAllocs[a.ID]; !ok { 102 | app.log.Info("adding new alloc to map", "id", a.ID, "namespace", a.Namespace, "job", a.Job.Name, "group", a.TaskGroup) 103 | app.Lock() 104 | app.allocs[a.ID] = a 105 | app.Unlock() 106 | updateCnt++ 107 | } 108 | } 109 | } else { 110 | // If this is the first run of the program, `allocs` will be empty. 111 | // This ideally only happens once when the program boots up. 112 | app.Lock() 113 | app.allocs = runningAllocs 114 | app.Unlock() 115 | app.configUpdated <- true 116 | break 117 | } 118 | 119 | // Only generate config if there were additions. 120 | if updateCnt > 0 { 121 | app.configUpdated <- true 122 | } 123 | 124 | // Now check if present allocs include allocs which aren't running anymore. 125 | for _, r := range presentAllocs { 126 | // This means that the alloc id we have in our map isn't running anymore, so enqueue it for deletion. 127 | if _, ok := runningAllocs[r.ID]; !ok { 128 | app.log.Info("enqueing non running alloc for deletion", "id", r.ID, "namespace", r.Namespace, "job", r.Job.Name, "group", r.TaskGroup) 129 | app.expiredAllocs = append(app.expiredAllocs, r.ID) 130 | } 131 | } 132 | 133 | case <-ctx.Done(): 134 | app.log.Warn("context cancellation received, quitting update worker") 135 | return 136 | } 137 | } 138 | } 139 | 140 | // CleanupAllocs removes the alloc from the map which are marked for deletion. 141 | // These could be old allocs that aren't running anymore but which need to be removed from 142 | // the config after a delay to ensure Vector has finished pushing all logs to upstream sink. 143 | func (app *App) CleanupAllocs(ctx context.Context, removeAllocInterval time.Duration) { 144 | var ( 145 | ticker = time.NewTicker(removeAllocInterval).C 146 | ) 147 | 148 | for { 149 | select { 150 | case <-ticker: 151 | deleteCnt := 0 152 | app.Lock() 153 | for _, id := range app.expiredAllocs { 154 | app.log.Info("cleaning up alloc", "id", id) 155 | delete(app.allocs, id) 156 | deleteCnt++ 157 | } 158 | // Reset the expired allocs to nil. 159 | app.expiredAllocs = nil 160 | app.Unlock() 161 | 162 | // Only generate config if there were deletions. 163 | if deleteCnt > 0 { 164 | app.configUpdated <- true 165 | } 166 | 167 | case <-ctx.Done(): 168 | app.log.Warn("context cancellation received, quitting cleanup worker") 169 | return 170 | } 171 | } 172 | } 173 | 174 | func (app *App) ConfigUpdater(ctx context.Context) { 175 | for { 176 | select { 177 | case <-app.configUpdated: 178 | app.RLock() 179 | allocs := app.allocs 180 | app.RUnlock() 181 | err := app.generateConfig(allocs) 182 | if err != nil { 183 | app.log.Error("error generating config", "error", err) 184 | } 185 | 186 | case <-ctx.Done(): 187 | app.log.Warn("context cancellation received, quitting config worker") 188 | return 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /config.sample.toml: -------------------------------------------------------------------------------- 1 | [app] 2 | log_level = "debug" # `debug` for verbose logs. `info` otherwise. 3 | env = "dev" # dev|prod. 4 | refresh_interval = "10s" # Interval at which list of allocations is updated. 5 | remove_alloc_interval = "30s" # If the alloc is completed or stopped, the allocation isn't removed immediately from vector's config. You can delay the removal of alloc by `n` duration to ensure that vector has finished collecting all logs till then. 6 | nomad_data_dir = "/opt/nomad/data/alloc" # Nomad data directory where alloc logs are stored. 7 | vector_config_dir = "examples/vector/" # Path to the generated vector config file. 8 | extra_templates_dir = "static/" # Extra templates that can be given. They will be rendered in directory mentioned by `vector_config_dir`. You can use variables mentioned in vector.toml.tmpl if required. 9 | -------------------------------------------------------------------------------- /dev/base.toml.tpl: -------------------------------------------------------------------------------- 1 | # Directory for Vector data storage: 2 | data_dir ="[[ env "NOMAD_ALLOC_DIR" ]]/data/" # Make sure the user which runs vector has R/W access to this directory. 3 | 4 | # Vector's API for introspection 5 | [api] 6 | enabled = true 7 | address = "0.0.0.0:8686" 8 | 9 | [sources.vector_logs] 10 | type = "internal_logs" 11 | 12 | [sinks.vector_stdout] 13 | type = "console" 14 | inputs = ["vector_logs"] 15 | target = "stdout" 16 | 17 | [sinks.vector_stdout.encoding] 18 | codec = "json" 19 | -------------------------------------------------------------------------------- /dev/deployment.nomad: -------------------------------------------------------------------------------- 1 | job "vector" { 2 | datacenters = ["dc1"] 3 | namespace = "default" 4 | type = "system" 5 | 6 | group "vector" { 7 | count = 1 8 | restart { 9 | attempts = 3 10 | interval = "10m" 11 | delay = "30s" 12 | mode = "fail" 13 | } 14 | 15 | # For vector's data directory. 16 | ephemeral_disk { 17 | size = 500 18 | sticky = true 19 | } 20 | 21 | network { 22 | port "metrics" { 23 | to = 8686 24 | static = 8686 25 | } 26 | mode = "host" 27 | } 28 | 29 | service { 30 | provider = "nomad" 31 | name = "vector-agent-monitoring" 32 | port = "metrics" 33 | } 34 | 35 | task "vector" { 36 | # Need access to the underlying `/opt/nomad/data/alloc` directory to get the logs. 37 | driver = "raw_exec" 38 | 39 | # Run the program. 40 | config { 41 | # Path to vector on your host machine. 42 | command = "/usr/local/bin/vector" 43 | args = [ 44 | "--watch-config", 45 | "--config", 46 | "$${NOMAD_TASK_DIR}/base.toml", 47 | "--config-dir=$${NOMAD_ALLOC_DIR}/vector_gen_configs/" 48 | ] 49 | } 50 | 51 | resources { 52 | cpu = 500 53 | memory = 500 54 | } 55 | 56 | # Template with Vector's configuration 57 | template { 58 | data = file(abspath("./dev/base.toml.tpl")) 59 | destination = "local/base.toml" 60 | change_mode = "restart" 61 | # overriding the delimiters to [[ ]] to avoid conflicts with Vector's native templating, which also uses {{ }} 62 | left_delimiter = "[[" 63 | right_delimiter = "]]" 64 | } 65 | 66 | # Some delay in killing the task in case Vector is still sending data to upstream sinks, 67 | kill_timeout = "60s" 68 | } 69 | 70 | task "nomad-vector-logger" { 71 | # Need access to Nomad API running. 72 | driver = "raw_exec" 73 | 74 | # Should run first so that it can generate a config for vector. 75 | lifecycle { 76 | hook = "prestart" 77 | sidecar = true 78 | } 79 | 80 | meta { 81 | nomad_addr = "${nomad.advertise.address}" 82 | } 83 | 84 | # Run the program. 85 | config { 86 | command = "/usr/local/bin/nomad-vector-logger.bin" 87 | args = [ 88 | "--config", 89 | "$${NOMAD_TASK_DIR}/nomad-vector-logger.toml" 90 | ] 91 | } 92 | 93 | resources { 94 | cpu = 500 95 | memory = 500 96 | } 97 | 98 | # Template with Vector's configuration 99 | template { 100 | data = file(abspath("./dev/nomad-vector-logger.toml.tpl")) 101 | destination = "local/nomad-vector-logger.toml" 102 | change_mode = "restart" 103 | } 104 | template { 105 | data = <&1 >/dev/null; do echo '.'; sleep 2; done" 131 | ] 132 | } 133 | 134 | resources { 135 | cpu = 200 136 | memory = 128 137 | } 138 | 139 | lifecycle { 140 | hook = "prestart" 141 | sidecar = false 142 | } 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /dev/nomad-vector-logger.toml.tpl: -------------------------------------------------------------------------------- 1 | [app] 2 | log_level = "debug" # `debug` for verbose logs. `info` otherwise. 3 | env = "dev" # dev|prod. 4 | refresh_interval = "10s" # Interval at which list of allocations is updated. 5 | remove_alloc_interval = "30s" # If the alloc is completed or stopped, the allocation isn't removed immediately from vector's config. You can delay the removal of alloc by `n` duration to ensure that vector has finished collecting all logs till then. 6 | nomad_data_dir = "/opt/nomad/data/alloc" # Nomad data directory where alloc logs are stored. 7 | vector_config_dir = "{{ env "NOMAD_ALLOC_DIR" }}/vector_gen_configs" # Path to the generated vector config file. 8 | extra_templates_dir = "{{ env "NOMAD_TASK_DIR" }}/static/" # Extra templates that can be given. They will be rendered in `$vector_config_dir`. You can use variables mentioned in vector.tmpl if required. 9 | -------------------------------------------------------------------------------- /dev/static/sink.toml.tpl: -------------------------------------------------------------------------------- 1 | # Whitelist selected namespaces, drop the rest. 2 | [transforms.route_logs] 3 | type = "route" 4 | inputs = ["transform_nomad_alloc*"] 5 | # Route conditions 6 | route."nginx" = '.nomad.job_name == "nginx"' 7 | 8 | [sinks.stdout_nomad] 9 | type = "console" 10 | inputs = ["route_logs.nginx"] 11 | target = "stdout" 12 | encoding.codec = "json" 13 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mr-karan/nomad-vector-logger 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/hashicorp/nomad/api v0.0.0-20220902193006-d33f1eac719c 7 | github.com/knadh/koanf v1.4.3 8 | github.com/spf13/pflag v1.0.5 9 | github.com/zerodha/logf v0.5.5 10 | ) 11 | 12 | require ( 13 | github.com/fsnotify/fsnotify v1.5.4 // indirect 14 | github.com/gorilla/websocket v1.5.0 // indirect 15 | github.com/hashicorp/cronexpr v1.1.1 // indirect 16 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 17 | github.com/hashicorp/go-rootcerts v1.0.2 // indirect 18 | github.com/mitchellh/copystructure v1.2.0 // indirect 19 | github.com/mitchellh/go-homedir v1.1.0 // indirect 20 | github.com/mitchellh/mapstructure v1.5.0 // indirect 21 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 22 | github.com/pelletier/go-toml v1.9.5 // indirect 23 | golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect 24 | ) 25 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 4 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 5 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 6 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 7 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 8 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= 9 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 10 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 11 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 12 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 13 | github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 14 | github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= 15 | github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw= 16 | github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ= 17 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0/go.mod h1:gqlclDEZp4aqJOancXK6TN24aKhT0W0Ae9MHk3wzTMM= 18 | github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4/go.mod h1:ZcBrrI3zBKlhGFNYWvju0I3TR93I7YIgAfy82Fh4lcQ= 19 | github.com/aws/aws-sdk-go-v2/service/appconfig v1.4.2/go.mod h1:FZ3HkCe+b10uFZZkFdvf98LHW21k49W8o8J366lqVKY= 20 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72HRZDLMtmVQiLG2tLfQcaWLCssELvGl+Zf2WVxMmR8= 21 | github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk= 22 | github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g= 23 | github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= 24 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 25 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 26 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 27 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 28 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 29 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 30 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 31 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 32 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 33 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 34 | github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 35 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 36 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 37 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 38 | github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= 39 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 40 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 41 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 42 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 43 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 44 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 45 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 46 | github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= 47 | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 48 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 49 | github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= 50 | github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= 51 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 52 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 53 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 54 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 55 | github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= 56 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 57 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 58 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 59 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 60 | github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= 61 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 62 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 63 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 64 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 65 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 66 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 67 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 68 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 69 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 70 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 71 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 72 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 73 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 74 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 75 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 76 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 77 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 78 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 79 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 80 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 81 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 82 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 83 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 84 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 85 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 86 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 87 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 88 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 89 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 90 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 91 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= 92 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 93 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 94 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 95 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 96 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 97 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 98 | github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ= 99 | github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= 100 | github.com/hashicorp/cronexpr v1.1.1 h1:NJZDd87hGXjoZBdvyCF9mX4DCq5Wy7+A/w+A7q0wn6c= 101 | github.com/hashicorp/cronexpr v1.1.1/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= 102 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 103 | github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 104 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 105 | github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 106 | github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 107 | github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= 108 | github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= 109 | github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= 110 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 111 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 112 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 113 | github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= 114 | github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= 115 | github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= 116 | github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= 117 | github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= 118 | github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= 119 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 120 | github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= 121 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 122 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 123 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 124 | github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 125 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 126 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 127 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 128 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 129 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 130 | github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= 131 | github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= 132 | github.com/hashicorp/nomad/api v0.0.0-20220902193006-d33f1eac719c h1:2iGTymqIZitYVzzKV3DnFxTFT2T5twce2mBj2M2WGCQ= 133 | github.com/hashicorp/nomad/api v0.0.0-20220902193006-d33f1eac719c/go.mod h1:Z0U0rpbh4Qlkgqu3iRDcfJBA+r3FgoeD1BfigmZhfzM= 134 | github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= 135 | github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= 136 | github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= 137 | github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= 138 | github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= 139 | github.com/hjson/hjson-go/v4 v4.0.0 h1:wlm6IYYqHjOdXH1gHev4VoXCaW20HdQAGCxdOEEg2cs= 140 | github.com/hjson/hjson-go/v4 v4.0.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E= 141 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 142 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 143 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= 144 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 145 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 146 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 147 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 148 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 149 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 150 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 151 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 152 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 153 | github.com/knadh/koanf v1.4.3 h1:rSJcSH5LSFhvzBRsAYfT3k7eLP0I4UxeZqjtAatk+wc= 154 | github.com/knadh/koanf v1.4.3/go.mod h1:5FAkuykKXZvLqhAbP4peWgM5CTcZmn7L1d27k/a+kfg= 155 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 156 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 157 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 158 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 159 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 160 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 161 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 162 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 163 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 164 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 165 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 166 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 167 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 168 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 169 | github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= 170 | github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= 171 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 172 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 173 | github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= 174 | github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= 175 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 176 | github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= 177 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= 178 | github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= 179 | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= 180 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 181 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 182 | github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 183 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 184 | github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= 185 | github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 186 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 187 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 188 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 189 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 190 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 191 | github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= 192 | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 193 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 194 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 195 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 196 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 197 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 198 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 199 | github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk= 200 | github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= 201 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 202 | github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 203 | github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= 204 | github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= 205 | github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 206 | github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 207 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 208 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 209 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 210 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 211 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 212 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 213 | github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= 214 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 215 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 216 | github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= 217 | github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= 218 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 219 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 220 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 221 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 222 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 223 | github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= 224 | github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= 225 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 226 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 227 | github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= 228 | github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 229 | github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA= 230 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 231 | github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= 232 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 233 | github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 234 | github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= 235 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 236 | github.com/shoenig/test v0.3.1 h1:dhGZztS6nQuvJ0o0RtUiQHaEO4hhArh/WmWwik3Ols0= 237 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 238 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 239 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 240 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 241 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 242 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 243 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 244 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 245 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 246 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 247 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 248 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 249 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 250 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 251 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 252 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 253 | github.com/zerodha/logf v0.5.5 h1:AhxHlixHNYwhFjvlgTv6uO4VBKYKxx2I6SbHoHtWLBk= 254 | github.com/zerodha/logf v0.5.5/go.mod h1:HWpfKsie+WFFpnUnUxelT6Z0FC6xu9+qt+oXNMPg6y8= 255 | go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= 256 | go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= 257 | go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= 258 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 259 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 260 | go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= 261 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 262 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 263 | golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= 264 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 265 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 266 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 267 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 268 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 269 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 270 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 271 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 272 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 273 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 274 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 275 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 276 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 277 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 278 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 279 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 280 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 281 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 282 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 283 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 284 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 285 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 286 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 287 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 288 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 289 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 290 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 291 | golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= 292 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 293 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 294 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 295 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 296 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 297 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 298 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 299 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 300 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 301 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 302 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 303 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 304 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 305 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 306 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 307 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 308 | golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 309 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 310 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 311 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 312 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 313 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 314 | golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 315 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 316 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 317 | golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 318 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 319 | golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 320 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 321 | golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 322 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 323 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 324 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 325 | golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 326 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 327 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 328 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 329 | golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 330 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 331 | golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 332 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 333 | golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 334 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 335 | golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY= 336 | golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 337 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 338 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 339 | golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 340 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 341 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 342 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 343 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 344 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 345 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 346 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 347 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 348 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 349 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 350 | golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 351 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 352 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 353 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 354 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 355 | golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 356 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 357 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 358 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 359 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 360 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 361 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 362 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 363 | google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 364 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 365 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 366 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 367 | google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= 368 | google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= 369 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 370 | google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 371 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 372 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 373 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 374 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 375 | google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 376 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 377 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 378 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 379 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 380 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 381 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 382 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 383 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 384 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 385 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 386 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 387 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 388 | gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= 389 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 390 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 391 | gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 392 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 393 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 394 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 395 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 396 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 397 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 398 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 399 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 400 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 401 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 402 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 403 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 404 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 405 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 406 | sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= 407 | -------------------------------------------------------------------------------- /init.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/hashicorp/nomad/api" 9 | "github.com/knadh/koanf" 10 | "github.com/knadh/koanf/parsers/toml" 11 | "github.com/knadh/koanf/providers/env" 12 | "github.com/knadh/koanf/providers/file" 13 | flag "github.com/spf13/pflag" 14 | "github.com/zerodha/logf" 15 | ) 16 | 17 | // initLogger initializes logger instance. 18 | func initLogger(ko *koanf.Koanf) logf.Logger { 19 | opts := logf.Opts{EnableCaller: true} 20 | if ko.String("app.log_level") == "debug" { 21 | opts.Level = logf.DebugLevel 22 | } 23 | if ko.String("app.env") == "dev" { 24 | opts.EnableColor = true 25 | } 26 | return logf.New(opts) 27 | } 28 | 29 | // initConfig loads config to `ko` object. 30 | func initConfig(cfgDefault string, envPrefix string) (*koanf.Koanf, error) { 31 | var ( 32 | ko = koanf.New(".") 33 | f = flag.NewFlagSet("front", flag.ContinueOnError) 34 | ) 35 | 36 | // Configure Flags. 37 | f.Usage = func() { 38 | fmt.Println(f.FlagUsages()) 39 | os.Exit(0) 40 | } 41 | 42 | // Register `--config` flag. 43 | cfgPath := f.String("config", cfgDefault, "Path to a config file to load.") 44 | 45 | // Parse and Load Flags. 46 | err := f.Parse(os.Args[1:]) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | // Load the config files from the path provided. 52 | err = ko.Load(file.Provider(*cfgPath), toml.Parser()) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | // Load environment variables if the key is given 58 | // and merge into the loaded config. 59 | if envPrefix != "" { 60 | err = ko.Load(env.Provider(envPrefix, ".", func(s string) string { 61 | return strings.Replace(strings.ToLower( 62 | strings.TrimPrefix(s, envPrefix)), "__", ".", -1) 63 | }), nil) 64 | if err != nil { 65 | return nil, err 66 | } 67 | } 68 | 69 | return ko, nil 70 | } 71 | 72 | // initNomadClient initialises a Nomad API client. 73 | func initNomadClient() (*api.Client, error) { 74 | client, err := api.NewClient(api.DefaultConfig()) 75 | if err != nil { 76 | return nil, err 77 | } 78 | return client, nil 79 | } 80 | 81 | func initOpts(ko *koanf.Koanf) Opts { 82 | return Opts{ 83 | refreshInterval: ko.MustDuration("app.refresh_interval"), 84 | removeAllocInterval: ko.MustDuration("app.remove_alloc_interval"), 85 | nomadDataDir: ko.MustString("app.nomad_data_dir"), 86 | vectorConfigDir: ko.MustString("app.vector_config_dir"), 87 | extraTemplatesDir: ko.String("app.extra_templates_dir"), 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "embed" 6 | "fmt" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | 11 | "github.com/hashicorp/nomad/api" 12 | ) 13 | 14 | var ( 15 | //go:embed vector.toml.tmpl 16 | vectorTmpl embed.FS 17 | // Version of the build. This is injected at build-time. 18 | buildString = "unknown" 19 | ) 20 | 21 | func main() { 22 | // Create a new context which gets cancelled upon receiving `SIGINT`/`SIGTERM`. 23 | ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) 24 | 25 | // Initialise and load the config. 26 | ko, err := initConfig("config.sample.toml", "NOMAD_VECTOR_LOGGER") 27 | if err != nil { 28 | fmt.Println("error initialising config", err) 29 | os.Exit(1) 30 | } 31 | 32 | // Initialise a new instance of app. 33 | app := App{ 34 | log: initLogger(ko), 35 | opts: initOpts(ko), 36 | configUpdated: make(chan bool, 1000), 37 | allocs: make(map[string]*api.Allocation, 0), 38 | expiredAllocs: make([]string, 0), 39 | } 40 | 41 | // Initialise nomad events stream. 42 | client, err := initNomadClient() 43 | if err != nil { 44 | app.log.Fatal("error initialising client", "err", err) 45 | } 46 | app.nomadClient = client 47 | 48 | // Set the node id in app. 49 | self, err := app.nomadClient.Agent().Self() 50 | if err != nil { 51 | app.log.Fatal("error fetching self endpoint", "err", err) 52 | } 53 | app.nodeID = self.Stats["client"]["node_id"] 54 | app.log.Info("setting node id in the app", "node", app.nodeID) 55 | 56 | // Start an instance of app. 57 | app.log.Info("booting nomad-vector-logger", "version", buildString) 58 | app.Start(ctx) 59 | } 60 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "stop old deployments" 6 | nomad job stop -purge vector || true 7 | nomad system gc 8 | 9 | echo "build binary" 10 | make build 11 | 12 | echo "copying bin to PATH" 13 | sudo cp ./bin/nomad-vector-logger.bin /usr/local/bin/ 14 | 15 | echo "deploying on nomad" 16 | nomad run dev/deployment.nomad 17 | -------------------------------------------------------------------------------- /sample/nomad.toml: -------------------------------------------------------------------------------- 1 | [sources.source_nomad_alloc_64a2f9fd-e003-0bb3-b5cd-838125283a06_proxy] 2 | type = "file" 3 | include = [ "/opt/nomad/data/alloc/64a2f9fd-e003-0bb3-b5cd-838125283a06/alloc/logs/proxy*" ] 4 | line_delimiter = "\n" 5 | read_from = "beginning" 6 | 7 | [transforms.transform_nomad_alloc_64a2f9fd-e003-0bb3-b5cd-838125283a06_proxy] 8 | type = "remap" 9 | inputs = ["source_nomad_alloc_64a2f9fd-e003-0bb3-b5cd-838125283a06_proxy"] 10 | source = ''' 11 | # Store Nomad metadata. 12 | .nomad.namespace = "default" 13 | .nomad.node_name = "pop-os" 14 | .nomad.job_name = "nginx" 15 | .nomad.group_name = "nginx" 16 | .nomad.task_name = "proxy" 17 | .nomad.alloc_id = "64a2f9fd-e003-0bb3-b5cd-838125283a06" 18 | ''' 19 | -------------------------------------------------------------------------------- /static/sink.toml: -------------------------------------------------------------------------------- 1 | [sinks.stdout_nomad] 2 | type = "console" 3 | inputs = ["transform_nomad_alloc*"] 4 | target = "stdout" 5 | 6 | [sinks.stdout_nomad.encoding] 7 | codec = "json" 8 | -------------------------------------------------------------------------------- /vector.toml.tmpl: -------------------------------------------------------------------------------- 1 | {{- range $value := . -}} 2 | [sources.source_{{$value.Key}}] 3 | type = "file" 4 | include = [ "{{$value.LogDir}}" ] 5 | line_delimiter = "\n" 6 | read_from = "beginning" 7 | [transforms.transform_{{$value.Key}}] 8 | type = "remap" 9 | inputs = ["source_{{$value.Key}}"] 10 | source = ''' 11 | # Store Nomad metadata. 12 | .nomad.namespace = "{{$value.Namespace}}" 13 | .nomad.node_name = "{{$value.Node}}" 14 | .nomad.job_name = "{{$value.Job}}" 15 | .nomad.group_name = "{{$value.Group}}" 16 | .nomad.task_name = "{{$value.Task}}" 17 | .nomad.alloc_id = "{{$value.ID}}" 18 | ''' 19 | {{ end}} --------------------------------------------------------------------------------