diff --git a/Makefile b/Makefile index 7710dd1..3eed35e 100644 --- a/Makefile +++ b/Makefile @@ -10,26 +10,42 @@ LDFLAGS=-ldflags "-s -w -X patchmon-agent/internal/version.Version=$(VERSION)" # Disable VCS stamping BUILD_FLAGS=-buildvcs=false -# Go variables -GOBASE=$(shell pwd) -GOBIN=$(GOBASE)/$(BUILD_DIR) -# Use full path to go binary to avoid PATH issues when running as root -GO_CMD=/usr/local/go/bin/go -# Use full path to golangci-lint binary to avoid PATH issues when running as root -GOLANGCI_LINT_CMD=/usr/local/go/bin/golangci-lint - -# Default target +#============================================================================= +# Build Variables +#============================================================================= +BINARY_NAME = patchmon-agent +BUILD_DIR = build +VERSION = 1.3.3 + +# Build flags +LDFLAGS = -ldflags "-s -w -X patchmon-agent/internal/version.Version=$(VERSION)" +BUILD_FLAGS = -buildvcs=false + +#============================================================================= +# Go Variables +#============================================================================= +GOBASE = $(shell pwd) +GOBIN = $(GOBASE)/$(BUILD_DIR) +GO_CMD ?= $(shell which go) +GOLANGCI_LINT_CMD ?= $(shell which golangci-lint || echo /usr/local/go/bin/golangci-lint) + +#============================================================================= +# Targets +#============================================================================= + .PHONY: all all: build -# Build the application +#------------------------------------------------------------------------------ +# Build Targets +#------------------------------------------------------------------------------ + .PHONY: build build: @echo "Building $(BINARY_NAME)..." @mkdir -p $(BUILD_DIR) @CGO_ENABLED=0 $(GO_CMD) build $(BUILD_FLAGS) $(LDFLAGS) -o $(GOBIN)/$(BINARY_NAME) ./cmd/patchmon-agent -# Build for multiple architectures .PHONY: build-all build-all: @echo "Building for multiple architectures..." @@ -39,53 +55,53 @@ build-all: @GOOS=linux GOARCH=arm64 CGO_ENABLED=0 $(GO_CMD) build $(BUILD_FLAGS) $(LDFLAGS) -o $(GOBIN)/$(BINARY_NAME)-linux-arm64 ./cmd/patchmon-agent @GOOS=linux GOARCH=arm CGO_ENABLED=0 $(GO_CMD) build $(BUILD_FLAGS) $(LDFLAGS) -o $(GOBIN)/$(BINARY_NAME)-linux-arm ./cmd/patchmon-agent -# Install dependencies +#------------------------------------------------------------------------------ +# Development Targets +#------------------------------------------------------------------------------ + .PHONY: deps deps: @echo "Installing dependencies..." @$(GO_CMD) mod download @$(GO_CMD) mod tidy -# Run tests .PHONY: test test: @echo "Running tests..." @$(GO_CMD) test -v ./... -# Run tests with coverage .PHONY: test-coverage test-coverage: @echo "Running tests with coverage..." @$(GO_CMD) test -v -coverprofile=coverage.out ./... @$(GO_CMD) tool cover -html=coverage.out -o coverage.html -# Format code .PHONY: fmt fmt: @echo "Formatting code..." @$(GO_CMD) fmt ./... -# Lint code .PHONY: lint lint: @echo "Linting code..." @PATH="/usr/local/bin/go/bin:$$PATH" GOFLAGS="$(BUILD_FLAGS)" $(GOLANGCI_LINT_CMD) run -# Clean build artifacts +#------------------------------------------------------------------------------ +# Maintenance Targets +#------------------------------------------------------------------------------ + .PHONY: clean clean: @echo "Cleaning build artifacts..." @rm -rf $(BUILD_DIR) @rm -f coverage.out coverage.html -# Install the binary to system .PHONY: install install: build @echo "Installing $(BINARY_NAME) to /usr/local/bin..." @sudo cp $(GOBIN)/$(BINARY_NAME) /usr/local/bin/ @sudo chmod +x /usr/local/bin/$(BINARY_NAME) -# Show help .PHONY: help help: @echo "Available targets:" diff --git a/cmd/patchmon-agent/commands/ansible_playbook.go b/cmd/patchmon-agent/commands/ansible_playbook.go new file mode 100644 index 0000000..aaff71a --- /dev/null +++ b/cmd/patchmon-agent/commands/ansible_playbook.go @@ -0,0 +1,219 @@ +package commands + +import ( + "context" + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/apenella/go-ansible/v2/pkg/execute" + "github.com/apenella/go-ansible/v2/pkg/playbook" + "github.com/spf13/cobra" +) + +var ( + ansibleExtraVars []string + ansibleExtraVarsJSON string +) + +// ansiblePlaybookCmd represents the ansible-playbook command +var ansiblePlaybookCmd = &cobra.Command{ + Use: "ansible-playbook [playbook-path]", + Short: "Execute an Ansible playbook", + Long: `Execute an Ansible playbook using the go-ansible library. + +This command allows you to run Ansible playbooks from the PatchMon agent. +The playbook path should be an absolute or relative path to a valid Ansible playbook YAML file. + +Variables can be passed using --extra-vars (key=value pairs) or --extra-vars-json (JSON format). + +Example: + patchmon-agent ansible-playbook /etc/patchmon/profiles/myplaybook.yml + patchmon-agent --ansible-playbook /etc/patchmon/profiles/myplaybook.yml + patchmon-agent ansible-playbook playbook.yml --extra-vars "key1=value1 key2=value2" + patchmon-agent ansible-playbook playbook.yml --extra-vars-json '{"key":"value"}'`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + playbookPath := args[0] + + // Check if playbook file exists + if _, err := os.Stat(playbookPath); os.IsNotExist(err) { + return fmt.Errorf("playbook file not found: %s", playbookPath) + } + + // Parse extra vars + extraVars, err := parseExtraVars(ansibleExtraVars, ansibleExtraVarsJSON) + if err != nil { + return fmt.Errorf("failed to parse extra vars: %w", err) + } + + return runAnsiblePlaybook(playbookPath, extraVars) + }, +} + +func init() { + ansiblePlaybookCmd.Flags().StringArrayVarP(&ansibleExtraVars, "extra-vars", "e", []string{}, "Extra variables as key=value pairs (can be specified multiple times)") + ansiblePlaybookCmd.Flags().StringVar(&ansibleExtraVarsJSON, "extra-vars-json", "", "Extra variables as JSON string") +} + +// parseKeyValuePairs parses a string containing key=value pairs, handling quoted strings with spaces +// This function handles both cases: +// 1. Single key=value pair: "key=value with spaces" +// 2. Multiple key=value pairs: "key1=value1 key2=value2" +func parseKeyValuePairs(input string) []string { + var pairs []string + var current strings.Builder + inQuotes := false + quoteChar := byte(0) + + input = strings.TrimSpace(input) + if input == "" { + return pairs + } + + for i := 0; i < len(input); i++ { + char := input[i] + + // Check for escaped quote + if char == '\\' && i+1 < len(input) && (input[i+1] == '"' || input[i+1] == '\'') { + if inQuotes { + current.WriteByte(input[i+1]) + } else { + current.WriteByte(char) + current.WriteByte(input[i+1]) + } + i++ // Skip next character + continue + } + + // Handle quote characters + if char == '"' || char == '\'' { + if !inQuotes { + // Starting a quoted section + inQuotes = true + quoteChar = char + current.WriteByte(char) + } else if char == quoteChar { + // Ending a quoted section + inQuotes = false + quoteChar = 0 + current.WriteByte(char) + } else { + // Different quote type inside quotes, treat as literal + current.WriteByte(char) + } + continue + } + + // If we encounter whitespace outside quotes, split here + if !inQuotes && (char == ' ' || char == '\t') { + if current.Len() > 0 { + pairs = append(pairs, current.String()) + current.Reset() + } + // Skip whitespace + continue + } + + current.WriteByte(char) + } + + // Add the last pair if there's anything left + if current.Len() > 0 { + pairs = append(pairs, current.String()) + } + + return pairs +} + +// parseExtraVars parses extra vars from both key=value pairs and JSON format +func parseExtraVars(extraVarsArray []string, extraVarsJSON string) (map[string]interface{}, error) { + result := make(map[string]interface{}) + + // Parse JSON format if provided + if extraVarsJSON != "" { + var jsonVars map[string]interface{} + if err := json.Unmarshal([]byte(extraVarsJSON), &jsonVars); err != nil { + return nil, fmt.Errorf("invalid JSON format: %w", err) + } + for k, v := range jsonVars { + result[k] = v + } + } + + // Parse key=value pairs, handling quoted strings with spaces + for _, ev := range extraVarsArray { + pairs := parseKeyValuePairs(ev) + for _, pair := range pairs { + parts := strings.SplitN(pair, "=", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("invalid extra-var format: %s (expected key=value)", pair) + } + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + + // Try to parse as JSON value (for booleans, numbers, etc.), fallback to string + var jsonValue interface{} + // First try parsing the entire value as JSON (handles quotes) + if err := json.Unmarshal([]byte(value), &jsonValue); err == nil { + // If it's a string in JSON, unwrap it + if str, ok := jsonValue.(string); ok { + result[key] = str + } else { + result[key] = jsonValue + } + } else { + // Remove quotes if present (handles both single and double quotes) + if len(value) >= 2 { + if (value[0] == '"' && value[len(value)-1] == '"') || + (value[0] == '\'' && value[len(value)-1] == '\'') { + value = value[1 : len(value)-1] + } + } + result[key] = value + } + } + } + + return result, nil +} + +// runAnsiblePlaybook executes an Ansible playbook +func runAnsiblePlaybook(playbookPath string, extraVars map[string]interface{}) error { + logger.WithField("playbook", playbookPath).Info("Executing Ansible playbook") + if len(extraVars) > 0 { + logger.WithField("extra_vars", extraVars).Debug("Using extra variables") + } + + ctx := context.Background() + + // Define the playbook options + playbookOptions := &playbook.AnsiblePlaybookOptions{ + Inventory: "localhost,", // Default to localhost, can be made configurable + ExtraVars: extraVars, + } + + // Create the Ansible playbook command + playbookCmd := &playbook.AnsiblePlaybookCmd{ + Playbooks: []string{playbookPath}, + PlaybookOptions: playbookOptions, + } + + // Create executor and execute the playbook + exec := execute.NewDefaultExecute( + execute.WithCmd(playbookCmd), + execute.WithErrorEnrich(playbook.NewAnsiblePlaybookErrorEnrich()), + ) + + // Execute the playbook + err := exec.Execute(ctx) + if err != nil { + logger.WithError(err).Error("Failed to execute Ansible playbook") + return fmt.Errorf("playbook execution failed: %w", err) + } + + logger.Info("✅ Ansible playbook executed successfully") + fmt.Println("✅ Ansible playbook executed successfully") + return nil +} diff --git a/cmd/patchmon-agent/commands/root.go b/cmd/patchmon-agent/commands/root.go index c97db37..69a46c8 100644 --- a/cmd/patchmon-agent/commands/root.go +++ b/cmd/patchmon-agent/commands/root.go @@ -57,6 +57,30 @@ func init() { rootCmd.AddCommand(updateAgentCmd) rootCmd.AddCommand(diagnosticsCmd) rootCmd.AddCommand(uninstallCmd) + rootCmd.AddCommand(ansiblePlaybookCmd) + + // Add ansible-playbook flag for --ansible-playbook syntax + rootCmd.PersistentFlags().String("ansible-playbook", "", "Execute an Ansible playbook") + + // Handle --ansible-playbook flag at root level + rootCmd.RunE = func(cmd *cobra.Command, args []string) error { + if playbookPath, _ := cmd.Flags().GetString("ansible-playbook"); playbookPath != "" { + // Parse extra vars for root flag usage + extraVarsArray, _ := cmd.Flags().GetStringArray("extra-vars") + extraVarsJSON, _ := cmd.Flags().GetString("extra-vars-json") + extraVars, err := parseExtraVars(extraVarsArray, extraVarsJSON) + if err != nil { + return fmt.Errorf("failed to parse extra vars: %w", err) + } + return runAnsiblePlaybook(playbookPath, extraVars) + } + // If no flag and no subcommand, show help + return cmd.Help() + } + + // Add extra-vars flags to root for --ansible-playbook usage + rootCmd.PersistentFlags().StringArrayP("extra-vars", "e", []string{}, "Extra variables as key=value pairs (can be specified multiple times)") + rootCmd.PersistentFlags().String("extra-vars-json", "", "Extra variables as JSON string") } // initialiseAgent initialises the configuration manager and logger @@ -131,4 +155,3 @@ func checkRoot() error { } return nil } - diff --git a/go.mod b/go.mod index 390fd8e..f5b63aa 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module patchmon-agent go 1.25 require ( + github.com/apenella/go-ansible/v2 v2.4.1 github.com/docker/docker v28.5.1+incompatible github.com/go-resty/resty/v2 v2.16.5 github.com/gorilla/websocket v1.5.3 @@ -16,6 +17,8 @@ require ( require ( github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/apenella/go-common-utils/data v0.0.0-20220913191136-86daaa87e7df // indirect + github.com/apenella/go-common-utils/error v0.0.0-20220913191136-86daaa87e7df // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect @@ -46,6 +49,7 @@ require ( github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect + github.com/stretchr/objx v0.5.3 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/numcpus v0.10.0 // indirect @@ -57,9 +61,11 @@ require ( go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.44.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/net v0.46.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/text v0.30.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.2 // indirect ) diff --git a/go.sum b/go.sum index c6655a6..c1432ca 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,12 @@ github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEK github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/apenella/go-ansible/v2 v2.4.1 h1:bepwxCNIVg3185dKmb60jhNhgvVWvVpICCnGHEF9MQc= +github.com/apenella/go-ansible/v2 v2.4.1/go.mod h1:C9u9Srpd/PEjv+r4lLYDFc1s9ebK8rBY/DWmnZ/ohXo= +github.com/apenella/go-common-utils/data v0.0.0-20220913191136-86daaa87e7df h1:sEikY2P+NZK/7VZUwIsnXIGElhsuFDSxh1bZYwHxdcI= +github.com/apenella/go-common-utils/data v0.0.0-20220913191136-86daaa87e7df/go.mod h1:cLVL6GjUiKG/WyBzX+KD6h/XRV/HnNZIZbMNNiBgQ9o= +github.com/apenella/go-common-utils/error v0.0.0-20220913191136-86daaa87e7df h1:SvlYbjlsSQDS7hbVT1h012/zdgvcwWJ+Yd9XRiiY/8s= +github.com/apenella/go-common-utils/error v0.0.0-20220913191136-86daaa87e7df/go.mod h1:+3dyIlHX350xJIUIffwMLswZXU+N2FwDE05VuKqxYdw= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= @@ -30,6 +36,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= +github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -89,6 +97,8 @@ github.com/shirou/gopsutil/v4 v4.25.9 h1:JImNpf6gCVhKgZhtaAHJ0serfFGtlfIlSC08eaK github.com/shirou/gopsutil/v4 v4.25.9/go.mod h1:gxIxoC+7nQRwUl/xNhutXlD8lq+jxTgpIkEf3rADHL8= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sosedoff/ansible-vault-go v0.2.0 h1:XqkBdqbXgTuFQ++NdrZvSdUTNozeb6S3V5x7FVs17vg= +github.com/sosedoff/ansible-vault-go v0.2.0/go.mod h1:wMU54HNJfY0n0KIgbpA9m15NBfaUDlJrAsaZp0FwzkI= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= @@ -101,6 +111,8 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= @@ -134,15 +146,19 @@ go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOV go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= -golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -159,6 +175,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=