diff --git a/.github/workflows/cli-output-compatibility.yml b/.github/workflows/cli-output-compatibility.yml index a810dd44..21615ca4 100644 --- a/.github/workflows/cli-output-compatibility.yml +++ b/.github/workflows/cli-output-compatibility.yml @@ -7,6 +7,9 @@ on: branches: [main] workflow_dispatch: +env: + GOPRIVATE: "github.com/brevdev/*" + jobs: cli-output-compatibility: runs-on: ubuntu-22.04 @@ -15,18 +18,17 @@ jobs: - name: Checkout code uses: actions/checkout@v2 - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.22.6' - cache: true - - - name: Install dependencies - run: go mod download + - name: Configure git for private modules + env: + TOKEN: ${{ secrets.GH_TOKEN }} + run: git config --global url."https://${TOKEN}@github.com".insteadOf "https://github.com" + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod - name: Run CLI output compatibility tests run: go test -v ./pkg/integration/ - - name: Report test results if: failure() run: | diff --git a/.github/workflows/legacy.yml b/.github/workflows/legacy.yml index 4f1a9c14..2445b1a3 100644 --- a/.github/workflows/legacy.yml +++ b/.github/workflows/legacy.yml @@ -7,6 +7,9 @@ on: branches: [main] workflow_dispatch: +env: + GOPRIVATE: "github.com/brevdev/*" + jobs: ci: strategy: @@ -20,6 +23,14 @@ jobs: steps: - uses: actions/checkout@v2 + - name: Configure git for private modules + env: + TOKEN: ${{ secrets.GH_TOKEN }} + run: git config --global url."https://${TOKEN}@github.com".insteadOf "https://github.com" + + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod - uses: actions/setup-go@v5 with: go-version: '1.22.6' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e5310ce0..27cfef97 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -24,10 +24,10 @@ jobs: with: go-version: '1.22.6' cache: true - - name: install - run: make install-tools - - name: lint - run: make lint + - name: golangci-lint + uses: golangci/golangci-lint-action@v8 + with: + version: v2.7.2 # - name: Report Status # if: always() # uses: ravsamhq/notify-slack-action@v1 diff --git a/.gitignore b/.gitignore index 8f1541e0..1276cd3d 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,7 @@ dist/ # binary brev-cli brev +brev-local # golang executable go1.* diff --git a/.golangci.bck.yml b/.golangci.bck.yml new file mode 100644 index 00000000..09b12371 --- /dev/null +++ b/.golangci.bck.yml @@ -0,0 +1,102 @@ +linters-settings: + goimports: + local-prefixes: github.com/brevdev/brev-cli + revive: + min-confidence: 0.8 + rules: + - name: blank-imports + - name: context-as-argument + - name: context-as-argument + - name: context-keys-type + - name: dot-imports + - name: error-return + - name: error-strings + - name: error-naming + - name: if-return + - name: increment-decrement + - name: var-naming + - name: var-declaration + - name: package-comments + - name: range + - name: receiver-naming + - name: time-naming + - name: unexported-return + - name: errorf + - name: empty-block + - name: superfluous-else + - name: unused-parameter + - name: unreachable-code + - name: redefines-builtin-id + gocyclo: + min-complexity: 16 + govet: + check-shadowing: true + misspell: + locale: US + nolintlint: + allow-leading-space: false # require machine-readable nolint directives (with no leading space) + allow-unused: false # report any unused nolint directives + require-explanation: true # require an explanation for nolint directives + require-specific: false # don't require nolint directives to be specific about which linter is being skipped + funlen: + lines: 100 + wrapcheck: + ignoreSigs: + - .WrapAndTrace + - .Errorf + - .Wrap + - .New + stylecheck: + checks: ["all", "-ST1020", "-ST1000"] + +run: + build-tags: + - codeanalysis + +linters: + # please, do not use `enable-all`: it's deprecated and will be removed soon. + # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint + disable-all: true + enable: + - errcheck + - gosimple + - govet + - ineffassign + # - staticcheck + - typecheck + - unused + - bodyclose + # - depguard + - dupl + - exportloopref + - forcetypeassert + - funlen + # - gci + - gocognit + # - goconst + - gocritic + - gocyclo + # - godot + - gofumpt + # - revive + # - gomnd + - goprintffuncname + - gosec + # - ifshort + - misspell + - noctx + - nolintlint + - rowserrcheck + - sqlclosecheck + - stylecheck + - thelper + - tparallel + - unconvert + - unparam + # - whitespace + # - errorlint + # - goerr113 + - wrapcheck +issues: + # enable issues excluded by default + exclude-use-default: false diff --git a/.golangci.yml b/.golangci.yml index 09b12371..11ed8dc9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,102 +1,113 @@ -linters-settings: - goimports: - local-prefixes: github.com/brevdev/brev-cli - revive: - min-confidence: 0.8 - rules: - - name: blank-imports - - name: context-as-argument - - name: context-as-argument - - name: context-keys-type - - name: dot-imports - - name: error-return - - name: error-strings - - name: error-naming - - name: if-return - - name: increment-decrement - - name: var-naming - - name: var-declaration - - name: package-comments - - name: range - - name: receiver-naming - - name: time-naming - - name: unexported-return - - name: errorf - - name: empty-block - - name: superfluous-else - - name: unused-parameter - - name: unreachable-code - - name: redefines-builtin-id - gocyclo: - min-complexity: 16 - govet: - check-shadowing: true - misspell: - locale: US - nolintlint: - allow-leading-space: false # require machine-readable nolint directives (with no leading space) - allow-unused: false # report any unused nolint directives - require-explanation: true # require an explanation for nolint directives - require-specific: false # don't require nolint directives to be specific about which linter is being skipped - funlen: - lines: 100 - wrapcheck: - ignoreSigs: - - .WrapAndTrace - - .Errorf - - .Wrap - - .New - stylecheck: - checks: ["all", "-ST1020", "-ST1000"] - +version: "2" run: build-tags: - codeanalysis - linters: - # please, do not use `enable-all`: it's deprecated and will be removed soon. - # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint - disable-all: true + default: none enable: - - errcheck - - gosimple - - govet - - ineffassign - # - staticcheck - - typecheck - - unused - bodyclose - # - depguard + - copyloopvar - dupl - - exportloopref + - errcheck - forcetypeassert - funlen - # - gci - gocognit - # - goconst + - goconst - gocritic - gocyclo - # - godot - - gofumpt - # - revive - # - gomnd - goprintffuncname - gosec - # - ifshort + - govet + - ineffassign - misspell - noctx - nolintlint + - revive - rowserrcheck - sqlclosecheck - - stylecheck - - thelper + - staticcheck - tparallel - unconvert - unparam - # - whitespace - # - errorlint - # - goerr113 + - unused + - whitespace - wrapcheck + settings: + errcheck: + exclude-functions: + - (*encoding/json.Encoder).Encode + funlen: + lines: 100 + gocyclo: + min-complexity: 15 + govet: + check-shadowing: true + misspell: + locale: US + nolintlint: + allow-leading-space: false # require machine-readable nolint directives (with no leading space) + require-explanation: true + require-specific: false + allow-unused: false + revive: + rules: + - name: blank-imports + - name: context-as-argument + - name: context-as-argument + - name: context-keys-type + - name: dot-imports + - name: error-return + - name: error-strings + - name: error-naming + - name: if-return + - name: increment-decrement + - name: var-naming + - name: var-declaration + - name: range + - name: receiver-naming + - name: time-naming + - name: unexported-return + - name: errorf + - name: empty-block + - name: superfluous-else + - name: unused-parameter + - name: unreachable-code + - name: redefines-builtin-id + staticcheck: + checks: + - all + - -ST1020 # https://staticcheck.dev/docs/checks#ST1020 - ignore exported fuction doc style + - -ST1021 # https://staticcheck.dev/docs/checks#ST1021 - ignore forced exported type doc style + - -S1016 # https://staticcheck.dev/docs/checks/#S1016 - ignore as explicit field copy is preferred + - -QF1008 # https://staticcheck.dev/docs/checks/#QF1008 - ignore as explicit use of embedded fields is preferred + - -QF1001 # https://staticcheck.dev/docs/checks/#QF1001 - De Morgan's law (use of "!a && !b" over "!(a || b)") is typically preferred, but this is always case by case + wrapcheck: + ignore-sigs: + - .WrapAndTrace + - .Errorf + - .Wrap + - .New + - .ValidateStruct + - .Permanent + - .Decode issues: - # enable issues excluded by default exclude-use-default: false + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofumpt + settings: + goimports: + local-prefixes: + - github.com/brevdev/brev-cli + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/Makefile b/Makefile index f459f671..7e04490d 100644 --- a/Makefile +++ b/Makefile @@ -14,15 +14,15 @@ ifdef env @echo "Building with env=$(env) wrapper..." @echo ${VERSION} CGO_ENABLED=0 go build -o brev -ldflags "-X github.com/brevdev/brev-cli/pkg/cmd/version.Version=${VERSION}" - @echo '#!/bin/sh' > brev - @echo '# Auto-generated wrapper with environment overrides' >> brev - @echo 'export BREV_CONSOLE_URL="https://localhost.nvidia.com:3000"' >> brev - @echo 'export BREV_AUTH_URL="https://api.stg.ngc.nvidia.com"' >> brev - @echo 'export BREV_AUTH_ISSUER_URL="https://stg.login.nvidia.com"' >> brev - @echo 'export BREV_API_URL="https://bd.$(env).brev.nvidia.com"' >> brev - @echo 'export BREV_GRPC_URL="api.$(env).brev.nvidia.com:443"' >> brev - @echo 'exec "$$(cd "$$(dirname "$$0")" && pwd)/brev" "$$@"' >> brev - @chmod +x brev + @echo '#!/bin/sh' > brev-local + @echo '# Auto-generated wrapper with environment overrides' >> brev-local + @echo 'export BREV_CONSOLE_URL="https://localhost.nvidia.com:3000"' >> brev-local + @echo 'export BREV_AUTH_URL="https://api.stg.ngc.nvidia.com"' >> brev-local + @echo 'export BREV_AUTH_ISSUER_URL="https://stg.login.nvidia.com"' >> brev-local + @echo 'export BREV_API_URL="https://bd.$(env).brev.nvidia.com"' >> brev-local + @echo 'export BREV_GRPC_URL="api.$(env).brev.nvidia.com:443"' >> brev-local + @echo 'exec "$$(cd "$$(dirname "$$0")" && pwd)/brev" "$$@"' >> brev-local + @chmod +x brev-local else @echo "Building without environment overrides (using config.go defaults)..." $(MAKE) fast-build @@ -54,6 +54,9 @@ clean: ## remove files created during build pipeline install-tools: ## go install tools $(call print-target) cd tools && go install $(shell cd tools && go list -e -f '{{ join .Imports " " }}' -tags=tools) + @if [ "$(shell uname)" = "Darwin" ] && ! command -v golangci-lint >/dev/null 2>&1; then \ + (echo "Installing golangci-lint on macOS via homebrew..." && brew install golangci-lint); \ + fi; .PHONY: generate generate: ## go generate @@ -78,6 +81,7 @@ fmtcheck: ## go fmt --check .PHONY: lint lint: ## golangci-lint $(call print-target) + golangci-lint --version golangci-lint run --timeout 5m .PHONY: test @@ -176,7 +180,6 @@ build-darwin-amd: echo ${VERSION} GOOS=darwin GOARCH=amd64 go build -o brev -ldflags "-X github.com/brevdev/brev-cli/pkg/cmd/version.Version=${VERSION}" - .PHONY: setup-workspace-repo setup-workspace-repo: build-linux-amd make setup-workspace setup_param_path=assets/test_setup_v0_repo.json diff --git a/byon.env b/byon.env new file mode 100644 index 00000000..baa9160a --- /dev/null +++ b/byon.env @@ -0,0 +1,4 @@ +BREV_AUTH_URL=https://api.stg.ngc.nvidia.com +BREV_AUTH_ISSUER_URL=https://stg.login.nvidia.com +BREV_API_URL=https://bd.dev2.brev.nvidia.com +BREV_GRPC_URL=api.dev2.brev.nvidia.com:443 \ No newline at end of file diff --git a/go.mod b/go.mod index d880d282..3970cc53 100644 --- a/go.mod +++ b/go.mod @@ -1,52 +1,59 @@ module github.com/brevdev/brev-cli -go 1.22.6 +go 1.25.1 require ( + buf.build/gen/go/brevdev/devplane/connectrpc/go v1.19.1-20251231160605-b3cca76916ad.2 + buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.11-20251231160605-b3cca76916ad.1 + connectrpc.com/connect v1.19.1 github.com/alessio/shellescape v1.4.1 + github.com/brevdev/dev-plane v0.5.1677-0.20260107215826-35134bf68ae9 github.com/brevdev/parse v0.0.11 github.com/briandowns/spinner v1.16.0 - github.com/fatih/color v1.13.0 + github.com/fatih/color v1.16.0 github.com/getsentry/sentry-go v0.14.0 github.com/gin-gonic/gin v1.10.0 github.com/go-git/go-git/v5 v5.13.2 github.com/go-resty/resty/v2 v2.7.0 github.com/golang-jwt/jwt v3.2.2+incompatible - github.com/google/go-cmp v0.6.0 + github.com/google/go-cmp v0.7.0 github.com/google/huproxy v0.0.0-20210816191033-a131ee126ce3 github.com/google/uuid v1.6.0 - github.com/gorilla/websocket v1.5.0 + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 github.com/hashicorp/go-multierror v1.1.1 - github.com/hashicorp/go-version v1.4.0 - github.com/jarcoal/httpmock v1.0.8 + github.com/hashicorp/go-version v1.7.0 + github.com/jarcoal/httpmock v1.4.0 github.com/jinzhu/copier v0.4.0 github.com/kevinburke/ssh_config v1.2.0 github.com/manifoldco/promptui v0.9.0 - github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/pkg/errors v0.9.1 github.com/robfig/cron/v3 v3.0.1 - github.com/samber/lo v1.33.0 + github.com/samber/lo v1.39.0 github.com/samber/mo v1.5.1 github.com/schollz/progressbar/v3 v3.9.0 github.com/sevlyar/go-daemon v0.1.5 - github.com/sirupsen/logrus v1.9.0 - github.com/spf13/afero v1.9.2 + github.com/sirupsen/logrus v1.9.3 + github.com/spf13/afero v1.15.0 github.com/spf13/cobra v1.8.1 - github.com/spf13/viper v1.13.0 - github.com/stretchr/testify v1.10.0 - github.com/tidwall/gjson v1.14.0 + github.com/spf13/viper v1.18.2 + github.com/stretchr/testify v1.11.1 + github.com/tidwall/gjson v1.18.0 github.com/tweekmonster/luser v0.0.0-20161003172636-3fa38070dbd7 github.com/wk8/go-ordered-map/v2 v2.0.0 github.com/writeas/go-strip-markdown v2.0.1+incompatible - golang.org/x/crypto v0.32.0 - golang.org/x/text v0.21.0 + go.uber.org/zap v1.27.0 + golang.org/x/crypto v0.42.0 + golang.org/x/sync v0.17.0 + golang.org/x/text v0.29.0 k8s.io/cli-runtime v0.31.1 ) require ( + buf.build/gen/go/brevdev/protoc-gen-gotag/protocolbuffers/go v1.36.11-20220906235457-8b4922735da5.1 // indirect dario.cat/mergo v1.0.0 // indirect - github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.1.5 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect @@ -56,106 +63,117 @@ require ( github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/cyphar/filepath-securejoin v0.3.6 // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect - github.com/fsnotify/fsnotify v1.5.4 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-openapi/swag/cmdutils v0.25.1 // indirect + github.com/go-openapi/swag/conv v0.25.1 // indirect + github.com/go-openapi/swag/fileutils v0.25.1 // indirect + github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/swag/jsonutils v0.25.1 // indirect + github.com/go-openapi/swag/loading v0.25.1 // indirect + github.com/go-openapi/swag/mangling v0.25.1 // indirect + github.com/go-openapi/swag/netutils v0.25.1 // indirect + github.com/go-openapi/swag/stringutils v0.25.1 // indirect + github.com/go-openapi/swag/typeutils v0.25.1 // indirect + github.com/go-openapi/swag/yamlutils v0.25.1 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect - github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/google/gnostic-models v0.6.8 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/hashicorp/hcl v1.0.1-vault-5 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/magiconair/properties v1.8.6 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect github.com/moby/term v0.5.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pjbgf/sha1cd v0.3.2 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/skeema/knownhosts v1.3.0 // indirect - github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/subosito/gotenv v1.4.1 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/tidwall/match v1.1.1 // indirect - github.com/tidwall/pretty v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/arch v0.8.0 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/tools v0.23.0 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect - k8s.io/apimachinery v0.31.1 // indirect - k8s.io/client-go v0.31.1 // indirect - sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + k8s.io/apimachinery v0.34.1 // indirect + k8s.io/client-go v0.34.1 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect ) require ( - github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect + github.com/chzyer/readline v1.5.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/go-errors/errors v1.4.2 // indirect - github.com/go-logr/logr v1.4.2 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.4 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/google/btree v1.0.1 // indirect - github.com/google/gofuzz v1.2.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-openapi/jsonpointer v0.22.1 // indirect + github.com/go-openapi/jsonreference v0.21.2 // indirect + github.com/go-openapi/swag v0.25.1 // indirect + github.com/gogo/protobuf v1.3.3 // indirect + github.com/google/btree v1.1.3 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/imdario/mergo v0.3.13 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jedib0t/go-pretty/v6 v6.3.1 - github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/rivo/uniseg v0.2.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/spf13/pflag v1.0.10 // indirect github.com/xlab/treeprint v1.2.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/term v0.28.0 // indirect - golang.org/x/time v0.3.0 // indirect - google.golang.org/protobuf v1.34.2 + golang.org/x/net v0.44.0 // indirect + golang.org/x/oauth2 v0.31.0 // indirect + golang.org/x/sys v0.36.0 + golang.org/x/term v0.35.0 // indirect + golang.org/x/time v0.13.0 // indirect + google.golang.org/protobuf v1.36.11 gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.31.1 // indirect + k8s.io/api v0.34.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect - k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect + k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect + k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect sigs.k8s.io/kustomize/api v0.17.2 // indirect sigs.k8s.io/kustomize/kyaml v0.17.1 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect ) + +replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 diff --git a/go.sum b/go.sum index 0baa56c5..a64b590b 100644 --- a/go.sum +++ b/go.sum @@ -1,50 +1,20 @@ +buf.build/gen/go/brevdev/devplane/connectrpc/go v1.19.1-20251231160605-b3cca76916ad.2 h1:0kN/kFTB+1FwQKYfRmclNov3zl2l6piRWsLIvxI0MNg= +buf.build/gen/go/brevdev/devplane/connectrpc/go v1.19.1-20251231160605-b3cca76916ad.2/go.mod h1:RmMcZfWXsOjdUZZ2WoT2PuhhrLYcHbPYguosSqJ5498= +buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.11-20251231160605-b3cca76916ad.1 h1:XQiAbF+9b+yRXr5oUIUYCJ7/drS3hjd3/BpCUO04hOI= +buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.11-20251231160605-b3cca76916ad.1/go.mod h1:V/y7Wxg0QvU4XPVwqErF5NHLobUT1QEyfgrGuQIxdPo= +buf.build/gen/go/brevdev/protoc-gen-gotag/protocolbuffers/go v1.36.11-20220906235457-8b4922735da5.1 h1:6amhprQmCKJ4wgJ6ngkh32d9V+dQcOLUZ/SfHdOnYgo= +buf.build/gen/go/brevdev/protoc-gen-gotag/protocolbuffers/go v1.36.11-20220906235457-8b4922735da5.1/go.mod h1:O+pnSHMru/naTMrm4tmpBoH3wz6PHa+R75HR7Mv8X2g= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14= +connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4= github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= @@ -57,6 +27,8 @@ github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPn github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/brevdev/dev-plane v0.5.1677-0.20260107215826-35134bf68ae9 h1:/yQ32N60xT7eusfDqRnof/alnxzXkqUAKrbrjeHdE3s= +github.com/brevdev/dev-plane v0.5.1677-0.20260107215826-35134bf68ae9/go.mod h1:diuBUR26OgEg588+WZwLlYKn+qdu+ZPmRdV8n5C7fhw= github.com/brevdev/parse v0.0.11 h1:OamoC1hKFW75ngzSQx9HHRh5bf/G6154Y9M2y4HNmIw= github.com/brevdev/parse v0.0.11/go.mod h1:ML13fBCP6yZsZearRnglD+6UlqkpiVN7Hjf8R9pd0TY= github.com/briandowns/spinner v1.16.0 h1:DFmp6hEaIx2QXXuqSJmtfSBSAjRmpGiKG6ip2Wm/yOs= @@ -66,12 +38,15 @@ github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1 github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= +github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= @@ -80,12 +55,9 @@ github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJ github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/creack/pty v1.1.20 h1:VIPb/a2s17qNeQgDnkfZC35RScx+blkKF8GV68n80J4= +github.com/creack/pty v1.1.20/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -94,25 +66,23 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM= github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +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/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/getsentry/sentry-go v0.14.0 h1:rlOBkuFZRKKdUnKO+0U3JclRDQKlRu5vVQtkWSQvC70= @@ -133,18 +103,38 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0= github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= -github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= +github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= +github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= +github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= +github.com/go-openapi/swag v0.25.1 h1:6uwVsx+/OuvFVPqfQmOOPsqTcm5/GkBhNwLqIR916n8= +github.com/go-openapi/swag v0.25.1/go.mod h1:bzONdGlT0fkStgGPd3bhZf1MnuPkf2YAys6h+jZipOo= +github.com/go-openapi/swag/cmdutils v0.25.1 h1:nDke3nAFDArAa631aitksFGj2omusks88GF1VwdYqPY= +github.com/go-openapi/swag/cmdutils v0.25.1/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.1 h1:+9o8YUg6QuqqBM5X6rYL/p1dpWeZRhoIt9x7CCP+he0= +github.com/go-openapi/swag/conv v0.25.1/go.mod h1:Z1mFEGPfyIKPu0806khI3zF+/EUXde+fdeksUl2NiDs= +github.com/go-openapi/swag/fileutils v0.25.1 h1:rSRXapjQequt7kqalKXdcpIegIShhTPXx7yw0kek2uU= +github.com/go-openapi/swag/fileutils v0.25.1/go.mod h1:+NXtt5xNZZqmpIpjqcujqojGFek9/w55b3ecmOdtg8M= +github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= +github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/swag/jsonutils v0.25.1 h1:AihLHaD0brrkJoMqEZOBNzTLnk81Kg9cWr+SPtxtgl8= +github.com/go-openapi/swag/jsonutils v0.25.1/go.mod h1:JpEkAjxQXpiaHmRO04N1zE4qbUEg3b7Udll7AMGTNOo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1 h1:DSQGcdB6G0N9c/KhtpYc71PzzGEIc/fZ1no35x4/XBY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1/go.mod h1:kjmweouyPwRUEYMSrbAidoLMGeJ5p6zdHi9BgZiqmsg= +github.com/go-openapi/swag/loading v0.25.1 h1:6OruqzjWoJyanZOim58iG2vj934TysYVptyaoXS24kw= +github.com/go-openapi/swag/loading v0.25.1/go.mod h1:xoIe2EG32NOYYbqxvXgPzne989bWvSNoWoyQVWEZicc= +github.com/go-openapi/swag/mangling v0.25.1 h1:XzILnLzhZPZNtmxKaz/2xIGPQsBsvmCjrJOWGNz/ync= +github.com/go-openapi/swag/mangling v0.25.1/go.mod h1:CdiMQ6pnfAgyQGSOIYnZkXvqhnnwOn997uXZMAd/7mQ= +github.com/go-openapi/swag/netutils v0.25.1 h1:2wFLYahe40tDUHfKT1GRC4rfa5T1B4GWZ+msEFA4Fl4= +github.com/go-openapi/swag/netutils v0.25.1/go.mod h1:CAkkvqnUJX8NV96tNhEQvKz8SQo2KF0f7LleiJwIeRE= +github.com/go-openapi/swag/stringutils v0.25.1 h1:Xasqgjvk30eUe8VKdmyzKtjkVjeiXx1Iz0zDfMNpPbw= +github.com/go-openapi/swag/stringutils v0.25.1/go.mod h1:JLdSAq5169HaiDUbTvArA2yQxmgn4D6h4A+4HqVvAYg= +github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3I3ysiFZqukA= +github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8= +github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk= +github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -157,30 +147,17 @@ github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPr github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -188,61 +165,31 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= -github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/huproxy v0.0.0-20210816191033-a131ee126ce3 h1:lzR4a91Howb3ff79Yzx7Jc5VUQvNy1Zj++gMEJDthjc= github.com/google/huproxy v0.0.0-20210816191033-a131ee126ce3/go.mod h1:KCcN3eAQJJnGsoUnICBk7xRLt2zOHpuD8+m3Co5Ydfg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= -github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -250,32 +197,22 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-version v1.4.0 h1:aAQzgqIrRKRa7w75CKpbBxYsmUoPjzVm1W59ca1L0J4= -github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= -github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= +github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jarcoal/httpmock v1.0.8 h1:8kI16SoO6LQKgPE7PvQuV+YuD/inwHd7fOOe2zMbo4k= -github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= +github.com/jarcoal/httpmock v1.4.0 h1:BvhqnH0JAYbNudL2GMJKgOHe2CtKlzJ/5rWKyp+hc2k= +github.com/jarcoal/httpmock v1.4.0/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jedib0t/go-pretty/v6 v6.3.1 h1:aOXiD9oqiuLH8btPQW6SfgtQN5zwhyfzZls8a6sPJ/I= github.com/jedib0t/go-pretty/v6 v6.3.1/go.mod h1:FMkOpgGD3EZ91cW8g/96RfxoV7bdeJyzXPYgz1L1ln0= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= @@ -287,9 +224,7 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02 github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -300,45 +235,42 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/maxatome/go-testdeep v1.14.0 h1:rRlLv1+kI8eOI3OaBXZwb3O7xY3exRzdW5QyX48g9wI= +github.com/maxatome/go-testdeep v1.14.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= -github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= -github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= -github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= +github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= @@ -347,26 +279,31 @@ github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/regen-network/protobuf v1.3.3-alpha.regen.1 h1:OHEc+q5iIAXpqiqFKeLpu5NwTIkVXUs48vFMwzqpqY4= +github.com/regen-network/protobuf v1.3.3-alpha.regen.1/go.mod h1:2DjTFR1HhMQhiWC5sZ4OhQ3+NtdbZ6oBDKQwq5Ou+FI= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/samber/lo v1.33.0 h1:2aKucr+rQV6gHpY3bpeZu69uYoQOzVhGT3J22Op6Cjk= -github.com/samber/lo v1.33.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= +github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/samber/mo v1.5.1 h1:5dRSevAB33Q/OrYwTmtksHHxquuf2urnRSUTsdTFysY= github.com/samber/mo v1.5.1/go.mod h1:pDuQgWscOVGGoEz+NAeth/Xq+MPAcXxCeph1XIAm/DU= github.com/schollz/progressbar/v3 v3.9.0 h1:k9SRNQ8KZyibz1UZOaKxnkUE3iGtmGSDt1YY9KlCYQk= @@ -377,22 +314,23 @@ github.com/sevlyar/go-daemon v0.1.5 h1:Zy/6jLbM8CfqJ4x4RPr7MJlSKt90f00kNM1D401C+ github.com/sevlyar/go-daemon v0.1.5/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= -github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= -github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +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.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU= -github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -401,25 +339,23 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= -github.com/thoas/go-funk v0.9.1/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= -github.com/tidwall/gjson v1.14.0 h1:6aeJ0bzojgWLa82gDQHcx3S0Lr/O51I9bJ5nv6JFx5w= -github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tweekmonster/luser v0.0.0-20161003172636-3fa38070dbd7 h1:X9dsIWPuuEJlPX//UmRKophhOKCGXc46RVIGuttks68= github.com/tweekmonster/luser v0.0.0-20161003172636-3fa38070dbd7/go.mod h1:UxoP3EypF8JfGEjAII8jx1q8rQyDnX8qdTCs/UQBVIE= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= @@ -436,353 +372,138 @@ github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +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/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0= +golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +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/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo= +golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +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-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= +golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= +golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200324203455-a04cca1dde73/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= @@ -790,46 +511,38 @@ gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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.0/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= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= -k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= -k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= -k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= +k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= +k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= k8s.io/cli-runtime v0.31.1 h1:/ZmKhmZ6hNqDM+yf9s3Y4KEYakNXUn5sod2LWGGwCuk= k8s.io/cli-runtime v0.31.1/go.mod h1:pKv1cDIaq7ehWGuXQ+A//1OIF+7DI+xudXtExMCbe9U= -k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= -k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg= +k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= +k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= -k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= -k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/api v0.17.2 h1:E7/Fjk7V5fboiuijoZHgs4aHuexi5Y2loXlVOAVAG5g= sigs.k8s.io/kustomize/api v0.17.2/go.mod h1:UWTz9Ct+MvoeQsHcJ5e+vziRRkwimm3HytpZgIYqye0= sigs.k8s.io/kustomize/kyaml v0.17.1 h1:TnxYQxFXzbmNG6gOINgGWQt09GghzgTP6mIurOgrLCQ= sigs.k8s.io/kustomize/kyaml v0.17.1/go.mod h1:9V0mCjIEYjlXuCdYsSXvyoy2BTsLESH7TlGV81S282U= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/pkg/brevcloud/client.go b/pkg/brevcloud/client.go new file mode 100644 index 00000000..b2c64f15 --- /dev/null +++ b/pkg/brevcloud/client.go @@ -0,0 +1,228 @@ +package brevcloud + +import ( + "context" + "net/http" + "strings" + "time" + + "connectrpc.com/connect" + + brevapiv2connect "buf.build/gen/go/brevdev/devplane/connectrpc/go/brevapi/v2/brevapiv2connect" + devplaneapiv1connect "buf.build/gen/go/brevdev/devplane/connectrpc/go/devplaneapi/v1/devplaneapiv1connect" + brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" + devplaneapiv1 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/devplaneapi/v1" + "github.com/brevdev/brev-cli/pkg/config" + breverrors "github.com/brevdev/brev-cli/pkg/errors" + "github.com/brevdev/brev-cli/pkg/store" +) + +// Client wraps brevapiv2 registration/inspection APIs for Spark flows. +// +// It intentionally exposes small, CLI-focused types instead of raw protos. +type Client struct { + operator brevapiv2connect.BrevCloudOperatorServiceClient + cloudCreds devplaneapiv1connect.CloudCredServiceClient + store *store.AuthHTTPStore +} + +// NewClient constructs a BrevCloud client backed by brevapiv2.BrevCloudOperatorService. +// +// Authentication is delegated to the provided AuthHTTPStore; a fresh access +// token is fetched for each request. +// NewClient constructs a BrevCloud client backed by brevapiv2.BrevCloudOperatorService. +// +// Authentication is delegated to the provided AuthHTTPStore; a fresh access +// token is fetched for each request. +func NewClient(s *store.AuthHTTPStore) *Client { + if s == nil { + return &Client{} + } + + baseURL := config.GlobalConfig.GetDevplaneAPIURL() + httpClient := &authHTTPClient{ + store: s, + base: http.DefaultClient, + } + + operator := brevapiv2connect.NewBrevCloudOperatorServiceClient(httpClient, baseURL) + + return &Client{ + operator: operator, + store: s, + } +} + +// authHTTPClient adapts AuthHTTPStore to connect.HTTPClient by injecting +// a Bearer token on each request. It mimics the OnBeforeRequest behavior +// used by the Resty client in AuthHTTPClient. +type authHTTPClient struct { + store *store.AuthHTTPStore + base *http.Client +} + +func (c *authHTTPClient) Do(req *http.Request) (*http.Response, error) { + if c.store == nil || c.base == nil { + return nil, breverrors.New("HTTP client not initialized") + } + + tokens, err := c.store.GetAuthTokens() + if err == nil && tokens != nil && tokens.AccessToken != "" { + req.Header.Set("Authorization", "Bearer "+tokens.AccessToken) + } + + return c.base.Do(req) +} + +// CreateRegistrationIntentRequest is the CLI-friendly payload for minting a +// single-use registration token for a BrevCloud node. +type CreateRegistrationIntentRequest struct { + CloudCredID string + OrgID string +} + +// CreateRegistrationIntentResponse is the subset of fields needed by Spark +// enroll flows. +type CreateRegistrationIntentResponse struct { + BrevCloudNodeID string + CloudCredID string + RegistrationToken string + ExpiresAt string +} + +// CreateRegistrationIntent calls brevapiv2.BrevCloudOperatorService.CreateRegistrationIntent. +func (c *Client) CreateRegistrationIntent(ctx context.Context, req CreateRegistrationIntentRequest) (*CreateRegistrationIntentResponse, error) { + if c == nil || c.operator == nil { + return nil, breverrors.New("operator client not initialized") + } + + pbReq := &brevapiv2.CreateRegistrationIntentRequest{ + CloudCredId: req.CloudCredID, + OrgId: req.OrgID, + } + + resp, err := c.operator.CreateRegistrationIntent(ctx, connect.NewRequest(pbReq)) + if err != nil { + return nil, breverrors.WrapAndTrace(err) + } + + msg := resp.Msg + out := &CreateRegistrationIntentResponse{ + BrevCloudNodeID: msg.GetBrevCloudNodeId(), + CloudCredID: msg.GetCloudCredId(), + RegistrationToken: msg.GetRegistrationToken(), + } + if ts := msg.GetExpiresAt(); ts != nil { + out.ExpiresAt = ts.AsTime().UTC().Format(time.RFC3339) + } + return out, nil +} + +// BrevCloudNode is a minimal view of a BrevCloud node suitable for status +// polling and user-facing output. +type BrevCloudNode struct { + ID string + CloudCredID string + DisplayName string + CloudName string + FirstSeenAt string + LastSeenAt string + Phase string + AgentVersion string +} + +// GetBrevCloudNode fetches node metadata via brevapiv2.BrevCloudOperatorService.GetBrevCloudNode. +func (c *Client) GetBrevCloudNode(ctx context.Context, brevCloudNodeID string) (*BrevCloudNode, error) { + if c == nil || c.operator == nil { + return nil, breverrors.New("operator client not initialized") + } + if strings.TrimSpace(brevCloudNodeID) == "" { + return nil, breverrors.New("brev cloud node id is required") + } + + pbReq := &brevapiv2.GetBrevCloudNodeRequest{ + BrevCloudNodeId: brevCloudNodeID, + } + + resp, err := c.operator.GetBrevCloudNode(ctx, connect.NewRequest(pbReq)) + if err != nil { + return nil, breverrors.WrapAndTrace(err) + } + + msg := resp.Msg + node := &BrevCloudNode{ + ID: msg.GetBrevCloudNodeId(), + CloudCredID: msg.GetCloudCredId(), + DisplayName: msg.GetDisplayName(), + CloudName: msg.GetCloudName(), + AgentVersion: msg.GetAgentVersion(), + Phase: mapPhase(msg.GetPhase()), + } + + if ts := msg.GetFirstSeenAt(); ts != nil { + node.FirstSeenAt = ts.AsTime().UTC().Format(time.RFC3339) + } + if ts := msg.GetLastSeenAt(); ts != nil { + node.LastSeenAt = ts.AsTime().UTC().Format(time.RFC3339) + } + + return node, nil +} + +// CloudCred represents a BrevCloud credential as seen by Spark flows. +// For now this is only used to attempt "smart" default resolution; when +// unimplemented, callers should fall back gracefully. +type CloudCred struct { + ID string + ProviderID string + Labels map[string]string +} + +func (c *Client) ListCloudCredID(ctx context.Context, orgID string) (string, error) { + resp, err := c.cloudCreds.ListCloudCred(ctx, connect.NewRequest(&devplaneapiv1.ListCloudCredRequest{ + Options: &devplaneapiv1.ListCloudCredOptions{ + HasAllLabels: map[string]string{ + "name": "brev-cloud-default", + "managedBy": "system", + "provider": "brevcloud", + "orgId": orgID, + }, + }, + })) + if err != nil { + return "", breverrors.WrapAndTrace(err) + } + + return resp.Msg.Items[0].CloudCredId, nil +} + +// MockRegistrationIntent constructs a safe, in-memory registration intent +// for demos or tests when opts.mockRegistration is true. +func MockRegistrationIntent(cloudCredID string) CreateRegistrationIntentResponse { + return CreateRegistrationIntentResponse{ + BrevCloudNodeID: "brev-mock-node", + CloudCredID: cloudCredID, + RegistrationToken: "brev-mock-registration-token", + ExpiresAt: time.Now().UTC().Add(15 * time.Minute).Format(time.RFC3339), + } +} + +func mapPhase(status *brevapiv2.BrevCloudNodeStatus) string { + if status == nil { + return "" + } + switch status.GetPhase() { + case brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_WAITING_FOR_REGISTRATION: + return "WAITING_FOR_REGISTRATION" + case brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ACTIVE: + return "ACTIVE" + case brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_OFFLINE: + return "OFFLINE" + case brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_STOPPED: + return "STOPPED" + case brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ERROR: + return "ERROR" + default: + return "" + } +} diff --git a/pkg/brevdaemon/agent.go b/pkg/brevdaemon/agent.go new file mode 100644 index 00000000..f47982f6 --- /dev/null +++ b/pkg/brevdaemon/agent.go @@ -0,0 +1,58 @@ +package brevdaemon + +import ( + "context" + "os" + "os/signal" + "syscall" + + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent" + agentconfig "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/config" + "github.com/brevdev/brev-cli/pkg/errors" + "go.uber.org/zap" +) + +const ( + exitCodeOK = 0 + exitCodeConfig = 2 + exitCodeError = 3 +) + +func Main() { + os.Exit(runMain()) +} + +func runMain() int { + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer cancel() + + code, runErr := run(ctx) + if runErr != nil { + zap.L().Error("brev-agent exited with error", zap.Error(runErr)) + } else { + zap.L().Info("brev-agent exited cleanly") + } + return code +} + +func run(ctx context.Context) (int, error) { + cfg, err := agentconfig.Load() + if err != nil { + return exitCodeConfig, errors.WrapAndTrace(err) + } + + agentLogger := zap.L().Named("brev-agent") + a, err := agent.NewAgent(cfg, agentLogger) + if err != nil { + return exitCodeError, errors.WrapAndTrace(err) + } + + if err := a.Run(ctx); err != nil { + if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { + return exitCodeOK, nil + } + return exitCodeError, errors.WrapAndTrace(err) + } + + return exitCodeOK, nil +} diff --git a/pkg/brevdaemon/agent/agent.go b/pkg/brevdaemon/agent/agent.go new file mode 100644 index 00000000..ab0946bf --- /dev/null +++ b/pkg/brevdaemon/agent/agent.go @@ -0,0 +1,223 @@ +package agent + +import ( + "context" + "sync" + "time" + + brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/client" + agentconfig "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/config" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/health" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/heartbeat" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/identity" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/telemetry" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/tunnel" + "github.com/brevdev/brev-cli/pkg/errors" + "go.uber.org/zap" + "golang.org/x/sync/errgroup" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// Agent is the top-level interface that drives the agent lifecycle. +type Agent interface { + Run(ctx context.Context) error +} + +type runner interface { + Run(ctx context.Context) error +} + +type tunnelProcess interface { + Start(ctx context.Context) error +} + +type agent struct { + cfg agentconfig.Config + log *zap.Logger + heartbeat runner + tunnel tunnelProcess + + statusReporter *health.Reporter + statusUpdates chan *brevapiv2.BrevCloudNodeStatus +} + +var ( + newBrevCloudAgentClient = client.New + detectHardware = telemetry.DetectHardware + ensureIdentity = identity.EnsureIdentity +) + +const defaultHeartbeatMaxInterval = 5 * time.Minute + +// NewAgent wires the agent components together. +func NewAgent(cfg agentconfig.Config, log *zap.Logger) (Agent, error) { + if log == nil { + return nil, errors.Errorf("logger cannot be nil") + } + + cli, err := newBrevCloudAgentClient(cfg) + if err != nil { + return nil, errors.WrapAndTrace(err) + } + + ctx := context.Background() + hw, err := detectHardware(ctx) + if err != nil { + return nil, errors.WrapAndTrace(err) + } + + store := identity.NewIdentityStore(cfg) + ident, err := ensureIdentity(ctx, cfg, cli, store, hw, log.Named("identity")) + if err != nil { + return nil, errors.WrapAndTrace(err) + } + + statusReporter := health.NewReporter(health.Status{ + Phase: brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ACTIVE, + LastTransitionTime: time.Now(), + }) + defaultStatus := &brevapiv2.BrevCloudNodeStatus{ + Phase: brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ACTIVE, + } + statusUpdates := make(chan *brevapiv2.BrevCloudNodeStatus, 1) + + hbRunner := &heartbeat.Runner{ + Client: cli, + Identity: ident, + Cfg: heartbeat.HeartbeatConfig{ + BaseInterval: cfg.HeartbeatInterval, + MaxInterval: defaultHeartbeatMaxInterval, + }, + Log: log.Named("heartbeat"), + DefaultStatus: defaultStatus, + StatusUpdates: statusUpdates, + } + + var tunnelMgr tunnelProcess + if cfg.EnableTunnel { + tunnelMgr = &tunnel.Manager{ + Client: cli, + Identity: ident, + Cfg: tunnel.TunnelConfig{ + SSHPort: cfg.TunnelSSHPort, + }, + Log: log.Named("tunnel"), + Health: statusReporter, + } + } + + return &agent{ + cfg: cfg, + log: log.Named("agent"), + heartbeat: hbRunner, + tunnel: tunnelMgr, + statusReporter: statusReporter, + statusUpdates: statusUpdates, + }, nil +} + +func (a *agent) Run(ctx context.Context) error { + a.log.Info("brev-agent starting") + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + g, gctx := errgroup.WithContext(ctx) + + stopStatusBridge := a.startStatusBridge(gctx) + defer stopStatusBridge() + + g.Go(func() error { + if err := a.heartbeat.Run(gctx); err != nil { + return errors.WrapAndTrace(err) + } + return nil + }) + + var tunnelWG sync.WaitGroup + if a.tunnel != nil { + tunnelWG.Add(1) + go func() { + defer tunnelWG.Done() + a.runTunnel(gctx) + }() + } + + err := g.Wait() + cancel() + tunnelWG.Wait() + + if err != nil { + a.log.Error("brev-agent stopped with error", zap.Error(err)) + return errors.WrapAndTrace(err) + } + + a.log.Info("brev-agent stopped cleanly") + return nil +} + +func (a *agent) startStatusBridge(ctx context.Context) func() { + if a.statusReporter == nil || a.statusUpdates == nil { + return func() {} + } + + bridgeCtx, cancel := context.WithCancel(ctx) + updates := a.statusReporter.Updates() + + go func() { + for { + select { + case <-bridgeCtx.Done(): + return + case status, ok := <-updates: + if !ok { + return + } + hbStatus := toHeartbeatStatus(status) + select { + case a.statusUpdates <- hbStatus: + case <-bridgeCtx.Done(): + return + } + } + } + }() + + return cancel +} + +func toHeartbeatStatus(status health.Status) *brevapiv2.BrevCloudNodeStatus { + hbStatus := &brevapiv2.BrevCloudNodeStatus{ + Phase: status.Phase, + Detail: status.Detail, + } + if !status.LastTransitionTime.IsZero() { + hbStatus.LastTransitionTime = timestamppb.New(status.LastTransitionTime) + } + return hbStatus +} + +func (a *agent) runTunnel(ctx context.Context) { + if a.tunnel == nil { + return + } + + for { + err := a.tunnel.Start(ctx) + switch { + case err == nil: + return + case errors.Is(err, context.Canceled): + return + default: + a.log.Warn("tunnel subsystem failed", zap.Error(err)) + } + + select { + case <-ctx.Done(): + return + case <-time.After(time.Second): + } + } +} diff --git a/pkg/brevdaemon/agent/agent_test.go b/pkg/brevdaemon/agent/agent_test.go new file mode 100644 index 00000000..22f898a5 --- /dev/null +++ b/pkg/brevdaemon/agent/agent_test.go @@ -0,0 +1,204 @@ +package agent + +import ( + "context" + "errors" + "path/filepath" + "testing" + "time" + + brevapiv2connect "buf.build/gen/go/brevdev/devplane/connectrpc/go/brevapi/v2/brevapiv2connect" + brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" + "connectrpc.com/connect" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/client" + agentconfig "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/config" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/heartbeat" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/identity" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/telemetry" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/zaptest" +) + +func TestNewAgentBuildsDependencies(t *testing.T) { + origClientFactory := newBrevCloudAgentClient + origDetect := detectHardware + origEnsure := ensureIdentity + + t.Cleanup(func() { + newBrevCloudAgentClient = origClientFactory + detectHardware = origDetect + ensureIdentity = origEnsure + }) + + newBrevCloudAgentClient = func(agentconfig.Config, ...client.Option) (brevapiv2connect.BrevCloudAgentServiceClient, error) { + return &stubBrevCloudClient{}, nil + } + + var detectCalled bool + detectHardware = func(context.Context) (telemetry.HardwareInfo, error) { + detectCalled = true + return telemetry.HardwareInfo{CPUCount: 8}, nil + } + + var ensureCalled bool + ensureIdentity = func(_ context.Context, _ agentconfig.Config, _ brevapiv2connect.BrevCloudAgentServiceClient, _ *identity.IdentityStore, _ telemetry.HardwareInfo, _ *zap.Logger) (identity.Identity, error) { + ensureCalled = true + return identity.Identity{ + InstanceID: "inst-1", + DeviceToken: "token-1", + }, nil + } + + dir := t.TempDir() + cfg := agentconfig.Config{ + BrevCloudAgentURL: "https://example.dev/v1", + StateDir: dir, + DeviceTokenPath: filepath.Join(dir, "device_token"), + RegistrationToken: "reg-token", + EnableTunnel: true, + TunnelSSHPort: 2022, + HeartbeatInterval: time.Second, + } + + agentIface, err := NewAgent(cfg, zaptest.NewLogger(t)) + require.NoError(t, err) + require.True(t, detectCalled) + require.True(t, ensureCalled) + + a, ok := agentIface.(*agent) + require.True(t, ok) + require.NotNil(t, a.heartbeat) + require.NotNil(t, a.tunnel) + require.NotNil(t, a.statusReporter) + require.NotNil(t, a.statusUpdates) + + runner, ok := a.heartbeat.(*heartbeat.Runner) + require.True(t, ok) + require.NotNil(t, runner.StatusUpdates) + require.NotNil(t, runner.DefaultStatus) +} + +func TestAgentRunPropagatesHeartbeatError(t *testing.T) { + hbErr := errors.New("heartbeat failed") + hb := &stubRunner{ + runFn: func(context.Context) error { + return hbErr + }, + } + + a := &agent{ + log: zaptest.NewLogger(t), + heartbeat: hb, + } + + err := a.Run(context.Background()) + require.ErrorIs(t, err, hbErr) +} + +func TestAgentRunStopsOnContextCancel(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hb := &stubRunner{ + runFn: func(ctx context.Context) error { + <-ctx.Done() + return nil + }, + } + tm := &stubTunnel{ + startFn: func(ctx context.Context) error { + <-ctx.Done() + return nil + }, + } + + a := &agent{ + log: zaptest.NewLogger(t), + heartbeat: hb, + tunnel: tm, + } + + go func() { + time.Sleep(10 * time.Millisecond) + cancel() + }() + + err := a.Run(ctx) + require.NoError(t, err) +} + +func TestAgentRunIgnoresTunnelError(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + hb := &stubRunner{ + runFn: func(ctx context.Context) error { + <-ctx.Done() + return nil + }, + } + + var startCalls int + tm := &stubTunnel{ + startFn: func(ctx context.Context) error { + startCalls++ + if startCalls == 1 { + return errors.New("boom") + } + <-ctx.Done() + return ctx.Err() + }, + } + + a := &agent{ + log: zaptest.NewLogger(t), + heartbeat: hb, + tunnel: tm, + } + + go func() { + time.Sleep(20 * time.Millisecond) + cancel() + }() + + err := a.Run(ctx) + require.NoError(t, err) + require.Equal(t, 1, startCalls) +} + +type stubBrevCloudClient struct{} + +func (s *stubBrevCloudClient) Register(context.Context, *connect.Request[brevapiv2.RegisterRequest]) (*connect.Response[brevapiv2.RegisterResponse], error) { + return connect.NewResponse(&brevapiv2.RegisterResponse{}), nil +} + +func (s *stubBrevCloudClient) Heartbeat(context.Context, *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) { + return connect.NewResponse(&brevapiv2.HeartbeatResponse{}), nil +} + +func (s *stubBrevCloudClient) GetTunnelToken(context.Context, *connect.Request[brevapiv2.GetTunnelTokenRequest]) (*connect.Response[brevapiv2.GetTunnelTokenResponse], error) { + return connect.NewResponse(&brevapiv2.GetTunnelTokenResponse{}), nil +} + +type stubRunner struct { + runFn func(context.Context) error +} + +func (s *stubRunner) Run(ctx context.Context) error { + if s.runFn != nil { + return s.runFn(ctx) + } + return nil +} + +type stubTunnel struct { + startFn func(context.Context) error +} + +func (s *stubTunnel) Start(ctx context.Context) error { + if s.startFn != nil { + return s.startFn(ctx) + } + return nil +} diff --git a/pkg/brevdaemon/agent/client/client.go b/pkg/brevdaemon/agent/client/client.go new file mode 100644 index 00000000..276d9700 --- /dev/null +++ b/pkg/brevdaemon/agent/client/client.go @@ -0,0 +1,137 @@ +package client + +import ( + "net/http" + + brevapiv2connect "buf.build/gen/go/brevdev/devplane/connectrpc/go/brevapi/v2/brevapiv2connect" + brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" + "connectrpc.com/connect" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/config" + "github.com/brevdev/brev-cli/pkg/errors" +) + +// Client is an alias to the generated BrevCloud agent RPC client. +type Client = brevapiv2connect.BrevCloudAgentServiceClient + +// Option configures client construction. +type Option func(*options) + +type options struct { + httpClient connect.HTTPClient + clientOpts []connect.ClientOption + customRPC brevapiv2connect.BrevCloudAgentServiceClient +} + +// WithHTTPClient overrides the HTTP client used for RPCs. +func WithHTTPClient(httpClient connect.HTTPClient) Option { + return func(o *options) { + o.httpClient = httpClient + } +} + +// WithClientOptions forwards raw connect client options to the underlying RPC client. +func WithClientOptions(opts ...connect.ClientOption) Option { + return func(o *options) { + o.clientOpts = append(o.clientOpts, opts...) + } +} + +// WithRPCClient injects a pre-built BrevCloudAgentService client. Primarily used for tests. +func WithRPCClient(rpc brevapiv2connect.BrevCloudAgentServiceClient) Option { + return func(o *options) { + o.customRPC = rpc + } +} + +// New constructs a generated BrevCloud agent client backed by the Connect RPC client. +func New(cfg config.Config, opts ...Option) (brevapiv2connect.BrevCloudAgentServiceClient, error) { + if cfg.BrevCloudAgentURL == "" { + return nil, errors.Errorf("brevcloud agent URL is required") + } + + merged := options{} + for _, opt := range opts { + if opt != nil { + opt(&merged) + } + } + + httpClient := merged.httpClient + if httpClient == nil { + httpClient = http.DefaultClient + } + + if merged.customRPC != nil { + return merged.customRPC, nil + } + + return brevapiv2connect.NewBrevCloudAgentServiceClient(httpClient, cfg.BrevCloudAgentURL, merged.clientOpts...), nil +} + +// ErrUnauthenticated indicates the control plane rejected a token. +var ErrUnauthenticated = errors.New("brevcloudagent: unauthenticated request") + +// RegistrationError provides structured context when registration is rejected. +type RegistrationError struct { + Reason brevapiv2.BrevCloudRegistrationErrorReason + Msg string +} + +// Error satisfies the error interface. +func (r *RegistrationError) Error() string { + if r == nil { + return "registration error" + } + return r.Msg +} + +// ClassifyError unwraps Connect errors to return richer error types used by callers. +func ClassifyError(err error) error { + if err == nil { + return nil + } + var connectErr *connect.Error + if errors.As(err, &connectErr) { + if regErr := registrationErrorFromConnect(connectErr); regErr != nil { + return errors.WrapAndTrace(regErr) + } + if connectErr.Code() == connect.CodeUnauthenticated { + return errors.WrapAndTrace(errors.Join(ErrUnauthenticated, err)) + } + } + return errors.WrapAndTrace(err) +} + +func registrationErrorFromConnect(err *connect.Error) error { + if err == nil { + return nil + } + for _, detail := range err.Details() { + msg, detailErr := detail.Value() + if detailErr != nil { + continue + } + regDetail, ok := msg.(*brevapiv2.BrevCloudRegistrationErrorDetail) + if !ok { + continue + } + return &RegistrationError{ + Reason: regDetail.GetReason(), + Msg: regDetail.GetMessage(), + } + } + return nil +} + +// BearerToken returns the HTTP Authorization header value for the provided token. +func BearerToken(token string) string { + return "Bearer " + token +} + +// ProtoString returns a protobuf-compatible optional string pointer when the value is non-empty. +func ProtoString(value string) *string { + if value == "" { + return nil + } + return &value +} diff --git a/pkg/brevdaemon/agent/client/client_test.go b/pkg/brevdaemon/agent/client/client_test.go new file mode 100644 index 00000000..b4dd9511 --- /dev/null +++ b/pkg/brevdaemon/agent/client/client_test.go @@ -0,0 +1,62 @@ +package client + +import ( + "context" + stderrs "errors" + "testing" + + brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" + "connectrpc.com/connect" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/config" + "github.com/stretchr/testify/require" +) + +func TestNewUsesCustomRPCClient(t *testing.T) { + custom := &stubRPC{} + cli, err := New(config.Config{BrevCloudAgentURL: "http://example.dev"}, WithRPCClient(custom)) + require.NoError(t, err) + require.Equal(t, custom, cli) +} + +func TestClassifyErrorMapsUnauthenticated(t *testing.T) { + raw := connect.NewError(connect.CodeUnauthenticated, stderrs.New("unauth")) + err := ClassifyError(raw) + require.Error(t, err) + require.True(t, stderrs.Is(err, ErrUnauthenticated)) +} + +func TestClassifyErrorMapsRegistrationDetail(t *testing.T) { + raw := connect.NewError(connect.CodeInvalidArgument, stderrs.New("bad token")) + detail, detailErr := connect.NewErrorDetail(&brevapiv2.BrevCloudRegistrationErrorDetail{ + Reason: brevapiv2.BrevCloudRegistrationErrorReason_BREV_CLOUD_REGISTRATION_ERROR_REASON_INVALID_TOKEN, + Message: "token invalid", + }) + require.NoError(t, detailErr) + raw.AddDetail(detail) + + err := ClassifyError(raw) + require.Error(t, err) + + var regErr *RegistrationError + require.ErrorAs(t, err, ®Err) + require.Equal(t, brevapiv2.BrevCloudRegistrationErrorReason_BREV_CLOUD_REGISTRATION_ERROR_REASON_INVALID_TOKEN, regErr.Reason) + require.Equal(t, "token invalid", regErr.Error()) +} + +func TestBearerToken(t *testing.T) { + require.Equal(t, "Bearer abc", BearerToken("abc")) +} + +type stubRPC struct{} + +func (s *stubRPC) Register(context.Context, *connect.Request[brevapiv2.RegisterRequest]) (*connect.Response[brevapiv2.RegisterResponse], error) { + return connect.NewResponse(&brevapiv2.RegisterResponse{}), nil +} + +func (s *stubRPC) Heartbeat(context.Context, *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) { + return connect.NewResponse(&brevapiv2.HeartbeatResponse{}), nil +} + +func (s *stubRPC) GetTunnelToken(context.Context, *connect.Request[brevapiv2.GetTunnelTokenRequest]) (*connect.Response[brevapiv2.GetTunnelTokenResponse], error) { + return connect.NewResponse(&brevapiv2.GetTunnelTokenResponse{}), nil +} diff --git a/pkg/brevdaemon/agent/config/config.go b/pkg/brevdaemon/agent/config/config.go new file mode 100644 index 00000000..cbfd751d --- /dev/null +++ b/pkg/brevdaemon/agent/config/config.go @@ -0,0 +1,159 @@ +package config + +import ( + "os" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/brevdev/brev-cli/pkg/errors" +) + +const ( + EnvBrevCloudURL = "BREV_AGENT_BREV_CLOUD_URL" + EnvRegistrationToken = "BREV_AGENT_REGISTRATION_TOKEN" //nolint:gosec // token env var name, not secret + EnvDisplayName = "BREV_AGENT_DISPLAY_NAME" + EnvCloudName = "BREV_AGENT_CLOUD_NAME" + EnvCloudCredID = "BREV_AGENT_CLOUD_CRED_ID" //nolint:gosec // credential env var name, not secret + EnvBrevCloudNodeID = "BREV_AGENT_BREV_CLOUD_NODE_ID" + EnvStateDir = "BREV_AGENT_STATE_DIR" + EnvDeviceTokenPath = "BREV_AGENT_DEVICE_TOKEN_PATH" //nolint:gosec // path key, not a credential + EnvHeartbeatInterval = "BREV_AGENT_HEARTBEAT_INTERVAL" + EnvEnableTunnel = "BREV_AGENT_ENABLE_TUNNEL" + EnvTunnelSSHPort = "BREV_AGENT_TUNNEL_SSH_PORT" + EnvTunnelCritical = "BREV_AGENT_TUNNEL_CRITICAL" + defaultStateDirName = ".brev-agent" + defaultDeviceTokenName = "device_token" + defaultHeartbeatInterval = 30 * time.Second + defaultTunnelSSHPort = 22 + defaultEnableTunnel = true + defaultTunnelCritical = true + fallbackRelativeStateBase = "." +) + +// Config captures all runtime configuration for the brev-agent binary. +type Config struct { + BrevCloudAgentURL string + RegistrationToken string + DisplayName string + CloudName string + + StateDir string + DeviceTokenPath string + + HeartbeatInterval time.Duration + + EnableTunnel bool + TunnelSSHPort int32 + TunnelCritical bool +} + +// Load constructs a Config from environment variables, applying defaults and validations +func Load() (Config, error) { + cfg := Config{ + HeartbeatInterval: defaultHeartbeatInterval, + EnableTunnel: defaultEnableTunnel, + TunnelSSHPort: defaultTunnelSSHPort, + TunnelCritical: defaultTunnelCritical, + } + + cfg.BrevCloudAgentURL = strings.TrimSpace(os.Getenv(EnvBrevCloudURL)) + if cfg.BrevCloudAgentURL == "" { + return Config{}, errors.Errorf("%s is required", EnvBrevCloudURL) + } + + cfg.RegistrationToken = strings.TrimSpace(os.Getenv(EnvRegistrationToken)) + cfg.DisplayName = strings.TrimSpace(os.Getenv(EnvDisplayName)) + cfg.CloudName = strings.TrimSpace(os.Getenv(EnvCloudName)) + stateDir, err := deriveStateDir(os.Getenv(EnvStateDir)) + if err != nil { + return Config{}, errors.WrapAndTrace(err) + } + cfg.StateDir = stateDir + + deviceToken, err := deriveDeviceTokenPath(os.Getenv(EnvDeviceTokenPath), stateDir) + if err != nil { + return Config{}, errors.WrapAndTrace(err) + } + cfg.DeviceTokenPath = deviceToken + + if intervalRaw := strings.TrimSpace(os.Getenv(EnvHeartbeatInterval)); intervalRaw != "" { + interval, err := time.ParseDuration(intervalRaw) + if err != nil { + return Config{}, errors.WrapAndTrace(errors.Errorf("%s must be a valid duration: %v", EnvHeartbeatInterval, err)) + } + if interval <= 0 { + return Config{}, errors.Errorf("%s must be positive", EnvHeartbeatInterval) + } + cfg.HeartbeatInterval = interval + } + + if enableTunnelRaw := strings.TrimSpace(os.Getenv(EnvEnableTunnel)); enableTunnelRaw != "" { + val, err := strconv.ParseBool(enableTunnelRaw) + if err != nil { + return Config{}, errors.WrapAndTrace(errors.Errorf("%s must be a boolean: %v", EnvEnableTunnel, err)) + } + cfg.EnableTunnel = val + } + + if portRaw := strings.TrimSpace(os.Getenv(EnvTunnelSSHPort)); portRaw != "" { + port, err := strconv.ParseInt(portRaw, 10, 32) + if err != nil { + return Config{}, errors.WrapAndTrace(errors.Errorf("%s must be an integer: %v", EnvTunnelSSHPort, err)) + } + if port <= 0 || port > 65535 { + return Config{}, errors.Errorf("%s must be between 1 and 65535", EnvTunnelSSHPort) + } + cfg.TunnelSSHPort = int32(port) + } + + if tunnelCriticalRaw := strings.TrimSpace(os.Getenv(EnvTunnelCritical)); tunnelCriticalRaw != "" { + val, err := strconv.ParseBool(tunnelCriticalRaw) + if err != nil { + return Config{}, errors.WrapAndTrace(errors.Errorf("%s must be a boolean: %v", EnvTunnelCritical, err)) + } + cfg.TunnelCritical = val + } + + return cfg, nil +} + +func deriveStateDir(override string) (string, error) { + if strings.TrimSpace(override) != "" { + return expandPath(override) + } + + home, err := os.UserHomeDir() + if err == nil && home != "" { + return filepath.Join(home, defaultStateDirName), nil + } + + return filepath.Join(fallbackRelativeStateBase, defaultStateDirName), nil +} + +func deriveDeviceTokenPath(override, stateDir string) (string, error) { + if strings.TrimSpace(override) != "" { + return expandPath(override) + } + return filepath.Join(stateDir, defaultDeviceTokenName), nil +} + +func expandPath(path string) (string, error) { + if path == "" { + return "", errors.Errorf("path cannot be empty") + } + + if strings.HasPrefix(path, "~") { + home, err := os.UserHomeDir() + if err != nil { + return "", errors.WrapAndTrace(err) + } + if home == "" { + return "", errors.Errorf("cannot expand ~, home directory unset") + } + path = filepath.Join(home, path[1:]) + } + + return filepath.Clean(path), nil +} diff --git a/pkg/brevdaemon/agent/config/config_test.go b/pkg/brevdaemon/agent/config/config_test.go new file mode 100644 index 00000000..8589b15c --- /dev/null +++ b/pkg/brevdaemon/agent/config/config_test.go @@ -0,0 +1,152 @@ +package config + +import ( + "os" + "path/filepath" + "testing" + "time" +) + +func TestLoadDefaults(t *testing.T) { + unsetConfigEnv(t) + t.Setenv(EnvBrevCloudURL, "https://example.dev/v1/brevcloudagent") + + cfg, err := Load() + if err != nil { + t.Fatalf("Load() error = %v", err) + } + + home, _ := os.UserHomeDir() + expectedStateDir := filepath.Join(home, defaultStateDirName) + if cfg.StateDir != expectedStateDir { + t.Fatalf("StateDir = %s, want %s", cfg.StateDir, expectedStateDir) + } + + expectedDevicePath := filepath.Join(expectedStateDir, defaultDeviceTokenName) + if cfg.DeviceTokenPath != expectedDevicePath { + t.Fatalf("DeviceTokenPath = %s, want %s", cfg.DeviceTokenPath, expectedDevicePath) + } + + if cfg.HeartbeatInterval != defaultHeartbeatInterval { + t.Fatalf("HeartbeatInterval = %s, want %s", cfg.HeartbeatInterval, defaultHeartbeatInterval) + } + + if !cfg.EnableTunnel { + t.Fatalf("EnableTunnel = false, want true") + } + + if cfg.TunnelSSHPort != defaultTunnelSSHPort { + t.Fatalf("TunnelSSHPort = %d, want %d", cfg.TunnelSSHPort, defaultTunnelSSHPort) + } + + if !cfg.TunnelCritical { + t.Fatalf("TunnelCritical = false, want true") + } +} + +func TestLoadOverrides(t *testing.T) { + unsetConfigEnv(t) + t.Setenv(EnvBrevCloudURL, "https://example.dev/v1") + t.Setenv(EnvRegistrationToken, "secret") + t.Setenv(EnvDisplayName, "edge-node") + t.Setenv(EnvCloudName, "private") + t.Setenv(EnvStateDir, "~/custom/.brevagent") + t.Setenv(EnvDeviceTokenPath, "~/custom/device_token.json") + t.Setenv(EnvHeartbeatInterval, "45s") + t.Setenv(EnvEnableTunnel, "false") + t.Setenv(EnvTunnelSSHPort, "2202") + t.Setenv(EnvTunnelCritical, "false") + + cfg, err := Load() + if err != nil { + t.Fatalf("Load() error = %v", err) + } + + if cfg.RegistrationToken != "secret" { + t.Fatalf("RegistrationToken = %s, want secret", cfg.RegistrationToken) + } + if cfg.DisplayName != "edge-node" { + t.Fatalf("DisplayName = %s, want edge-node", cfg.DisplayName) + } + if cfg.CloudName != "private" { + t.Fatalf("CloudName = %s, want private", cfg.CloudName) + } + if cfg.HeartbeatInterval != 45*time.Second { + t.Fatalf("HeartbeatInterval = %s, want 45s", cfg.HeartbeatInterval) + } + if cfg.EnableTunnel { + t.Fatalf("EnableTunnel = true, want false") + } + if cfg.TunnelSSHPort != 2202 { + t.Fatalf("TunnelSSHPort = %d, want 2202", cfg.TunnelSSHPort) + } + if cfg.TunnelCritical { + t.Fatalf("TunnelCritical = true, want false") + } + home, _ := os.UserHomeDir() + expectedStateDir := filepath.Join(home, "custom", ".brevagent") + if cfg.StateDir != expectedStateDir { + t.Fatalf("StateDir = %s, want %s", cfg.StateDir, expectedStateDir) + } + expectedDevicePath := filepath.Join(home, "custom", "device_token.json") + if cfg.DeviceTokenPath != expectedDevicePath { + t.Fatalf("DeviceTokenPath = %s, want %s", cfg.DeviceTokenPath, expectedDevicePath) + } +} + +func TestLoadMissingBrevCloudURL(t *testing.T) { + unsetConfigEnv(t) + _, err := Load() + if err == nil { + t.Fatalf("expected error for missing brevcloud url") + } +} + +func TestLoadInvalidValues(t *testing.T) { + unsetConfigEnv(t) + t.Setenv(EnvBrevCloudURL, "https://example.dev/v1") + t.Setenv(EnvHeartbeatInterval, "abc") + + if _, err := Load(); err == nil { + t.Fatalf("expected invalid interval error") + } + + t.Setenv(EnvHeartbeatInterval, "30s") + t.Setenv(EnvEnableTunnel, "not-bool") + if _, err := Load(); err == nil { + t.Fatalf("expected invalid bool error") + } + + t.Setenv(EnvEnableTunnel, "true") + t.Setenv(EnvTunnelSSHPort, "100000") + if _, err := Load(); err == nil { + t.Fatalf("expected invalid port error") + } + + t.Setenv(EnvTunnelSSHPort, "22") + t.Setenv(EnvTunnelCritical, "not-bool") + if _, err := Load(); err == nil { + t.Fatalf("expected invalid tunnel critical bool error") + } +} + +func unsetConfigEnv(t *testing.T) { + t.Helper() + envs := []string{ + EnvBrevCloudURL, + EnvRegistrationToken, + EnvDisplayName, + EnvCloudName, + EnvCloudCredID, + EnvBrevCloudNodeID, + EnvStateDir, + EnvDeviceTokenPath, + EnvHeartbeatInterval, + EnvEnableTunnel, + EnvTunnelSSHPort, + EnvTunnelCritical, + } + for _, key := range envs { + t.Setenv(key, "") + } +} diff --git a/pkg/brevdaemon/agent/health/reporter.go b/pkg/brevdaemon/agent/health/reporter.go new file mode 100644 index 00000000..93d7317f --- /dev/null +++ b/pkg/brevdaemon/agent/health/reporter.go @@ -0,0 +1,112 @@ +package health + +import ( + "sync" + "time" + + brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" +) + +// Status mirrors the heartbeat payload but keeps Go-native fields for internal coordination. +type Status struct { + Phase brevapiv2.BrevCloudNodePhase + Detail string + LastTransitionTime time.Time +} + +// Reporter fan-outs status updates to interested consumers and stamps transition times. +// It is safe for concurrent use. +type Reporter struct { + mu sync.Mutex + current Status + now func() time.Time + + updates chan Status +} + +// Option configures Reporter construction. +type Option func(*Reporter) + +// WithClock overrides the wall clock used to stamp LastTransitionTime. Tests may +// inject deterministic values. +func WithClock(now func() time.Time) Option { + return func(r *Reporter) { + if now != nil { + r.now = now + } + } +} + +// NewReporter constructs a Reporter seeded with an initial status. +func NewReporter(initial Status, opts ...Option) *Reporter { + r := &Reporter{ + current: initial, + now: time.Now, + updates: make(chan Status, 1), + } + for _, opt := range opts { + if opt != nil { + opt(r) + } + } + if r.current.LastTransitionTime.IsZero() { + r.current.LastTransitionTime = r.now() + } + return r +} + +// Updates exposes a read-only channel for consumers who want to react to transitions. +func (r *Reporter) Updates() <-chan Status { + return r.updates +} + +// Current returns the latest status snapshot. +func (r *Reporter) Current() Status { + r.mu.Lock() + defer r.mu.Unlock() + return r.current +} + +// Publish records the provided status and broadcasts it if it represents a change. +// If LastTransitionTime is zero it gets stamped with the reporter clock. +func (r *Reporter) Publish(next Status) Status { + r.mu.Lock() + defer r.mu.Unlock() + + if next.LastTransitionTime.IsZero() { + next.LastTransitionTime = r.now() + } + + if statusesEqual(r.current, next) { + return r.current + } + + r.current = next + select { + case r.updates <- next: + default: + } + return r.current +} + +// MarkActive marks the subsystem as healthy. +func (r *Reporter) MarkActive(detail string) Status { + return r.Publish(Status{ + Phase: brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ACTIVE, + Detail: detail, + }) +} + +// MarkError marks the subsystem as unhealthy with additional detail. +func (r *Reporter) MarkError(detail string) Status { + return r.Publish(Status{ + Phase: brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ERROR, + Detail: detail, + }) +} + +func statusesEqual(a, b Status) bool { + return a.Phase == b.Phase && + a.Detail == b.Detail && + a.LastTransitionTime.Equal(b.LastTransitionTime) +} diff --git a/pkg/brevdaemon/agent/health/reporter_test.go b/pkg/brevdaemon/agent/health/reporter_test.go new file mode 100644 index 00000000..4ca5e51b --- /dev/null +++ b/pkg/brevdaemon/agent/health/reporter_test.go @@ -0,0 +1,93 @@ +package health + +import ( + "testing" + "time" + + brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" +) + +func TestNewReporterSeedsTimestamp(t *testing.T) { + base := time.Unix(100, 0) + reporter := NewReporter(Status{}, WithClock(func() time.Time { + return base + })) + + got := reporter.Current() + if !got.LastTransitionTime.Equal(base) { + t.Fatalf("LastTransitionTime = %s, want %s", got.LastTransitionTime, base) + } +} + +func TestReporterTransitionsEmitUpdates(t *testing.T) { + clockValues := []time.Time{ + time.Unix(10, 0), // initial + time.Unix(20, 0), // error transition + time.Unix(30, 0), // active transition + } + var clockIdx int + reporter := NewReporter(Status{}, WithClock(func() time.Time { + v := clockValues[clockIdx] + if clockIdx < len(clockValues)-1 { + clockIdx++ + } + return v + })) + + updates := reporter.Updates() + + reporter.MarkError("missing binary") + select { + case got := <-updates: + if got.Phase != brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ERROR { + t.Fatalf("Phase = %v, want BrevCloudNodePhase_ERROR", got.Phase) + } + if got.Detail != "missing binary" { + t.Fatalf("Detail = %q, want %q", got.Detail, "missing binary") + } + if !got.LastTransitionTime.Equal(clockValues[1]) { + t.Fatalf("LastTransitionTime = %s, want %s", got.LastTransitionTime, clockValues[1]) + } + case <-time.After(time.Second): + t.Fatalf("expected update for error transition") + } + + reporter.MarkActive("recovered") + select { + case got := <-updates: + if got.Phase != brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ACTIVE { + t.Fatalf("Phase = %v, want BrevCloudNodePhase_ACTIVE", got.Phase) + } + if got.Detail != "recovered" { + t.Fatalf("Detail = %q, want %q", got.Detail, "recovered") + } + if !got.LastTransitionTime.Equal(clockValues[2]) { + t.Fatalf("LastTransitionTime = %s, want %s", got.LastTransitionTime, clockValues[2]) + } + case <-time.After(time.Second): + t.Fatalf("expected update for recovery transition") + } +} + +func TestReporterPublishNoChangeDoesNotEmit(t *testing.T) { + initial := Status{ + Phase: brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ACTIVE, + Detail: "ok", + LastTransitionTime: time.Unix(42, 0), + } + reporter := NewReporter(initial) + updates := reporter.Updates() + + reporter.Publish(initial) + + select { + case <-updates: + t.Fatalf("expected no update for identical publish") + default: + } + + current := reporter.Current() + if current != initial { + t.Fatalf("Current changed = %+v, want %+v", current, initial) + } +} diff --git a/pkg/brevdaemon/agent/heartbeat/heartbeat.go b/pkg/brevdaemon/agent/heartbeat/heartbeat.go new file mode 100644 index 00000000..2f5fa594 --- /dev/null +++ b/pkg/brevdaemon/agent/heartbeat/heartbeat.go @@ -0,0 +1,235 @@ +package heartbeat + +import ( + "context" + "time" + + brevapiv2connect "buf.build/gen/go/brevdev/devplane/connectrpc/go/brevapi/v2/brevapiv2connect" + brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" + "connectrpc.com/connect" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/client" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/identity" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/telemetry" + "github.com/brevdev/brev-cli/pkg/errors" + "go.uber.org/zap" + "google.golang.org/protobuf/types/known/timestamppb" +) + +const ( + defaultBaseInterval = 30 * time.Second + defaultMaxInterval = 5 * time.Minute +) + +// HeartbeatConfig controls how frequently heartbeats run. +type HeartbeatConfig struct { + BaseInterval time.Duration + MaxInterval time.Duration +} + +// Runner drives the periodic heartbeat loop. +type Runner struct { + Client brevapiv2connect.BrevCloudAgentServiceClient + Identity identity.Identity + Cfg HeartbeatConfig + Log *zap.Logger + + SampleUtilization func(context.Context) (telemetry.UtilizationInfo, error) + Sleep func(context.Context, time.Duration) error + Now func() time.Time + + DefaultStatus *brevapiv2.BrevCloudNodeStatus + StatusUpdates <-chan *brevapiv2.BrevCloudNodeStatus +} + +// Run executes the heartbeat loop until the context is canceled or an +// unrecoverable error occurs. +func (r *Runner) Run(ctx context.Context) error { //nolint:funlen // loop coordinates retries, telemetry, and server responses + if err := r.validate(); err != nil { + return errors.WrapAndTrace(err) + } + + // sampleFn := r.SampleUtilization + // if sampleFn == nil { + // sampleFn = telemetry.SampleUtilization + // } + sleepFn := r.Sleep + if sleepFn == nil { + sleepFn = defaultSleep + } + nowFn := r.Now + if nowFn == nil { + nowFn = time.Now + } + + baseStatus := r.DefaultStatus + if baseStatus == nil { + baseStatus = &brevapiv2.BrevCloudNodeStatus{ + Phase: brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ACTIVE, + } + } + currentStatus := cloneStatus(baseStatus) + + base := r.Cfg.BaseInterval + if base <= 0 { + base = defaultBaseInterval + } + maxInterval := r.Cfg.MaxInterval + if maxInterval <= 0 { + maxInterval = defaultMaxInterval + } + if maxInterval < base { + maxInterval = base + } + + nextDelay := time.Duration(0) + currentBackoff := base + + for { + if nextDelay > 0 { + if err := sleepFn(ctx, nextDelay); err != nil { + if errors.Is(err, context.Canceled) { + return nil + } + return errors.WrapAndTrace(err) + } + } + + // util, err := sampleFn(ctx) + // if err != nil { + // r.Log.Warn("failed to sample utilization", zap.Error(err)) + // currentBackoff = minDurationValue(currentBackoff*2, maxInterval) + // nextDelay = currentBackoff + // continue + // } + + req := &brevapiv2.HeartbeatRequest{ + BrevCloudNodeId: r.Identity.InstanceID, + ObservedAt: timestamppb.New(nowFn()), + Status: cloneStatus(currentStatus), + DeviceFingerprintHash: r.Identity.DeviceFingerprintHash, + HardwareFingerprint: r.Identity.HardwareFingerprint, + } + + if updated, ok := r.nextStatus(); ok { + currentStatus = updated + req.Status = cloneStatus(currentStatus) + } + + connectReq := connect.NewRequest(req) + connectReq.Header().Set("Authorization", client.BearerToken(r.Identity.DeviceToken)) + + resp, err := r.Client.Heartbeat(ctx, connectReq) + if err != nil { + if classified := client.ClassifyError(err); classified != nil { + if errors.Is(classified, client.ErrUnauthenticated) { + return classified //nolint:wrapcheck // classification intentionally returned verbatim for caller handling + } + r.Log.Warn("heartbeat failed", zap.Error(classified)) + err = classified + } else { + r.Log.Warn("heartbeat failed", zap.Error(err)) + } + currentBackoff = minDurationValue(currentBackoff*2, maxInterval) + nextDelay = currentBackoff + continue + } + + currentBackoff = base + nextDelay = base + if interval := resp.Msg.GetNextHeartbeatInterval(); interval != nil { + nextDelay = clampDuration(interval.AsDuration(), base, maxInterval) + } + } +} + +func (r *Runner) validate() error { + if r.Client == nil { + return errors.Errorf("client is required") + } + if r.Identity.DeviceToken == "" { + return errors.Errorf("device token is required") + } + if r.Identity.InstanceID == "" { + return errors.Errorf("brevcloud node id is required") + } + if r.Log == nil { + return errors.Errorf("logger is required") + } + return nil +} + +func (r *Runner) nextStatus() (*brevapiv2.BrevCloudNodeStatus, bool) { + if r.StatusUpdates == nil { + return nil, false + } + + var updated *brevapiv2.BrevCloudNodeStatus + changed := false + for { + select { + case status, ok := <-r.StatusUpdates: + if !ok { + r.StatusUpdates = nil + return updated, changed + } + updated = normalizeStatus(status) + changed = true + continue + default: + return updated, changed + } + } +} + +func normalizeStatus(status *brevapiv2.BrevCloudNodeStatus) *brevapiv2.BrevCloudNodeStatus { + if status == nil { + return nil + } + out := cloneStatus(status) + if out.GetPhase() == brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_UNSPECIFIED { + out.Phase = brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ACTIVE + } + return out +} + +func cloneStatus(status *brevapiv2.BrevCloudNodeStatus) *brevapiv2.BrevCloudNodeStatus { + if status == nil { + return nil + } + out := *status + if status.LastTransitionTime != nil { + out.LastTransitionTime = timestamppb.New(status.LastTransitionTime.AsTime()) + } + return &out +} + +func defaultSleep(ctx context.Context, d time.Duration) error { + if d <= 0 { + return nil + } + timer := time.NewTimer(d) + defer timer.Stop() + select { + case <-ctx.Done(): + return errors.WrapAndTrace(ctx.Err()) + case <-timer.C: + return nil + } +} + +func minDurationValue(a, b time.Duration) time.Duration { + if a <= b { + return a + } + return b +} + +func clampDuration(value, minInterval, maxInterval time.Duration) time.Duration { + if value < minInterval { + return minInterval + } + if value > maxInterval { + return maxInterval + } + return value +} diff --git a/pkg/brevdaemon/agent/heartbeat/heartbeat_test.go b/pkg/brevdaemon/agent/heartbeat/heartbeat_test.go new file mode 100644 index 00000000..c384c807 --- /dev/null +++ b/pkg/brevdaemon/agent/heartbeat/heartbeat_test.go @@ -0,0 +1,205 @@ +package heartbeat + +import ( + "context" + stderrs "errors" + "testing" + "time" + + brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" + "connectrpc.com/connect" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/client" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/identity" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/telemetry" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestRunnerSendsHeartbeatAndUsesServerInterval(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var captured *connect.Request[brevapiv2.HeartbeatRequest] + clientStub := &stubClient{ + heartbeatFn: func(_ context.Context, req *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) { + captured = req + cancel() + return connect.NewResponse(&brevapiv2.HeartbeatResponse{ + NextHeartbeatInterval: durationpb.New(45 * time.Second), + }), nil + }, + } + + var sleeps []time.Duration + sleepFn := func(ctx context.Context, d time.Duration) error { + sleeps = append(sleeps, d) + <-ctx.Done() + return ctx.Err() + } + + runner := Runner{ + Client: clientStub, + Identity: identity.Identity{ + InstanceID: "fn-1", + DeviceToken: "token", + }, + Cfg: HeartbeatConfig{ + BaseInterval: 30 * time.Second, + MaxInterval: time.Minute, + }, + Log: zaptest.NewLogger(t), + Sleep: sleepFn, + Now: func() time.Time { return time.Unix(0, 0) }, + } + + err := runner.Run(ctx) + require.NoError(t, err) + require.NotNil(t, captured) + require.Equal(t, "fn-1", captured.Msg.GetBrevCloudNodeId()) + require.Equal(t, brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ACTIVE, captured.Msg.GetStatus().GetPhase()) + require.Len(t, sleeps, 1) + require.Equal(t, 45*time.Second, sleeps[0]) +} + +func TestRunnerBackoffOnFailures(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + callCount := 0 + clientStub := &stubClient{ + heartbeatFn: func(_ context.Context, _ *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) { + callCount++ + if callCount < 3 { + return nil, stderrs.New("temporary failure") + } + cancel() + return connect.NewResponse(&brevapiv2.HeartbeatResponse{}), nil + }, + } + + var sleeps []time.Duration + sleepFn := func(ctx context.Context, d time.Duration) error { + sleeps = append(sleeps, d) + select { + case <-ctx.Done(): + return ctx.Err() + default: + return nil + } + } + + runner := Runner{ + Client: clientStub, + Identity: identity.Identity{ + InstanceID: "fn", + DeviceToken: "token", + }, + Cfg: HeartbeatConfig{ + BaseInterval: time.Second, + MaxInterval: 10 * time.Second, + }, + Log: zaptest.NewLogger(t), + Sleep: sleepFn, + Now: time.Now, + } + + err := runner.Run(ctx) + require.NoError(t, err) + require.Equal(t, 3, callCount) + require.Equal(t, []time.Duration{2 * time.Second, 4 * time.Second, time.Second}, sleeps) +} + +func TestRunnerAppliesStatusUpdates(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + transition := time.Unix(100, 0) + updates := make(chan *brevapiv2.BrevCloudNodeStatus, 2) + updates <- &brevapiv2.BrevCloudNodeStatus{ + Phase: brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ERROR, + Detail: "tunnel missing", + LastTransitionTime: timestamppb.New(transition), + } + + var captured []*connect.Request[brevapiv2.HeartbeatRequest] + clientStub := &stubClient{ + heartbeatFn: func(_ context.Context, req *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) { + captured = append(captured, req) + cancel() + return connect.NewResponse(&brevapiv2.HeartbeatResponse{}), nil + }, + } + + runner := Runner{ + Client: clientStub, + Identity: identity.Identity{ + InstanceID: "inst-1", + DeviceToken: "token", + }, + Log: zaptest.NewLogger(t), + Sleep: func(ctx context.Context, _ time.Duration) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + return nil + } + }, + Now: func() time.Time { return time.Unix(0, 0) }, + StatusUpdates: updates, + } + + err := runner.Run(ctx) + require.NoError(t, err) + require.Len(t, captured, 1) + require.NotNil(t, captured[0].Msg.GetStatus()) + require.Equal(t, brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ERROR, captured[0].Msg.GetStatus().GetPhase()) + require.Equal(t, "tunnel missing", captured[0].Msg.GetStatus().GetDetail()) + require.True(t, captured[0].Msg.GetStatus().GetLastTransitionTime().AsTime().Equal(transition)) +} + +func TestRunnerStopsOnUnauthenticated(t *testing.T) { + ctx := context.Background() + clientStub := &stubClient{ + heartbeatFn: func(context.Context, *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) { + return nil, connect.NewError(connect.CodeUnauthenticated, stderrs.New("invalid token")) + }, + } + + runner := Runner{ + Client: clientStub, + Identity: identity.Identity{ + InstanceID: "inst", + DeviceToken: "token", + }, + Log: zaptest.NewLogger(t), + SampleUtilization: func(context.Context) (telemetry.UtilizationInfo, error) { + return telemetry.UtilizationInfo{}, nil + }, + } + + err := runner.Run(ctx) + require.Error(t, err) + require.True(t, stderrs.Is(err, client.ErrUnauthenticated)) +} + +type stubClient struct { + heartbeatFn func(ctx context.Context, req *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) +} + +func (s *stubClient) Register(_ context.Context, _ *connect.Request[brevapiv2.RegisterRequest]) (*connect.Response[brevapiv2.RegisterResponse], error) { + return connect.NewResponse(&brevapiv2.RegisterResponse{}), nil +} + +func (s *stubClient) Heartbeat(ctx context.Context, req *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) { + if s.heartbeatFn != nil { + return s.heartbeatFn(ctx, req) + } + return connect.NewResponse(&brevapiv2.HeartbeatResponse{}), nil +} + +func (s *stubClient) GetTunnelToken(_ context.Context, _ *connect.Request[brevapiv2.GetTunnelTokenRequest]) (*connect.Response[brevapiv2.GetTunnelTokenResponse], error) { + return connect.NewResponse(&brevapiv2.GetTunnelTokenResponse{}), nil +} diff --git a/pkg/brevdaemon/agent/identity/identity.go b/pkg/brevdaemon/agent/identity/identity.go new file mode 100644 index 00000000..0c9ff7ab --- /dev/null +++ b/pkg/brevdaemon/agent/identity/identity.go @@ -0,0 +1,284 @@ +package identity + +import ( + "context" + "crypto/rand" + "encoding/base64" + "os" + "path/filepath" + "strings" + + brevapiv2connect "buf.build/gen/go/brevdev/devplane/connectrpc/go/brevapi/v2/brevapiv2connect" + brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" + "connectrpc.com/connect" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/client" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/config" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/telemetry" + "github.com/brevdev/brev-cli/pkg/errors" + "go.uber.org/zap" +) + +// Identity represents the durable device identity for the agent. +type Identity struct { + InstanceID string + DeviceToken string + HardwareFingerprint string + DeviceFingerprintHash string + DeviceFingerprintStored string + DeviceSalt string +} + +// IdentityStore persists the instance identity to disk. +type IdentityStore struct { + cfg config.Config + tokenPath string + instanceIDPath string + stateDir string + deviceSaltPath string + deviceFPHashPath string + deviceFPStoredPath string + hardwareFPPath string +} + +// NewIdentityStore constructs a store backed by the configured state directory. +func NewIdentityStore(cfg config.Config) *IdentityStore { + instancePath := filepath.Join(cfg.StateDir, "instance_id") + deviceSaltPath := filepath.Join(cfg.StateDir, "device_salt") + return &IdentityStore{ + cfg: cfg, + tokenPath: cfg.DeviceTokenPath, + instanceIDPath: instancePath, + stateDir: cfg.StateDir, + deviceSaltPath: deviceSaltPath, + deviceFPHashPath: filepath.Join(cfg.StateDir, "device_fingerprint_hash"), + deviceFPStoredPath: filepath.Join(cfg.StateDir, "device_fingerprint_stored"), + hardwareFPPath: filepath.Join(cfg.StateDir, "hardware_fingerprint"), + } +} + +// Load returns the stored identity if present. +func (s *IdentityStore) Load() (Identity, bool, error) { + data, err := os.ReadFile(s.tokenPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return Identity{}, false, nil + } + return Identity{}, false, errors.WrapAndTrace(err) + } + + token := strings.TrimSpace(string(data)) + var instanceID string + if data, err := os.ReadFile(s.instanceIDPath); err == nil { + instanceID = strings.TrimSpace(string(data)) + } + + if err := ensurePermissions(s.tokenPath); err != nil { + return Identity{}, false, errors.WrapAndTrace(err) + } + + deviceSalt, err := readOptionalFile(s.deviceSaltPath) + if err != nil { + return Identity{}, false, errors.WrapAndTrace(err) + } + deviceFPHash, err := readOptionalFile(s.deviceFPHashPath) + if err != nil { + return Identity{}, false, errors.WrapAndTrace(err) + } + deviceFPStored, err := readOptionalFile(s.deviceFPStoredPath) + if err != nil { + return Identity{}, false, errors.WrapAndTrace(err) + } + hardwareFP, err := readOptionalFile(s.hardwareFPPath) + if err != nil { + return Identity{}, false, errors.WrapAndTrace(err) + } + + if token == "" { + return Identity{}, false, nil + } + return Identity{ + InstanceID: instanceID, + DeviceToken: token, + DeviceSalt: deviceSalt, + DeviceFingerprintHash: deviceFPHash, + DeviceFingerprintStored: deviceFPStored, + HardwareFingerprint: hardwareFP, + }, true, nil +} + +// Save writes the identity to disk. +func (s *IdentityStore) Save(id Identity) error { + dir := s.stateDir + if dir == "" { + dir = filepath.Dir(s.tokenPath) + } + if err := os.MkdirAll(dir, 0o700); err != nil { + return errors.WrapAndTrace(err) + } + + if err := writeRequired(s.tokenPath, id.DeviceToken); err != nil { + return err + } + if err := writeOptional(s.deviceSaltPath, id.DeviceSalt); err != nil { + return err + } + if err := writeOptional(s.deviceFPHashPath, id.DeviceFingerprintHash); err != nil { + return err + } + if err := writeOptional(s.deviceFPStoredPath, id.DeviceFingerprintStored); err != nil { + return err + } + if err := writeOptional(s.hardwareFPPath, id.HardwareFingerprint); err != nil { + return err + } + return writeOptional(s.instanceIDPath, id.InstanceID) +} + +func writeRequired(path, value string) error { + if err := os.WriteFile(path, []byte(value+"\n"), 0o600); err != nil { + return errors.WrapAndTrace(err) + } + if err := ensurePermissions(path); err != nil { + return errors.WrapAndTrace(err) + } + return nil +} + +func writeOptional(path, value string) error { + if value == "" { + return nil + } + return writeRequired(path, value) +} + +// EnsureIdentity loads an existing device identity or registers a new one. +func EnsureIdentity( + ctx context.Context, + cfg config.Config, + agentClient brevapiv2connect.BrevCloudAgentServiceClient, + store *IdentityStore, + hw telemetry.HardwareInfo, + log *zap.Logger, +) (Identity, error) { + if store == nil { + return Identity{}, errors.Errorf("identity store is required") + } + if agentClient == nil { + return Identity{}, errors.Errorf("brevcloud agent client is required") + } + + id, ok, err := store.Load() + if err != nil { + return Identity{}, errors.WrapAndTrace(err) + } + if ok && id.DeviceToken != "" { + log.Info("loaded device identity from disk", + zap.String("instance_id", id.InstanceID)) + return id, nil + } + + if cfg.RegistrationToken == "" { + return Identity{}, errors.Errorf("registration token required to register device") + } + + salt, err := ensureDeviceSalt(store.deviceSaltPath) + if err != nil { + return Identity{}, errors.WrapAndTrace(err) + } + + hardwareFingerprint, err := computeHardwareFingerprint(hw) + if err != nil { + return Identity{}, errors.WrapAndTrace(err) + } + req := &brevapiv2.RegisterRequest{ + RegistrationToken: cfg.RegistrationToken, + Hardware: hw.ToProto(), + HardwareFingerprint: hardwareFingerprint, + } + if cfg.DisplayName != "" { + req.DisplayName = client.ProtoString(cfg.DisplayName) + } + if cfg.CloudName != "" { + req.CloudName = client.ProtoString(cfg.CloudName) + } + + log.Info("registering device with brevcloud agent service") + resp, err := agentClient.Register(ctx, connect.NewRequest(req)) + if err != nil { + return Identity{}, errors.WrapAndTrace(client.ClassifyError(err)) + } + + newIdentity := Identity{ + InstanceID: resp.Msg.GetBrevCloudNodeId(), + DeviceToken: resp.Msg.GetDeviceToken(), + DeviceSalt: salt, + DeviceFingerprintStored: resp.Msg.GetDeviceFingerprint(), + } + if err := store.Save(newIdentity); err != nil { + return Identity{}, errors.WrapAndTrace(err) + } + + log.Info("device registration complete", + zap.String("instance_id", newIdentity.InstanceID)) + return newIdentity, nil +} + +func ensurePermissions(path string) error { + info, err := os.Stat(path) + if err != nil { + return errors.WrapAndTrace(err) + } + const desired = 0o600 + if info.Mode().Perm() != desired { + if err := os.Chmod(path, desired); err != nil { + return errors.WrapAndTrace(err) + } + } + return nil +} + +func readOptionalFile(path string) (string, error) { + data, err := os.ReadFile(path) // #nosec G304 -- path is derived from controlled config/state dir + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return "", nil + } + return "", errors.WrapAndTrace(err) + } + value := strings.TrimSpace(string(data)) + if value == "" { + return "", nil + } + if err := ensurePermissions(path); err != nil { + return "", errors.WrapAndTrace(err) + } + return value, nil +} + +func ensureDeviceSalt(path string) (string, error) { + if value, err := readOptionalFile(path); err == nil && value != "" { + return value, nil + } else if err != nil { + return "", errors.WrapAndTrace(err) + } + + raw := make([]byte, 32) + if _, err := rand.Read(raw); err != nil { + return "", errors.WrapAndTrace(err) + } + salt := base64.RawURLEncoding.EncodeToString(raw) + if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { + return "", errors.WrapAndTrace(err) + } + if err := os.WriteFile(path, []byte(salt+"\n"), 0o600); err != nil { + return "", errors.WrapAndTrace(err) + } + if err := ensurePermissions(path); err != nil { + return "", errors.WrapAndTrace(err) + } + return salt, nil +} + +func computeHardwareFingerprint(_ telemetry.HardwareInfo) (string, error) { + return rand.Text(), nil +} diff --git a/pkg/brevdaemon/agent/identity/identity_test.go b/pkg/brevdaemon/agent/identity/identity_test.go new file mode 100644 index 00000000..3d0857c6 --- /dev/null +++ b/pkg/brevdaemon/agent/identity/identity_test.go @@ -0,0 +1,167 @@ +package identity + +import ( + "context" + "os" + "path/filepath" + "testing" + + brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" + "connectrpc.com/connect" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/config" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/telemetry" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest" +) + +func TestIdentityStoreSaveAndLoad(t *testing.T) { + dir := t.TempDir() + cfg := config.Config{ + StateDir: dir, + DeviceTokenPath: filepath.Join(dir, "device_token"), + } + + store := NewIdentityStore(cfg) + id := Identity{ + InstanceID: "inst-123", + DeviceToken: "token-abc", + DeviceSalt: "salt-xyz", + DeviceFingerprintHash: "fp-device-hash", + DeviceFingerprintStored: "fp-device-scoped", + HardwareFingerprint: "fp-hw", + } + + require.NoError(t, store.Save(id)) + + loaded, ok, err := store.Load() + require.NoError(t, err) + require.True(t, ok) + require.Equal(t, id.InstanceID, loaded.InstanceID) + require.Equal(t, id.DeviceToken, loaded.DeviceToken) + require.Equal(t, id.DeviceSalt, loaded.DeviceSalt) + require.Equal(t, id.DeviceFingerprintHash, loaded.DeviceFingerprintHash) + require.Equal(t, id.DeviceFingerprintStored, loaded.DeviceFingerprintStored) + require.Equal(t, id.HardwareFingerprint, loaded.HardwareFingerprint) +} + +func TestIdentityStoreLoadMissing(t *testing.T) { + dir := t.TempDir() + cfg := config.Config{ + StateDir: dir, + DeviceTokenPath: filepath.Join(dir, "device_token"), + } + + store := NewIdentityStore(cfg) + _, ok, err := store.Load() + require.NoError(t, err) + require.False(t, ok) +} + +func TestEnsureIdentityUsesStoredToken(t *testing.T) { + dir := t.TempDir() + cfg := config.Config{ + StateDir: dir, + DeviceTokenPath: filepath.Join(dir, "device_token"), + } + store := NewIdentityStore(cfg) + + err := store.Save(Identity{ + InstanceID: "existing", + DeviceToken: "token", + DeviceFingerprintHash: "existing-fp-hash", + HardwareFingerprint: "existing-hw", + }) + require.NoError(t, err) + + var registerCalled bool + client := &stubClient{ + registerFn: func(context.Context, *connect.Request[brevapiv2.RegisterRequest]) (*connect.Response[brevapiv2.RegisterResponse], error) { + registerCalled = true + return connect.NewResponse(&brevapiv2.RegisterResponse{}), nil + }, + } + + hw := telemetry.HardwareInfo{} + log := zaptest.NewLogger(t) + id, err := EnsureIdentity(context.Background(), cfg, client, store, hw, log) + require.NoError(t, err) + require.Equal(t, "existing", id.InstanceID) + require.False(t, registerCalled) +} + +func TestEnsureIdentityRegistersWhenMissing(t *testing.T) { + dir := t.TempDir() + cfg := config.Config{ + StateDir: dir, + DeviceTokenPath: filepath.Join(dir, "device_token"), + RegistrationToken: "reg-token", + DisplayName: "node", + CloudName: "cloud", + } + + store := NewIdentityStore(cfg) + + var captured *brevapiv2.RegisterRequest + client := &stubClient{ + registerFn: func(_ context.Context, req *connect.Request[brevapiv2.RegisterRequest]) (*connect.Response[brevapiv2.RegisterResponse], error) { + captured = req.Msg + return connect.NewResponse(&brevapiv2.RegisterResponse{ + BrevCloudNodeId: "fn-new", + DeviceToken: "new-token", + DeviceFingerprint: "scoped-device-fp", + }), nil + }, + } + + hw := telemetry.HardwareInfo{CPUCount: 8, RAMBytes: 1024} + log := zaptest.NewLogger(t) + + id, err := EnsureIdentity(context.Background(), cfg, client, store, hw, log) + require.NoError(t, err) + require.Equal(t, "fn-new", id.InstanceID) + require.Equal(t, "new-token", id.DeviceToken) + require.Equal(t, "scoped-device-fp", id.DeviceFingerprintStored) + + require.NotNil(t, captured) + require.Equal(t, "reg-token", captured.GetRegistrationToken()) + require.Equal(t, "node", captured.GetDisplayName()) + require.Equal(t, "cloud", captured.GetCloudName()) + require.NotNil(t, captured.GetHardware()) + + data, err := os.ReadFile(cfg.DeviceTokenPath) + require.NoError(t, err) + require.Contains(t, string(data), "new-token") +} + +func TestEnsureIdentityRequiresRegistrationToken(t *testing.T) { + dir := t.TempDir() + cfg := config.Config{ + StateDir: dir, + DeviceTokenPath: filepath.Join(dir, "device_token"), + } + store := NewIdentityStore(cfg) + log := zaptest.NewLogger(t) + + client := &stubClient{} + _, err := EnsureIdentity(context.Background(), cfg, client, store, telemetry.HardwareInfo{}, log) + require.Error(t, err) +} + +type stubClient struct { + registerFn func(ctx context.Context, req *connect.Request[brevapiv2.RegisterRequest]) (*connect.Response[brevapiv2.RegisterResponse], error) +} + +func (s *stubClient) Register(ctx context.Context, req *connect.Request[brevapiv2.RegisterRequest]) (*connect.Response[brevapiv2.RegisterResponse], error) { + if s.registerFn != nil { + return s.registerFn(ctx, req) + } + return connect.NewResponse(&brevapiv2.RegisterResponse{}), nil +} + +func (s *stubClient) Heartbeat(context.Context, *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) { + return connect.NewResponse(&brevapiv2.HeartbeatResponse{}), nil +} + +func (s *stubClient) GetTunnelToken(context.Context, *connect.Request[brevapiv2.GetTunnelTokenRequest]) (*connect.Response[brevapiv2.GetTunnelTokenResponse], error) { + return connect.NewResponse(&brevapiv2.GetTunnelTokenResponse{}), nil +} diff --git a/pkg/brevdaemon/agent/telemetry/hardware.go b/pkg/brevdaemon/agent/telemetry/hardware.go new file mode 100644 index 00000000..0a4cafcd --- /dev/null +++ b/pkg/brevdaemon/agent/telemetry/hardware.go @@ -0,0 +1,351 @@ +package telemetry + +import ( + "context" + "math" + "os" + "os/exec" + "runtime" + "strconv" + "strings" + + brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" + devplaneapiv1 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/devplaneapi/v1" + "github.com/brevdev/brev-cli/pkg/errors" + "golang.org/x/sys/unix" +) + +// HardwareInfo holds the coarse specs captured during registration. +type HardwareInfo struct { + CPUCount int + RAMBytes int64 + GPUs []GPUInfo + MachineModel string + Architecture string + Storage []StorageInfo +} + +// GPUInfo aggregates GPUs with identical model/memory characteristics. +type GPUInfo struct { + Model string + MemoryBytes int64 + Count int +} + +// StorageInfo captures best-effort block device capacity. +type StorageInfo struct { + Name string + SizeBytes int64 + Type string +} + +// DetectHardware collects CPU count, memory, and GPU details for the host. +func DetectHardware(ctx context.Context) (HardwareInfo, error) { + hw := HardwareInfo{ + CPUCount: runtime.NumCPU(), + Architecture: runtime.GOARCH, + } + + ram, err := systemRAMBytes() + if err != nil { + return HardwareInfo{}, errors.WrapAndTrace(err) + } + hw.RAMBytes = ram + + gpus, err := detectGPUs(ctx) + if err != nil { + return HardwareInfo{}, errors.WrapAndTrace(err) + } + hw.GPUs = gpus + + hw.Storage = detectStorage(ctx) + hw.MachineModel = detectMachineModel() + + return hw, nil +} + +// ToProto converts the telemetry DTO into the protobuf request payload used by the agent RPCs. +func (h HardwareInfo) ToProto() *brevapiv2.HardwareInfo { + out := &brevapiv2.HardwareInfo{ + CpuCount: clampToInt32(h.CPUCount), + } + if h.RAMBytes > 0 { + out.RamBytes = bytesValue(h.RAMBytes) + } + if h.MachineModel != "" { + out.SystemModel = protoString(h.MachineModel) + } + if h.Architecture != "" { + out.Architecture = protoString(h.Architecture) + } + if len(h.Storage) > 0 { + out.Storage = make([]*brevapiv2.StorageInfo, 0, len(h.Storage)) + for _, s := range h.Storage { + entry := &brevapiv2.StorageInfo{ + Name: s.Name, + Type: s.Type, + } + if s.SizeBytes > 0 { + entry.Capacity = bytesValue(s.SizeBytes) + } + out.Storage = append(out.Storage, entry) + } + } + if len(h.GPUs) > 0 { + out.Gpus = make([]*brevapiv2.GPUInfo, 0, len(h.GPUs)) + for _, gpu := range h.GPUs { + out.Gpus = append(out.Gpus, &brevapiv2.GPUInfo{ + Model: gpu.Model, + Count: clampToInt32(gpu.Count), + MemoryBytes: bytesValue(gpu.MemoryBytes), + }) + } + } + return out +} + +var ( + runtimeGOOS = runtime.GOOS + readFile = osReadFile + lookupExecutable = exec.LookPath + execCommand = runCommand +) + +func detectMachineModel() string { + if runtimeGOOS != goosLinux { + return "" + } + paths := []string{ + "/sys/devices/virtual/dmi/id/product_name", + "/sys/devices/virtual/dmi/id/board_name", + } + for _, path := range paths { + data, err := readFile(path) + if err != nil { + continue + } + val := strings.TrimSpace(string(data)) + if val != "" { + return val + } + } + return "" +} + +// detectStorage uses lsblk (if available) to list block devices with their sizes. +// It is best-effort and ignores errors; only linux is supported. +func detectStorage(ctx context.Context) []StorageInfo { + if runtimeGOOS != goosLinux { + return nil + } + if _, err := lookupExecutable("lsblk"); err != nil { + return nil + } + // -b: bytes, -d: no partitions, -o: NAME,TYPE,SIZE + out, err := execCommand(ctx, "lsblk", "-b", "-d", "-o", "NAME,TYPE,SIZE") + if err != nil { + return nil + } + lines := strings.Split(strings.TrimSpace(string(out)), "\n") + if len(lines) == 0 { + return nil + } + var storage []StorageInfo + for _, line := range lines[1:] { // skip header + fields := strings.Fields(line) + if len(fields) < 3 { + continue + } + name := fields[0] + typ := fields[1] + // Ignore loopback and other virtual block devices that are not real disks. + if typ == "loop" { + continue + } + sizeStr := fields[2] + if name == "" || typ == "" || sizeStr == "" { + continue + } + size, err := strconv.ParseInt(sizeStr, 10, 64) + if err != nil || size <= 0 { + continue + } + storage = append(storage, StorageInfo{ + Name: name, + SizeBytes: size, + Type: typ, + }) + } + return storage +} + +func systemRAMBytes() (int64, error) { + switch runtimeGOOS { + case goosLinux: + data, err := readFile("/proc/meminfo") + if err != nil { + return 0, errors.WrapAndTrace(err) + } + total, _, err := parseMeminfo(data) + return total, err + case darwin: + value, err := darwinMemoryBytes() + if err != nil { + return 0, errors.WrapAndTrace(err) + } + return value, nil + default: + return 0, errors.Errorf("unsupported platform %s", runtimeGOOS) + } +} + +func darwinMemoryBytes() (int64, error) { + value, err := unix.SysctlUint64("hw.memsize") + if err != nil { + return 0, errors.WrapAndTrace(err) + } + if value > math.MaxInt64 { + return 0, errors.Errorf("memsize exceeds supported range") + } + return int64(value), nil +} + +func detectGPUs(ctx context.Context) ([]GPUInfo, error) { + if _, err := lookupExecutable("nvidia-smi"); err != nil { + if errors.Is(err, exec.ErrNotFound) { + return nil, nil + } + return nil, errors.WrapAndTrace(err) + } + + out, err := execCommand(ctx, "nvidia-smi", "--query-gpu=name,memory.total", "--format=csv,noheader,nounits") + if err != nil { + return nil, errors.WrapAndTrace(err) + } + + return parseGPUHardware(string(out)) +} + +func parseGPUHardware(raw string) ([]GPUInfo, error) { + lines := strings.Split(strings.TrimSpace(raw), "\n") + counts := map[string]*GPUInfo{} + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" { + continue + } + parts := strings.Split(line, ",") + if len(parts) < 2 { + continue + } + model := strings.TrimSpace(parts[0]) + if model == "" { + continue + } + memStr := strings.TrimSpace(parts[1]) + if memStr == "" { + continue + } + + // Some drivers report "[N/A]" (e.g., GB10 unified memory). Try a known fallback before skipping. + var memBytes int64 + if strings.EqualFold(memStr, "n/a") || strings.EqualFold(memStr, "[n/a]") { + if mb, ok := knownMemoryForModel(model); ok { + memBytes = mb + } else { + continue + } + } else { + memMiB, err := strconv.ParseFloat(memStr, 64) + if err != nil { + if mb, ok := knownMemoryForModel(model); ok { + memBytes = mb + } else { + // Skip unparsable memory values rather than aborting the agent. + continue + } + } else { + memBytes = int64(memMiB * 1024 * 1024) + } + } + + key := model + "|" + strconv.FormatInt(memBytes, 10) + entry, ok := counts[key] + if !ok { + entry = &GPUInfo{ + Model: model, + MemoryBytes: memBytes, + } + counts[key] = entry + } + entry.Count++ + } + + if len(counts) == 0 { + return nil, nil + } + + results := make([]GPUInfo, 0, len(counts)) + for _, gpu := range counts { + results = append(results, *gpu) + } + return results, nil +} + +func knownMemoryForModel(model string) (int64, bool) { + lower := strings.ToLower(model) + switch { + case strings.Contains(lower, "gb10"): + // NVIDIA GB10 (Grace Blackwell Superchip) uses unified LPDDR5x shared between CPU/GPU. + // Prefer the system RAM total (unified memory) if available; otherwise fall back to 128GB. + if ram, err := systemRAMBytes(); err == nil && ram > 0 { + return ram, true + } + return 128 * 1024 * 1024 * 1024, true + default: + return 0, false + } +} + +func osReadFile(path string) ([]byte, error) { + // #nosec G304 -- path is derived from config or OS-specific defaults. + data, err := os.ReadFile(path) + if err != nil { + return nil, errors.WrapAndTrace(err) + } + return data, nil +} + +func runCommand(ctx context.Context, name string, args ...string) ([]byte, error) { + cmd := exec.CommandContext(ctx, name, args...) + out, err := cmd.Output() + if err != nil { + return nil, errors.WrapAndTrace(err) + } + return out, nil +} + +func bytesValue(v int64) *devplaneapiv1.Bytes { + if v <= 0 { + return nil + } + return &devplaneapiv1.Bytes{Value: v} +} + +func clampToInt32(v int) int32 { + switch { + case v > math.MaxInt32: + return math.MaxInt32 + case v < math.MinInt32: + return math.MinInt32 + default: + return int32(v) + } +} + +func protoString(value string) *string { + if value == "" { + return nil + } + return &value +} diff --git a/pkg/brevdaemon/agent/telemetry/hardware_test.go b/pkg/brevdaemon/agent/telemetry/hardware_test.go new file mode 100644 index 00000000..693b4960 --- /dev/null +++ b/pkg/brevdaemon/agent/telemetry/hardware_test.go @@ -0,0 +1,101 @@ +package telemetry + +import ( + "context" + "os/exec" + "runtime" + "testing" + + "github.com/brevdev/brev-cli/pkg/errors" + "github.com/stretchr/testify/require" +) + +func TestDetectHardwareWithoutGPU(t *testing.T) { + origGOOS := runtimeGOOS + runtimeGOOS = goosLinux + t.Cleanup(func() { runtimeGOOS = origGOOS }) + + origReadFile := readFile + readFile = func(path string) ([]byte, error) { + switch path { + case "/proc/meminfo": + return []byte("MemTotal: 2048 kB\nMemAvailable: 1024 kB\n"), nil + case "/sys/devices/virtual/dmi/id/product_name": + return []byte("DGX-SYSTEM\n"), nil + default: + return nil, errors.Errorf("unexpected path %s", path) + } + } + t.Cleanup(func() { readFile = origReadFile }) + + origLookup := lookupExecutable + lookupExecutable = func(cmd string) (string, error) { + if cmd == "lsblk" { + return "/bin/lsblk", nil + } + return "", exec.ErrNotFound + } + t.Cleanup(func() { lookupExecutable = origLookup }) + + origExec := execCommand + execCommand = func(_ context.Context, name string, _ ...string) ([]byte, error) { + require.Equal(t, "lsblk", name) + return []byte("NAME TYPE SIZE\nsda disk 102400000000\n"), nil + } + t.Cleanup(func() { execCommand = origExec }) + + hw, err := DetectHardware(context.Background()) + require.NoError(t, err) + require.Equal(t, int64(2048*1024), hw.RAMBytes) + require.Empty(t, hw.GPUs) + require.Equal(t, "DGX-SYSTEM", hw.MachineModel) + require.Len(t, hw.Storage, 1) + require.Equal(t, int64(102400000000), hw.Storage[0].SizeBytes) + require.Equal(t, "sda", hw.Storage[0].Name) + require.Equal(t, "disk", hw.Storage[0].Type) + require.Equal(t, runtime.GOARCH, hw.Architecture) +} + +func TestParseGPUHardwareAggregatesCounts(t *testing.T) { + raw := ` +A100-SXM4-40GB, 40960 +A100-SXM4-40GB, 40960 +RTX 6000, 24576 +` + gpus, err := parseGPUHardware(raw) + require.NoError(t, err) + require.Len(t, gpus, 2) + + results := map[string]GPUInfo{} + for _, gpu := range gpus { + results[gpu.Model] = gpu + } + + require.Equal(t, 2, results["A100-SXM4-40GB"].Count) + require.Equal(t, int64(40960*1024*1024), results["A100-SXM4-40GB"].MemoryBytes) + require.Equal(t, 1, results["RTX 6000"].Count) +} + +func TestHardwareToProtoConversion(t *testing.T) { + info := HardwareInfo{ + CPUCount: 8, + RAMBytes: 32 << 30, + Architecture: "arm64", + GPUs: []GPUInfo{ + {Model: "A100", MemoryBytes: 40 << 30, Count: 2}, + }, + Storage: []StorageInfo{ + {Name: "nvme0n1", SizeBytes: 512 << 30, Type: "nvme"}, + }, + } + clientInfo := info.ToProto() + require.Equal(t, int32(8), clientInfo.GetCpuCount()) + require.Equal(t, int64(32<<30), clientInfo.GetRamBytes().GetValue()) + require.Equal(t, "arm64", clientInfo.GetArchitecture()) + require.Len(t, clientInfo.GetGpus(), 1) + require.Equal(t, int32(2), clientInfo.GetGpus()[0].GetCount()) + require.Len(t, clientInfo.GetStorage(), 1) + require.Equal(t, "nvme0n1", clientInfo.GetStorage()[0].GetName()) + require.Equal(t, int64(512<<30), clientInfo.GetStorage()[0].GetCapacity().GetValue()) + require.Equal(t, "nvme", clientInfo.GetStorage()[0].GetType()) +} diff --git a/pkg/brevdaemon/agent/telemetry/os_consts.go b/pkg/brevdaemon/agent/telemetry/os_consts.go new file mode 100644 index 00000000..66b8a49f --- /dev/null +++ b/pkg/brevdaemon/agent/telemetry/os_consts.go @@ -0,0 +1,6 @@ +package telemetry + +const ( + goosLinux = "linux" + darwin = "darwin" +) diff --git a/pkg/brevdaemon/agent/telemetry/utilization.go b/pkg/brevdaemon/agent/telemetry/utilization.go new file mode 100644 index 00000000..9baacbbe --- /dev/null +++ b/pkg/brevdaemon/agent/telemetry/utilization.go @@ -0,0 +1,400 @@ +package telemetry + +import ( + "bufio" + "bytes" + "context" + "io" + "math" + "math/big" + "os/exec" + "strconv" + "strings" + "time" + + brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" + "github.com/brevdev/brev-cli/pkg/errors" + "golang.org/x/sys/unix" +) + +// UtilizationInfo captures runtime metrics reported via heartbeat. +type UtilizationInfo struct { + CPUPercent float32 + MemoryUsedBytes int64 + MemoryTotalBytes int64 + DiskPercent float32 + DiskUsedBytes int64 + DiskTotalBytes int64 + GPUs []GPUUtilization +} + +// GPUUtilization mirrors the subset of nvidia-smi fields used in heartbeats. +type GPUUtilization struct { + Index int + Model string + UtilizationPercent float32 + MemoryUsedBytes int64 + MemoryTotalBytes int64 + TemperatureCelsius *float32 +} + +// ToProto converts the telemetry DTO into the protobuf heartbeat payload. +func (u UtilizationInfo) ToProto() *brevapiv2.ResourceUtilization { + out := &brevapiv2.ResourceUtilization{ + CpuPercent: u.CPUPercent, + DiskPercent: u.DiskPercent, + } + if u.MemoryUsedBytes > 0 { + out.MemoryUsed = bytesValue(u.MemoryUsedBytes) + } + if u.MemoryTotalBytes > 0 { + out.MemoryTotal = bytesValue(u.MemoryTotalBytes) + } + if u.DiskUsedBytes > 0 { + out.DiskUsed = bytesValue(u.DiskUsedBytes) + } + if u.DiskTotalBytes > 0 { + out.DiskTotal = bytesValue(u.DiskTotalBytes) + } + if len(u.GPUs) > 0 { + out.Gpus = make([]*brevapiv2.GPUUtilization, 0, len(u.GPUs)) + for _, gpu := range u.GPUs { + out.Gpus = append(out.Gpus, gpuUtilizationToProto(gpu)) + } + } + return out +} + +const cpuSampleInterval = 200 * time.Millisecond + +var ( + statfsFunc = unix.Statfs + cpuSampleWait = cpuSampleInterval +) + +// SampleUtilization returns best-effort runtime metrics. +func SampleUtilization(ctx context.Context) (UtilizationInfo, error) { + util := UtilizationInfo{} + + if runtimeGOOS == "linux" { + used, total, err := readLinuxMemory() + if err != nil { + return UtilizationInfo{}, errors.WrapAndTrace(err) + } + util.MemoryUsedBytes = used + util.MemoryTotalBytes = total + + if pct, err := sampleLinuxCPUPercent(ctx); err == nil { + util.CPUPercent = pct + } else { + return UtilizationInfo{}, errors.WrapAndTrace(err) + } + } else { + if total, err := systemRAMBytes(); err == nil { + util.MemoryTotalBytes = total + } + } + + if used, total, pct, err := diskUsage("/"); err == nil { + util.DiskUsedBytes = used + util.DiskTotalBytes = total + util.DiskPercent = pct + } + + if gpus, err := detectGPUUtilization(ctx); err == nil { + util.GPUs = gpus + } + + return util, nil +} + +func readLinuxMemory() (used, total int64, err error) { + data, err := readFile("/proc/meminfo") + if err != nil { + return 0, 0, errors.WrapAndTrace(err) + } + total, available, err := parseMeminfo(data) + if err != nil { + return 0, 0, errors.WrapAndTrace(err) + } + return total - available, total, nil +} + +func parseMeminfo(data []byte) (total, available int64, err error) { + scanner := bufio.NewScanner(bytes.NewReader(data)) + var ( + memFree int64 + buffers int64 + cached int64 + ) + for scanner.Scan() { + line := scanner.Text() + switch { + case strings.HasPrefix(line, "MemTotal:"): + total, err = parseMeminfoValue(line) + if err != nil { + return 0, 0, err + } + case strings.HasPrefix(line, "MemAvailable:"): + available, err = parseMeminfoValue(line) + if err != nil { + return 0, 0, err + } + case strings.HasPrefix(line, "MemFree:"): + memFree, err = parseMeminfoValue(line) + if err != nil { + return 0, 0, err + } + case strings.HasPrefix(line, "Buffers:"): + buffers, err = parseMeminfoValue(line) + if err != nil { + return 0, 0, err + } + case strings.HasPrefix(line, "Cached:"): + cached, err = parseMeminfoValue(line) + if err != nil { + return 0, 0, err + } + } + } + if err := scanner.Err(); err != nil { + return 0, 0, errors.WrapAndTrace(err) + } + if total == 0 { + return 0, 0, errors.Errorf("meminfo missing MemTotal") + } + if available == 0 { + available = memFree + buffers + cached + } + if available > total { + available = total + } + return total, available, nil +} + +func parseMeminfoValue(line string) (int64, error) { + fields := strings.Fields(line) + if len(fields) < 2 { + return 0, errors.Errorf("invalid meminfo line: %s", line) + } + value, err := strconv.ParseInt(fields[1], 10, 64) + if err != nil { + return 0, errors.WrapAndTrace(err) + } + // Values are reported in kB. + return value * 1024, nil +} + +func sampleLinuxCPUPercent(ctx context.Context) (float32, error) { + first, err := readCPUTimes() + if err != nil { + return 0, err + } + + wait := cpuSampleWait + if wait > 0 { + timer := time.NewTimer(wait) + defer timer.Stop() + select { + case <-ctx.Done(): + return 0, errors.WrapAndTrace(ctx.Err()) + case <-timer.C: + } + } else { + select { + case <-ctx.Done(): + return 0, errors.WrapAndTrace(ctx.Err()) + default: + } + } + + second, err := readCPUTimes() + if err != nil { + return 0, err + } + + totalDelta := second.total - first.total + idleDelta := second.idle - first.idle + if totalDelta <= 0 { + return 0, nil + } + usage := float32(totalDelta-idleDelta) / float32(totalDelta) * 100 + if usage < 0 { + usage = 0 + } + if usage > 100 { + usage = 100 + } + return usage, nil +} + +type cpuTimes struct { + idle uint64 + total uint64 +} + +func readCPUTimes() (cpuTimes, error) { + data, err := readFile("/proc/stat") + if err != nil { + return cpuTimes{}, errors.WrapAndTrace(err) + } + return parseCPUStat(data) +} + +func parseCPUStat(data []byte) (cpuTimes, error) { + reader := bufio.NewReader(bytes.NewReader(data)) + line, err := reader.ReadString('\n') + if err != nil && !errors.Is(err, io.EOF) { + return cpuTimes{}, errors.WrapAndTrace(err) + } + fields := strings.Fields(line) + if len(fields) < 5 { + return cpuTimes{}, errors.Errorf("invalid cpu stat line: %s", line) + } + var total uint64 + var idle uint64 + for idx, field := range fields[1:] { + val, parseErr := strconv.ParseUint(field, 10, 64) + if parseErr != nil { + return cpuTimes{}, errors.WrapAndTrace(parseErr) + } + total += val + if idx == 3 { + idle = val + } + if idx == 4 { + idle += val // iowait counts as idle + } + } + return cpuTimes{idle: idle, total: total}, nil +} + +func diskUsage(path string) (used, total int64, percent float32, err error) { + var fs unix.Statfs_t + if err := statfsFunc(path, &fs); err != nil { + return 0, 0, 0, errors.WrapAndTrace(err) + } + blockSize := int64(fs.Bsize) + totalBlocks, err := safeUint64ToInt64(fs.Blocks) + if err != nil { + return 0, 0, 0, errors.WrapAndTrace(err) + } + freeBlocks, err := safeUint64ToInt64(fs.Bfree) + if err != nil { + return 0, 0, 0, errors.WrapAndTrace(err) + } + total, err = safeMulInt64(totalBlocks, blockSize) + if err != nil { + return 0, 0, 0, errors.WrapAndTrace(err) + } + free, err := safeMulInt64(freeBlocks, blockSize) + if err != nil { + return 0, 0, 0, errors.WrapAndTrace(err) + } + used = total - free + if total > 0 { + percent = float32(used) / float32(total) * 100 + } + return used, total, percent, nil +} + +func detectGPUUtilization(ctx context.Context) ([]GPUUtilization, error) { + if _, err := lookupExecutable("nvidia-smi"); err != nil { + if errors.Is(err, exec.ErrNotFound) { + return nil, nil + } + return nil, errors.WrapAndTrace(err) + } + + out, err := execCommand(ctx, "nvidia-smi", + "--query-gpu=index,name,utilization.gpu,memory.used,memory.total,temperature.gpu", + "--format=csv,noheader,nounits", + ) + if err != nil { + return nil, errors.WrapAndTrace(err) + } + return parseGPUUtilization(string(out)) +} + +func parseGPUUtilization(raw string) ([]GPUUtilization, error) { + lines := strings.Split(strings.TrimSpace(raw), "\n") + results := make([]GPUUtilization, 0, len(lines)) + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" { + continue + } + fields := strings.Split(line, ",") + if len(fields) < 6 { + continue + } + index, err := strconv.Atoi(strings.TrimSpace(fields[0])) + if err != nil { + return nil, errors.WrapAndTrace(err) + } + utilPercent, err := strconv.ParseFloat(strings.TrimSpace(fields[2]), 32) + if err != nil { + return nil, errors.WrapAndTrace(err) + } + memUsed, err := strconv.ParseFloat(strings.TrimSpace(fields[3]), 64) + if err != nil { + return nil, errors.WrapAndTrace(err) + } + memTotal, err := strconv.ParseFloat(strings.TrimSpace(fields[4]), 64) + if err != nil { + return nil, errors.WrapAndTrace(err) + } + tempStr := strings.TrimSpace(fields[5]) + var tempPtr *float32 + if tempStr != "" { + tempVal, err := strconv.ParseFloat(tempStr, 32) + if err != nil { + return nil, errors.WrapAndTrace(err) + } + tempF := float32(tempVal) + tempPtr = &tempF + } + results = append(results, GPUUtilization{ + Index: index, + Model: strings.TrimSpace(fields[1]), + UtilizationPercent: float32(utilPercent), + MemoryUsedBytes: int64(memUsed * 1024 * 1024), + MemoryTotalBytes: int64(memTotal * 1024 * 1024), + TemperatureCelsius: tempPtr, + }) + } + return results, nil +} + +func safeUint64ToInt64(val uint64) (int64, error) { + if val > math.MaxInt64 { + return 0, errors.Errorf("value %d exceeds int64 range", val) + } + return int64(val), nil +} + +func safeMulInt64(a, b int64) (int64, error) { + result := big.NewInt(0).Mul(big.NewInt(a), big.NewInt(b)) + if !result.IsInt64() { + return 0, errors.Errorf("multiplication overflow") + } + return result.Int64(), nil +} + +func gpuUtilizationToProto(gpu GPUUtilization) *brevapiv2.GPUUtilization { + out := &brevapiv2.GPUUtilization{ + Index: clampToInt32(gpu.Index), + Model: gpu.Model, + UtilizationPercent: gpu.UtilizationPercent, + } + if gpu.MemoryUsedBytes > 0 { + out.MemoryUsed = bytesValue(gpu.MemoryUsedBytes) + } + if gpu.MemoryTotalBytes > 0 { + out.MemoryTotal = bytesValue(gpu.MemoryTotalBytes) + } + if gpu.TemperatureCelsius != nil { + out.TemperatureCelsius = gpu.TemperatureCelsius + } + return out +} diff --git a/pkg/brevdaemon/agent/telemetry/utilization_test.go b/pkg/brevdaemon/agent/telemetry/utilization_test.go new file mode 100644 index 00000000..f12385d7 --- /dev/null +++ b/pkg/brevdaemon/agent/telemetry/utilization_test.go @@ -0,0 +1,118 @@ +package telemetry + +import ( + "context" + "os" + "os/exec" + "sync/atomic" + "testing" + + "github.com/stretchr/testify/require" + "golang.org/x/sys/unix" +) + +func TestUtilizationToProtoConversion(t *testing.T) { + temp := float32(65) + util := UtilizationInfo{ + CPUPercent: 55.5, + MemoryUsedBytes: 4 << 30, + MemoryTotalBytes: 16 << 30, + DiskPercent: 25, + DiskUsedBytes: 100 << 30, + DiskTotalBytes: 400 << 30, + GPUs: []GPUUtilization{ + { + Index: 0, + Model: "A100", + UtilizationPercent: 80, + MemoryUsedBytes: 10 << 30, + MemoryTotalBytes: 40 << 30, + TemperatureCelsius: &temp, + }, + }, + } + + clientUtil := util.ToProto() + require.Equal(t, util.CPUPercent, clientUtil.GetCpuPercent()) + require.Equal(t, util.MemoryUsedBytes, clientUtil.GetMemoryUsed().GetValue()) + require.Len(t, clientUtil.GetGpus(), 1) + require.Equal(t, temp, clientUtil.GetGpus()[0].GetTemperatureCelsius()) +} + +func TestParseMeminfoFallbacks(t *testing.T) { + data := []byte(`MemTotal: 2048 kB +MemFree: 512 kB +Buffers: 256 kB +Cached: 256 kB +`) + total, available, err := parseMeminfo(data) + require.NoError(t, err) + require.Equal(t, int64(2048*1024), total) + require.Equal(t, int64(1024*1024), available) +} + +func TestSampleUtilizationLinux(t *testing.T) { + origGOOS := runtimeGOOS + runtimeGOOS = goosLinux + t.Cleanup(func() { runtimeGOOS = origGOOS }) + + statReads := atomic.Int32{} + + origRead := readFile + readFile = func(path string) ([]byte, error) { + switch path { + case "/proc/meminfo": + return []byte("MemTotal: 2048 kB\nMemAvailable: 1024 kB\n"), nil + case "/proc/stat": + if statReads.Add(1) == 1 { + return []byte("cpu 100 0 100 200 0 0 0 0 0 0\n"), nil + } + return []byte("cpu 150 0 100 250 0 0 0 0 0 0\n"), nil + default: + return nil, os.ErrNotExist + } + } + t.Cleanup(func() { readFile = origRead }) + + origStatfs := statfsFunc + statfsFunc = func(_ string, st *unix.Statfs_t) error { + st.Blocks = 100 + st.Bsize = 1024 + st.Bfree = 20 + return nil + } + t.Cleanup(func() { statfsFunc = origStatfs }) + + origLookup := lookupExecutable + lookupExecutable = func(string) (string, error) { + return "", exec.ErrNotFound + } + t.Cleanup(func() { lookupExecutable = origLookup }) + + origWait := cpuSampleWait + cpuSampleWait = 0 + t.Cleanup(func() { cpuSampleWait = origWait }) + + util, err := SampleUtilization(context.Background()) + require.NoError(t, err) + require.Equal(t, int64(1024*1024), util.MemoryUsedBytes) + require.Equal(t, int64(2048*1024), util.MemoryTotalBytes) + require.InEpsilon(t, 50, util.CPUPercent, 0.01) + require.Equal(t, int64((100-20)*1024), util.DiskUsedBytes) + require.Equal(t, int64(100*1024), util.DiskTotalBytes) + require.InEpsilon(t, 80, util.DiskPercent, 0.01) +} + +func TestParseGPUUtilization(t *testing.T) { + raw := ` +0, A100, 80, 1024, 4096, 65 +1, A100, 60, 2048, 4096, 0 +` + gpus, err := parseGPUUtilization(raw) + require.NoError(t, err) + require.Len(t, gpus, 2) + require.Equal(t, 0, gpus[0].Index) + require.Equal(t, float32(80), gpus[0].UtilizationPercent) + require.Equal(t, int64(1024*1024*1024), gpus[0].MemoryUsedBytes) + require.NotNil(t, gpus[0].TemperatureCelsius) +} diff --git a/pkg/brevdaemon/agent/tunnel/tunnel.go b/pkg/brevdaemon/agent/tunnel/tunnel.go new file mode 100644 index 00000000..9c9edfdb --- /dev/null +++ b/pkg/brevdaemon/agent/tunnel/tunnel.go @@ -0,0 +1,335 @@ +package tunnel + +import ( + "context" + cryptorand "crypto/rand" + "fmt" + "math" + "math/big" + "os" + "os/exec" + "time" + + brevapiv2connect "buf.build/gen/go/brevdev/devplane/connectrpc/go/brevapi/v2/brevapiv2connect" + brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" + "connectrpc.com/connect" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/client" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/health" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/identity" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/telemetry" + "github.com/brevdev/brev-cli/pkg/errors" + "github.com/brevdev/dev-plane/pkg/brevcloud/appaccess" + "go.uber.org/zap" +) + +const ( + defaultTunnelName = "default" + + minRetryDelay = 5 * time.Second + maxRetryDelay = 5 * time.Minute +) + +// TunnelConfig configures tunnel management. +type TunnelConfig struct { + SSHPort int32 + ClientBinary string +} + +// Manager fetches tunnel tokens and boots the tunnel client. +type Manager struct { + Client brevapiv2connect.BrevCloudAgentServiceClient + Identity identity.Identity + Cfg TunnelConfig + Log *zap.Logger + + CommandFactory func(ctx context.Context, name string, args ...string) Command + Health StatusPublisher + Sleep func(context.Context, time.Duration) error + LookPath func(string) (string, error) + + DetectHardware func(context.Context) (telemetry.HardwareInfo, error) + ProbeTimeout time.Duration + AppConfig appaccess.Config + + jitter func(time.Duration) time.Duration +} + +// Command abstracts exec.Cmd for testability. +type Command interface { + Start() error + Wait() error + SetEnv([]string) + CombinedOutput() ([]byte, error) +} + +// StatusPublisher captures the subset of health reporter APIs used by the tunnel manager. +type StatusPublisher interface { + MarkActive(detail string) health.Status + MarkError(detail string) health.Status +} + +// Start requests a tunnel token and launches the tunnel binary. It blocks until +// the context is canceled or the process exits unexpectedly. +func (m *Manager) Start(ctx context.Context) error { //nolint:gocognit,gocyclo,funlen // orchestration loop with retries and state handling + if err := m.validate(); err != nil { + return errors.WrapAndTrace(err) + } + + sleepFn := m.Sleep + if sleepFn == nil { + sleepFn = defaultSleep + } + jitterFn := m.jitter + if jitterFn == nil { + jitterFn = addJitter + } + + req := &brevapiv2.GetTunnelTokenRequest{ + BrevCloudNodeId: m.Identity.InstanceID, + RequestedPorts: tunnelPortsToProto([]brevapiv2.TunnelPortMapping{ + { + LocalPort: m.Cfg.SSHPort, + }, + }), + TunnelName: client.ProtoString(defaultTunnelName), + } + + retryDelay := minRetryDelay + + for { + if errors.Is(ctx.Err(), context.Canceled) { + return nil + } + + req.AppIngresses = nil + + token, err := m.requestTunnelToken(ctx, req) + if err != nil { + if errors.Is(err, context.Canceled) { + return nil + } + m.publishError("failed to fetch tunnel token", err) + if err := m.delay(ctx, sleepFn, jitterFn(retryDelay)); err != nil { + if errors.Is(err, context.Canceled) { + return nil + } + return err + } + retryDelay = nextDelay(retryDelay) + continue + } + + if err := m.configureCloudflaredService(ctx, token); err != nil { + if errors.Is(err, context.Canceled) && errors.Is(ctx.Err(), context.Canceled) { + return nil + } + m.publishError("failed to configure cloudflared service", err) + if err := m.delay(ctx, sleepFn, jitterFn(retryDelay)); err != nil { + if errors.Is(err, context.Canceled) { + return nil + } + return err + } + retryDelay = nextDelay(retryDelay) + continue + } + + m.markActive("cloudflared service configured") + return nil + } +} + +func (m *Manager) validate() error { + if m.Client == nil { + return errors.Errorf("client is required") + } + if m.Log == nil { + return errors.Errorf("logger is required") + } + if m.Identity.DeviceToken == "" || m.Identity.InstanceID == "" { + return errors.Errorf("identity is required") + } + if m.Cfg.SSHPort <= 0 { + m.Cfg.SSHPort = 22 + } + return nil +} + +func (m *Manager) newCommand(ctx context.Context, name string, args ...string) Command { + if m.CommandFactory != nil { + return m.CommandFactory(ctx, name, args...) + } + return &execCmdWrapper{Cmd: exec.CommandContext(ctx, name, args...)} +} + +type execCmdWrapper struct { + *exec.Cmd +} + +func (w *execCmdWrapper) SetEnv(env []string) { + w.Env = env +} + +func (w *execCmdWrapper) CombinedOutput() ([]byte, error) { + out, err := w.Cmd.CombinedOutput() + if err != nil { + return out, errors.WrapAndTrace(err) + } + return out, nil +} + +func (m *Manager) requestTunnelToken(ctx context.Context, req *brevapiv2.GetTunnelTokenRequest) (*brevapiv2.GetTunnelTokenResponse, error) { + connectReq := connect.NewRequest(req) + connectReq.Header().Set("Authorization", client.BearerToken(m.Identity.DeviceToken)) + + resp, err := m.Client.GetTunnelToken(ctx, connectReq) + if err != nil { + return nil, errors.WrapAndTrace(client.ClassifyError(err)) + } + return resp.Msg, nil +} + +func (m *Manager) delay(ctx context.Context, sleepFn func(context.Context, time.Duration) error, delay time.Duration) error { + return m.sleepWithBackoff(ctx, sleepFn, delay) +} + +func (m *Manager) publishError(msg string, err error, fields ...zap.Field) { + if err != nil { + fields = append(fields, zap.Error(err)) + m.Log.Warn(msg, fields...) + m.markError(fmt.Sprintf("%s: %v", msg, err)) + return + } + m.Log.Warn(msg, fields...) + m.markError(msg) +} + +func (m *Manager) markError(detail string) { + if m.Health != nil { + m.Health.MarkError(detail) + } +} + +func (m *Manager) markActive(detail string) { + if m.Health != nil { + m.Health.MarkActive(detail) + } +} + +func (m *Manager) configureCloudflaredService(ctx context.Context, token *brevapiv2.GetTunnelTokenResponse) error { + commands := []struct { + name string + args []string + }{ + { + name: "sudo", + args: []string{"cloudflared", "service", "install", token.GetToken()}, + }, + } + + for _, cmdSpec := range commands { + if err := m.runCommand(ctx, cmdSpec.name, cmdSpec.args...); err != nil { + return errors.WrapAndTrace(err) + } + } + + return nil +} + +func (m *Manager) runCommand(ctx context.Context, name string, args ...string) error { + cmd := m.newCommand(ctx, name, args...) + cmd.SetEnv(os.Environ()) + out, err := cmd.CombinedOutput() + if err != nil { + return errors.WrapAndTrace(fmt.Errorf("command %s %v failed: %w\n%s", name, args, err, string(out))) + } + return nil +} + +func (m *Manager) sleepWithBackoff(ctx context.Context, sleepFn func(context.Context, time.Duration) error, delay time.Duration) error { + if delay <= 0 { + return nil + } + if err := sleepFn(ctx, delay); err != nil { + return errors.WrapAndTrace(err) + } + return nil +} + +func nextDelay(current time.Duration) time.Duration { + if current <= 0 { + return minRetryDelay + } + next := current * 2 + if next > maxRetryDelay { + return maxRetryDelay + } + return next +} + +func addJitter(delay time.Duration) time.Duration { + if delay <= 0 { + return 0 + } + + randRange := big.NewInt(401) // 0-400 inclusive + n, err := cryptorand.Int(cryptorand.Reader, randRange) + if err != nil { + return delay + } + factor := 0.8 + float64(n.Int64())/1000.0 + return time.Duration(float64(delay) * factor) +} + +func tunnelPortsToProto(ports []brevapiv2.TunnelPortMapping) []*brevapiv2.TunnelPortMapping { + if len(ports) == 0 { + return nil + } + out := make([]*brevapiv2.TunnelPortMapping, 0, len(ports)) + for _, port := range ports { + lp := port.LocalPort + rp := port.RemotePort + if lp <= 0 && rp <= 0 { + continue + } + if rp <= 0 { + rp = lp + } + if lp <= 0 { + lp = rp + } + if rp > math.MaxInt32 || lp > math.MaxInt32 || lp < math.MinInt32 || rp < math.MinInt32 { + continue + } + out = append(out, &brevapiv2.TunnelPortMapping{ + LocalPort: lp, + RemotePort: rp, + Protocol: port.Protocol, + }) + } + if len(out) == 0 { + return nil + } + return out +} + +func defaultSleep(ctx context.Context, d time.Duration) error { + if d <= 0 { + return nil + } + timer := time.NewTimer(d) + defer timer.Stop() + select { + case <-ctx.Done(): + return errors.WrapAndTrace(ctx.Err()) + case <-timer.C: + return nil + } +} + +func (m *Manager) detectHardwareFunc() func(context.Context) (telemetry.HardwareInfo, error) { + if m.DetectHardware != nil { + return m.DetectHardware + } + return telemetry.DetectHardware +} diff --git a/pkg/brevdaemon/agent/tunnel/tunnel_test.go b/pkg/brevdaemon/agent/tunnel/tunnel_test.go new file mode 100644 index 00000000..3d999f77 --- /dev/null +++ b/pkg/brevdaemon/agent/tunnel/tunnel_test.go @@ -0,0 +1,192 @@ +package tunnel + +import ( + "context" + "errors" + "testing" + "time" + + brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" + "connectrpc.com/connect" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/health" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/identity" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest" +) + +func TestManagerConfiguresCloudflaredService(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + requested := false + stubClient := &stubClient{ + getTunnelFn: func(context.Context, *connect.Request[brevapiv2.GetTunnelTokenRequest]) (*connect.Response[brevapiv2.GetTunnelTokenResponse], error) { + requested = true + return connect.NewResponse(&brevapiv2.GetTunnelTokenResponse{ + Token: "token", + Endpoint: "endpoint", + }), nil + }, + } + + var executed []struct { + name string + args []string + } + reporter := &fakeReporter{} + manager := Manager{ + Client: stubClient, + Identity: identity.Identity{ + InstanceID: "inst", + DeviceToken: "device", + }, + Cfg: TunnelConfig{SSHPort: 22}, + Log: zaptest.NewLogger(t), + Health: reporter, + CommandFactory: func(_ context.Context, name string, args ...string) Command { + executed = append(executed, struct { + name string + args []string + }{name: name, args: args}) + return &fakeCmd{ + startFn: func() error { + return nil + }, + waitFn: func() error { return nil }, + } + }, + jitter: func(d time.Duration) time.Duration { return d }, + } + + err := manager.Start(ctx) + require.NoError(t, err) + require.True(t, requested) + require.Len(t, executed, 1) + require.Equal(t, "sudo", executed[0].name) + require.Equal(t, []string{"cloudflared", "service", "install", "token"}, executed[0].args) + require.NotEmpty(t, reporter.actives) + require.Contains(t, reporter.actives[0], "cloudflared service configured") +} + +func TestManagerRetriesOnConfigureError(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var sleepCalls int + reporter := &fakeReporter{} + + stubClient := &stubClient{ + getTunnelFn: func(context.Context, *connect.Request[brevapiv2.GetTunnelTokenRequest]) (*connect.Response[brevapiv2.GetTunnelTokenResponse], error) { + return connect.NewResponse(&brevapiv2.GetTunnelTokenResponse{ + Token: "token", + Endpoint: "endpoint", + }), nil + }, + } + + commandCalls := 0 + + manager := Manager{ + Client: stubClient, + Health: reporter, + jitter: func(d time.Duration) time.Duration { return d }, + Log: zaptest.NewLogger(t), + Sleep: func(_ context.Context, _ time.Duration) error { + sleepCalls++ + return nil + }, + Identity: identity.Identity{ + InstanceID: "inst", + DeviceToken: "device", + }, + CommandFactory: func(cmdCtx context.Context, _ string, _ ...string) Command { + commandCalls++ + if commandCalls == 1 { + return &fakeCmd{ + startFn: func() error { return errors.New("boom") }, + waitFn: func() error { return nil }, + } + } + return &fakeCmd{ + startFn: func() error { return nil }, + waitFn: func() error { + cancel() + <-cmdCtx.Done() + return context.Canceled + }, + } + }, + } + + err := manager.Start(ctx) + require.NoError(t, err) + require.Equal(t, 2, commandCalls) + require.GreaterOrEqual(t, sleepCalls, 1) + require.NotEmpty(t, reporter.errors) + require.Contains(t, reporter.errors[0], "failed to configure cloudflared service") +} + +type fakeCmd struct { + startFn func() error + waitFn func() error + env []string +} + +func (f *fakeCmd) Start() error { + if f.startFn != nil { + return f.startFn() + } + return nil +} + +func (f *fakeCmd) Wait() error { + if f.waitFn != nil { + return f.waitFn() + } + return nil +} + +func (f *fakeCmd) CombinedOutput() ([]byte, error) { + if err := f.Start(); err != nil { + return nil, err + } + return nil, f.Wait() +} + +func (f *fakeCmd) SetEnv(env []string) { + f.env = env +} + +type fakeReporter struct { + errors []string + actives []string +} + +func (f *fakeReporter) MarkActive(detail string) health.Status { + f.actives = append(f.actives, detail) + return health.Status{} +} + +func (f *fakeReporter) MarkError(detail string) health.Status { + f.errors = append(f.errors, detail) + return health.Status{} +} + +type stubClient struct { + getTunnelFn func(context.Context, *connect.Request[brevapiv2.GetTunnelTokenRequest]) (*connect.Response[brevapiv2.GetTunnelTokenResponse], error) +} + +func (s *stubClient) Register(context.Context, *connect.Request[brevapiv2.RegisterRequest]) (*connect.Response[brevapiv2.RegisterResponse], error) { + return connect.NewResponse(&brevapiv2.RegisterResponse{}), nil +} + +func (s *stubClient) Heartbeat(context.Context, *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) { + return connect.NewResponse(&brevapiv2.HeartbeatResponse{}), nil +} + +func (s *stubClient) GetTunnelToken(ctx context.Context, req *connect.Request[brevapiv2.GetTunnelTokenRequest]) (*connect.Response[brevapiv2.GetTunnelTokenResponse], error) { + if s.getTunnelFn != nil { + return s.getTunnelFn(ctx, req) + } + return connect.NewResponse(&brevapiv2.GetTunnelTokenResponse{}), nil +} diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index 2980b0ce..61c4a26b 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -223,6 +223,10 @@ func NewBrevCommand() *cobra.Command { //nolint:funlen,gocognit,gocyclo // defin cobra.AddTemplateFunc("workspaceCommands", workspaceCommands) cobra.AddTemplateFunc("hasProviderDependentCommands", hasProviderDependentCommands) cobra.AddTemplateFunc("providerDependentCommands", providerDependentCommands) + cobra.AddTemplateFunc("hasRegisterCommands", hasRegisterCommands) + cobra.AddTemplateFunc("registerCommands", registerCommands) + cobra.AddTemplateFunc("subTreeCommands", subTreeCommands) + cobra.AddTemplateFunc("isSubTreeCommand", isSubTreeCommand) cobra.AddTemplateFunc("hasAccessCommands", hasAccessCommands) cobra.AddTemplateFunc("accessCommands", accessCommands) cobra.AddTemplateFunc("hasOrganizationCommands", hasOrganizationCommands) @@ -287,7 +291,9 @@ func createCmdTree(cmd *cobra.Command, t *terminal.Terminal, loginCmdStore *stor cmd.AddCommand(reset.NewCmdReset(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(profile.NewCmdProfile(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(refresh.NewCmdRefresh(t, loginCmdStore)) - cmd.AddCommand(register.NewCmdRegister(t)) + cmd.AddCommand(register.NewCmdRegister(t, loginCmdStore)) + cmd.AddCommand(register.NewCmdBrevAgent(t)) + cmd.AddCommand(register.NewCmdUnregister(t, loginCmdStore)) cmd.AddCommand(runtasks.NewCmdRunTasks(t, noLoginCmdStore)) cmd.AddCommand(proxy.NewCmdProxy(t, noLoginCmdStore)) cmd.AddCommand(healthcheck.NewCmdHealthcheck(t, noLoginCmdStore)) @@ -326,6 +332,10 @@ func hasProviderDependentCommands(cmd *cobra.Command) bool { return len(providerDependentCommands(cmd)) > 0 } +func hasRegisterCommands(cmd *cobra.Command) bool { + return len(registerCommands(cmd)) > 0 +} + func workspaceCommands(cmd *cobra.Command) []*cobra.Command { cmds := []*cobra.Command{} for _, sub := range cmd.Commands() { @@ -396,6 +406,28 @@ func providerDependentCommands(cmd *cobra.Command) []*cobra.Command { return cmds } +func registerCommands(cmd *cobra.Command) []*cobra.Command { + cmds := []*cobra.Command{} + for _, sub := range cmd.Commands() { + if isRegisterCommand(sub) { + cmds = append(cmds, sub) + } + } + return cmds +} + +// SubTreeCommands are commands that themselves have a tree of subcommands. Use of the 'sub-tree' annotation +// will mark commands as this way, adding the 'Available Commands' section to the hep text. +func subTreeCommands(cmd *cobra.Command) []*cobra.Command { + cmds := []*cobra.Command{} + for _, sub := range cmd.Commands() { + if isSubTreeCommand(sub) { + cmds = append(cmds, sub) + } + } + return cmds +} + func isWorkspaceCommand(cmd *cobra.Command) bool { _, ok := cmd.Annotations["workspace"] return ok @@ -431,6 +463,16 @@ func isProviderDependentCommand(cmd *cobra.Command) bool { return ok } +func isRegisterCommand(cmd *cobra.Command) bool { + _, ok := cmd.Annotations["register"] + return ok +} + +func isSubTreeCommand(cmd *cobra.Command) bool { + _, ok := cmd.Annotations["sub-tree"] + return ok +} + var usageTemplate = `Usage:{{if .Runnable}} {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} @@ -489,8 +531,18 @@ Debug Commands: {{rpad .Name .NamePadding }} {{.Short}} {{- end}}{{- end}} -{{- end}}{{if .HasAvailableLocalFlags}} +{{- end}} + +{{- if hasRegisterCommands . }} +Register Commands: +{{- range registerCommands . }} + {{rpad .Name .NamePadding }} {{.Short}} +{{- end}}{{- end}} +{{if isSubTreeCommand . }} +{{$cmds := .Commands}}Available Commands:{{range $cmds}} + {{rpad .Name .NamePadding }} {{.Short}} +{{- end}}{{- end}}{{if .HasAvailableLocalFlags}} Flags: {{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} diff --git a/pkg/cmd/configureenvvars/configureenvvars_test.go b/pkg/cmd/configureenvvars/configureenvvars_test.go index 01272f04..6616bdba 100644 --- a/pkg/cmd/configureenvvars/configureenvvars_test.go +++ b/pkg/cmd/configureenvvars/configureenvvars_test.go @@ -148,7 +148,7 @@ export ` + BrevManagedEnvVarsKey + "=foo", got := generateExportString(tt.args.brevEnvsString, tt.args.envFileContents) diff := cmp.Diff(tt.want, got) if diff != "" { - t.Fatalf(diff) + t.Fatalf("%s", diff) } }) } @@ -234,7 +234,7 @@ func Test_addUnsetEntriesToOutput(t *testing.T) { got := addUnsetEntriesToOutput(tt.args.currentEnvs, tt.args.newEnvs, tt.args.output) diff := cmp.Diff(tt.want, got) if diff != "" { - t.Fatalf(diff) + t.Fatalf("%s", diff) } }) } diff --git a/pkg/cmd/connect/connect.go b/pkg/cmd/connect/connect.go index dbb26346..e7ad93f5 100644 --- a/pkg/cmd/connect/connect.go +++ b/pkg/cmd/connect/connect.go @@ -35,7 +35,7 @@ func NewCmdConnect(t *terminal.Terminal, store connectStore) *cobra.Command { func RunConnect(t *terminal.Terminal, _ []string, _ connectStore) error { t.Vprintf("Connect the AWS IAM user to create instances in your AWS account.\n") - t.Vprintf(t.Yellow("\tFollow the guide here: %s", "https://onboarding.brev.dev/connect-aws\n\n")) + t.Vprint(t.Yellow("\tFollow the guide here: %s", "https://onboarding.brev.dev/connect-aws\n\n")) // t.Vprintf(t.Yellow("Connect the AWS IAM user to create dev environments in your AWS account.\n\n")) AccessKeyID := terminal.PromptGetInput(terminal.PromptContent{ @@ -49,9 +49,9 @@ func RunConnect(t *terminal.Terminal, _ []string, _ connectStore) error { Mask: '*', }) - t.Vprintf("\n") - t.Vprintf(AccessKeyID) - t.Vprintf(SecretAccessKey) + t.Vprint("\n") + t.Vprint(AccessKeyID) + t.Vprint(SecretAccessKey) return nil } diff --git a/pkg/cmd/register/agent.go b/pkg/cmd/register/agent.go new file mode 100644 index 00000000..7500e3c4 --- /dev/null +++ b/pkg/cmd/register/agent.go @@ -0,0 +1,45 @@ +package register + +import ( + "fmt" + "time" + + "github.com/spf13/cobra" + + "github.com/brevdev/brev-cli/pkg/terminal" +) + +const ( + agentShort = "Run the Brev agent daemon" + agentLong = "Runs the Brev agent daemon in a continuous loop, reporting status and handling workloads." +) + +func NewCmdBrevAgent(t *terminal.Terminal) *cobra.Command { + cmd := &cobra.Command{ + Use: "agent", + Short: agentShort, + Long: agentLong, + RunE: func(cmd *cobra.Command, args []string) error { + return runBrevAgent(t) + }, + } + return cmd +} + +func runBrevAgent(t *terminal.Terminal) error { + t.Vprint(t.Green("Starting Brev agent daemon...\n")) + + ticker := time.NewTicker(1 * time.Minute) + defer ticker.Stop() + + // Print immediately on startup + t.Vprint(fmt.Sprintf("[%s] Brev agent running\n", time.Now().Format(time.RFC3339))) + + // TODO this should call the logic in pkg/brevdaemon/agent.go + for { + select { + case <-ticker.C: + t.Vprint(fmt.Sprintf("[%s] Brev agent heartbeat\n", time.Now().Format(time.RFC3339))) + } + } +} diff --git a/pkg/cmd/register/install-binary.sh b/pkg/cmd/register/install-binary.sh new file mode 100644 index 00000000..36d8fa17 --- /dev/null +++ b/pkg/cmd/register/install-binary.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -eo pipefail + +# Installs the latest brev-cli binary as "brevd" from GitHub releases + +# Detect OS and architecture +OS="$(uname -s | tr '[:upper:]' '[:lower:]')" +ARCH="$(uname -m)" +case "${ARCH}" in + x86_64) ARCH="amd64" ;; + aarch64) ARCH="arm64" ;; +esac + +# Get the appropriate download URL for this platform +DOWNLOAD_URL="$(curl -s https://api.github.com/repos/brevdev/brev-cli/releases/latest | grep "browser_download_url.*${OS}.*${ARCH}" | cut -d '"' -f 4)" + +# Verify we found a suitable release +if [ -z "${DOWNLOAD_URL}" ]; then + echo "Error: Could not find release for ${OS} ${ARCH}" >&2 + exit 1 +fi + +# Create temporary directory and ensure cleanup +TMP_DIR="$(mktemp -d)" +trap 'rm -rf "${TMP_DIR}"' EXIT + +# Download and extract the release +curl -sL "${DOWNLOAD_URL}" -o "${TMP_DIR}/brev.tar.gz" +tar -xzf "${TMP_DIR}/brev.tar.gz" -C "${TMP_DIR}" + +# Install the binary as "brevd" to /usr/local/bin/brevd +sudo mv "${TMP_DIR}/brev" /usr/local/bin/brevd +sudo chmod +x /usr/local/bin/brevd + +echo "Successfully installed brevd to /usr/local/bin/brevd" diff --git a/pkg/cmd/register/install-service.sh b/pkg/cmd/register/install-service.sh new file mode 100644 index 00000000..6e98f867 --- /dev/null +++ b/pkg/cmd/register/install-service.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Installs the brev-agent systemd service. +# This script is idempotent and can be run multiple times safely. + +STATE_DIR="${STATE_DIR:-/home/brevcloud/.brev-agent}" +SERVICE_FILE="/etc/systemd/system/brev-agent.service" +ENV_FILE="/etc/default/brev-agent" + +echo "Installing brev-agent systemd service..." + +# Define the desired service file content +read -r -d '' SERVICE_CONTENT <<'EOF' || true +[Unit] +Description=Brev Agent +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +EnvironmentFile=-/etc/default/brev-agent +ExecStart=/usr/local/bin/brev agent +Restart=on-failure +RestartSec=10s +User=brevcloud +Group=brevcloud + +[Install] +WantedBy=multi-user.target +EOF + +# Check if service file exists and has correct content +reload_systemd=false +if sudo test -f "${SERVICE_FILE}"; then + existing_content=$(sudo cat "${SERVICE_FILE}" 2>/dev/null || echo "") + if [ "${existing_content}" = "${SERVICE_CONTENT}" ]; then + echo "Service file '${SERVICE_FILE}' already exists with correct content" + else + echo "Updating service file '${SERVICE_FILE}'..." + echo "${SERVICE_CONTENT}" | sudo tee "${SERVICE_FILE}" > /dev/null + reload_systemd=true + fi +else + echo "Creating service file '${SERVICE_FILE}'..." + echo "${SERVICE_CONTENT}" | sudo tee "${SERVICE_FILE}" > /dev/null + reload_systemd=true +fi + +# Create default environment file if it doesn't exist +if sudo test -f "${ENV_FILE}"; then + echo "Environment file '${ENV_FILE}' already exists, preserving existing configuration" +else + echo "Creating environment file '${ENV_FILE}'..." + sudo tee "${ENV_FILE}" > /dev/null </dev/null; then + echo "Service 'brev-agent' is already enabled" +else + echo "Enabling service 'brev-agent' to start on boot..." + sudo systemctl enable brev-agent + echo "Service enabled" +fi + +# Start the service if it's not already running +if sudo systemctl is-active --quiet brev-agent 2>/dev/null; then + echo "Service 'brev-agent' is already running" +else + echo "Starting service 'brev-agent'..." + sudo systemctl start brev-agent + echo "Service started" +fi + +echo "Brev-agent systemd service is ready and running" \ No newline at end of file diff --git a/pkg/cmd/register/install-user.sh b/pkg/cmd/register/install-user.sh new file mode 100644 index 00000000..ed6af458 --- /dev/null +++ b/pkg/cmd/register/install-user.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Creates the brev service user with passwordless sudo and an SSH directory. +# This script is idempotent and can be run multiple times safely. + +BREV_USER="${BREV_USER:-brevcloud}" +BREV_HOME="${BREV_HOME:-/home/${BREV_USER}}" +SUDOERS_FILE="/etc/sudoers.d/${BREV_USER}" + +echo "Configuring user ${BREV_USER}..." + +# Create or update the BREV_USER +if ! id -u "${BREV_USER}" >/dev/null 2>&1; then + # If the BREV_USER does not exist, create it and set the home directory and shell to /bin/bash + echo "Creating user '${BREV_USER}' with home directory '${BREV_HOME}' and shell '/bin/bash'..." + sudo useradd -m -d "${BREV_HOME}" -s /bin/bash "${BREV_USER}" +else + # If the BREV_USER exists, ensure the shell is set to /bin/bash if it is not already + echo "User '${BREV_USER}' already exists" + current_shell=$(getent passwd "${BREV_USER}" | cut -d: -f7) + if [ "${current_shell}" != "/bin/bash" ]; then + echo "Updating user '${BREV_USER}'s shell to '/bin/bash'..." + sudo usermod -s /bin/bash "${BREV_USER}" + else + echo "User '${BREV_USER}'s shell is already '/bin/bash'" + fi +fi + +# Ensure the home directory exists with the right permissions. +if [ ! -d "${BREV_HOME}" ]; then + echo "Creating home directory ${BREV_HOME}..." + sudo install -d -m 700 -o "${BREV_USER}" -g "${BREV_USER}" "${BREV_HOME}" +else + # Directory exists, ensure correct permissions and ownership + echo "Home directory '${BREV_HOME}' already exists, ensuring correct permissions and ownership..." + sudo chown "${BREV_USER}:${BREV_USER}" "${BREV_HOME}" + sudo chmod 700 "${BREV_HOME}" +fi + +# Grant passwordless sudo to the brev user (idempotent). +sudoers_content="${BREV_USER} ALL=(ALL) NOPASSWD:ALL" +if sudo test -f "${SUDOERS_FILE}"; then + existing_content=$(sudo cat "${SUDOERS_FILE}" 2>/dev/null || echo "") + if [ "${existing_content}" = "${sudoers_content}" ]; then + echo "Sudoers file already configured correctly" + else + echo "Updating sudoers file..." + echo "${sudoers_content}" | sudo tee "${SUDOERS_FILE}" >/dev/null + sudo chmod 0440 "${SUDOERS_FILE}" + sudo visudo -c -f "${SUDOERS_FILE}" + fi +else + echo "Creating sudoers file..." + echo "${sudoers_content}" | sudo tee "${SUDOERS_FILE}" >/dev/null + sudo chmod 0440 "${SUDOERS_FILE}" + sudo visudo -c -f "${SUDOERS_FILE}" +fi + +# Prepare SSH directory and authorized_keys. +ssh_dir="${BREV_HOME}/.ssh" +authorized_keys="${ssh_dir}/authorized_keys" + +if ! sudo test -d "${ssh_dir}"; then + echo "Creating SSH directory..." + sudo install -d -m 700 -o "${BREV_USER}" -g "${BREV_USER}" "${ssh_dir}" +else + # Directory exists, ensure correct permissions and ownership + echo "SSH directory '${ssh_dir}' already exists, ensuring correct permissions and ownership..." + sudo chown "${BREV_USER}:${BREV_USER}" "${ssh_dir}" + sudo chmod 700 "${ssh_dir}" +fi + +if ! sudo test -f "${authorized_keys}"; then + echo "Creating authorized_keys file..." + sudo touch "${authorized_keys}" +else + echo "Authorized keys file '${authorized_keys}' already exists" +fi + +# Ensure correct permissions and ownership for authorized_keys +echo "Ensuring correct permissions and ownership for authorized_keys..." +sudo chown "${BREV_USER}:${BREV_USER}" "${authorized_keys}" +sudo chmod 600 "${authorized_keys}" + +# Final ownership consistency check (avoid unnecessary recursive chown). +# Only fix ownership if something is wrong to avoid touching all files unnecessarily. +if [ "$(stat -c '%U' "${BREV_HOME}" 2>/dev/null || stat -f '%Su' "${BREV_HOME}" 2>/dev/null)" != "${BREV_USER}" ]; then + echo "Fixing home directory ownership..." + sudo chown -R "${BREV_USER}:${BREV_USER}" "${BREV_HOME}" +fi + +echo "User ${BREV_USER} is ready at ${BREV_HOME}" \ No newline at end of file diff --git a/pkg/cmd/register/register.go b/pkg/cmd/register/register.go index 1c16586b..859c9df7 100644 --- a/pkg/cmd/register/register.go +++ b/pkg/cmd/register/register.go @@ -1,44 +1,672 @@ -// Package register provides the brev register command for DGX Spark registration package register import ( - "github.com/brevdev/brev-cli/pkg/terminal" + "context" + _ "embed" + "encoding/json" + "errors" + "fmt" + "os" + "os/exec" + "strings" + "time" + "github.com/brevdev/brev-cli/pkg/store" + "github.com/brevdev/brev-cli/pkg/terminal" + "github.com/briandowns/spinner" "github.com/spf13/cobra" + + brevcloud "github.com/brevdev/brev-cli/pkg/brevcloud" + agentconfig "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/config" + breverrors "github.com/brevdev/brev-cli/pkg/errors" + "github.com/brevdev/brev-cli/pkg/files" + sparklib "github.com/brevdev/brev-cli/pkg/spark" ) -var ( - registerLong = `Register your DGX Spark with NVIDIA Brev +//go:embed install-binary.sh +var installBinaryScript string + +//go:embed install-service.sh +var installServiceScript string -Join the waitlist to be among the first to register your DGX Spark -for early access integration with Brev.` +//go:embed uninstall-service.sh +var uninstallServiceScript string - registerExample = ` brev register` +//go:embed install-user.sh +var installUserScript string + +//go:embed uninstall-user.sh +var uninstallUserScript string + +const ( + defaultEnrollTimeout = 10 * time.Minute + envFilePath = "/etc/default/brevd" + stateDirDefault = "/home/brevcloud/.brev-agent" + serviceName = "brevd" + binaryPath = "/usr/local/bin/brevd" + binaryInstallScriptPath = "/tmp/install-brevd-binary.sh" + serviceInstallScriptPath = "/tmp/install-brevd-service.sh" + userInstallScriptPath = "/tmp/install-brevd-user.sh" ) -func NewCmdRegister(t *terminal.Terminal) *cobra.Command { +func NewCmdRegister(t *terminal.Terminal, loginCmdStore *store.AuthHTTPStore) *cobra.Command { + cmd := &cobra.Command{ + Annotations: map[string]string{"register": "", "sub-tree": ""}, + Use: "register", + Short: "Register a node into Brev", + Args: cobra.MinimumNArgs(1), + } + + cmd.AddCommand(NewCmdRegisterLocalHost(t, loginCmdStore)) + cmd.AddCommand(NewCmdRegisterRemoteHost(t, loginCmdStore)) + + return cmd +} + +func NewCmdUnregister(t *terminal.Terminal, loginCmdStore *store.AuthHTTPStore) *cobra.Command { cmd := &cobra.Command{ - Annotations: map[string]string{"configuration": ""}, - Use: "register", - Aliases: []string{"spark"}, - DisableFlagsInUseLine: true, - Short: "Register your DGX Spark with Brev", - Long: registerLong, - Example: registerExample, + Annotations: map[string]string{"register": "", "sub-tree": ""}, + Use: "unregister", + Short: "Unregister a node from Brev", + Args: cobra.MinimumNArgs(1), + } + + cmd.AddCommand(NewCmdUnregisterLocalHost(t, loginCmdStore)) + cmd.AddCommand(NewCmdUnregisterRemoteHost(t, loginCmdStore)) + + return cmd +} + +type registerOptions struct { + hostAlias string +} + +func NewCmdRegisterLocalHost(t *terminal.Terminal, loginCmdStore *store.AuthHTTPStore) *cobra.Command { + cmd := &cobra.Command{ + Use: "this", + Aliases: []string{"local"}, + Short: "Register this machine into Brev", RunE: func(cmd *cobra.Command, args []string) error { - runRegister(t) - return nil + return runRegisterLocal(cmd.Context(), t, loginCmdStore, args) + }, + } + + return cmd +} + +func NewCmdRegisterRemoteHost(t *terminal.Terminal, loginCmdStore *store.AuthHTTPStore) *cobra.Command { + var opts registerOptions + cmd := &cobra.Command{ + Use: "remote", + Short: "Register a remote machine into Brev", + RunE: func(cmd *cobra.Command, args []string) error { + return runRegisterRemote(cmd.Context(), t, loginCmdStore, args) + }, + } + + cmd.Flags().StringVar(&opts.hostAlias, "host-alias", "", "Alias of the host to register") + + return cmd +} + +func NewCmdUnregisterLocalHost(t *terminal.Terminal, loginCmdStore *store.AuthHTTPStore) *cobra.Command { + cmd := &cobra.Command{ + Use: "this", + Aliases: []string{"local"}, + Short: "Unregister this machine from Brev", + RunE: func(cmd *cobra.Command, args []string) error { + return runUnregisterLocal(cmd.Context(), t, loginCmdStore, args) + }, + } + + return cmd +} + +func NewCmdUnregisterRemoteHost(t *terminal.Terminal, loginCmdStore *store.AuthHTTPStore) *cobra.Command { + var opts registerOptions + cmd := &cobra.Command{ + Use: "remote", + Short: "Unregister a remote machine from Brev", + RunE: func(cmd *cobra.Command, args []string) error { + return runUnregisterRemote(cmd.Context(), t, loginCmdStore, args) + }, + } + + cmd.Flags().StringVar(&opts.hostAlias, "host-alias", "", "Alias of the host to register") + + return cmd +} + +func runRegisterLocal(ctx context.Context, t *terminal.Terminal, loginStore *store.AuthHTTPStore, args []string) error { + // Step 1: Install the brevcloud user + if err := runEmbeddedScriptLocally("brevcloud-install-user", installUserScript); err != nil { + return fmt.Errorf("failed to install brevcloud user: %w", err) + } + + // Step 2: Install the brev-agent systemd service + if err := runEmbeddedScriptLocally("brev-agent-install-service", installServiceScript); err != nil { + return fmt.Errorf("failed to install brev-agent systemd service: %w", err) + } + + return nil +} + +func runUnregisterLocal(ctx context.Context, t *terminal.Terminal, loginStore *store.AuthHTTPStore, args []string) error { + // Step 1: Uninstall the brev-agent systemd service + if err := runEmbeddedScriptLocally("brev-agent-uninstall-service", uninstallServiceScript); err != nil { + return fmt.Errorf("failed to uninstall brev-agent systemd service: %w", err) + } + + // Step 2: Uninstall the brevcloud user + if err := runEmbeddedScriptLocally("brevcloud-uninstall-user", uninstallUserScript); err != nil { + return fmt.Errorf("failed to uninstall brevcloud user: %w", err) + } + return nil +} + +func runRegisterRemote(ctx context.Context, t *terminal.Terminal, loginStore *store.AuthHTTPStore, args []string) error { + fmt.Println("register remote") + return nil +} + +func runUnregisterRemote(ctx context.Context, t *terminal.Terminal, loginStore *store.AuthHTTPStore, args []string) error { + fmt.Println("unregister remote") + return nil +} + +func runEmbeddedScriptLocally(scriptName string, scriptContents string) error { + // Create a temporary file to act as the install script + tmpFile, err := os.CreateTemp("", fmt.Sprintf("install-%s-*.sh", scriptName)) + if err != nil { + return fmt.Errorf("failed to create temp file for %s installation: %w", scriptName, err) + } + defer os.Remove(tmpFile.Name()) // clean up the temp file + + // Write the script contents to the temporary file + _, err = tmpFile.WriteString(scriptContents) + if err != nil { + return fmt.Errorf("failed to write install script to temp file: %w", err) + } + + // Ensure the file is executable + err = tmpFile.Chmod(0o755) + if err != nil { + return fmt.Errorf("failed to update permissions of temp file: %w", err) + } + + // Close the file to allow for execution + err = tmpFile.Close() + if err != nil { + return fmt.Errorf("failed to close temp file: %w", err) + } + + // Execute the file to run the script + cmd := exec.Command(tmpFile.Name()) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + return cmd.Run() +} + +type enrollOptions struct { + agentVersion string + wait bool + timeout time.Duration + printCmd bool + dryRun bool + json bool + mockRegistration bool +} + +func NewCmdSparkEnroll(t *terminal.Terminal, loginCmdStore *store.AuthHTTPStore) *cobra.Command { + var opts enrollOptions + cmd := &cobra.Command{ + Use: "enroll [spark-alias]", + Short: "Enroll a Spark node into Brev", + Args: cobra.MaximumNArgs(1), + SilenceUsage: true, + SilenceErrors: true, + RunE: func(cmd *cobra.Command, args []string) error { + alias := "" + if len(args) > 0 { + alias = args[0] + } + + if loginCmdStore == nil { + return breverrors.WrapAndTrace(errors.New("authenticated store unavailable")) + } + + return runSparkEnroll(cmd.Context(), t, loginCmdStore, alias, opts) }, } + cmd.Flags().StringVar(&opts.agentVersion, "agent-version", "", "Agent version to expect") + cmd.Flags().BoolVar(&opts.wait, "wait", false, "Wait for Brev node to report active") + cmd.Flags().DurationVar(&opts.timeout, "timeout", defaultEnrollTimeout, "Overall timeout for enroll") + cmd.Flags().BoolVar(&opts.printCmd, "print-cmd", false, "Print remote ssh commands") + cmd.Flags().BoolVar(&opts.dryRun, "dry-run", false, "Print actions without executing") + cmd.Flags().BoolVar(&opts.json, "json", false, "Output JSON result") + return cmd } -func runRegister(t *terminal.Terminal) { - t.Vprint("\n") - t.Vprint(t.Green("Thanks so much for your interest in registering your DGX Spark with Brev!\n\n")) - t.Vprint("To be on the waitlist for early access to this feature, please fill out this form:\n\n") - t.Vprint(t.Yellow(" 👉 https://forms.gle/RHCHGmZuiMQQ2faA6\n\n")) - t.Vprint("We will reach out to the provided email with updates and instructions on how to register soon (:\n") - t.Vprint("\n") +type enrollResult struct { + BrevCloudNodeID string `json:"brev_cloud_node_id"` + CloudCredID string `json:"cloud_cred_id"` + CloudName string `json:"cloud_name,omitempty"` + Phase string `json:"phase,omitempty"` + LastSeenAt string `json:"last_seen_at,omitempty"` + AgentVersion string `json:"agent_version,omitempty"` +} + +func runSparkEnroll(ctx context.Context, t *terminal.Terminal, loginStore *store.AuthHTTPStore, alias string, opts enrollOptions) error { + ctx, cancel := sparklib.WithTimeout(ctx, opts.timeout) + defer cancel() + + uiEnabled := !opts.json + var sp *spinner.Spinner + stopSpinner := func() { + if sp != nil { + sp.Stop() + sp = nil + } + } + defer stopSpinner() + + fail := func(err error) error { + msg := formatEnrollError(err, opts) + stopSpinner() + t.Eprint(t.Red("\n Failed: " + msg)) + return errors.New(msg) + } + + if uiEnabled { + if user, err := loginStore.GetCurrentUser(); err == nil { + identity := user.Email + if identity == "" { + identity = user.Username + } + if identity != "" { + t.Print(fmt.Sprintf("Logged in as %s", identity)) + } + } + } + + if uiEnabled { + searchLabel := t.Yellow("DGX Spark") + if alias != "" { + searchLabel = fmt.Sprintf("%s %s", t.Yellow("DGX Spark"), t.Yellow(alias)) + } + sp = t.NewSpinner() + sp.Suffix = fmt.Sprintf(" Searching for %s...", searchLabel) + sp.Start() + } + + host, err := resolveSparkHost(t, alias) + if err != nil { + return fail(err) + } + + aliasLabel := host.Alias + if aliasLabel == "" { + aliasLabel = sparklib.HostLabel(host) + } + if uiEnabled { + stopSpinner() + t.Print(fmt.Sprintf("\n %s %s %s", t.Green("✓"), t.Green("Found"), t.Yellow(aliasLabel))) + sp = t.NewSpinner() + sp.Suffix = fmt.Sprintf(" Registering %s 🤙 ...", t.Yellow(aliasLabel)) + sp.Start() + } + + cloudCredID, err := resolveDefaultCloudCred(ctx, loginStore) + if err != nil { + return fail(err) + } + + if opts.dryRun { + t.Print(fmt.Sprintf("Dry-run: would connect to %s with cloud_cred_id=%s", sparklib.HostLabel(host), cloudCredID)) + return nil + } + + brevCloudClient := brevcloud.NewClient(loginStore) + remote := sparklib.NewRemoteRunner(files.AppFs) + orgID := "" + if org, err := loginStore.GetActiveOrganizationOrDefault(); err == nil && org != nil { + orgID = org.ID + } + + if uiEnabled { + sp = t.NewSpinner() + if opts.mockRegistration { + sp.Suffix = " Configuring (mock)..." + } else { + sp.Suffix = " Configuring..." + } + sp.Start() + } + + if err := probeConnectivity(ctx, remote, host, opts.printCmd); err != nil { + return fail(err) + } + + if err := ensureBrevCloudUser(ctx, remote, host, opts.printCmd); err != nil { + return fail(err) + } + + if err := ensureStateDir(ctx, remote, host, opts.printCmd); err != nil { + return fail(err) + } + + // Minimal path: ensure agent and unit pre-exist. + if err := ensureAgentPresent(ctx, remote, host, opts.printCmd); err != nil { + return fail(err) + } + + var intent brevcloud.CreateRegistrationIntentResponse + if opts.mockRegistration { + intent = brevcloud.MockRegistrationIntent(cloudCredID) + } else { + req := brevcloud.CreateRegistrationIntentRequest{ + CloudCredID: cloudCredID, + OrgID: orgID, + } + resp, err := brevCloudClient.CreateRegistrationIntent(ctx, req) + if err != nil { + return fail(err) + } + intent = *resp + } + + if intent.RegistrationToken == "" { + return fail(errors.New("registration token missing from registration intent")) + } + + configCloudCredID := intent.CloudCredID + if configCloudCredID == "" { + configCloudCredID = cloudCredID + } + if err := writeAgentConfig(ctx, remote, host, intent.BrevCloudNodeID, intent.RegistrationToken, configCloudCredID, opts.printCmd); err != nil { + return fail(err) + } + + var result enrollResult + result.BrevCloudNodeID = intent.BrevCloudNodeID + result.CloudCredID = intent.CloudCredID + if result.CloudCredID == "" { + result.CloudCredID = cloudCredID + } + + if opts.wait && !opts.mockRegistration { + node, err := waitForBrevCloudNode(ctx, brevCloudClient, intent.BrevCloudNodeID, t) + if err != nil { + return fail(err) + } + result.CloudName = node.CloudName + result.Phase = node.Phase + result.LastSeenAt = node.LastSeenAt + result.AgentVersion = node.AgentVersion + t.Vprintf("brev cloud node active: phase=%s last_seen_at=%s agent=%s", node.Phase, node.LastSeenAt, node.AgentVersion) + } + + stopSpinner() + if uiEnabled { + if opts.mockRegistration { + t.Print("\n" + t.Green("✓ Mock enroll finished (config written, no restart)")) + } else { + t.Print("\n" + t.Green("✓ Registration complete")) + } + t.Print(fmt.Sprintf("Manage your %s %s", t.Yellow("Spark"), t.Blue(fmt.Sprintf("https://brev.nvidia.com/org/%s/environments", orgID)))) + } + + if opts.json { + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + return breverrors.WrapAndTrace(enc.Encode(result)) + } + + t.Vprintf("Enrolled BrevCloud node: %s (cloud cred: %s)", result.BrevCloudNodeID, result.CloudCredID) + if result.Phase != "" { + t.Vprintf("Phase: %s LastSeen: %s Agent: %s", result.Phase, result.LastSeenAt, result.AgentVersion) + } + + return nil +} + +func resolveSparkHost(_ *terminal.Terminal, alias string) (sparklib.Host, error) { + locator := sparklib.NewSyncSSHConfigLocator() + resolver := sparklib.NewDefaultSyncConfigResolver(files.AppFs, locator) + hosts, err := resolver.ResolveHosts() + if err != nil { + return sparklib.Host{}, breverrors.WrapAndTrace(err) + } + selected, err := sparklib.SelectHost(hosts, alias, sparklib.TerminalPrompter{}) + if err != nil { + return sparklib.Host{}, breverrors.WrapAndTrace(err) + } + return selected, nil +} + +func resolveDefaultCloudCred(ctx context.Context, loginStore *store.AuthHTTPStore) (string, error) { + client := brevcloud.NewClient(loginStore) + org, err := loginStore.GetActiveOrganizationOrDefault() + cred, err := client.ListCloudCredID(ctx, org.ID) + if err != nil { + return "", err + } + + return cred, nil +} + +func ensureBrevCloudUser(ctx context.Context, remote sparklib.RemoteRunner, host sparklib.Host, printCmd bool) error { + userWriteCmd := buildWriteScriptCmd(userInstallScriptPath, installUserScript) + userExecuteCmd := buildExecuteScriptCmd(userInstallScriptPath, "") + + return runInstallScript(ctx, remote, host, printCmd, "brevcloud user", userInstallScriptPath, userWriteCmd, userExecuteCmd) +} + +func ensureAgentPresent(ctx context.Context, remote sparklib.RemoteRunner, host sparklib.Host, printCmd bool) error { + binaryWriteCmd := buildWriteScriptCmd(binaryInstallScriptPath, installBinaryScript) + binaryExecuteCmd := buildExecuteScriptCmd(binaryInstallScriptPath, "") + + serviceWriteCmd := buildWriteScriptCmd(serviceInstallScriptPath, installServiceScript) + serviceExecuteCmd := buildExecuteScriptCmd(serviceInstallScriptPath, fmt.Sprintf("STATE_DIR=%s", stateDirDefault)) + + // First check if brevd is already installed + checkCmd := fmt.Sprintf("test -x %s || sudo test -x %s", binaryPath, binaryPath) + if printCmd { + fmt.Printf("[remote] %s\n", checkCmd) + } + _, err := remote.Run(ctx, host, checkCmd) + // If brevd doesn't exist, install it from GitHub releases + if err != nil { + if printCmd { + fmt.Printf("[remote] Installing brevd from GitHub releases...\n") + } + + if err := runInstallScript(ctx, remote, host, printCmd, "brevd", binaryInstallScriptPath, binaryWriteCmd, binaryExecuteCmd); err != nil { + return err + } + } + + // Now check for systemd service file + serviceCheck := fmt.Sprintf("systemctl status %s >/dev/null 2>&1 || sudo systemctl status %s >/dev/null 2>&1 || test -f /etc/systemd/system/%s.service", serviceName, serviceName, serviceName) + if printCmd { + fmt.Printf("[remote] %s\n", serviceCheck) + } + out, err := remote.Run(ctx, host, serviceCheck) + if err != nil { + // If systemd service doesn't exist, install it + if printCmd { + fmt.Printf("[remote] Installing brevd systemd service...\n") + } + + if err := runInstallScript(ctx, remote, host, printCmd, "brevd systemd service", serviceInstallScriptPath, serviceWriteCmd, serviceExecuteCmd); err != nil { + return err + } + } else if printCmd { + fmt.Printf("[remote] systemd service check output: %s\n", strings.TrimSpace(out)) + } + + return nil +} + +func buildWriteScriptCmd(remotePath, script string) string { + return fmt.Sprintf("cat > %[1]s <<'SCRIPT_EOF'\n%[2]s\nSCRIPT_EOF\nchmod +x %[1]s", remotePath, script) +} + +func buildExecuteScriptCmd(remotePath, envPrefix string) string { + return fmt.Sprintf("%s %s && rm -f %s", envPrefix, remotePath, remotePath) +} + +func runInstallScript(ctx context.Context, remote sparklib.RemoteRunner, host sparklib.Host, printCmd bool, scriptName, remotePath, writeCmd, executeCmd string) error { + if printCmd { + fmt.Printf("[remote] Writing %s install script to %s\n", scriptName, remotePath) + } + if _, err := remote.Run(ctx, host, writeCmd); err != nil { + return fmt.Errorf("failed to write %s install script on %s: %w", scriptName, sparklib.HostLabel(host), err) + } + + if printCmd { + fmt.Printf("[remote] %s\n", executeCmd) + } + out, err := remote.Run(ctx, host, executeCmd) + if err != nil { + return fmt.Errorf("failed to install %s on %s: err=%v output=%s", scriptName, sparklib.HostLabel(host), err, strings.TrimSpace(out)) + } + if printCmd { + fmt.Printf("[remote] %s install output: %s\n", scriptName, strings.TrimSpace(out)) + } + return nil +} + +func ensureStateDir(ctx context.Context, remote sparklib.RemoteRunner, host sparklib.Host, printCmd bool) error { + cmds := []string{ + fmt.Sprintf("sudo install -d -m 700 -o brevcloud -g brevcloud %s", stateDirDefault), + fmt.Sprintf("sudo mkdir -p %s && sudo chown brevcloud:brevcloud %s && sudo chmod 700 %s", stateDirDefault, stateDirDefault, stateDirDefault), + fmt.Sprintf("mkdir -p %s", stateDirDefault), + } + var errs []string + for _, cmd := range cmds { + if printCmd { + fmt.Printf("[remote] %s\n", cmd) + } + out, err := remote.Run(ctx, host, cmd) + if err == nil { + return nil + } + errs = append(errs, fmt.Sprintf("cmd=%s err=%v output=%s", cmd, err, strings.TrimSpace(out))) + } + return fmt.Errorf("failed to create state dir on %s; attempts: %s", sparklib.HostLabel(host), strings.Join(errs, " | ")) +} + +func writeAgentConfig(ctx context.Context, remote sparklib.RemoteRunner, host sparklib.Host, brevCloudNodeID, registrationToken, cloudCredID string, printCmd bool) error { + brevCloudURL := strings.TrimSpace(os.Getenv(agentconfig.EnvBrevCloudURL)) + if brevCloudURL == "" { + return fmt.Errorf("BREV_AGENT_BREV_CLOUD_URL must be set to configure the agent") + } + var b strings.Builder + b.WriteString(agentconfig.EnvBrevCloudURL) + b.WriteString("=") + b.WriteString(brevCloudURL) + b.WriteString("\n") + b.WriteString(agentconfig.EnvRegistrationToken) + b.WriteString("=") + b.WriteString(registrationToken) + if brevCloudNodeID != "" { + b.WriteString("\n") + b.WriteString(agentconfig.EnvBrevCloudNodeID) + b.WriteString("=") + b.WriteString(brevCloudNodeID) + } + if cloudCredID != "" { + b.WriteString("\n") + b.WriteString(agentconfig.EnvCloudCredID) + b.WriteString("=") + b.WriteString(cloudCredID) + } + b.WriteString("\n") + b.WriteString(agentconfig.EnvStateDir) + b.WriteString("=") + b.WriteString(stateDirDefault) + b.WriteString("\n") + payload := b.String() + cmds := []string{ + fmt.Sprintf("cat <<'EOF' | sudo -n tee %s >/dev/null\n%sEOF\n", envFilePath, payload), + fmt.Sprintf("cat <<'EOF' | sudo tee %s >/dev/null\n%sEOF\n", envFilePath, payload), + fmt.Sprintf("cat <<'EOF' | tee %s >/dev/null\n%sEOF\n", envFilePath, payload), + } + var errs []string + for _, cmd := range cmds { + if printCmd { + fmt.Printf("[remote] %s\n", cmd) + } + out, err := remote.Run(ctx, host, cmd) + if err == nil { + return nil + } + errs = append(errs, fmt.Sprintf("cmd=%s err=%v output=%s", cmd, err, strings.TrimSpace(out))) + } + return fmt.Errorf("failed to write config on %s; attempts: %s", sparklib.HostLabel(host), strings.Join(errs, " | ")) +} + +func waitForBrevCloudNode(ctx context.Context, client *brevcloud.Client, brevCloudNodeID string, t *terminal.Terminal) (*brevcloud.BrevCloudNode, error) { + interval := 3 * time.Second + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + node, err := client.GetBrevCloudNode(ctx, brevCloudNodeID) + if err != nil { + return nil, err + } + if strings.EqualFold(node.Phase, "ACTIVE") || node.LastSeenAt != "" { + return node, nil + } + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-ticker.C: + } + } +} + +func formatEnrollError(err error, opts enrollOptions) string { + if err == nil { + return "" + } + + raw := strings.TrimSpace(err.Error()) + if isSudoError(raw) { + return "Sudo required on target; rerun with a TTY or configure passwordless sudo." + } + + if errors.Is(err, context.DeadlineExceeded) { + return "Timed out waiting for node to register" + } + + firstLine := raw + if idx := strings.IndexByte(raw, '\n'); idx >= 0 { + firstLine = strings.TrimSpace(raw[:idx]) + } + return firstLine +} + +func isSudoError(msg string) bool { + lower := strings.ToLower(msg) + return strings.Contains(lower, "usage: sudo") || + strings.Contains(lower, "sudo: a password is required") || + strings.Contains(lower, "sudo: no tty present") || + strings.Contains(lower, "sudo: sorry, you must have a tty") +} + +func probeConnectivity(ctx context.Context, remote sparklib.RemoteRunner, host sparklib.Host, printCmd bool) error { + cmd := "uname -a && whoami && hostname" + if printCmd { + fmt.Printf("[remote] %s\n", cmd) + } + out, err := remote.Run(ctx, host, cmd) + if err != nil { + return fmt.Errorf("ssh connectivity check failed on %s: err=%v output=%s", sparklib.HostLabel(host), err, strings.TrimSpace(out)) + } + return nil } diff --git a/pkg/cmd/register/spark.go b/pkg/cmd/register/spark.go new file mode 100644 index 00000000..d8fff6bb --- /dev/null +++ b/pkg/cmd/register/spark.go @@ -0,0 +1,32 @@ +package register + +import ( + "github.com/spf13/cobra" + + "github.com/brevdev/brev-cli/pkg/store" + "github.com/brevdev/brev-cli/pkg/terminal" +) + +const ( + sparkShort = "Manage NVIDIA Spark connectivity" + sparkLong = "Commands for connecting Brev to NVIDIA Spark environments." +) + +// NewCmdSpark is the root Spark command; subcommands (e.g., ssh, connect) +// are added beneath it. +func NewCmdSpark(t *terminal.Terminal, loginCmdStore *store.AuthHTTPStore, noLoginCmdStore *store.AuthHTTPStore) *cobra.Command { + // Keep stores for future subcommands (e.g., connect) even if unused now. + _ = loginCmdStore + _ = noLoginCmdStore + + cmd := &cobra.Command{ + Use: "spark", + Short: sparkShort, + Long: sparkLong, + } + + // cmd.AddCommand(NewCmdSparkEnroll(t, loginCmdStore)) + // cmd.AddCommand(NewCmdSparkAgent(t)) + + return cmd +} diff --git a/pkg/cmd/register/uninstall-service.sh b/pkg/cmd/register/uninstall-service.sh new file mode 100644 index 00000000..5e460614 --- /dev/null +++ b/pkg/cmd/register/uninstall-service.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Removes the brev-agent systemd service. +# This script reverses everything done by install-service.sh. +# This script is idempotent and can be run multiple times safely. + +SERVICE_NAME="brev-agent" +SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" +ENV_FILE="/etc/default/${SERVICE_NAME}" + +echo "Uninstalling ${SERVICE_NAME} systemd service..." + +reload_systemd=false + +# Stop the service if it's running +if sudo systemctl is-active --quiet "${SERVICE_NAME}" 2>/dev/null; then + echo "Stopping service '${SERVICE_NAME}'..." + sudo systemctl stop "${SERVICE_NAME}" + echo "Service stopped" +else + echo "Service '${SERVICE_NAME}' is not running" +fi + +# Disable the service if it's enabled +if sudo systemctl is-enabled --quiet "${SERVICE_NAME}" 2>/dev/null; then + echo "Disabling service '${SERVICE_NAME}'..." + sudo systemctl disable "${SERVICE_NAME}" + echo "Service disabled" +else + echo "Service '${SERVICE_NAME}' is not enabled" +fi + +# Remove the service file if it exists +if sudo test -f "${SERVICE_FILE}"; then + echo "Removing service file '${SERVICE_FILE}'..." + sudo rm -f "${SERVICE_FILE}" + reload_systemd=true + echo "Service file removed" +else + echo "Service file '${SERVICE_FILE}' does not exist" +fi + +# Remove the environment file if it exists +if sudo test -f "${ENV_FILE}"; then + echo "Removing environment file '${ENV_FILE}'..." + sudo rm -f "${ENV_FILE}" + echo "Environment file removed" +else + echo "Environment file '${ENV_FILE}' does not exist" +fi + +# Reload systemd only if the service file was removed +if [ "${reload_systemd}" = true ]; then + echo "Reloading systemd daemon..." + sudo systemctl daemon-reload + echo "Systemd daemon reloaded" +else + echo "No systemd daemon reload needed" +fi + +# Reset failed state if it exists +if sudo systemctl is-failed --quiet "${SERVICE_NAME}" 2>/dev/null; then + echo "Resetting failed state for '${SERVICE_NAME}'..." + sudo systemctl reset-failed "${SERVICE_NAME}" 2>/dev/null || true +fi + +echo "Uninstall complete for ${SERVICE_NAME}" diff --git a/pkg/cmd/register/uninstall-user.sh b/pkg/cmd/register/uninstall-user.sh new file mode 100644 index 00000000..2df8a299 --- /dev/null +++ b/pkg/cmd/register/uninstall-user.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Removes the brev service user and all associated configuration. +# This script reverses everything done by install-user.sh. + +BREV_USER="${BREV_USER:-brevcloud}" +BREV_HOME="${BREV_HOME:-/home/${BREV_USER}}" +SUDOERS_FILE="/etc/sudoers.d/${BREV_USER}" + +echo "Uninstalling user ${BREV_USER}..." + +# Remove the sudoers file if it exists +if sudo test -f "${SUDOERS_FILE}"; then + echo "Removing sudoers file '${SUDOERS_FILE}'..." + sudo rm -f "${SUDOERS_FILE}" + echo "Sudoers file removed" +else + echo "Sudoers file '${SUDOERS_FILE}' does not exist, skipping..." +fi + +# Check if the user exists +if id -u "${BREV_USER}" >/dev/null 2>&1; then + echo "User '${BREV_USER}' exists, proceeding with removal..." + + # Kill any processes owned by the user + if pgrep -u "${BREV_USER}" >/dev/null 2>&1; then + echo "Killing processes owned by '${BREV_USER}'..." + sudo pkill -u "${BREV_USER}" || true + sleep 2 + # Force kill if still running + if pgrep -u "${BREV_USER}" >/dev/null 2>&1; then + echo "Force killing remaining processes..." + sudo pkill -9 -u "${BREV_USER}" || true + sleep 1 + fi + fi + + # Remove the user and home directory + echo "Removing user '${BREV_USER}' and home directory..." + sudo userdel -r "${BREV_USER}" 2>/dev/null || { + # If userdel -r fails (e.g., home directory doesn't exist or is already removed) + # try without -r flag + sudo userdel "${BREV_USER}" 2>/dev/null || true + } + + # Manually remove home directory if it still exists + if sudo test -d "${BREV_HOME}"; then + echo "Manually removing home directory '${BREV_HOME}'..." + sudo rm -rf "${BREV_HOME}" + fi + + echo "User and home directory removed" +else + echo "User '${BREV_USER}' does not exist, skipping user removal..." +fi + +# Clean up any remaining group if it exists and is empty +if getent group "${BREV_USER}" >/dev/null 2>&1; then + echo "Removing group '${BREV_USER}'..." + sudo groupdel "${BREV_USER}" 2>/dev/null || { + echo "Note: Group '${BREV_USER}' could not be removed (may still be in use)" + } +fi + +echo "Uninstall complete for user ${BREV_USER}" diff --git a/pkg/cmd/spark/agent.go b/pkg/cmd/spark/agent.go new file mode 100644 index 00000000..93d9e39d --- /dev/null +++ b/pkg/cmd/spark/agent.go @@ -0,0 +1,43 @@ +package spark + +import ( + "fmt" + "time" + + "github.com/spf13/cobra" + + "github.com/brevdev/brev-cli/pkg/terminal" +) + +const ( + agentShort = "Run the Brev agent daemon on the Spark node" + agentLong = "Runs the Brev agent daemon in a continuous loop, reporting status and handling workloads." +) + +func NewCmdSparkAgent(t *terminal.Terminal) *cobra.Command { + cmd := &cobra.Command{ + Use: "agent", + Short: agentShort, + Long: agentLong, + RunE: func(cmd *cobra.Command, args []string) error { + return runSparkAgent(t) + }, + } + return cmd +} + +func runSparkAgent(t *terminal.Terminal) error { + t.Vprint(t.Green("Starting Brev agent daemon...\n")) + + ticker := time.NewTicker(1 * time.Minute) + defer ticker.Stop() + + // Print immediately on startup + t.Vprint(fmt.Sprintf("[%s] Brev agent running\n", time.Now().Format(time.RFC3339))) + + // TODO this should call the logic in pkg/brevdaemon/agent.go + for ts := range ticker.C { + t.Vprint(fmt.Sprintf("[%s] Brev agent heartbeat\n", ts.Format(time.RFC3339))) + } + return nil +} diff --git a/pkg/cmd/spark/enroll.go b/pkg/cmd/spark/enroll.go new file mode 100644 index 00000000..732b1a45 --- /dev/null +++ b/pkg/cmd/spark/enroll.go @@ -0,0 +1,499 @@ +package spark + +import ( + "context" + _ "embed" + "encoding/json" + "errors" + "fmt" + "os" + "strings" + "time" + + "github.com/briandowns/spinner" + "github.com/spf13/cobra" + + brevcloud "github.com/brevdev/brev-cli/pkg/brevcloud" + agentconfig "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/config" + breverrors "github.com/brevdev/brev-cli/pkg/errors" + "github.com/brevdev/brev-cli/pkg/files" + sparklib "github.com/brevdev/brev-cli/pkg/spark" + "github.com/brevdev/brev-cli/pkg/store" + "github.com/brevdev/brev-cli/pkg/terminal" +) + +//go:embed install-binary.sh +var installBinaryScript string + +//go:embed install-service.sh +var installServiceScript string + +//go:embed install-user.sh +var installUserScript string + +const ( + defaultEnrollTimeout = 10 * time.Minute + envFilePath = "/etc/default/brevd" + stateDirDefault = "/home/brevcloud/.brev-agent" + serviceName = "brevd" + binaryPath = "/usr/local/bin/brevd" + binaryInstallScriptPath = "/tmp/install-brevd-binary.sh" + serviceInstallScriptPath = "/tmp/install-brevd-service.sh" + userInstallScriptPath = "/tmp/install-brevd-user.sh" +) + +type enrollOptions struct { + agentVersion string + wait bool + timeout time.Duration + printCmd bool + dryRun bool + json bool + mockRegistration bool +} + +func NewCmdSparkEnroll(t *terminal.Terminal, loginCmdStore *store.AuthHTTPStore) *cobra.Command { + var opts enrollOptions + cmd := &cobra.Command{ + Use: "enroll [spark-alias]", + Short: "Enroll a Spark node into Brev", + Args: cobra.MaximumNArgs(1), + SilenceUsage: true, + SilenceErrors: true, + RunE: func(cmd *cobra.Command, args []string) error { + alias := "" + if len(args) > 0 { + alias = args[0] + } + + if loginCmdStore == nil { + return breverrors.WrapAndTrace(errors.New("authenticated store unavailable")) + } + + return runSparkEnroll(cmd.Context(), t, loginCmdStore, alias, opts) + }, + } + + cmd.Flags().StringVar(&opts.agentVersion, "agent-version", "", "Agent version to expect") + cmd.Flags().BoolVar(&opts.wait, "wait", false, "Wait for Brev node to report active") + cmd.Flags().DurationVar(&opts.timeout, "timeout", defaultEnrollTimeout, "Overall timeout for enroll") + cmd.Flags().BoolVar(&opts.printCmd, "print-cmd", false, "Print remote ssh commands") + cmd.Flags().BoolVar(&opts.dryRun, "dry-run", false, "Print actions without executing") + cmd.Flags().BoolVar(&opts.json, "json", false, "Output JSON result") + + return cmd +} + +type enrollResult struct { + BrevCloudNodeID string `json:"brev_cloud_node_id"` + CloudCredID string `json:"cloud_cred_id"` + CloudName string `json:"cloud_name,omitempty"` + Phase string `json:"phase,omitempty"` + LastSeenAt string `json:"last_seen_at,omitempty"` + AgentVersion string `json:"agent_version,omitempty"` +} + +func runSparkEnroll(ctx context.Context, t *terminal.Terminal, loginStore *store.AuthHTTPStore, alias string, opts enrollOptions) error { //nolint:gocognit,gocyclo,funlen // enroll flow is linear but long; revisit if logic changes + ctx, cancel := sparklib.WithTimeout(ctx, opts.timeout) + defer cancel() + + uiEnabled := !opts.json + var sp *spinner.Spinner + stopSpinner := func() { + if sp != nil { + sp.Stop() + sp = nil + } + } + defer stopSpinner() + + fail := func(err error) error { + msg := formatEnrollError(err) + stopSpinner() + t.Eprint(t.Red("\n Failed: " + msg)) + return errors.New(msg) + } + + if uiEnabled { + if user, err := loginStore.GetCurrentUser(); err == nil { + identity := user.Email + if identity == "" { + identity = user.Username + } + if identity != "" { + t.Print(fmt.Sprintf("Logged in as %s", identity)) + } + } + } + + if uiEnabled { + searchLabel := t.Yellow("DGX Spark") + if alias != "" { + searchLabel = fmt.Sprintf("%s %s", t.Yellow("DGX Spark"), t.Yellow(alias)) + } + sp = t.NewSpinner() + sp.Suffix = fmt.Sprintf(" Searching for %s...", searchLabel) + sp.Start() + } + + host, err := resolveSparkHost(t, alias) + if err != nil { + return fail(err) + } + + aliasLabel := host.Alias + if aliasLabel == "" { + aliasLabel = sparklib.HostLabel(host) + } + if uiEnabled { + stopSpinner() + t.Print(fmt.Sprintf("\n %s %s %s", t.Green("✓"), t.Green("Found"), t.Yellow(aliasLabel))) + sp = t.NewSpinner() + sp.Suffix = fmt.Sprintf(" Registering %s 🤙 ...", t.Yellow(aliasLabel)) + sp.Start() + } + + cloudCredID, err := resolveDefaultCloudCred(ctx, loginStore) + if err != nil { + return fail(err) + } + + if opts.dryRun { + t.Print(fmt.Sprintf("Dry-run: would connect to %s with cloud_cred_id=%s", sparklib.HostLabel(host), cloudCredID)) + return nil + } + + brevCloudClient := brevcloud.NewClient(loginStore) + remote := sparklib.NewRemoteRunner(files.AppFs) + orgID := "" + if org, err := loginStore.GetActiveOrganizationOrDefault(); err == nil && org != nil { + orgID = org.ID + } + + if uiEnabled { + sp = t.NewSpinner() + if opts.mockRegistration { + sp.Suffix = " Configuring (mock)..." + } else { + sp.Suffix = " Configuring..." + } + sp.Start() + } + + if err := probeConnectivity(ctx, remote, host, opts.printCmd); err != nil { + return fail(err) + } + + if err := ensureBrevCloudUser(ctx, remote, host, opts.printCmd); err != nil { + return fail(err) + } + + if err := ensureStateDir(ctx, remote, host, opts.printCmd); err != nil { + return fail(err) + } + + // Minimal path: ensure agent and unit pre-exist. + if err := ensureAgentPresent(ctx, remote, host, opts.printCmd); err != nil { + return fail(err) + } + + var intent brevcloud.CreateRegistrationIntentResponse + if opts.mockRegistration { + intent = brevcloud.MockRegistrationIntent(cloudCredID) + } else { + req := brevcloud.CreateRegistrationIntentRequest{ + CloudCredID: cloudCredID, + OrgID: orgID, + } + resp, err := brevCloudClient.CreateRegistrationIntent(ctx, req) + if err != nil { + return fail(err) + } + intent = *resp + } + + if intent.RegistrationToken == "" { + return fail(errors.New("registration token missing from registration intent")) + } + + configCloudCredID := intent.CloudCredID + if configCloudCredID == "" { + configCloudCredID = cloudCredID + } + if err := writeAgentConfig(ctx, remote, host, intent.BrevCloudNodeID, intent.RegistrationToken, configCloudCredID, opts.printCmd); err != nil { + return fail(err) + } + + var result enrollResult + result.BrevCloudNodeID = intent.BrevCloudNodeID + result.CloudCredID = intent.CloudCredID + if result.CloudCredID == "" { + result.CloudCredID = cloudCredID + } + + if opts.wait && !opts.mockRegistration { + node, err := waitForBrevCloudNode(ctx, brevCloudClient, intent.BrevCloudNodeID) + if err != nil { + return fail(err) + } + result.CloudName = node.CloudName + result.Phase = node.Phase + result.LastSeenAt = node.LastSeenAt + result.AgentVersion = node.AgentVersion + t.Vprintf("brev cloud node active: phase=%s last_seen_at=%s agent=%s", node.Phase, node.LastSeenAt, node.AgentVersion) + } + + stopSpinner() + if uiEnabled { + if opts.mockRegistration { + t.Print("\n" + t.Green("✓ Mock enroll finished (config written, no restart)")) + } else { + t.Print("\n" + t.Green("✓ Registration complete")) + } + t.Print(fmt.Sprintf("Manage your %s %s", t.Yellow("Spark"), t.Blue(fmt.Sprintf("https://brev.nvidia.com/org/%s/environments", orgID)))) + } + + if opts.json { + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + return breverrors.WrapAndTrace(enc.Encode(result)) + } + + t.Vprintf("Enrolled BrevCloud node: %s (cloud cred: %s)", result.BrevCloudNodeID, result.CloudCredID) + if result.Phase != "" { + t.Vprintf("Phase: %s LastSeen: %s Agent: %s", result.Phase, result.LastSeenAt, result.AgentVersion) + } + + return nil +} + +func resolveSparkHost(_ *terminal.Terminal, alias string) (sparklib.Host, error) { + locator := sparklib.NewSyncSSHConfigLocator() + resolver := sparklib.NewDefaultSyncConfigResolver(files.AppFs, locator) + hosts, err := resolver.ResolveHosts() + if err != nil { + return sparklib.Host{}, breverrors.WrapAndTrace(err) + } + selected, err := sparklib.SelectHost(hosts, alias, sparklib.TerminalPrompter{}) + if err != nil { + return sparklib.Host{}, breverrors.WrapAndTrace(err) + } + return selected, nil +} + +func resolveDefaultCloudCred(ctx context.Context, loginStore *store.AuthHTTPStore) (string, error) { + client := brevcloud.NewClient(loginStore) + org, err := loginStore.GetActiveOrganizationOrDefault() + if err != nil { + return "", breverrors.WrapAndTrace(err) + } + if org == nil { + return "", breverrors.New("no active organization found") + } + + cred, err := client.ListCloudCredID(ctx, org.ID) + if err != nil { + return "", breverrors.WrapAndTrace(err) + } + + return cred, nil +} + +func ensureBrevCloudUser(ctx context.Context, remote sparklib.RemoteRunner, host sparklib.Host, printCmd bool) error { + userWriteCmd := buildWriteScriptCmd(userInstallScriptPath, installUserScript) + userExecuteCmd := buildExecuteScriptCmd(userInstallScriptPath, "") + + return runInstallScript(ctx, remote, host, printCmd, "brevcloud user", userInstallScriptPath, userWriteCmd, userExecuteCmd) +} + +func ensureAgentPresent(ctx context.Context, remote sparklib.RemoteRunner, host sparklib.Host, printCmd bool) error { + binaryWriteCmd := buildWriteScriptCmd(binaryInstallScriptPath, installBinaryScript) + binaryExecuteCmd := buildExecuteScriptCmd(binaryInstallScriptPath, "") + + serviceWriteCmd := buildWriteScriptCmd(serviceInstallScriptPath, installServiceScript) + serviceExecuteCmd := buildExecuteScriptCmd(serviceInstallScriptPath, fmt.Sprintf("STATE_DIR=%s", stateDirDefault)) + + // First check if brevd is already installed + checkCmd := fmt.Sprintf("test -x %s || sudo test -x %s", binaryPath, binaryPath) + if printCmd { + fmt.Printf("[remote] %s\n", checkCmd) + } + _, err := remote.Run(ctx, host, checkCmd) + // If brevd doesn't exist, install it from GitHub releases + if err != nil { + if printCmd { + fmt.Printf("[remote] Installing brevd from GitHub releases...\n") + } + + if err := runInstallScript(ctx, remote, host, printCmd, "brevd", binaryInstallScriptPath, binaryWriteCmd, binaryExecuteCmd); err != nil { + return err + } + } + + // Now check for systemd service file + serviceCheck := fmt.Sprintf("systemctl status %s >/dev/null 2>&1 || sudo systemctl status %s >/dev/null 2>&1 || test -f /etc/systemd/system/%s.service", serviceName, serviceName, serviceName) + if printCmd { + fmt.Printf("[remote] %s\n", serviceCheck) + } + out, err := remote.Run(ctx, host, serviceCheck) + if err != nil { + // If systemd service doesn't exist, install it + if printCmd { + fmt.Printf("[remote] Installing brevd systemd service...\n") + } + + if err := runInstallScript(ctx, remote, host, printCmd, "brevd systemd service", serviceInstallScriptPath, serviceWriteCmd, serviceExecuteCmd); err != nil { + return err + } + } else if printCmd { + fmt.Printf("[remote] systemd service check output: %s\n", strings.TrimSpace(out)) + } + + return nil +} + +func buildWriteScriptCmd(remotePath, script string) string { + return fmt.Sprintf("cat > %[1]s <<'SCRIPT_EOF'\n%[2]s\nSCRIPT_EOF\nchmod +x %[1]s", remotePath, script) +} + +func buildExecuteScriptCmd(remotePath, envPrefix string) string { + return fmt.Sprintf("%s %s && rm -f %s", envPrefix, remotePath, remotePath) +} + +func runInstallScript(ctx context.Context, remote sparklib.RemoteRunner, host sparklib.Host, printCmd bool, scriptName, remotePath, writeCmd, executeCmd string) error { + if printCmd { + fmt.Printf("[remote] Writing %s install script to %s\n", scriptName, remotePath) + } + if _, err := remote.Run(ctx, host, writeCmd); err != nil { + return fmt.Errorf("failed to write %s install script on %s: %w", scriptName, sparklib.HostLabel(host), err) + } + + if printCmd { + fmt.Printf("[remote] %s\n", executeCmd) + } + out, err := remote.Run(ctx, host, executeCmd) + if err != nil { + return fmt.Errorf("failed to install %s on %s: err=%v output=%s", scriptName, sparklib.HostLabel(host), err, strings.TrimSpace(out)) + } + if printCmd { + fmt.Printf("[remote] %s install output: %s\n", scriptName, strings.TrimSpace(out)) + } + return nil +} + +func ensureStateDir(ctx context.Context, remote sparklib.RemoteRunner, host sparklib.Host, printCmd bool) error { + cmds := []string{ + fmt.Sprintf("sudo install -d -m 700 -o brevcloud -g brevcloud %s", stateDirDefault), + fmt.Sprintf("sudo mkdir -p %s && sudo chown brevcloud:brevcloud %s && sudo chmod 700 %s", stateDirDefault, stateDirDefault, stateDirDefault), + fmt.Sprintf("mkdir -p %s", stateDirDefault), + } + var errs []string + for _, cmd := range cmds { + if printCmd { + fmt.Printf("[remote] %s\n", cmd) + } + out, err := remote.Run(ctx, host, cmd) + if err == nil { + return nil + } + errs = append(errs, fmt.Sprintf("cmd=%s err=%v output=%s", cmd, err, strings.TrimSpace(out))) + } + return fmt.Errorf("failed to create state dir on %s; attempts: %s", sparklib.HostLabel(host), strings.Join(errs, " | ")) +} + +func writeAgentConfig(ctx context.Context, remote sparklib.RemoteRunner, host sparklib.Host, brevCloudNodeID, registrationToken, cloudCredID string, printCmd bool) error { + brevCloudURL := strings.TrimSpace(os.Getenv(agentconfig.EnvBrevCloudURL)) + if brevCloudURL == "" { + return fmt.Errorf("BREV_AGENT_BREV_CLOUD_URL must be set to configure the agent") + } + lines := []string{ + fmt.Sprintf("%s=%s", agentconfig.EnvBrevCloudURL, brevCloudURL), + fmt.Sprintf("%s=%s", agentconfig.EnvRegistrationToken, registrationToken), + } + if brevCloudNodeID != "" { + lines = append(lines, fmt.Sprintf("%s=%s", agentconfig.EnvBrevCloudNodeID, brevCloudNodeID)) + } + if cloudCredID != "" { + lines = append(lines, fmt.Sprintf("%s=%s", agentconfig.EnvCloudCredID, cloudCredID)) + } + lines = append(lines, fmt.Sprintf("%s=%s", agentconfig.EnvStateDir, stateDirDefault), "") + payload := strings.Join(lines, "\n") + cmds := []string{ + fmt.Sprintf("cat <<'EOF' | sudo -n tee %s >/dev/null\n%sEOF\n", envFilePath, payload), + fmt.Sprintf("cat <<'EOF' | sudo tee %s >/dev/null\n%sEOF\n", envFilePath, payload), + fmt.Sprintf("cat <<'EOF' | tee %s >/dev/null\n%sEOF\n", envFilePath, payload), + } + var errs []string + for _, cmd := range cmds { + if printCmd { + fmt.Printf("[remote] %s\n", cmd) + } + out, err := remote.Run(ctx, host, cmd) + if err == nil { + return nil + } + errs = append(errs, fmt.Sprintf("cmd=%s err=%v output=%s", cmd, err, strings.TrimSpace(out))) + } + return fmt.Errorf("failed to write config on %s; attempts: %s", sparklib.HostLabel(host), strings.Join(errs, " | ")) +} + +func waitForBrevCloudNode(ctx context.Context, client *brevcloud.Client, brevCloudNodeID string) (*brevcloud.BrevCloudNode, error) { + interval := 3 * time.Second + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + node, err := client.GetBrevCloudNode(ctx, brevCloudNodeID) + if err != nil { + return nil, breverrors.WrapAndTrace(err) + } + if strings.EqualFold(node.Phase, "ACTIVE") || node.LastSeenAt != "" { + return node, nil + } + select { + case <-ctx.Done(): + return nil, breverrors.WrapAndTrace(ctx.Err()) + case <-ticker.C: + } + } +} + +func formatEnrollError(err error) string { + if err == nil { + return "" + } + + raw := strings.TrimSpace(err.Error()) + if isSudoError(raw) { + return "Sudo required on target; rerun with a TTY or configure passwordless sudo." + } + + if errors.Is(err, context.DeadlineExceeded) { + return "Timed out waiting for node to register" + } + + firstLine := raw + if idx := strings.IndexByte(raw, '\n'); idx >= 0 { + firstLine = strings.TrimSpace(raw[:idx]) + } + return firstLine +} + +func isSudoError(msg string) bool { + lower := strings.ToLower(msg) + return strings.Contains(lower, "usage: sudo") || + strings.Contains(lower, "sudo: a password is required") || + strings.Contains(lower, "sudo: no tty present") || + strings.Contains(lower, "sudo: sorry, you must have a tty") +} + +func probeConnectivity(ctx context.Context, remote sparklib.RemoteRunner, host sparklib.Host, printCmd bool) error { + cmd := "uname -a && whoami && hostname" + if printCmd { + fmt.Printf("[remote] %s\n", cmd) + } + out, err := remote.Run(ctx, host, cmd) + if err != nil { + return fmt.Errorf("ssh connectivity check failed on %s: err=%v output=%s", sparklib.HostLabel(host), err, strings.TrimSpace(out)) + } + return nil +} diff --git a/pkg/cmd/spark/install-binary.sh b/pkg/cmd/spark/install-binary.sh new file mode 100644 index 00000000..36d8fa17 --- /dev/null +++ b/pkg/cmd/spark/install-binary.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -eo pipefail + +# Installs the latest brev-cli binary as "brevd" from GitHub releases + +# Detect OS and architecture +OS="$(uname -s | tr '[:upper:]' '[:lower:]')" +ARCH="$(uname -m)" +case "${ARCH}" in + x86_64) ARCH="amd64" ;; + aarch64) ARCH="arm64" ;; +esac + +# Get the appropriate download URL for this platform +DOWNLOAD_URL="$(curl -s https://api.github.com/repos/brevdev/brev-cli/releases/latest | grep "browser_download_url.*${OS}.*${ARCH}" | cut -d '"' -f 4)" + +# Verify we found a suitable release +if [ -z "${DOWNLOAD_URL}" ]; then + echo "Error: Could not find release for ${OS} ${ARCH}" >&2 + exit 1 +fi + +# Create temporary directory and ensure cleanup +TMP_DIR="$(mktemp -d)" +trap 'rm -rf "${TMP_DIR}"' EXIT + +# Download and extract the release +curl -sL "${DOWNLOAD_URL}" -o "${TMP_DIR}/brev.tar.gz" +tar -xzf "${TMP_DIR}/brev.tar.gz" -C "${TMP_DIR}" + +# Install the binary as "brevd" to /usr/local/bin/brevd +sudo mv "${TMP_DIR}/brev" /usr/local/bin/brevd +sudo chmod +x /usr/local/bin/brevd + +echo "Successfully installed brevd to /usr/local/bin/brevd" diff --git a/pkg/cmd/spark/install-service.sh b/pkg/cmd/spark/install-service.sh new file mode 100644 index 00000000..7838a333 --- /dev/null +++ b/pkg/cmd/spark/install-service.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -eo pipefail + +STATE_DIR="${STATE_DIR:-/home/brevcloud/.brev-agent}" + +# Create systemd service file +sudo tee /etc/systemd/system/brevd.service > /dev/null <<'EOF' +[Unit] +Description=Brev Daemon +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +EnvironmentFile=-/etc/default/brevd +ExecStart=/usr/local/bin/brevd spark agent +Restart=on-failure +RestartSec=10s +User=brevcloud +Group=brevcloud + +[Install] +WantedBy=multi-user.target +EOF + +# Create default environment file if it doesn't exist +if [ ! -f /etc/default/brevd ]; then + sudo tee /etc/default/brevd > /dev/null </dev/null 2>&1; then + sudo useradd -m -d "${BREV_HOME}" -s /bin/bash "${BREV_USER}" +fi + +# Ensure the home directory exists with the right permissions. +sudo install -d -m 700 -o "${BREV_USER}" -g "${BREV_USER}" "${BREV_HOME}" + +# Grant passwordless sudo to the brev user. +echo "${BREV_USER} ALL=(ALL) NOPASSWD:ALL" | sudo tee "${SUDOERS_FILE}" >/dev/null +sudo chmod 0440 "${SUDOERS_FILE}" +sudo visudo -c -f "${SUDOERS_FILE}" + +# Prepare SSH directory and authorized_keys. +sudo install -d -m 700 -o "${BREV_USER}" -g "${BREV_USER}" "${BREV_HOME}/.ssh" +sudo touch "${BREV_HOME}/.ssh/authorized_keys" +sudo chmod 600 "${BREV_HOME}/.ssh/authorized_keys" +sudo chown -R "${BREV_USER}:${BREV_USER}" "${BREV_HOME}/.ssh" + +# Final ownership consistency. +sudo chown -R "${BREV_USER}:${BREV_USER}" "${BREV_HOME}" + +echo "User ${BREV_USER} is ready at ${BREV_HOME}" \ No newline at end of file diff --git a/pkg/cmd/spark/spark.go b/pkg/cmd/spark/spark.go new file mode 100644 index 00000000..0ceccb20 --- /dev/null +++ b/pkg/cmd/spark/spark.go @@ -0,0 +1,32 @@ +package spark + +import ( + "github.com/spf13/cobra" + + "github.com/brevdev/brev-cli/pkg/store" + "github.com/brevdev/brev-cli/pkg/terminal" +) + +const ( + sparkShort = "Manage NVIDIA Spark connectivity" + sparkLong = "Commands for connecting Brev to NVIDIA Spark environments." +) + +// NewCmdSpark is the root Spark command; subcommands (e.g., ssh, connect) +// are added beneath it. +func NewCmdSpark(t *terminal.Terminal, loginCmdStore *store.AuthHTTPStore, noLoginCmdStore *store.AuthHTTPStore) *cobra.Command { + // Keep stores for future subcommands (e.g., connect) even if unused now. + _ = loginCmdStore + _ = noLoginCmdStore + + cmd := &cobra.Command{ + Use: "spark", + Short: sparkShort, + Long: sparkLong, + } + + // cmd.AddCommand(NewCmdSparkEnroll(t, loginCmdStore)) + // cmd.AddCommand(NewCmdSparkAgent(t)) + + return cmd +} diff --git a/pkg/cmd/sshkeys/sshkeys.go b/pkg/cmd/sshkeys/sshkeys.go index a0c18987..9197af45 100644 --- a/pkg/cmd/sshkeys/sshkeys.go +++ b/pkg/cmd/sshkeys/sshkeys.go @@ -44,11 +44,11 @@ func NewCmdSSHKeys(t *terminal.Terminal, sshKeyStore SSHKeyStore) *cobra.Command } func DisplaySSHKeys(t *terminal.Terminal, publicKey string) { - t.Vprintf(publicKey) + t.Vprint(publicKey) t.Print("\n") - t.Eprintf(t.Yellow("Copy 👆 and add it to your git provider:\n")) - t.Eprintf(t.Yellow("\tGithub: https://github.com/settings/keys\n")) - t.Eprintf(t.Yellow("\tGitlab: https://gitlab.com/-/profile/keys\n")) - t.Eprintf(t.Yellow("Check authentication by starting a new instance\n")) - t.Eprintf(t.Yellow("\tbrev start --empty --name test-ssh && brev delete test-ssh\n")) + t.Eprint(t.Yellow("Copy 👆 and add it to your git provider:\n")) + t.Eprint(t.Yellow("\tGithub: https://github.com/settings/keys\n")) + t.Eprint(t.Yellow("\tGitlab: https://gitlab.com/-/profile/keys\n")) + t.Eprint(t.Yellow("Check authentication by starting a new instance\n")) + t.Eprint(t.Yellow("\tbrev start --empty --name test-ssh && brev delete test-ssh\n")) } diff --git a/pkg/cmd/start/start.go b/pkg/cmd/start/start.go index 77437dd2..05baf718 100644 --- a/pkg/cmd/start/start.go +++ b/pkg/cmd/start/start.go @@ -32,6 +32,8 @@ var ( ` ) +const instanceSpinnerSuffix = " Creating your instance. Hang tight 🤙" + type StartStore interface { util.GetWorkspaceByNameOrIDErrStore GetWorkspaces(organizationID string, options *store.GetWorkspacesOptions) ([]entity.Workspace, error) @@ -250,7 +252,7 @@ func maybeStartEmpty(t *terminal.Terminal, user *entity.User, options StartOptio func startWorkspaceFromPath(user *entity.User, t *terminal.Terminal, options StartOptions, startStore StartStore) error { pathExists := allutil.DoesPathExist(options.RepoOrPathOrNameOrID) if !pathExists { - return fmt.Errorf(strings.Join([]string{"Path:", options.RepoOrPathOrNameOrID, "does not exist."}, " ")) + return fmt.Errorf("Path: %s does not exist.", options.RepoOrPathOrNameOrID) } var gitpath string if options.RepoOrPathOrNameOrID == "." { @@ -258,13 +260,13 @@ func startWorkspaceFromPath(user *entity.User, t *terminal.Terminal, options Sta } else { gitpath = filepath.Join(options.RepoOrPathOrNameOrID, ".git", "config") } - file, error := startStore.GetFileAsString(gitpath) - if error != nil { - return fmt.Errorf(strings.Join([]string{"Could not read .git/config at", options.RepoOrPathOrNameOrID}, " ")) + fileContents, readErr := startStore.GetFileAsString(gitpath) + if readErr != nil { + return fmt.Errorf("Could not read .git/config at %s", options.RepoOrPathOrNameOrID) } // Get GitUrl var gitURL string - for _, v := range strings.Split(file, "\n") { + for _, v := range strings.Split(fileContents, "\n") { if strings.Contains(v, "url") { gitURL = strings.Split(v, "= ")[1] } @@ -365,7 +367,7 @@ func createEmptyWorkspace(user *entity.User, t *terminal.Terminal, options Start t.Vprintf("\tCloud %s\n\n", t.Green(cwOptions.WorkspaceGroupID)) s := t.NewSpinner() - s.Suffix = " Creating your instance. Hang tight 🤙" + s.Suffix = instanceSpinnerSuffix s.Start() w, err := startStore.CreateWorkspace(orgID, cwOptions) if err != nil { @@ -471,7 +473,7 @@ func joinProjectWithNewWorkspace(t *terminal.Terminal, templateWorkspace entity. t.Vprintf("\tCloud %s\n", cwOptions.WorkspaceGroupID) s := t.NewSpinner() - s.Suffix = " Creating your instance. Hang tight 🤙" + s.Suffix = instanceSpinnerSuffix s.Start() w, err := startStore.CreateWorkspace(orgID, cwOptions) if err != nil { @@ -639,7 +641,7 @@ func createWorkspace(user *entity.User, t *terminal.Terminal, workspace NewWorks t.Vprintf("\tCloud %s\n", options.WorkspaceGroupID) s := t.NewSpinner() - s.Suffix = " Creating your instance. Hang tight 🤙" + s.Suffix = instanceSpinnerSuffix s.Start() w, err := startStore.CreateWorkspace(orgID, options) if err != nil { diff --git a/pkg/cmd/updatemodel/updatemodel.go b/pkg/cmd/updatemodel/updatemodel.go index 032563f2..389c1435 100644 --- a/pkg/cmd/updatemodel/updatemodel.go +++ b/pkg/cmd/updatemodel/updatemodel.go @@ -62,7 +62,7 @@ func NewCmdupdatemodel(t *terminal.Terminal, store updatemodelStore) *cobra.Comm if params != nil && params.WorkspaceKeyPair != nil { keys := params.WorkspaceKeyPair - pubkeys, err := ssh.NewPublicKeys("ubuntu", []byte(keys.PrivateKeyData), "") //nolint:govet //abc + pubkeys, err := ssh.NewPublicKeys("ubuntu", []byte(keys.PrivateKeyData), "") if err != nil { return nil, breverrors.WrapAndTrace(err) } diff --git a/pkg/config/config.go b/pkg/config/config.go index c89d48aa..56aeb906 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -8,6 +8,7 @@ type EnvVarName string // should be caps with underscore const ( brevAPIURL EnvVarName = "BREV_API_URL" + brevAPIV2URL EnvVarName = "BREV_API_V2_URL" brevGRPCURL EnvVarName = "BREV_GRPC_URL" brevAuthURL EnvVarName = "BREV_AUTH_URL" brevAuthIssuerURL EnvVarName = "BREV_AUTH_ISSUER_URL" @@ -32,9 +33,17 @@ func (c ConstantsConfig) GetBrevAPIURl() string { return getEnvOrDefault(brevAPIURL, "https://brevapi.us-west-2-prod.control-plane.brev.dev") } -func (c ConstantsConfig) GetBrevGRPCURL() string { - // GRPC does not use https:// prefix - return getEnvOrDefault(brevGRPCURL, "api.brev.dev:443") +// func (c ConstantsConfig) GetBrevGRPCURL() string { +// return getEnvOrDefault(brevGRPCURL, "api.brev.dev:443") +// } + +// GetDevplaneAPIURL returns the base URL for DevPlane APIs. +// Prefer BREV_API_V2_URL when set, otherwise fall back to BREV_API_URL. +func (c ConstantsConfig) GetDevplaneAPIURL() string { + if v2 := os.Getenv(string(brevAPIV2URL)); v2 != "" { + return v2 + } + return c.GetBrevAPIURl() } func (c ConstantsConfig) GetBrevAuthURL() string { diff --git a/pkg/files/files.go b/pkg/files/files.go index fbf0ad56..45a38391 100644 --- a/pkg/files/files.go +++ b/pkg/files/files.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "io/ioutil" - "os" "os/exec" "path/filepath" @@ -186,7 +185,7 @@ func OverwriteJSON(fs afero.Fs, filepath string, v interface{}) error { if err != nil { return breverrors.WrapAndTrace(err) } - err = ioutil.WriteFile(filepath, dataBytes, os.ModePerm) + err = ioutil.WriteFile(filepath, dataBytes, 0o600) if err != nil { return breverrors.WrapAndTrace(err) } diff --git a/pkg/mergeshells/mergeshells.go b/pkg/mergeshells/mergeshells.go index 6de97c7c..d9622920 100644 --- a/pkg/mergeshells/mergeshells.go +++ b/pkg/mergeshells/mergeshells.go @@ -214,7 +214,7 @@ func WriteBrevFile(t *terminal.Terminal, deps []string, gitURL string, path stri t.Vprint(t.Yellow(strings.Join(deps, " \n"))) shellString := GenerateShellScript(path) fmt.Println(GenerateLogs(shellString)) - mderr := os.MkdirAll(filepath.Join(path, ".brev"), os.ModePerm) + mderr := os.MkdirAll(filepath.Join(path, ".brev"), 0o750) if mderr == nil { // generate a string that is the collections.Concatenation of dependency-ordering the contents of all the dependencies // found by cat'ing the directory generated from the deps string, using the translated ruby code with go generics @@ -481,11 +481,8 @@ func nodeVersion(path string) *string { paths := recursivelyFindFile([]string{"package\\-lock\\.json$", "package\\.json$"}, path) retval := "" if len(paths) > 0 { - sort.Strings(paths) - i := len(paths) - 1 - keypath := "engines.node" jsonstring, _ := files.CatFile(paths[i]) value := gjson.Get(jsonstring, keypath) @@ -503,20 +500,15 @@ func gatsbyVersion(path string) *string { retval := "" var foundGatsby bool if len(paths) > 0 { - sort.Strings(paths) for _, path := range paths { keypath := "dependencies.gatsby" - jsonstring, err := files.CatFile(path) + jsonstring, _ := files.CatFile(path) value := gjson.Get(jsonstring, keypath) - if err != nil { - // - } if value.String() != "" { foundGatsby = true } - } if foundGatsby { return &retval @@ -530,17 +522,13 @@ func goVersion(path string) *string { paths := recursivelyFindFile([]string{"go\\.mod"}, path) if len(paths) > 0 { - sort.Strings(paths) for _, path := range paths { fmt.Println(path) - res, err := readGoMod(path) - if err != nil { - // - } + res, _ := readGoMod(path) + return &res } - } return nil } @@ -564,7 +552,6 @@ func recursivelyFindFile(filenames []string, path string) []string { for _, f := range files { dir, err := os.Stat(appendPath(path, f.Name())) if err != nil { - // fmt.Println(t.Red(err.Error())) } else { for _, filename := range filenames { @@ -572,16 +559,7 @@ func recursivelyFindFile(filenames []string, path string) []string { res := r.MatchString(f.Name()) if res { - // t.Vprint(t.Yellow(filename) + "---" + t.Yellow(path+f.Name())) paths = append(paths, appendPath(path, f.Name())) - - // fileContents, err := catFile(appendPath(path, f.Name())) - // if err != nil { - // // - // } - - // TODO: read - // if file has json, read the json } } @@ -590,9 +568,7 @@ func recursivelyFindFile(filenames []string, path string) []string { } } } - // TODO: make the list collections.Unique - return paths } diff --git a/pkg/spark/parser.go b/pkg/spark/parser.go new file mode 100644 index 00000000..f704ddaf --- /dev/null +++ b/pkg/spark/parser.go @@ -0,0 +1,191 @@ +package spark + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/kevinburke/ssh_config" + "github.com/spf13/afero" + + breverrors "github.com/brevdev/brev-cli/pkg/errors" +) + +type SyncConfigResolver struct { + fs afero.Fs + locator ConfigLocator + homeDir func() (string, error) +} + +func NewDefaultSyncConfigResolver(fs afero.Fs, locator ConfigLocator) SyncConfigResolver { + return NewSyncConfigResolver(fs, locator, os.UserHomeDir) +} + +func NewSyncConfigResolver(fs afero.Fs, locator ConfigLocator, home func() (string, error)) SyncConfigResolver { + return SyncConfigResolver{ + fs: fs, + locator: locator, + homeDir: home, + } +} + +func (r SyncConfigResolver) ResolveHosts() ([]Host, error) { + configPath, err := r.locator.ConfigPath() + if err != nil { + return nil, breverrors.WrapAndTrace(err) + } + + exists, err := afero.Exists(r.fs, configPath) + if err != nil { + return nil, breverrors.WrapAndTrace(err) + } + if !exists { + return nil, breverrors.WrapAndTrace(fmt.Errorf("Sync ssh_config not found at %s. Launch NVIDIA Sync then rerun.", configPath)) + } + + data, err := afero.ReadFile(r.fs, configPath) + if err != nil { + return nil, breverrors.WrapAndTrace(err) + } + + cfg, err := ssh_config.Decode(bytes.NewReader(data)) + if err != nil { + return nil, breverrors.WrapAndTrace(fmt.Errorf("failed to parse Sync ssh_config at %s", configPath)) + } + + home, err := r.homeDir() + if err != nil { + return nil, breverrors.WrapAndTrace(err) + } + + var hosts []Host + seen := map[string]bool{} + for _, hostBlock := range cfg.Hosts { + aliases := sparkAliases(hostBlock.Patterns) + if len(aliases) == 0 { + continue + } + + kvs := collectKVs(hostBlock.Nodes) + for _, alias := range aliases { + if seen[alias] { + continue + } + h, err := buildHost(alias, kvs, home) + if err != nil { + return nil, breverrors.WrapAndTrace(err) + } + seen[alias] = true + hosts = append(hosts, h) + } + } + + if len(hosts) == 0 { + return nil, breverrors.WrapAndTrace(fmt.Errorf("no Spark hosts found in %s. Launch NVIDIA Sync then rerun.", configPath)) + } + + return hosts, nil +} + +func buildHost(alias string, kvs map[string]string, home string) (Host, error) { + hostname := kvs["Hostname"] + if hostname == "" { + return Host{}, fmt.Errorf("missing Hostname for %s", alias) + } + + user := kvs["User"] + if user == "" { + return Host{}, fmt.Errorf("missing User for %s", alias) + } + + port := 22 + if portStr := kvs["Port"]; portStr != "" { + parsed, err := strconv.Atoi(portStr) + if err != nil { + return Host{}, fmt.Errorf("invalid Port for %s: %s", alias, portStr) + } + port = parsed + } + + identityFile := kvs["IdentityFile"] + if identityFile == "" { + return Host{}, fmt.Errorf("missing IdentityFile for %s", alias) + } + identityFile = expandPath(identityFile, home) + + options := map[string]string{} + for k, v := range kvs { + if isCoreField(k) { + continue + } + options[k] = v + } + + return Host{ + Alias: alias, + Hostname: hostname, + User: user, + Port: port, + IdentityFile: identityFile, + Options: options, + }, nil +} + +func sparkAliases(patterns []*ssh_config.Pattern) []string { + var aliases []string + for _, p := range patterns { + name := strings.TrimSpace(p.String()) + // Include all explicitly named hosts; skip wildcards. + if name == "" || name == "*" { + continue + } + aliases = append(aliases, name) + } + return aliases +} + +func collectKVs(nodes []ssh_config.Node) map[string]string { + result := map[string]string{} + for _, node := range nodes { + kv, ok := node.(*ssh_config.KV) + if !ok { + continue + } + key := strings.TrimSpace(kv.Key) + value := strings.TrimSpace(kv.Value) + if key == "" { + continue + } + result[key] = value + } + return result +} + +func expandPath(path string, home string) string { + if path == "" { + return path + } + + if strings.HasPrefix(path, "~") { + trimmed := strings.TrimPrefix(path, "~") + return filepath.Join(home, strings.TrimPrefix(trimmed, string(filepath.Separator))) + } + + if filepath.IsAbs(path) { + return path + } + + return filepath.Join(home, path) +} + +func isCoreField(key string) bool { + switch strings.ToLower(key) { + case "hostname", "user", "port", "identityfile": + return true + default: + return false + } +} diff --git a/pkg/spark/parser_test.go b/pkg/spark/parser_test.go new file mode 100644 index 00000000..ad83fc48 --- /dev/null +++ b/pkg/spark/parser_test.go @@ -0,0 +1,86 @@ +package spark + +import ( + "testing" + + "github.com/spf13/afero" + "github.com/stretchr/testify/require" +) + +const testerHome = "/home/tester" + +type staticLocator struct { + path string +} + +func (s staticLocator) ConfigPath() (string, error) { + return s.path, nil +} + +func TestResolveHostsParsesSparkEntries(t *testing.T) { + fs := afero.NewMemMapFs() + configPath := "/tmp/ssh_config" + config := ` +Host spark-one spark-alt + Hostname spark-one.local + User ubuntu + Port 2222 + IdentityFile ~/.ssh/spark_one + ProxyJump jump.example.com + StrictHostKeyChecking no + +Host notspark + Hostname ignored.local + User nobody + +Host spark-two + Hostname 10.0.0.2 + User ec2-user + IdentityFile /keys/two.pem +` + err := afero.WriteFile(fs, configPath, []byte(config), 0o600) + require.NoError(t, err) + + resolver := NewSyncConfigResolver(fs, staticLocator{path: configPath}, func() (string, error) { return testerHome, nil }) + hosts, err := resolver.ResolveHosts() + require.NoError(t, err) + require.Len(t, hosts, 3) + + require.Equal(t, "spark-one", hosts[0].Alias) + require.Equal(t, "spark-alt", hosts[1].Alias) + require.Equal(t, "spark-two", hosts[2].Alias) + + require.Equal(t, testerHome+"/.ssh/spark_one", hosts[0].IdentityFile) + require.Equal(t, "spark-one.local", hosts[0].Hostname) + require.Equal(t, 2222, hosts[0].Port) + require.Equal(t, "ubuntu", hosts[0].User) + require.Equal(t, map[string]string{"ProxyJump": "jump.example.com", "StrictHostKeyChecking": "no"}, hosts[0].Options) + + require.Equal(t, "/keys/two.pem", hosts[2].IdentityFile) + require.Equal(t, 22, hosts[2].Port) // default +} + +func TestResolveHostsMissingFile(t *testing.T) { + fs := afero.NewMemMapFs() + resolver := NewSyncConfigResolver(fs, staticLocator{path: "/tmp/missing"}, func() (string, error) { return testerHome, nil }) + + _, err := resolver.ResolveHosts() + require.Error(t, err) +} + +func TestResolveHostsMissingFields(t *testing.T) { + fs := afero.NewMemMapFs() + configPath := "/tmp/ssh_config" + config := ` +Host spark-one + User ubuntu + IdentityFile ~/.ssh/spark_one +` + err := afero.WriteFile(fs, configPath, []byte(config), 0o600) + require.NoError(t, err) + + resolver := NewSyncConfigResolver(fs, staticLocator{path: configPath}, func() (string, error) { return testerHome, nil }) + _, err = resolver.ResolveHosts() + require.Error(t, err) + require.ErrorContains(t, err, "Hostname") +} diff --git a/pkg/spark/paths.go b/pkg/spark/paths.go new file mode 100644 index 00000000..f07ddb39 --- /dev/null +++ b/pkg/spark/paths.go @@ -0,0 +1,57 @@ +package spark + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + + breverrors "github.com/brevdev/brev-cli/pkg/errors" +) + +// ConfigLocator resolves the path to the Sync-managed ssh_config. +type ConfigLocator interface { + ConfigPath() (string, error) +} + +// SyncSSHConfigLocator resolves the ssh_config generated by NVIDIA Sync. +type SyncSSHConfigLocator struct { + homeDir func() (string, error) + goos string +} + +func NewSyncSSHConfigLocator() SyncSSHConfigLocator { + return SyncSSHConfigLocator{ + homeDir: os.UserHomeDir, + goos: runtime.GOOS, + } +} + +func NewSyncSSHConfigLocatorWithHome(home func() (string, error), goos string) SyncSSHConfigLocator { + return SyncSSHConfigLocator{ + homeDir: home, + goos: goos, + } +} + +// ConfigPath returns the platform-appropriate path to the Sync ssh_config file. +func (l SyncSSHConfigLocator) ConfigPath() (string, error) { + home, err := l.homeDir() + if err != nil { + return "", breverrors.WrapAndTrace(err) + } + + var parts []string + switch l.goos { + case "darwin": + parts = []string{home, "Library", "Application Support", "NVIDIA", "Sync", "config", "ssh_config"} + case "linux": + parts = []string{home, ".config", "NVIDIA", "Sync", "config", "ssh_config"} + case "windows": + parts = []string{home, "AppData", "Local", "NVIDIA Corporation", "Sync", "config", "ssh_config"} + default: + return "", breverrors.WrapAndTrace(fmt.Errorf("unsupported OS for Sync ssh_config: %s", l.goos)) + } + + return filepath.Clean(filepath.Join(parts...)), nil +} diff --git a/pkg/spark/paths_test.go b/pkg/spark/paths_test.go new file mode 100644 index 00000000..f6f15674 --- /dev/null +++ b/pkg/spark/paths_test.go @@ -0,0 +1,51 @@ +package spark + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSyncSSHConfigLocatorPaths(t *testing.T) { + cases := []struct { + name string + goos string + home string + expected string + }{ + { + name: "darwin", + goos: "darwin", + home: "/Users/tester", + expected: filepath.Join("/Users/tester", "Library", "Application Support", "NVIDIA", "Sync", "config", "ssh_config"), + }, + { + name: "linux", + goos: "linux", + home: "/home/tester", + expected: filepath.Join("/home/tester", ".config", "NVIDIA", "Sync", "config", "ssh_config"), + }, + { + name: "windows", + goos: "windows", + home: filepath.FromSlash("C:/Users/tester"), + expected: filepath.Join(filepath.FromSlash("C:/Users/tester"), "AppData", "Local", "NVIDIA Corporation", "Sync", "config", "ssh_config"), + }, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + locator := NewSyncSSHConfigLocatorWithHome(func() (string, error) { return tt.home, nil }, tt.goos) + path, err := locator.ConfigPath() + require.NoError(t, err) + require.Equal(t, tt.expected, path) + }) + } +} + +func TestSyncSSHConfigLocatorUnsupported(t *testing.T) { + locator := NewSyncSSHConfigLocatorWithHome(func() (string, error) { return "/home/tester", nil }, "plan9") + _, err := locator.ConfigPath() + require.Error(t, err) +} diff --git a/pkg/spark/remote.go b/pkg/spark/remote.go new file mode 100644 index 00000000..de490ed8 --- /dev/null +++ b/pkg/spark/remote.go @@ -0,0 +1,109 @@ +package spark + +import ( + "bytes" + "context" + "fmt" + "os/exec" + "sort" + "strconv" + "strings" + "time" + + "github.com/brevdev/brev-cli/pkg/errors" + "github.com/spf13/afero" +) + +// RemoteRunner executes commands on a Spark host over ssh and returns stdout/stderr. +type RemoteRunner struct { + fs afero.Fs +} + +func NewRemoteRunner(fs afero.Fs) RemoteRunner { + return RemoteRunner{fs: fs} +} + +// Run executes the provided remote shell command via ssh and returns combined output. +func (r RemoteRunner) Run(ctx context.Context, host Host, remoteCmd string) (string, error) { + if host.IdentityFile == "" { + return "", errors.WrapAndTrace(fmt.Errorf("missing identity file for %s", host.Alias)) + } + + exists, err := afero.Exists(r.fs, host.IdentityFile) + if err != nil { + return "", err + } + if !exists { + return "", errors.WrapAndTrace(fmt.Errorf("identity file not found at %s", host.IdentityFile)) + } + + argv := buildSSHCommand(host, remoteCmd) + cmd := exec.CommandContext(ctx, argv[0], argv[1:]...) //nolint:gosec // command args constructed from validated host data + + var buf bytes.Buffer + cmd.Stdout = &buf + cmd.Stderr = &buf + + if err := cmd.Run(); err != nil { + return buf.String(), errors.WrapAndTrace(fmt.Errorf("ssh to %s failed for command %q: %w\noutput:\n%s", hostLabel(host), remoteCmd, err, buf.String())) + } + return buf.String(), nil +} + +func buildSSHCommand(host Host, remoteCmd string) []string { + args := []string{ + "ssh", + "-i", host.IdentityFile, + "-p", strconv.Itoa(host.Port), + "-o", "BatchMode=yes", + } + + if len(host.Options) > 0 { + keys := make([]string, 0, len(host.Options)) + for k := range host.Options { + keys = append(keys, k) + } + sort.Strings(keys) + for _, key := range keys { + args = append(args, "-o", fmt.Sprintf("%s=%s", key, host.Options[key])) + } + } + + escaped := escapeSingleQuotes(remoteCmd) + args = append(args, fmt.Sprintf("%s@%s", host.User, host.Hostname), "--", "bash", "-lc", fmt.Sprintf("'%s'", escaped)) + return args +} + +// WithTimeout wraps a context with timeout; if parent already has deadline it respects the earlier one. +func WithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) { + if d <= 0 { + return ctx, func() {} + } + return context.WithTimeout(ctx, d) +} + +// QuoteForShell safely wraps a string for single-quoted shell contexts. +func QuoteForShell(s string) string { + if s == "" { + return "''" + } + // Replace single quotes with '"'"' sequence. + return "'" + strings.ReplaceAll(s, "'", `'\"'\"'`) + "'" +} + +func hostLabel(h Host) string { + return fmt.Sprintf("%s@%s:%d", h.User, h.Hostname, h.Port) +} + +// HostLabel is exported for reuse in other packages. +func HostLabel(h Host) string { + return hostLabel(h) +} + +// escapeSingleQuotes makes a string safe for single-quoted bash -lc payloads. +func escapeSingleQuotes(s string) string { + if s == "" { + return s + } + return strings.ReplaceAll(s, "'", `'\''`) +} diff --git a/pkg/spark/select.go b/pkg/spark/select.go new file mode 100644 index 00000000..621e4ec5 --- /dev/null +++ b/pkg/spark/select.go @@ -0,0 +1,76 @@ +package spark + +import ( + "fmt" + "strings" + + "github.com/brevdev/brev-cli/pkg/terminal" + + breverrors "github.com/brevdev/brev-cli/pkg/errors" +) + +// SelectHost chooses the correct Host based on user input or interactive prompt. +func SelectHost(hosts []Host, requestedAlias string, prompter Prompter) (Host, error) { + if len(hosts) == 0 { + return Host{}, breverrors.WrapAndTrace(fmt.Errorf("no Spark hosts available; open NVIDIA Sync and retry")) + } + + if requestedAlias != "" { + for _, h := range hosts { + if h.Alias == requestedAlias { + return h, nil + } + } + return Host{}, breverrors.WrapAndTrace(fmt.Errorf("Spark host %s not found. Available: %s", requestedAlias, strings.Join(hostAliases(hosts), ", "))) + } + + if len(hosts) == 1 { + return hosts[0], nil + } + + if prompter == nil { + return Host{}, breverrors.WrapAndTrace(fmt.Errorf("multiple Spark hosts detected; rerun with an alias (%s)", strings.Join(hostAliases(hosts), ", "))) + } + + label := "Select Spark host" + choices := hostChoices(hosts) + selection, err := prompter.Select(label, choices) + if err != nil { + return Host{}, breverrors.WrapAndTrace(err) + } + + for i, choice := range choices { + if choice == selection { + return hosts[i], nil + } + } + + return Host{}, breverrors.WrapAndTrace(fmt.Errorf("invalid selection")) +} + +// TerminalPrompter bridges to the CLI prompt implementation. +type TerminalPrompter struct{} + +func (TerminalPrompter) Select(label string, options []string) (string, error) { + content := terminal.PromptSelectContent{ + Label: label, + Items: options, + } + return terminal.PromptSelectInput(content), nil +} + +func hostAliases(hosts []Host) []string { + aliases := make([]string, 0, len(hosts)) + for _, h := range hosts { + aliases = append(aliases, h.Alias) + } + return aliases +} + +func hostChoices(hosts []Host) []string { + choices := make([]string, 0, len(hosts)) + for _, h := range hosts { + choices = append(choices, fmt.Sprintf("%s (%s@%s:%d)", h.Alias, h.User, h.Hostname, h.Port)) + } + return choices +} diff --git a/pkg/spark/select_test.go b/pkg/spark/select_test.go new file mode 100644 index 00000000..85681c57 --- /dev/null +++ b/pkg/spark/select_test.go @@ -0,0 +1,49 @@ +package spark + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +type fakePrompter struct { + selection string + err error +} + +func (f fakePrompter) Select(_ string, _ []string) (string, error) { + return f.selection, f.err +} + +func TestSelectHostWithAlias(t *testing.T) { + host := Host{Alias: "spark-one"} + selected, err := SelectHost([]Host{host}, "spark-one", fakePrompter{}) + require.NoError(t, err) + require.Equal(t, host, selected) +} + +func TestSelectHostAutoPickSingle(t *testing.T) { + host := Host{Alias: "spark-one"} + selected, err := SelectHost([]Host{host}, "", fakePrompter{}) + require.NoError(t, err) + require.Equal(t, host, selected) +} + +func TestSelectHostPromptsWhenMultiple(t *testing.T) { + hosts := []Host{ + {Alias: "spark-one", User: "u1", Hostname: "h1", Port: 22}, + {Alias: "spark-two", User: "u2", Hostname: "h2", Port: 22}, + } + + choice := fmt.Sprintf("%s (%s@%s:%d)", hosts[1].Alias, hosts[1].User, hosts[1].Hostname, hosts[1].Port) + selected, err := SelectHost(hosts, "", fakePrompter{selection: choice}) + require.NoError(t, err) + require.Equal(t, hosts[1], selected) +} + +func TestSelectHostMissingAlias(t *testing.T) { + hosts := []Host{{Alias: "spark-one"}} + _, err := SelectHost(hosts, "spark-missing", fakePrompter{}) + require.Error(t, err) +} diff --git a/pkg/spark/types.go b/pkg/spark/types.go new file mode 100644 index 00000000..b26079da --- /dev/null +++ b/pkg/spark/types.go @@ -0,0 +1,28 @@ +package spark + +// Host represents a Spark target discovered from the NVIDIA Sync ssh_config. +// Options holds any additional ssh config options we should pass through +// (e.g., ProxyJump, StrictHostKeyChecking). +type Host struct { + Alias string + Hostname string + User string + Port int + IdentityFile string + Options map[string]string +} + +// HostResolver discovers Spark hosts available for connection. +type HostResolver interface { + ResolveHosts() ([]Host, error) +} + +// Prompter surfaces interactive selection to the user. +type Prompter interface { + Select(label string, options []string) (string, error) +} + +// Executor runs the ssh command (or other Spark-related commands later). +type Executor interface { + Run(argv []string) error +} diff --git a/pkg/store/basic.go b/pkg/store/basic.go index 72260298..5e182867 100644 --- a/pkg/store/basic.go +++ b/pkg/store/basic.go @@ -10,6 +10,8 @@ import ( breverrors "github.com/brevdev/brev-cli/pkg/errors" ) +const goosLinux = "linux" + type BasicStore struct { envGetter func(string) string } @@ -31,7 +33,7 @@ func (b BasicStore) GetWSLHostHomeDir() (string, error) { if runtime.GOOS == "windows" { return "", breverrors.New("not supported on windows") } - if runtime.GOOS == "linux" { + if runtime.GOOS == goosLinux { path := "" if b.envGetter == nil { path = os.Getenv("PATH") diff --git a/pkg/terminal/terminal.go b/pkg/terminal/terminal.go index 8e61276d..1304d159 100644 --- a/pkg/terminal/terminal.go +++ b/pkg/terminal/terminal.go @@ -51,23 +51,23 @@ func New() (t *Terminal) { } func (t *Terminal) Print(a string) { - fmt.Fprintln(t.out, a) + _, _ = fmt.Fprintln(t.out, a) } func (t *Terminal) Vprint(a string) { - fmt.Fprintln(t.verbose, a) + _, _ = fmt.Fprintln(t.verbose, a) } func (t *Terminal) Vprintf(format string, a ...interface{}) { - fmt.Fprintf(t.verbose, format, a...) + _, _ = fmt.Fprintf(t.verbose, format, a...) } func (t *Terminal) Eprint(a string) { - fmt.Fprintln(t.err, a) + _, _ = fmt.Fprintln(t.err, a) } func (t *Terminal) Eprintf(format string, a ...interface{}) { - fmt.Fprintf(t.err, format, a...) + _, _ = fmt.Fprintf(t.err, format, a...) } func (t *Terminal) Errprint(err error, a string) { diff --git a/pkg/workspacemanager/workspacemanager.go b/pkg/workspacemanager/workspacemanager.go deleted file mode 100644 index 7063a86d..00000000 --- a/pkg/workspacemanager/workspacemanager.go +++ /dev/null @@ -1,240 +0,0 @@ -package workspacemanager - -import ( - "errors" - "fmt" - "os/exec" - "strings" - - "github.com/brevdev/brev-cli/pkg/collections" - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/store" -) - -type WorkspaceManager struct{} - -func NewWorkspaceManager() *WorkspaceManager { - // NOT YET IMPLEMENTED - return &WorkspaceManager{} -} - -func (w WorkspaceManager) Start(workspaceID string) error { - // somewhere we need to check if they have docker installed and all the tools needed before running this - fmt.Println(workspaceID) - // get a list of workspaces that have been run previously - // if the workspaceID matches any of them, then run the (first, last, most recently run?) version - previouslyRunWorkspaces := fetchPreviouslyRunWorkspaces() - matchingWorkspaces := collections.Filter(previouslyRunWorkspaces, collections.P2(nameMatches, workspaceID)) - // it should either find a pre-existing container with this name - if len(matchingWorkspaces) > 0 { - // and start it if it is currently stopped - workspaceToStart := collections.First(collections.SortBy(workspacePriorityFunc, matchingWorkspaces)) - return startLocalWorkspace(*workspaceToStart) - } - // or if that is not the case, then start a new one via the following steps: - // given a workspace id, resolve it to get the full data object for the workspace - workspace := resolveWorkspaceID(workspaceID) - // turn the workspace object into setup parameters - setupParams := workspaceToSetupParams(workspace) - // we need the workspace base container - baseImage := fetchBaseImage() - // we need to retrieve the kubernetes token - // we need to mix in the kubernetes token - kubernetesServiceTokenDir := fetchKubernetesServiceTokenDir() - mountToken := collections.P2(mixInKubernetesToken, kubernetesServiceTokenDir) - // we need to mount the setup parameters to it, we need to map in the kubernetes service token - mountParams := collections.P2(mountSetupParams, setupParams) - // we need to mount a volume for the workspace/ folder - - mountWSVolume := collections.P2(mountWorkspaceVolume, generateWorkspaceVolume(workspaceID)) - // we need to make sure we map in everything in meta - mapMeta := collections.P2(mapValuesThroughMeta, generateMetaValues(workspace)) - // we need to expose the ports correctly so it can speak with the outside world (and the other way as well) - // think about how this can be running more than one workspace (clever port-mapping?) - portMapping := generatePortMapping(workspace, previouslyRunWorkspaces) - mapPorts := collections.P2(exposePorts, portMapping) - // as long as each takes what the other provides, this function chain will work (evaluated right-to-left) - preparedImage := collections.C5(mapPorts, mapMeta, mountWSVolume, mountParams, mountToken)(baseImage) // right-to-left application - // we need to execute a docker run with the workspace volume pointed to the correct space - // (this might need to be run in privileged mode) - return dockerExecute(workspaceID, preparedImage) - - // ASIDES BUT IMPORTANT - // need to build a secret manager config manager - // right now there is a config file which gets put into the workspace - // inside the workspace vault agent updates when the file changes - // the way we change that file with a kubernetes thing -- but we will need to build that magic for this - - // maybe later we need to configure a health check to reboot if it doesn't path (but for now probably ok) - - // see simulate-workspace in the Makefile for some inspiration - // e2e test folder setup_test.go to see docker exec being run -} - -func (w WorkspaceManager) Stop(workspaceID string) error { - runningWorkspaces := fetchRunningWorkspaces() - matchingWorkspaces := collections.Filter(runningWorkspaces, collections.P2(nameMatches, workspaceID)) - if len(matchingWorkspaces) > 0 { - workspaceToStop := collections.First(collections.SortBy(workspacePriorityFunc, matchingWorkspaces)) - return stopLocalWorkspace(*workspaceToStop) - } - return errors.New(strings.Join([]string{"No entity.WorkspaceID by name", workspaceID, "currently running."}, " ")) -} - -func (w WorkspaceManager) Reset(workspaceID string) error { - err := w.Stop(workspaceID) - if err != nil { - return err // do we want this to return if it can't find one to stop, - // or should it just start one if it can find one to start? - } - return w.Start(workspaceID) -} - -type DockerContainer struct { - CommandArgs []string - Name string - VolumeMap map[string]string - ShellPreference string `default:"zsh"` - PortMap map[string]string - BaseImage string `default:"brevdev/ubuntu-proxy:0.3.7"` - Privileged bool // `default:true` -} - -type LocalWorkspace struct { - Name string -} - -func workspacePriorityFunc(left LocalWorkspace, right LocalWorkspace) bool { - // NOT YET IMPLEMENTED - // this should tell us what counts are 'more' or 'less'. example options are - // doing the most recently run, or the most recently created, or the most recently added... - return false -} - -func fetchPreviouslyRunWorkspaces() []LocalWorkspace { - // NOT YET IMPLEMENTED - return []LocalWorkspace{} -} - -func fetchRunningWorkspaces() []LocalWorkspace { - // NOT YET IMPLEMENTED - // take output of docker ps and use it somehow? - return []LocalWorkspace{} -} - -func nameMatches(name string, workspace LocalWorkspace) bool { - // NOT YET IMPLEMENTED - return false -} - -func startLocalWorkspace(workspace LocalWorkspace) error { - // NOT YET IMPLEMENTED - return errors.New("Start entity.Workspace Not Yet Implemented") -} - -func stopLocalWorkspace(workspace LocalWorkspace) error { - // NOT YET IMPLEMENTED - return errors.New("Stop entity.Workspace Not Yet Implemented") -} - -func resolveWorkspaceID(workspaceID string) entity.Workspace { - // NOT YET IMPLEMENTED - return entity.Workspace{} -} - -func workspaceToSetupParams(workspace entity.Workspace) store.SetupParamsV0 { - // NOT YET IMPLEMENTED - return store.SetupParamsV0{} -} - -func fetchBaseImage() DockerContainer { - // NOT YET IMPLEMENTED - return DockerContainer{} -} - -func fetchKubernetesServiceTokenDir() string { - // NOT YET IMPLEMENTED - return "/var/run/secrets/kubernetes.io/serviceaccount/" // not yet implemented -} - -func mixInKubernetesToken(tokenDirectory string, container DockerContainer) DockerContainer { - // NOT YET IMPLEMENTED - return DockerContainer{} -} - -func mountSetupParams(setupParams store.SetupParamsV0, container DockerContainer) DockerContainer { - // NOT YET IMPLEMENTED - return DockerContainer{} -} - -func generateWorkspaceVolume(workspaceID string) string { - // NOT YET IMPLEMENTED - return workspaceID -} - -func mountWorkspaceVolume(path string, container DockerContainer) DockerContainer { - // NOT YET IMPLEMENTED - return DockerContainer{} -} - -func generateMetaValues(workspace entity.Workspace) map[string]string { - // NOT YET IMPLEMENTED - return map[string]string{} -} - -func mapValuesThroughMeta(valuesMap map[string]string, container DockerContainer) DockerContainer { - // NOT YET IMPLEMENTED - return container -} - -func generatePortMapping(workspace entity.Workspace, previouslyRunWorkspaces []LocalWorkspace) map[string]string { - // NOT YET IMPLEMENTED - return map[string]string{} -} - -func exposePorts(portMap map[string]string, container DockerContainer) DockerContainer { - // NOT YET IMPLEMENTED - return DockerContainer{} -} - -func portMapToString(portMap map[string]string) string { - return strings.Join(collections.Fmap(func(key string) string { - val := portMap[key] - return strings.Join([]string{"-p", strings.Join([]string{key, val}, ":")}, " ") - }, collections.Keys(portMap)), " ") -} - -func volumeMapToString(volumeMap map[string]string) string { - return strings.Join(collections.Fmap(func(key string) string { - val := volumeMap[key] - return strings.Join([]string{"-v", strings.Join([]string{key, val}, ":")}, " ") - }, collections.Keys(volumeMap)), " ") -} - -func containerToString(workspaceID string, container DockerContainer) string { - privilegeString := "" - if container.Privileged { - privilegeString = "--privileged=true" - } - return strings.Join([]string{ - "docker run", "-d", // detached - privilegeString, // name - "--name", workspaceID, - "-it", // i attaches to stdin, t to terminal - portMapToString(container.PortMap), - volumeMapToString(container.VolumeMap), - container.ShellPreference, - }, " ") -} - -func dockerExecute(workspaceID string, container DockerContainer) error { - // NOT YET IMPLEMENTED - command := containerToString(workspaceID, container) - fmt.Println("final command is ") - fmt.Println(command) - parts := strings.Split(command, " ") - _, err := exec.Command(parts[0], parts[1:]...).Output() //nolint:gosec // fine - return breverrors.WrapAndTrace(err) - // return errors.New("Docker Execute Not Yet Implemented") -} diff --git a/pkg/workspacemanager/workspacemanager_test.go b/pkg/workspacemanager/workspacemanager_test.go deleted file mode 100644 index 631cbf74..00000000 --- a/pkg/workspacemanager/workspacemanager_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package workspacemanager - -import ( - "testing" -) - -func Test_NewWorkspaceManager(t *testing.T) { - NewWorkspaceManager() - // assert.NotNil(t, wm) -} - -func Test_StartWorkspaceManager(t *testing.T) { - wm := NewWorkspaceManager() - _ = wm.Start("") - // assert.Nil(t, _) -} - -func Test_StopWorkspaceManager(t *testing.T) { - wm := NewWorkspaceManager() - _ = wm.Stop("") - // assert.Nil(t, _) -} - -func Test_ResetWorkspaceManager(t *testing.T) { - wm := NewWorkspaceManager() - _ = wm.Reset("") - // assert.Nil(t, _) -} diff --git a/pkg/workspacemanagerv2/containermanager.go b/pkg/workspacemanagerv2/containermanager.go deleted file mode 100644 index 5a563aaf..00000000 --- a/pkg/workspacemanagerv2/containermanager.go +++ /dev/null @@ -1,335 +0,0 @@ -package workspacemanagerv2 - -import ( - "context" - "encoding/json" - "fmt" - "os/exec" - "strings" - - breverrors "github.com/brevdev/brev-cli/pkg/errors" -) - -type DockerContainerManager struct{} - -var _ ContainerManager = DockerContainerManager{} - -type inspectResult struct { - ID string `json:"Id"` - State state `json:"State"` -} - -type state struct { - Status string `json:"Status"` -} - -type inspectResults []inspectResult - -func (c DockerContainerManager) GetContainer(ctx context.Context, containerIdentifier string) (*Container, error) { - cmd := exec.CommandContext(ctx, "docker", "container", "inspect", containerIdentifier) - out, err := cmd.CombinedOutput() - if err != nil { - return nil, breverrors.WrapAndTrace(fmt.Errorf(string(out))) - } - - res := inspectResults{} - err = json.Unmarshal(out, &res) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - if len(res) == 0 { - return nil, fmt.Errorf("no results") - } - return &Container{ - ID: res[0].ID, - Status: DockerStatusToContainerStatus(res[0].State.Status), - }, nil -} - -func DockerStatusToContainerStatus(status string) ContainerStatus { - if status == "created" || status == "exited" { - return ContainerStopped - } - return ContainerStatus(status) -} - -func (c DockerContainerManager) StopContainer(ctx context.Context, containerIdentifier string) error { - cmd := exec.CommandContext(ctx, "docker", "container", "stop", containerIdentifier) - out, err := cmd.CombinedOutput() - if err != nil { - return breverrors.WrapAndTrace(fmt.Errorf(string(out))) - } - return nil -} - -func (c DockerContainerManager) DeleteContainer(ctx context.Context, containerIdentifier string) error { - cmd := exec.CommandContext(ctx, "docker", "container", "rm", containerIdentifier) - out, err := cmd.CombinedOutput() - if err != nil { - return breverrors.WrapAndTrace(fmt.Errorf(string(out))) - } - return nil -} - -func (c DockerContainerManager) StartContainer(ctx context.Context, containerIdentifier string) error { - cmd := exec.CommandContext(ctx, "docker", "container", "start", containerIdentifier) - out, err := cmd.CombinedOutput() - if err != nil { - return breverrors.WrapAndTrace(fmt.Errorf(string(out))) - } - return nil -} - -func (c DockerContainerManager) DeleteVolume(ctx context.Context, volumeName string) error { - cmd := exec.CommandContext(ctx, "docker", "volume", "rm", volumeName) - out, err := cmd.CombinedOutput() - if err != nil { - return breverrors.WrapAndTrace(fmt.Errorf(string(out))) - } - return nil -} - -func (c DockerContainerManager) CreateContainer(ctx context.Context, options CreateContainerOptions, image string) (string, error) { - // TODO use official docker client - volumes := []string{} - for _, v := range options.Volumes { - volumes = append(volumes, "--volume", fmt.Sprintf("%s:%s", v.GetIdentifier(), v.GetMountToPath())) - } - ports := []string{} - for _, p := range options.Ports { - ports = append(ports, "--publish", p) - } - portsAndVolumes := append(ports, volumes...) //nolint:gocritic // not clear why the linter doesn't like this pattern - createArgs := append([]string{"--name", options.Name, "--privileged"}, portsAndVolumes...) - command := []string{} - if options.Command != "" { - command = []string{options.Command} - } - allCommand := append(command, options.CommandArgs...) //nolint:gocritic // not clear why the linter doesn't like this pattern - postOptionArgs := append([]string{image}, allCommand...) - dockerArgs := append([]string{"container", "create"}, append(createArgs, postOptionArgs...)...) - cmd := exec.CommandContext(ctx, "docker", dockerArgs...) //nolint:gosec // in sandboxed env - out, err := cmd.CombinedOutput() - if err != nil { - return "", breverrors.WrapAndTrace(fmt.Errorf(string(out))) - } - res := strings.Fields(string(out)) - if len(res) == 0 { - return "", fmt.Errorf("invalid docker create result:\n%s", string(out)) - } - return res[len(res)-1], nil -} - -// [ -// { -// "Id": "149a3bed2b0d0595df159e4eb032fdc62f3f3f508a59bacf86cfc9e131f98910", -// "Created": "2022-05-14T00:33:39.375942173Z", -// "Path": "/docker-entrypoint.sh", -// "Args": [ -// "nginx", -// "-g", -// "daemon off;" -// ], -// "State": { -// "Status": "exited", -// "Running": false, -// "Paused": false, -// "Restarting": false, -// "OOMKilled": false, -// "Dead": false, -// "Pid": 0, -// "ExitCode": 0, -// "Error": "", -// "StartedAt": "2022-05-14T00:33:40.468926916Z", -// "FinishedAt": "2022-05-14T00:33:42.19311363Z" -// }, -// "Image": "sha256:7425d3a7c478efbeb75f0937060117343a9a510f72f5f7ad9f14b1501a36940c", -// "ResolvConfPath": "/var/lib/docker/containers/149a3bed2b0d0595df159e4eb032fdc62f3f3f508a59bacf86cfc9e131f98910/resolv.conf", -// "HostnamePath": "/var/lib/docker/containers/149a3bed2b0d0595df159e4eb032fdc62f3f3f508a59bacf86cfc9e131f98910/hostname", -// "HostsPath": "/var/lib/docker/containers/149a3bed2b0d0595df159e4eb032fdc62f3f3f508a59bacf86cfc9e131f98910/hosts", -// "LogPath": "/var/lib/docker/containers/149a3bed2b0d0595df159e4eb032fdc62f3f3f508a59bacf86cfc9e131f98910/149a3bed2b0d0595df159e4eb032fdc62f3f3f508a59bacf86cfc9e131f98910-json.log", -// "Name": "/ecstatic_lichterman", -// "RestartCount": 0, -// "Driver": "overlay2", -// "Platform": "linux", -// "MountLabel": "", -// "ProcessLabel": "", -// "AppArmorProfile": "", -// "ExecIDs": null, -// "HostConfig": { -// "Binds": null, -// "ContainerIDFile": "", -// "LogConfig": { -// "Type": "json-file", -// "Config": {} -// }, -// "NetworkMode": "default", -// "PortBindings": {}, -// "RestartPolicy": { -// "Name": "no", -// "MaximumRetryCount": 0 -// }, -// "AutoRemove": false, -// "VolumeDriver": "", -// "VolumesFrom": null, -// "CapAdd": null, -// "CapDrop": null, -// "CgroupnsMode": "host", -// "Dns": [], -// "DnsOptions": [], -// "DnsSearch": [], -// "ExtraHosts": null, -// "GroupAdd": null, -// "IpcMode": "private", -// "Cgroup": "", -// "Links": null, -// "OomScoreAdj": 0, -// "PidMode": "", -// "Privileged": false, -// "PublishAllPorts": false, -// "ReadonlyRootfs": false, -// "SecurityOpt": null, -// "UTSMode": "", -// "UsernsMode": "", -// "ShmSize": 67108864, -// "Runtime": "runc", -// "ConsoleSize": [ -// 0, -// 0 -// ], -// "Isolation": "", -// "CpuShares": 0, -// "Memory": 0, -// "NanoCpus": 0, -// "CgroupParent": "", -// "BlkioWeight": 0, -// "BlkioWeightDevice": [], -// "BlkioDeviceReadBps": null, -// "BlkioDeviceWriteBps": null, -// "BlkioDeviceReadIOps": null, -// "BlkioDeviceWriteIOps": null, -// "CpuPeriod": 0, -// "CpuQuota": 0, -// "CpuRealtimePeriod": 0, -// "CpuRealtimeRuntime": 0, -// "CpusetCpus": "", -// "CpusetMems": "", -// "Devices": [], -// "DeviceCgroupRules": null, -// "DeviceRequests": null, -// "KernelMemory": 0, -// "KernelMemoryTCP": 0, -// "MemoryReservation": 0, -// "MemorySwap": 0, -// "MemorySwappiness": null, -// "OomKillDisable": false, -// "PidsLimit": null, -// "Ulimits": null, -// "CpuCount": 0, -// "CpuPercent": 0, -// "IOMaximumIOps": 0, -// "IOMaximumBandwidth": 0, -// "MaskedPaths": [ -// "/proc/asound", -// "/proc/acpi", -// "/proc/kcore", -// "/proc/keys", -// "/proc/latency_stats", -// "/proc/timer_list", -// "/proc/timer_stats", -// "/proc/sched_debug", -// "/proc/scsi", -// "/sys/firmware" -// ], -// "ReadonlyPaths": [ -// "/proc/bus", -// "/proc/fs", -// "/proc/irq", -// "/proc/sys", -// "/proc/sysrq-trigger" -// ] -// }, -// "GraphDriver": { -// "Data": { -// "LowerDir": "/var/lib/docker/overlay2/30f56bebc7a574013a60253618494c668f918f929098d60e476f6d1c07fb8f03-init/diff:/var/lib/docker/overlay2/fa7b0f78f551c8f7a4319a58cc5c98ece716afcbc496724caa399191d4222733/diff:/var/lib/docker/overlay2/773f813086e1c74c4dcad7970b449ae57372885528aa66beb94a28cda63cac71/diff:/var/lib/docker/overlay2/dc66da211f30d5cb69975ec8ffac758637b4c6a5f114d6fb876f2ac308f518ee/diff:/var/lib/docker/overlay2/4baccdbaebab6fce8fa33b1fb821f77c27967b2ed575b4aeee6ac545fb987189/diff:/var/lib/docker/overlay2/47f4b9cb4ee4ede5bf0cca6f40a84203288e0d14fa5cdfdf518347fe5f02a5f9/diff:/var/lib/docker/overlay2/aaea9506d207f44e3ab00538eb1f32a74c4df7b4d29d96f96611326840671597/diff", -// "MergedDir": "/var/lib/docker/overlay2/30f56bebc7a574013a60253618494c668f918f929098d60e476f6d1c07fb8f03/merged", -// "UpperDir": "/var/lib/docker/overlay2/30f56bebc7a574013a60253618494c668f918f929098d60e476f6d1c07fb8f03/diff", -// "WorkDir": "/var/lib/docker/overlay2/30f56bebc7a574013a60253618494c668f918f929098d60e476f6d1c07fb8f03/work" -// }, -// "Name": "overlay2" -// }, -// "Mounts": [], -// "Config": { -// "Hostname": "149a3bed2b0d", -// "Domainname": "", -// "User": "", -// "AttachStdin": false, -// "AttachStdout": true, -// "AttachStderr": true, -// "ExposedPorts": { -// "80/tcp": {} -// }, -// "Tty": false, -// "OpenStdin": false, -// "StdinOnce": false, -// "Env": [ -// "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", -// "NGINX_VERSION=1.21.6", -// "NJS_VERSION=0.7.2", -// "PKG_RELEASE=1~bullseye" -// ], -// "Cmd": [ -// "nginx", -// "-g", -// "daemon off;" -// ], -// "Image": "nginx", -// "Volumes": null, -// "WorkingDir": "", -// "Entrypoint": [ -// "/docker-entrypoint.sh" -// ], -// "OnBuild": null, -// "Labels": { -// "maintainer": "NGINX Docker Maintainers " -// }, -// "StopSignal": "SIGQUIT" -// }, -// "NetworkSettings": { -// "Bridge": "", -// "SandboxID": "e3e9ef1e5e64793e242e6f7e25dc82f0c66fd182ef16fe03e9bcdd0f3f28123c", -// "HairpinMode": false, -// "LinkLocalIPv6Address": "", -// "LinkLocalIPv6PrefixLen": 0, -// "Ports": {}, -// "SandboxKey": "/var/run/docker/netns/e3e9ef1e5e64", -// "SecondaryIPAddresses": null, -// "SecondaryIPv6Addresses": null, -// "EndpointID": "", -// "Gateway": "", -// "GlobalIPv6Address": "", -// "GlobalIPv6PrefixLen": 0, -// "IPAddress": "", -// "IPPrefixLen": 0, -// "IPv6Gateway": "", -// "MacAddress": "", -// "Networks": { -// "bridge": { -// "IPAMConfig": null, -// "Links": null, -// "Aliases": null, -// "NetworkID": "4110f5ecbb596b3368fc6c02dc61d50933f5296f124e1f1cc17c287b87baf43d", -// "EndpointID": "", -// "Gateway": "", -// "IPAddress": "", -// "IPPrefixLen": 0, -// "IPv6Gateway": "", -// "GlobalIPv6Address": "", -// "GlobalIPv6PrefixLen": 0, -// "MacAddress": "", -// "DriverOpts": null -// } -// } -// } -// } -// ] diff --git a/pkg/workspacemanagerv2/containermanager_test.go b/pkg/workspacemanagerv2/containermanager_test.go deleted file mode 100644 index 01363154..00000000 --- a/pkg/workspacemanagerv2/containermanager_test.go +++ /dev/null @@ -1,141 +0,0 @@ -package workspacemanagerv2 - -import ( - "context" - "fmt" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "testing" - "time" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" -) - -func GetAllContainerManagers() []ContainerManager { - return []ContainerManager{DockerContainerManager{}} -} - -// TIP: use docker inspect to get information about container like volume mounted, command, ports etc. - -func Test_GetContainerDNE(t *testing.T) { - dcms := GetAllContainerManagers() - for _, cm := range dcms { - res, err := cm.GetContainer(context.TODO(), "dne") - assert.Error(t, err) - assert.Empty(t, res) - } -} - -func Test_CreateThenGetContainer(t *testing.T) { - dcms := GetAllContainerManagers() - for _, cm := range dcms { - ctx := context.Background() - containerID, err := cm.CreateContainer(ctx, CreateContainerOptions{}, "hello-world") - if !assert.Nil(t, err) { - return - } - res, err := cm.GetContainer(ctx, containerID) - assert.Nil(t, err) - assert.Equal(t, containerID, res.ID) - } -} - -func Test_CreateThenStartThenStopContainer(t *testing.T) { - dcms := GetAllContainerManagers() - for _, cm := range dcms { - ctx := context.Background() - containerID, err := cm.CreateContainer(ctx, CreateContainerOptions{}, "nginx") - if !assert.Nil(t, err) { - return - } - gr1, err := cm.GetContainer(ctx, containerID) - assert.Nil(t, err) - assert.Equal(t, ContainerStopped, gr1.Status) - - err = cm.StartContainer(ctx, containerID) - assert.Nil(t, err) - - gr2, err := cm.GetContainer(ctx, containerID) - assert.Nil(t, err) - assert.Equal(t, ContainerRunning, gr2.Status) - - err = cm.StopContainer(ctx, containerID) - assert.Nil(t, err) - - gr3, err := cm.GetContainer(ctx, containerID) - assert.Nil(t, err) - assert.Equal(t, ContainerStopped, gr3.Status) - - err = cm.DeleteContainer(ctx, containerID) - assert.Nil(t, err) - - gr4, err := cm.GetContainer(ctx, containerID) - assert.Error(t, err) - assert.Empty(t, gr4) - } -} - -func Test_PortMapping(t *testing.T) { - dcms := GetAllContainerManagers() - for _, cm := range dcms { - ctx := context.Background() - containerID, err := cm.CreateContainer(ctx, CreateContainerOptions{ - Ports: []string{"3456:80"}, - }, "nginx") - if !assert.Nil(t, err) { - return - } - - err = cm.StartContainer(ctx, containerID) - assert.Nil(t, err) - - time.Sleep(time.Second * 1) - cmd := exec.CommandContext(ctx, "curl", "http://localhost:3456") - out, err := cmd.CombinedOutput() - fmt.Println(string(out)) - assert.Nil(t, err) - - err = cm.StopContainer(ctx, containerID) - assert.Nil(t, err) - } -} - -func Test_Volumes(t *testing.T) { - t.Skip() - dcms := GetAllContainerManagers() - for _, cm := range dcms { - ctx := context.Background() - localPath := fmt.Sprintf("/tmp/brevcli-test-volume/%s", uuid.New().String()) - fmt.Println(localPath) - - err := os.MkdirAll(localPath, os.ModePerm) - assert.Nil(t, err) - - _, err = os.OpenFile(filepath.Join(localPath, "original"), os.O_CREATE, 0o600) //nolint:gosec // test - assert.Nil(t, err) - - containerID, err := cm.CreateContainer(ctx, CreateContainerOptions{ - Volumes: []Volume{ - SimpleVolume{ - Identifier: localPath, - MountToPath: "/volume", - }, - }, - Command: "cp", - CommandArgs: []string{"/volume/original", "/volume/new"}, - }, "nginx") - if !assert.Nil(t, err) { - return - } - - err = cm.StartContainer(ctx, containerID) - assert.Nil(t, err) - - info, err := ioutil.ReadDir(localPath) - assert.Nil(t, err) - assert.Len(t, info, 2) - } -} diff --git a/pkg/workspacemanagerv2/volumes.go b/pkg/workspacemanagerv2/volumes.go deleted file mode 100644 index 14ce1294..00000000 --- a/pkg/workspacemanagerv2/volumes.go +++ /dev/null @@ -1,114 +0,0 @@ -package workspacemanagerv2 - -import ( - "context" - "io" - "os" - "path/filepath" - - breverrors "github.com/brevdev/brev-cli/pkg/errors" -) - -type StaticFiles struct { - FromMountPathPrefix string - ToMountPath string - FileMap map[string]io.Reader -} - -var _ Volume = StaticFiles{} - -func NewStaticFiles(path string, fileMap map[string]io.Reader) StaticFiles { - return StaticFiles{ToMountPath: path, FileMap: fileMap} -} - -func (s StaticFiles) WithPathPrefix(prefix string) StaticFiles { - s.FromMountPathPrefix = prefix - return s -} - -func (s StaticFiles) GetIdentifier() string { - return s.GetMountFromPath() -} - -func (s StaticFiles) GetMountToPath() string { - return s.ToMountPath -} - -func (s StaticFiles) GetMountFromPath() string { - return s.FromMountPathPrefix -} - -func (s StaticFiles) Setup(_ context.Context) error { - path := s.GetMountFromPath() - err := os.MkdirAll(path, os.ModePerm) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - for k, v := range s.FileMap { - filePath := filepath.Join(s.FromMountPathPrefix, k) - f, err := os.Create(filePath) //nolint:gosec // executed in safe space - if err != nil { - return breverrors.WrapAndTrace(err) - } - defer f.Close() //nolint:errcheck // defer - - _, err = io.Copy(f, v) - if err != nil { - return breverrors.WrapAndTrace(err) - } - err = f.Sync() - if err != nil { - return breverrors.WrapAndTrace(err) - } - } - - return nil -} - -func (s StaticFiles) Teardown(_ context.Context) error { - err := os.RemoveAll(s.FromMountPathPrefix) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -type SimpleVolume struct { - Identifier string - MountToPath string -} - -var _ Volume = SimpleVolume{} - -func (s SimpleVolume) GetIdentifier() string { - return s.Identifier -} - -func (s SimpleVolume) GetMountToPath() string { - return s.MountToPath -} - -func (s SimpleVolume) Setup(_ context.Context) error { - return nil -} - -func (s SimpleVolume) Teardown(_ context.Context) error { - return nil -} - -type SymLinkVolume struct { - FromSymLinkPath string - LocalVolumePath string - MountToPath string -} - -// Deprecated: var _ Volume = SymLinkVolume{} - -type DynamicVolume struct { - FromMountPathPrefix string - ToMountPath string - FileMap map[string]func(string) -} - -// Deprecated: var _ Volume = DynamicVolume{} diff --git a/pkg/workspacemanagerv2/volumes_test.go b/pkg/workspacemanagerv2/volumes_test.go deleted file mode 100644 index 98bdd918..00000000 --- a/pkg/workspacemanagerv2/volumes_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package workspacemanagerv2 - -// func Test_SymlinkVolume(t *testing.T) { -// err := os.MkdirAll("/tmp/test-symlink-volume/original", os.ModePerm) -// assert.Nil(t, err) -// _, err = os.OpenFile("/tmp/test-symlink-volume/original/file", os.O_CREATE, 0o600) -// assert.Nil(t, err) - -// res := NewSymLinkVolume("/tmp/test-symlink-volume/original", "/tmp/test-symlink-volume/vol", "path") -// err = res.Setup(context.TODO()) -// assert.Nil(t, err) -// } diff --git a/pkg/workspacemanagerv2/workspacemanagerv2.go b/pkg/workspacemanagerv2/workspacemanagerv2.go deleted file mode 100644 index e118951f..00000000 --- a/pkg/workspacemanagerv2/workspacemanagerv2.go +++ /dev/null @@ -1,311 +0,0 @@ -package workspacemanagerv2 - -import ( - "bytes" - "context" - "encoding/json" - "io" - "path/filepath" - "strings" - - "github.com/brevdev/brev-cli/pkg/entity" - breverrors "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/brev-cli/pkg/store" -) - -// Step one design for cloud -// Assume k8s secret exists -// Copy current workspace style - -type WorkspaceManager struct { - ContainerManager ContainerManager - Store WorkspaceManagerStore -} - -type ContainerStatus string - -const ( - ContainerRunning ContainerStatus = "running" - ContainerStopped ContainerStatus = "stopped" -) - -type Container struct { - ID string - Status ContainerStatus -} - -type CreateContainerOptions struct { - Name string - Volumes []Volume - Ports []string - - Command string - CommandArgs []string -} - -// ContainerManager Interface for docker, podman etc. -type ContainerManager interface { - GetContainer(ctx context.Context, containerID string) (*Container, error) - StopContainer(ctx context.Context, containerID string) error - DeleteContainer(ctx context.Context, containerID string) error - CreateContainer(ctx context.Context, options CreateContainerOptions, image string) (string, error) - StartContainer(ctx context.Context, containerID string) error - DeleteVolume(ctx context.Context, volumeName string) error -} - -type WorkspaceManagerStore interface { - GetWorkspace(id string) (*entity.Workspace, error) - GetWorkspaceMeta(id string) (*store.WorkspaceMeta, error) - GetWorkspaceSetupParams(id string) (*store.SetupParamsV0, error) - GetWorkspaceSecretsConfig(id string) (string, error) -} - -func NewWorkspaceManager(cm ContainerManager, store WorkspaceManagerStore) *WorkspaceManager { - return &WorkspaceManager{ContainerManager: cm, Store: store} -} - -func (w WorkspaceManager) MakeContainerWorkspace(workspaceID string) (*ContainerWorkspace, error) { - workspace, err := w.Store.GetWorkspace(workspaceID) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - setupParams, err := w.Store.GetWorkspaceSetupParams(workspaceID) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - paramsData, err := json.MarshalIndent(setupParams, "", " ") - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - workspaceMeta, err := w.Store.GetWorkspaceMeta(workspaceID) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - workspaceData, err := json.MarshalIndent(workspaceMeta, "", " ") - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - secretsConfig, err := w.Store.GetWorkspaceSecretsConfig(workspaceID) - if err != nil { - return nil, breverrors.WrapAndTrace(err) - } - - basePath := "/tmp/brev/volumes" // TODO proper path that will be saved - workspaceVolumesPath := filepath.Join(basePath, workspace.ID) - - localMeta := filepath.Join(workspaceVolumesPath, "etc/meta") - metaVolumes := NewStaticFiles("/etc/meta", map[string]io.Reader{ - "setup_v0.json": bytes.NewBuffer(paramsData), - "workspace.json": bytes.NewBuffer(workspaceData), - }). - WithPathPrefix(localMeta) - - secretsLocalConfig := filepath.Join(workspaceVolumesPath, "etc/config") - secretsConfigVolumes := NewStaticFiles("/etc/config", map[string]io.Reader{ - "config.hcl": bytes.NewBuffer([]byte(secretsConfig)), - }). - WithPathPrefix(secretsLocalConfig) - - workspaceVolLocalPath := filepath.Join(workspaceVolumesPath, "home/brev/workspace") - workspaceVol := SimpleVolume{ - Identifier: workspaceVolLocalPath, - MountToPath: "/home/brev/workspace", - } - - k8sTokenVol := SimpleVolume{ - Identifier: "/var/run/secrets", - MountToPath: "/var/run/secrets", - } - - fuseVol := SimpleVolume{ - Identifier: "/dev/fuse", - MountToPath: "/dev/fuse", - } - - containerWorkspace := NewContainerWorkspace(w.ContainerManager, workspaceID, workspace.WorkspaceTemplate.Image, - []Volume{ - metaVolumes, - secretsConfigVolumes, - workspaceVol, - k8sTokenVol, - fuseVol, - }, - ) - - return containerWorkspace, nil -} - -func (w WorkspaceManager) Start(ctx context.Context, workspaceID string) error { - containerWorkspace, err := w.MakeContainerWorkspace(workspaceID) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - err = containerWorkspace.Start(ctx) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -func (w WorkspaceManager) Reset(ctx context.Context, workspaceID string) error { - containerWorkspace, err := w.MakeContainerWorkspace(workspaceID) - if err != nil { - return breverrors.WrapAndTrace(err) - } - err = containerWorkspace.Reset(ctx) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -func (w WorkspaceManager) Stop(ctx context.Context, workspaceID string) error { - containerWorkspace, err := w.MakeContainerWorkspace(workspaceID) - if err != nil { - return breverrors.WrapAndTrace(err) - } - err = containerWorkspace.Stop(ctx) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -type ContainerWorkspace struct { - ContainerManager ContainerManager - Identifier string - Image string - Volumes []Volume -} - -func NewContainerWorkspace(cm ContainerManager, identifier string, image string, volumes []Volume) *ContainerWorkspace { - return &ContainerWorkspace{ContainerManager: cm, Identifier: identifier, Image: image, Volumes: volumes} -} - -func (c ContainerWorkspace) Start(ctx context.Context) error { - container, err := c.ContainerManager.GetContainer(ctx, c.Identifier) - if err != nil && !strings.Contains(err.Error(), "No such container") { - return breverrors.WrapAndTrace(err) - } - if container == nil { //nolint:gocritic // I like the else statement here - err := c.CreateNew(ctx) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } else if container.Status == ContainerStopped { - err := c.StartFromStopped(ctx) - if err != nil { - return breverrors.WrapAndTrace(err) - } - } else if container.Status == ContainerRunning { - _ = 0 - // do nothing - } - return nil -} - -func (c ContainerWorkspace) CreateNew(ctx context.Context) error { - err := c.CreateVolumes(ctx) - if err != nil { - return breverrors.WrapAndTrace(err) - } - containerID, err := c.ContainerManager.CreateContainer(ctx, CreateContainerOptions{ - Name: c.Identifier, - Volumes: c.Volumes, - }, c.Image) - if err != nil { - return breverrors.WrapAndTrace(err) - } - err = c.ContainerManager.StartContainer(ctx, containerID) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -func (c ContainerWorkspace) StartFromStopped(ctx context.Context) error { - // update necessary volumes // on second thoguht maybe not (not current behavior) - // // params? - // start - err := c.ContainerManager.StartContainer(ctx, c.Identifier) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -func (c ContainerWorkspace) Reset(ctx context.Context) error { - err := c.Stop(ctx) - if err != nil { - return breverrors.WrapAndTrace(err) - } - err = c.Start(ctx) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -func (c ContainerWorkspace) Rebuild(ctx context.Context) error { - err := c.Stop(ctx) - if err != nil { - return breverrors.WrapAndTrace(err) - } - err = c.ContainerManager.DeleteContainer(ctx, c.Identifier) - if err != nil { - return breverrors.WrapAndTrace(err) - } - err = c.DeleteVolumes(ctx) - if err != nil { - return breverrors.WrapAndTrace(err) - } - err = c.Start(ctx) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -func (c ContainerWorkspace) CreateVolumes(ctx context.Context) error { - // two kinds of updates depending on env - // // start volume processes (to support dynamic volumes like k8s token, hcl config, etc) - // // could be static init, sym link, callback, or poll base - for _, v := range c.Volumes { - err := v.Setup(ctx) // should this be a goroutine? - if err != nil { - return breverrors.WrapAndTrace(err) - } - } - return nil -} - -func (c ContainerWorkspace) DeleteVolumes(ctx context.Context) error { - for _, v := range c.Volumes { - err := v.Teardown(ctx) // should this be a goroutine? - if err != nil { - return breverrors.WrapAndTrace(err) - } - } - return nil -} - -func (c ContainerWorkspace) Stop(ctx context.Context) error { - err := c.ContainerManager.StopContainer(ctx, c.Identifier) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil -} - -type Volume interface { - GetIdentifier() string // this may be a name or path to external mount - GetMountToPath() string - Setup(ctx context.Context) error - Teardown(ctx context.Context) error // should also clear/delete -} diff --git a/pkg/workspacemanagerv2/workspacemanagerv2_test.go b/pkg/workspacemanagerv2/workspacemanagerv2_test.go deleted file mode 100644 index dcc4a175..00000000 --- a/pkg/workspacemanagerv2/workspacemanagerv2_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package workspacemanagerv2 - -import ( - "context" - "io" - "strings" - "testing" - - "github.com/brevdev/brev-cli/pkg/entity" - "github.com/brevdev/brev-cli/pkg/store" - "github.com/stretchr/testify/assert" -) - -type TestStore struct{} - -var TestImage = "brevdev/ubuntu-proxy:0.3.17" - -func (t TestStore) GetWorkspace(id string) (*entity.Workspace, error) { - return &entity.Workspace{ - ID: id, - WorkspaceTemplate: entity.WorkspaceTemplate{ - Image: TestImage, - }, - }, nil -} - -func (t TestStore) GetWorkspaceSetupParams(_ string) (*store.SetupParamsV0, error) { - return &store.SetupParamsV0{ - WorkspaceHost: "", - WorkspacePort: 0, - WorkspaceBaseRepo: "", - WorkspaceProjectRepo: "", - WorkspaceProjectRepoBranch: "", - WorkspaceApplicationStartScripts: []string{}, - WorkspaceUsername: "", - WorkspaceEmail: "", - WorkspacePassword: "", - WorkspaceKeyPair: &store.KeyPair{ - PublicKeyData: "", - PrivateKeyData: "", - }, - ProjectSetupScript: new(string), - ProjectFolderName: "", - ProjectBrevPath: "", - VerbYaml: "", - DisableSetup: false, - }, nil -} - -func (t TestStore) GetWorkspaceSecretsConfig(_ string) (string, error) { - return "my config", nil -} - -func (t TestStore) GetWorkspaceMeta(id string) (*store.WorkspaceMeta, error) { - return &store.WorkspaceMeta{ - WorkspaceID: id, - WorkspaceGroupID: "", - UserID: "", - OrganizationID: "", - }, nil -} - -func Test_NewWorkspaceManager(t *testing.T) { - cm := DockerContainerManager{} - store := TestStore{} - wm := NewWorkspaceManager(cm, store) - assert.NotNil(t, wm) -} - -func Test_StartWorkspaceManager(t *testing.T) { - t.Skip() - ctx := context.Background() - cm := DockerContainerManager{} - store := TestStore{} - wm := NewWorkspaceManager(cm, store) - err := wm.Start(ctx, "test") - assert.Nil(t, err) -} - -func Test_StopWorkspaceManager(t *testing.T) { - t.Skip() - ctx := context.Background() - cm := DockerContainerManager{} - store := TestStore{} - wm := NewWorkspaceManager(cm, store) - err := wm.Stop(ctx, "test") - assert.Nil(t, err) -} - -func Test_ResetWorkspaceManager(t *testing.T) { - t.Skip() - ctx := context.Background() - cm := DockerContainerManager{} - store := TestStore{} - wm := NewWorkspaceManager(cm, store) - err := wm.Reset(ctx, "test") - assert.Nil(t, err) -} - -func TestCreateWorkspace(t *testing.T) { - v := NewStaticFiles("path", map[string]io.Reader{"doom": strings.NewReader("boom")}) - v = v.WithPathPrefix("prefix") - assert.Equal(t, "prefix", v.FromMountPathPrefix) -} diff --git a/scripts/.gitignore b/scripts/.gitignore new file mode 100644 index 00000000..7fc667f0 --- /dev/null +++ b/scripts/.gitignore @@ -0,0 +1 @@ +instances \ No newline at end of file diff --git a/scripts/ec2/ec2.go b/scripts/ec2/ec2.go new file mode 100644 index 00000000..0a8a3d7d --- /dev/null +++ b/scripts/ec2/ec2.go @@ -0,0 +1,405 @@ +package main + +import ( + "context" + "fmt" + "os" + "os/exec" + "path/filepath" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/ec2" + ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" +) + +const ( + instancesDirectory = "./instances" + + instanceType = ec2types.InstanceTypeG4dnXlarge // g4dn.xlarge = 1xT4 + imageId = "ami-0f5fcdfbd140e4ab7" // ami-0f5fcdfbd140e4ab7 = Ubuntu Server 24.04 LTS + region = "us-east-2" +) + +func main() { + ctx := context.Background() + + awsConfig, err := config.LoadDefaultConfig(ctx, config.WithRegion(region)) + if err != nil { + panic(err) + } + ec2Client := ec2.NewFromConfig(awsConfig) + + fmt.Printf("EC2 client configured for region: %s\n", region) + + err = run(ctx, ec2Client) + if err != nil { + fmt.Printf("error: %v\n", err) + os.Exit(1) + } + + os.Exit(0) +} + +func run(ctx context.Context, ec2Client *ec2.Client) error { + if len(os.Args) < 2 { + return fmt.Errorf("usage: %s ", os.Args[0]) + } + + switch os.Args[1] { + case "create": + fmt.Println("Creating EC2 instance...") + return create(ctx, ec2Client) + case "ssh": + if len(os.Args) < 3 { + return fmt.Errorf("usage: %s ssh ", os.Args[0]) + } + fmt.Printf("SSHing into EC2 instance: %s\n", os.Args[2]) + return ssh(ctx, ec2Client, os.Args[2]) + case "scp": + if len(os.Args) < 4 { + return fmt.Errorf("usage: %s scp ", os.Args[0]) + } + fmt.Printf("SCPing from %s on EC2 instance: %s\n", os.Args[2], os.Args[3]) + return scp(ctx, ec2Client, os.Args[2], os.Args[3]) + case "list": + fmt.Println("Listing EC2 instances...") + return list(ctx, ec2Client) + case "delete": + if len(os.Args) < 3 { + return fmt.Errorf("usage: %s delete ", os.Args[0]) + } + fmt.Printf("Deleting EC2 instance: %s\n", os.Args[2]) + return delete(ctx, ec2Client, os.Args[2]) + } + + return nil +} + +func create(ctx context.Context, ec2Client *ec2.Client) error { + id := fmt.Sprintf("brev-cli-test-%d", time.Now().Unix()) + + // Create a local directory for the below resources + baseDir := instancesDirectory + instanceDir := filepath.Join(baseDir, id) + err := os.MkdirAll(instanceDir, 0o755) + if err != nil { + return err + } + + // Create a security group that allows SSH access + fmt.Println("Creating security group...") + createSecurityGroupOutput, err := ec2Client.CreateSecurityGroup(ctx, &ec2.CreateSecurityGroupInput{ + GroupName: aws.String(id), + Description: aws.String("Allow SSH access for Brev CLI Test"), + TagSpecifications: []ec2types.TagSpecification{ + { + ResourceType: ec2types.ResourceTypeSecurityGroup, + Tags: []ec2types.Tag{ + { + Key: aws.String("Name"), + Value: aws.String(id), + }, + { + Key: aws.String("Owner"), + Value: aws.String("Brev CLI Test"), + }, + }, + }, + }, + }) + if err != nil { + return err + } + securityGroupId := *createSecurityGroupOutput.GroupId + + // Store the security group ID in the local directory + err = os.WriteFile(filepath.Join(instanceDir, "security_group_id.txt"), []byte(securityGroupId), 0o644) + if err != nil { + return err + } + + // Add SSH inbound rule to the security group + fmt.Println("Adding SSH inbound rule to security group...") + _, err = ec2Client.AuthorizeSecurityGroupIngress(ctx, &ec2.AuthorizeSecurityGroupIngressInput{ + GroupId: aws.String(securityGroupId), + IpPermissions: []ec2types.IpPermission{ + { + IpProtocol: aws.String("tcp"), + FromPort: aws.Int32(22), + ToPort: aws.Int32(22), + IpRanges: []ec2types.IpRange{ + { + CidrIp: aws.String("0.0.0.0/0"), + Description: aws.String("Allow SSH from anywhere"), + }, + }, + }, + }, + }) + if err != nil { + return err + } + + // Create unique keypair for the instance + fmt.Println("Creating keypair...") + createKeyPairOutput, err := ec2Client.CreateKeyPair(ctx, &ec2.CreateKeyPairInput{ + KeyName: aws.String(id), + KeyType: ec2types.KeyTypeRsa, + TagSpecifications: []ec2types.TagSpecification{ + { + ResourceType: ec2types.ResourceTypeKeyPair, + Tags: []ec2types.Tag{ + { + Key: aws.String("Name"), + Value: aws.String(id), + }, + { + Key: aws.String("Owner"), + Value: aws.String("Brev CLI Test"), + }, + }, + }, + }, + }) + if err != nil { + return err + } + keyName := *createKeyPairOutput.KeyName + + // Store the key name in the local directory + err = os.WriteFile(filepath.Join(instanceDir, "key_name.txt"), []byte(keyName), 0o644) + if err != nil { + return err + } + + // Store the key .pem file in the local directory + err = os.WriteFile(filepath.Join(instanceDir, "key.pem"), []byte(*createKeyPairOutput.KeyMaterial), 0o400) + if err != nil { + return err + } + + // Create EC2 instance for use in testing with BYON + fmt.Println("Creating EC2 instance with configuration:") + fmt.Printf("\tInstance type: %s\n", instanceType) + fmt.Printf("\tImage ID: %s\n", imageId) + fmt.Printf("\tKey name: %s\n", keyName) + fmt.Printf("\tSecurity group ID: %s\n", securityGroupId) + fmt.Printf("\tMin count: 1\n") + fmt.Printf("\tMax count: 1\n") + runInstancesOutput, err := ec2Client.RunInstances(ctx, &ec2.RunInstancesInput{ + InstanceType: instanceType, + ImageId: aws.String(imageId), + KeyName: aws.String(keyName), + SecurityGroupIds: []string{securityGroupId}, + MinCount: aws.Int32(1), + MaxCount: aws.Int32(1), + TagSpecifications: []ec2types.TagSpecification{ + { + ResourceType: ec2types.ResourceTypeInstance, + Tags: []ec2types.Tag{ + { + Key: aws.String("Name"), + Value: aws.String(id), + }, + { + Key: aws.String("Owner"), + Value: aws.String("Brev CLI Test"), + }, + }, + }, + }, + }) + if err != nil { + return err + } + instanceId := *runInstancesOutput.Instances[0].InstanceId + + // Store the instance ID in the local directory + err = os.WriteFile(filepath.Join(instanceDir, "instance_id.txt"), []byte(instanceId), 0o644) + if err != nil { + return err + } + + return nil +} + +func ssh(ctx context.Context, ec2Client *ec2.Client, instanceId string) error { + // Read the instance ID from the local directory + instanceIdBytes, err := os.ReadFile(filepath.Join(instancesDirectory, instanceId, "instance_id.txt")) + if err != nil { + return err + } + ec2InstanceId := string(instanceIdBytes) + + // Get the instance details + describeInstancesOutput, err := ec2Client.DescribeInstances(ctx, &ec2.DescribeInstancesInput{ + InstanceIds: []string{ec2InstanceId}, + }) + if err != nil { + return err + } + ec2Instance := describeInstancesOutput.Reservations[0].Instances[0] + + // Build the path to the .pem file + pemFilePath := filepath.Join(instancesDirectory, instanceId, "key.pem") + + // SSH into the instance + fmt.Printf("Connecting to %s...\n", *ec2Instance.PublicIpAddress) + cmd := exec.Command("ssh", "-i", pemFilePath, fmt.Sprintf("ubuntu@%s", *ec2Instance.PublicIpAddress)) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err = cmd.Run() + if err != nil { + return fmt.Errorf("SSH connection failed: %w", err) + } + + return nil +} + +func scp(ctx context.Context, ec2Client *ec2.Client, instanceId string, localFilePath string) error { + // Read the instance ID from the local directory + instanceIdBytes, err := os.ReadFile(filepath.Join(instancesDirectory, instanceId, "instance_id.txt")) + if err != nil { + return err + } + ec2InstanceId := string(instanceIdBytes) + + // Get the instance details + describeInstancesOutput, err := ec2Client.DescribeInstances(ctx, &ec2.DescribeInstancesInput{ + InstanceIds: []string{ec2InstanceId}, + }) + if err != nil { + return err + } + ec2Instance := describeInstancesOutput.Reservations[0].Instances[0] + + // Build the path to the .pem file + pemFilePath := filepath.Join(instancesDirectory, instanceId, "key.pem") + + // SCP the file to the instance + cmd := exec.Command("scp", "-i", pemFilePath, localFilePath, fmt.Sprintf("ubuntu@%s:%s", *ec2Instance.PublicIpAddress, "/home/ubuntu")) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err = cmd.Run() + if err != nil { + return fmt.Errorf("SCP failed: %w", err) + } + + return nil +} + +func list(ctx context.Context, ec2Client *ec2.Client) error { + // Abort if the instances directory does not exist + if _, err := os.Stat(instancesDirectory); os.IsNotExist(err) { + return nil + } + + entries, err := os.ReadDir(instancesDirectory) + if err != nil { + return err + } + + for _, entry := range entries { + fmt.Println(entry.Name()) + + // Read the instance ID from the local directory + instanceIdBytes, err := os.ReadFile(filepath.Join(instancesDirectory, entry.Name(), "instance_id.txt")) + if err != nil { + return err + } + ec2InstanceId := string(instanceIdBytes) + + // Get the instance details + describeInstancesOutput, err := ec2Client.DescribeInstances(ctx, &ec2.DescribeInstancesInput{ + InstanceIds: []string{ec2InstanceId}, + }) + if err != nil { + return err + } + ec2Instance := describeInstancesOutput.Reservations[0].Instances[0] + // Print as if JSON + fmt.Printf("\tInstance ID: %s\n", *ec2Instance.InstanceId) + fmt.Printf("\tInstance type: %s\n", ec2Instance.InstanceType) + fmt.Printf("\tImage ID: %s\n", *ec2Instance.ImageId) + fmt.Printf("\tKey name: %s\n", *ec2Instance.KeyName) + fmt.Printf("\tPrivate IP address: %s\n", *ec2Instance.PrivateIpAddress) + fmt.Printf("\tPublic IP address: %s\n", *ec2Instance.PublicIpAddress) + fmt.Printf("\tState: %s\n", ec2Instance.State.Name) + } + return nil +} + +func delete(ctx context.Context, ec2Client *ec2.Client, instanceId string) error { + // Read the instance ID from the local directory + instanceIdBytes, err := os.ReadFile(filepath.Join(instancesDirectory, instanceId, "instance_id.txt")) + if err != nil { + return err + } + ec2InstanceId := string(instanceIdBytes) + + // Read the key name from the local directory + keyNameBytes, err := os.ReadFile(filepath.Join(instancesDirectory, instanceId, "key_name.txt")) + if err != nil { + return err + } + keyName := string(keyNameBytes) + + // Read the security group ID from the local directory + securityGroupIdBytes, err := os.ReadFile(filepath.Join(instancesDirectory, instanceId, "security_group_id.txt")) + if err != nil { + return err + } + securityGroupId := string(securityGroupIdBytes) + + // Delete the instance + fmt.Printf("Terminating instance: %s\n", ec2InstanceId) + _, err = ec2Client.TerminateInstances(ctx, &ec2.TerminateInstancesInput{ + InstanceIds: []string{ec2InstanceId}, + }) + if err != nil { + return err + } + + // Wait for instance to terminate before deleting security group (note, this can easily take more than 5 minutes) + fmt.Println("Waiting for instance to terminate...") + waiter := ec2.NewInstanceTerminatedWaiter(ec2Client) + err = waiter.Wait(ctx, &ec2.DescribeInstancesInput{ + InstanceIds: []string{ec2InstanceId}, + }, 15*time.Minute) + if err != nil { + fmt.Printf("Warning: error waiting for instance termination: %v\n", err) + } + + // Delete the security group + fmt.Printf("Deleting security group: %s\n", securityGroupId) + _, err = ec2Client.DeleteSecurityGroup(ctx, &ec2.DeleteSecurityGroupInput{ + GroupId: aws.String(securityGroupId), + }) + if err != nil { + return err + } + + // Delete the key pair + fmt.Printf("Deleting key pair: %s\n", keyName) + _, err = ec2Client.DeleteKeyPair(ctx, &ec2.DeleteKeyPairInput{ + KeyName: aws.String(keyName), + }) + if err != nil { + return err + } + + // Delete the local directory + fmt.Printf("Deleting local directory: %s\n", filepath.Join(instancesDirectory, instanceId)) + err = os.RemoveAll(filepath.Join(instancesDirectory, instanceId)) + if err != nil { + return err + } + + return nil +} diff --git a/scripts/ec2/go.mod b/scripts/ec2/go.mod new file mode 100644 index 00000000..29a1a046 --- /dev/null +++ b/scripts/ec2/go.mod @@ -0,0 +1,24 @@ +module github.com/brevdev/brev-cli/scripts + +go 1.25.1 + +require ( + github.com/aws/aws-sdk-go-v2 v1.41.0 + github.com/aws/aws-sdk-go-v2/config v1.32.6 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.279.0 +) + +require ( + github.com/aws/aws-sdk-go-v2/credentials v1.19.6 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect + github.com/aws/smithy-go v1.24.0 // indirect +) diff --git a/scripts/ec2/go.sum b/scripts/ec2/go.sum new file mode 100644 index 00000000..61308b19 --- /dev/null +++ b/scripts/ec2/go.sum @@ -0,0 +1,30 @@ +github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4= +github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= +github.com/aws/aws-sdk-go-v2/config v1.32.6 h1:hFLBGUKjmLAekvi1evLi5hVvFQtSo3GYwi+Bx4lpJf8= +github.com/aws/aws-sdk-go-v2/config v1.32.6/go.mod h1:lcUL/gcd8WyjCrMnxez5OXkO3/rwcNmvfno62tnXNcI= +github.com/aws/aws-sdk-go-v2/credentials v1.19.6 h1:F9vWao2TwjV2MyiyVS+duza0NIRtAslgLUM0vTA1ZaE= +github.com/aws/aws-sdk-go-v2/credentials v1.19.6/go.mod h1:SgHzKjEVsdQr6Opor0ihgWtkWdfRAIwxYzSJ8O85VHY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.279.0 h1:o7eJKe6VYAnqERPlLAvDW5VKXV6eTKv1oxTpMoDP378= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.279.0/go.mod h1:Wg68QRgy2gEGGdmTPU/UbVpdv8sM14bUZmF64KFwAsY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 h1:aM/Q24rIlS3bRAhTyFurowU8A0SMyGDtEOY/l/s/1Uw= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.8/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk= +github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= +github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= diff --git a/tools/go.mod b/tools/go.mod index 5d96ba96..92f6de12 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -3,14 +3,11 @@ module github.com/brevdev/brev-cli/tools go 1.22.6 require ( - github.com/golangci/golangci-lint v1.57.2 github.com/goreleaser/goreleaser v1.21.2 mvdan.cc/gofumpt v0.6.0 ) require ( - 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect - 4d63.com/gochecknoglobals v0.2.1 // indirect cloud.google.com/go v0.110.10 // indirect cloud.google.com/go/compute v1.23.3 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect @@ -19,12 +16,7 @@ require ( cloud.google.com/go/storage v1.35.1 // indirect code.gitea.io/sdk/gitea v0.16.0 // indirect dario.cat/mergo v1.0.0 // indirect - github.com/4meepo/tagalign v1.3.3 // indirect - github.com/Abirdcfly/dupword v0.0.14 // indirect github.com/AlekSi/pointer v1.2.0 // indirect - github.com/Antonboom/errname v0.1.12 // indirect - github.com/Antonboom/nilnil v0.1.7 // indirect - github.com/Antonboom/testifylint v1.2.0 // indirect github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect @@ -43,23 +35,13 @@ require ( github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect github.com/BurntSushi/toml v1.3.2 // indirect - github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect - github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect - github.com/alecthomas/go-check-sumtype v0.1.4 // indirect github.com/alessio/shellescape v1.4.1 // indirect - github.com/alexkohler/nakedret/v2 v2.0.4 // indirect - github.com/alexkohler/prealloc v1.0.0 // indirect - github.com/alingse/asasalint v0.0.11 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/ashanbrown/forbidigo v1.6.0 // indirect - github.com/ashanbrown/makezero v1.1.1 // indirect github.com/atc0005/go-teams-notify/v2 v2.8.0 // indirect github.com/aws/aws-sdk-go v1.44.314 // indirect github.com/aws/aws-sdk-go-v2 v1.20.0 // indirect @@ -87,41 +69,23 @@ require ( github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220517224237-e6f29200ae04 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/bkielbasa/cyclop v1.2.1 // indirect github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect - github.com/blizzy78/varnamelen v0.8.0 // indirect - github.com/bombsimon/wsl/v4 v4.2.1 // indirect - github.com/breml/bidichk v0.2.7 // indirect - github.com/breml/errchkjson v0.3.6 // indirect github.com/buger/jsonparser v1.1.1 // indirect - github.com/butuzov/ireturn v0.3.0 // indirect - github.com/butuzov/mirror v1.1.0 // indirect github.com/caarlos0/ctrlc v1.2.0 // indirect github.com/caarlos0/env/v9 v9.0.0 // indirect github.com/caarlos0/go-reddit/v3 v3.0.1 // indirect github.com/caarlos0/go-shellwords v1.0.12 // indirect github.com/caarlos0/go-version v0.1.1 // indirect github.com/caarlos0/log v0.4.2 // indirect - github.com/catenacyber/perfsprint v0.7.1 // indirect github.com/cavaliergopher/cpio v1.0.1 // indirect - github.com/ccojocar/zxcvbn-go v1.0.2 // indirect github.com/cenkalti/backoff/v4 v4.2.0 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/charithe/durationcheck v0.0.10 // indirect github.com/charmbracelet/lipgloss v0.8.0 // indirect - github.com/chavacava/garif v0.1.0 // indirect github.com/chrismellard/docker-credential-acr-env v0.0.0-20220327082430-c57b701bfc08 // indirect - github.com/ckaznocha/intrange v0.1.1 // indirect github.com/cloudflare/circl v1.4.0 // indirect github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect - github.com/curioswitch/go-reassign v0.2.0 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect - github.com/daixiang0/gci v0.12.3 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidmz/go-pageant v1.0.2 // indirect - github.com/denis-tingaikin/go-header v0.5.0 // indirect github.com/dghubble/go-twitter v0.0.0-20211115160449-93a8679adecb // indirect github.com/dghubble/oauth1 v0.7.2 // indirect github.com/dghubble/sling v1.4.0 // indirect @@ -138,15 +102,9 @@ require ( github.com/docker/go-units v0.5.0 // indirect github.com/elliotchance/orderedmap/v2 v2.2.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect - github.com/ettle/strcase v0.2.0 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fatih/color v1.16.0 // indirect - github.com/fatih/structtag v1.2.0 // indirect - github.com/firefart/nonamedreturns v1.0.4 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/ghostiam/protogetter v0.3.5 // indirect - github.com/go-critic/go-critic v0.11.2 // indirect github.com/go-fed/httpsig v1.1.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect @@ -162,27 +120,11 @@ require ( github.com/go-openapi/swag v0.22.3 // indirect github.com/go-openapi/validate v0.22.1 // indirect github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect - github.com/go-toolsmith/astcast v1.1.0 // indirect - github.com/go-toolsmith/astcopy v1.1.0 // indirect - github.com/go-toolsmith/astequal v1.2.0 // indirect - github.com/go-toolsmith/astfmt v1.1.0 // indirect - github.com/go-toolsmith/astp v1.1.0 // indirect - github.com/go-toolsmith/strparse v1.1.0 // indirect - github.com/go-toolsmith/typep v1.1.0 // indirect - github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect - github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect github.com/gobwas/glob v0.2.3 // indirect - github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect - github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e // indirect - github.com/golangci/misspell v0.4.1 // indirect - github.com/golangci/plugin-module-register v0.1.1 // indirect - github.com/golangci/revgrep v0.5.2 // indirect - github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-containerregistry v0.16.1 // indirect github.com/google/go-github/v55 v55.0.0 // indirect @@ -195,79 +137,46 @@ require ( github.com/google/wire v0.5.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect - github.com/gordonklaus/ineffassign v0.1.0 // indirect github.com/goreleaser/chglog v0.5.0 // indirect github.com/goreleaser/fileglob v1.3.0 // indirect github.com/goreleaser/nfpm/v2 v2.33.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect - github.com/gostaticanalysis/analysisutil v0.7.1 // indirect - github.com/gostaticanalysis/comment v1.4.2 // indirect - github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect - github.com/gostaticanalysis/nilerr v0.1.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.2 // indirect github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/hexops/gotextdiff v1.0.3 // indirect github.com/huandu/xstrings v1.3.3 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/jsonschema v0.9.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/jgautheron/goconst v1.7.1 // indirect - github.com/jingyugao/rowserrcheck v1.1.1 // indirect - github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect - github.com/jjti/go-spancheck v0.5.3 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/julz/importas v0.1.0 // indirect - github.com/karamaru-alpha/copyloopvar v1.0.10 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/kisielk/errcheck v1.7.0 // indirect - github.com/kkHAIKE/contextcheck v1.1.5 // indirect github.com/klauspost/compress v1.17.0 // indirect github.com/klauspost/pgzip v1.2.6 // indirect - github.com/kulti/thelper v0.6.3 // indirect - github.com/kunwardeep/paralleltest v1.0.10 // indirect github.com/kylelemons/godebug v1.1.0 // indirect - github.com/kyoh86/exportloopref v0.1.11 // indirect - github.com/ldez/gomoddirectives v0.2.4 // indirect - github.com/ldez/tagliatelle v0.5.0 // indirect - github.com/leonklingele/grouper v1.1.1 // indirect github.com/letsencrypt/boulder v0.0.0-20221109233200-85aa52084eaf // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/lufeee/execinquery v1.2.1 // indirect - github.com/macabu/inamedparam v0.1.3 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/maratori/testableexamples v1.0.0 // indirect - github.com/maratori/testpackage v1.1.1 // indirect - github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-mastodon v0.0.6 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/mgechev/revive v1.3.7 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/moricho/tparallel v0.3.1 // indirect github.com/muesli/mango v0.1.0 // indirect github.com/muesli/mango-cobra v1.2.0 // indirect github.com/muesli/mango-pflag v0.1.0 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/roff v0.1.0 // indirect github.com/muesli/termenv v0.15.2 // indirect - github.com/nakabonne/nestif v0.3.1 // indirect - github.com/nishanths/exhaustive v0.12.0 // indirect - github.com/nishanths/predeclared v0.2.2 // indirect - github.com/nunnatsa/ginkgolinter v0.16.2 // indirect github.com/oklog/ulid v1.3.1 // indirect - github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/onsi/gomega v1.31.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc3 // indirect github.com/pelletier/go-toml v1.9.5 // indirect @@ -275,88 +184,41 @@ require ( github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/polyfloyd/go-errorlint v1.4.8 // indirect - github.com/prometheus/client_golang v1.15.1 // indirect - github.com/prometheus/client_model v0.4.0 // indirect - github.com/prometheus/common v0.42.0 // indirect - github.com/prometheus/procfs v0.9.0 // indirect - github.com/quasilyte/go-ruleguard v0.4.2 // indirect - github.com/quasilyte/gogrep v0.5.0 // indirect - github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect - github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect github.com/rivo/uniseg v0.4.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/ryancurrah/gomodguard v1.3.1 // indirect - github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect - github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect - github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b // indirect - github.com/sashamelentyev/interfacebloat v1.1.0 // indirect - github.com/sashamelentyev/usestdlibvars v1.25.0 // indirect - github.com/securego/gosec/v2 v2.19.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect - github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect github.com/shopspring/decimal v1.2.0 // indirect github.com/sigstore/cosign/v2 v2.0.3-0.20230523133326-0544abd8fc8a // indirect github.com/sigstore/rekor v1.2.0 // indirect github.com/sigstore/sigstore v1.6.4 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/sivchari/containedctx v1.0.3 // indirect - github.com/sivchari/tenv v1.7.1 // indirect github.com/skeema/knownhosts v1.2.1 // indirect github.com/slack-go/slack v0.12.3 // indirect - github.com/sonatard/noctx v0.0.2 // indirect - github.com/sourcegraph/go-diff v0.7.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.5.1 // indirect github.com/spf13/cobra v1.7.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.16.0 // indirect - github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect - github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect - github.com/stretchr/objx v0.5.2 // indirect - github.com/stretchr/testify v1.9.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect - github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c // indirect - github.com/tdakkota/asciicheck v0.2.0 // indirect github.com/technoweenie/multipartstreamer v1.0.1 // indirect - github.com/tetafro/godot v1.4.16 // indirect github.com/theupdateframework/go-tuf v0.5.2 // indirect - github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect - github.com/timonwong/loggercheck v0.9.4 // indirect github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect - github.com/tomarrell/wrapcheck/v2 v2.8.3 // indirect - github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect github.com/ulikunitz/xz v0.5.11 // indirect - github.com/ultraware/funlen v0.1.0 // indirect - github.com/ultraware/whitespace v0.1.0 // indirect - github.com/uudashr/gocognit v1.1.2 // indirect github.com/vbatts/tar-split v0.11.3 // indirect github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/xanzy/go-gitlab v0.91.1 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect - github.com/xen0n/gosmopolitan v1.2.2 // indirect - github.com/yagipy/maintidx v1.0.0 // indirect - github.com/yeya24/promlinter v0.2.0 // indirect - github.com/ykadowak/zerologlint v0.1.5 // indirect - gitlab.com/bosi/decorder v0.4.1 // indirect gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect - go-simpler.org/musttag v0.9.0 // indirect - go-simpler.org/sloglint v0.5.0 // indirect go.mongodb.org/mongo-driver v1.11.3 // indirect go.opencensus.io v0.24.0 // indirect - go.uber.org/atomic v1.11.0 // indirect go.uber.org/automaxprocs v1.5.3 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.24.0 // indirect gocloud.dev v0.34.0 // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect - golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect golang.org/x/mod v0.16.0 // indirect golang.org/x/net v0.22.0 // indirect golang.org/x/oauth2 v0.15.0 // indirect @@ -381,8 +243,6 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - honnef.co/go/tools v0.4.7 // indirect - mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14 // indirect sigs.k8s.io/kind v0.20.0 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/tools/go.sum b/tools/go.sum index 490927c9..5e89606d 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -1,68 +1,22 @@ -4d63.com/gocheckcompilerdirectives v1.2.1 h1:AHcMYuw56NPjq/2y615IGg2kYkBdTvOaojYCBcRE7MA= -4d63.com/gocheckcompilerdirectives v1.2.1/go.mod h1:yjDJSxmDTtIHHCqX0ufRYZDL6vQtMG7tJdKVeWwsqvs= -4d63.com/gochecknoglobals v0.2.1 h1:1eiorGsgHOFOuoOiJDy2psSrQbRdIHrlge0IJIkUgDc= -4d63.com/gochecknoglobals v0.2.1/go.mod h1:KRE8wtJB3CXCsb1xy421JfTHIIbmT3U5ruxw2Qu8fSU= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= cloud.google.com/go/kms v1.15.5 h1:pj1sRfut2eRbD9pFRjNnPNg/CzJPuQAzUujMIM1vVeM= cloud.google.com/go/kms v1.15.5/go.mod h1:cU2H5jnp6G2TDpUGZyqTCoy1n16fbubHZjmVXSMtwDI= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w= cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= code.gitea.io/sdk/gitea v0.16.0 h1:gAfssETO1Hv9QbE+/nhWu7EjoFQYKt6kPoyDytQgw00= code.gitea.io/sdk/gitea v0.16.0/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBlOlEBg= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/4meepo/tagalign v1.3.3 h1:ZsOxcwGD/jP4U/aw7qeWu58i7dwYemfy5Y+IF1ACoNw= -github.com/4meepo/tagalign v1.3.3/go.mod h1:Q9c1rYMZJc9dPRkbQPpcBNCLEmY2njbAsXhQOZFE2dE= -github.com/Abirdcfly/dupword v0.0.14 h1:3U4ulkc8EUo+CaT105/GJ1BQwtgyj6+VaBVbAX11Ba8= -github.com/Abirdcfly/dupword v0.0.14/go.mod h1:VKDAbxdY8YbKUByLGg8EETzYSuC4crm9WwI6Y3S0cLI= github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w= github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0= -github.com/Antonboom/errname v0.1.12 h1:oh9ak2zUtsLp5oaEd/erjB4GPu9w19NyoIskZClDcQY= -github.com/Antonboom/errname v0.1.12/go.mod h1:bK7todrzvlaZoQagP1orKzWXv59X/x0W0Io2XT1Ssro= -github.com/Antonboom/nilnil v0.1.7 h1:ofgL+BA7vlA1K2wNQOsHzLJ2Pw5B5DpWRLdDAVvvTow= -github.com/Antonboom/nilnil v0.1.7/go.mod h1:TP+ScQWVEq0eSIxqU8CbdT5DFWoHp0MbP+KMUO1BKYQ= -github.com/Antonboom/testifylint v1.2.0 h1:015bxD8zc5iY8QwTp4+RG9I4kIbqwvGX9TrBbb7jGdM= -github.com/Antonboom/testifylint v1.2.0/go.mod h1:rkmEqjqVnHDRNsinyN6fPSLnoajzFwsCcguJgwADBkw= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0 h1:8q4SaHjFsClSvuVne0ID/5Ka8u3fcIHyqkLjcFpNRHQ= @@ -113,17 +67,10 @@ github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= -github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= -github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 h1:sATXp1x6/axKxz2Gjxv8MALP0bXaNRfQinEwyfMcx8c= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0/go.mod h1:Nl76DrGNJTA1KJ0LePKBw/vznBX1EHbAZX8mwjR82nI= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= @@ -134,8 +81,6 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= -github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= -github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k= @@ -144,25 +89,8 @@ github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ github.com/ProtonMail/gopenpgp/v2 v2.7.1/go.mod h1:/BU5gfAVwqyd8EfC3Eu7zmuhwYQpKs+cGD8M//iiaxs= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= -github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= -github.com/alecthomas/go-check-sumtype v0.1.4 h1:WCvlB3l5Vq5dZQTFmodqL2g68uHiSwwlWcT5a2FGK0c= -github.com/alecthomas/go-check-sumtype v0.1.4/go.mod h1:WyYPfhfkdhyrdaligV6svFopZV8Lqdzn5pyVBaV6jhQ= -github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= -github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= -github.com/alexkohler/nakedret/v2 v2.0.4 h1:yZuKmjqGi0pSmjGpOC016LtPJysIL0WEUiaXW5SUnNg= -github.com/alexkohler/nakedret/v2 v2.0.4/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU= -github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= -github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= -github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= -github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -170,10 +98,6 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY= -github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= -github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= -github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= github.com/atc0005/go-teams-notify/v2 v2.8.0 h1:971J5qivrzBbYMDAdmW7v9s7W2u2jiIRVcY+LaIJqww= github.com/atc0005/go-teams-notify/v2 v2.8.0/go.mod h1:SIeE1UfCcVRYMqP5b+r1ZteHyA/2UAjzWF5COnZ8q0w= github.com/aws/aws-sdk-go v1.44.314 h1:d/5Jyk/Fb+PBd/4nzQg0JuC2W4A0knrDIzBgK/ggAow= @@ -243,30 +167,12 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bkielbasa/cyclop v1.2.1 h1:AeF71HZDob1P2/pRm1so9cd1alZnrpyc4q2uP2l0gJY= -github.com/bkielbasa/cyclop v1.2.1/go.mod h1:K/dT/M0FPAiYjBgQGau7tz+3TMh4FWAEqlMhzFWCrgM= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= -github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= -github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= -github.com/bombsimon/wsl/v4 v4.2.1 h1:Cxg6u+XDWff75SIFFmNsqnIOgob+Q9hG6y/ioKbRFiM= -github.com/bombsimon/wsl/v4 v4.2.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo= -github.com/breml/bidichk v0.2.7 h1:dAkKQPLl/Qrk7hnP6P+E0xOodrq8Us7+U0o4UBOAlQY= -github.com/breml/bidichk v0.2.7/go.mod h1:YodjipAGI9fGcYM7II6wFvGhdMYsC5pHDlGzqvEW3tQ= -github.com/breml/errchkjson v0.3.6 h1:VLhVkqSBH96AvXEyclMR37rZslRrY2kcyq+31HCsVrA= -github.com/breml/errchkjson v0.3.6/go.mod h1:jhSDoFheAF2RSDOlCfhHO9KqhZgAYLyvHe7bRCX8f/U= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/butuzov/ireturn v0.3.0 h1:hTjMqWw3y5JC3kpnC5vXmFJAWI/m31jaCYQqzkS6PL0= -github.com/butuzov/ireturn v0.3.0/go.mod h1:A09nIiwiqzN/IoVo9ogpa0Hzi9fex1kd9PSD6edP5ZA= -github.com/butuzov/mirror v1.1.0 h1:ZqX54gBVMXu78QLoiqdwpl2mgmoOJTk7s4p4o+0avZI= -github.com/butuzov/mirror v1.1.0/go.mod h1:8Q0BdQU6rC6WILDiBM60DBfvV78OLJmMmixe7GF45AE= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/caarlos0/ctrlc v1.2.0 h1:AtbThhmbeYx1WW3WXdWrd94EHKi+0NPRGS4/4pzrjwk= github.com/caarlos0/ctrlc v1.2.0/go.mod h1:n3gDlSjsXZ7rbD9/RprIR040b7oaLfNStikPd4gFago= @@ -286,35 +192,20 @@ github.com/caarlos0/sshmarshal v0.1.0 h1:zTCZrDORFfWh526Tsb7vCm3+Yg/SfW/Ub8aQDeo github.com/caarlos0/sshmarshal v0.1.0/go.mod h1:7Pd/0mmq9x/JCzKauogNjSQEhivBclCQHfr9dlpDIyA= github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8= github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk= -github.com/catenacyber/perfsprint v0.7.1 h1:PGW5G/Kxn+YrN04cRAZKC+ZuvlVwolYMrIyyTJ/rMmc= -github.com/catenacyber/perfsprint v0.7.1/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM= github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc= -github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= -github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= -github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= github.com/charmbracelet/keygen v0.4.3 h1:ywOZRwkDlpmkawl0BgLTxaYWDSqp6Y4nfVVmgyyO1Mg= github.com/charmbracelet/keygen v0.4.3/go.mod h1:4e4FT3HSdLU/u83RfJWvzJIaVb8aX4MxtDlfXwpDJaI= github.com/charmbracelet/lipgloss v0.8.0 h1:IS00fk4XAHcf8uZKc3eHeMUTCxUH6NkaTrdyCQk84RU= github.com/charmbracelet/lipgloss v0.8.0/go.mod h1:p4eYUZZJ/0oXTuCQKFF8mqyKCz0ja6y+7DniDDw5KKU= -github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= -github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= github.com/chrismellard/docker-credential-acr-env v0.0.0-20220327082430-c57b701bfc08 h1:9Qh4lJ/KMr5iS1zfZ8I97+3MDpiKjl+0lZVUNBhdvRs= github.com/chrismellard/docker-credential-acr-env v0.0.0-20220327082430-c57b701bfc08/go.mod h1:MAuu1uDJNOS3T3ui0qmKdPUwm59+bO19BbTph2wZafE= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/ckaznocha/intrange v0.1.1 h1:gHe4LfqCspWkh8KpJFs20fJz3XRHFBFUV9yI7Itu83Q= -github.com/ckaznocha/intrange v0.1.1/go.mod h1:RWffCw/vKBwHeOEwWdCikAtY0q4gGt8VhJZEEA5n+RE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.4.0 h1:BV7h5MgrktNzytKmWjpOtdYrf0lkkbF8YMlBGPhJQrY= @@ -328,19 +219,13 @@ github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= -github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= -github.com/daixiang0/gci v0.12.3 h1:yOZI7VAxAGPQmkb1eqt5g/11SUlwoat1fSblGLmdiQc= -github.com/daixiang0/gci v0.12.3/go.mod h1:xtHP9N7AHdNvtRNfcx9gwTDfw7FRJx4bZUsiEfiNNAI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= -github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= -github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= github.com/dghubble/go-twitter v0.0.0-20211115160449-93a8679adecb h1:7ENzkH+O3juL+yj2undESLTaAeRllHwCs/b8z6aWSfc= github.com/dghubble/go-twitter v0.0.0-20211115160449-93a8679adecb/go.mod h1:qhZBgV9e4WyB1JNjHpcXVkUe3knWUwYuAPB1hITdm50= github.com/dghubble/oauth1 v0.7.2 h1:pwcinOZy8z6XkNxvPmUDY52M7RDPxt0Xw1zgZ6Cl5JA= @@ -386,8 +271,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= -github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw= @@ -398,22 +281,12 @@ github.com/facebookgo/muster v0.0.0-20150708232844-fd3d7953fd52 h1:a4DFiKFJiDRGF github.com/facebookgo/muster v0.0.0-20150708232844-fd3d7953fd52/go.mod h1:yIquW87NGRw1FU5p5lEkpnt/QxoH5uPAOUlOVkAUuMg= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= -github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= -github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y= -github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= -github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/ghostiam/protogetter v0.3.5 h1:+f7UiF8XNd4w3a//4DnusQ2SZjPkUjxkMEfjbxOK4Ug= -github.com/ghostiam/protogetter v0.3.5/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= -github.com/go-critic/go-critic v0.11.2 h1:81xH/2muBphEgPtcwH1p6QD+KzXl2tMSi3hXjBSxDnM= -github.com/go-critic/go-critic v0.11.2/go.mod h1:OePaicfjsf+KPy33yq4gzv6CO7TEQ9Rom6ns1KsJnl8= github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= @@ -424,17 +297,6 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY= github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc= github.com/go-openapi/analysis v0.21.4/go.mod h1:4zQ35W4neeZTqh3ol0rv/O8JBbka9QyAgQRPp9y3pfo= @@ -473,36 +335,11 @@ github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+ github.com/go-openapi/validate v0.22.1 h1:G+c2ub6q47kfX1sOBLwIQwzBVt8qmOAARyo/9Fqs9NU= github.com/go-openapi/validate v0.22.1/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU= github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= -github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= -github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= -github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw= -github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= -github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ= -github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw= -github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY= -github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco= -github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4= -github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= -github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA= -github.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk= -github.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus= -github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= -github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw= -github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= -github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= -github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= -github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c= -github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U= -github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= @@ -529,9 +366,6 @@ github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/V github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= -github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= @@ -539,60 +373,29 @@ github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= -github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= -github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e h1:ULcKCDV1LOZPFxGZaA6TlQbiM3J2GCPnkx/bGF6sX/g= -github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e/go.mod h1:Pm5KhLPA8gSnQwrQ6ukebRcapGb/BG9iUkdaiCcGHJM= -github.com/golangci/golangci-lint v1.57.2 h1:NNhxfZyL5He1WWDrIvl1a4n5bvWZBcgAqBwlJAAgLTw= -github.com/golangci/golangci-lint v1.57.2/go.mod h1:ApiG3S3Ca23QyfGp5BmsorTiVxJpr5jGiNS0BkdSidg= -github.com/golangci/misspell v0.4.1 h1:+y73iSicVy2PqyX7kmUefHusENlrP9YwuHZHPLGQj/g= -github.com/golangci/misspell v0.4.1/go.mod h1:9mAN1quEo3DlpbaIKKyEvRxK1pwqR9s/Sea1bJCtlNI= -github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= -github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= -github.com/golangci/revgrep v0.5.2 h1:EndcWoRhcnfj2NHQ+28hyuXpLMF+dQmCN+YaeeIl4FU= -github.com/golangci/revgrep v0.5.2/go.mod h1:bjAMA+Sh/QUfTDcHzxfyHxr4xKvllVr/0sCv2e7jJHA= -github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed h1:IURFTjxeTfNFP0hTEi1YKjB/ub8zkpaOqFFMApi2EAs= -github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed/go.mod h1:XLXN8bNw4CGRPaqgl3bv/lhz7bsGPh4/xSaMTbo2vkQ= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -612,24 +415,10 @@ github.com/google/go-replayers/grpcreplay v1.1.0 h1:S5+I3zYyZ+GQz68OfbURDdt/+cSM github.com/google/go-replayers/grpcreplay v1.1.0/go.mod h1:qzAvJ8/wi57zq7gWqaE6AwLM6miiXUQwP1S+I9icmhk= github.com/google/go-replayers/httpreplay v1.2.0 h1:VM1wEyyjaoU53BwrOnaf9VhAyQQEEioJvFYxYcLRKzk= github.com/google/go-replayers/httpreplay v1.2.0/go.mod h1:WahEFFZZ7a1P4VM1qEeHy+tME4bwyqPcwWbNlUI1Mcg= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/ko v0.14.1 h1:CU+iuIYOOUoSLpBi/gYhnJerig/an/6/cUaEZIOWSGo= github.com/google/ko v0.14.1/go.mod h1:OrNWWNU4PEdaOArS3M2trorYoIbUwaZYBUHPOw6CVL0= -github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/rpmpack v0.5.0 h1:L16KZ3QvkFGpYhmp23iQip+mx1X39foEsqszjMNBm8A= github.com/google/rpmpack v0.5.0/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= @@ -647,14 +436,10 @@ github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8= github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= -github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= -github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= github.com/goreleaser/chglog v0.5.0 h1:Sk6BMIpx8+vpAf8KyPit34OgWui8c7nKTMHhYx88jJ4= github.com/goreleaser/chglog v0.5.0/go.mod h1:Ri46M3lrMuv76FHszs3vtABR8J8k1w9JHYAzxeeOl28= github.com/goreleaser/fileglob v1.3.0 h1:/X6J7U8lbDpQtBvGcwwPS6OpzkNVlVEsFUVRx9+k+7I= @@ -668,18 +453,6 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= -github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= -github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= -github.com/gostaticanalysis/comment v1.4.2 h1:hlnx5+S2fY9Zo9ePo4AhgYsYHbM2+eAv8m/s1JiCd6Q= -github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= -github.com/gostaticanalysis/forcetypeassert v0.1.0 h1:6eUflI3DiGusXGK6X7cCcIgVCpZ2CiZ1Q7jl6ZxNV70= -github.com/gostaticanalysis/forcetypeassert v0.1.0/go.mod h1:qZEedyP/sY1lTGV1uJ3VhWZ2mqag3IkWsDHVbplHXak= -github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk= -github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= -github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= -github.com/gostaticanalysis/testutil v0.4.0 h1:nhdCmubdmDF6VEatUNjgUZBJKWRqugoISdUv3PPQgHY= -github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -692,23 +465,17 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0= github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= -github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= -github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/honeycombio/beeline-go v1.10.0 h1:cUDe555oqvw8oD76BQJ8alk7FP0JZ/M/zXpNvOEDLDc= github.com/honeycombio/beeline-go v1.10.0/go.mod h1:Zz5WMeQCJzFt2Mvf8t6HC1X8RLskLVR/e8rvcmXB1G8= github.com/honeycombio/libhoney-go v1.16.0 h1:kPpqoz6vbOzgp7jC6SR7SkNj7rua7rgxvznI6M3KdHc= github.com/honeycombio/libhoney-go v1.16.0/go.mod h1:izP4fbREuZ3vqC4HlCAmPrcPT9gxyxejRjGtCYpmBn0= github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= @@ -722,14 +489,6 @@ github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPa github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk= -github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= -github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= -github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= -github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af h1:KA9BjwUk7KlCh6S9EAGWBt1oExIUv9WyNCiRz5amv48= -github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= -github.com/jjti/go-spancheck v0.5.3 h1:vfq4s2IB8T3HvbpiwDTYgVPj1Ze/ZSXrTtaZRTc7CuM= -github.com/jjti/go-spancheck v0.5.3/go.mod h1:eQdOX1k3T+nAKvZDyLC3Eby0La4dZ+I19iOl5NzSPFE= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -739,31 +498,14 @@ github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548/go.mod h1:hGT6jSUVz github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= -github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= -github.com/karamaru-alpha/copyloopvar v1.0.10 h1:8HYDy6KQYqTmD7JuhZMWS1nwPru9889XI24ROd/+WXI= -github.com/karamaru-alpha/copyloopvar v1.0.10/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/errcheck v1.7.0 h1:+SbscKmWJ5mOK/bO1zS60F5I9WwZDWOfRsC4RwfwRV0= -github.com/kisielk/errcheck v1.7.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kkHAIKE/contextcheck v1.1.5 h1:CdnJh63tcDe53vG+RebdpdXJTc9atMgGqdx8LXxiilg= -github.com/kkHAIKE/contextcheck v1.1.5/go.mod h1:O930cpht4xb1YQpK+1+AgoM3mFsvxr7uyFptcnWTYUA= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= @@ -771,8 +513,6 @@ github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -781,28 +521,12 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= -github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= -github.com/kunwardeep/paralleltest v1.0.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCTdvWJ/lDDs= -github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/kyoh86/exportloopref v0.1.11 h1:1Z0bcmTypkL3Q4k+IDHMWTcnCliEZcaPiIe0/ymEyhQ= -github.com/kyoh86/exportloopref v0.1.11/go.mod h1:qkV4UF1zGl6EkF1ox8L5t9SwyeBAZ3qLMd6up458uqA= -github.com/ldez/gomoddirectives v0.2.4 h1:j3YjBIjEBbqZ0NKtBNzr8rtMHTOrLPeiwTkfUJZ3alg= -github.com/ldez/gomoddirectives v0.2.4/go.mod h1:oWu9i62VcQDYp9EQ0ONTfqLNh+mDLWWDO+SO0qSQw5g= -github.com/ldez/tagliatelle v0.5.0 h1:epgfuYt9v0CG3fms0pEgIMNPuFf/LpPIfjk4kyqSioo= -github.com/ldez/tagliatelle v0.5.0/go.mod h1:rj1HmWiL1MiKQuOONhd09iySTEkUuE/8+5jtPYz9xa4= -github.com/leonklingele/grouper v1.1.1 h1:suWXRU57D4/Enn6pXR0QVqqWWrnJ9Osrz+5rjt8ivzU= -github.com/leonklingele/grouper v1.1.1/go.mod h1:uk3I3uDfi9B6PeUjsCKi6ndcf63Uy7snXgR4yDYQVDY= github.com/letsencrypt/boulder v0.0.0-20221109233200-85aa52084eaf h1:ndns1qx/5dL43g16EQkPV/i8+b3l5bYQwLeoSBe7tS8= github.com/letsencrypt/boulder v0.0.0-20221109233200-85aa52084eaf/go.mod h1:aGkAgvWY/IUcVFfuly53REpfv5edu25oij+qHRFaraA= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/lufeee/execinquery v1.2.1 h1:hf0Ems4SHcUGBxpGN7Jz78z1ppVkP/837ZlETPCEtOM= -github.com/lufeee/execinquery v1.2.1/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM= -github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk= -github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -810,33 +534,22 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= -github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= -github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= -github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= -github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 h1:gWg6ZQ4JhDfJPqlo2srm/LN17lpybq15AryXIRcWYLE= -github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-mastodon v0.0.6 h1:lqU1sOeeIapaDsDUL6udDZIzMb2Wqapo347VZlaOzf0= github.com/mattn/go-mastodon v0.0.6/go.mod h1:cg7RFk2pcUfHZw/IvKe1FUzmlq5KnLFqs7eV2PHplV8= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/mgechev/revive v1.3.7 h1:502QY0vQGe9KtYJ9FpxMz9rL+Fc/P13CI5POL4uHCcE= -github.com/mgechev/revive v1.3.7/go.mod h1:RJ16jUbF0OWC3co/+XTxmFNgEpUPwnnA0BRllX2aDNA= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= @@ -851,14 +564,7 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= -github.com/moricho/tparallel v0.3.1 h1:fQKD4U1wRMAYNngDonW5XupoB/ZGJHdpzrWqgyg9krA= -github.com/moricho/tparallel v0.3.1/go.mod h1:leENX2cUv7Sv2qDgdi0D0fCftN8fRC67Bcn8pqzeYNI= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/muesli/mango v0.1.0 h1:DZQK45d2gGbql1arsYA4vfg4d7I9Hfx5rX/GCmzsAvI= @@ -873,23 +579,9 @@ github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8= github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= -github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= -github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= -github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= -github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/nunnatsa/ginkgolinter v0.16.2 h1:8iLqHIZvN4fTLDC0Ke9tbSZVcyVHoBs0HIbnVSxfHJk= -github.com/nunnatsa/ginkgolinter v0.16.2/go.mod h1:4tWRinDN1FeJgU+iJANW/kz7xKN5nYRAOfJDQUS9dOQ= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= -github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo= github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -900,13 +592,6 @@ github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/ github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= -github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= -github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= -github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= -github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= -github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= -github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= @@ -923,44 +608,17 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polyfloyd/go-errorlint v1.4.8 h1:jiEjKDH33ouFktyez7sckv6pHWif9B7SuS8cutDXFHw= -github.com/polyfloyd/go-errorlint v1.4.8/go.mod h1:NNCxFcFjZcw3xNjVdCchERkEM6Oz7wta2XJVxRftwO4= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= -github.com/quasilyte/go-ruleguard v0.4.2 h1:htXcXDK6/rO12kiTHKfHuqR4kr3Y4M0J0rOL6CH/BYs= -github.com/quasilyte/go-ruleguard v0.4.2/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= -github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= -github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= -github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= -github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= -github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= -github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8= @@ -972,49 +630,25 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryancurrah/gomodguard v1.3.1 h1:fH+fUg+ngsQO0ruZXXHnA/2aNllWA1whly4a6UvyzGE= -github.com/ryancurrah/gomodguard v1.3.1/go.mod h1:DGFHzEhi6iJ0oIDfMuo3TgrS+L9gZvrEfmjjuelnRU0= -github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= -github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= -github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/NsJFwFK7Uxc= -github.com/sanposhiho/wastedassign/v2 v2.0.7/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= -github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= -github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b h1:qYTY2tN72LhgDj2rtWG+LI6TXFl2ygFQQ4YezfVaGQE= github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b/go.mod h1:/pA7k3zsXKdjjAiUhB5CjuKib9KJGCaLvZwtxGC8U0s= -github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= -github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= -github.com/sashamelentyev/usestdlibvars v1.25.0 h1:IK8SI2QyFzy/2OD2PYnhy84dpfNo9qADrRt6LH8vSzU= -github.com/sashamelentyev/usestdlibvars v1.25.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= -github.com/securego/gosec/v2 v2.19.0 h1:gl5xMkOI0/E6Hxx0XCY2XujA3V7SNSefA8sC+3f1gnk= -github.com/securego/gosec/v2 v2.19.0/go.mod h1:hOkDcHz9J/XIgIlPDXalxjeVYsHxoWUc5zJSHxcB8YM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= -github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/sigstore/cosign/v2 v2.0.3-0.20230523133326-0544abd8fc8a h1:4j4hrwYblDkNouA2fZ/hKvtJhV/N+jJGhLoRXUNLYmE= github.com/sigstore/cosign/v2 v2.0.3-0.20230523133326-0544abd8fc8a/go.mod h1:em8IHAamkOMXzXHjHx5NdLO1d8erWDMlGRlx0XE5TtI= github.com/sigstore/rekor v1.2.0 h1:ahlnoEY3zo8Vc+eZLPobamw6YfBTAbI0lthzUQd6qe4= github.com/sigstore/rekor v1.2.0/go.mod h1:zcFO54qIg2G1/i0sE/nvmELUOng/n0MPjTszRYByVPo= github.com/sigstore/sigstore v1.6.4 h1:jH4AzR7qlEH/EWzm+opSpxCfuUcjHL+LJPuQE7h40WE= github.com/sigstore/sigstore v1.6.4/go.mod h1:pjR64lBxnjoSrAr+Ydye/FV73IfrgtoYlAI11a8xMfA= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= -github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= -github.com/sivchari/tenv v1.7.1 h1:PSpuD4bu6fSmtWMxSGWcvqUUgIn7k3yOJhOIzVWn8Ak= -github.com/sivchari/tenv v1.7.1/go.mod h1:64yStXKSOxDfX47NlhVwND4dHwfZDdbp2Lyl018Icvg= github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/slack-go/slack v0.12.3 h1:92/dfFU8Q5XP6Wp5rr5/T5JHLM5c5Smtn53fhToAP88= @@ -1023,10 +657,6 @@ github.com/smartystreets/assertions v1.13.1 h1:Ef7KhSmjZcK6AVf9YbJdvPYG9avaF0Zxu github.com/smartystreets/assertions v1.13.1/go.mod h1:cXr/IwVfSo/RbCSPhoAPv73p3hlSdrBH/b3SdnW/LMY= github.com/smartystreets/goconvey v1.8.0 h1:Oi49ha/2MURE0WexF052Z0m+BNSGirfjg5RL+JXWq3w= github.com/smartystreets/goconvey v1.8.0/go.mod h1:EdX8jtrTIj26jmjCOVNMVSIYAtgexqXKHOXW2Dx9JLg= -github.com/sonatard/noctx v0.0.2 h1:L7Dz4De2zDQhW8S0t+KUjY0MAQJd6SgVwhzNIc4ok00= -github.com/sonatard/noctx v0.0.2/go.mod h1:kzFz+CzWSjQ2OzIm46uJZoXuBpa2+0y3T36U18dWqIo= -github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= -github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -1043,15 +673,10 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= -github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= -github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= -github.com/stbenjam/no-sprintf-host-port v0.1.1 h1:tYugd/yrm1O0dV+ThCbaKZh195Dfm07ysF0U6JQXczc= -github.com/stbenjam/no-sprintf-host-port v0.1.1/go.mod h1:TLhvtIvONRzdmkFiio4O8LHsN9N74I+PhRquPsxpL0I= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -1068,44 +693,20 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c h1:+aPplBwWcHBo6q9xrfWdMrT9o4kltkmmvpemgIjep/8= -github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c/go.mod h1:SbErYREK7xXdsRiigaQiQkI9McGRzYMvlKYaP3Nimdk= -github.com/tdakkota/asciicheck v0.2.0 h1:o8jvnUANo0qXtnslk2d3nMKTFNlOnJjRrNcj0j9qkHM= -github.com/tdakkota/asciicheck v0.2.0/go.mod h1:Qb7Y9EgjCLJGup51gDHFzbI08/gbGhL/UVhYIPWG2rg= github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= -github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= -github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= -github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= -github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= -github.com/tetafro/godot v1.4.16 h1:4ChfhveiNLk4NveAZ9Pu2AN8QZ2nkUGFuadM9lrr5D0= -github.com/tetafro/godot v1.4.16/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= github.com/theupdateframework/go-tuf v0.5.2 h1:habfDzTmpbzBLIFGWa2ZpVhYvFBoK0C1onC3a4zuPRA= github.com/theupdateframework/go-tuf v0.5.2/go.mod h1:SyMV5kg5n4uEclsyxXJZI2UxPFJNDc4Y+r7wv+MlvTA= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 h1:quvGphlmUVU+nhpFa4gg4yJyTRJ13reZMDHrKwYw53M= -github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966/go.mod h1:27bSVNWSBOHm+qRp1T9qzaIpsWEP6TbUnei/43HK+PQ= -github.com/timonwong/loggercheck v0.9.4 h1:HKKhqrjcVj8sxL7K77beXh0adEm6DLjV/QOGeMXEVi4= -github.com/timonwong/loggercheck v0.9.4/go.mod h1:caz4zlPcgvpEkXgVnAJGowHAMW2NwHaNlpS8xDbVhTg= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= -github.com/tomarrell/wrapcheck/v2 v2.8.3 h1:5ov+Cbhlgi7s/a42BprYoxsr73CbdMUTzE3bRDFASUs= -github.com/tomarrell/wrapcheck/v2 v2.8.3/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= -github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= -github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y= github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE= github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ultraware/funlen v0.1.0 h1:BuqclbkY6pO+cvxoq7OsktIXZpgBSkYTQtmwhAK81vI= -github.com/ultraware/funlen v0.1.0/go.mod h1:XJqmOQja6DpxarLj6Jj1U7JuoS8PvL4nEqDaQhy22p4= -github.com/ultraware/whitespace v0.1.0 h1:O1HKYoh0kIeqE8sFqZf1o0qbORXUCOQFrlaQyZsczZw= -github.com/ultraware/whitespace v0.1.0/go.mod h1:/se4r3beMFNmewJ4Xmz0nMQ941GJt+qmSHGP9emHYe0= github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= -github.com/uudashr/gocognit v1.1.2 h1:l6BAEKJqQH2UpKAPKdMfZf5kE4W/2xk8pfU1OVLvniI= -github.com/uudashr/gocognit v1.1.2/go.mod h1:aAVdLURqcanke8h3vg35BC++eseDm66Z7KmchI5et4k= github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck= github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= @@ -1131,63 +732,28 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= -github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= -github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= -github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= -github.com/yeya24/promlinter v0.2.0 h1:xFKDQ82orCU5jQujdaD8stOHiv8UN68BSdn2a8u8Y3o= -github.com/yeya24/promlinter v0.2.0/go.mod h1:u54lkmBOZrpEbQQ6gox2zWKKLKu2SGe+2KOiextY+IA= -github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= -github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -gitlab.com/bosi/decorder v0.4.1 h1:VdsdfxhstabyhZovHafFw+9eJ6eU0d2CkFNJcZz/NU4= -gitlab.com/bosi/decorder v0.4.1/go.mod h1:jecSqWUew6Yle1pCr2eLWTensJMmsxHsBwt+PVbkAqA= gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8= gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0= -go-simpler.org/assert v0.7.0 h1:OzWWZqfNxt8cLS+MlUp6Tgk1HjPkmgdKBq9qvy8lZsA= -go-simpler.org/assert v0.7.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= -go-simpler.org/musttag v0.9.0 h1:Dzt6/tyP9ONr5g9h9P3cnYWCxeBFRkd0uJL/w+1Mxos= -go-simpler.org/musttag v0.9.0/go.mod h1:gA9nThnalvNSKpEoyp3Ko4/vCX2xTpqKoUtNqXOnVR4= -go-simpler.org/sloglint v0.5.0 h1:2YCcd+YMuYpuqthCgubcF5lBSjb6berc5VMOYUHKrpY= -go-simpler.org/sloglint v0.5.0/go.mod h1:EUknX5s8iXqf18KQxKnaBHUPVriiPnOrPjjJcsaTcSQ= go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= go.mongodb.org/mongo-driver v1.11.3 h1:Ql6K6qYHEzB6xvu4+AU0BoRoqf9vFPcc4o7MUIdPW8Y= go.mongodb.org/mongo-driver v1.11.3/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= -go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= gocloud.dev v0.34.0 h1:LzlQY+4l2cMtuNfwT2ht4+fiXwWf/NmPTnXUlLmGif4= gocloud.dev v0.34.0/go.mod h1:psKOachbnvY3DAOPbsFVmLIErwsbWPUG2H5i65D38vE= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -1197,7 +763,6 @@ golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= @@ -1205,114 +770,47 @@ golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= -golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8= -golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1320,65 +818,29 @@ golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= @@ -1387,97 +849,37 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190321232350-e250d351ecad/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= -golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= -golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= -golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= -golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= @@ -1487,61 +889,15 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.152.0 h1:t0r1vPnfMc260S2Ci+en7kfCZaLOPs5KI0sVV/6jZrY= google.golang.org/api v0.152.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo= @@ -1549,17 +905,9 @@ google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= @@ -1571,13 +919,11 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/alexcesaro/statsd.v2 v2.0.0 h1:FXkZSCZIH17vLCO5sO2UucTHsH9pc+17F6pl3JVCwMc= @@ -1598,10 +944,8 @@ gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -1615,21 +959,9 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.4.7 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs= -honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo= mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA= -mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14 h1:zCr3iRRgdk5eIikZNDphGcM6KGVTx3Yu+/Uu9Es254w= -mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14/go.mod h1:ZzZjEpJDOmx8TdVU6umamY3Xy0UAQUI2DHbf05USVbI= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/kind v0.20.0 h1:f0sc3v9mQbGnjBUaqSFST1dwIuiikKVGgoTwpoP33a8= sigs.k8s.io/kind v0.20.0/go.mod h1:aBlbxg08cauDgZ612shr017/rZwqd7AS563FvpWKPVs= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= diff --git a/tools/tools.go b/tools/tools.go index 0c6ef206..a731f141 100644 --- a/tools/tools.go +++ b/tools/tools.go @@ -8,7 +8,6 @@ package tools // https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module // https://github.com/golang/go/issues/25922 import ( - _ "github.com/golangci/golangci-lint/cmd/golangci-lint" _ "github.com/goreleaser/goreleaser" _ "mvdan.cc/gofumpt" )