├── .gitignore ├── GNUmakefile ├── main.go ├── .github └── workflows │ └── go.yml ├── state.go ├── examples └── binserve-aarch64.nomad ├── structs.go ├── config.go ├── handle.go ├── README.md ├── go.mod ├── driver_test.go ├── LICENSE ├── driver.go └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | nomad-driver-systemd 2 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | PLUGIN_BINARY=nomad-driver-systemd 2 | export GO111MODULE=on 3 | 4 | default: build 5 | 6 | .PHONY: clean 7 | clean: ## Remove build artifacts 8 | rm -rf ${PLUGIN_BINARY} 9 | 10 | build: 11 | go build -o ${PLUGIN_BINARY} . 12 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/hashicorp/go-hclog" 5 | "github.com/hashicorp/nomad/plugins" 6 | ) 7 | 8 | func main() { 9 | // Serve the plugin 10 | plugins.Serve(factory) 11 | } 12 | 13 | // factory returns a new instance of a nomad driver plugin 14 | func factory(log hclog.Logger) interface{} { 15 | return NewSystemdDriver(log) 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v4 21 | with: 22 | go-version: '1.22' 23 | 24 | - name: Build 25 | run: go build -v ./... 26 | 27 | - name: Test 28 | run: sudo -E env "PATH=$PATH" CI=1 go test -v ./... 29 | -------------------------------------------------------------------------------- /state.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // taskStore provides a mechanism to store and retrieve 8 | // task handles given a string identifier. The ID should 9 | // be unique per task 10 | type taskStore struct { 11 | store map[string]*taskHandle 12 | lock sync.RWMutex 13 | } 14 | 15 | func newTaskStore() *taskStore { 16 | return &taskStore{store: map[string]*taskHandle{}} 17 | } 18 | 19 | func (ts *taskStore) Set(id string, handle *taskHandle) { 20 | ts.lock.Lock() 21 | defer ts.lock.Unlock() 22 | ts.store[id] = handle 23 | } 24 | 25 | func (ts *taskStore) Get(id string) (*taskHandle, bool) { 26 | ts.lock.RLock() 27 | defer ts.lock.RUnlock() 28 | t, ok := ts.store[id] 29 | return t, ok 30 | } 31 | 32 | func (ts *taskStore) Delete(id string) { 33 | ts.lock.Lock() 34 | defer ts.lock.Unlock() 35 | delete(ts.store, id) 36 | } 37 | -------------------------------------------------------------------------------- /examples/binserve-aarch64.nomad: -------------------------------------------------------------------------------- 1 | job "binserv-aarch64" { 2 | 3 | datacenters = ["dc1"] 4 | type = "service" 5 | 6 | group "binserv" { 7 | 8 | task "binserv" { 9 | 10 | driver = "systemd" 11 | kill_timeout = "5s" 12 | 13 | artifact { 14 | source = "https://github.com/mufeedvh/binserve/releases/download/v0.2.0/binserve-v0.2.0-aarch64-unknown-linux-gnu.tar.gz" 15 | options { 16 | checksum = "sha256:fd5453bf734add72e0ae62a64e207b0c4e69060669d170240b4f6cfadc0e6e7e" 17 | } 18 | } 19 | 20 | config { 21 | command = "binserve-v0.2.0-aarch64-unknown-linux-gnu/binserve" 22 | args = [ 23 | "--host", 24 | "0.0.0.0:1337", 25 | ] 26 | } 27 | 28 | resources { 29 | cpu = 200 30 | memory = 20 31 | } 32 | 33 | } 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /structs.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/coreos/go-systemd/v22/dbus" 5 | ds "github.com/godbus/dbus/v5" 6 | ) 7 | 8 | type execStopPost struct { 9 | Path string // the binary path to execute 10 | Args []string // an array with all arguments to pass to the executed command, starting with argument 0 11 | UncleanIsFailure bool // a boolean whether it should be considered a failure if the process exits uncleanly 12 | } 13 | 14 | type envEntry struct { 15 | Key string 16 | Value string 17 | } 18 | 19 | func PropExecStopPost(command []string) dbus.Property { 20 | return dbus.Property{ 21 | Name: "ExecStopPost", 22 | Value: ds.MakeVariant([]execStopPost{ 23 | { 24 | Path: command[0], 25 | Args: command, 26 | UncleanIsFailure: true, 27 | }, 28 | }), 29 | } 30 | } 31 | 32 | func PropEnvironment(env map[string]string) dbus.Property { 33 | entries := []string{} 34 | for k, v := range env { 35 | // FIXME proper escaping 36 | entries = append(entries, k+"="+v) 37 | } 38 | return dbus.Property{ 39 | Name: "Environment", 40 | Value: ds.MakeVariant(entries), 41 | } 42 | } 43 | 44 | func PropString(name string, value string) dbus.Property { 45 | return dbus.Property{ 46 | Name: name, 47 | Value: ds.MakeVariant(value), 48 | } 49 | } 50 | 51 | func PropBool(name string, value bool) dbus.Property { 52 | return dbus.Property{ 53 | Name: name, 54 | Value: ds.MakeVariant(value), 55 | } 56 | } 57 | 58 | func PropUInt64(name string, value uint64) dbus.Property { 59 | return dbus.Property{ 60 | Name: name, 61 | Value: ds.MakeVariant(value), 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/hashicorp/nomad/plugins/base" 5 | "github.com/hashicorp/nomad/plugins/drivers" 6 | "github.com/hashicorp/nomad/plugins/shared/hclspec" 7 | ) 8 | 9 | const ( 10 | LOG_DRIVER_NOMAD = "nomad" 11 | LOG_DRIVER_JOURNALD = "journald" 12 | ) 13 | 14 | var ( 15 | // pluginInfo describes the plugin 16 | pluginInfo = &base.PluginInfoResponse{ 17 | Type: base.PluginTypeDriver, 18 | PluginApiVersions: []string{drivers.ApiVersion010}, 19 | PluginVersion: pluginVersion, 20 | Name: pluginName, 21 | } 22 | 23 | // configSpec is the specification of the plugin's configuration 24 | // this is used to validate the configuration specified for the plugin 25 | // on the client. 26 | // this is not global, but can be specified on a per-client basis. 27 | configSpec = hclspec.NewObject(map[string]*hclspec.Spec{ 28 | // TODO: define plugin's agent configuration schema. 29 | // 30 | // The schema should be defined using HCL specs and it will be used to 31 | // validate the agent configuration provided by the user in the 32 | // `plugin` stanza (https://www.nomadproject.io/docs/configuration/plugin.html). 33 | // 34 | // For example, for the schema below a valid configuration would be: 35 | // 36 | // plugin "hello-driver-plugin" { 37 | // config { 38 | // shell = "fish" 39 | // } 40 | // } 41 | // "shell": hclspec.NewDefault( 42 | // hclspec.NewAttr("shell", "string", false), 43 | // hclspec.NewLiteral(`"bash"`), 44 | // ), 45 | }) 46 | 47 | // taskConfigSpec is the specification of the plugin's configuration for 48 | // a task 49 | // this is used to validated the configuration specified for the plugin 50 | // when a job is submitted. 51 | taskConfigSpec = hclspec.NewObject(map[string]*hclspec.Spec{ 52 | // TODO: define plugin's task configuration schema 53 | // 54 | // The schema should be defined using HCL specs and it will be used to 55 | // validate the task configuration provided by the user when they 56 | // submit a job. 57 | // 58 | // For example, for the schema below a valid task would be: 59 | // job "example" { 60 | // group "example" { 61 | // task "say-hi" { 62 | // driver = "hello-driver-plugin" 63 | // config { 64 | // greeting = "Hi" 65 | // } 66 | // } 67 | // } 68 | // } 69 | "command": hclspec.NewAttr("command", "string", false), 70 | "args": hclspec.NewAttr("args", "list(string)", false), 71 | "logging": hclspec.NewBlock("logging", false, hclspec.NewObject(map[string]*hclspec.Spec{ 72 | "driver": hclspec.NewAttr("driver", "string", false), 73 | //"extraFields": hclspec.NewAttr("options", "map(string)", false), 74 | })), 75 | }) 76 | 77 | // capabilities indicates what optional features this driver supports 78 | // this should be set according to the target run time. 79 | capabilities = &drivers.Capabilities{ 80 | // TODO: set plugin's capabilities 81 | // 82 | // The plugin's capabilities signal Nomad which extra functionalities 83 | // are supported. For a list of available options check the docs page: 84 | // https://godoc.org/github.com/hashicorp/nomad/plugins/drivers#Capabilities 85 | SendSignals: true, 86 | Exec: false, 87 | } 88 | ) 89 | 90 | // PluginConfig contains configuration information for the plugin 91 | type PluginConfig struct { 92 | // TODO: create decoded plugin configuration struct 93 | // 94 | // This struct is the decoded version of the schema defined in the 95 | // configSpec variable above. It's used to convert the HCL configuration 96 | // passed by the Nomad agent into Go contructs. 97 | // Shell string `codec:"shell"` 98 | } 99 | 100 | // TaskConfig contains configuration information for a task that runs with 101 | // this plugin 102 | type TaskConfig struct { 103 | Command string `codec:"command"` 104 | Args []string `codec:"args"` 105 | Logging LoggingConfig `codec:"logging"` 106 | } 107 | 108 | // LoggingConfig is the tasks logging configuration 109 | type LoggingConfig struct { 110 | Driver string `codec:"driver"` 111 | //ExtraFields hclutils.MapStrStr `codec:"extraFields"` 112 | } 113 | -------------------------------------------------------------------------------- /handle.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "time" 7 | 8 | "github.com/hashicorp/go-hclog" 9 | "github.com/hashicorp/nomad/client/lib/cpustats" 10 | "github.com/hashicorp/nomad/plugins/drivers" 11 | ) 12 | 13 | var ( 14 | measuredCPUStats = []string{"System Mode", "User Mode", "Percent"} 15 | measuredMemStats = []string{"Usage", "Max Usage"} 16 | ) 17 | 18 | // taskHandle should store all relevant runtime information 19 | // such as process ID if this is a local task or other meta 20 | // data if this driver deals with external APIs 21 | type taskHandle struct { 22 | // stateLock syncs access to all fields below 23 | stateLock sync.RWMutex 24 | driver *Driver 25 | 26 | logger hclog.Logger 27 | taskConfig *drivers.TaskConfig 28 | procState drivers.TaskState 29 | startedAt time.Time 30 | completedAt time.Time 31 | exitResult *drivers.ExitResult 32 | collectionInterval time.Duration 33 | 34 | // systemd unit name 35 | unitName string 36 | 37 | // task properties (periodically polled) 38 | properties map[string]interface{} 39 | 40 | totalCPUStats *cpustats.Tracker 41 | } 42 | 43 | func (h *taskHandle) TaskStatus() *drivers.TaskStatus { 44 | h.stateLock.RLock() 45 | defer h.stateLock.RUnlock() 46 | 47 | return &drivers.TaskStatus{ 48 | ID: h.taskConfig.ID, 49 | Name: h.taskConfig.Name, 50 | State: h.procState, 51 | StartedAt: h.startedAt, 52 | CompletedAt: h.completedAt, 53 | ExitResult: h.exitResult, 54 | DriverAttributes: map[string]string{ 55 | //"pid": strconv.Itoa(h.pid), 56 | }, 57 | } 58 | } 59 | 60 | func (h *taskHandle) IsRunning() bool { 61 | h.stateLock.RLock() 62 | defer h.stateLock.RUnlock() 63 | return h.procState == drivers.TaskStateRunning 64 | } 65 | 66 | func (h *taskHandle) runUnitMonitor() { 67 | h.logger.Debug("Monitoring Unit", "unit", h.unitName) 68 | timer := time.NewTimer(0) 69 | defer func() { 70 | h.logger.Debug("No longer monitoring Unit", "unit", h.unitName) 71 | }() 72 | 73 | for { 74 | select { 75 | case <-h.driver.ctx.Done(): 76 | return 77 | 78 | case <-timer.C: 79 | timer.Reset(h.collectionInterval) 80 | } 81 | 82 | gone := false 83 | conn, err := h.driver.connection() 84 | if err != nil { 85 | h.logger.Warn("Unable to get unit properties, no dbus connection", "unit", h.unitName, "err", err) 86 | gone = true 87 | } else { 88 | props, err := conn.GetAllPropertiesContext(h.driver.ctx, h.unitName) 89 | if err == nil { 90 | // cache latest known property set in handle. It's used by the stats collector 91 | h.stateLock.Lock() 92 | h.properties = props 93 | h.stateLock.Unlock() 94 | state := props["ActiveState"] 95 | // is the unit still running? 96 | if state == "inactive" || state == "failed" || props["SubState"] == "exited" { 97 | gone = true 98 | if props["Result"] == "oom-kill" { 99 | // propagate OOM kills 100 | h.exitResult.OOMKilled = true 101 | } 102 | h.exitResult.ExitCode = int(props["ExecMainStatus"].(int32)) 103 | // cleanup 104 | _, err = conn.StopUnitContext(h.driver.ctx, h.unitName, "replace", nil) 105 | if err != nil { 106 | // Might not be a problem. This can be the case when we run a regular StopTask operation 107 | h.logger.Debug("Unable to stop/remove completed unit", "unit", h.unitName, "err", err) 108 | } else { 109 | h.logger.Debug("Unit removed", "unit", h.unitName) 110 | 111 | } 112 | } 113 | } else { 114 | h.logger.Warn("Unable to get unit properties, may be gone", "unit", h.unitName, "err", err) 115 | gone = true 116 | } 117 | } 118 | 119 | if gone { 120 | h.logger.Info("Unit is not running anymore", "unit", h.unitName) 121 | // container was stopped, get exit code and other post mortem infos 122 | h.stateLock.Lock() 123 | h.completedAt = time.Now() 124 | 125 | h.procState = drivers.TaskStateExited 126 | h.stateLock.Unlock() 127 | return 128 | } 129 | 130 | } 131 | } 132 | 133 | func (h *taskHandle) isRunning() bool { 134 | h.stateLock.RLock() 135 | defer h.stateLock.RUnlock() 136 | return h.procState == drivers.TaskStateRunning 137 | } 138 | 139 | func (h *taskHandle) runExitWatcher(ctx context.Context, exitChannel chan *drivers.ExitResult) { 140 | timer := time.NewTimer(0) 141 | h.logger.Debug("Starting exitWatcher", "unit", h.unitName) 142 | 143 | defer func() { 144 | h.logger.Debug("Stopping exitWatcher", "unit", h.unitName) 145 | // be sure to get the whole result 146 | h.stateLock.Lock() 147 | result := h.exitResult 148 | h.stateLock.Unlock() 149 | exitChannel <- result 150 | close(exitChannel) 151 | }() 152 | 153 | for { 154 | if !h.isRunning() { 155 | return 156 | } 157 | select { 158 | case <-ctx.Done(): 159 | return 160 | case <-timer.C: 161 | timer.Reset(time.Second) 162 | } 163 | } 164 | } 165 | 166 | func (h *taskHandle) runStatsEmitter(ctx context.Context, statsChannel chan *drivers.TaskResourceUsage, interval time.Duration) { 167 | timer := time.NewTimer(0) 168 | h.logger.Debug("Starting statsEmitter", "unit", h.unitName) 169 | h.collectionInterval = interval 170 | for { 171 | select { 172 | case <-ctx.Done(): 173 | h.logger.Debug("Stopping statsEmitter", "unit", h.unitName) 174 | return 175 | case <-timer.C: 176 | timer.Reset(interval) 177 | } 178 | 179 | h.stateLock.Lock() 180 | t := time.Now() 181 | 182 | cpuNanosProp := h.properties["CPUUsageNSec"] 183 | memoryProp := h.properties["MemoryCurrent"] 184 | if cpuNanosProp == nil || memoryProp == nil { 185 | h.stateLock.Unlock() 186 | continue 187 | } 188 | 189 | cpuNanos := h.properties["CPUUsageNSec"].(uint64) 190 | memory := h.properties["MemoryCurrent"].(uint64) 191 | 192 | //FIXME implement cpu stats correctly 193 | totalPercent := h.totalCPUStats.Percent(float64(cpuNanos)) 194 | cs := &drivers.CpuStats{ 195 | SystemMode: 0, //h.systemCPUStats.Percent(float64(h.containerStats.CPUStats.CPUUsage.UsageInKernelmode)), 196 | UserMode: totalPercent, 197 | Percent: totalPercent, 198 | TotalTicks: h.totalCPUStats.TicksConsumed(totalPercent), 199 | Measured: measuredCPUStats, 200 | } 201 | 202 | ms := &drivers.MemoryStats{ 203 | MaxUsage: memory, 204 | Usage: memory, 205 | RSS: memory, 206 | Measured: measuredMemStats, 207 | } 208 | h.stateLock.Unlock() 209 | 210 | // update uasge 211 | usage := drivers.TaskResourceUsage{ 212 | ResourceUsage: &drivers.ResourceUsage{ 213 | CpuStats: cs, 214 | MemoryStats: ms, 215 | }, 216 | Timestamp: t.UTC().UnixNano(), 217 | } 218 | // send stats to nomad 219 | statsChannel <- &usage 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Nomad systemd Driver 2 | ===================== 3 | 4 | This is a nomad task driver which leverages systemd transient units as isolation 5 | primitive. 6 | 7 | ### Why? 8 | 9 | TL;DR : Curiosity 10 | 11 | This project tries to combine a few ideas: 12 | 13 | - orchestrate, configure and deploy service definitions via nomad 14 | - use nomad features to download your binary or image (artifact stanza etc.) 15 | - run the actual binary via the prevalent systemd service manager 16 | - do not delegate service restarts, lifecycle to systemd, use nomad 17 | 18 | But we have already [nomad exec and raw-exec](https://www.nomadproject.io/docs/drivers/exec) drivers! 19 | Yes, they are at first sight somewhat similar but not very feature rich and/or insecure. 20 | 21 | Other people played also with nomad and systemd: 22 | - [arianvpn/monad-driver-systemd](https://github.com/arianvp/nomad-driver-systemd) 23 | - [nspawn driver](https://www.nomadproject.io/plugins/drivers/community/nspawn) 24 | 25 | #### Systemd usage 26 | 27 | Having systemd between nomand and the actual service binary brings certain benefits: 28 | 29 | - stability through delegation, the nomad agent is not responsible for controlling the actual command 30 | - this plugin talks to systemd via dbus. That means that we have the possibility to run 31 | nomad as unprivileged user or even in a container if we configure polkit correctly. 32 | - leverage systemd features 33 | 34 | About transient units: a lesser known systemd feature 35 | (see [systemd-run](https://www.freedesktop.org/software/systemd/man/systemd-run.html)) allows you to 36 | run a unit ad-hoc without declaring it in a file and without performing a daemon-reload etc. 37 | Systemd will forget about this unit once it completes. 38 | 39 | The plugin builds upon this mechanism and manages systemd units via the dbus api. 40 | It's easily possible to mirror almost all systemd-exec unit options into the task driver. 41 | 42 | One exception is important: service failures and restarts must not be done by systemd because 43 | it would heavily conflit with nomads scheduler. 44 | 45 | #### Containerless 46 | 47 | Containers are nice. But they do not come for free. 48 | 49 | Sometimes we want to run a simple (go, rust,...) binary on our worker nodes. 50 | Wrapping such a binary into a container image is of course possible. 51 | 52 | But do we really have to do it? 53 | 54 | We had services long before we had containers. A service was usually installed by a package manager and 55 | started/supervised by the hosts init system (sysV, supervisor, systemd, ...). 56 | 57 | Systemd added many cgroup based security features to services which most people would 58 | associate with a container runtime: 59 | 60 | - resource limits and accounting (memory, cpu, io, ...) 61 | - filesystem isolation 62 | - ephemeral uids 63 | - even network isolation 64 | 65 | What about images? 66 | 67 | Images are nice. It's the defacto standard to bundle and distribute all dependencies for a service. 68 | But as sayed above: sometimes your service is just a binary and it's configuration file. 69 | 70 | In this case a mere fileserver (think S3 bucket...) can replace a image repository. 71 | 72 | #### maybe: A thin wasm driver 73 | 74 | Some people move towards web assembly (WASM) on the server. 75 | They need a way to distribute and start a WASM binary without much overhead. 76 | 77 | It's certainly possible to evolve this plugin also into this direction. 78 | 79 | We could store .wasm files on https servers, use [nomads artifact](https://www.nomadproject.io/docs/job-specification/artifact) feature to download them directly into 80 | the allocation directory and finally launch a service. 81 | 82 | ## Features 83 | 84 | It's a rather simple POC for now. 85 | My first goal is to see if people find it useful. 86 | 87 | Only a few features are implemented: 88 | 89 | * [ ] Run in a systemd user session. *nomad must be root for now!* 90 | * [x] Specify a command and arguments 91 | * [x] Run that command under a different Uid 92 | * [ ] Run that command with a different Gid 93 | * [x] [Nomad runtime environment](https://www.nomadproject.io/docs/runtime/environment.html) is populated 94 | * [ ] Send signals via nomad 95 | * [ ] Nomad exec support 96 | * [x] Configure environment variables 97 | * [x] Collect basic CPU and Memory metrics 98 | * [x] Optionally forward logs to nomad instead of system Journal 99 | * [x] Propagate command exit code back into nomad 100 | * [x] Propagate command OOM kills back into nomad 101 | * [ ] Configure CPU, resource limits 102 | * [x] Configure Memory resource limits 103 | * [ ] Configure Swap resource limits 104 | * [ ] Configure IO resource limits 105 | * [ ] Configure systemd based filesystem restrictions, e.g. chroot 106 | * [ ] Make use of systemd image mounts 107 | * [ ] Bind mount host volumes etc 108 | * [ ] Make use of the many systemd security options 109 | * [ ] ... 110 | 111 | ## Requirements 112 | 113 | - [Go](https://golang.org/doc/install) v1.18 or later (to compile the plugin) 114 | - [Nomad](https://www.nomadproject.io/downloads.html) to run the plugin 115 | - A linux system or container controlled by systemd 116 | 117 | ## Building The Driver from source 118 | 119 | ```sh 120 | $ make build 121 | ``` 122 | 123 | ## Driver Configuration 124 | 125 | No configuration is possible yet. 126 | 127 | ## Task Configuration 128 | 129 | * **command** - (Manatory) The command to run in your task. 130 | 131 | The exact behavior depends on the fact if you configure a absolute (first char is a "/") or 132 | relative path. 133 | 134 | A command with a relative path will be prefixed with the local task directory. This makes 135 | it convenient to download and start a binary via and [artifact](https://www.nomadproject.io/docs/job-specification/artifact). 136 | stanza. 137 | 138 | ```hcl 139 | config { 140 | command = "some-command" 141 | } 142 | ``` 143 | 144 | * **args** - (Optional) A list of arguments to the command. 145 | 146 | ```hcl 147 | config { 148 | args = [ 149 | "arg1", 150 | "arg2", 151 | ] 152 | } 153 | ``` 154 | 155 | * **user** - Run the command as a specific user/uid. See [Task configuration](https://www.nomadproject.io/docs/job-specification/task.html#user) 156 | 157 | ```hcl 158 | user = nobody 159 | 160 | config { 161 | } 162 | 163 | ``` 164 | 165 | * **logging** - Configure logging. 166 | 167 | `driver = "nomad"` (default) Systemd redirects its stdout and stderr streams directly to Nomad. 168 | 169 | ```hcl 170 | config { 171 | logging = { 172 | driver = "nomad" 173 | } 174 | } 175 | ``` 176 | 177 | `driver = "journald"` Write logs into the system journal. Nomad will not receive any logs. 178 | 179 | ```hcl 180 | config { 181 | logging = { 182 | driver = "journald" 183 | } 184 | } 185 | ``` 186 | 187 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/towe75/nomad-driver-systemd 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/coreos/go-systemd/v22 v22.5.0 7 | github.com/godbus/dbus/v5 v5.1.0 8 | github.com/hashicorp/go-hclog v1.6.2 9 | github.com/hashicorp/nomad v1.7.3 10 | github.com/stretchr/testify v1.8.4 11 | ) 12 | 13 | require ( 14 | github.com/LK4D4/joincontext v0.0.0-20171026170139-1724345da6d5 // indirect 15 | github.com/Masterminds/goutils v1.1.1 // indirect 16 | github.com/Masterminds/semver/v3 v3.2.0 // indirect 17 | github.com/Masterminds/sprig/v3 v3.2.3 // indirect 18 | github.com/Microsoft/go-winio v0.6.0 // indirect 19 | github.com/agext/levenshtein v1.2.1 // indirect 20 | github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect 21 | github.com/armon/go-metrics v0.4.1 // indirect 22 | github.com/armon/go-radix v1.0.0 // indirect 23 | github.com/bgentry/speakeasy v0.1.0 // indirect 24 | github.com/brianvoe/gofakeit/v6 v6.20.1 // indirect 25 | github.com/cenkalti/backoff/v3 v3.2.2 // indirect 26 | github.com/container-storage-interface/spec v1.7.0 // indirect 27 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 28 | github.com/docker/docker v23.0.8+incompatible // indirect 29 | github.com/dustin/go-humanize v1.0.1 // indirect 30 | github.com/fatih/color v1.15.0 // indirect 31 | github.com/fsnotify/fsnotify v1.4.9 // indirect 32 | github.com/go-jose/go-jose/v3 v3.0.1 // indirect 33 | github.com/go-ole/go-ole v1.2.6 // indirect 34 | github.com/gojuno/minimock/v3 v3.0.6 // indirect 35 | github.com/golang-jwt/jwt/v5 v5.0.0 // indirect 36 | github.com/golang/protobuf v1.5.3 // indirect 37 | github.com/google/btree v1.0.1 // indirect 38 | github.com/google/go-cmp v0.6.0 // indirect 39 | github.com/google/uuid v1.3.1 // indirect 40 | github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect 41 | github.com/hashicorp/consul/api v1.26.1 // indirect 42 | github.com/hashicorp/cronexpr v1.1.2 // indirect 43 | github.com/hashicorp/errwrap v1.1.0 // indirect 44 | github.com/hashicorp/go-bexpr v0.1.13 // indirect 45 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 46 | github.com/hashicorp/go-immutable-radix v1.3.1 // indirect 47 | github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect 48 | github.com/hashicorp/go-msgpack v1.1.5 // indirect 49 | github.com/hashicorp/go-multierror v1.1.1 // indirect 50 | github.com/hashicorp/go-plugin v1.6.0 // indirect 51 | github.com/hashicorp/go-retryablehttp v0.7.2 // indirect 52 | github.com/hashicorp/go-rootcerts v1.0.2 // indirect 53 | github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.4 // indirect 54 | github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 // indirect 55 | github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 // indirect 56 | github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect 57 | github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.2 // indirect 58 | github.com/hashicorp/go-set/v2 v2.1.0 // indirect 59 | github.com/hashicorp/go-sockaddr v1.0.2 // indirect 60 | github.com/hashicorp/go-uuid v1.0.3 // indirect 61 | github.com/hashicorp/go-version v1.6.0 // indirect 62 | github.com/hashicorp/golang-lru v0.5.4 // indirect 63 | github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect 64 | github.com/hashicorp/hcl v1.0.1-vault-3 // indirect 65 | github.com/hashicorp/hcl/v2 v2.9.2-0.20220525143345-ab3cae0737bc // indirect 66 | github.com/hashicorp/memberlist v0.5.0 // indirect 67 | github.com/hashicorp/raft v1.5.0 // indirect 68 | github.com/hashicorp/raft-autopilot v0.1.6 // indirect 69 | github.com/hashicorp/serf v0.10.1 // indirect 70 | github.com/hashicorp/vault/api v1.10.0 // indirect 71 | github.com/hashicorp/yamux v0.1.1 // indirect 72 | github.com/hpcloud/tail v1.0.1-0.20170814160653-37f427138745 // indirect 73 | github.com/huandu/xstrings v1.4.0 // indirect 74 | github.com/imdario/mergo v0.3.13 // indirect 75 | github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f // indirect 76 | github.com/kr/pretty v0.3.1 // indirect 77 | github.com/kr/text v0.2.0 // indirect 78 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect 79 | github.com/mattn/go-colorable v0.1.13 // indirect 80 | github.com/mattn/go-isatty v0.0.17 // indirect 81 | github.com/miekg/dns v1.1.50 // indirect 82 | github.com/mitchellh/cli v1.1.5 // indirect 83 | github.com/mitchellh/copystructure v1.2.0 // indirect 84 | github.com/mitchellh/go-homedir v1.1.0 // indirect 85 | github.com/mitchellh/go-testing-interface v1.14.2-0.20210821155943-2d9075ca8770 // indirect 86 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 87 | github.com/mitchellh/hashstructure v1.1.0 // indirect 88 | github.com/mitchellh/mapstructure v1.5.0 // indirect 89 | github.com/mitchellh/pointerstructure v1.2.1 // indirect 90 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 91 | github.com/moby/sys/mount v0.3.3 // indirect 92 | github.com/moby/sys/mountinfo v0.6.2 // indirect 93 | github.com/oklog/run v1.1.0 // indirect 94 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 95 | github.com/posener/complete v1.2.3 // indirect 96 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect 97 | github.com/rogpeppe/go-internal v1.10.0 // indirect 98 | github.com/ryanuber/go-glob v1.0.0 // indirect 99 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect 100 | github.com/shirou/gopsutil/v3 v3.23.9 // indirect 101 | github.com/shoenig/go-m1cpu v0.1.6 // indirect 102 | github.com/shoenig/test v1.7.0 // indirect 103 | github.com/shopspring/decimal v1.3.1 // indirect 104 | github.com/sirupsen/logrus v1.9.3 // indirect 105 | github.com/spf13/cast v1.5.0 // indirect 106 | github.com/tklauser/go-sysconf v0.3.12 // indirect 107 | github.com/tklauser/numcpus v0.6.1 // indirect 108 | github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect 109 | github.com/vmihailenco/tagparser v0.1.1 // indirect 110 | github.com/yusufpapurcu/wmi v1.2.3 // indirect 111 | github.com/zclconf/go-cty v1.12.1 // indirect 112 | golang.org/x/crypto v0.17.0 // indirect 113 | golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect 114 | golang.org/x/mod v0.13.0 // indirect 115 | golang.org/x/net v0.17.0 // indirect 116 | golang.org/x/sync v0.4.0 // indirect 117 | golang.org/x/sys v0.15.0 // indirect 118 | golang.org/x/text v0.14.0 // indirect 119 | golang.org/x/time v0.3.0 // indirect 120 | golang.org/x/tools v0.14.0 // indirect 121 | google.golang.org/appengine v1.6.7 // indirect 122 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect 123 | google.golang.org/grpc v1.59.0 // indirect 124 | google.golang.org/protobuf v1.31.0 // indirect 125 | gopkg.in/fsnotify.v1 v1.4.7 // indirect 126 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 127 | gopkg.in/yaml.v3 v3.0.1 // indirect 128 | oss.indeed.com/go/libtime v1.6.0 // indirect 129 | ) 130 | -------------------------------------------------------------------------------- /driver_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io/ioutil" 7 | "path/filepath" 8 | "reflect" 9 | "testing" 10 | "time" 11 | 12 | "github.com/hashicorp/go-hclog" 13 | "github.com/hashicorp/nomad/client/taskenv" 14 | "github.com/hashicorp/nomad/helper/testlog" 15 | "github.com/hashicorp/nomad/helper/uuid" 16 | "github.com/hashicorp/nomad/nomad/structs" 17 | "github.com/hashicorp/nomad/plugins/base" 18 | "github.com/hashicorp/nomad/plugins/drivers" 19 | dtestutil "github.com/hashicorp/nomad/plugins/drivers/testutils" 20 | tu "github.com/hashicorp/nomad/testutil" 21 | "github.com/stretchr/testify/require" 22 | ) 23 | 24 | var ( 25 | longRunningCommand = []string{"/usr/bin/sleep", "1000d"} 26 | ) 27 | 28 | // systemdDriverHarness wires up everything needed to launch a task with a systed driver. 29 | // A driver plugin interface and cleanup function is returned 30 | func systemdDriverHarness(t *testing.T, cfg map[string]interface{}) *dtestutil.DriverHarness { 31 | 32 | logger := testlog.HCLogger(t) 33 | if testing.Verbose() { 34 | logger.SetLevel(hclog.Trace) 35 | } else { 36 | logger.SetLevel(hclog.Info) 37 | } 38 | 39 | baseConfig := base.Config{} 40 | pluginConfig := PluginConfig{} 41 | if err := base.MsgPackEncode(&baseConfig.PluginConfig, &pluginConfig); err != nil { 42 | t.Error("Unable to encode plugin config", err) 43 | } 44 | 45 | d := NewSystemdDriver(logger).(*Driver) 46 | d.SetConfig(&baseConfig) 47 | d.buildFingerprint() 48 | // d.config.Volumes.Enabled = true 49 | // if enforce, err := ioutil.ReadFile("/sys/fs/selinux/enforce"); err == nil { 50 | // if string(enforce) == "1" { 51 | // d.logger.Info("Enabling SelinuxLabel") 52 | // d.config.Volumes.SelinuxLabel = "z" 53 | // } 54 | // } 55 | // d.config.GC.Container = true 56 | // if v, ok := cfg["GC.Container"]; ok { 57 | // if bv, ok := v.(bool); ok { 58 | // d.config.GC.Container = bv 59 | // } 60 | // } 61 | 62 | harness := dtestutil.NewDriverHarness(t, d) 63 | 64 | return harness 65 | } 66 | 67 | func createBasicResources() *drivers.Resources { 68 | res := drivers.Resources{ 69 | NomadResources: &structs.AllocatedTaskResources{ 70 | Memory: structs.AllocatedMemoryResources{ 71 | MemoryMB: 100, 72 | }, 73 | Cpu: structs.AllocatedCpuResources{ 74 | CpuShares: 250, 75 | }, 76 | }, 77 | LinuxResources: &drivers.LinuxResources{ 78 | CPUPeriod: 100000, 79 | CPUQuota: 100000, 80 | CPUShares: 500, 81 | MemoryLimitBytes: 256 * 1024 * 1024, 82 | PercentTicks: float64(500) / float64(2000), 83 | }, 84 | } 85 | return &res 86 | } 87 | 88 | func getSystemdDriver(t *testing.T, harness *dtestutil.DriverHarness) *Driver { 89 | driver, ok := harness.Impl().(*Driver) 90 | require.True(t, ok) 91 | return driver 92 | } 93 | 94 | func newTaskConfig(command []string) TaskConfig { 95 | 96 | return TaskConfig{ 97 | Command: command[0], 98 | Args: command[1:], 99 | } 100 | } 101 | 102 | // read a tasks stdout logfile into a string, fail on error 103 | func readStdoutLog(t *testing.T, task *drivers.TaskConfig) string { 104 | logfile := filepath.Join(filepath.Dir(task.StdoutPath), fmt.Sprintf("%s.stdout.0", task.Name)) 105 | stdout, err := ioutil.ReadFile(logfile) 106 | require.NoError(t, err) 107 | return string(stdout) 108 | } 109 | 110 | // read a tasks stderr logfile into a string, fail on error 111 | func readStderrLog(t *testing.T, task *drivers.TaskConfig) string { 112 | logfile := filepath.Join(filepath.Dir(task.StderrPath), fmt.Sprintf("%s.stderr.0", task.Name)) 113 | stderr, err := ioutil.ReadFile(logfile) 114 | require.NoError(t, err) 115 | return string(stderr) 116 | } 117 | 118 | // can we at least setup the harness? 119 | func TestSystemdDriver_Setup(t *testing.T) { 120 | systemdDriverHarness(t, nil) 121 | } 122 | 123 | // Ensure that we require a command 124 | func TestSystemdDriver_Start_NoCommand(t *testing.T) { 125 | if !tu.IsCI() { 126 | t.Parallel() 127 | } 128 | 129 | taskCfg := TaskConfig{} 130 | task := &drivers.TaskConfig{ 131 | ID: uuid.Generate(), 132 | Name: "nocommand", 133 | AllocID: uuid.Generate(), 134 | Resources: createBasicResources(), 135 | } 136 | require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg)) 137 | 138 | d := systemdDriverHarness(t, nil) 139 | cleanup := d.MkAllocDir(task, false) 140 | defer cleanup() 141 | 142 | _, _, err := d.StartTask(task) 143 | require.Error(t, err) 144 | require.Contains(t, err.Error(), "command name required") 145 | 146 | d.DestroyTask(task.ID, true) 147 | 148 | } 149 | 150 | // start a long running command 151 | func TestSystemdDriver_LongRunning(t *testing.T) { 152 | if !tu.IsCI() { 153 | t.Parallel() 154 | } 155 | 156 | taskCfg := newTaskConfig(longRunningCommand) 157 | task := &drivers.TaskConfig{ 158 | ID: uuid.Generate(), 159 | Name: "longrunning", 160 | AllocID: uuid.Generate(), 161 | Resources: createBasicResources(), 162 | } 163 | require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg)) 164 | 165 | d := systemdDriverHarness(t, nil) 166 | cleanup := d.MkAllocDir(task, true) 167 | defer cleanup() 168 | 169 | _, _, err := d.StartTask(task) 170 | require.NoError(t, err) 171 | 172 | defer d.DestroyTask(task.ID, true) 173 | 174 | // Attempt to wait 175 | waitCh, err := d.WaitTask(context.Background(), task.ID) 176 | require.NoError(t, err) 177 | 178 | select { 179 | case <-waitCh: 180 | t.Fatalf("wait channel should not have received an exit result") 181 | case <-time.After(time.Duration(tu.TestMultiplier()*3) * time.Second): 182 | } 183 | } 184 | 185 | // test a short-living command 186 | func TestSystemdDriver_ShortLiving(t *testing.T) { 187 | if !tu.IsCI() { 188 | t.Parallel() 189 | } 190 | 191 | taskCfg := newTaskConfig([]string{"/usr/bin/ls"}) 192 | task := &drivers.TaskConfig{ 193 | ID: uuid.Generate(), 194 | Name: "shortliving", 195 | AllocID: uuid.Generate(), 196 | Resources: createBasicResources(), 197 | } 198 | require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg)) 199 | 200 | d := systemdDriverHarness(t, nil) 201 | cleanup := d.MkAllocDir(task, true) 202 | defer cleanup() 203 | 204 | _, _, err := d.StartTask(task) 205 | require.NoError(t, err) 206 | 207 | defer d.DestroyTask(task.ID, true) 208 | 209 | // Attempt to wait 210 | waitCh, err := d.WaitTask(context.Background(), task.ID) 211 | require.NoError(t, err) 212 | 213 | select { 214 | case res := <-waitCh: 215 | if !res.Successful() { 216 | require.Fail(t, "ExitResult should be successful: %v", res) 217 | } 218 | case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 219 | require.Fail(t, "timeout") 220 | } 221 | } 222 | 223 | func TestSystemdDriver_AllocDir(t *testing.T) { 224 | if !tu.IsCI() { 225 | t.Parallel() 226 | } 227 | 228 | exp := []byte{'w', 'i', 'n'} 229 | file := "output.txt" 230 | 231 | taskCfg := newTaskConfig([]string{ 232 | "/bin/sh", 233 | "-c", 234 | fmt.Sprintf(`echo -n %s > $%s/%s`, 235 | string(exp), taskenv.AllocDir, file), 236 | }) 237 | task := &drivers.TaskConfig{ 238 | ID: uuid.Generate(), 239 | Name: "allocDir", 240 | AllocID: uuid.Generate(), 241 | Resources: createBasicResources(), 242 | } 243 | require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg)) 244 | 245 | d := systemdDriverHarness(t, nil) 246 | cleanup := d.MkAllocDir(task, true) 247 | defer cleanup() 248 | 249 | _, _, err := d.StartTask(task) 250 | require.NoError(t, err) 251 | 252 | defer d.DestroyTask(task.ID, true) 253 | 254 | // Attempt to wait 255 | waitCh, err := d.WaitTask(context.Background(), task.ID) 256 | require.NoError(t, err) 257 | 258 | select { 259 | case res := <-waitCh: 260 | if !res.Successful() { 261 | require.Fail(t, fmt.Sprintf("ExitResult should be successful: %v", res)) 262 | } 263 | case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 264 | require.Fail(t, "timeout") 265 | } 266 | 267 | // Check that data was written to the shared alloc directory. 268 | outputFile := filepath.Join(task.TaskDir().SharedAllocDir, file) 269 | act, err := ioutil.ReadFile(outputFile) 270 | if err != nil { 271 | t.Fatalf("Couldn't read expected output: %v", err) 272 | } 273 | 274 | if !reflect.DeepEqual(act, exp) { 275 | t.Fatalf("Command outputted %v; want %v", act, exp) 276 | } 277 | } 278 | 279 | // Check log_opt=nomad logger 280 | func TestSystemdDriver_LogNomad(t *testing.T) { 281 | if !tu.IsCI() { 282 | t.Parallel() 283 | } 284 | 285 | stdoutMagic := uuid.Generate() 286 | stderrMagic := uuid.Generate() 287 | 288 | taskCfg := newTaskConfig([]string{ 289 | "/bin/sh", 290 | "-c", 291 | fmt.Sprintf("echo %s; 1>&2 echo %s", stdoutMagic, stderrMagic), 292 | }) 293 | taskCfg.Logging.Driver = "nomad" 294 | task := &drivers.TaskConfig{ 295 | ID: uuid.Generate(), 296 | Name: "logNomad", 297 | AllocID: uuid.Generate(), 298 | Resources: createBasicResources(), 299 | } 300 | require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg)) 301 | 302 | d := systemdDriverHarness(t, nil) 303 | cleanup := d.MkAllocDir(task, true) 304 | defer cleanup() 305 | 306 | _, _, err := d.StartTask(task) 307 | require.NoError(t, err) 308 | 309 | defer d.DestroyTask(task.ID, true) 310 | 311 | // Attempt to wait 312 | waitCh, err := d.WaitTask(context.Background(), task.ID) 313 | require.NoError(t, err) 314 | 315 | select { 316 | case <-waitCh: 317 | case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 318 | t.Fatalf("Container did not exit in time") 319 | } 320 | 321 | stdoutLog := readStdoutLog(t, task) 322 | require.Contains(t, stdoutLog, stdoutMagic, "stdoutMagic in stdout") 323 | stderrLog := readStderrLog(t, task) 324 | require.Contains(t, stderrLog, stderrMagic, "stderrMagic in stderr") 325 | } 326 | 327 | // check setting a environment variable for the task 328 | func TestSystemdDriver_Env(t *testing.T) { 329 | if !tu.IsCI() { 330 | t.Parallel() 331 | } 332 | 333 | taskCfg := newTaskConfig([]string{ 334 | "/bin/sh", 335 | "-c", 336 | "echo $testvar", 337 | }) 338 | 339 | testvalue := uuid.Generate() 340 | 341 | task := &drivers.TaskConfig{ 342 | ID: uuid.Generate(), 343 | Name: "env", 344 | AllocID: uuid.Generate(), 345 | Resources: createBasicResources(), 346 | Env: map[string]string{"testvar": testvalue}, 347 | } 348 | require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg)) 349 | 350 | d := systemdDriverHarness(t, nil) 351 | cleanup := d.MkAllocDir(task, true) 352 | defer cleanup() 353 | 354 | _, _, err := d.StartTask(task) 355 | require.NoError(t, err) 356 | 357 | defer d.DestroyTask(task.ID, true) 358 | 359 | // Attempt to wait 360 | waitCh, err := d.WaitTask(context.Background(), task.ID) 361 | require.NoError(t, err) 362 | 363 | select { 364 | case res := <-waitCh: 365 | // should have a exitcode=0 result 366 | require.True(t, res.Successful()) 367 | case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 368 | t.Fatalf("Task did not exit in time") 369 | } 370 | 371 | // see if stdout was populated with the contents of testvalue 372 | tasklog := readStdoutLog(t, task) 373 | require.Contains(t, tasklog, testvalue) 374 | 375 | } 376 | 377 | // check setting a user for the task 378 | func TestSystemdDriver_User(t *testing.T) { 379 | if !tu.IsCI() { 380 | t.Parallel() 381 | } 382 | 383 | taskCfg := newTaskConfig([]string{ 384 | // print our username to stdout 385 | "/usr/bin/whoami", 386 | }) 387 | 388 | task := &drivers.TaskConfig{ 389 | ID: uuid.Generate(), 390 | Name: "user", 391 | AllocID: uuid.Generate(), 392 | Resources: createBasicResources(), 393 | } 394 | // use "www-data" as a user for our test, it's part of the busybox image 395 | task.User = "www-data" 396 | require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg)) 397 | 398 | d := systemdDriverHarness(t, nil) 399 | cleanup := d.MkAllocDir(task, true) 400 | defer cleanup() 401 | 402 | _, _, err := d.StartTask(task) 403 | require.NoError(t, err) 404 | 405 | defer d.DestroyTask(task.ID, true) 406 | 407 | // Attempt to wait 408 | waitCh, err := d.WaitTask(context.Background(), task.ID) 409 | require.NoError(t, err) 410 | 411 | select { 412 | case res := <-waitCh: 413 | // should have a exitcode=0 result 414 | require.True(t, res.Successful()) 415 | case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 416 | t.Fatalf("Task did not exit in time") 417 | } 418 | 419 | // see if stdout was populated with the "whoami" output 420 | tasklog := readStdoutLog(t, task) 421 | require.Contains(t, tasklog, "www-data") 422 | 423 | } 424 | 425 | // test exit code propagation 426 | func TestSystemdDriver_ExitCode(t *testing.T) { 427 | if !tu.IsCI() { 428 | t.Parallel() 429 | } 430 | 431 | taskCfg := newTaskConfig([]string{ 432 | "/bin/sh", 433 | "-c", 434 | "exit 123", 435 | }) 436 | task := &drivers.TaskConfig{ 437 | ID: uuid.Generate(), 438 | Name: "exitcode", 439 | AllocID: uuid.Generate(), 440 | Resources: createBasicResources(), 441 | } 442 | require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg)) 443 | 444 | d := systemdDriverHarness(t, nil) 445 | cleanup := d.MkAllocDir(task, true) 446 | defer cleanup() 447 | 448 | _, _, err := d.StartTask(task) 449 | require.NoError(t, err) 450 | 451 | defer d.DestroyTask(task.ID, true) 452 | 453 | // Attempt to wait 454 | waitCh, err := d.WaitTask(context.Background(), task.ID) 455 | require.NoError(t, err) 456 | 457 | select { 458 | case res := <-waitCh: 459 | require.Equal(t, 123, res.ExitCode, "Should have got exit code 123") 460 | case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 461 | t.Fatalf("Task did not exit in time") 462 | } 463 | } 464 | 465 | // test oom flag propagation 466 | func TestSystemdDriver_OOM(t *testing.T) { 467 | 468 | if !tu.IsCI() { 469 | t.Parallel() 470 | } 471 | 472 | taskCfg := newTaskConfig([]string{ 473 | // Incrementally creates a bigger and bigger variable. 474 | // "stress", 475 | // "--vm", 476 | // "1", 477 | "/bin/bash", 478 | "-ec", 479 | "sleep 1;for b in {0..99999999}; do a=$b$a; done", 480 | }) 481 | 482 | task := &drivers.TaskConfig{ 483 | ID: uuid.Generate(), 484 | Name: "oom", 485 | AllocID: uuid.Generate(), 486 | Resources: createBasicResources(), 487 | } 488 | // limit memory to 5MB to trigger oom soon enough 489 | task.Resources.NomadResources.Memory.MemoryMB = 5 490 | require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg)) 491 | 492 | d := systemdDriverHarness(t, nil) 493 | cleanup := d.MkAllocDir(task, true) 494 | defer cleanup() 495 | 496 | _, _, err := d.StartTask(task) 497 | require.NoError(t, err) 498 | 499 | defer d.DestroyTask(task.ID, true) 500 | 501 | // Attempt to wait 502 | waitCh, err := d.WaitTask(context.Background(), task.ID) 503 | require.NoError(t, err) 504 | 505 | select { 506 | case res := <-waitCh: 507 | require.False(t, res.Successful(), "Should have failed because of oom but was successful") 508 | require.True(t, res.OOMKilled, "OOM Flag not set") 509 | case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 510 | t.Fatalf("Task did not exit in time") 511 | } 512 | } 513 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /driver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "regexp" 8 | "sync" 9 | "time" 10 | 11 | "github.com/coreos/go-systemd/v22/dbus" 12 | "github.com/hashicorp/go-hclog" 13 | "github.com/hashicorp/nomad/drivers/shared/eventer" 14 | "github.com/hashicorp/nomad/plugins/base" 15 | "github.com/hashicorp/nomad/plugins/drivers" 16 | "github.com/hashicorp/nomad/plugins/shared/hclspec" 17 | "github.com/hashicorp/nomad/plugins/shared/structs" 18 | ) 19 | 20 | const ( 21 | // pluginName is the name of the plugin 22 | // this is used for logging and (along with the version) for uniquely 23 | // identifying plugin binaries fingerprinted by the client 24 | pluginName = "systemd" 25 | 26 | // pluginVersion allows the client to identify and use newer versions of 27 | // an installed plugin 28 | pluginVersion = "v0.1.0" 29 | 30 | // fingerprintPeriod is the interval at which the plugin will send 31 | // fingerprint responses 32 | fingerprintPeriod = 30 * time.Second 33 | 34 | // taskHandleVersion is the version of task handle which this plugin sets 35 | // and understands how to decode 36 | // this is used to allow modification and migration of the task schema 37 | // used by the plugin 38 | taskHandleVersion = 1 39 | ) 40 | 41 | var ( 42 | unitNameRegex = regexp.MustCompile(`nomad_.*_.*\.service`) 43 | ) 44 | 45 | // TaskState is the runtime state which is encoded in the handle returned to 46 | // Nomad client. 47 | // This information is needed to rebuild the task state and handler during 48 | // recovery. 49 | type TaskState struct { 50 | TaskConfig *drivers.TaskConfig 51 | StartedAt time.Time 52 | 53 | // The plugin keeps track of its running tasks in a in-memory data 54 | // structure. If the plugin crashes, this data will be lost, so Nomad 55 | // will respawn a new instance of the plugin and try to restore its 56 | // in-memory representation of the running tasks using the RecoverTask() 57 | // method below. 58 | UnitName string 59 | } 60 | 61 | // Driver is an example driver plugin. When provisioned in a job, 62 | // the taks will output a greet specified by the user. 63 | type Driver struct { 64 | // eventer is used to handle multiplexing of TaskEvents calls such that an 65 | // event can be broadcast to all callers 66 | eventer *eventer.Eventer 67 | 68 | // config is the plugin configuration set by the SetConfig RPC 69 | config *PluginConfig 70 | 71 | // nomadConfig is the client config from Nomad 72 | nomadConfig *base.ClientDriverConfig 73 | 74 | // tasks is the in memory datastore mapping taskIDs to driver handles 75 | tasks *taskStore 76 | 77 | // ctx is the context for the driver. It is passed to other subsystems to 78 | // coordinate shutdown 79 | ctx context.Context 80 | 81 | // signalShutdown is called when the driver is shutting down and cancels 82 | // the ctx passed to any subsystems 83 | signalShutdown context.CancelFunc 84 | 85 | // logger will log to the Nomad agent 86 | logger hclog.Logger 87 | 88 | // our dbus connection 89 | busCon *dbus.Conn 90 | // guards connection access 91 | cmutex sync.Mutex 92 | } 93 | 94 | // NewSystemdDriver returns a new DriverPlugin implementation 95 | func NewSystemdDriver(logger hclog.Logger) drivers.DriverPlugin { 96 | ctx, cancel := context.WithCancel(context.Background()) 97 | logger = logger.Named(pluginName) 98 | 99 | return &Driver{ 100 | eventer: eventer.NewEventer(ctx, logger), 101 | config: &PluginConfig{}, 102 | tasks: newTaskStore(), 103 | ctx: ctx, 104 | signalShutdown: cancel, 105 | logger: logger, 106 | } 107 | } 108 | 109 | // PluginInfo returns information describing the plugin. 110 | func (d *Driver) PluginInfo() (*base.PluginInfoResponse, error) { 111 | return pluginInfo, nil 112 | } 113 | 114 | // ConfigSchema returns the plugin configuration schema. 115 | func (d *Driver) ConfigSchema() (*hclspec.Spec, error) { 116 | return configSpec, nil 117 | } 118 | 119 | // SetConfig is called by the client to pass the configuration for the plugin. 120 | func (d *Driver) SetConfig(cfg *base.Config) error { 121 | var config PluginConfig 122 | if len(cfg.PluginConfig) != 0 { 123 | if err := base.MsgPackDecode(cfg.PluginConfig, &config); err != nil { 124 | return err 125 | } 126 | } 127 | 128 | // Save the configuration to the plugin 129 | d.config = &config 130 | 131 | // TODO: parse and validated any configuration value if necessary. 132 | // 133 | // If your driver agent configuration requires any complex validation 134 | // (some dependency between attributes) or special data parsing (the 135 | // string "10s" into a time.Interval) you can do it here and update the 136 | // value in d.config. 137 | // 138 | // In the example below we check if the shell specified by the user is 139 | // supported by the plugin. 140 | // shell := d.config.Shell 141 | // if shell != "bash" && shell != "fish" { 142 | // return fmt.Errorf("invalid shell %s", d.config.Shell) 143 | // } 144 | 145 | // Save the Nomad agent configuration 146 | if cfg.AgentConfig != nil { 147 | d.nomadConfig = cfg.AgentConfig.Driver 148 | } 149 | 150 | // TODO: initialize any extra requirements if necessary. 151 | // 152 | // Here you can use the config values to initialize any resources that are 153 | // shared by all tasks that use this driver, such as a daemon process. 154 | 155 | return nil 156 | } 157 | 158 | // TaskConfigSchema returns the HCL schema for the configuration of a task. 159 | func (d *Driver) TaskConfigSchema() (*hclspec.Spec, error) { 160 | return taskConfigSpec, nil 161 | } 162 | 163 | // Capabilities returns the features supported by the driver. 164 | func (d *Driver) Capabilities() (*drivers.Capabilities, error) { 165 | return capabilities, nil 166 | } 167 | 168 | // Fingerprint returns a channel that will be used to send health information 169 | // and other driver specific node attributes. 170 | func (d *Driver) Fingerprint(ctx context.Context) (<-chan *drivers.Fingerprint, error) { 171 | ch := make(chan *drivers.Fingerprint) 172 | go d.handleFingerprint(ctx, ch) 173 | return ch, nil 174 | } 175 | 176 | func (d *Driver) connection() (*dbus.Conn, error) { 177 | d.cmutex.Lock() 178 | defer d.cmutex.Unlock() 179 | 180 | if d.busCon != nil { 181 | return d.busCon, nil 182 | } 183 | 184 | newconn, err := dbus.NewWithContext(d.ctx) 185 | if err != nil { 186 | return nil, err 187 | } 188 | 189 | d.busCon = newconn 190 | d.logger.Debug("Dbus connected") 191 | return d.busCon, nil 192 | } 193 | 194 | // handleFingerprint manages the channel and the flow of fingerprint data. 195 | func (d *Driver) handleFingerprint(ctx context.Context, ch chan<- *drivers.Fingerprint) { 196 | defer close(ch) 197 | 198 | // Nomad expects the initial fingerprint to be sent immediately 199 | ticker := time.NewTimer(0) 200 | for { 201 | select { 202 | case <-ctx.Done(): 203 | return 204 | case <-d.ctx.Done(): 205 | return 206 | case <-ticker.C: 207 | // after the initial fingerprint we can set the proper fingerprint 208 | // period 209 | ticker.Reset(fingerprintPeriod) 210 | ch <- d.buildFingerprint() 211 | } 212 | } 213 | } 214 | 215 | // buildFingerprint returns the driver's fingerprint data 216 | func (d *Driver) buildFingerprint() *drivers.Fingerprint { 217 | 218 | fp := &drivers.Fingerprint{ 219 | Attributes: map[string]*structs.Attribute{}, 220 | Health: drivers.HealthStateHealthy, 221 | HealthDescription: drivers.DriverHealthy, 222 | } 223 | 224 | conn, err := d.connection() 225 | if err != nil { 226 | d.logger.Error("No dbus connection", "err", err) 227 | // we're unhealthy 228 | return &drivers.Fingerprint{ 229 | Health: drivers.HealthStateUndetected, 230 | HealthDescription: fmt.Sprintf("No dbus connection"), 231 | } 232 | } 233 | 234 | d.cmutex.Lock() 235 | defer d.cmutex.Unlock() 236 | 237 | // check if we can talk to systemd, get the system state 238 | _, err = conn.SystemStateContext(d.ctx) 239 | if err != nil { 240 | d.logger.Error("Unable to get system state", "err", err) 241 | d.busCon.Close() 242 | d.busCon = nil 243 | // we're unhealthy 244 | return &drivers.Fingerprint{ 245 | Health: drivers.HealthStateUndetected, 246 | HealthDescription: fmt.Sprintf("No dbus connection"), 247 | } 248 | } 249 | 250 | // check if system state is (still) "running" 251 | // if prop.Value.Value().(string) != "running" { 252 | // d.logger.Error("System state incorrect", "state", prop.Value.Value().(string)) 253 | // d.busCon.Close() 254 | // d.busCon = nil 255 | // // we're unhealthy 256 | // return &drivers.Fingerprint{ 257 | // Health: drivers.HealthStateUndetected, 258 | // HealthDescription: fmt.Sprintf("System state incorrect: %v", prop.Value.Value()), 259 | // } 260 | // } 261 | 262 | // ready to rock 263 | return fp 264 | } 265 | 266 | // StartTask returns a task handle and a driver network if necessary. 267 | func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drivers.DriverNetwork, error) { 268 | if _, ok := d.tasks.Get(cfg.ID); ok { 269 | return nil, nil, fmt.Errorf("task with ID %q already started", cfg.ID) 270 | } 271 | 272 | var driverConfig TaskConfig 273 | if err := cfg.DecodeDriverConfig(&driverConfig); err != nil { 274 | return nil, nil, fmt.Errorf("failed to decode driver config: %v", err) 275 | } 276 | 277 | d.logger.Trace("starting task", "driver_cfg", hclog.Fmt("%+v", driverConfig)) 278 | handle := drivers.NewTaskHandle(taskHandleVersion) 279 | handle.Config = cfg 280 | 281 | if driverConfig.Command == "" { 282 | return nil, nil, fmt.Errorf("command name required") 283 | } 284 | // autoprefix command name with local task dir in case that it is not 285 | // a absolute path 286 | if driverConfig.Command[0] != '/' { 287 | driverConfig.Command = cfg.TaskDir().Dir + "/local/" + driverConfig.Command 288 | } 289 | 290 | // TODO ensure to include port_map into tasks environment map 291 | //cfg.Env = taskenv.SetPortMapEnvs(cfg.Env, driverConfig.PortMap) 292 | 293 | execStart := []string{driverConfig.Command} 294 | execStart = append(execStart, driverConfig.Args...) 295 | // populate properties for the to-be-started systemd transient unit 296 | properties := []dbus.Property{ 297 | // set ID as Description. Helps with relating events back to task 298 | dbus.PropDescription(cfg.ID), 299 | dbus.PropType("simple"), 300 | 301 | // do not remove service unit when process dies 302 | // this allows us to get the terminal state unit properties back into the plugin 303 | PropBool("RemainAfterExit", true), 304 | 305 | // to-be-started command incl. args 306 | dbus.PropExecStart(execStart, true), 307 | 308 | // set proper task environment 309 | PropEnvironment(cfg.Env), 310 | // transient units are gone when main process finishes 311 | // we need to write a file here to get exit code back into nomad 312 | //PropExecStopPost([]string{"env"}), 313 | PropString("User", cfg.User), 314 | 315 | // Enforce memory limits 316 | PropBool("MemoryAccounting", true), 317 | // todo: configurable swap 318 | PropUInt64("MemorySwapMax", uint64(0)), 319 | } 320 | 321 | // Memory 322 | // FIXME: handle MemoryMB vs MemoryMaxMB, swap etc 323 | if cfg.Resources.NomadResources.Memory.MemoryMaxMB > 0 { 324 | properties = append(properties, PropUInt64("MemoryMax", uint64(cfg.Resources.NomadResources.Memory.MemoryMaxMB*1024*1024))) 325 | } 326 | 327 | if cfg.Resources.NomadResources.Memory.MemoryMB > 0 { 328 | if cfg.Resources.NomadResources.Memory.MemoryMaxMB > 0 { 329 | properties = append(properties, PropUInt64("MemoryHigh", uint64(cfg.Resources.NomadResources.Memory.MemoryMB*1024*1024))) 330 | } else { 331 | properties = append(properties, PropUInt64("MemoryMax", uint64(cfg.Resources.NomadResources.Memory.MemoryMB*1024*1024))) 332 | } 333 | } 334 | 335 | // Logging 336 | if driverConfig.Logging.Driver == "" || driverConfig.Logging.Driver == LOG_DRIVER_NOMAD { 337 | properties = append(properties, PropString("StandardOutputFileToAppend", cfg.StdoutPath)) 338 | properties = append(properties, PropString("StandardErrorFileToAppend", cfg.StderrPath)) 339 | } else if driverConfig.Logging.Driver == LOG_DRIVER_JOURNALD { 340 | properties = append(properties, PropString("StandardOutput", "journal")) 341 | properties = append(properties, PropString("StandardError", "journal")) 342 | // TODO: configure LogExtraFields 343 | } else { 344 | return nil, nil, fmt.Errorf("Invalid logging.driver option") 345 | } 346 | 347 | //hard, soft, err := memoryLimits(cfg.Resources.NomadResources.Memory, driverConfig.MemoryReservation) 348 | 349 | unitName := BuildUnitNameForTask(cfg) 350 | startupCh := make(chan string) 351 | conn, err := d.connection() 352 | if err != nil { 353 | return nil, nil, fmt.Errorf("failed to start task, no dbus connection: %v", err) 354 | } 355 | 356 | res, err := conn.StartTransientUnitContext(d.ctx, unitName, "replace", properties, startupCh) 357 | if err != nil { 358 | return nil, nil, fmt.Errorf("failed to start task, could not create unit: %v", err) 359 | } 360 | 361 | // wait for startup 362 | <-startupCh 363 | 364 | h := &taskHandle{ 365 | taskConfig: cfg, 366 | procState: drivers.TaskStateRunning, 367 | startedAt: time.Now().Round(time.Millisecond), 368 | logger: d.logger, 369 | unitName: unitName, 370 | driver: d, 371 | collectionInterval: time.Second, 372 | exitResult: &drivers.ExitResult{}, 373 | } 374 | 375 | driverState := TaskState{ 376 | TaskConfig: cfg, 377 | StartedAt: h.startedAt, 378 | UnitName: h.unitName, 379 | } 380 | 381 | if err := handle.SetDriverState(&driverState); err != nil { 382 | return nil, nil, fmt.Errorf("failed to set driver state: %v", err) 383 | } 384 | 385 | d.tasks.Set(cfg.ID, h) 386 | 387 | go h.runUnitMonitor() 388 | d.logger.Info("Started unit", "unit", unitName, "id", res) 389 | return handle, nil, nil 390 | } 391 | 392 | // BuildUnitNameForTask returns the systemd unit name for a specific Task in our group 393 | func BuildUnitNameForTask(cfg *drivers.TaskConfig) string { 394 | return fmt.Sprintf("nomad_%s_%s.service", cfg.Name, cfg.AllocID) 395 | } 396 | 397 | // RecoverTask recreates the in-memory state of a task from a TaskHandle. 398 | func (d *Driver) RecoverTask(handle *drivers.TaskHandle) error { 399 | if handle == nil { 400 | return errors.New("error: handle cannot be nil") 401 | } 402 | 403 | if _, ok := d.tasks.Get(handle.Config.ID); ok { 404 | return nil 405 | } 406 | 407 | var taskState TaskState 408 | if err := handle.GetDriverState(&taskState); err != nil { 409 | return fmt.Errorf("failed to decode task state from handle: %v", err) 410 | } 411 | 412 | d.logger.Debug("Checking for recoverable task", "task", handle.Config.Name, "taskid", handle.Config.ID, "unit", taskState.UnitName) 413 | 414 | conn, err := d.connection() 415 | if err != nil { 416 | return fmt.Errorf("failed to recover task, no dbus connection: %v", err) 417 | } 418 | 419 | state, err := conn.GetUnitPropertyContext(d.ctx, taskState.UnitName, "ActiveState") 420 | if err == nil { 421 | if state.Value.String() == "inactive" { 422 | d.logger.Debug("Found a inactive unit", "task", handle.Config.ID, "unit", taskState.UnitName) 423 | return nil 424 | } 425 | } else { 426 | d.logger.Debug("Recovery lookup found no unit", "task", handle.Config.ID, "unit", taskState.UnitName, "error", err) 427 | return nil 428 | } 429 | 430 | h := &taskHandle{ 431 | procState: drivers.TaskStateRunning, 432 | 433 | taskConfig: taskState.TaskConfig, 434 | startedAt: taskState.StartedAt, 435 | unitName: taskState.UnitName, 436 | 437 | exitResult: &drivers.ExitResult{}, 438 | logger: d.logger, 439 | driver: d, 440 | collectionInterval: time.Second, 441 | } 442 | 443 | d.tasks.Set(taskState.TaskConfig.ID, h) 444 | 445 | go h.runUnitMonitor() 446 | 447 | d.logger.Debug("Recovered task unit", "task", handle.Config.ID, "unit", taskState.UnitName) 448 | return nil 449 | } 450 | 451 | // WaitTask returns a channel used to notify Nomad when a task exits. 452 | func (d *Driver) WaitTask(ctx context.Context, taskID string) (<-chan *drivers.ExitResult, error) { 453 | d.logger.Debug("WaitTask called", "task", taskID) 454 | handle, ok := d.tasks.Get(taskID) 455 | if !ok { 456 | return nil, drivers.ErrTaskNotFound 457 | } 458 | ch := make(chan *drivers.ExitResult) 459 | 460 | go handle.runExitWatcher(ctx, ch) 461 | return ch, nil 462 | } 463 | 464 | func (d *Driver) handleWait(ctx context.Context, handle *taskHandle, ch chan *drivers.ExitResult) { 465 | defer close(ch) 466 | 467 | // TODO: implement driver specific logic to notify Nomad the task has been 468 | // var result *drivers.ExitResult 469 | // result = &drivers.ExitResult{ 470 | // ExitCode: 0, 471 | // Signal: 0, 472 | // } 473 | 474 | // completed and what was the exit result. 475 | // 476 | // When a result is sent in the result channel Nomad will stop the task and 477 | // emit an event that an operator can use to get an insight on why the task 478 | // stopped. 479 | // 480 | // In the example below we block and wait until the executor finishes 481 | // running, at which point we send the exit code and signal in the result 482 | // channel. 483 | // ps, err := handle.exec.Wait(ctx) 484 | // if err != nil { 485 | // result = &drivers.ExitResult{ 486 | // Err: fmt.Errorf("executor: error waiting on process: %v", err), 487 | // } 488 | // } else { 489 | // result = &drivers.ExitResult{ 490 | // ExitCode: ps.ExitCode, 491 | // Signal: ps.Signal, 492 | // } 493 | // } 494 | 495 | for { 496 | select { 497 | case <-ctx.Done(): 498 | return 499 | case <-d.ctx.Done(): 500 | return 501 | //case ch <- result: 502 | } 503 | } 504 | } 505 | 506 | // StopTask stops a running task with the given signal and within the timeout window. 507 | func (d *Driver) StopTask(taskID string, timeout time.Duration, signal string) error { 508 | d.logger.Info("Stopping task", "taskID", taskID, "signal", signal) 509 | handle, ok := d.tasks.Get(taskID) 510 | if !ok { 511 | return drivers.ErrTaskNotFound 512 | } 513 | 514 | ctx, cancel := context.WithTimeout(d.ctx, timeout) 515 | defer cancel() 516 | 517 | conn, err := d.connection() 518 | if err != nil { 519 | return fmt.Errorf("failed to stop task, no dbus connection: %v", err) 520 | } 521 | _, err = conn.StopUnitContext(ctx, handle.unitName, "replace", nil) 522 | if err != nil { 523 | d.logger.Error("Could not stop/kill unit", "unit", handle.unitName, "error", err) 524 | return err 525 | } 526 | 527 | return nil 528 | } 529 | 530 | // DestroyTask cleans up and removes a task that has terminated. 531 | func (d *Driver) DestroyTask(taskID string, force bool) error { 532 | d.logger.Info("Destroy task", "taskID", taskID) 533 | handle, ok := d.tasks.Get(taskID) 534 | if !ok { 535 | return drivers.ErrTaskNotFound 536 | } 537 | 538 | if handle.IsRunning() && !force { 539 | return errors.New("cannot destroy running task") 540 | } 541 | 542 | if handle.isRunning() { 543 | d.logger.Debug("Have to destroyTask but unit is still running", "unit", handle.unitName) 544 | // we can not do anything, so catching the error is useless 545 | 546 | conn, err := d.connection() 547 | if err != nil { 548 | return fmt.Errorf("failed to stop task, no dbus connection: %v", err) 549 | } 550 | _, err = conn.StopUnitContext(d.ctx, handle.unitName, "replace", nil) 551 | if err != nil { 552 | d.logger.Error("Could not stop/kill unit", "unit", handle.unitName, "error", err) 553 | return err 554 | } 555 | // wait a while for stats emitter to collect exit code etc. 556 | for i := 0; i < 20; i++ { 557 | if !handle.isRunning() { 558 | break 559 | } 560 | time.Sleep(time.Millisecond * 250) 561 | } 562 | if handle.isRunning() { 563 | d.logger.Warn("stats emitter did not exit while stop/kill unit during destroy", "error", err) 564 | } 565 | } 566 | 567 | d.tasks.Delete(taskID) 568 | 569 | return nil 570 | } 571 | 572 | // InspectTask returns detailed status information for the referenced taskID. 573 | func (d *Driver) InspectTask(taskID string) (*drivers.TaskStatus, error) { 574 | handle, ok := d.tasks.Get(taskID) 575 | if !ok { 576 | return nil, drivers.ErrTaskNotFound 577 | } 578 | 579 | return handle.TaskStatus(), nil 580 | } 581 | 582 | // TaskStats returns a channel which the driver should send stats to at the given interval. 583 | func (d *Driver) TaskStats(ctx context.Context, taskID string, interval time.Duration) (<-chan *drivers.TaskResourceUsage, error) { 584 | handle, ok := d.tasks.Get(taskID) 585 | if !ok { 586 | return nil, drivers.ErrTaskNotFound 587 | } 588 | 589 | statsChannel := make(chan *drivers.TaskResourceUsage) 590 | go handle.runStatsEmitter(ctx, statsChannel, interval) 591 | return statsChannel, nil 592 | } 593 | 594 | // TaskEvents returns a channel that the plugin can use to emit task related events. 595 | func (d *Driver) TaskEvents(ctx context.Context) (<-chan *drivers.TaskEvent, error) { 596 | return d.eventer.TaskEvents(ctx) 597 | } 598 | 599 | // SignalTask forwards a signal to a task. 600 | // This is an optional capability. 601 | func (d *Driver) SignalTask(taskID string, signal string) error { 602 | _, ok := d.tasks.Get(taskID) 603 | if !ok { 604 | return drivers.ErrTaskNotFound 605 | } 606 | 607 | // TODO: implement driver specific signal handling logic. 608 | // 609 | // The given signal must be forwarded to the target taskID. If this plugin 610 | // doesn't support receiving signals (capability SendSignals is set to 611 | // false) you can just return nil. 612 | // sig := os.Interrupt 613 | // if s, ok := signals.SignalLookup[signal]; ok { 614 | // sig = s 615 | // } else { 616 | // d.logger.Warn("unknown signal to send to task, using SIGINT instead", "signal", signal, "task_id", handle.taskConfig.ID) 617 | 618 | // } 619 | 620 | // FIXME: implement 621 | return drivers.ErrTaskNotFound 622 | //return handle.exec.Signal(sig) 623 | } 624 | 625 | // ExecTask returns the result of executing the given command inside a task. 626 | // This is an optional capability. 627 | func (d *Driver) ExecTask(taskID string, cmd []string, timeout time.Duration) (*drivers.ExecTaskResult, error) { 628 | // TODO: implement driver specific logic to execute commands in a task. 629 | return nil, errors.New("This driver does not support exec") 630 | } 631 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= 4 | github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= 5 | github.com/LK4D4/joincontext v0.0.0-20171026170139-1724345da6d5 h1:U7q69tqXiCf6m097GRlNQB0/6SI1qWIOHYHhCEvDxF4= 6 | github.com/LK4D4/joincontext v0.0.0-20171026170139-1724345da6d5/go.mod h1:nxQPcNPR/34g+HcK2hEsF99O+GJgIkW/OmPl8wtzhmk= 7 | github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= 8 | github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= 9 | github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= 10 | github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= 11 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= 12 | github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= 13 | github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= 14 | github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= 15 | github.com/Masterminds/sprig/v3 v3.2.1/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= 16 | github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= 17 | github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= 18 | github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= 19 | github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= 20 | github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= 21 | github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= 22 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 23 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 24 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 25 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 26 | github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= 27 | github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= 28 | github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= 29 | github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= 30 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 31 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 32 | github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= 33 | github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= 34 | github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= 35 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 36 | github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= 37 | github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 38 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 39 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 40 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 41 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 42 | github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= 43 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 44 | github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= 45 | github.com/brianvoe/gofakeit/v6 v6.20.1 h1:8ihJ60OvPnPJ2W6wZR7M+TTeaZ9bml0z6oy4gvyJ/ek= 46 | github.com/brianvoe/gofakeit/v6 v6.20.1/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8= 47 | github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= 48 | github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= 49 | github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= 50 | github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= 51 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 52 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 53 | github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= 54 | github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= 55 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 56 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 57 | github.com/container-storage-interface/spec v1.7.0 h1:gW8eyFQUZWWrMWa8p1seJ28gwDoN5CVJ4uAbQ+Hdycw= 58 | github.com/container-storage-interface/spec v1.7.0/go.mod h1:JYuzLqr9VVNoDJl44xp/8fmCOvWPDKzuGTwCoklhuqk= 59 | github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= 60 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 61 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 62 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 63 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 64 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 65 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 66 | github.com/docker/docker v23.0.8+incompatible h1:z4ZCIwfqHgOEwhxmAWugSL1PFtPQmLP60EVhJYJPaX8= 67 | github.com/docker/docker v23.0.8+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 68 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 69 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 70 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 71 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 72 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 73 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 74 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 75 | github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= 76 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 77 | github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= 78 | github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= 79 | github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= 80 | github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= 81 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 82 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 83 | github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= 84 | github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= 85 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 86 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 87 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 88 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 89 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 90 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 91 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 92 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 93 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 94 | github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= 95 | github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= 96 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 97 | github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= 98 | github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 99 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 100 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 101 | github.com/gojuno/minimock/v3 v3.0.4/go.mod h1:HqeqnwV8mAABn3pO5hqF+RE7gjA0jsN8cbbSogoGrzI= 102 | github.com/gojuno/minimock/v3 v3.0.6 h1:YqHcVR10x2ZvswPK8Ix5yk+hMpspdQ3ckSpkOzyF85I= 103 | github.com/gojuno/minimock/v3 v3.0.6/go.mod h1:v61ZjAKHr+WnEkND63nQPCZ/DTfQgJdvbCi3IuoMblY= 104 | github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= 105 | github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 106 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 107 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 108 | github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 109 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 110 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 111 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 112 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 113 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 114 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 115 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 116 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 117 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 118 | github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= 119 | github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= 120 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 121 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 122 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 123 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 124 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 125 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 126 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 127 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 128 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 129 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 130 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 131 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 132 | github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= 133 | github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 134 | github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= 135 | github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= 136 | github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM= 137 | github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A= 138 | github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU= 139 | github.com/hashicorp/consul/sdk v0.15.0/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= 140 | github.com/hashicorp/cronexpr v1.1.2 h1:wG/ZYIKT+RT3QkOdgYc+xsKWVRgnxJ1OJtjjy84fJ9A= 141 | github.com/hashicorp/cronexpr v1.1.2/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= 142 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 143 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 144 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 145 | github.com/hashicorp/go-bexpr v0.1.13 h1:HNwp7vZrMpRq8VZXj8VF90LbZpRjQQpim1oJF0DgSwg= 146 | github.com/hashicorp/go-bexpr v0.1.13/go.mod h1:gN7hRKB3s7yT+YvTdnhZVLTENejvhlkZ8UE4YVBS+Q8= 147 | github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 148 | github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 149 | github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 150 | github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= 151 | github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= 152 | github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= 153 | github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= 154 | github.com/hashicorp/go-hclog v1.6.2 h1:NOtoftovWkDheyUM/8JW3QMiXyxJK3uHRK7wV04nD2I= 155 | github.com/hashicorp/go-hclog v1.6.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= 156 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 157 | github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= 158 | github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 159 | github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo= 160 | github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw= 161 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 162 | github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 163 | github.com/hashicorp/go-msgpack v1.1.5 h1:9byZdVjKTe5mce63pRVNP1L7UAmdHOTEMGehn6KvJWs= 164 | github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YNK9NfGLXJ69/4= 165 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 166 | github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= 167 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 168 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 169 | github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A= 170 | github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI= 171 | github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= 172 | github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0= 173 | github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= 174 | github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= 175 | github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= 176 | github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.4 h1:6ajbq64FhrIJZ6prrff3upVVDil4yfCrnSKwTH0HIPE= 177 | github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.4/go.mod h1:myX7XYMJRIP4PLHtYJiKMTJcKOX0M5ZJNwP0iw+l3uw= 178 | github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= 179 | github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs= 180 | github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= 181 | github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 h1:SMGUnbpAcat8rIKHkBPjfv81yC46a8eCNZ2hsR2l1EI= 182 | github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1/go.mod h1:Ch/bf00Qnx77MZd49JRgHYqHQjtEmTgGU2faufpVZb0= 183 | github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= 184 | github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= 185 | github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= 186 | github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1/go.mod h1:l8slYwnJA26yBz+ErHpp2IRCLr0vuOMGBORIz4rRiAs= 187 | github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.2 h1:phcbL8urUzF/kxA/Oj6awENaRwfWsjP59GW7u2qlDyY= 188 | github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.2/go.mod h1:l8slYwnJA26yBz+ErHpp2IRCLr0vuOMGBORIz4rRiAs= 189 | github.com/hashicorp/go-set/v2 v2.1.0 h1:iERPCQWks+I+4bTgy0CT2myZsCqNgBg79ZHqwniohXo= 190 | github.com/hashicorp/go-set/v2 v2.1.0/go.mod h1:6q4nh8UCVZODn2tJ5RbJi8+ki7pjZBsAEYGt6yaGeTo= 191 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 192 | github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= 193 | github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= 194 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 195 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 196 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 197 | github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= 198 | github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 199 | github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= 200 | github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 201 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 202 | github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= 203 | github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 204 | github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4= 205 | github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 206 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 207 | github.com/hashicorp/hcl v1.0.1-vault-3 h1:V95v5KSTu6DB5huDSKiq4uAfILEuNigK/+qPET6H/Mg= 208 | github.com/hashicorp/hcl v1.0.1-vault-3/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= 209 | github.com/hashicorp/hcl/v2 v2.9.2-0.20220525143345-ab3cae0737bc h1:32lGaCPq5JPYNgFFTjl/cTIar9UWWxCbimCs5G2hMHg= 210 | github.com/hashicorp/hcl/v2 v2.9.2-0.20220525143345-ab3cae0737bc/go.mod h1:odKNpEeZv3COD+++SQcPyACuKOlM5eBoQlzRyN5utIQ= 211 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 212 | github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= 213 | github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= 214 | github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= 215 | github.com/hashicorp/nomad v1.7.3 h1:iNZFZaSi+kcnm4+Po/mrn1GKJ2spDJehgtLpvADYHlk= 216 | github.com/hashicorp/nomad v1.7.3/go.mod h1:SBmk/k507T3B65May9GTLJwybafDTUSdpxX+jJvumSc= 217 | github.com/hashicorp/raft v1.2.0/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= 218 | github.com/hashicorp/raft v1.5.0 h1:uNs9EfJ4FwiArZRxxfd/dQ5d33nV31/CdCHArH89hT8= 219 | github.com/hashicorp/raft v1.5.0/go.mod h1:pKHB2mf/Y25u3AHNSXVRv+yT+WAnmeTX0BwVppVQV+M= 220 | github.com/hashicorp/raft-autopilot v0.1.6 h1:C1q3RNF2FfXNZfHWbvVAu0QixaQK8K5pX4O5lh+9z4I= 221 | github.com/hashicorp/raft-autopilot v0.1.6/go.mod h1:Af4jZBwaNOI+tXfIqIdbcAnh/UyyqIMj/pOISIfhArw= 222 | github.com/hashicorp/raft-boltdb v0.0.0-20171010151810-6e5ba93211ea/go.mod h1:pNv7Wc3ycL6F5oOWn+tPGo2gWD4a5X+yp/ntwdKLjRk= 223 | github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= 224 | github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= 225 | github.com/hashicorp/vault/api v1.10.0 h1:/US7sIjWN6Imp4o/Rj1Ce2Nr5bki/AXi9vAW3p2tOJQ= 226 | github.com/hashicorp/vault/api v1.10.0/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W0Vp1IrFI8N8= 227 | github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= 228 | github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= 229 | github.com/hexdigest/gowrap v1.1.7/go.mod h1:Z+nBFUDLa01iaNM+/jzoOA1JJ7sm51rnYFauKFUB5fs= 230 | github.com/hpcloud/tail v1.0.1-0.20170814160653-37f427138745 h1:8as8OQ+RF1QrsHvWWsKBtBKINhD9QaD1iozA1wrO4aA= 231 | github.com/hpcloud/tail v1.0.1-0.20170814160653-37f427138745/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 232 | github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 233 | github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 234 | github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 235 | github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= 236 | github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 237 | github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= 238 | github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= 239 | github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= 240 | github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f h1:E87tDTVS5W65euzixn7clSzK66puSt1H4I5SC0EmHH4= 241 | github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f/go.mod h1:3J2qVK16Lq8V+wfiL2lPeDZ7UWMxk5LemerHa1p6N00= 242 | github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= 243 | github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= 244 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 245 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 246 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 247 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 248 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 249 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 250 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 251 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 252 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 253 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 254 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 255 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 256 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 257 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 258 | github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= 259 | github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= 260 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= 261 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= 262 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 263 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 264 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 265 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 266 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 267 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 268 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 269 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 270 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 271 | github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= 272 | github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= 273 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 274 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 275 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 276 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 277 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 278 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 279 | github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= 280 | github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= 281 | github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= 282 | github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= 283 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 284 | github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= 285 | github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= 286 | github.com/mitchellh/cli v1.1.5 h1:OxRIeJXpAMztws/XHlN2vu6imG5Dpq+j61AzAX5fLng= 287 | github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2cLtRR4= 288 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= 289 | github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= 290 | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= 291 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 292 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 293 | github.com/mitchellh/go-testing-interface v1.14.2-0.20210821155943-2d9075ca8770 h1:drhDO54gdT/a15GBcMRmunZiNcLgPiFIJa23KzmcvcU= 294 | github.com/mitchellh/go-testing-interface v1.14.2-0.20210821155943-2d9075ca8770/go.mod h1:SO/iHr6q2EzbqRApt+8/E9wqebTwQn5y+UlB04bxzo0= 295 | github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 296 | github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 297 | github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= 298 | github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= 299 | github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0= 300 | github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA= 301 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 302 | github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 303 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 304 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 305 | github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQhoHZAz4x7TIw= 306 | github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= 307 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 308 | github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= 309 | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 310 | github.com/moby/sys/mount v0.3.3 h1:fX1SVkXFJ47XWDoeFW4Sq7PdQJnV2QIDZAqjNqgEjUs= 311 | github.com/moby/sys/mount v0.3.3/go.mod h1:PBaEorSNTLG5t/+4EgukEQVlAvVEc6ZjTySwKdqp5K0= 312 | github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= 313 | github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= 314 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 315 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 316 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 317 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 318 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 319 | github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= 320 | github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= 321 | github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 322 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 323 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 324 | github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= 325 | github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 326 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 327 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 328 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 329 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 330 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 331 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 332 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 333 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 334 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 335 | github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= 336 | github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= 337 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= 338 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 339 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 340 | github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= 341 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 342 | github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= 343 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 344 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 345 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 346 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 347 | github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 348 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 349 | github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= 350 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 351 | github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 352 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 353 | github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= 354 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 355 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 356 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 357 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 358 | github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 359 | github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= 360 | github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= 361 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= 362 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 363 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 364 | github.com/shirou/gopsutil/v3 v3.23.9 h1:ZI5bWVeu2ep4/DIxB4U9okeYJ7zp/QLTO4auRb/ty/E= 365 | github.com/shirou/gopsutil/v3 v3.23.9/go.mod h1:x/NWSb71eMcjFIO0vhyGW5nZ7oSIgVjrCnADckb85GA= 366 | github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= 367 | github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= 368 | github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= 369 | github.com/shoenig/test v1.7.0 h1:eWcHtTXa6QLnBvm0jgEabMRN/uJ4DMV3M8xUGgRkZmk= 370 | github.com/shoenig/test v1.7.0/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI= 371 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 372 | github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= 373 | github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 374 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 375 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 376 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 377 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 378 | github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 379 | github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= 380 | github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= 381 | github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 382 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 383 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 384 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 385 | github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= 386 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 387 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 388 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 389 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 390 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 391 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 392 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 393 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 394 | github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= 395 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 396 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 397 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 398 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 399 | github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= 400 | github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= 401 | github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= 402 | github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= 403 | github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= 404 | github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= 405 | github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U= 406 | github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= 407 | github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY= 408 | github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= 409 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 410 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 411 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 412 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 413 | github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= 414 | github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 415 | github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= 416 | github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= 417 | github.com/zclconf/go-cty v1.12.1 h1:PcupnljUm9EIvbgSHQnHhUr3fO6oFmkOrvs2BAFNXXY= 418 | github.com/zclconf/go-cty v1.12.1/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= 419 | github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= 420 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 421 | go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= 422 | go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= 423 | go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= 424 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 425 | go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= 426 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 427 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 428 | golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 429 | golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= 430 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 431 | golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 432 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 433 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 434 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 435 | golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 436 | golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= 437 | golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= 438 | golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 439 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 440 | golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= 441 | golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= 442 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 443 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 444 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 445 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 446 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 447 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 448 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 449 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 450 | golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= 451 | golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 452 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 453 | golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 454 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 455 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 456 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 457 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 458 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 459 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 460 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 461 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 462 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 463 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 464 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 465 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 466 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 467 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 468 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 469 | golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= 470 | golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 471 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 472 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 473 | golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= 474 | golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= 475 | golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 476 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 477 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 478 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 479 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 480 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 481 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 482 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 483 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 484 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 485 | golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= 486 | golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 487 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 488 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 489 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 490 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 491 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 492 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 493 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 494 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 495 | golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 496 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 497 | golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 498 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 499 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 500 | golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 501 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 502 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 503 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 504 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 505 | golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 506 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 507 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 508 | golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 509 | golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 510 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 511 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 512 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 513 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 514 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 515 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 516 | golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 517 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 518 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 519 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 520 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 521 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 522 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 523 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 524 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 525 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 526 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 527 | golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= 528 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 529 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 530 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 531 | golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= 532 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 533 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 534 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 535 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 536 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 537 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 538 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 539 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 540 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 541 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 542 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 543 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 544 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 545 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 546 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 547 | golang.org/x/tools v0.0.0-20190424220101-1e8e1cfdf96b/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 548 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 549 | golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 550 | golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 551 | golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 552 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 553 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 554 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 555 | golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 556 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 557 | golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= 558 | golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= 559 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 560 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 561 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 562 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 563 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 564 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 565 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 566 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= 567 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 568 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 569 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 570 | google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 571 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= 572 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= 573 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 574 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 575 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 576 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 577 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 578 | google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= 579 | google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= 580 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 581 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 582 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= 583 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 584 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 585 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 586 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 587 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 588 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 589 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 590 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 591 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 592 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 593 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 594 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 595 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 596 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 597 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 598 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 599 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 600 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 601 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 602 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 603 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 604 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 605 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 606 | oss.indeed.com/go/libtime v1.6.0 h1:XQyczJihse/wQGo59OfPF3f4f+Sywv4R8vdGB3S9BfU= 607 | oss.indeed.com/go/libtime v1.6.0/go.mod h1:B2sdEcuzB0zhTKkAuHy4JInKRc7Al3tME4qWam6R7mA= 608 | --------------------------------------------------------------------------------