├── .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}}
--------------------------------------------------------------------------------