From 037ff7fe4cc23fc1ed5be8dd2a220e149d5be965 Mon Sep 17 00:00:00 2001 From: Pratik Patel Date: Tue, 6 Jan 2026 17:08:07 -0800 Subject: [PATCH 01/22] feat(BREV-): brevcloud daemon --- brevcloud-scripts/brevd/README.md | 76 ++ brevcloud-scripts/brevd/enroll.sh | 159 +++ brevcloud-scripts/brevd/install.sh | 141 +++ brevcloud-scripts/brevd/systemd/brevd.service | 17 + go.mod | 296 ++++- go.sum | 1063 ++++++++++------- pkg/brevcloud/client.go | 216 ++++ pkg/brevdaemon/agent.go | 58 + pkg/brevdaemon/agent/README.md | 0 pkg/brevdaemon/agent/agent.go | 240 ++++ pkg/brevdaemon/agent/agent_test.go | 201 ++++ pkg/brevdaemon/agent/client/client.go | 647 ++++++++++ pkg/brevdaemon/agent/client/client_test.go | 290 +++++ pkg/brevdaemon/agent/config/config.go | 159 +++ pkg/brevdaemon/agent/config/config_test.go | 149 +++ pkg/brevdaemon/agent/health/reporter.go | 112 ++ pkg/brevdaemon/agent/health/reporter_test.go | 93 ++ pkg/brevdaemon/agent/heartbeat/heartbeat.go | 225 ++++ .../agent/heartbeat/heartbeat_test.go | 213 ++++ pkg/brevdaemon/agent/identity/identity.go | 309 +++++ .../agent/identity/identity_test.go | 165 +++ pkg/brevdaemon/agent/telemetry/hardware.go | 305 +++++ .../agent/telemetry/hardware_test.go | 101 ++ .../agent/telemetry/memory_darwin.go | 21 + .../agent/telemetry/memory_other.go | 9 + pkg/brevdaemon/agent/telemetry/os_consts.go | 3 + pkg/brevdaemon/agent/telemetry/utilization.go | 381 ++++++ .../agent/telemetry/utilization_test.go | 118 ++ pkg/brevdaemon/agent/tunnel/ingress.go | 195 +++ pkg/brevdaemon/agent/tunnel/ingress_test.go | 236 ++++ pkg/brevdaemon/agent/tunnel/tunnel.go | 361 ++++++ pkg/brevdaemon/agent/tunnel/tunnel_test.go | 194 +++ pkg/brevdaemon/provider/tunnel.go | 8 + pkg/cmd/cmd.go | 2 + pkg/cmd/register/register.go | 4 + pkg/cmd/spark/agent.go | 44 + pkg/cmd/spark/enroll.go | 782 ++++++++++++ pkg/cmd/spark/install-binary.sh | 36 + pkg/cmd/spark/install-service.sh | 45 + pkg/cmd/spark/spark.go | 33 + pkg/cmd/spark/ssh.go | 67 ++ pkg/config/config.go | 15 +- pkg/spark/parser.go | 191 +++ pkg/spark/parser_test.go | 84 ++ pkg/spark/paths.go | 57 + pkg/spark/paths_test.go | 51 + pkg/spark/remote.go | 108 ++ pkg/spark/select.go | 76 ++ pkg/spark/select_test.go | 49 + pkg/spark/ssh_runner.go | 88 ++ pkg/spark/ssh_runner_test.go | 76 ++ pkg/spark/types.go | 28 + 52 files changed, 8068 insertions(+), 529 deletions(-) create mode 100644 brevcloud-scripts/brevd/README.md create mode 100644 brevcloud-scripts/brevd/enroll.sh create mode 100644 brevcloud-scripts/brevd/install.sh create mode 100644 brevcloud-scripts/brevd/systemd/brevd.service create mode 100644 pkg/brevcloud/client.go create mode 100644 pkg/brevdaemon/agent.go create mode 100644 pkg/brevdaemon/agent/README.md create mode 100644 pkg/brevdaemon/agent/agent.go create mode 100644 pkg/brevdaemon/agent/agent_test.go create mode 100644 pkg/brevdaemon/agent/client/client.go create mode 100644 pkg/brevdaemon/agent/client/client_test.go create mode 100644 pkg/brevdaemon/agent/config/config.go create mode 100644 pkg/brevdaemon/agent/config/config_test.go create mode 100644 pkg/brevdaemon/agent/health/reporter.go create mode 100644 pkg/brevdaemon/agent/health/reporter_test.go create mode 100644 pkg/brevdaemon/agent/heartbeat/heartbeat.go create mode 100644 pkg/brevdaemon/agent/heartbeat/heartbeat_test.go create mode 100644 pkg/brevdaemon/agent/identity/identity.go create mode 100644 pkg/brevdaemon/agent/identity/identity_test.go create mode 100644 pkg/brevdaemon/agent/telemetry/hardware.go create mode 100644 pkg/brevdaemon/agent/telemetry/hardware_test.go create mode 100644 pkg/brevdaemon/agent/telemetry/memory_darwin.go create mode 100644 pkg/brevdaemon/agent/telemetry/memory_other.go create mode 100644 pkg/brevdaemon/agent/telemetry/os_consts.go create mode 100644 pkg/brevdaemon/agent/telemetry/utilization.go create mode 100644 pkg/brevdaemon/agent/telemetry/utilization_test.go create mode 100644 pkg/brevdaemon/agent/tunnel/ingress.go create mode 100644 pkg/brevdaemon/agent/tunnel/ingress_test.go create mode 100644 pkg/brevdaemon/agent/tunnel/tunnel.go create mode 100644 pkg/brevdaemon/agent/tunnel/tunnel_test.go create mode 100644 pkg/brevdaemon/provider/tunnel.go create mode 100644 pkg/cmd/spark/agent.go create mode 100644 pkg/cmd/spark/enroll.go create mode 100644 pkg/cmd/spark/install-binary.sh create mode 100644 pkg/cmd/spark/install-service.sh create mode 100644 pkg/cmd/spark/spark.go create mode 100644 pkg/cmd/spark/ssh.go create mode 100644 pkg/spark/parser.go create mode 100644 pkg/spark/parser_test.go create mode 100644 pkg/spark/paths.go create mode 100644 pkg/spark/paths_test.go create mode 100644 pkg/spark/remote.go create mode 100644 pkg/spark/select.go create mode 100644 pkg/spark/select_test.go create mode 100644 pkg/spark/ssh_runner.go create mode 100644 pkg/spark/ssh_runner_test.go create mode 100644 pkg/spark/types.go diff --git a/brevcloud-scripts/brevd/README.md b/brevcloud-scripts/brevd/README.md new file mode 100644 index 00000000..f3cbb461 --- /dev/null +++ b/brevcloud-scripts/brevd/README.md @@ -0,0 +1,76 @@ +# brevd service artifacts + +This directory contains minimal assets for running the DevPlane `brevd` daemon as a background service on Linux hosts. + +## Files + +- `systemd/brevd.service` - systemd unit referencing `/usr/local/bin/brevd` and `/etc/default/brevd`. +- `install.sh` - helper script to copy the agent binary, drop configuration stubs, and enable the service. + +## Building the `brevd` binary + +Use `go build` from the repo root. For Linux target nodes (including OrbStack), cross-compile for the desired architecture: + +``` +GOOS=linux GOARCH=amd64 go build -o brevd ./cmd/brevd +``` + +Adjust `GOARCH` (e.g., `arm64`) to match the target hardware. The resulting `brevd` binary can be copied directly into the VM or passed to `install.sh`. + +## Environment file (`/etc/default/brevd`) + +The unit loads configuration from `/etc/default/brevd`. Set the same environment variables used when running the binary manually: + +- `BREV_AGENT_BREVCLOUD_URL` +- `BREV_AGENT_REGISTRATION_TOKEN` +- `BREV_AGENT_DISPLAY_NAME` +- `BREV_AGENT_CLOUD_NAME` +- `BREV_AGENT_CLOUD_CRED_ID` +- `BREV_AGENT_STATE_DIR` +- `BREV_AGENT_DEVICE_TOKEN_PATH` +- `BREV_AGENT_HEARTBEAT_INTERVAL` +- `BREV_AGENT_ENABLE_TUNNEL` +- `BREV_AGENT_TUNNEL_SSH_PORT` + +Unset values fall back to the agent defaults (see `internal/agent/config`). + +> **Important:** `BREV_AGENT_BREVCLOUD_URL` must target the agent ingress exposed by the control plane. The public server mounts the Connect handler under `/agent/v1` (see `internal/cmd/devplane/public_server.go`), so your URL should look like `https:///agent/v1`. The agent will append the Connect RPC paths on top of that base. + +## Manual install + +1. Build or download the `brevd` binary. +2. Copy it to `/usr/local/bin/brevd` and ensure it is executable. +3. Copy `systemd/brevd.service` to `/etc/systemd/system/brevd.service`. +4. Create `/etc/default/brevd`, populate the environment variables above, and protect it (`chmod 600`). +5. Reload systemd: `sudo systemctl daemon-reload`. +6. Enable and start: `sudo systemctl enable --now brevd`. + +The `install.sh` script automates these steps and can be re-run safely to update the binary or unit. + +## Monitoring and logs + +- Inspect current status and last few log lines: + + ``` + sudo systemctl status brevd + ``` + +- Stream live logs from the agent: + + ``` + sudo journalctl -u brevd -f + ``` + +- Show logs for a specific boot/session or time window (example: last hour): + + ``` + sudo journalctl -u brevd --since "1 hour ago" + ``` + +- If you installed using `install.sh`, the environment file resides at `/etc/default/brevd`. You can check the active configuration with: + + ``` + sudo cat /etc/default/brevd + ``` + +These commands work the same on OrbStack VMs and physical Linux hosts. diff --git a/brevcloud-scripts/brevd/enroll.sh b/brevcloud-scripts/brevd/enroll.sh new file mode 100644 index 00000000..04e9eab3 --- /dev/null +++ b/brevcloud-scripts/brevd/enroll.sh @@ -0,0 +1,159 @@ +#!/usr/bin/env bash +set -euo pipefail + +LOG_DIR=/tmp/brevd +LOG_FILE="${LOG_DIR}/enroll.log" +ENV_FILE="/etc/default/brevd" +SERVICE="brevd" +MOCK=%t + +log() { + local msg="$1" + mkdir -p "${LOG_DIR}" + logger -t brev-enroll "${msg}" 2>/dev/null || true + printf '%s\n' "${msg}" >>"${LOG_FILE}" +} + +run_cmd() { + local cmd="$1" + bash -c "${cmd}" >>"${LOG_FILE}" 2>&1 +} + +ensure_dir() { + local dir="$1" + local cmds=( + "sudo -n mkdir -p \"${dir}\"" + "sudo mkdir -p \"${dir}\"" + "mkdir -p \"${dir}\"" + ) + for c in "${cmds[@]}"; do + if run_cmd "${c}"; then + return 0 + fi + done + log "failed to create dir ${dir}" + exit 1 +} + +write_file() { + local content="$1" + local dest="$2" + local tmp="${dest}.tmp" + + printf '%s\n' "${content}" >"${tmp}" + + local cmds=( + "sudo -n tee \"${dest}\" >/dev/null" + "sudo tee \"${dest}\" >/dev/null" + "tee \"${dest}\" >/dev/null" + ) + for c in "${cmds[@]}"; do + if bash -c "${c}" <"${tmp}" >>"${LOG_FILE}" 2>&1; then + rm -f "${tmp}" + return 0 + fi + done + + log "failed to write file to ${dest}" + rm -f "${tmp}" + exit 1 +} + +set_file_mode() { + local mode="$1" + local path="$2" + local cmds=( + "sudo -n chmod ${mode} \"${path}\"" + "sudo chmod ${mode} \"${path}\"" + "chmod ${mode} \"${path}\"" + ) + for c in "${cmds[@]}"; do + if run_cmd "${c}"; then + return 0 + fi + done + log "failed to chmod ${mode} ${path}" + exit 1 +} + +extract_env_value() { + # Extracts an env value from a string containing KEY=VALUE lines. + # Leaves quoting intact only long enough to strip it; always succeeds. + local key="$1" + local content="$2" + while IFS= read -r line; do + # Skip comments and blank lines. + if [[ -z "${line}" || "${line}" =~ ^[[:space:]]*# ]]; then + continue + fi + # Trim leading spaces. + line="${line#"${line%%[![:space:]]*}"}" + if [[ "${line}" == "${key}="* ]]; then + local val="${line#${key}=}" + # Strip matching quotes. + if [[ "${val}" =~ ^\".*\"$ || "${val}" =~ ^\'.*\'$ ]]; then + val="${val:1:${#val}-2}" + fi + printf '%s' "${val}" + break + fi + done <<<"${content}" + return 0 +} + +restart_service() { + local svc="$1" + local cmds=( + "sudo -n systemctl restart \"${svc}\"" + "sudo systemctl restart \"${svc}\"" + "systemctl restart \"${svc}\"" + ) + for c in "${cmds[@]}"; do + if run_cmd "${c}"; then + return 0 + fi + done + log "failed to restart service ${svc}" + exit 1 +} + +# Populate from caller (brev CLI) via string substitution. +ENV_CONTENT=$(cat <<'EOF' +%s +EOF +) + +log "enroll start" +log "probe: $(uname -a)" +log "user: $(whoami)" +log "host: $(hostname)" +log "env file target: ${ENV_FILE}" + +ensure_dir "$(dirname "${ENV_FILE}")" +write_file "${ENV_CONTENT}" "${ENV_FILE}" +set_file_mode 600 "${ENV_FILE}" + +state_dir="$(extract_env_value "BREV_AGENT_STATE_DIR" "${ENV_CONTENT}")" +device_token_path="$(extract_env_value "BREV_AGENT_DEVICE_TOKEN_PATH" "${ENV_CONTENT}")" + +if [[ -n "${state_dir}" ]]; then + log "ensuring state dir ${state_dir}" + ensure_dir "${state_dir}" +fi + +if [[ -n "${device_token_path}" ]]; then + log "ensuring device token parent dir for ${device_token_path}" + ensure_dir "$(dirname "${device_token_path}")" +elif [[ -n "${state_dir}" ]]; then + # Default device token lives under the state dir. + ensure_dir "${state_dir}" +fi + +if [[ "${MOCK}" == "true" ]]; then + log "mock mode: skipping agent restart" + exit 0 +fi + +restart_service "${SERVICE}" +log "enroll success" + diff --git a/brevcloud-scripts/brevd/install.sh b/brevcloud-scripts/brevd/install.sh new file mode 100644 index 00000000..a37def7e --- /dev/null +++ b/brevcloud-scripts/brevd/install.sh @@ -0,0 +1,141 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" + +BIN_SOURCE_DEFAULT="${ROOT_DIR}/brevd" +BIN_TARGET_DEFAULT="/usr/local/bin/brevd" +ENV_FILE_DEFAULT="/etc/default/brevd" +UNIT_SOURCE="${SCRIPT_DIR}/systemd/brevd.service" +UNIT_TARGET_DEFAULT="/etc/systemd/system/brevd.service" +AUTO_ENABLE=true + +usage() { + cat <&2 + exit 1 + fi +} + +ensure_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Missing required command: $1" >&2 + exit 1 + fi +} + +BIN_SOURCE="${BIN_SOURCE_DEFAULT}" +BIN_TARGET="${BIN_TARGET_DEFAULT}" +ENV_FILE="${ENV_FILE_DEFAULT}" +UNIT_TARGET="${UNIT_TARGET_DEFAULT}" + +while [[ $# -gt 0 ]]; do + case "$1" in + --binary) + BIN_SOURCE="$2" + shift 2 + ;; + --bin-target) + BIN_TARGET="$2" + shift 2 + ;; + --env-file) + ENV_FILE="$2" + shift 2 + ;; + --unit-path) + UNIT_TARGET="$2" + shift 2 + ;; + --skip-enable) + AUTO_ENABLE=false + shift 1 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage + exit 1 + ;; + esac +done + +main() { + require_root + ensure_cmd install + ensure_cmd systemctl + + if [[ ! -f "${BIN_SOURCE}" ]]; then + echo "brevd binary not found at ${BIN_SOURCE}" >&2 + exit 1 + fi + + echo "Installing brevd binary to ${BIN_TARGET}" + install -m 0755 "${BIN_SOURCE}" "${BIN_TARGET}" + + if [[ ! -f "${UNIT_SOURCE}" ]]; then + echo "unit file missing at ${UNIT_SOURCE}" >&2 + exit 1 + fi + + echo "Installing systemd unit to ${UNIT_TARGET}" + install -m 0644 "${UNIT_SOURCE}" "${UNIT_TARGET}" + + if [[ ! -f "${ENV_FILE}" ]]; then + echo "Creating default env file at ${ENV_FILE}" + cat <<"EOF" > "${ENV_FILE}" +# Env vars consumed by brevd. Replace placeholder values before starting. +BREV_AGENT_BREVCLOUD_URL="https://controlplane.example.com/agent/v1" +BREV_AGENT_REGISTRATION_TOKEN="replace-me" +BREV_AGENT_CLOUD_CRED_ID="replace-me" +# Optional overrides: +# BREV_AGENT_DISPLAY_NAME="my-node" +# BREV_AGENT_CLOUD_NAME="edge-cluster-a" +# BREV_AGENT_STATE_DIR="/var/lib/brevd" +# BREV_AGENT_DEVICE_TOKEN_PATH="/var/lib/brevd/device_token" +# BREV_AGENT_HEARTBEAT_INTERVAL="30s" +# BREV_AGENT_ENABLE_TUNNEL="true" +# BREV_AGENT_TUNNEL_SSH_PORT="22" +EOF + else + echo "Env file already exists at ${ENV_FILE}; leaving as-is" + fi + chmod 600 "${ENV_FILE}" + + echo "Reloading systemd units" + systemctl daemon-reload + + if [[ "${AUTO_ENABLE}" == true ]]; then + echo "Enabling and starting brevd" + systemctl enable --now "$(basename "${UNIT_TARGET}")" + else + echo "Skipping enable/start per --skip-enable" + fi + + echo "Done." +} + +main "$@" + diff --git a/brevcloud-scripts/brevd/systemd/brevd.service b/brevcloud-scripts/brevd/systemd/brevd.service new file mode 100644 index 00000000..452ebe59 --- /dev/null +++ b/brevcloud-scripts/brevd/systemd/brevd.service @@ -0,0 +1,17 @@ +[Unit] +Description=Brev Daemon +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +EnvironmentFile=-/etc/default/brevd +ExecStart=/usr/local/bin/brevd +Restart=on-failure +RestartSec=10s +User=root +Group=root + +[Install] +WantedBy=multi-user.target + diff --git a/go.mod b/go.mod index d880d282..fc8d8fe9 100644 --- a/go.mod +++ b/go.mod @@ -1,161 +1,327 @@ 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.1665 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 ( + ariga.io/atlas v0.31.1-0.20250212144724-069be8033e83 // indirect + buf.build/gen/go/brevdev/protoc-gen-gotag/protocolbuffers/go v1.36.11-20220906235457-8b4922735da5.1 // indirect + connectrpc.com/otelconnect v0.7.0 // 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 + entgo.io/ent v0.14.4 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/DataDog/datadog-agent/comp/core/tagger/origindetection v0.67.0 // indirect + github.com/DataDog/datadog-agent/pkg/obfuscate v0.67.0 // indirect + github.com/DataDog/datadog-agent/pkg/proto v0.67.0 // indirect + github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.69.0 // indirect + github.com/DataDog/datadog-agent/pkg/trace v0.67.0 // indirect + github.com/DataDog/datadog-agent/pkg/util/log v0.67.0 // indirect + github.com/DataDog/datadog-agent/pkg/util/scrubber v0.67.0 // indirect + github.com/DataDog/datadog-agent/pkg/version v0.67.0 // indirect + github.com/DataDog/datadog-go/v5 v5.6.0 // indirect + github.com/DataDog/dd-trace-go/v2 v2.3.0 // indirect + github.com/DataDog/go-libddwaf/v4 v4.3.2 // indirect + github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20250721125240-fdf1ef85b633 // indirect + github.com/DataDog/go-sqllexer v0.1.6 // indirect + github.com/DataDog/go-tuf v1.1.0-0.5.2 // indirect + github.com/DataDog/gostackparse v0.7.0 // indirect + github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes v0.27.0 // indirect + github.com/DataDog/sketches-go v1.4.7 // indirect + github.com/Masterminds/semver/v3 v3.3.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.1.5 // indirect + github.com/agext/levenshtein v1.2.1 // indirect + github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect + github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect + github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 // indirect + github.com/aws/aws-sdk-go-v2 v1.39.2 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect + github.com/aws/aws-sdk-go-v2/config v1.31.11 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.18.15 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25 // indirect + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.31.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.6 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6 // indirect + github.com/aws/aws-sdk-go-v2/service/pricing v1.24.6 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sqs v1.31.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.29.5 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 // indirect + github.com/aws/smithy-go v1.23.0 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect + github.com/bmatcuk/doublestar v1.3.4 // indirect + github.com/bojanz/currency v1.3.1 // indirect + github.com/brevdev/cloud v0.0.0-20251215210807-d272e4e4e8ff // indirect + github.com/brevdev/verb v0.3.17 // indirect github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 // indirect github.com/cloudflare/circl v1.6.1 // indirect + github.com/cloudflare/cloudflare-go v0.94.0 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect + github.com/cockroachdb/apd/v3 v3.2.1 // indirect github.com/cyphar/filepath-securejoin v0.3.6 // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/ebitengine/purego v0.8.3 // 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/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect + github.com/felixge/httpsnoop v1.0.4 // 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/gliderlabs/ssh v0.3.8 // 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-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-openapi/inflect v0.19.0 // 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-ozzo/ozzo-validation/v4 v4.3.0 // 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/go-viper/mapstructure/v2 v2.4.0 // 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/golang/mock v1.7.0-rc.1 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/go-github/v45 v45.2.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect + github.com/hashicorp/hcl v1.0.1-vault-5 // indirect + github.com/hashicorp/hcl/v2 v2.13.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/klauspost/compress v1.18.0 // 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/logrusorgru/aurora v2.0.3+incompatible // indirect + github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mitchellh/go-wordwrap v1.0.0 // 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/nexus-rpc/sdk-go v0.3.0 // indirect + github.com/ns1/ns1-go v2.4.0+incompatible // indirect + github.com/outcaste-io/ristretto v0.2.3 // indirect + github.com/pborman/uuid v1.2.1 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect github.com/pjbgf/sha1cd v0.3.2 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect + github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect + github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect + github.com/redis/go-redis/v9 v9.7.3 // indirect + github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3 // indirect + github.com/robfig/cron v1.2.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/secure-systems-lab/go-securesystemslib v0.9.0 // indirect + github.com/segmentio/ksuid v1.0.4 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/shirou/gopsutil/v4 v4.25.3 // 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/spaolacci/murmur3 v1.1.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/srikrsna/protoc-gen-gotag v0.6.2 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/stripe/stripe-go/v79 v79.2.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/theckman/httpforwarded v0.4.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/tinylib/msgp v1.2.5 // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect + github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.2 // indirect + github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.0 // indirect + github.com/uptrace/opentelemetry-go-extra/otelzap v0.3.0 // indirect + github.com/vertoforce/go-emailrep v0.0.0-20220319001959-76d0921a0d9c // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + github.com/xeonx/timeago v1.0.0-rc5 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + github.com/zclconf/go-cty v1.14.4 // indirect + github.com/zclconf/go-cty-yaml v1.1.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/collector/component v1.31.0 // indirect + go.opentelemetry.io/collector/featuregate v1.31.0 // indirect + go.opentelemetry.io/collector/internal/telemetry v0.125.0 // indirect + go.opentelemetry.io/collector/pdata v1.31.0 // indirect + go.opentelemetry.io/collector/semconv v0.125.0 // indirect + go.opentelemetry.io/contrib/bridges/otelzap v0.10.0 // indirect + go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.46.1 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect + go.opentelemetry.io/contrib/processors/baggagecopy v0.11.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 // indirect + go.opentelemetry.io/otel/log v0.14.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.4.0 // indirect + go.temporal.io/api v1.47.0 // indirect + go.temporal.io/sdk v1.33.1 // indirect + go.temporal.io/sdk/contrib/opentelemetry v0.6.0 // indirect + go.uber.org/atomic v1.11.0 // 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 + golang.org/x/mod v0.27.0 // indirect + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect + google.golang.org/grpc v1.75.0 // 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/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/golang/protobuf v1.5.4 // indirect - github.com/google/btree v1.0.1 // indirect - github.com/google/gofuzz v1.2.0 // 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..9fe8f414 100644 --- a/go.sum +++ b/go.sum @@ -1,118 +1,235 @@ +ariga.io/atlas v0.31.1-0.20250212144724-069be8033e83 h1:nX4HXncwIdvQ8/8sIUIf1nyCkK8qdBaHQ7EtzPpuiGE= +ariga.io/atlas v0.31.1-0.20250212144724-069be8033e83/go.mod h1:Oe1xWPuu5q9LzyrWfbZmEZxFYeu4BHTyzfjeW2aZp/w= +braces.dev/errtrace v0.3.0 h1:pzfd6LcWgfWtXLaNFWRnxV/7NP+FSOlIjRLwDuHfPxs= +braces.dev/errtrace v0.3.0/go.mod h1:YQpXdo+u5iimgQdZzFoic8AjedEDncXGpp6/2SfazzI= +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= +connectrpc.com/grpchealth v1.2.0 h1:aHP33Bki+F2jPNI1mFVSFG7v0qJrgmfbg7X7nOdSj0M= +connectrpc.com/grpchealth v1.2.0/go.mod h1:fZos12C4p/ZaZC6OwBGZUM+i/fhnRhv75ax/6V/zIeM= +connectrpc.com/otelconnect v0.7.0 h1:ZH55ZZtcJOTKWWLy3qmL4Pam4RzRWBJFOqTPyAqCXkY= +connectrpc.com/otelconnect v0.7.0/go.mod h1:Bt2ivBymHZHqxvo4HkJ0EwHuUzQN6k2l0oH+mp/8nwc= 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= +entgo.io/ent v0.14.4 h1:/DhDraSLXIkBhyiVoJeSshr4ZYi7femzhj6/TckzZuI= +entgo.io/ent v0.14.4/go.mod h1:aDPE/OziPEu8+OWbzy4UlvWmD2/kbRuWfK2A40hcxJM= +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/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DataDog/datadog-agent/comp/core/tagger/origindetection v0.67.0 h1:2mEwRWvhIPHMPK4CMD8iKbsrYBxeMBSuuCXumQAwShU= +github.com/DataDog/datadog-agent/comp/core/tagger/origindetection v0.67.0/go.mod h1:ejJHsyJTG7NU6c6TDbF7dmckD3g+AUGSdiSXy+ZyaCE= +github.com/DataDog/datadog-agent/pkg/obfuscate v0.67.0 h1:NcvyDVIUA0NbBDbp7QJnsYhoBv548g8bXq886795mCQ= +github.com/DataDog/datadog-agent/pkg/obfuscate v0.67.0/go.mod h1:1oPcs3BUTQhiTkmk789rb7ob105MxNV6OuBa28BdukQ= +github.com/DataDog/datadog-agent/pkg/proto v0.67.0 h1:7dO6mKYRb7qSiXEu7Q2mfeKbhp4hykCAULy4BfMPmsQ= +github.com/DataDog/datadog-agent/pkg/proto v0.67.0/go.mod h1:bKVXB7pxBg0wqXF6YSJ+KU6PeCWKDyJj83kUH1ab+7o= +github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.69.0 h1:/DsN4R+IkC6t1+4cHSfkxzLtDl84rBbPC5Wa9srBAoM= +github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.69.0/go.mod h1:Th2LD/IGid5Rza55pzqGu6nUdOv/Rts6wPwLjTyOSTs= +github.com/DataDog/datadog-agent/pkg/trace v0.67.0 h1:dqt+/nObo0JKyaEqIMZgfqGZbx9TfEHpCkrjQ/zzH7k= +github.com/DataDog/datadog-agent/pkg/trace v0.67.0/go.mod h1:zmZoEtKvOnaKHbJGBKH3a4xuyPrSfBaF0ZE3Q3rCoDw= +github.com/DataDog/datadog-agent/pkg/util/log v0.67.0 h1:xrH15QNqeJZkYoXYi44VCIvGvTwlQ3z2iT2QVTGiT7s= +github.com/DataDog/datadog-agent/pkg/util/log v0.67.0/go.mod h1:dfVLR+euzEyg1CeiExgJQq1c1dod42S6IeiRPj8H7Yk= +github.com/DataDog/datadog-agent/pkg/util/scrubber v0.67.0 h1:aIWF85OKxXGo7rVyqJ7jm7lm2qCQrgyXzYyFuw0T2EQ= +github.com/DataDog/datadog-agent/pkg/util/scrubber v0.67.0/go.mod h1:Lfap5FuM4b/Pw9IrTuAvWBWZEmXOvZhCya3dYv4G8O0= +github.com/DataDog/datadog-agent/pkg/version v0.67.0 h1:TB8H8r+laB1Qdttvvc6XJVyLGxp8E6j2f2Mh5IPbYmQ= +github.com/DataDog/datadog-agent/pkg/version v0.67.0/go.mod h1:kvAw/WbI7qLAsDI2wHabZfM7Cv2zraD3JA3323GEB+8= +github.com/DataDog/datadog-go/v5 v5.6.0 h1:2oCLxjF/4htd55piM75baflj/KoE6VYS7alEUqFvRDw= +github.com/DataDog/datadog-go/v5 v5.6.0/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw= +github.com/DataDog/dd-trace-go/v2 v2.3.0 h1:0Y5kx+Wbod0z8moY0vUbKl6OM0oIV4zAynsVmsq+XT8= +github.com/DataDog/dd-trace-go/v2 v2.3.0/go.mod h1:yFomJ/rqKNLDbS9ohIDibdz8q9GK0MUSSkBdVDCibGA= +github.com/DataDog/go-libddwaf/v4 v4.3.2 h1:YGvW2Of1C4e1yU+p7iibmhN2zEOgi9XEchbhQjBxb/A= +github.com/DataDog/go-libddwaf/v4 v4.3.2/go.mod h1:/AZqP6zw3qGJK5mLrA0PkfK3UQDk1zCI2fUNCt4xftE= +github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20250721125240-fdf1ef85b633 h1:ZRLR9Lbym748e8RznWzmSoK+OfV+8qW6SdNYA4/IqdA= +github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20250721125240-fdf1ef85b633/go.mod h1:YFoTl1xsMzdSRFIu33oCSPS/3+HZAPGpO3oOM96wXCM= +github.com/DataDog/go-sqllexer v0.1.6 h1:skEXpWEVCpeZFIiydoIa2f2rf+ymNpjiIMqpW4w3YAk= +github.com/DataDog/go-sqllexer v0.1.6/go.mod h1:GGpo1h9/BVSN+6NJKaEcJ9Jn44Hqc63Rakeb+24Mjgo= +github.com/DataDog/go-tuf v1.1.0-0.5.2 h1:4CagiIekonLSfL8GMHRHcHudo1fQnxELS9g4tiAupQ4= +github.com/DataDog/go-tuf v1.1.0-0.5.2/go.mod h1:zBcq6f654iVqmkk8n2Cx81E1JnNTMOAx1UEO/wZR+P0= +github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4= +github.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM= +github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes v0.27.0 h1:5US5SqqhfkZkg/E64uvn7YmeTwnudJHtlPEH/LOT99w= +github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes v0.27.0/go.mod h1:VRo4D6rj92AExpVBlq3Gcuol9Nm1bber12KyxRjKGWw= +github.com/DataDog/sketches-go v1.4.7 h1:eHs5/0i2Sdf20Zkj0udVFWuCrXGRFig2Dcfm5rtcTxc= +github.com/DataDog/sketches-go v1.4.7/go.mod h1:eAmQ/EBmtSO+nQp7IZMZVRPT4BQTmIc5RZQ+deGlTPM= +github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= +github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= 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/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= +github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= 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/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/aws/aws-sdk-go-v2 v1.39.2 h1:EJLg8IdbzgeD7xgvZ+I8M1e0fL0ptn/M47lianzth0I= +github.com/aws/aws-sdk-go-v2 v1.39.2/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc= +github.com/aws/aws-sdk-go-v2/config v1.31.11 h1:6QOO1mP0MgytbfKsL/r/gE1P6/c/4pPzrrU3hKxa5fs= +github.com/aws/aws-sdk-go-v2/config v1.31.11/go.mod h1:KzpDsPX/dLxaUzoqM3sN2NOhbQIW4HW/0W8rQA1YFEs= +github.com/aws/aws-sdk-go-v2/credentials v1.18.15 h1:Gqy7/05KEfUSulSvwxnB7t8DuZMR3ShzNcwmTD6HOLU= +github.com/aws/aws-sdk-go-v2/credentials v1.18.15/go.mod h1:VWDWSRpYHjcjURRaQ7NUzgeKFN8Iv31+EOMT/W+bFyc= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9 h1:Mv4Bc0mWmv6oDuSWTKnk+wgeqPL5DRFu5bQL9BGPQ8Y= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9/go.mod h1:IKlKfRppK2a1y0gy1yH6zD+yX5uplJ6UuPlgd48dJiQ= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 h1:se2vOWGD3dWQUtfn4wEjRQJb1HK1XsNIt825gskZ970= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9/go.mod h1:hijCGH2VfbZQxqCDN7bwz/4dzxV+hkyhjawAtdPWKZA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 h1:6RBnKZLkJM4hQ+kN6E7yWFveOTg8NLPHAkqrs4ZPlTU= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9/go.mod h1:V9rQKRmK7AWuEsOMnHzKj8WyrIir1yUJbZxDuZLFvXI= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25 h1:r67ps7oHCYnflpgDy2LZU0MAQtQbYIOqNNnqGO6xQkE= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25/go.mod h1:GrGY+Q4fIokYLtjCVB/aFfCVL6hhGUFl8inD18fDalE= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.31.1 h1:dZXY07Dm59TxAjJcUfNMJHLDI/gLMxTRZefn2jFAVsw= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.31.1/go.mod h1:lVLqEtX+ezgtfalyJs7Peb0uv9dEpAQP5yuq2O26R44= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6 h1:HCpPsWqmYQieU7SS6E9HXfdAMSud0pteVXieJmcpIRI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6/go.mod h1:ngUiVRCco++u+soRRVBIvBZxSMMvOVMXA4PJ36JLfSw= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.6 h1:6tayEze2Y+hiL3kdnEUxSPsP+pJsUfwLSFspFl1ru9Q= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.6/go.mod h1:qVNb/9IOVsLCZh0x2lnagrBwQ9fxajUpXS7OZfIsKn0= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 h1:5r34CgVOD4WZudeEKZ9/iKpiT6cM1JyEROpXjOcdWv8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9/go.mod h1:dB12CEbNWPbzO2uC6QSWHteqOg4JfBVJOojbAoAUb5I= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6 h1:BbGDtTi0T1DYlmjBiCr/le3wzhA37O8QTC5/Ab8+EXk= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6/go.mod h1:hLMJt7Q8ePgViKupeymbqI0la+t9/iYFBjxQCFwuAwI= +github.com/aws/aws-sdk-go-v2/service/pricing v1.24.6 h1:szjboYLF1w4WLtm/UH33NRPSdpXvAk1IXBszp/KTGqk= +github.com/aws/aws-sdk-go-v2/service/pricing v1.24.6/go.mod h1:A8YqLVVssHNWJrTuFSPjeRT2+TqIkXPrFa8c/C8E5pA= +github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0 h1:nyuzXooUNJexRT0Oy0UQY6AhOzxPxhtt4DcBIHyCnmw= +github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0/go.mod h1:sT/iQz8JK3u/5gZkT+Hmr7GzVZehUMkRZpOaAwYXeGY= +github.com/aws/aws-sdk-go-v2/service/sqs v1.31.4 h1:mE2ysZMEeQ3ulHWs4mmc4fZEhOfeY1o6QXAfDqjbSgw= +github.com/aws/aws-sdk-go-v2/service/sqs v1.31.4/go.mod h1:lCN2yKnj+Sp9F6UzpoPPTir+tSaC9Jwf6LcmTqnXFZw= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.5 h1:WwL5YLHabIBuAlEKRoLgqLz1LxTvCEpwsQr7MiW/vnM= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.5/go.mod h1:5PfYspyCU5Vw1wNPsxi15LZovOnULudOQuVxphSflQA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1 h1:5fm5RTONng73/QA73LhCNR7UT9RpFH3hR6HWL6bIgVY= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1/go.mod h1:xBEjWD13h+6nq+z4AkqSfSvqRKFgDIQeaMguAJndOWo= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 h1:p3jIvqYwUZgu/XYeI48bJxOhvm47hZb5HUQ0tn6Q9kA= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.6/go.mod h1:WtKK+ppze5yKPkZ0XwqIVWD4beCwv056ZbPQNoeHqM8= +github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE= +github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= 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/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 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/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0= +github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= +github.com/bojanz/currency v1.3.1 h1:3BUAvy/5hU/Pzqg5nrQslVihV50QG+A2xKPoQw1RKH4= +github.com/bojanz/currency v1.3.1/go.mod h1:jNoZiJyRTqoU5DFoa+n+9lputxPUDa8Fz8BdDrW06Go= +github.com/brevdev/cloud v0.0.0-20251215210807-d272e4e4e8ff h1:bvEIkBovSDRl+RE3pIVbtZMqM5xsoN1m04pP8MO45W8= +github.com/brevdev/cloud v0.0.0-20251215210807-d272e4e4e8ff/go.mod h1:8k/vlciWQVfhAgXRziX/3GuOmQMcP1Q8KwlC1Uptj/o= +github.com/brevdev/dev-plane v0.5.1665 h1:02x+bXlSmQNDYQPWNQonvSJYykIuevh/9+E30Q7dDcA= +github.com/brevdev/dev-plane v0.5.1665/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/brevdev/verb v0.3.17 h1:o373MEFqxYYXPMyDOu/fxHQrrcshnYvA7ymUkolZ+FM= +github.com/brevdev/verb v0.3.17/go.mod h1:TMQ2D4GKD4NhvXmzNz+sACDYqN23BO+qiJiEB4pyzWY= github.com/briandowns/spinner v1.16.0 h1:DFmp6hEaIx2QXXuqSJmtfSBSAjRmpGiKG6ip2Wm/yOs= github.com/briandowns/spinner v1.16.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ= +github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= 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/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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/cihub/seelog v0.0.0-20170130134532-f561c5e57575 h1:kHaBemcxl8o/pQ5VM1c8PVE1PubbNx3mjUr09OqWGCs= +github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575/go.mod h1:9d6lWj8KzO/fd/NrVaLscBKmPigpZpn5YawRPw+e3Yo= 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= +github.com/cloudflare/cloudflare-go v0.94.0 h1:WADmVhCdnn1A9sm5NU08by49Vbh4Lj/JBgTWTr7q7Qc= +github.com/cloudflare/cloudflare-go v0.94.0/go.mod h1:N1u1cLZ4lG6NeezGOWi7P6aq1DK2iVYg9ze7GZbUmZE= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 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/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= +github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= 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= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/ebitengine/purego v0.8.3 h1:K+0AjQp63JEZTEMZiwsI9g0+hAMNohwUOtY0RPGexmc= +github.com/ebitengine/purego v0.8.3/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= 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/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw= +github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA= 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/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +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 +250,50 @@ 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-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= +github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= +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-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= 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= @@ -155,32 +304,27 @@ github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBEx github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 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/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= +github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +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/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= +github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= 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,94 +332,84 @@ 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.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 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.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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/go-github/v45 v45.2.0 h1:5oRLszbrkvxDDqBCNj2hjDZMKmvexaZ1xw/FCD+K3FI= +github.com/google/go-github/v45 v45.2.0/go.mod h1:FObaZJEDSTa/WGCzZ2Z3eoCDXWJKMenWWTrd8jrta28= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.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.0.0/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/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= 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= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= 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-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +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/hashicorp/hcl/v2 v2.13.0 h1:0Apadu1w6M11dyGFxWnmhhcMjkbAiKCv7G1r/2QgCNc= +github.com/hashicorp/hcl/v2 v2.13.0/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0= 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/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= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 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= @@ -283,117 +417,183 @@ github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4 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/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 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/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 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= 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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 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/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 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/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= +github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc= +github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= +github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= +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/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +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/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +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/nexus-rpc/sdk-go v0.3.0 h1:Y3B0kLYbMhd4C2u00kcYajvmOrfozEtTV/nHSnV57jA= +github.com/nexus-rpc/sdk-go v0.3.0/go.mod h1:TpfkM2Cw0Rlk9drGkoiSMpFqflKTiQLWUNyKJjF8mKQ= +github.com/ns1/ns1-go v2.4.0+incompatible h1:WYLNc1preJKfVVJL0zCOXfePlREOwrpJrDhJNn1nnLI= +github.com/ns1/ns1-go v2.4.0+incompatible/go.mod h1:+5cGIDXMoO+J3+C8FJ8J0xkyiTdgCvDA+JXQ7f1cPKs= +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/open-telemetry/opentelemetry-collector-contrib/pkg/sampling v0.125.0 h1:0dOJCEtabevxxDQmxed69oMzSw+gb3ErCnFwFYZFu0M= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/sampling v0.125.0/go.mod h1:QwzQhtxPThXMUDW1XRXNQ+l0GrI2BRsvNhX6ZuKyAds= +github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor v0.125.0 h1:F68/Nbpcvo3JZpaWlRUDJtG7xs8FHBZ7A8GOMauDkyc= +github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor v0.125.0/go.mod h1:haO4cJtAk05Y0p7NO9ME660xxtSh54ifCIIT7+PO9C0= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/outcaste-io/ristretto v0.2.3 h1:AK4zt/fJ76kjlYObOeNwh4T3asEuaCmp26pOvUOL9w0= +github.com/outcaste-io/ristretto v0.2.3/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkLAr0VN/s4+MHac= +github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= +github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= 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= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY= +github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= 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.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= 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/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 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/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg= +github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= +github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= +github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= +github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= +github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= +github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= +github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= +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/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3 h1:4+LEVOB87y175cLJC/mbsgKmoDOjrBldtXvioEy96WY= +github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3/go.mod h1:vl5+MqJ1nBINuSsUI2mGgH79UweUT/B5Fy8857PqyyI= 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 v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= +github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= 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= github.com/schollz/progressbar/v3 v3.9.0/go.mod h1:W5IEwbJecncFGBvuEh4A7HT1nZZ6WNIL2i3qbnI0WKY= +github.com/secure-systems-lab/go-securesystemslib v0.9.0 h1:rf1HIbL64nUpEIZnjLZ3mcNEL9NBPB0iuVjyxvq3LZc= +github.com/secure-systems-lab/go-securesystemslib v0.9.0/go.mod h1:DVHKMcZ+V4/woA/peqr+L0joiRXbPpQ042GgJckkFgw= +github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= +github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sevlyar/go-daemon v0.1.5 h1:Zy/6jLbM8CfqJ4x4RPr7MJlSKt90f00kNM1D401C+Qk= github.com/sevlyar/go-daemon v0.1.5/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE= +github.com/shirou/gopsutil/v4 v4.25.3 h1:SeA68lsu8gLggyMbmCn8cmp97V1TI9ld9sVzAUcKcKE= +github.com/shirou/gopsutil/v4 v4.25.3/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 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/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.5.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +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/srikrsna/protoc-gen-gotag v0.6.2 h1:ULdarjI7FNUA6CNlLPIzSNvjdV2P4C2LSygPLvCVtfA= +github.com/srikrsna/protoc-gen-gotag v0.6.2/go.mod h1:cplWV0ZNBhuF54gnj6rU9pLNrqjXf5vh65Xqa1Kjy+4= 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= @@ -402,30 +602,52 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf 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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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/stripe/stripe-go/v79 v79.2.0 h1:IU27tefsCor2P9ZOBvTn1Ra7WPd52Qz5ymuFQIeBLqg= +github.com/stripe/stripe-go/v79 v79.2.0/go.mod h1:cuH6X0zC8peY6f1AubHwgJ/fJSn2dh5pfiCr6CjyKVU= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/theckman/httpforwarded v0.4.0 h1:N55vGJT+6ojTnLY3LQCNliJC4TW0P0Pkeys1G1WpX2w= +github.com/theckman/httpforwarded v0.4.0/go.mod h1:GVkFynv6FJreNbgH/bpOU9ITDZ7a5WuzdNCtIMI1pVI= +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/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po= +github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= 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= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.2 h1:USRngIQppxeyb39XzkVHXwQesKK0+JSwnHE/1c7fgic= +github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.2/go.mod h1:1frv9RN1rlTq0jzCq+mVuEQisubZCQ4OU6S/8CaHzGY= +github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.0 h1:iIY5MNcGJp3frOx7cPq4Xc6CFthI8pjpHGAeZ230pVM= +github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.0/go.mod h1:aiX/F5+EYbY2ed2OQEYRXzMcNGvI9pip5gW2ZtBDers= +github.com/uptrace/opentelemetry-go-extra/otelzap v0.3.0 h1:sj/jUvwHdJTGPeilGnLcRgTI/OaBcaOqVymBKEmSH54= +github.com/uptrace/opentelemetry-go-extra/otelzap v0.3.0/go.mod h1:hDQ7Ntn6wEgjwtH7xJ1B235xNHhFffl7GHmZqs3WcK8= +github.com/vertoforce/go-emailrep v0.0.0-20220319001959-76d0921a0d9c h1:FUMp0n9BZL+c+j4l99Z6xSvB+4H1s0ZXhMTiCcq9Hbg= +github.com/vertoforce/go-emailrep v0.0.0-20220319001959-76d0921a0d9c/go.mod h1:kXw8KbVnFSZxdpnSdaFLK3XrdBu9QZfUSTlDPgVGMvU= +github.com/vmihailenco/msgpack/v4 v4.3.13 h1:A2wsiTbvp63ilDaWmsk2wjx6xZdxQOvpiNlKBGKKXKI= +github.com/vmihailenco/msgpack/v4 v4.3.13/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc= +github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/wk8/go-ordered-map/v2 v2.0.0 h1:jWOAU/F5AkYb8jr/rkVPe418g7nf2CZBzyfOR4Y7Q1w= github.com/wk8/go-ordered-map/v2 v2.0.0/go.mod h1:fGIuB3GmY3JZP6L3t5riKtaSH9u13IYVYvar5Ee+9lM= github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6FkrCNfXkvbR+Nbu1ls48pXYcw= @@ -434,355 +656,279 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 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/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +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/xeonx/timeago v1.0.0-rc5 h1:pwcQGpaH3eLfPtXeyPA4DmHWjoQt0Ea7/++FwpxqLxg= +github.com/xeonx/timeago v1.0.0-rc5/go.mod h1:qDLrYEFynLO7y5Ho7w3GwgtYgpy5UfhcXIIQvMKVDkA= 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= +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/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= +github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0= +github.com/zclconf/go-cty-yaml v1.1.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/collector/component v1.31.0 h1:9LzU8X1RhV3h8/QsAoTX23aFUfoJ3EUc9O/vK+hFpSI= +go.opentelemetry.io/collector/component v1.31.0/go.mod h1:JbZl/KywXJxpUXPbt96qlEXJSym1zQ2hauMxYMuvlxM= +go.opentelemetry.io/collector/component/componentstatus v0.125.0 h1:zlxGQZYd9kknRZSjRpOYW5SBjl0a5zYFYRPbreobXoU= +go.opentelemetry.io/collector/component/componentstatus v0.125.0/go.mod h1:bHXc2W8bqqo9adOvCgvhcO7pYzJOSpyV4cuQ1wiIl04= +go.opentelemetry.io/collector/component/componenttest v0.125.0 h1:E2mpnMQbkMpYoZ3Q8pHx4kod7kedjwRs1xqDpzCe/84= +go.opentelemetry.io/collector/component/componenttest v0.125.0/go.mod h1:pQtsE1u/SPZdTphP5BZP64XbjXSq6wc+mDut5Ws/JDI= +go.opentelemetry.io/collector/consumer v1.31.0 h1:L+y66ywxLHnAxnUxv0JDwUf5bFj53kMxCCyEfRKlM7s= +go.opentelemetry.io/collector/consumer v1.31.0/go.mod h1:rPsqy5ni+c6xNMUkOChleZYO/nInVY6eaBNZ1FmWJVk= +go.opentelemetry.io/collector/consumer/consumertest v0.125.0 h1:TUkxomGS4DAtjBvcWQd2UY4FDLLEKMQD6iOIDUr/5dM= +go.opentelemetry.io/collector/consumer/consumertest v0.125.0/go.mod h1:vkHf3y85cFLDHARO/cTREVjLjOPAV+cQg7lkC44DWOY= +go.opentelemetry.io/collector/consumer/xconsumer v0.125.0 h1:oTreUlk1KpMSWwuHFnstW+orrjGTyvs2xd3o/Dpy+hI= +go.opentelemetry.io/collector/consumer/xconsumer v0.125.0/go.mod h1:FX0G37r0W+wXRgxxFtwEJ4rlsCB+p0cIaxtU3C4hskw= +go.opentelemetry.io/collector/featuregate v1.31.0 h1:20q7plPQZwmAiaYAa6l1m/i2qDITZuWlhjr4EkmeQls= +go.opentelemetry.io/collector/featuregate v1.31.0/go.mod h1:Y/KsHbvREENKvvN9RlpiWk/IGBK+CATBYzIIpU7nccc= +go.opentelemetry.io/collector/internal/telemetry v0.125.0 h1:6lcGOxw3dAg7LfXTKdN8ZjR+l7KvzLdEiPMhhLwG4r4= +go.opentelemetry.io/collector/internal/telemetry v0.125.0/go.mod h1:5GyFslLqjZgq1DZTtFiluxYhhXrCofHgOOOybodDPGE= +go.opentelemetry.io/collector/pdata v1.31.0 h1:P5WuLr1l2JcIvr6Dw2hl01ltp2ZafPnC4Isv+BLTBqU= +go.opentelemetry.io/collector/pdata v1.31.0/go.mod h1:m41io9nWpy7aCm/uD1L9QcKiZwOP0ldj83JEA34dmlk= +go.opentelemetry.io/collector/pdata/pprofile v0.125.0 h1:Qqlx8w1HpiYZ9RQqjmMQIysI0cHNO1nh3E/fCTeFysA= +go.opentelemetry.io/collector/pdata/pprofile v0.125.0/go.mod h1:p/yK023VxAp8hm27/1G5DPTcMIpnJy3cHGAFUQZGyaQ= +go.opentelemetry.io/collector/pdata/testdata v0.125.0 h1:due1Hl0EEVRVwfCkiamRy5E8lS6yalv0lo8Zl/SJtGw= +go.opentelemetry.io/collector/pdata/testdata v0.125.0/go.mod h1:1GpEWlgdMrd+fWsBk37ZC2YmOP5YU3gFQ4rWuCu9g24= +go.opentelemetry.io/collector/pipeline v0.125.0 h1:oitBgcAFqntDB4ihQJUHJSQ8IHqKFpPkaTVbTYdIUzM= +go.opentelemetry.io/collector/pipeline v0.125.0/go.mod h1:TO02zju/K6E+oFIOdi372Wk0MXd+Szy72zcTsFQwXl4= +go.opentelemetry.io/collector/processor v1.31.0 h1:+u7sBUpnCBsHYoALp4hfr9VEjLHHYa4uKENGITe0K9Q= +go.opentelemetry.io/collector/processor v1.31.0/go.mod h1:5hDYJ7/hTdfd2tF2Rj5Hs6+mfyFz2O7CaPzVvW1qHQc= +go.opentelemetry.io/collector/processor/processorhelper v0.125.0 h1:QRpX7oFW88DAZhy+Q93npklRoaQr8ue0GKpeup7C/Fk= +go.opentelemetry.io/collector/processor/processorhelper v0.125.0/go.mod h1:oXRvslUuN62wErcoJrcEJYoTXu5wHyNyJsE+/a9Cc9s= +go.opentelemetry.io/collector/processor/processortest v0.125.0 h1:ZVAN4iZPDcWhpzKqnuok2NIuS5hwGVVQUOWkJFR12tA= +go.opentelemetry.io/collector/processor/processortest v0.125.0/go.mod h1:VAw0IRG35cWTBjBtreXeXJEgqkRegfjrH/EuLhNX2+I= +go.opentelemetry.io/collector/processor/xprocessor v0.125.0 h1:VWYPMW1VmDq6xB7M5SYjBpQCCIq3MhQ3W++wU47QpZM= +go.opentelemetry.io/collector/processor/xprocessor v0.125.0/go.mod h1:bCxUyFVlksANg8wjYZqWVsRB33lkLQ294rTrju/IZiM= +go.opentelemetry.io/collector/semconv v0.125.0 h1:SyRP617YGvNSWRSKMy7Lbk9RaJSR+qFAAfyxJOeZe4s= +go.opentelemetry.io/collector/semconv v0.125.0/go.mod h1:te6VQ4zZJO5Lp8dM2XIhDxDiL45mwX0YAQQWRQ0Qr9U= +go.opentelemetry.io/contrib/bridges/otelzap v0.10.0 h1:ojdSRDvjrnm30beHOmwsSvLpoRF40MlwNCA+Oo93kXU= +go.opentelemetry.io/contrib/bridges/otelzap v0.10.0/go.mod h1:oTTm4g7NEtHSV2i/0FeVdPaPgUIZPfQkFbq0vbzqnv0= +go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.46.1 h1:PGmSzEMllKQwBQHe9SERAsCytvgLhsb8OrRLeW+40xw= +go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.46.1/go.mod h1:h0dNRrQsnnlMonPE/+FXrXtDYZEyZSTaIOfs+n8P/RQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= +go.opentelemetry.io/contrib/processors/baggagecopy v0.11.0 h1:kCgcpaw83eiQq3q9kC0mlSF+2/GFj979aphWGlHmxRw= +go.opentelemetry.io/contrib/processors/baggagecopy v0.11.0/go.mod h1:HA84H6DSS0J6sbXzDj8bjmrooSK1UhPZvh3Dijltw5A= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0= +go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM= +go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno= +go.opentelemetry.io/otel/log/logtest v0.14.0 h1:BGTqNeluJDK2uIHAY8lRqxjVAYfqgcaTbVk1n3MWe5A= +go.opentelemetry.io/otel/log/logtest v0.14.0/go.mod h1:IuguGt8XVP4XA4d2oEEDMVDBBCesMg8/tSGWDjuKfoA= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg= +go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg= +go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY= 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.temporal.io/api v1.47.0 h1:0pg8wZC9Jv79iMpe6jXMPQzADQJ5OiPuklYfC51bXGM= +go.temporal.io/api v1.47.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= +go.temporal.io/sdk v1.33.1 h1:eZx3frTgCVWL4pubVVg2Ok+xjfyJiAvjAN7102JwXxs= +go.temporal.io/sdk v1.33.1/go.mod h1:WwCmJZLy7zabz3ar5NRAQEygsdP8tgR9sDjISSHuWZw= +go.temporal.io/sdk/contrib/opentelemetry v0.6.0 h1:rNBArDj5iTUkcMwKocUShoAW59o6HdS7Nq4CTp4ldj8= +go.temporal.io/sdk/contrib/opentelemetry v0.6.0/go.mod h1:Lem8VrE2ks8P+FYcRM3UphPoBr+tfM3v/Kaf0qStzSg= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +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/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= 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.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +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.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +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-20190820162420-60c769a6c586/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/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.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= 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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 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.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/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-20201204225414-ed752295db88/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-20210330210617-4fbd30eecc44/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-20210510120138-977fb7262007/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-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-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/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-20220627191245-f75cf1eec38b/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.5/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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +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-20191108193012-7d206e10da11/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.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +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= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= 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/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= 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-20200423170343-7949de9c1215/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/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= 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/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= 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.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/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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= @@ -794,42 +940,37 @@ 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.0-20210107192922-496545a6307b/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= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= 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..48261686 --- /dev/null +++ b/pkg/brevcloud/client.go @@ -0,0 +1,216 @@ +package brevcloud + +import ( + "context" + "fmt" + "net/http" + "strings" + "time" + + "connectrpc.com/connect" + + brevapiv2connect "buf.build/gen/go/brevdev/devplane/connectrpc/go/brevapi/v2/brevapiv2connect" + brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" + "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 + 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 +} + +// ListCloudCred is a placeholder for future integration with a BrevCloud +// cloud credential listing API. Today it returns an error so callers can +// fall back to explicit --cloud-cred-id flags. +func (c *Client) ListCloudCred(ctx context.Context) ([]CloudCred, error) { + _ = ctx + return nil, fmt.Errorf("ListCloudCred not implemented") +} + +// 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..c35bff03 --- /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 +} \ No newline at end of file diff --git a/pkg/brevdaemon/agent/README.md b/pkg/brevdaemon/agent/README.md new file mode 100644 index 00000000..e69de29b diff --git a/pkg/brevdaemon/agent/agent.go b/pkg/brevdaemon/agent/agent.go new file mode 100644 index 00000000..c5bffd23 --- /dev/null +++ b/pkg/brevdaemon/agent/agent.go @@ -0,0 +1,240 @@ +package agent + +import ( + "context" + "sync" + "time" + + "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" +) + +// 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 client.HeartbeatStatus +} + +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: client.NodePhaseActive, + LastTransitionTime: time.Now(), + }) + defaultStatus := client.HeartbeatStatus{ + Phase: client.NodePhaseActive, + } + statusUpdates := make(chan client.HeartbeatStatus, 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 +} + +// Run preserves the legacy entrypoint used by cmd/devplane/agent.go so we do +// not break existing workflows while the new standalone agent binary is built. +func Run(ctx context.Context) error { + agentLogger := zap.L().Named("legacy-agent") + cfg, err := agentconfig.Load() + if err != nil { + return errors.WrapAndTrace(err) + } + a, err := NewAgent(cfg, agentLogger) + if err != nil { + return errors.WrapAndTrace(err) + } + if err := a.Run(ctx); err != nil { + return errors.WrapAndTrace(err) + } + 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) client.HeartbeatStatus { + hbStatus := client.HeartbeatStatus{ + Phase: status.Phase, + Detail: status.Detail, + } + if !status.LastTransitionTime.IsZero() { + t := status.LastTransitionTime + hbStatus.LastTransitionTime = &t + } + 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..8b29619f --- /dev/null +++ b/pkg/brevdaemon/agent/agent_test.go @@ -0,0 +1,201 @@ +package agent + +import ( + "context" + "errors" + "path/filepath" + "testing" + "time" + + "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) (client.BrevCloudAgentClient, 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, _ client.BrevCloudAgentClient, _ *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, client.RegisterParams) (client.RegisterResult, error) { + return client.RegisterResult{}, nil +} + +func (s *stubBrevCloudClient) Heartbeat(context.Context, client.HeartbeatParams) (client.HeartbeatResult, error) { + return client.HeartbeatResult{}, nil +} + +func (s *stubBrevCloudClient) GetTunnelToken(context.Context, client.TunnelTokenParams) (client.TunnelTokenResult, error) { + return client.TunnelTokenResult{}, 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..f43f9860 --- /dev/null +++ b/pkg/brevdaemon/agent/client/client.go @@ -0,0 +1,647 @@ +package client + +import ( + "context" + "math" + "net/http" + "time" + + brevapiv2connect "buf.build/gen/go/brevdev/devplane/connectrpc/go/brevapi/v2/brevapiv2connect" + brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" + devplaneapiv1 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/devplaneapi/v1" + "connectrpc.com/connect" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/config" + "github.com/brevdev/brev-cli/pkg/brevdaemon/provider" + "github.com/brevdev/brev-cli/pkg/errors" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// BrevCloudAgentClient defines the BrevCloud agent RPCs exercised by the brev-agent binary. +type BrevCloudAgentClient interface { + Register(ctx context.Context, req RegisterParams) (RegisterResult, error) + Heartbeat(ctx context.Context, req HeartbeatParams) (HeartbeatResult, error) + GetTunnelToken(ctx context.Context, req TunnelTokenParams) (TunnelTokenResult, error) +} + +// RegisterParams carries the payload for BrevCloudAgentService.Register. +type RegisterParams struct { + RegistrationToken string + DisplayName string + CloudName string + Capabilities []string + Hardware *HardwareInfo + AgentVersion string + HardwareFingerprint string + DeviceFingerprintHash string +} + +// RegisterResult captures the subset of response fields the agent cares about. +type RegisterResult struct { + BrevCloudNodeID string + DeviceToken string + HeartbeatInterval time.Duration + DisplayName string + CloudName string + CloudCredID string + DeviceFingerprint string +} + +// HeartbeatParams drives BrevCloudAgentService.Heartbeat. +type HeartbeatParams struct { + BrevCloudNodeID string + DeviceToken string + ObservedAt time.Time + Status *HeartbeatStatus + Utilization *UtilizationInfo + AgentVersion string + DisplayName string + CloudName string + DeviceFingerprintHash string + HardwareFingerprint string +} + +// HeartbeatStatus mirrors the minimal metadata fields surfaced by the proto. +type HeartbeatStatus struct { + Phase NodePhase + Detail string + LastTransitionTime *time.Time +} + +// HeartbeatResult returns server guidance after a heartbeat. +type HeartbeatResult struct { + ServerTime time.Time + NextHeartbeatInterval time.Duration + NodeConfig *brevapiv2.BrevCloudNodeConfig + Commands []*brevapiv2.BrevCloudCommand +} + +// TunnelTokenParams drives BrevCloudAgentService.GetTunnelToken. +type TunnelTokenParams struct { + BrevCloudNodeID string + DeviceToken string + TunnelName string + Ports []provider.TunnelPortMapping + AppIngresses []AppIngress +} + +// AppIngress describes a single HTTP ingress request from the agent to the control plane. +type AppIngress struct { + AppID string + Protocol string + LocalPort int + RemotePort int + HostnamePrefix string + PathPrefix string + ForceHTTPS bool +} + +// TunnelTokenResult returns tunnel connection metadata. +type TunnelTokenResult struct { + Token string + Endpoint string + TTL time.Duration + ExpiresAt *time.Time + SecondsToExp *time.Duration + PortMappings []*brevapiv2.TunnelPortMapping +} + +// HardwareInfo is the DTO the agent uses when registering. +type HardwareInfo struct { + CPUCount int + RAMBytes int64 + GPUs []GPUInfo + MachineModel string + Architecture string + Storage []StorageInfo +} + +// GPUInfo captures high-level GPU specs. +type GPUInfo struct { + Model string + MemoryBytes int64 + Count int +} + +// StorageInfo captures block device capacity for registration. +type StorageInfo struct { + Name string + Capacity int64 + Type string +} + +// UtilizationInfo wraps the runtime metrics included in heartbeats. +type UtilizationInfo struct { + CPUPercent float32 + MemoryUsedBytes int64 + MemoryTotalBytes int64 + DiskPercent float32 + DiskUsedBytes int64 + DiskTotalBytes int64 + GPUs []GPUUtilization +} + +// GPUUtilization mirrors the proto payload for GPU metrics. +type GPUUtilization struct { + Index int + Model string + UtilizationPercent float32 + MemoryUsedBytes int64 + MemoryTotalBytes int64 + TemperatureCelsius *float32 +} + +// NodePhase aligns with devplaneapi.v1.BrevCloudNodePhase without exposing proto enums to callers. +type NodePhase int + +const ( + NodePhaseUnspecified NodePhase = iota + NodePhaseWaitingForRegistration + NodePhaseActive + NodePhaseOffline + NodePhaseStopped + NodePhaseError +) + +// 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 + } +} + +// 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 +} + +// New constructs a BrevCloudAgentClient backed by the Connect RPC client. +func New(cfg config.Config, opts ...Option) (BrevCloudAgentClient, 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 + } + + var rpcClient brevapiv2connect.BrevCloudAgentServiceClient + if merged.customRPC != nil { + rpcClient = merged.customRPC + } else { + rpcClient = brevapiv2connect.NewBrevCloudAgentServiceClient(httpClient, cfg.BrevCloudAgentURL, merged.clientOpts...) + } + + return &brevcloudAgentClient{ + rpc: rpcClient, + }, nil +} + +type brevcloudAgentClient struct { + rpc brevapiv2connect.BrevCloudAgentServiceClient +} + +func (c *brevcloudAgentClient) Register(ctx context.Context, params RegisterParams) (RegisterResult, error) { + if params.RegistrationToken == "" { + return RegisterResult{}, errors.Errorf("registration token is required") + } + if params.DeviceFingerprintHash == "" { + return RegisterResult{}, errors.Errorf("device fingerprint hash is required") + } + + req := &brevapiv2.RegisterRequest{ + RegistrationToken: params.RegistrationToken, + Capabilities: params.Capabilities, + Hardware: hardwareInfoToProto(params.Hardware), + DeviceFingerprintHash: params.DeviceFingerprintHash, + HardwareFingerprint: params.HardwareFingerprint, + } + if params.DisplayName != "" { + req.DisplayName = protoString(params.DisplayName) + } + if params.CloudName != "" { + req.CloudName = protoString(params.CloudName) + } + if params.AgentVersion != "" { + req.Agent = &brevapiv2.AgentInfo{ + Version: params.AgentVersion, + } + } + + resp, err := c.rpc.Register(ctx, connect.NewRequest(req)) + if err != nil { + return RegisterResult{}, classifyError(err) + } + + result := RegisterResult{ + BrevCloudNodeID: resp.Msg.GetBrevCloudNodeId(), + DeviceToken: resp.Msg.GetDeviceToken(), + DisplayName: resp.Msg.GetDisplayName(), + CloudName: resp.Msg.GetCloudName(), + CloudCredID: resp.Msg.GetCloudCredId(), + DeviceFingerprint: resp.Msg.GetDeviceFingerprint(), + } + if interval := resp.Msg.GetHeartbeatInterval(); interval != nil { + result.HeartbeatInterval = interval.AsDuration() + } + return result, nil +} + +func (c *brevcloudAgentClient) Heartbeat(ctx context.Context, params HeartbeatParams) (HeartbeatResult, error) { + if params.BrevCloudNodeID == "" { + return HeartbeatResult{}, errors.Errorf("brevcloud node id is required") + } + if params.DeviceToken == "" { + return HeartbeatResult{}, errors.Errorf("device token is required") + } + + req := &brevapiv2.HeartbeatRequest{ + BrevCloudNodeId: params.BrevCloudNodeID, + Utilization: utilizationToProto(params.Utilization), + } + + if !params.ObservedAt.IsZero() { + req.ObservedAt = timestamppbNew(params.ObservedAt) + } + if params.AgentVersion != "" { + req.Agent = &brevapiv2.AgentInfo{ + Version: params.AgentVersion, + } + } + if params.Status != nil { + req.Status = heartbeatStatusToProto(params.Status) + } + if params.DisplayName != "" { + req.DisplayName = protoString(params.DisplayName) + } + if params.CloudName != "" { + req.CloudName = protoString(params.CloudName) + } + if params.DeviceFingerprintHash != "" { + req.DeviceFingerprintHash = params.DeviceFingerprintHash + } + if params.HardwareFingerprint != "" { + req.HardwareFingerprint = params.HardwareFingerprint + } + + connectReq := connect.NewRequest(req) + connectReq.Header().Set("Authorization", bearerToken(params.DeviceToken)) + + resp, err := c.rpc.Heartbeat(ctx, connectReq) + if err != nil { + return HeartbeatResult{}, classifyError(err) + } + + result := HeartbeatResult{ + NodeConfig: resp.Msg.GetNodeConfig(), + Commands: resp.Msg.GetCommands(), + } + if ts := resp.Msg.GetServerTime(); ts != nil { + result.ServerTime = ts.AsTime() + } + if interval := resp.Msg.GetNextHeartbeatInterval(); interval != nil { + result.NextHeartbeatInterval = interval.AsDuration() + } + return result, nil +} + +func (c *brevcloudAgentClient) GetTunnelToken(ctx context.Context, params TunnelTokenParams) (TunnelTokenResult, error) { + if params.BrevCloudNodeID == "" { + return TunnelTokenResult{}, errors.Errorf("brevcloud node id is required") + } + if params.DeviceToken == "" { + return TunnelTokenResult{}, errors.Errorf("device token is required") + } + + req := &brevapiv2.GetTunnelTokenRequest{ + BrevCloudNodeId: params.BrevCloudNodeID, + RequestedPorts: tunnelPortsToProto(params.Ports), + } + if params.TunnelName != "" { + req.TunnelName = protoString(params.TunnelName) + } + if ingresses := appIngressesToProto(params.AppIngresses); len(ingresses) > 0 { + req.AppIngresses = ingresses + } + + connectReq := connect.NewRequest(req) + connectReq.Header().Set("Authorization", bearerToken(params.DeviceToken)) + + resp, err := c.rpc.GetTunnelToken(ctx, connectReq) + if err != nil { + return TunnelTokenResult{}, classifyError(err) + } + + result := TunnelTokenResult{ + Token: resp.Msg.GetToken(), + Endpoint: resp.Msg.GetEndpoint(), + PortMappings: resp.Msg.GetPortMappings(), + } + if ttl := resp.Msg.GetTtl(); ttl != nil { + result.TTL = ttl.AsDuration() + } + if expires := resp.Msg.GetExpiresAt(); expires != nil { + t := expires.AsTime() + result.ExpiresAt = &t + if now := time.Now(); t.After(now) { + d := t.Sub(now) + result.SecondsToExp = &d + } + } + return result, nil +} + +func hardwareInfoToProto(info *HardwareInfo) *brevapiv2.HardwareInfo { + if info == nil { + return nil + } + + cpuCount := clampToInt32(info.CPUCount) + out := &brevapiv2.HardwareInfo{ + CpuCount: cpuCount, + } + if info.RAMBytes > 0 { + out.RamBytes = bytesValue(info.RAMBytes) + } + if info.MachineModel != "" { + out.SystemModel = protoString(info.MachineModel) + } + if info.Architecture != "" { + out.Architecture = protoString(info.Architecture) + } + if len(info.Storage) > 0 { + out.Storage = make([]*brevapiv2.StorageInfo, 0, len(info.Storage)) + for _, s := range info.Storage { + entry := &brevapiv2.StorageInfo{ + Name: s.Name, + Type: s.Type, + } + if s.Capacity > 0 { + entry.Capacity = bytesValue(s.Capacity) + } + out.Storage = append(out.Storage, entry) + } + } + if len(info.GPUs) > 0 { + out.Gpus = make([]*brevapiv2.GPUInfo, 0, len(info.GPUs)) + for _, gpu := range info.GPUs { + out.Gpus = append(out.Gpus, &brevapiv2.GPUInfo{ + Model: gpu.Model, + Count: clampToInt32(gpu.Count), + MemoryBytes: bytesValue(gpu.MemoryBytes), + }) + } + } + return out +} + +func utilizationToProto(info *UtilizationInfo) *brevapiv2.ResourceUtilization { + if info == nil { + return nil + } + out := &brevapiv2.ResourceUtilization{ + CpuPercent: info.CPUPercent, + DiskPercent: info.DiskPercent, + } + if info.MemoryUsedBytes > 0 { + out.MemoryUsed = bytesValue(info.MemoryUsedBytes) + } + if info.MemoryTotalBytes > 0 { + out.MemoryTotal = bytesValue(info.MemoryTotalBytes) + } + if info.DiskUsedBytes > 0 { + out.DiskUsed = bytesValue(info.DiskUsedBytes) + } + if info.DiskTotalBytes > 0 { + out.DiskTotal = bytesValue(info.DiskTotalBytes) + } + if len(info.GPUs) > 0 { + out.Gpus = make([]*brevapiv2.GPUUtilization, 0, len(info.GPUs)) + for _, gpu := range info.GPUs { + out.Gpus = append(out.Gpus, gpuUtilizationToProto(gpu)) + } + } + return out +} + +func heartbeatStatusToProto(status *HeartbeatStatus) *brevapiv2.BrevCloudNodeStatus { + if status == nil { + return nil + } + out := &brevapiv2.BrevCloudNodeStatus{ + Phase: convertNodePhase(status.Phase), + Detail: status.Detail, + } + if status.LastTransitionTime != nil && !status.LastTransitionTime.IsZero() { + out.LastTransitionTime = timestamppbNew(*status.LastTransitionTime) + } + return out +} + +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 +} + +func tunnelPortsToProto(ports []provider.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: int32(lp), + RemotePort: int32(rp), + Protocol: port.Protocol, + }) + } + if len(out) == 0 { + return nil + } + return out +} + +func appIngressesToProto(ingresses []AppIngress) []*brevapiv2.AppIngressRequest { + if len(ingresses) == 0 { + return nil + } + + out := make([]*brevapiv2.AppIngressRequest, 0, len(ingresses)) + for _, ingress := range ingresses { + lp := ingress.LocalPort + rp := ingress.RemotePort + + if lp <= 0 || lp > math.MaxInt32 || rp < 0 || rp > math.MaxInt32 { + continue + } + + out = append(out, &brevapiv2.AppIngressRequest{ + AppId: ingress.AppID, + Protocol: ingress.Protocol, + LocalPort: int32(lp), //nolint:gosec // G115: range checked above. + RemotePort: int32(rp), + HostnamePrefix: ingress.HostnamePrefix, + PathPrefix: ingress.PathPrefix, + ForceHttps: ingress.ForceHTTPS, + }) + } + + if len(out) == 0 { + return nil + } + return out +} + +func bytesValue(v int64) *devplaneapiv1.Bytes { + if v <= 0 { + return nil + } + return &devplaneapiv1.Bytes{Value: v} +} + +func convertNodePhase(phase NodePhase) brevapiv2.BrevCloudNodePhase { + switch phase { + case NodePhaseWaitingForRegistration: + return brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_WAITING_FOR_REGISTRATION + case NodePhaseActive: + return brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ACTIVE + case NodePhaseOffline: + return brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_OFFLINE + case NodePhaseStopped: + return brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_STOPPED + case NodePhaseError: + return brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ERROR + default: + return brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_UNSPECIFIED + } +} + +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 +} + +func protoString(s string) *string { + if s == "" { + return nil + } + return &s +} + +func timestamppbNew(t time.Time) *timestamppb.Timestamp { + return timestamppb.New(t) +} + +func bearerToken(token string) string { + return "Bearer " + token +} + +func clampToInt32(v int) int32 { + if v > math.MaxInt32 { + return math.MaxInt32 + } + if v < math.MinInt32 { + return math.MinInt32 + } + return int32(v) +} diff --git a/pkg/brevdaemon/agent/client/client_test.go b/pkg/brevdaemon/agent/client/client_test.go new file mode 100644 index 00000000..907bb5f8 --- /dev/null +++ b/pkg/brevdaemon/agent/client/client_test.go @@ -0,0 +1,290 @@ +package client + +import ( + "context" + stderrors "errors" + "net/http" + "net/http/httptest" + "testing" + "time" + + "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/dev-plane/internal/brevcloud/provider" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestRegisterSuccess(t *testing.T) { + var captured *brevapiv2.RegisterRequest + svc := &testBrevCloudAgentService{ + registerFn: func(_ context.Context, req *connect.Request[brevapiv2.RegisterRequest]) (*connect.Response[brevapiv2.RegisterResponse], error) { + captured = req.Msg + return connect.NewResponse(&brevapiv2.RegisterResponse{ + BrevCloudNodeId: "fn-123", + DeviceToken: "device-token", + HeartbeatInterval: durationpb.New(45 * time.Second), + DisplayName: "stored-name", + CloudName: "stored-cloud", + CloudCredId: "cc-123", + DeviceFingerprint: "scoped-device-fp", + }), nil + }, + } + + client := newTestClient(t, svc) + + params := RegisterParams{ + RegistrationToken: "reg-token", + DisplayName: "node-one", + CloudName: "private-cloud", + AgentVersion: "v1.2.3", + Capabilities: []string{"docker"}, + HardwareFingerprint: "shape-fp", + DeviceFingerprintHash: "device-fp-hash", + Hardware: &HardwareInfo{ + CPUCount: 16, + RAMBytes: 64 << 30, + Architecture: "amd64", + GPUs: []GPUInfo{ + {Model: "A100", MemoryBytes: 40 << 30, Count: 2}, + }, + Storage: []StorageInfo{ + {Name: "nvme0n1", Capacity: 1 << 40, Type: "nvme"}, + }, + }, + } + + result, err := client.Register(context.Background(), params) + require.NoError(t, err) + require.Equal(t, "fn-123", result.BrevCloudNodeID) + require.Equal(t, "device-token", result.DeviceToken) + require.Equal(t, 45*time.Second, result.HeartbeatInterval) + require.Equal(t, "stored-name", result.DisplayName) + require.Equal(t, "stored-cloud", result.CloudName) + require.Equal(t, "cc-123", result.CloudCredID) + require.Equal(t, "scoped-device-fp", result.DeviceFingerprint) + + require.Equal(t, "node-one", captured.GetDisplayName()) + require.Equal(t, "private-cloud", captured.GetCloudName()) + require.Equal(t, "reg-token", captured.GetRegistrationToken()) + require.Equal(t, []string{"docker"}, captured.GetCapabilities()) + require.Equal(t, "shape-fp", captured.GetHardwareFingerprint()) + require.Equal(t, "device-fp-hash", captured.GetDeviceFingerprintHash()) + require.Equal(t, int32(16), captured.GetHardware().GetCpuCount()) + require.Equal(t, int64(64<<30), captured.GetHardware().GetRamBytes().GetValue()) + require.Len(t, captured.GetHardware().GetGpus(), 1) + require.Equal(t, "A100", captured.GetHardware().GetGpus()[0].GetModel()) + require.Equal(t, int64(40<<30), captured.GetHardware().GetGpus()[0].GetMemoryBytes().GetValue()) + require.Equal(t, int32(2), captured.GetHardware().GetGpus()[0].GetCount()) + require.Equal(t, "amd64", captured.GetHardware().GetArchitecture()) + require.Len(t, captured.GetHardware().GetStorage(), 1) + require.Equal(t, "nvme0n1", captured.GetHardware().GetStorage()[0].GetName()) + require.Equal(t, int64(1<<40), captured.GetHardware().GetStorage()[0].GetCapacity().GetValue()) + require.Equal(t, "nvme", captured.GetHardware().GetStorage()[0].GetType()) + require.Equal(t, "v1.2.3", captured.GetAgent().GetVersion()) +} + +func TestHeartbeatSendsAuthorizationAndPayload(t *testing.T) { + var ( + capturedHeader string + capturedReq *brevapiv2.HeartbeatRequest + ) + now := time.Now().UTC().Truncate(time.Second) + lastTransition := now.Add(-time.Hour) + temp := float32(65.5) + + svc := &testBrevCloudAgentService{ + heartbeatFn: func(_ context.Context, req *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) { + capturedHeader = req.Header().Get("Authorization") + capturedReq = req.Msg + return connect.NewResponse(&brevapiv2.HeartbeatResponse{ + ServerTime: timestamppb.New(now.Add(5 * time.Second)), + NextHeartbeatInterval: durationpb.New(90 * time.Second), + }), nil + }, + } + + client := newTestClient(t, svc) + + params := HeartbeatParams{ + BrevCloudNodeID: "fn-1", + DeviceToken: "device-token", + ObservedAt: now, + AgentVersion: "v2.0.0", + DisplayName: "node-one", + CloudName: "edge", + DeviceFingerprintHash: "device-fp-hash", + HardwareFingerprint: "shape-fp", + Status: &HeartbeatStatus{ + Phase: NodePhaseActive, + Detail: "running", + LastTransitionTime: &lastTransition, + }, + Utilization: &UtilizationInfo{ + CPUPercent: 25.5, + MemoryUsedBytes: 4 << 30, + MemoryTotalBytes: 16 << 30, + DiskPercent: 50, + DiskUsedBytes: 100 << 30, + DiskTotalBytes: 200 << 30, + GPUs: []GPUUtilization{ + { + Index: 0, + Model: "A100", + UtilizationPercent: 80, + MemoryUsedBytes: 10 << 30, + MemoryTotalBytes: 40 << 30, + TemperatureCelsius: &temp, + }, + }, + }, + } + + result, err := client.Heartbeat(context.Background(), params) + require.NoError(t, err) + require.Equal(t, "Bearer device-token", capturedHeader) + require.Equal(t, "fn-1", capturedReq.GetBrevCloudNodeId()) + require.Equal(t, now, capturedReq.GetObservedAt().AsTime()) + require.Equal(t, "v2.0.0", capturedReq.GetAgent().GetVersion()) + require.Equal(t, brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ACTIVE, capturedReq.GetStatus().GetPhase()) + require.Equal(t, "running", capturedReq.GetStatus().GetDetail()) + require.Equal(t, lastTransition.UTC(), capturedReq.GetStatus().GetLastTransitionTime().AsTime()) + require.Equal(t, float32(25.5), capturedReq.GetUtilization().GetCpuPercent()) + require.Equal(t, int64(4<<30), capturedReq.GetUtilization().GetMemoryUsed().GetValue()) + require.Equal(t, "node-one", capturedReq.GetDisplayName()) + require.Equal(t, "edge", capturedReq.GetCloudName()) + require.Equal(t, "device-fp-hash", capturedReq.GetDeviceFingerprintHash()) + require.Equal(t, "shape-fp", capturedReq.GetHardwareFingerprint()) + + require.Equal(t, now.Add(5*time.Second), result.ServerTime) + require.Equal(t, 90*time.Second, result.NextHeartbeatInterval) +} + +func TestGetTunnelTokenSendsPortsAndAuth(t *testing.T) { + var capturedReq *brevapiv2.GetTunnelTokenRequest + svc := &testBrevCloudAgentService{ + tunnelFn: func(_ context.Context, req *connect.Request[brevapiv2.GetTunnelTokenRequest]) (*connect.Response[brevapiv2.GetTunnelTokenResponse], error) { + capturedReq = req.Msg + require.Equal(t, "Bearer device-token", req.Header().Get("Authorization")) + return connect.NewResponse(&brevapiv2.GetTunnelTokenResponse{ + Token: "tunnel-token", + Endpoint: "example.dev:443", + Ttl: durationpb.New(2 * time.Minute), + }), nil + }, + } + + client := newTestClient(t, svc) + result, err := client.GetTunnelToken(context.Background(), TunnelTokenParams{ + BrevCloudNodeID: "fn-1", + DeviceToken: "device-token", + TunnelName: "default", + Ports: []brevcloud.TunnelPortMapping{ + {LocalPort: 22}, + {RemotePort: 8080}, + }, + }) + require.NoError(t, err) + require.Equal(t, "fn-1", capturedReq.GetBrevCloudNodeId()) + require.Equal(t, "default", capturedReq.GetTunnelName()) + require.Len(t, capturedReq.GetRequestedPorts(), 2) + require.Equal(t, int32(22), capturedReq.GetRequestedPorts()[0].GetLocalPort()) + require.Equal(t, int32(22), capturedReq.GetRequestedPorts()[0].GetRemotePort()) + require.Equal(t, int32(8080), capturedReq.GetRequestedPorts()[1].GetRemotePort()) + + require.Equal(t, "tunnel-token", result.Token) + require.Equal(t, "example.dev:443", result.Endpoint) + require.Equal(t, 2*time.Minute, result.TTL) +} + +func TestRegisterMapsRegistrationError(t *testing.T) { + svc := &testBrevCloudAgentService{ + registerFn: func(context.Context, *connect.Request[brevapiv2.RegisterRequest]) (*connect.Response[brevapiv2.RegisterResponse], error) { + connectErr := connect.NewError(connect.CodeInvalidArgument, stderrors.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) + connectErr.AddDetail(detail) + return nil, connectErr + }, + } + + client := newTestClient(t, svc) + _, err := client.Register(context.Background(), RegisterParams{ + RegistrationToken: "token", + DeviceFingerprintHash: "device-fp-hash", + HardwareFingerprint: "shape-fp", + }) + require.Error(t, err) + + var regErr *RegistrationError + require.True(t, stderrors.As(err, ®Err)) + require.Equal(t, brevapiv2.BrevCloudRegistrationErrorReason_BREV_CLOUD_REGISTRATION_ERROR_REASON_INVALID_TOKEN, regErr.Reason) + require.Equal(t, "token invalid", regErr.Error()) +} + +func TestHeartbeatUnauthenticated(t *testing.T) { + svc := &testBrevCloudAgentService{ + heartbeatFn: func(context.Context, *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) { + return nil, connect.NewError(connect.CodeUnauthenticated, stderrors.New("invalid token")) + }, + } + + client := newTestClient(t, svc) + _, err := client.Heartbeat(context.Background(), HeartbeatParams{ + BrevCloudNodeID: "fn-1", + DeviceToken: "bad-token", + }) + require.Error(t, err) + require.True(t, stderrors.Is(err, ErrUnauthenticated)) +} + +type testBrevCloudAgentService struct { + registerFn func(ctx context.Context, req *connect.Request[brevapiv2.RegisterRequest]) (*connect.Response[brevapiv2.RegisterResponse], error) + heartbeatFn func(ctx context.Context, req *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) + tunnelFn func(ctx context.Context, req *connect.Request[brevapiv2.GetTunnelTokenRequest]) (*connect.Response[brevapiv2.GetTunnelTokenResponse], error) +} + +func (s *testBrevCloudAgentService) 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 *testBrevCloudAgentService) 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 *testBrevCloudAgentService) GetTunnelToken(ctx context.Context, req *connect.Request[brevapiv2.GetTunnelTokenRequest]) (*connect.Response[brevapiv2.GetTunnelTokenResponse], error) { + if s.tunnelFn != nil { + return s.tunnelFn(ctx, req) + } + return connect.NewResponse(&brevapiv2.GetTunnelTokenResponse{}), nil +} + +func newTestClient(t *testing.T, handler brevapiv2connect.BrevCloudAgentServiceHandler) BrevCloudAgentClient { + t.Helper() + + path, h := brevapiv2connect.NewBrevCloudAgentServiceHandler(handler) + mux := http.NewServeMux() + mux.Handle(path, h) + + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + client, err := New(config.Config{ + BrevCloudAgentURL: server.URL, + }) + require.NoError(t, err) + return client +} diff --git a/pkg/brevdaemon/agent/config/config.go b/pkg/brevdaemon/agent/config/config.go new file mode 100644 index 00000000..e04b698e --- /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/dev-plane/pkg/errors" +) + +const ( + envPrefix = "BREV_AGENT_" + envBrevCloudURL = envPrefix + "BREV_CLOUD_URL" + envRegistrationToken = envPrefix + "REGISTRATION_TOKEN" + envDisplayName = envPrefix + "DISPLAY_NAME" + envCloudName = envPrefix + "CLOUD_NAME" + envStateDir = envPrefix + "STATE_DIR" + envDeviceTokenPath = envPrefix + "DEVICE_TOKEN_PATH" + envHeartbeatInterval = envPrefix + "HEARTBEAT_INTERVAL" + envEnableTunnel = envPrefix + "ENABLE_TUNNEL" + envTunnelSSHPort = envPrefix + "TUNNEL_SSH_PORT" + envTunnelCritical = envPrefix + "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 int + TunnelCritical bool +} + +// Load constructs a Config from environment variables, applying defaults and +// validation as defined in the MVP plan. +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.Atoi(portRaw) + 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 = 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..1899d159 --- /dev/null +++ b/pkg/brevdaemon/agent/config/config_test.go @@ -0,0 +1,149 @@ +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{ + envRegistrationToken, + envDisplayName, + envCloudName, + 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..82621e4f --- /dev/null +++ b/pkg/brevdaemon/agent/health/reporter.go @@ -0,0 +1,112 @@ +package health + +import ( + "sync" + "time" + + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/client" +) + +// Status mirrors the heartbeat payload but keeps Go-native fields for internal coordination. +type Status struct { + Phase client.NodePhase + 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: client.NodePhaseActive, + Detail: detail, + }) +} + +// MarkError marks the subsystem as unhealthy with additional detail. +func (r *Reporter) MarkError(detail string) Status { + return r.Publish(Status{ + Phase: client.NodePhaseError, + 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..49ad4677 --- /dev/null +++ b/pkg/brevdaemon/agent/health/reporter_test.go @@ -0,0 +1,93 @@ +package health + +import ( + "testing" + "time" + + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/client" +) + +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 != client.NodePhaseError { + t.Fatalf("Phase = %v, want NodePhaseError", 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 != client.NodePhaseActive { + t.Fatalf("Phase = %v, want NodePhaseActive", 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: client.NodePhaseActive, + 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..44791237 --- /dev/null +++ b/pkg/brevdaemon/agent/heartbeat/heartbeat.go @@ -0,0 +1,225 @@ +package heartbeat + +import ( + "context" + "time" + + "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/dev-plane/pkg/errors" + "go.uber.org/zap" +) + +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 client.BrevCloudAgentClient + 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 *client.HeartbeatStatus + StatusUpdates <-chan client.HeartbeatStatus +} + +// Run executes the heartbeat loop until the context is canceled or an +// unrecoverable error occurs. +func (r *Runner) Run(ctx context.Context) error { //nolint:gocyclo,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 = &client.HeartbeatStatus{ + Phase: client.NodePhaseActive, + } + } + 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 + } + + params := client.HeartbeatParams{ + BrevCloudNodeID: r.Identity.InstanceID, + DeviceToken: r.Identity.DeviceToken, + ObservedAt: nowFn(), + Utilization: util.ToClient(), + AgentVersion: "", // set by orchestrator later + Status: currentStatusPtr(currentStatus), + DeviceFingerprintHash: r.Identity.DeviceFingerprintHash, + HardwareFingerprint: r.Identity.HardwareFingerprint, + } + + if updated, ok := r.nextStatus(); ok { + currentStatus = updated + params.Status = currentStatusPtr(currentStatus) + } + + res, err := r.Client.Heartbeat(ctx, params) + if err != nil { + if errors.Is(err, client.ErrUnauthenticated) { + return errors.WrapAndTrace(err) + } + r.Log.Warn("heartbeat failed", zap.Error(err)) + currentBackoff = minDurationValue(currentBackoff*2, maxInterval) + nextDelay = currentBackoff + continue + } + + currentBackoff = base + nextDelay = base + if res.NextHeartbeatInterval > 0 { + nextDelay = clampDuration(res.NextHeartbeatInterval, 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() (client.HeartbeatStatus, bool) { + if r.StatusUpdates == nil { + return client.HeartbeatStatus{}, false + } + + var updated client.HeartbeatStatus + 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 client.HeartbeatStatus) client.HeartbeatStatus { + if status.Phase == client.NodePhaseUnspecified { + status.Phase = client.NodePhaseActive + } + return cloneStatus(status) +} + +func cloneStatus(status client.HeartbeatStatus) client.HeartbeatStatus { + out := status + if status.LastTransitionTime != nil { + t := *status.LastTransitionTime + out.LastTransitionTime = &t + } + return out +} + +func currentStatusPtr(status client.HeartbeatStatus) *client.HeartbeatStatus { + s := cloneStatus(status) + return &s +} + +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..f17aa016 --- /dev/null +++ b/pkg/brevdaemon/agent/heartbeat/heartbeat_test.go @@ -0,0 +1,213 @@ +package heartbeat + +import ( + "context" + "errors" + "testing" + "time" + + "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" +) + +func TestRunnerSendsHeartbeatAndUsesServerInterval(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var captured client.HeartbeatParams + clientStub := &stubClient{ + heartbeatFn: func(_ context.Context, params client.HeartbeatParams) (client.HeartbeatResult, error) { + captured = params + cancel() + return client.HeartbeatResult{ + NextHeartbeatInterval: 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), + SampleUtilization: func(context.Context) (telemetry.UtilizationInfo, error) { + return telemetry.UtilizationInfo{ + CPUPercent: 10, + }, nil + }, + Sleep: sleepFn, + Now: func() time.Time { return time.Unix(0, 0) }, + } + + err := runner.Run(ctx) + require.NoError(t, err) + require.Equal(t, "fn-1", captured.BrevCloudNodeID) + require.Equal(t, "token", captured.DeviceToken) + require.NotNil(t, captured.Status) + require.Equal(t, client.NodePhaseActive, captured.Status.Phase) + 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, _ client.HeartbeatParams) (client.HeartbeatResult, error) { + callCount++ + if callCount < 3 { + return client.HeartbeatResult{}, errors.New("temporary failure") + } + cancel() + return client.HeartbeatResult{}, 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), + SampleUtilization: func(context.Context) (telemetry.UtilizationInfo, error) { + return telemetry.UtilizationInfo{}, nil + }, + 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 client.HeartbeatStatus, 2) + updates <- client.HeartbeatStatus{ + Phase: client.NodePhaseError, + Detail: "tunnel missing", + LastTransitionTime: &transition, + } + + var captured []client.HeartbeatParams + clientStub := &stubClient{ + heartbeatFn: func(_ context.Context, params client.HeartbeatParams) (client.HeartbeatResult, error) { + captured = append(captured, params) + cancel() + return client.HeartbeatResult{}, nil + }, + } + + runner := Runner{ + Client: clientStub, + Identity: identity.Identity{ + InstanceID: "inst-1", + DeviceToken: "token", + }, + Log: zaptest.NewLogger(t), + SampleUtilization: func(context.Context) (telemetry.UtilizationInfo, error) { + return telemetry.UtilizationInfo{}, nil + }, + 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].Status) + require.Equal(t, client.NodePhaseError, captured[0].Status.Phase) + require.Equal(t, "tunnel missing", captured[0].Status.Detail) + require.True(t, captured[0].Status.LastTransitionTime.Equal(transition)) +} + +func TestRunnerStopsOnUnauthenticated(t *testing.T) { + ctx := context.Background() + clientStub := &stubClient{ + heartbeatFn: func(context.Context, client.HeartbeatParams) (client.HeartbeatResult, error) { + return client.HeartbeatResult{}, client.ErrUnauthenticated + }, + } + + 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, errors.Is(err, client.ErrUnauthenticated)) +} + +type stubClient struct { + heartbeatFn func(ctx context.Context, req client.HeartbeatParams) (client.HeartbeatResult, error) +} + +func (s *stubClient) Register(_ context.Context, _ client.RegisterParams) (client.RegisterResult, error) { + return client.RegisterResult{}, nil +} + +func (s *stubClient) Heartbeat(ctx context.Context, req client.HeartbeatParams) (client.HeartbeatResult, error) { + if s.heartbeatFn != nil { + return s.heartbeatFn(ctx, req) + } + return client.HeartbeatResult{}, nil +} + +func (s *stubClient) GetTunnelToken(_ context.Context, _ client.TunnelTokenParams) (client.TunnelTokenResult, error) { + return client.TunnelTokenResult{}, nil +} diff --git a/pkg/brevdaemon/agent/identity/identity.go b/pkg/brevdaemon/agent/identity/identity.go new file mode 100644 index 00000000..49b8094c --- /dev/null +++ b/pkg/brevdaemon/agent/identity/identity.go @@ -0,0 +1,309 @@ +package identity + +import ( + "context" + "crypto/rand" + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "os" + "path/filepath" + "strings" + + "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/dev-plane/pkg/brevcloud/agent" + "github.com/brevdev/dev-plane/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 client.BrevCloudAgentClient, + 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) + } + hardwareFP, err := computeHardwareFingerprint(hw) + if err != nil { + return Identity{}, errors.WrapAndTrace(err) + } + deviceFPHash := computeDeviceFingerprintHash(hardwareFP, salt) + + params := client.RegisterParams{ + RegistrationToken: cfg.RegistrationToken, + DisplayName: cfg.DisplayName, + CloudName: cfg.CloudName, + Hardware: hw.ToClient(), + HardwareFingerprint: hardwareFP, + DeviceFingerprintHash: deviceFPHash, + } + + log.Info("registering device with brevcloud agent service") + res, err := agentClient.Register(ctx, params) + if err != nil { + return Identity{}, errors.WrapAndTrace(err) + } + + newIdentity := Identity{ + InstanceID: res.BrevCloudNodeID, + DeviceToken: res.DeviceToken, + DeviceSalt: salt, + DeviceFingerprintHash: deviceFPHash, + DeviceFingerprintStored: res.DeviceFingerprint, + HardwareFingerprint: hardwareFP, + } + 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(hw telemetry.HardwareInfo) (string, error) { + desc := brevcloud.HardwareDescriptor{ + CPUs: hw.CPUCount, + RAM: hw.RAMBytes, + } + for _, gpu := range hw.GPUs { + count := gpu.Count + if count <= 0 { + count = 1 + } + for i := 0; i < count; i++ { + desc.GPUs = append(desc.GPUs, brevcloud.GPUDescriptor{ + Model: gpu.Model, + Memory: gpu.MemoryBytes, + }) + } + } + fp, err := brevcloud.ComputeHardwareFingerprint(desc) + if err != nil { + return "", errors.WrapAndTrace(err) + } + return fp, nil +} + +func computeDeviceFingerprintHash(hardwareFP, deviceSalt string) string { + sum := sha256.Sum256([]byte(strings.Join([]string{hardwareFP, deviceSalt}, "|"))) + return hex.EncodeToString(sum[:]) +} diff --git a/pkg/brevdaemon/agent/identity/identity_test.go b/pkg/brevdaemon/agent/identity/identity_test.go new file mode 100644 index 00000000..1a6e1e8b --- /dev/null +++ b/pkg/brevdaemon/agent/identity/identity_test.go @@ -0,0 +1,165 @@ +package identity + +import ( + "context" + "os" + "path/filepath" + "testing" + + "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/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, client.RegisterParams) (client.RegisterResult, error) { + registerCalled = true + return client.RegisterResult{}, 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) + + client := &stubClient{ + registerFn: func(_ context.Context, params client.RegisterParams) (client.RegisterResult, error) { + require.Equal(t, "reg-token", params.RegistrationToken) + require.Equal(t, "node", params.DisplayName) + require.Equal(t, "cloud", params.CloudName) + require.NotEmpty(t, params.DeviceFingerprintHash) + require.NotEmpty(t, params.HardwareFingerprint) + return client.RegisterResult{ + BrevCloudNodeID: "fn-new", + DeviceToken: "new-token", + CloudCredID: "cc-123", + 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.NotEmpty(t, id.DeviceFingerprintHash) + + 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, params client.RegisterParams) (client.RegisterResult, error) +} + +func (s *stubClient) Register(ctx context.Context, params client.RegisterParams) (client.RegisterResult, error) { + if s.registerFn != nil { + return s.registerFn(ctx, params) + } + return client.RegisterResult{}, nil +} + +func (s *stubClient) Heartbeat(context.Context, client.HeartbeatParams) (client.HeartbeatResult, error) { + return client.HeartbeatResult{}, nil +} + +func (s *stubClient) GetTunnelToken(context.Context, client.TunnelTokenParams) (client.TunnelTokenResult, error) { + return client.TunnelTokenResult{}, nil +} diff --git a/pkg/brevdaemon/agent/telemetry/hardware.go b/pkg/brevdaemon/agent/telemetry/hardware.go new file mode 100644 index 00000000..956d574d --- /dev/null +++ b/pkg/brevdaemon/agent/telemetry/hardware.go @@ -0,0 +1,305 @@ +package telemetry + +import ( + "context" + "os" + "os/exec" + "runtime" + "strconv" + "strings" + + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/client" + "github.com/brevdev/dev-plane/pkg/errors" +) + +// 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 +} + +// ToClient converts the telemetry DTO into the client package structure. +func (h HardwareInfo) ToClient() *client.HardwareInfo { + out := &client.HardwareInfo{ + CPUCount: h.CPUCount, + RAMBytes: h.RAMBytes, + Architecture: h.Architecture, + } + if h.MachineModel != "" { + out.MachineModel = h.MachineModel + } + if len(h.Storage) > 0 { + out.Storage = make([]client.StorageInfo, 0, len(h.Storage)) + for _, s := range h.Storage { + out.Storage = append(out.Storage, client.StorageInfo{ + Name: s.Name, + Capacity: s.SizeBytes, + Type: s.Type, + }) + } + } + if len(h.GPUs) > 0 { + out.GPUs = make([]client.GPUInfo, 0, len(h.GPUs)) + for _, gpu := range h.GPUs { + out.GPUs = append(out.GPUs, client.GPUInfo{ + Model: gpu.Model, + MemoryBytes: gpu.MemoryBytes, + Count: gpu.Count, + }) + } + } + 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 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 +} diff --git a/pkg/brevdaemon/agent/telemetry/hardware_test.go b/pkg/brevdaemon/agent/telemetry/hardware_test.go new file mode 100644 index 00000000..6c0108e0 --- /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/dev-plane/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 TestHardwareToClientConversion(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.ToClient() + require.Equal(t, 8, clientInfo.CPUCount) + require.Equal(t, int64(32<<30), clientInfo.RAMBytes) + require.Equal(t, "arm64", clientInfo.Architecture) + require.Len(t, clientInfo.GPUs, 1) + require.Equal(t, 2, clientInfo.GPUs[0].Count) + require.Len(t, clientInfo.Storage, 1) + require.Equal(t, "nvme0n1", clientInfo.Storage[0].Name) + require.Equal(t, int64(512<<30), clientInfo.Storage[0].Capacity) + require.Equal(t, "nvme", clientInfo.Storage[0].Type) +} diff --git a/pkg/brevdaemon/agent/telemetry/memory_darwin.go b/pkg/brevdaemon/agent/telemetry/memory_darwin.go new file mode 100644 index 00000000..edcaf5ce --- /dev/null +++ b/pkg/brevdaemon/agent/telemetry/memory_darwin.go @@ -0,0 +1,21 @@ +//go:build darwin + +package telemetry + +import ( + "math" + + "github.com/brevdev/dev-plane/pkg/errors" + "golang.org/x/sys/unix" +) + +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 +} diff --git a/pkg/brevdaemon/agent/telemetry/memory_other.go b/pkg/brevdaemon/agent/telemetry/memory_other.go new file mode 100644 index 00000000..fe384edc --- /dev/null +++ b/pkg/brevdaemon/agent/telemetry/memory_other.go @@ -0,0 +1,9 @@ +//go:build !darwin + +package telemetry + +import "github.com/brevdev/dev-plane/pkg/errors" + +func darwinMemoryBytes() (int64, error) { + return 0, errors.New("darwin memory lookup unsupported on this platform") +} diff --git a/pkg/brevdaemon/agent/telemetry/os_consts.go b/pkg/brevdaemon/agent/telemetry/os_consts.go new file mode 100644 index 00000000..665c7fa6 --- /dev/null +++ b/pkg/brevdaemon/agent/telemetry/os_consts.go @@ -0,0 +1,3 @@ +package telemetry + +const goosLinux = "linux" diff --git a/pkg/brevdaemon/agent/telemetry/utilization.go b/pkg/brevdaemon/agent/telemetry/utilization.go new file mode 100644 index 00000000..4cb87cb0 --- /dev/null +++ b/pkg/brevdaemon/agent/telemetry/utilization.go @@ -0,0 +1,381 @@ +package telemetry + +import ( + "bufio" + "bytes" + "context" + "io" + "math" + "math/big" + "os/exec" + "strconv" + "strings" + "time" + + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/client" + "github.com/brevdev/dev-plane/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 +} + +// ToClient converts the telemetry DTO into the client payload type. +func (u UtilizationInfo) ToClient() *client.UtilizationInfo { + out := &client.UtilizationInfo{ + CPUPercent: u.CPUPercent, + MemoryUsedBytes: u.MemoryUsedBytes, + MemoryTotalBytes: u.MemoryTotalBytes, + DiskPercent: u.DiskPercent, + DiskUsedBytes: u.DiskUsedBytes, + DiskTotalBytes: u.DiskTotalBytes, + } + if len(u.GPUs) > 0 { + out.GPUs = make([]client.GPUUtilization, 0, len(u.GPUs)) + for _, gpu := range u.GPUs { + out.GPUs = append(out.GPUs, client.GPUUtilization{ + Index: gpu.Index, + Model: gpu.Model, + UtilizationPercent: gpu.UtilizationPercent, + MemoryUsedBytes: gpu.MemoryUsedBytes, + MemoryTotalBytes: gpu.MemoryTotalBytes, + TemperatureCelsius: gpu.TemperatureCelsius, + }) + } + } + 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) { //nolint:gocyclo // parser handles multiple meminfo keys + 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 +} diff --git a/pkg/brevdaemon/agent/telemetry/utilization_test.go b/pkg/brevdaemon/agent/telemetry/utilization_test.go new file mode 100644 index 00000000..953c385f --- /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 TestUtilizationToClientConversion(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.ToClient() + require.Equal(t, util.CPUPercent, clientUtil.CPUPercent) + require.Equal(t, util.MemoryUsedBytes, clientUtil.MemoryUsedBytes) + require.Len(t, clientUtil.GPUs, 1) + require.Equal(t, temp, *clientUtil.GPUs[0].TemperatureCelsius) +} + +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/ingress.go b/pkg/brevdaemon/agent/tunnel/ingress.go new file mode 100644 index 00000000..808cd754 --- /dev/null +++ b/pkg/brevdaemon/agent/tunnel/ingress.go @@ -0,0 +1,195 @@ +package tunnel + +import ( + "context" + "net" + "net/http" + "os/exec" + "strconv" + "strings" + "time" + + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/client" + "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/telemetry" + "github.com/brevdev/dev-plane/pkg/errors" +) + +const ( + dashboardServiceName = "dgx-dashboard.service" + loopbackHost = "127.0.0.1" +) + +type ( + probeFunc func(ctx context.Context, host string, port int, timeout time.Duration) error + httpProbeFunc func(ctx context.Context, host string, port int, protocol string, timeout time.Duration) error + systemdStatusFunc func(ctx context.Context, service string, timeout time.Duration) (active bool, supported bool, err error) +) + +func detectInstanceTypeFromHardware(hw telemetry.HardwareInfo) appaccess.InstanceType { + model := strings.ToUpper(hw.MachineModel) + if strings.Contains(model, "DGX") { + return appaccess.InstanceTypeDGXSpark + } + for _, gpu := range hw.GPUs { + if strings.Contains(strings.ToUpper(gpu.Model), "DGX") { + return appaccess.InstanceTypeDGXSpark + } + } + return appaccess.InstanceTypeUnknown +} + +func buildAppIngresses(ctx context.Context, cfg appaccess.Config, tcpProbe probeFunc, httpProbe httpProbeFunc, systemdCheck systemdStatusFunc, timeout time.Duration, instanceType appaccess.InstanceType) []client.AppIngress { + if instanceType != appaccess.InstanceTypeDGXSpark { + return nil + } + apps := cfg.AllowedAppsForInstance(appaccess.InstanceTypeDGXSpark) + if len(apps) == 0 { + return nil + } + if tcpProbe == nil { + tcpProbe = defaultIngressProbe + } + if httpProbe == nil { + httpProbe = defaultHTTPProbe + } + if systemdCheck == nil { + systemdCheck = defaultSystemdStatus + } + + timeout = clampProbeTimeout(timeout) + ingresses := make([]client.AppIngress, 0, len(apps)) + for _, spec := range apps { + if spec.DefaultPort <= 0 { + continue + } + + switch spec.ID { + case appaccess.AppIDDGXDashboard: + if dashboardHealthy(ctx, systemdCheck, httpProbe, spec.Protocol, spec.DefaultPort, timeout) { + ingresses = append(ingresses, appIngressFromSpec(spec)) + } + case appaccess.AppIDJupyter: + if err := httpProbe(ctx, loopbackHost, spec.DefaultPort, spec.Protocol, timeout); err == nil { + ingresses = append(ingresses, appIngressFromSpec(spec)) + } + default: + if err := tcpProbe(ctx, loopbackHost, spec.DefaultPort, timeout); err == nil { + ingresses = append(ingresses, appIngressFromSpec(spec)) + } + } + } + if len(ingresses) == 0 { + return nil + } + return ingresses +} + +func appIngressFromSpec(spec appaccess.AppSpec) client.AppIngress { + pathPrefix := spec.PathPrefix + if pathPrefix == "" { + pathPrefix = "/" + } + return client.AppIngress{ + AppID: string(spec.ID), + Protocol: spec.Protocol, + LocalPort: spec.DefaultPort, + HostnamePrefix: string(spec.ID), + PathPrefix: pathPrefix, + ForceHTTPS: spec.ForceHTTPS, + } +} + +func dashboardHealthy(ctx context.Context, systemdCheck systemdStatusFunc, httpProbe httpProbeFunc, protocol string, port int, timeout time.Duration) bool { + active, supported, err := systemdCheck(ctx, dashboardServiceName, timeout) + if err != nil { + return false + } + if supported { + return active + } + if httpProbe == nil { + return false + } + return httpProbe(ctx, loopbackHost, port, protocol, timeout) == nil +} + +func defaultSystemdStatus(ctx context.Context, service string, timeout time.Duration) (bool, bool, error) { + if service == "" { + return false, false, errors.Errorf("service name is required") + } + timeout = clampProbeTimeout(timeout) + statusCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + cmd := exec.CommandContext(statusCtx, "systemctl", "show", "-p", "ActiveState", "--value", service) + out, err := cmd.Output() + if err != nil { + if errors.Is(err, exec.ErrNotFound) { + return false, false, nil + } + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + stderr := strings.ToLower(string(exitErr.Stderr)) + if strings.Contains(stderr, "system has not been booted with systemd") || strings.Contains(stderr, "failed to connect to bus") { + return false, false, nil + } + return false, true, nil + } + return false, false, errors.WrapAndTrace(err) + } + + state := strings.TrimSpace(string(out)) + return strings.EqualFold(state, "active"), true, nil +} + +func defaultHTTPProbe(ctx context.Context, host string, port int, protocol string, timeout time.Duration) error { + if host == "" { + host = loopbackHost + } + if port <= 0 { + return errors.Errorf("invalid port %d", port) + } + + if protocol == "" { + protocol = "http" + } + + timeout = clampProbeTimeout(timeout) + client := &http.Client{Timeout: timeout} + req, err := http.NewRequestWithContext(ctx, http.MethodGet, protocol+"://"+net.JoinHostPort(host, strconv.Itoa(port)), nil) + if err != nil { + return errors.WrapAndTrace(err) + } + + resp, err := client.Do(req) + if err != nil { + return errors.WrapAndTrace(err) + } + return errors.WrapAndTrace(resp.Body.Close()) +} + +func defaultIngressProbe(ctx context.Context, host string, port int, timeout time.Duration) error { + if host == "" { + host = loopbackHost + } + if port <= 0 { + return errors.Errorf("invalid port %d", port) + } + dialer := &net.Dialer{Timeout: timeout} + conn, err := dialer.DialContext(ctx, "tcp", net.JoinHostPort(host, strconv.Itoa(port))) + if err != nil { + return errors.WrapAndTrace(err) + } + return errors.WrapAndTrace(conn.Close()) +} + +func clampProbeTimeout(timeout time.Duration) time.Duration { + switch { + case timeout <= 0: + return 750 * time.Millisecond + case timeout > 10*time.Second: + return 10 * time.Second + default: + return timeout + } +} diff --git a/pkg/brevdaemon/agent/tunnel/ingress_test.go b/pkg/brevdaemon/agent/tunnel/ingress_test.go new file mode 100644 index 00000000..1164a42c --- /dev/null +++ b/pkg/brevdaemon/agent/tunnel/ingress_test.go @@ -0,0 +1,236 @@ +package tunnel + +import ( + "context" + "testing" + "time" + + "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/dev-plane/internal/appaccess" + "github.com/brevdev/dev-plane/pkg/errors" + "github.com/stretchr/testify/require" + "go.uber.org/zap" +) + +func TestDetectInstanceTypeFromHardware(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + hw telemetry.HardwareInfo + want appaccess.InstanceType + }{ + { + name: "machine model DGX", + hw: telemetry.HardwareInfo{MachineModel: "DGX GH200"}, + want: appaccess.InstanceTypeDGXSpark, + }, + { + name: "gpu model DGX", + hw: telemetry.HardwareInfo{ + GPUs: []telemetry.GPUInfo{{Model: "DGX A100"}}, + }, + want: appaccess.InstanceTypeDGXSpark, + }, + { + name: "non DGX", + hw: telemetry.HardwareInfo{MachineModel: "c2-standard"}, + want: appaccess.InstanceTypeUnknown, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := detectInstanceTypeFromHardware(tt.hw) + require.Equal(t, tt.want, got) + }) + } +} + +func TestBuildAppIngressesSparkHealthy(t *testing.T) { + t.Parallel() + + cfg := appaccess.DefaultConfig() + ingresses := buildAppIngresses( + context.Background(), + cfg, + nil, + successHTTPProbe, + stubSystemd(true, true), + 500*time.Millisecond, + appaccess.InstanceTypeDGXSpark, + ) + + require.Len(t, ingresses, 2) + requireAppIngress(t, ingresses, string(appaccess.AppIDDGXDashboard), 11000) + requireAppIngress(t, ingresses, string(appaccess.AppIDJupyter), 8888) +} + +func TestBuildAppIngressesNonSpark(t *testing.T) { + t.Parallel() + + cfg := appaccess.DefaultConfig() + ingresses := buildAppIngresses(context.Background(), cfg, nil, successHTTPProbe, stubSystemd(true, true), 500*time.Millisecond, appaccess.InstanceTypeUnknown) + require.Nil(t, ingresses) +} + +func TestBuildAppIngressesDashboardInactiveSkips(t *testing.T) { + t.Parallel() + + cfg := appaccess.DefaultConfig() + ingresses := buildAppIngresses(context.Background(), cfg, nil, successHTTPProbe, stubSystemd(false, true), 500*time.Millisecond, appaccess.InstanceTypeDGXSpark) + + require.Len(t, ingresses, 1) + requireAppIngress(t, ingresses, string(appaccess.AppIDJupyter), 8888) +} + +func TestBuildAppIngressesDashboardFallsBackWhenSystemdUnavailable(t *testing.T) { + t.Parallel() + + cfg := appaccess.DefaultConfig() + ingresses := buildAppIngresses(context.Background(), cfg, nil, successHTTPProbe, stubSystemd(false, false), 500*time.Millisecond, appaccess.InstanceTypeDGXSpark) + + require.Len(t, ingresses, 2) + requireAppIngress(t, ingresses, string(appaccess.AppIDDGXDashboard), 11000) + requireAppIngress(t, ingresses, string(appaccess.AppIDJupyter), 8888) +} + +func TestBuildAppIngressesProbeFailureSkips(t *testing.T) { + t.Parallel() + + cfg := appaccess.DefaultConfig() + ingresses := buildAppIngresses(context.Background(), cfg, nil, failingHTTPProbe, stubSystemd(false, false), 500*time.Millisecond, appaccess.InstanceTypeDGXSpark) + require.Nil(t, ingresses) +} + +func stubSystemd(active bool, supported bool) systemdStatusFunc { + return func(context.Context, string, time.Duration) (bool, bool, error) { + return active, supported, nil + } +} + +func successHTTPProbe(context.Context, string, int, string, time.Duration) error { + return nil +} + +func failingHTTPProbe(context.Context, string, int, string, time.Duration) error { + return errors.New("probe failed") +} + +func requireAppIngress(t *testing.T, ingresses []client.AppIngress, appID string, port int) { + t.Helper() + for _, ingress := range ingresses { + if ingress.AppID == appID { + require.Equal(t, port, ingress.LocalPort) + require.Equal(t, "https", ingress.Protocol) + require.Equal(t, "/", ingress.PathPrefix) + require.Equal(t, appID, ingress.HostnamePrefix) + require.True(t, ingress.ForceHTTPS) + return + } + } + require.Fail(t, "expected ingress not found", "app_id=%s", appID) +} + +func TestManagerRequestsSSHWithoutAppIngressesForNonSpark(t *testing.T) { + t.Parallel() + + ctx := context.Background() + clientStub := &stubAgentClient{ + resp: client.TunnelTokenResult{Token: "token"}, + } + manager := Manager{ + Client: clientStub, + Identity: identity.Identity{InstanceID: "brev-node", DeviceToken: "device-token"}, + Cfg: TunnelConfig{SSHPort: 22}, + Log: zap.NewNop(), + DetectHardware: func(context.Context) (telemetry.HardwareInfo, error) { + return telemetry.HardwareInfo{MachineModel: "c2-standard"}, nil + }, + HTTPProbe: successHTTPProbe, + SystemdStatus: stubSystemd(true, true), + CommandFactory: func(context.Context, string, ...string) Command { return stubCommand{} }, + ProbeTimeout: 500 * time.Millisecond, + AppConfig: appaccess.DefaultConfig(), + Sleep: func(context.Context, time.Duration) error { return nil }, + Probe: defaultIngressProbe, + } + + require.NoError(t, manager.Start(ctx)) + require.Len(t, clientStub.lastReq.Ports, 1) + require.Equal(t, 22, clientStub.lastReq.Ports[0].LocalPort) + require.Empty(t, clientStub.lastReq.AppIngresses) +} + +func TestManagerIncludesAppIngressesForSparkWhenProbesPass(t *testing.T) { + t.Parallel() + + ctx := context.Background() + clientStub := &stubAgentClient{ + resp: client.TunnelTokenResult{Token: "token"}, + } + manager := Manager{ + Client: clientStub, + Identity: identity.Identity{InstanceID: "brev-node", DeviceToken: "device-token"}, + Cfg: TunnelConfig{SSHPort: 22}, + Log: zap.NewNop(), + DetectHardware: func(context.Context) (telemetry.HardwareInfo, error) { + return telemetry.HardwareInfo{MachineModel: "DGX Station"}, nil + }, + HTTPProbe: successHTTPProbe, + SystemdStatus: stubSystemd(true, true), + CommandFactory: func(context.Context, string, ...string) Command { return stubCommand{} }, + ProbeTimeout: 500 * time.Millisecond, + AppConfig: appaccess.DefaultConfig(), + Sleep: func(context.Context, time.Duration) error { return nil }, + Probe: defaultIngressProbe, + } + + require.NoError(t, manager.Start(ctx)) + require.Len(t, clientStub.lastReq.Ports, 1) + require.Len(t, clientStub.lastReq.AppIngresses, 2) + requireAppIngressParams(t, clientStub.lastReq.AppIngresses, string(appaccess.AppIDDGXDashboard), 11000) + requireAppIngressParams(t, clientStub.lastReq.AppIngresses, string(appaccess.AppIDJupyter), 8888) +} + +func requireAppIngressParams(t *testing.T, ingresses []client.AppIngress, appID string, port int) { + t.Helper() + for _, ingress := range ingresses { + if ingress.AppID == appID { + require.Equal(t, port, ingress.LocalPort) + require.Equal(t, appID, ingress.HostnamePrefix) + require.Equal(t, "https", ingress.Protocol) + require.True(t, ingress.ForceHTTPS) + return + } + } + require.Fail(t, "expected app ingress missing", "app_id=%s", appID) +} + +type stubAgentClient struct { + lastReq client.TunnelTokenParams + resp client.TunnelTokenResult +} + +func (s *stubAgentClient) Register(context.Context, client.RegisterParams) (client.RegisterResult, error) { + return client.RegisterResult{}, nil +} + +func (s *stubAgentClient) Heartbeat(context.Context, client.HeartbeatParams) (client.HeartbeatResult, error) { + return client.HeartbeatResult{}, nil +} + +func (s *stubAgentClient) GetTunnelToken(_ context.Context, req client.TunnelTokenParams) (client.TunnelTokenResult, error) { + s.lastReq = req + return s.resp, nil +} + +type stubCommand struct{} + +func (stubCommand) Start() error { return nil } +func (stubCommand) Wait() error { return nil } +func (stubCommand) SetEnv([]string) {} +func (stubCommand) CombinedOutput() ([]byte, error) { return nil, nil } diff --git a/pkg/brevdaemon/agent/tunnel/tunnel.go b/pkg/brevdaemon/agent/tunnel/tunnel.go new file mode 100644 index 00000000..2bedea6b --- /dev/null +++ b/pkg/brevdaemon/agent/tunnel/tunnel.go @@ -0,0 +1,361 @@ +package tunnel + +import ( + "context" + cryptorand "crypto/rand" + "fmt" + "math/big" + "os" + "os/exec" + "time" + + "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/dev-plane/pkg/errors" + "go.uber.org/zap" +) + +const ( + defaultTunnelName = "default" + + minRetryDelay = 5 * time.Second + maxRetryDelay = 5 * time.Minute +) + +// TunnelConfig configures tunnel management. +type TunnelConfig struct { + SSHPort int + ClientBinary string +} + +// TunnelPortMapping describes how a local port is exposed through the tunnel. +type TunnelPortMapping struct { + LocalPort int + RemotePort int + Protocol string +} + +// Manager fetches tunnel tokens and boots the tunnel client. +type Manager struct { + Client client.BrevCloudAgentClient + 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) + Probe probeFunc + HTTPProbe httpProbeFunc + SystemdStatus systemdStatusFunc + 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 + } + + detectFn := m.detectHardwareFunc() + probeFn := m.probeFunc() + httpProbe := m.httpProbeFunc() + systemdStatus := m.systemdStatusFunc() + probeTimeout := m.probeTimeout() + appCfg := m.appAccessConfig() + + hw, hwErr := detectFn(ctx) + if hwErr != nil { + m.Log.Info("skipping app ingress discovery: hardware detection failed", zap.Error(hwErr)) + } + instanceType := detectInstanceTypeFromHardware(hw) + + req := client.TunnelTokenParams{ + BrevCloudNodeID: m.Identity.InstanceID, + DeviceToken: m.Identity.DeviceToken, + TunnelName: defaultTunnelName, + Ports: []TunnelPortMapping{ + {LocalPort: m.Cfg.SSHPort}, + }, + } + + retryDelay := minRetryDelay + + for { + if errors.Is(ctx.Err(), context.Canceled) { + return nil + } + + req.AppIngresses = nil + if instanceType == appaccess.InstanceTypeDGXSpark { + if ingresses := buildAppIngresses(ctx, appCfg, probeFn, httpProbe, systemdStatus, probeTimeout, instanceType); len(ingresses) > 0 { + req.AppIngresses = ingresses + } + } + + 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.Cmd.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 client.TunnelTokenParams) (client.TunnelTokenResult, error) { + res, err := m.Client.GetTunnelToken(ctx, req) + if err != nil { + return client.TunnelTokenResult{}, errors.WrapAndTrace(err) + } + return res, 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 client.TunnelTokenResult) error { + commands := []struct { + name string + args []string + }{ + { + name: "sudo", + args: []string{"cloudflared", "service", "install", token.Token}, + }, + { + name: "sudo", + args: []string{"systemctl", "restart", "cloudflared.service"}, + }, + } + + 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 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 +} + +func (m *Manager) probeFunc() probeFunc { + if m.Probe != nil { + return m.Probe + } + return defaultIngressProbe +} + +func (m *Manager) httpProbeFunc() httpProbeFunc { + if m.HTTPProbe != nil { + return m.HTTPProbe + } + return defaultHTTPProbe +} + +func (m *Manager) systemdStatusFunc() systemdStatusFunc { + if m.SystemdStatus != nil { + return m.SystemdStatus + } + return defaultSystemdStatus +} + +func (m *Manager) probeTimeout() time.Duration { + if m.ProbeTimeout > 0 { + return m.ProbeTimeout + } + return 750 * time.Millisecond +} + +func (m *Manager) appAccessConfig() appaccess.Config { + if len(m.AppConfig.Allowlist) == 0 { + return appaccess.DefaultConfig() + } + return m.AppConfig +} diff --git a/pkg/brevdaemon/agent/tunnel/tunnel_test.go b/pkg/brevdaemon/agent/tunnel/tunnel_test.go new file mode 100644 index 00000000..8e83b63c --- /dev/null +++ b/pkg/brevdaemon/agent/tunnel/tunnel_test.go @@ -0,0 +1,194 @@ +package tunnel + +import ( + "context" + "errors" + "testing" + "time" + + "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/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, client.TunnelTokenParams) (client.TunnelTokenResult, error) { + requested = true + return client.TunnelTokenResult{ + Token: "token", + Endpoint: "endpoint", + TTL: time.Minute, + }, 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, 2) + require.Equal(t, "sudo", executed[0].name) + require.Equal(t, []string{"cloudflared", "service", "install", "token"}, executed[0].args) + require.Equal(t, "sudo", executed[1].name) + require.Equal(t, []string{"systemctl", "restart", "cloudflared.service"}, executed[1].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, client.TunnelTokenParams) (client.TunnelTokenResult, error) { + return client.TunnelTokenResult{ + 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, client.TunnelTokenParams) (client.TunnelTokenResult, error) +} + +func (s *stubClient) Register(context.Context, client.RegisterParams) (client.RegisterResult, error) { + return client.RegisterResult{}, nil +} + +func (s *stubClient) Heartbeat(context.Context, client.HeartbeatParams) (client.HeartbeatResult, error) { + return client.HeartbeatResult{}, nil +} + +func (s *stubClient) GetTunnelToken(ctx context.Context, params client.TunnelTokenParams) (client.TunnelTokenResult, error) { + if s.getTunnelFn != nil { + return s.getTunnelFn(ctx, params) + } + return client.TunnelTokenResult{}, nil +} diff --git a/pkg/brevdaemon/provider/tunnel.go b/pkg/brevdaemon/provider/tunnel.go new file mode 100644 index 00000000..f72e8c4d --- /dev/null +++ b/pkg/brevdaemon/provider/tunnel.go @@ -0,0 +1,8 @@ +package provider + +// TunnelPortMapping describes how a local port is exposed through the tunnel. +type TunnelPortMapping struct { + LocalPort int + RemotePort int + Protocol string +} diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index 2980b0ce..05cf722a 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -40,6 +40,7 @@ import ( "github.com/brevdev/brev-cli/pkg/cmd/set" "github.com/brevdev/brev-cli/pkg/cmd/setupworkspace" "github.com/brevdev/brev-cli/pkg/cmd/shell" + "github.com/brevdev/brev-cli/pkg/cmd/spark" "github.com/brevdev/brev-cli/pkg/cmd/sshkeys" "github.com/brevdev/brev-cli/pkg/cmd/start" "github.com/brevdev/brev-cli/pkg/cmd/status" @@ -296,6 +297,7 @@ func createCmdTree(cmd *cobra.Command, t *terminal.Terminal, loginCmdStore *stor cmd.AddCommand(recreate.NewCmdRecreate(t, loginCmdStore)) cmd.AddCommand(writeconnectionevent.NewCmdwriteConnectionEvent(t, loginCmdStore)) cmd.AddCommand(updatemodel.NewCmdupdatemodel(t, loginCmdStore)) + cmd.AddCommand(spark.NewCmdSpark(t, loginCmdStore, noLoginCmdStore)) } func hasWorkspaceCommands(cmd *cobra.Command) bool { diff --git a/pkg/cmd/register/register.go b/pkg/cmd/register/register.go index 1c16586b..05e8acfe 100644 --- a/pkg/cmd/register/register.go +++ b/pkg/cmd/register/register.go @@ -42,3 +42,7 @@ func runRegister(t *terminal.Terminal) { t.Vprint("We will reach out to the provided email with updates and instructions on how to register soon (:\n") t.Vprint("\n") } + +// func runRegisterReal(t *terminal.Terminal) *cobra.Command { +// return spark.NewCmdSpark(t, loginCmdStore, noLoginCmdStore) +// } \ No newline at end of file diff --git a/pkg/cmd/spark/agent.go b/pkg/cmd/spark/agent.go new file mode 100644 index 00000000..ff56b2f4 --- /dev/null +++ b/pkg/cmd/spark/agent.go @@ -0,0 +1,44 @@ +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))) + + for { + select { + case <-ticker.C: + t.Vprint(fmt.Sprintf("[%s] Brev agent heartbeat\n", time.Now().Format(time.RFC3339))) + } + } +} diff --git a/pkg/cmd/spark/enroll.go b/pkg/cmd/spark/enroll.go new file mode 100644 index 00000000..1f34a56b --- /dev/null +++ b/pkg/cmd/spark/enroll.go @@ -0,0 +1,782 @@ +package spark + +import ( + "context" + _ "embed" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "os" + "strings" + "time" + + "github.com/briandowns/spinner" + "github.com/spf13/cobra" + + brevcloud "github.com/brevdev/brev-cli/pkg/brevcloud" + "github.com/brevdev/brev-cli/pkg/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 + +const ( + defaultEnrollTimeout = 10 * time.Minute + envFilePath = "/etc/default/brevd" + stateDirDefault = "/var/lib/devplane/brevd" + serviceName = "brevd" + binaryPath = "/usr/local/bin/brevd" +) + +type enrollOptions struct { + cloudCredID string + user string + noSudoers bool + systemd bool + agentVersion string + wait bool + timeout time.Duration + printCmd bool + dryRun bool + json bool + assumeInstalled 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 env := os.Getenv("BREVD_ASSUME_INSTALLED"); strings.EqualFold(env, "true") { + opts.assumeInstalled = true + } + + if loginCmdStore == nil { + return breverrors.WrapAndTrace(errors.New("authenticated store unavailable")) + } + + if opts.cloudCredID == "" { + if cid, err := resolveDefaultCloudCred(cmd.Context(), loginCmdStore); err == nil && cid != "" { + opts.cloudCredID = cid + logStep(t, fmt.Sprintf("resolved default brev cloud cred: %s", opts.cloudCredID)) + } else { + logStep(t, "no default brev cloud cred found; proceeding without cloud cred") + } + } else { + logStep(t, fmt.Sprintf("using provided brevcloud cloud cred: %s", opts.cloudCredID)) + } + + return runSparkEnroll(cmd.Context(), t, loginCmdStore, alias, opts) + }, + } + + cmd.Flags().StringVar(&opts.cloudCredID, "cloud-cred-id", "", "Brev cloud credential id") + cmd.Flags().StringVar(&opts.user, "user", "brevcloud", "Remote user to ensure exists") + cmd.Flags().BoolVar(&opts.noSudoers, "no-sudoers", false, "Skip sudoers entry for the user") + cmd.Flags().BoolVar(&opts.systemd, "systemd", true, "Manage systemd unit (set false to skip)") + 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") + cmd.Flags().BoolVar(&opts.assumeInstalled, "assume-installed", false, "Assume brevd binary and systemd unit already exist") + + 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 { + 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() + } + + logStep(t, "resolving spark host via Sync ssh_config") + host, err := resolveSparkHost(t, alias) + if err != nil { + return fail(err) + } + + logStep(t, fmt.Sprintf("target host: %s@%s:%d", host.User, host.Hostname, host.Port)) + 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() + } + + if opts.dryRun { + logStep(t, fmt.Sprintf("dry-run: would connect to %s with cloud_cred_id=%s", host.Alias, opts.cloudCredID)) + if uiEnabled { + t.Print(fmt.Sprintf("Dry-run: would connect to %s with cloud_cred_id=%s", sparklib.HostLabel(host), opts.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() + } + + logStep(t, "probing SSH connectivity (uname/whoami/hostname)") + if err := probeConnectivity(ctx, remote, host, opts.printCmd); err != nil { + return fail(err) + } + logStep(t, "SSH connectivity probe succeeded") + + // Minimal path: ensure agent and unit pre-exist. + logStep(t, "checking for existing brevd binary and systemd unit on remote") + if err := ensureAgentPresent(ctx, remote, host, opts.printCmd); err != nil { + return fail(err) + } + logStep(t, "verified brevd binary and unit are present") + + var intent brevcloud.CreateRegistrationIntentResponse + if opts.mockRegistration { + logStep(t, "mocking registration intent (no API call)") + intent = brevcloud.MockRegistrationIntent(opts.cloudCredID) + } else { + logStep(t, fmt.Sprintf("requesting registration intent for cloud cred %s", opts.cloudCredID)) + req := brevcloud.CreateRegistrationIntentRequest{ + CloudCredID: opts.cloudCredID, + OrgID: orgID, + } + ref := "" + switch { + case req.CloudCredID != "" && req.OrgID != "": + ref = fmt.Sprintf("cloud cred %s org %s", req.CloudCredID, req.OrgID) + case req.CloudCredID != "": + ref = fmt.Sprintf("cloud cred %s", req.CloudCredID) + case req.OrgID != "": + ref = fmt.Sprintf("org %s", req.OrgID) + default: + ref = "no reference" + } + resp, err := brevCloudClient.CreateRegistrationIntent(ctx, req) + if err != nil { + return fail(fmt.Errorf("failed to create registration intent (%s): %w", ref, err)) + } + intent = *resp + logStep(t, fmt.Sprintf("created registration intent: brev_cloud_node_id=%s expires_at=%s", intent.BrevCloudNodeID, intent.ExpiresAt)) + if strings.TrimSpace(intent.RegistrationToken) == "" { + return fail(fmt.Errorf("registration intent returned empty registration token for %s", ref)) + } + } + + logStep(t, "generating remote enroll script") + brevCloudBase := buildAgentAPIBase(config.GlobalConfig.GetDevplaneAPIURL()) + script := buildEnrollScript(intent.BrevCloudNodeID, intent.RegistrationToken, opts.cloudCredID, brevCloudBase, opts.mockRegistration) + writeCmd := fmt.Sprintf("mkdir -p /tmp/brevd && cat <<'ENROLL_EOF' >/tmp/brevd/enroll.sh\n%s\nENROLL_EOF\nchmod +x /tmp/brevd/enroll.sh", script) + if opts.printCmd { + fmt.Printf("[remote] write enroll script to /tmp/brevd/enroll.sh\n") + } + out, err := remote.Run(ctx, host, writeCmd) + if err != nil { + return fail(fmt.Errorf("failed to write enroll script on %s: err=%v output=%s", sparklib.HostLabel(host), err, strings.TrimSpace(out))) + } + + logStep(t, "executing remote enroll script") + if opts.printCmd { + fmt.Printf("[remote] sudo /tmp/brevd/enroll.sh || /tmp/brevd/enroll.sh\n") + } + runEnrollCmd := "sudo /tmp/brevd/enroll.sh || /tmp/brevd/enroll.sh" + out, err = remote.Run(ctx, host, runEnrollCmd) + if err != nil { + return fail(fmt.Errorf("remote enroll script failed on %s: err=%v output=%s", sparklib.HostLabel(host), err, strings.TrimSpace(out))) + } + + var result enrollResult + result.BrevCloudNodeID = intent.BrevCloudNodeID + result.CloudCredID = intent.CloudCredID + + if opts.wait && !opts.mockRegistration { + logStep(t, fmt.Sprintf("waiting for brev cloud node %s to report active...", intent.BrevCloudNodeID)) + 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")) + } + if manageURL := enrollmentManageURL(loginStore, aliasLabel); manageURL != "" { + t.Print(fmt.Sprintf("Manage your %s %s", t.Yellow("Spark"), t.Blue(manageURL))) + } + } + + 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(t *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) + creds, err := client.ListCloudCred(ctx) + if err != nil { + return "", err + } + + matchAll := map[string]string{ + "name": "brevcloud-default", + "managedBy": "system", + "provider": "brevcloud", + } + + for _, cc := range creds { + if cc.ProviderID != "" && strings.ToLower(cc.ProviderID) != "brevcloud" { + continue + } + if hasAllLabels(cc.Labels, matchAll) { + return cc.ID, nil + } + } + + for _, cc := range creds { + if hasAllLabels(cc.Labels, matchAll) { + return cc.ID, nil + } + } + + return "", fmt.Errorf("no default brevcloud cloud credential found; provide --cloud-cred-id or BREV_CLOUD_CRED_ID") +} + +func hasAllLabels(labels map[string]string, required map[string]string) bool { + if len(required) == 0 { + return true + } + for k, v := range required { + if labels == nil { + return false + } + if val, ok := labels[k]; !ok || val != v { + return false + } + } + return true +} + +func ensureAgentPresent(ctx context.Context, remote sparklib.RemoteRunner, host sparklib.Host, printCmd bool) error { + // 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") + } + + // Write the install script to remote and execute it + writeScriptCmd := fmt.Sprintf("cat > /tmp/install-brevd-binary.sh <<'SCRIPT_EOF'\n%s\nSCRIPT_EOF\nchmod +x /tmp/install-brevd-binary.sh", installBinaryScript) + if printCmd { + fmt.Printf("[remote] Writing install script to /tmp/install-brevd-binary.sh\n") + } + if _, err := remote.Run(ctx, host, writeScriptCmd); err != nil { + return fmt.Errorf("failed to write install script on %s: %w", sparklib.HostLabel(host), err) + } + + // Execute the install script + executeCmd := "/tmp/install-brevd-binary.sh && rm -f /tmp/install-brevd-binary.sh" + if printCmd { + fmt.Printf("[remote] %s\n", executeCmd) + } + out, err := remote.Run(ctx, host, executeCmd) + if err != nil { + return fmt.Errorf("failed to install brevd on %s: err=%v output=%s", sparklib.HostLabel(host), err, strings.TrimSpace(out)) + } + if printCmd { + fmt.Printf("[remote] brevd install output: %s\n", strings.TrimSpace(out)) + } + } + + // 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") + } + + // Write the service install script to remote and execute it with STATE_DIR env var + writeScriptCmd := fmt.Sprintf("cat > /tmp/install-brevd-service.sh <<'SCRIPT_EOF'\n%s\nSCRIPT_EOF\nchmod +x /tmp/install-brevd-service.sh", installServiceScript) + if printCmd { + fmt.Printf("[remote] Writing service install script to /tmp/install-brevd-service.sh\n") + } + if _, err := remote.Run(ctx, host, writeScriptCmd); err != nil { + return fmt.Errorf("failed to write service install script on %s: %w", sparklib.HostLabel(host), err) + } + + // Execute the service install script with STATE_DIR environment variable + executeCmd := fmt.Sprintf("STATE_DIR=%s /tmp/install-brevd-service.sh && rm -f /tmp/install-brevd-service.sh", stateDirDefault) + if printCmd { + fmt.Printf("[remote] %s\n", executeCmd) + } + out, err := remote.Run(ctx, host, executeCmd) + if err != nil { + return fmt.Errorf("failed to install brevd systemd service on %s: err=%v output=%s", sparklib.HostLabel(host), err, strings.TrimSpace(out)) + } + if printCmd { + fmt.Printf("[remote] systemd service install output: %s\n", strings.TrimSpace(out)) + } + } else if printCmd { + fmt.Printf("[remote] systemd service check output: %s\n", strings.TrimSpace(out)) + } + + return nil +} + +func ensureConfigDir(ctx context.Context, remote sparklib.RemoteRunner, host sparklib.Host, printCmd bool) error { + cmds := []string{ + fmt.Sprintf("sudo -n mkdir -p %s", stateDirDefault), + fmt.Sprintf("sudo mkdir -p %s", 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 config 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 { + var b strings.Builder + b.WriteString("brev_cloud_node_id: ") + b.WriteString(brevCloudNodeID) + b.WriteString("\nregistration_token: ") + b.WriteString(registrationToken) + if cloudCredID != "" { + b.WriteString("\ncloud_cred_id: ") + b.WriteString(cloudCredID) + } + 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 restartService(ctx context.Context, remote sparklib.RemoteRunner, host sparklib.Host, printCmd bool) error { + cmd := fmt.Sprintf("sudo systemctl restart %s", serviceName) + if printCmd { + fmt.Printf("[remote] %s\n", cmd) + } + _, err := remote.Run(ctx, host, cmd) + return breverrors.WrapAndTrace(err) +} + +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: + logStep(t, fmt.Sprintf("still waiting for brev cloud node %s (phase=%s last_seen_at=%s)", brevCloudNodeID, node.Phase, node.LastSeenAt)) + } + } +} + +func logStep(t *terminal.Terminal, msg string) { + // step logging suppressed for quieter output + _ = t + _ = msg +} + +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." + } + + var httpErr *store.HTTPResponseError + if errors.As(err, &httpErr) && httpErr.Response != nil && httpErr.Response.StatusCode() == http.StatusNotFound { + hint := "" + if !opts.mockRegistration { + hint = " Use --mock-registration for demos." + } + return fmt.Sprintf("Failed to create registration intent (cloud cred %s): endpoint not available (404).%s", opts.cloudCredID, hint) + } + + 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 enrollmentManageURL(loginStore *store.AuthHTTPStore, alias string) string { + if loginStore == nil { + return "" + } + meta, err := loginStore.GetCurrentWorkspaceMeta() + if err != nil || meta == nil || meta.OrganizationID == "" || alias == "" { + return "" + } + return fmt.Sprintf("https://brev.nvidia.com/org/%s/compute/%s", meta.OrganizationID, url.PathEscape(alias)) +} + +func buildAgentAPIBase(raw string) string { + base := strings.TrimSpace(raw) + if base == "" { + return "" + } + if !strings.HasPrefix(base, "http://") && !strings.HasPrefix(base, "https://") { + base = "https://" + strings.TrimPrefix(base, "//") + } + base = strings.TrimRight(base, "/") + return base + "/agent/v1" +} + +func buildEnrollScript(brevCloudNodeID, registrationToken, cloudCredID, brevCloudBase string, mock bool) string { + var envContent strings.Builder + envContent.WriteString("BREV_AGENT_BREV_CLOUD_NODE_ID=") + envContent.WriteString(brevCloudNodeID) + envContent.WriteString("\nBREV_AGENT_REGISTRATION_TOKEN=") + envContent.WriteString(registrationToken) + envContent.WriteString("\nBREV_AGENT_STATE_DIR=") + envContent.WriteString(stateDirDefault) + if brevCloudBase != "" { + envContent.WriteString("\nBREV_AGENT_BREV_CLOUD_URL=") + envContent.WriteString(brevCloudBase) + } + if cloudCredID != "" { + envContent.WriteString("\nBREV_AGENT_CLOUD_CRED_ID=") + envContent.WriteString(cloudCredID) + } + envContent.WriteString("\n") + + script := fmt.Sprintf(`#!/usr/bin/env bash +set -euo pipefail + +LOG_DIR=/tmp/brevd +LOG_FILE="${LOG_DIR}/enroll.log" +ENV_FILE="%s" +SERVICE="%s" +MOCK=%t + +log() { + local msg="$1" + mkdir -p "${LOG_DIR}" + logger -t brev-enroll "${msg}" 2>/dev/null || true + printf '%%s\n' "${msg}" >>"${LOG_FILE}" +} + +run_cmd() { + local cmd="$1" + bash -c "${cmd}" >>"${LOG_FILE}" 2>&1 +} + +ensure_dir() { + local dir="$1" + local cmds=( + "sudo -n mkdir -p \"${dir}\"" + "sudo mkdir -p \"${dir}\"" + "mkdir -p \"${dir}\"" + ) + for c in "${cmds[@]}"; do + if run_cmd "${c}"; then + return 0 + fi + done + log "failed to create dir ${dir}" + exit 1 +} + +write_file() { + local content="$1" + local dest="$2" + local tmp="${dest}.tmp" + + printf '%%s\n' "${content}" >"${tmp}" + + local cmds=( + "sudo -n tee \"${dest}\" >/dev/null" + "sudo tee \"${dest}\" >/dev/null" + "tee \"${dest}\" >/dev/null" + ) + for c in "${cmds[@]}"; do + if bash -c "${c}" <"${tmp}" >>"${LOG_FILE}" 2>&1; then + rm -f "${tmp}" + return 0 + fi + done + + log "failed to write file to ${dest}" + rm -f "${tmp}" + exit 1 +} + +set_file_mode() { + local mode="$1" + local path="$2" + local cmds=( + "sudo -n chmod ${mode} \"${path}\"" + "sudo chmod ${mode} \"${path}\"" + "chmod ${mode} \"${path}\"" + ) + for c in "${cmds[@]}"; do + if run_cmd "${c}"; then + return 0 + fi + done + log "failed to chmod ${mode} ${path}" + exit 1 +} + +extract_env_value() { + local key="$1" + local content="$2" + while IFS= read -r line; do + if [[ -z "${line}" || "${line}" =~ ^[[:space:]]*# ]]; then + continue + fi + line="${line#"${line%%[![:space:]]*}"}" + if [[ "${line}" == "${key}="* ]]; then + local val="${line#${key}=}" + if [[ "${val}" =~ ^\".*\"$ || "${val}" =~ ^\'.*\'$ ]]; then + val="${val:1:${#val}-2}" + fi + printf '%%s' "${val}" + break + fi + done <<<"${content}" + return 0 +} + +restart_service() { + local svc="$1" + local cmds=( + "sudo -n systemctl restart \"${svc}\"" + "sudo systemctl restart \"${svc}\"" + "systemctl restart \"${svc}\"" + ) + for c in "${cmds[@]}"; do + if run_cmd "${c}"; then + return 0 + fi + done + log "failed to restart service ${svc}" + exit 1 +} + +ENV_CONTENT=$(cat <<'EOF' +%s +EOF +) + +log "enroll start" +log "probe: $(uname -a)" +log "user: $(whoami)" +log "host: $(hostname)" +log "env file target: ${ENV_FILE}" + +ensure_dir "$(dirname "${ENV_FILE}")" +write_file "${ENV_CONTENT}" "${ENV_FILE}" +set_file_mode 600 "${ENV_FILE}" + +state_dir="$(extract_env_value "BREV_AGENT_STATE_DIR" "${ENV_CONTENT}")" +device_token_path="$(extract_env_value "BREV_AGENT_DEVICE_TOKEN_PATH" "${ENV_CONTENT}")" + +if [[ -n "${state_dir}" ]]; then + log "ensuring state dir ${state_dir}" + ensure_dir "${state_dir}" +fi + +if [[ -n "${device_token_path}" ]]; then + log "ensuring device token parent dir for ${device_token_path}" + ensure_dir "$(dirname "${device_token_path}")" +elif [[ -n "${state_dir}" ]]; then + ensure_dir "${state_dir}" +fi + +if [[ "${MOCK}" == "true" ]]; then + log "mock mode: skipping agent restart" + exit 0 +fi + +restart_service "${SERVICE}" +log "enroll success" +`, envFilePath, serviceName, mock, envContent.String()) + + return script +} + +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..34231110 --- /dev/null +++ b/pkg/cmd/spark/install-binary.sh @@ -0,0 +1,36 @@ +#!/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..162bfb6c --- /dev/null +++ b/pkg/cmd/spark/install-service.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -eo pipefail + +# Installs the brevd systemd service +# Expects STATE_DIR to be set (defaults to /var/lib/devplane/brevd) + +STATE_DIR="${STATE_DIR:-/var/lib/devplane/brevd}" + +# 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=root +Group=root + +[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 < 0 { + alias = args[0] + } + + err := runSparkSSH(t, alias, printCmd) + if err != nil { + return breverrors.WrapAndTrace(err) + } + return nil + }, + } + + cmd.Flags().BoolVar(&printCmd, "print-cmd", false, "Print the resolved ssh command before running") + + return cmd +} + +func runSparkSSH(t *terminal.Terminal, alias string, printCmd bool) error { + locator := sparklib.NewSyncSSHConfigLocator() + resolver := sparklib.NewDefaultSyncConfigResolver(files.AppFs, locator) + hosts, err := resolver.ResolveHosts() + if err != nil { + return breverrors.WrapAndTrace(err) + } + + selected, err := sparklib.SelectHost(hosts, alias, sparklib.TerminalPrompter{}) + if err != nil { + return breverrors.WrapAndTrace(err) + } + + if printCmd { + args := sparklib.BuildSSHArgs(selected) + t.Vprint(strings.Join(args, " ")) + } + + runner := sparklib.NewDefaultSSHRunner(files.AppFs) + return breverrors.WrapAndTrace(runner.Run(selected)) +} 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/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..e32f47f0 --- /dev/null +++ b/pkg/spark/parser_test.go @@ -0,0 +1,84 @@ +package spark + +import ( + "testing" + + "github.com/spf13/afero" + "github.com/stretchr/testify/require" +) + +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 "/home/tester", 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, "/home/tester/.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 "/home/tester", 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 "/home/tester", 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..635a702c --- /dev/null +++ b/pkg/spark/remote.go @@ -0,0 +1,108 @@ +package spark + +import ( + "bytes" + "context" + "fmt" + "os/exec" + "sort" + "strconv" + "strings" + "time" + + "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 "", 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 "", fmt.Errorf("identity file not found at %s", host.IdentityFile) + } + + argv := buildSSHCommand(host, remoteCmd) + cmd := exec.CommandContext(ctx, argv[0], argv[1:]...) + + var buf bytes.Buffer + cmd.Stdout = &buf + cmd.Stderr = &buf + + if err := cmd.Run(); err != nil { + return buf.String(), 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/ssh_runner.go b/pkg/spark/ssh_runner.go new file mode 100644 index 00000000..26b077bf --- /dev/null +++ b/pkg/spark/ssh_runner.go @@ -0,0 +1,88 @@ +package spark + +import ( + "fmt" + "os" + "os/exec" + "sort" + "strconv" + + "github.com/spf13/afero" + + breverrors "github.com/brevdev/brev-cli/pkg/errors" +) + +type SSHRunner struct { + executor Executor + fs afero.Fs +} + +func NewDefaultSSHRunner(fs afero.Fs) SSHRunner { + return NewSSHRunner(fs, ProcessExecutor{}) +} + +func NewSSHRunner(fs afero.Fs, executor Executor) SSHRunner { + return SSHRunner{ + executor: executor, + fs: fs, + } +} + +func (r SSHRunner) Run(host Host) error { + if host.IdentityFile == "" { + return breverrors.WrapAndTrace(fmt.Errorf("missing identity file for %s", host.Alias)) + } + + exists, err := afero.Exists(r.fs, host.IdentityFile) + if err != nil { + return breverrors.WrapAndTrace(err) + } + if !exists { + return breverrors.WrapAndTrace(fmt.Errorf("identity file not found at %s", host.IdentityFile)) + } + + argv := BuildSSHArgs(host) + return breverrors.WrapAndTrace(r.executor.Run(argv)) +} + +func BuildSSHArgs(host Host) []string { + args := []string{ + "ssh", + "-i", host.IdentityFile, + "-p", strconv.Itoa(host.Port), + } + + 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])) + } + } + + target := host.Hostname + if host.Alias != "" { + target = host.Alias + } + args = append(args, fmt.Sprintf("%s@%s", host.User, target)) + + return args +} + +type ProcessExecutor struct{} + +func (ProcessExecutor) Run(argv []string) error { + if len(argv) == 0 { + return breverrors.WrapAndTrace(fmt.Errorf("no command provided")) + } + + cmd := exec.Command(argv[0], argv[1:]...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + return cmd.Run() +} diff --git a/pkg/spark/ssh_runner_test.go b/pkg/spark/ssh_runner_test.go new file mode 100644 index 00000000..b3ad8757 --- /dev/null +++ b/pkg/spark/ssh_runner_test.go @@ -0,0 +1,76 @@ +package spark + +import ( + "testing" + + "github.com/spf13/afero" + "github.com/stretchr/testify/require" +) + +type capturingExecutor struct { + argv []string + err error +} + +func (c *capturingExecutor) Run(argv []string) error { + c.argv = argv + return c.err +} + +func TestBuildSSHArgsOrdersOptions(t *testing.T) { + host := Host{ + Alias: "spark-one", + Hostname: "h1", + User: "u1", + Port: 2222, + IdentityFile: "/id", + Options: map[string]string{ + "ProxyJump": "jump", + "StrictHostKeyChecking": "no", + }, + } + + args := BuildSSHArgs(host) + require.Equal(t, []string{ + "ssh", + "-i", "/id", + "-p", "2222", + "-o", "ProxyJump=jump", + "-o", "StrictHostKeyChecking=no", + "u1@h1", + }, args) +} + +func TestSSHRunnerRequiresIdentityFile(t *testing.T) { + fs := afero.NewMemMapFs() + exec := &capturingExecutor{} + + runner := NewSSHRunner(fs, exec) + err := runner.Run(Host{Alias: "spark-one"}) + require.Error(t, err) +} + +func TestSSHRunnerRunsCommand(t *testing.T) { + fs := afero.NewMemMapFs() + exec := &capturingExecutor{} + err := afero.WriteFile(fs, "/keys/id", []byte("pem"), 0o600) + require.NoError(t, err) + + host := Host{ + Alias: "spark-one", + Hostname: "h1", + User: "u1", + Port: 22, + IdentityFile: "/keys/id", + Options: map[string]string{ + "ProxyJump": "jump", + }, + } + + runner := NewSSHRunner(fs, exec) + err = runner.Run(host) + require.NoError(t, err) + + expected := []string{"ssh", "-i", "/keys/id", "-p", "22", "-o", "ProxyJump=jump", "u1@h1"} + require.Equal(t, expected, exec.argv) +} 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 +} From 64f90046119d5a2c7616bfcacb8d3f07fffdfe4a Mon Sep 17 00:00:00 2001 From: Pratik Patel Date: Wed, 7 Jan 2026 12:13:49 -0800 Subject: [PATCH 02/22] adding brevcloud user create script --- brevcloud-scripts/brevcloud-user.sh | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 brevcloud-scripts/brevcloud-user.sh diff --git a/brevcloud-scripts/brevcloud-user.sh b/brevcloud-scripts/brevcloud-user.sh new file mode 100644 index 00000000..0d1de992 --- /dev/null +++ b/brevcloud-scripts/brevcloud-user.sh @@ -0,0 +1,22 @@ +export BREV_USER=brevcloud +id -u $BREV_USER >/dev/null 2>&1 || sudo useradd -m -s /bin/bash $BREV_USER +sudo chmod 0700 /home/$BREV_USER +export BREV_HOME=/home/$BREV_USER +echo "$BREV_USER ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/$BREV_USER >/dev/null +sudo chmod 0440 /etc/sudoers.d/$BREV_USER +sudo visudo -c -f /etc/sudoers.d/$BREV_USER +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 mkdir $BREV_HOME/.ssh +sudo touch $BREV_HOME/.ssh/authorized_keys +sudo chmod 700 $BREV_HOME/.ssh +sudo chmod 600 $BREV_HOME/.ssh/authorized_keys +sudo chown -R $BREV_USER:$BREV_USER /home/$BREV_USER/.ssh +export BREV_KEY='' +sudo grep -qxF "$BREV_KEY" $BREV_HOME/.ssh/authorized_keys || echo "$BREV_KEY" | sudo tee -a $BREV_HOME/.ssh/authorized_keys >/dev/null +cat /home/$BREV_USER/.ssh/authorized_keys +sudo cat /home/$BREV_USER/.ssh/authorized_keys +sudo chown -R $BREV_USER:$BREV_USER $BREV_HOME +sudo -u $BREV_USER whoami +sudo -u $BREV_USER ls -la $BREV_HOME \ No newline at end of file From 6f69d6d418304f226b90a77892f3f980d9ba9f24 Mon Sep 17 00:00:00 2001 From: Pratik Patel Date: Wed, 7 Jan 2026 13:56:39 -0800 Subject: [PATCH 03/22] cleanup --- .../{ => brevd}/brevcloud-user.sh | 0 pkg/brevdaemon/agent/client/client_test.go | 2 +- pkg/brevdaemon/agent/telemetry/hardware.go | 15 ++++++++++++- .../agent/telemetry/memory_darwin.go | 21 ------------------- .../agent/telemetry/memory_other.go | 9 -------- pkg/brevdaemon/agent/telemetry/os_consts.go | 1 + pkg/brevdaemon/agent/tunnel/tunnel.go | 14 +++++-------- 7 files changed, 21 insertions(+), 41 deletions(-) rename brevcloud-scripts/{ => brevd}/brevcloud-user.sh (100%) delete mode 100644 pkg/brevdaemon/agent/telemetry/memory_darwin.go delete mode 100644 pkg/brevdaemon/agent/telemetry/memory_other.go diff --git a/brevcloud-scripts/brevcloud-user.sh b/brevcloud-scripts/brevd/brevcloud-user.sh similarity index 100% rename from brevcloud-scripts/brevcloud-user.sh rename to brevcloud-scripts/brevd/brevcloud-user.sh diff --git a/pkg/brevdaemon/agent/client/client_test.go b/pkg/brevdaemon/agent/client/client_test.go index 907bb5f8..05b36410 100644 --- a/pkg/brevdaemon/agent/client/client_test.go +++ b/pkg/brevdaemon/agent/client/client_test.go @@ -183,7 +183,7 @@ func TestGetTunnelTokenSendsPortsAndAuth(t *testing.T) { BrevCloudNodeID: "fn-1", DeviceToken: "device-token", TunnelName: "default", - Ports: []brevcloud.TunnelPortMapping{ + Ports: []provider.TunnelPortMapping{ {LocalPort: 22}, {RemotePort: 8080}, }, diff --git a/pkg/brevdaemon/agent/telemetry/hardware.go b/pkg/brevdaemon/agent/telemetry/hardware.go index 956d574d..210e31c0 100644 --- a/pkg/brevdaemon/agent/telemetry/hardware.go +++ b/pkg/brevdaemon/agent/telemetry/hardware.go @@ -2,6 +2,7 @@ package telemetry import ( "context" + "math" "os" "os/exec" "runtime" @@ -10,6 +11,7 @@ import ( "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/client" "github.com/brevdev/dev-plane/pkg/errors" + "golang.org/x/sys/unix" ) // HardwareInfo holds the coarse specs captured during registration. @@ -178,7 +180,7 @@ func systemRAMBytes() (int64, error) { } total, _, err := parseMeminfo(data) return total, err - case "darwin": + case darwin: value, err := darwinMemoryBytes() if err != nil { return 0, errors.WrapAndTrace(err) @@ -189,6 +191,17 @@ func systemRAMBytes() (int64, error) { } } +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) { diff --git a/pkg/brevdaemon/agent/telemetry/memory_darwin.go b/pkg/brevdaemon/agent/telemetry/memory_darwin.go deleted file mode 100644 index edcaf5ce..00000000 --- a/pkg/brevdaemon/agent/telemetry/memory_darwin.go +++ /dev/null @@ -1,21 +0,0 @@ -//go:build darwin - -package telemetry - -import ( - "math" - - "github.com/brevdev/dev-plane/pkg/errors" - "golang.org/x/sys/unix" -) - -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 -} diff --git a/pkg/brevdaemon/agent/telemetry/memory_other.go b/pkg/brevdaemon/agent/telemetry/memory_other.go deleted file mode 100644 index fe384edc..00000000 --- a/pkg/brevdaemon/agent/telemetry/memory_other.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !darwin - -package telemetry - -import "github.com/brevdev/dev-plane/pkg/errors" - -func darwinMemoryBytes() (int64, error) { - return 0, errors.New("darwin memory lookup unsupported on this platform") -} diff --git a/pkg/brevdaemon/agent/telemetry/os_consts.go b/pkg/brevdaemon/agent/telemetry/os_consts.go index 665c7fa6..fb812702 100644 --- a/pkg/brevdaemon/agent/telemetry/os_consts.go +++ b/pkg/brevdaemon/agent/telemetry/os_consts.go @@ -1,3 +1,4 @@ package telemetry const goosLinux = "linux" +const darwin = "darwin" diff --git a/pkg/brevdaemon/agent/tunnel/tunnel.go b/pkg/brevdaemon/agent/tunnel/tunnel.go index 2bedea6b..c7e0cda1 100644 --- a/pkg/brevdaemon/agent/tunnel/tunnel.go +++ b/pkg/brevdaemon/agent/tunnel/tunnel.go @@ -13,6 +13,7 @@ import ( "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/brevdaemon/provider" "github.com/brevdev/dev-plane/pkg/errors" "go.uber.org/zap" ) @@ -30,13 +31,6 @@ type TunnelConfig struct { ClientBinary string } -// TunnelPortMapping describes how a local port is exposed through the tunnel. -type TunnelPortMapping struct { - LocalPort int - RemotePort int - Protocol string -} - // Manager fetches tunnel tokens and boots the tunnel client. type Manager struct { Client client.BrevCloudAgentClient @@ -106,8 +100,10 @@ func (m *Manager) Start(ctx context.Context) error { //nolint:gocognit,gocyclo,f BrevCloudNodeID: m.Identity.InstanceID, DeviceToken: m.Identity.DeviceToken, TunnelName: defaultTunnelName, - Ports: []TunnelPortMapping{ - {LocalPort: m.Cfg.SSHPort}, + Ports: []provider.TunnelPortMapping{ + { + LocalPort: m.Cfg.SSHPort, + }, }, } From cdbf35828e3234f5fffdb66420bd110cad5997b5 Mon Sep 17 00:00:00 2001 From: Pratik Patel Date: Wed, 7 Jan 2026 14:11:56 -0800 Subject: [PATCH 04/22] fetched devplane --- go.mod | 150 +------ go.sum | 426 +------------------- pkg/brevdaemon/agent/client/client.go | 6 +- pkg/brevdaemon/agent/client/client_test.go | 4 +- pkg/brevdaemon/agent/identity/identity.go | 6 +- pkg/brevdaemon/agent/tunnel/ingress.go | 1 + pkg/brevdaemon/agent/tunnel/ingress_test.go | 2 +- pkg/brevdaemon/agent/tunnel/tunnel.go | 5 +- pkg/brevdaemon/provider/tunnel.go | 8 - 9 files changed, 17 insertions(+), 591 deletions(-) delete mode 100644 pkg/brevdaemon/provider/tunnel.go diff --git a/go.mod b/go.mod index fc8d8fe9..3dda0356 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( 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.1665 + 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.16.0 @@ -50,95 +50,28 @@ require ( ) require ( - ariga.io/atlas v0.31.1-0.20250212144724-069be8033e83 // indirect buf.build/gen/go/brevdev/protoc-gen-gotag/protocolbuffers/go v1.36.11-20220906235457-8b4922735da5.1 // indirect - connectrpc.com/otelconnect v0.7.0 // indirect dario.cat/mergo v1.0.0 // indirect - entgo.io/ent v0.14.4 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/DataDog/datadog-agent/comp/core/tagger/origindetection v0.67.0 // indirect - github.com/DataDog/datadog-agent/pkg/obfuscate v0.67.0 // indirect - github.com/DataDog/datadog-agent/pkg/proto v0.67.0 // indirect - github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.69.0 // indirect - github.com/DataDog/datadog-agent/pkg/trace v0.67.0 // indirect - github.com/DataDog/datadog-agent/pkg/util/log v0.67.0 // indirect - github.com/DataDog/datadog-agent/pkg/util/scrubber v0.67.0 // indirect - github.com/DataDog/datadog-agent/pkg/version v0.67.0 // indirect - github.com/DataDog/datadog-go/v5 v5.6.0 // indirect - github.com/DataDog/dd-trace-go/v2 v2.3.0 // indirect - github.com/DataDog/go-libddwaf/v4 v4.3.2 // indirect - github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20250721125240-fdf1ef85b633 // indirect - github.com/DataDog/go-sqllexer v0.1.6 // indirect - github.com/DataDog/go-tuf v1.1.0-0.5.2 // indirect - github.com/DataDog/gostackparse v0.7.0 // indirect - github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes v0.27.0 // indirect - github.com/DataDog/sketches-go v1.4.7 // indirect - github.com/Masterminds/semver/v3 v3.3.1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.1.5 // indirect - github.com/agext/levenshtein v1.2.1 // indirect - github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect - github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect - github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect - github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 // indirect - github.com/aws/aws-sdk-go-v2 v1.39.2 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect - github.com/aws/aws-sdk-go-v2/config v1.31.11 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.18.15 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25 // indirect - github.com/aws/aws-sdk-go-v2/service/dynamodb v1.31.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.6 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6 // indirect - github.com/aws/aws-sdk-go-v2/service/pricing v1.24.6 // indirect - github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sqs v1.31.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.29.5 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 // indirect - github.com/aws/smithy-go v1.23.0 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/bmatcuk/doublestar v1.3.4 // indirect - github.com/bojanz/currency v1.3.1 // indirect - github.com/brevdev/cloud v0.0.0-20251215210807-d272e4e4e8ff // indirect - github.com/brevdev/verb v0.3.17 // indirect github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/cenkalti/backoff/v5 v5.0.3 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 // indirect github.com/cloudflare/circl v1.6.1 // indirect - github.com/cloudflare/cloudflare-go v0.94.0 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect - github.com/cockroachdb/apd/v3 v3.2.1 // indirect github.com/cyphar/filepath-securejoin v0.3.6 // indirect - github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/ebitengine/purego v0.8.3 // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect - github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect - github.com/felixge/httpsnoop v1.0.4 // 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/gliderlabs/ssh v0.3.8 // 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-logr/stdr v1.2.2 // indirect - github.com/go-ole/go-ole v1.3.0 // indirect - github.com/go-openapi/inflect v0.19.0 // 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 @@ -154,116 +87,38 @@ require ( 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/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/mock v1.7.0-rc.1 // indirect github.com/google/gnostic-models v0.7.0 // indirect - github.com/google/go-github/v45 v45.2.0 // indirect - github.com/google/go-querystring v1.1.0 // indirect - github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect - github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/hcl v1.0.1-vault-5 // indirect - github.com/hashicorp/hcl/v2 v2.13.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/logrusorgru/aurora v2.0.3+incompatible // indirect - github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mitchellh/go-wordwrap v1.0.0 // 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/nexus-rpc/sdk-go v0.3.0 // indirect - github.com/ns1/ns1-go v2.4.0+incompatible // indirect - github.com/outcaste-io/ristretto v0.2.3 // indirect - github.com/pborman/uuid v1.2.1 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect - github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect github.com/pjbgf/sha1cd v0.3.2 // indirect - github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect - github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect - github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect - github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect - github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect - github.com/redis/go-redis/v9 v9.7.3 // indirect - github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3 // indirect - github.com/robfig/cron v1.2.0 // 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/secure-systems-lab/go-securesystemslib v0.9.0 // indirect - github.com/segmentio/ksuid v1.0.4 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect - github.com/shirou/gopsutil/v4 v4.25.3 // indirect github.com/skeema/knownhosts v1.3.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/cast v1.6.0 // indirect - github.com/srikrsna/protoc-gen-gotag v0.6.2 // indirect - github.com/stretchr/objx v0.5.2 // indirect - github.com/stripe/stripe-go/v79 v79.2.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/theckman/httpforwarded v0.4.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect - github.com/tinylib/msgp v1.2.5 // indirect - github.com/tklauser/go-sysconf v0.3.15 // indirect - github.com/tklauser/numcpus v0.10.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect - github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.2 // indirect - github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.0 // indirect - github.com/uptrace/opentelemetry-go-extra/otelzap v0.3.0 // indirect - github.com/vertoforce/go-emailrep v0.0.0-20220319001959-76d0921a0d9c // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect - github.com/xeonx/timeago v1.0.0-rc5 // indirect - github.com/yusufpapurcu/wmi v1.2.4 // indirect - github.com/zclconf/go-cty v1.14.4 // indirect - github.com/zclconf/go-cty-yaml v1.1.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/collector/component v1.31.0 // indirect - go.opentelemetry.io/collector/featuregate v1.31.0 // indirect - go.opentelemetry.io/collector/internal/telemetry v0.125.0 // indirect - go.opentelemetry.io/collector/pdata v1.31.0 // indirect - go.opentelemetry.io/collector/semconv v0.125.0 // indirect - go.opentelemetry.io/contrib/bridges/otelzap v0.10.0 // indirect - go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.46.1 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect - go.opentelemetry.io/contrib/processors/baggagecopy v0.11.0 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 // indirect - go.opentelemetry.io/otel/log v0.14.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect - go.opentelemetry.io/proto/otlp v1.4.0 // indirect - go.temporal.io/api v1.47.0 // indirect - go.temporal.io/sdk v1.33.1 // indirect - go.temporal.io/sdk/contrib/opentelemetry v0.6.0 // indirect - go.uber.org/atomic v1.11.0 // 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-20250819193227-8b4c13bb791b // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect - google.golang.org/grpc v1.75.0 // 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 @@ -283,7 +138,6 @@ require ( 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/golang/protobuf v1.5.4 // 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 diff --git a/go.sum b/go.sum index 9fe8f414..6d282632 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -ariga.io/atlas v0.31.1-0.20250212144724-069be8033e83 h1:nX4HXncwIdvQ8/8sIUIf1nyCkK8qdBaHQ7EtzPpuiGE= -ariga.io/atlas v0.31.1-0.20250212144724-069be8033e83/go.mod h1:Oe1xWPuu5q9LzyrWfbZmEZxFYeu4BHTyzfjeW2aZp/w= braces.dev/errtrace v0.3.0 h1:pzfd6LcWgfWtXLaNFWRnxV/7NP+FSOlIjRLwDuHfPxs= braces.dev/errtrace v0.3.0/go.mod h1:YQpXdo+u5iimgQdZzFoic8AjedEDncXGpp6/2SfazzI= buf.build/gen/go/brevdev/devplane/connectrpc/go v1.19.1-20251231160605-b3cca76916ad.2 h1:0kN/kFTB+1FwQKYfRmclNov3zl2l6piRWsLIvxI0MNg= @@ -11,159 +9,39 @@ buf.build/gen/go/brevdev/protoc-gen-gotag/protocolbuffers/go v1.36.11-2022090623 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14= connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w= -connectrpc.com/grpchealth v1.2.0 h1:aHP33Bki+F2jPNI1mFVSFG7v0qJrgmfbg7X7nOdSj0M= -connectrpc.com/grpchealth v1.2.0/go.mod h1:fZos12C4p/ZaZC6OwBGZUM+i/fhnRhv75ax/6V/zIeM= -connectrpc.com/otelconnect v0.7.0 h1:ZH55ZZtcJOTKWWLy3qmL4Pam4RzRWBJFOqTPyAqCXkY= -connectrpc.com/otelconnect v0.7.0/go.mod h1:Bt2ivBymHZHqxvo4HkJ0EwHuUzQN6k2l0oH+mp/8nwc= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -entgo.io/ent v0.14.4 h1:/DhDraSLXIkBhyiVoJeSshr4ZYi7femzhj6/TckzZuI= -entgo.io/ent v0.14.4/go.mod h1:aDPE/OziPEu8+OWbzy4UlvWmD2/kbRuWfK2A40hcxJM= 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/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= -github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/DataDog/datadog-agent/comp/core/tagger/origindetection v0.67.0 h1:2mEwRWvhIPHMPK4CMD8iKbsrYBxeMBSuuCXumQAwShU= -github.com/DataDog/datadog-agent/comp/core/tagger/origindetection v0.67.0/go.mod h1:ejJHsyJTG7NU6c6TDbF7dmckD3g+AUGSdiSXy+ZyaCE= -github.com/DataDog/datadog-agent/pkg/obfuscate v0.67.0 h1:NcvyDVIUA0NbBDbp7QJnsYhoBv548g8bXq886795mCQ= -github.com/DataDog/datadog-agent/pkg/obfuscate v0.67.0/go.mod h1:1oPcs3BUTQhiTkmk789rb7ob105MxNV6OuBa28BdukQ= -github.com/DataDog/datadog-agent/pkg/proto v0.67.0 h1:7dO6mKYRb7qSiXEu7Q2mfeKbhp4hykCAULy4BfMPmsQ= -github.com/DataDog/datadog-agent/pkg/proto v0.67.0/go.mod h1:bKVXB7pxBg0wqXF6YSJ+KU6PeCWKDyJj83kUH1ab+7o= -github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.69.0 h1:/DsN4R+IkC6t1+4cHSfkxzLtDl84rBbPC5Wa9srBAoM= -github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.69.0/go.mod h1:Th2LD/IGid5Rza55pzqGu6nUdOv/Rts6wPwLjTyOSTs= -github.com/DataDog/datadog-agent/pkg/trace v0.67.0 h1:dqt+/nObo0JKyaEqIMZgfqGZbx9TfEHpCkrjQ/zzH7k= -github.com/DataDog/datadog-agent/pkg/trace v0.67.0/go.mod h1:zmZoEtKvOnaKHbJGBKH3a4xuyPrSfBaF0ZE3Q3rCoDw= -github.com/DataDog/datadog-agent/pkg/util/log v0.67.0 h1:xrH15QNqeJZkYoXYi44VCIvGvTwlQ3z2iT2QVTGiT7s= -github.com/DataDog/datadog-agent/pkg/util/log v0.67.0/go.mod h1:dfVLR+euzEyg1CeiExgJQq1c1dod42S6IeiRPj8H7Yk= -github.com/DataDog/datadog-agent/pkg/util/scrubber v0.67.0 h1:aIWF85OKxXGo7rVyqJ7jm7lm2qCQrgyXzYyFuw0T2EQ= -github.com/DataDog/datadog-agent/pkg/util/scrubber v0.67.0/go.mod h1:Lfap5FuM4b/Pw9IrTuAvWBWZEmXOvZhCya3dYv4G8O0= -github.com/DataDog/datadog-agent/pkg/version v0.67.0 h1:TB8H8r+laB1Qdttvvc6XJVyLGxp8E6j2f2Mh5IPbYmQ= -github.com/DataDog/datadog-agent/pkg/version v0.67.0/go.mod h1:kvAw/WbI7qLAsDI2wHabZfM7Cv2zraD3JA3323GEB+8= -github.com/DataDog/datadog-go/v5 v5.6.0 h1:2oCLxjF/4htd55piM75baflj/KoE6VYS7alEUqFvRDw= -github.com/DataDog/datadog-go/v5 v5.6.0/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw= -github.com/DataDog/dd-trace-go/v2 v2.3.0 h1:0Y5kx+Wbod0z8moY0vUbKl6OM0oIV4zAynsVmsq+XT8= -github.com/DataDog/dd-trace-go/v2 v2.3.0/go.mod h1:yFomJ/rqKNLDbS9ohIDibdz8q9GK0MUSSkBdVDCibGA= -github.com/DataDog/go-libddwaf/v4 v4.3.2 h1:YGvW2Of1C4e1yU+p7iibmhN2zEOgi9XEchbhQjBxb/A= -github.com/DataDog/go-libddwaf/v4 v4.3.2/go.mod h1:/AZqP6zw3qGJK5mLrA0PkfK3UQDk1zCI2fUNCt4xftE= -github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20250721125240-fdf1ef85b633 h1:ZRLR9Lbym748e8RznWzmSoK+OfV+8qW6SdNYA4/IqdA= -github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20250721125240-fdf1ef85b633/go.mod h1:YFoTl1xsMzdSRFIu33oCSPS/3+HZAPGpO3oOM96wXCM= -github.com/DataDog/go-sqllexer v0.1.6 h1:skEXpWEVCpeZFIiydoIa2f2rf+ymNpjiIMqpW4w3YAk= -github.com/DataDog/go-sqllexer v0.1.6/go.mod h1:GGpo1h9/BVSN+6NJKaEcJ9Jn44Hqc63Rakeb+24Mjgo= -github.com/DataDog/go-tuf v1.1.0-0.5.2 h1:4CagiIekonLSfL8GMHRHcHudo1fQnxELS9g4tiAupQ4= -github.com/DataDog/go-tuf v1.1.0-0.5.2/go.mod h1:zBcq6f654iVqmkk8n2Cx81E1JnNTMOAx1UEO/wZR+P0= -github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4= -github.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM= -github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes v0.27.0 h1:5US5SqqhfkZkg/E64uvn7YmeTwnudJHtlPEH/LOT99w= -github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes v0.27.0/go.mod h1:VRo4D6rj92AExpVBlq3Gcuol9Nm1bber12KyxRjKGWw= -github.com/DataDog/sketches-go v1.4.7 h1:eHs5/0i2Sdf20Zkj0udVFWuCrXGRFig2Dcfm5rtcTxc= -github.com/DataDog/sketches-go v1.4.7/go.mod h1:eAmQ/EBmtSO+nQp7IZMZVRPT4BQTmIc5RZQ+deGlTPM= -github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= -github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= 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/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= -github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= -github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= -github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= 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/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= -github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= -github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= -github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= -github.com/aws/aws-sdk-go-v2 v1.39.2 h1:EJLg8IdbzgeD7xgvZ+I8M1e0fL0ptn/M47lianzth0I= -github.com/aws/aws-sdk-go-v2 v1.39.2/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc= -github.com/aws/aws-sdk-go-v2/config v1.31.11 h1:6QOO1mP0MgytbfKsL/r/gE1P6/c/4pPzrrU3hKxa5fs= -github.com/aws/aws-sdk-go-v2/config v1.31.11/go.mod h1:KzpDsPX/dLxaUzoqM3sN2NOhbQIW4HW/0W8rQA1YFEs= -github.com/aws/aws-sdk-go-v2/credentials v1.18.15 h1:Gqy7/05KEfUSulSvwxnB7t8DuZMR3ShzNcwmTD6HOLU= -github.com/aws/aws-sdk-go-v2/credentials v1.18.15/go.mod h1:VWDWSRpYHjcjURRaQ7NUzgeKFN8Iv31+EOMT/W+bFyc= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9 h1:Mv4Bc0mWmv6oDuSWTKnk+wgeqPL5DRFu5bQL9BGPQ8Y= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9/go.mod h1:IKlKfRppK2a1y0gy1yH6zD+yX5uplJ6UuPlgd48dJiQ= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 h1:se2vOWGD3dWQUtfn4wEjRQJb1HK1XsNIt825gskZ970= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9/go.mod h1:hijCGH2VfbZQxqCDN7bwz/4dzxV+hkyhjawAtdPWKZA= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 h1:6RBnKZLkJM4hQ+kN6E7yWFveOTg8NLPHAkqrs4ZPlTU= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9/go.mod h1:V9rQKRmK7AWuEsOMnHzKj8WyrIir1yUJbZxDuZLFvXI= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25 h1:r67ps7oHCYnflpgDy2LZU0MAQtQbYIOqNNnqGO6xQkE= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25/go.mod h1:GrGY+Q4fIokYLtjCVB/aFfCVL6hhGUFl8inD18fDalE= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.31.1 h1:dZXY07Dm59TxAjJcUfNMJHLDI/gLMxTRZefn2jFAVsw= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.31.1/go.mod h1:lVLqEtX+ezgtfalyJs7Peb0uv9dEpAQP5yuq2O26R44= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6 h1:HCpPsWqmYQieU7SS6E9HXfdAMSud0pteVXieJmcpIRI= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6/go.mod h1:ngUiVRCco++u+soRRVBIvBZxSMMvOVMXA4PJ36JLfSw= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.6 h1:6tayEze2Y+hiL3kdnEUxSPsP+pJsUfwLSFspFl1ru9Q= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.6/go.mod h1:qVNb/9IOVsLCZh0x2lnagrBwQ9fxajUpXS7OZfIsKn0= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 h1:5r34CgVOD4WZudeEKZ9/iKpiT6cM1JyEROpXjOcdWv8= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9/go.mod h1:dB12CEbNWPbzO2uC6QSWHteqOg4JfBVJOojbAoAUb5I= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6 h1:BbGDtTi0T1DYlmjBiCr/le3wzhA37O8QTC5/Ab8+EXk= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6/go.mod h1:hLMJt7Q8ePgViKupeymbqI0la+t9/iYFBjxQCFwuAwI= -github.com/aws/aws-sdk-go-v2/service/pricing v1.24.6 h1:szjboYLF1w4WLtm/UH33NRPSdpXvAk1IXBszp/KTGqk= -github.com/aws/aws-sdk-go-v2/service/pricing v1.24.6/go.mod h1:A8YqLVVssHNWJrTuFSPjeRT2+TqIkXPrFa8c/C8E5pA= -github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0 h1:nyuzXooUNJexRT0Oy0UQY6AhOzxPxhtt4DcBIHyCnmw= -github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0/go.mod h1:sT/iQz8JK3u/5gZkT+Hmr7GzVZehUMkRZpOaAwYXeGY= -github.com/aws/aws-sdk-go-v2/service/sqs v1.31.4 h1:mE2ysZMEeQ3ulHWs4mmc4fZEhOfeY1o6QXAfDqjbSgw= -github.com/aws/aws-sdk-go-v2/service/sqs v1.31.4/go.mod h1:lCN2yKnj+Sp9F6UzpoPPTir+tSaC9Jwf6LcmTqnXFZw= -github.com/aws/aws-sdk-go-v2/service/sso v1.29.5 h1:WwL5YLHabIBuAlEKRoLgqLz1LxTvCEpwsQr7MiW/vnM= -github.com/aws/aws-sdk-go-v2/service/sso v1.29.5/go.mod h1:5PfYspyCU5Vw1wNPsxi15LZovOnULudOQuVxphSflQA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1 h1:5fm5RTONng73/QA73LhCNR7UT9RpFH3hR6HWL6bIgVY= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1/go.mod h1:xBEjWD13h+6nq+z4AkqSfSvqRKFgDIQeaMguAJndOWo= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 h1:p3jIvqYwUZgu/XYeI48bJxOhvm47hZb5HUQ0tn6Q9kA= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.6/go.mod h1:WtKK+ppze5yKPkZ0XwqIVWD4beCwv056ZbPQNoeHqM8= -github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE= -github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= 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/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 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/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0= -github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= -github.com/bojanz/currency v1.3.1 h1:3BUAvy/5hU/Pzqg5nrQslVihV50QG+A2xKPoQw1RKH4= -github.com/bojanz/currency v1.3.1/go.mod h1:jNoZiJyRTqoU5DFoa+n+9lputxPUDa8Fz8BdDrW06Go= -github.com/brevdev/cloud v0.0.0-20251215210807-d272e4e4e8ff h1:bvEIkBovSDRl+RE3pIVbtZMqM5xsoN1m04pP8MO45W8= -github.com/brevdev/cloud v0.0.0-20251215210807-d272e4e4e8ff/go.mod h1:8k/vlciWQVfhAgXRziX/3GuOmQMcP1Q8KwlC1Uptj/o= -github.com/brevdev/dev-plane v0.5.1665 h1:02x+bXlSmQNDYQPWNQonvSJYykIuevh/9+E30Q7dDcA= -github.com/brevdev/dev-plane v0.5.1665/go.mod h1:diuBUR26OgEg588+WZwLlYKn+qdu+ZPmRdV8n5C7fhw= +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/brevdev/verb v0.3.17 h1:o373MEFqxYYXPMyDOu/fxHQrrcshnYvA7ymUkolZ+FM= -github.com/brevdev/verb v0.3.17/go.mod h1:TMQ2D4GKD4NhvXmzNz+sACDYqN23BO+qiJiEB4pyzWY= github.com/briandowns/spinner v1.16.0 h1:DFmp6hEaIx2QXXuqSJmtfSBSAjRmpGiKG6ip2Wm/yOs= github.com/briandowns/spinner v1.16.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ= -github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= -github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= -github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= -github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= -github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= -github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= 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/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= -github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/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.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= @@ -173,20 +51,14 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk 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/cihub/seelog v0.0.0-20170130134532-f561c5e57575 h1:kHaBemcxl8o/pQ5VM1c8PVE1PubbNx3mjUr09OqWGCs= -github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575/go.mod h1:9d6lWj8KzO/fd/NrVaLscBKmPigpZpn5YawRPw+e3Yo= 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= -github.com/cloudflare/cloudflare-go v0.94.0 h1:WADmVhCdnn1A9sm5NU08by49Vbh4Lj/JBgTWTr7q7Qc= -github.com/cloudflare/cloudflare-go v0.94.0/go.mod h1:N1u1cLZ4lG6NeezGOWi7P6aq1DK2iVYg9ze7GZbUmZE= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 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/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= -github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.20 h1:VIPb/a2s17qNeQgDnkfZC35RScx+blkKF8GV68n80J4= github.com/creack/pty v1.1.20/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= @@ -196,16 +68,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= -github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/ebitengine/purego v0.8.3 h1:K+0AjQp63JEZTEMZiwsI9g0+hAMNohwUOtY0RPGexmc= -github.com/ebitengine/purego v0.8.3/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= 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.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= @@ -216,14 +78,9 @@ 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/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw= -github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 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/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 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= @@ -250,18 +107,8 @@ 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-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= -github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= -github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= 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= @@ -304,13 +151,8 @@ github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBEx github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 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/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= -github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= 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= @@ -319,9 +161,6 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU 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.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= -github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= github.com/golang/protobuf v1.2.0/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= @@ -332,10 +171,6 @@ 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.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.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 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= @@ -346,25 +181,15 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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/go-github/v45 v45.2.0 h1:5oRLszbrkvxDDqBCNj2hjDZMKmvexaZ1xw/FCD+K3FI= -github.com/google/go-github/v45 v45.2.0/go.mod h1:FObaZJEDSTa/WGCzZ2Z3eoCDXWJKMenWWTrd8jrta28= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.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/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.0.0/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/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= @@ -373,27 +198,15 @@ github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5T 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/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= -github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= 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= -github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= -github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= 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-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= -github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= 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/hashicorp/hcl/v2 v2.13.0 h1:0Apadu1w6M11dyGFxWnmhhcMjkbAiKCv7G1r/2QgCNc= -github.com/hashicorp/hcl/v2 v2.13.0/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0= 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.4.0 h1:BvhqnH0JAYbNudL2GMJKgOHe2CtKlzJ/5rWKyp+hc2k= @@ -404,10 +217,6 @@ github.com/jedib0t/go-pretty/v6 v6.3.1 h1:aOXiD9oqiuLH8btPQW6SfgtQN5zwhyfzZls8a6 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/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= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 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/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= @@ -417,14 +226,10 @@ github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4 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/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 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/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -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.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -432,19 +237,10 @@ 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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 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/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 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/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= -github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= -github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc= -github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= -github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= 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= @@ -459,14 +255,10 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D 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/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= -github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 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/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 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= @@ -481,66 +273,32 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/ 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/nexus-rpc/sdk-go v0.3.0 h1:Y3B0kLYbMhd4C2u00kcYajvmOrfozEtTV/nHSnV57jA= -github.com/nexus-rpc/sdk-go v0.3.0/go.mod h1:TpfkM2Cw0Rlk9drGkoiSMpFqflKTiQLWUNyKJjF8mKQ= -github.com/ns1/ns1-go v2.4.0+incompatible h1:WYLNc1preJKfVVJL0zCOXfePlREOwrpJrDhJNn1nnLI= -github.com/ns1/ns1-go v2.4.0+incompatible/go.mod h1:+5cGIDXMoO+J3+C8FJ8J0xkyiTdgCvDA+JXQ7f1cPKs= 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/open-telemetry/opentelemetry-collector-contrib/pkg/sampling v0.125.0 h1:0dOJCEtabevxxDQmxed69oMzSw+gb3ErCnFwFYZFu0M= -github.com/open-telemetry/opentelemetry-collector-contrib/pkg/sampling v0.125.0/go.mod h1:QwzQhtxPThXMUDW1XRXNQ+l0GrI2BRsvNhX6ZuKyAds= -github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor v0.125.0 h1:F68/Nbpcvo3JZpaWlRUDJtG7xs8FHBZ7A8GOMauDkyc= -github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor v0.125.0/go.mod h1:haO4cJtAk05Y0p7NO9ME660xxtSh54ifCIIT7+PO9C0= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/outcaste-io/ristretto v0.2.3 h1:AK4zt/fJ76kjlYObOeNwh4T3asEuaCmp26pOvUOL9w0= -github.com/outcaste-io/ristretto v0.2.3/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkLAr0VN/s4+MHac= -github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= -github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= 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= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY= -github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= 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-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= 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/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg= -github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= -github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= -github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= -github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= -github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= -github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= -github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= -github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= 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/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3 h1:4+LEVOB87y175cLJC/mbsgKmoDOjrBldtXvioEy96WY= -github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3/go.mod h1:vl5+MqJ1nBINuSsUI2mGgH79UweUT/B5Fy8857PqyyI= 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 v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= -github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= 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.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -556,17 +314,10 @@ 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= github.com/schollz/progressbar/v3 v3.9.0/go.mod h1:W5IEwbJecncFGBvuEh4A7HT1nZZ6WNIL2i3qbnI0WKY= -github.com/secure-systems-lab/go-securesystemslib v0.9.0 h1:rf1HIbL64nUpEIZnjLZ3mcNEL9NBPB0iuVjyxvq3LZc= -github.com/secure-systems-lab/go-securesystemslib v0.9.0/go.mod h1:DVHKMcZ+V4/woA/peqr+L0joiRXbPpQ042GgJckkFgw= -github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= -github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sevlyar/go-daemon v0.1.5 h1:Zy/6jLbM8CfqJ4x4RPr7MJlSKt90f00kNM1D401C+Qk= github.com/sevlyar/go-daemon v0.1.5/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE= -github.com/shirou/gopsutil/v4 v4.25.3 h1:SeA68lsu8gLggyMbmCn8cmp97V1TI9ld9sVzAUcKcKE= -github.com/shirou/gopsutil/v4 v4.25.3/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 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.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -575,10 +326,6 @@ github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0 github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= 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/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= -github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= -github.com/spf13/afero v1.5.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= 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= @@ -590,10 +337,7 @@ 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/srikrsna/protoc-gen-gotag v0.6.2 h1:ULdarjI7FNUA6CNlLPIzSNvjdV2P4C2LSygPLvCVtfA= -github.com/srikrsna/protoc-gen-gotag v0.6.2/go.mod h1:cplWV0ZNBhuF54gnj6rU9pLNrqjXf5vh65Xqa1Kjy+4= 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= @@ -601,8 +345,6 @@ 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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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= @@ -611,12 +353,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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/stripe/stripe-go/v79 v79.2.0 h1:IU27tefsCor2P9ZOBvTn1Ra7WPd52Qz5ymuFQIeBLqg= -github.com/stripe/stripe-go/v79 v79.2.0/go.mod h1:cuH6X0zC8peY6f1AubHwgJ/fJSn2dh5pfiCr6CjyKVU= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/theckman/httpforwarded v0.4.0 h1:N55vGJT+6ojTnLY3LQCNliJC4TW0P0Pkeys1G1WpX2w= -github.com/theckman/httpforwarded v0.4.0/go.mod h1:GVkFynv6FJreNbgH/bpOU9ITDZ7a5WuzdNCtIMI1pVI= 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= @@ -624,30 +362,12 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT 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/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po= -github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= -github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= -github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= -github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= -github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= 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= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.2 h1:USRngIQppxeyb39XzkVHXwQesKK0+JSwnHE/1c7fgic= -github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.2/go.mod h1:1frv9RN1rlTq0jzCq+mVuEQisubZCQ4OU6S/8CaHzGY= -github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.0 h1:iIY5MNcGJp3frOx7cPq4Xc6CFthI8pjpHGAeZ230pVM= -github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.0/go.mod h1:aiX/F5+EYbY2ed2OQEYRXzMcNGvI9pip5gW2ZtBDers= -github.com/uptrace/opentelemetry-go-extra/otelzap v0.3.0 h1:sj/jUvwHdJTGPeilGnLcRgTI/OaBcaOqVymBKEmSH54= -github.com/uptrace/opentelemetry-go-extra/otelzap v0.3.0/go.mod h1:hDQ7Ntn6wEgjwtH7xJ1B235xNHhFffl7GHmZqs3WcK8= -github.com/vertoforce/go-emailrep v0.0.0-20220319001959-76d0921a0d9c h1:FUMp0n9BZL+c+j4l99Z6xSvB+4H1s0ZXhMTiCcq9Hbg= -github.com/vertoforce/go-emailrep v0.0.0-20220319001959-76d0921a0d9c/go.mod h1:kXw8KbVnFSZxdpnSdaFLK3XrdBu9QZfUSTlDPgVGMvU= -github.com/vmihailenco/msgpack/v4 v4.3.13 h1:A2wsiTbvp63ilDaWmsk2wjx6xZdxQOvpiNlKBGKKXKI= -github.com/vmihailenco/msgpack/v4 v4.3.13/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= -github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc= -github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/wk8/go-ordered-map/v2 v2.0.0 h1:jWOAU/F5AkYb8jr/rkVPe418g7nf2CZBzyfOR4Y7Q1w= github.com/wk8/go-ordered-map/v2 v2.0.0/go.mod h1:fGIuB3GmY3JZP6L3t5riKtaSH9u13IYVYvar5Ee+9lM= github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6FkrCNfXkvbR+Nbu1ls48pXYcw= @@ -656,114 +376,16 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 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/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -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/xeonx/timeago v1.0.0-rc5 h1:pwcQGpaH3eLfPtXeyPA4DmHWjoQt0Ea7/++FwpxqLxg= -github.com/xeonx/timeago v1.0.0-rc5/go.mod h1:qDLrYEFynLO7y5Ho7w3GwgtYgpy5UfhcXIIQvMKVDkA= 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.27/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/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= -github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= -github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= -github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0= -github.com/zclconf/go-cty-yaml v1.1.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/collector/component v1.31.0 h1:9LzU8X1RhV3h8/QsAoTX23aFUfoJ3EUc9O/vK+hFpSI= -go.opentelemetry.io/collector/component v1.31.0/go.mod h1:JbZl/KywXJxpUXPbt96qlEXJSym1zQ2hauMxYMuvlxM= -go.opentelemetry.io/collector/component/componentstatus v0.125.0 h1:zlxGQZYd9kknRZSjRpOYW5SBjl0a5zYFYRPbreobXoU= -go.opentelemetry.io/collector/component/componentstatus v0.125.0/go.mod h1:bHXc2W8bqqo9adOvCgvhcO7pYzJOSpyV4cuQ1wiIl04= -go.opentelemetry.io/collector/component/componenttest v0.125.0 h1:E2mpnMQbkMpYoZ3Q8pHx4kod7kedjwRs1xqDpzCe/84= -go.opentelemetry.io/collector/component/componenttest v0.125.0/go.mod h1:pQtsE1u/SPZdTphP5BZP64XbjXSq6wc+mDut5Ws/JDI= -go.opentelemetry.io/collector/consumer v1.31.0 h1:L+y66ywxLHnAxnUxv0JDwUf5bFj53kMxCCyEfRKlM7s= -go.opentelemetry.io/collector/consumer v1.31.0/go.mod h1:rPsqy5ni+c6xNMUkOChleZYO/nInVY6eaBNZ1FmWJVk= -go.opentelemetry.io/collector/consumer/consumertest v0.125.0 h1:TUkxomGS4DAtjBvcWQd2UY4FDLLEKMQD6iOIDUr/5dM= -go.opentelemetry.io/collector/consumer/consumertest v0.125.0/go.mod h1:vkHf3y85cFLDHARO/cTREVjLjOPAV+cQg7lkC44DWOY= -go.opentelemetry.io/collector/consumer/xconsumer v0.125.0 h1:oTreUlk1KpMSWwuHFnstW+orrjGTyvs2xd3o/Dpy+hI= -go.opentelemetry.io/collector/consumer/xconsumer v0.125.0/go.mod h1:FX0G37r0W+wXRgxxFtwEJ4rlsCB+p0cIaxtU3C4hskw= -go.opentelemetry.io/collector/featuregate v1.31.0 h1:20q7plPQZwmAiaYAa6l1m/i2qDITZuWlhjr4EkmeQls= -go.opentelemetry.io/collector/featuregate v1.31.0/go.mod h1:Y/KsHbvREENKvvN9RlpiWk/IGBK+CATBYzIIpU7nccc= -go.opentelemetry.io/collector/internal/telemetry v0.125.0 h1:6lcGOxw3dAg7LfXTKdN8ZjR+l7KvzLdEiPMhhLwG4r4= -go.opentelemetry.io/collector/internal/telemetry v0.125.0/go.mod h1:5GyFslLqjZgq1DZTtFiluxYhhXrCofHgOOOybodDPGE= -go.opentelemetry.io/collector/pdata v1.31.0 h1:P5WuLr1l2JcIvr6Dw2hl01ltp2ZafPnC4Isv+BLTBqU= -go.opentelemetry.io/collector/pdata v1.31.0/go.mod h1:m41io9nWpy7aCm/uD1L9QcKiZwOP0ldj83JEA34dmlk= -go.opentelemetry.io/collector/pdata/pprofile v0.125.0 h1:Qqlx8w1HpiYZ9RQqjmMQIysI0cHNO1nh3E/fCTeFysA= -go.opentelemetry.io/collector/pdata/pprofile v0.125.0/go.mod h1:p/yK023VxAp8hm27/1G5DPTcMIpnJy3cHGAFUQZGyaQ= -go.opentelemetry.io/collector/pdata/testdata v0.125.0 h1:due1Hl0EEVRVwfCkiamRy5E8lS6yalv0lo8Zl/SJtGw= -go.opentelemetry.io/collector/pdata/testdata v0.125.0/go.mod h1:1GpEWlgdMrd+fWsBk37ZC2YmOP5YU3gFQ4rWuCu9g24= -go.opentelemetry.io/collector/pipeline v0.125.0 h1:oitBgcAFqntDB4ihQJUHJSQ8IHqKFpPkaTVbTYdIUzM= -go.opentelemetry.io/collector/pipeline v0.125.0/go.mod h1:TO02zju/K6E+oFIOdi372Wk0MXd+Szy72zcTsFQwXl4= -go.opentelemetry.io/collector/processor v1.31.0 h1:+u7sBUpnCBsHYoALp4hfr9VEjLHHYa4uKENGITe0K9Q= -go.opentelemetry.io/collector/processor v1.31.0/go.mod h1:5hDYJ7/hTdfd2tF2Rj5Hs6+mfyFz2O7CaPzVvW1qHQc= -go.opentelemetry.io/collector/processor/processorhelper v0.125.0 h1:QRpX7oFW88DAZhy+Q93npklRoaQr8ue0GKpeup7C/Fk= -go.opentelemetry.io/collector/processor/processorhelper v0.125.0/go.mod h1:oXRvslUuN62wErcoJrcEJYoTXu5wHyNyJsE+/a9Cc9s= -go.opentelemetry.io/collector/processor/processortest v0.125.0 h1:ZVAN4iZPDcWhpzKqnuok2NIuS5hwGVVQUOWkJFR12tA= -go.opentelemetry.io/collector/processor/processortest v0.125.0/go.mod h1:VAw0IRG35cWTBjBtreXeXJEgqkRegfjrH/EuLhNX2+I= -go.opentelemetry.io/collector/processor/xprocessor v0.125.0 h1:VWYPMW1VmDq6xB7M5SYjBpQCCIq3MhQ3W++wU47QpZM= -go.opentelemetry.io/collector/processor/xprocessor v0.125.0/go.mod h1:bCxUyFVlksANg8wjYZqWVsRB33lkLQ294rTrju/IZiM= -go.opentelemetry.io/collector/semconv v0.125.0 h1:SyRP617YGvNSWRSKMy7Lbk9RaJSR+qFAAfyxJOeZe4s= -go.opentelemetry.io/collector/semconv v0.125.0/go.mod h1:te6VQ4zZJO5Lp8dM2XIhDxDiL45mwX0YAQQWRQ0Qr9U= -go.opentelemetry.io/contrib/bridges/otelzap v0.10.0 h1:ojdSRDvjrnm30beHOmwsSvLpoRF40MlwNCA+Oo93kXU= -go.opentelemetry.io/contrib/bridges/otelzap v0.10.0/go.mod h1:oTTm4g7NEtHSV2i/0FeVdPaPgUIZPfQkFbq0vbzqnv0= -go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.46.1 h1:PGmSzEMllKQwBQHe9SERAsCytvgLhsb8OrRLeW+40xw= -go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.46.1/go.mod h1:h0dNRrQsnnlMonPE/+FXrXtDYZEyZSTaIOfs+n8P/RQ= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= -go.opentelemetry.io/contrib/processors/baggagecopy v0.11.0 h1:kCgcpaw83eiQq3q9kC0mlSF+2/GFj979aphWGlHmxRw= -go.opentelemetry.io/contrib/processors/baggagecopy v0.11.0/go.mod h1:HA84H6DSS0J6sbXzDj8bjmrooSK1UhPZvh3Dijltw5A= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0= -go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM= -go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno= -go.opentelemetry.io/otel/log/logtest v0.14.0 h1:BGTqNeluJDK2uIHAY8lRqxjVAYfqgcaTbVk1n3MWe5A= -go.opentelemetry.io/otel/log/logtest v0.14.0/go.mod h1:IuguGt8XVP4XA4d2oEEDMVDBBCesMg8/tSGWDjuKfoA= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg= -go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= -go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg= -go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY= 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.temporal.io/api v1.47.0 h1:0pg8wZC9Jv79iMpe6jXMPQzADQJ5OiPuklYfC51bXGM= -go.temporal.io/api v1.47.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= -go.temporal.io/sdk v1.33.1 h1:eZx3frTgCVWL4pubVVg2Ok+xjfyJiAvjAN7102JwXxs= -go.temporal.io/sdk v1.33.1/go.mod h1:WwCmJZLy7zabz3ar5NRAQEygsdP8tgR9sDjISSHuWZw= -go.temporal.io/sdk/contrib/opentelemetry v0.6.0 h1:rNBArDj5iTUkcMwKocUShoAW59o6HdS7Nq4CTp4ldj8= -go.temporal.io/sdk/contrib/opentelemetry v0.6.0/go.mod h1:Lem8VrE2ks8P+FYcRM3UphPoBr+tfM3v/Kaf0qStzSg= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -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/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= 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.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 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.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= 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= @@ -774,7 +396,6 @@ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUu 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-20190820162420-60c769a6c586/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-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -787,13 +408,8 @@ golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1Rac 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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 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.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= 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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -803,9 +419,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 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.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= @@ -818,7 +431,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ 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-20201020160332-67f06af15bc9/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.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= @@ -827,25 +439,17 @@ golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5h 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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/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-20201204225414-ed752295db88/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-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-20210615035016-665e8c7367d1/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-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/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-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/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= @@ -860,9 +464,7 @@ 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.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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= @@ -872,43 +474,26 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm 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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/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-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= 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= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= 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.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200324203455-a04cca1dde73/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= -google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 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.27.0/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.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= -google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= 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= @@ -917,13 +502,9 @@ 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.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 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/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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= @@ -936,14 +517,11 @@ 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-20210107192922-496545a6307b/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= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= diff --git a/pkg/brevdaemon/agent/client/client.go b/pkg/brevdaemon/agent/client/client.go index f43f9860..007f8472 100644 --- a/pkg/brevdaemon/agent/client/client.go +++ b/pkg/brevdaemon/agent/client/client.go @@ -11,7 +11,7 @@ import ( devplaneapiv1 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/devplaneapi/v1" "connectrpc.com/connect" "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/config" - "github.com/brevdev/brev-cli/pkg/brevdaemon/provider" + "github.com/brevdev/dev-plane/pkg/brevcloud/tunnel" "github.com/brevdev/brev-cli/pkg/errors" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -80,7 +80,7 @@ type TunnelTokenParams struct { BrevCloudNodeID string DeviceToken string TunnelName string - Ports []provider.TunnelPortMapping + Ports []tunnel.TunnelPortMapping AppIngresses []AppIngress } @@ -497,7 +497,7 @@ func gpuUtilizationToProto(gpu GPUUtilization) *brevapiv2.GPUUtilization { return out } -func tunnelPortsToProto(ports []provider.TunnelPortMapping) []*brevapiv2.TunnelPortMapping { +func tunnelPortsToProto(ports []tunnel.TunnelPortMapping) []*brevapiv2.TunnelPortMapping { if len(ports) == 0 { return nil } diff --git a/pkg/brevdaemon/agent/client/client_test.go b/pkg/brevdaemon/agent/client/client_test.go index 05b36410..c15c60e2 100644 --- a/pkg/brevdaemon/agent/client/client_test.go +++ b/pkg/brevdaemon/agent/client/client_test.go @@ -12,7 +12,7 @@ import ( 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/dev-plane/internal/brevcloud/provider" + "github.com/brevdev/dev-plane/pkg/brevcloud/tunnel" "github.com/stretchr/testify/require" "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" @@ -183,7 +183,7 @@ func TestGetTunnelTokenSendsPortsAndAuth(t *testing.T) { BrevCloudNodeID: "fn-1", DeviceToken: "device-token", TunnelName: "default", - Ports: []provider.TunnelPortMapping{ + Ports: []tunnel.TunnelPortMapping{ {LocalPort: 22}, {RemotePort: 8080}, }, diff --git a/pkg/brevdaemon/agent/identity/identity.go b/pkg/brevdaemon/agent/identity/identity.go index 49b8094c..15db0566 100644 --- a/pkg/brevdaemon/agent/identity/identity.go +++ b/pkg/brevdaemon/agent/identity/identity.go @@ -280,7 +280,7 @@ func ensureDeviceSalt(path string) (string, error) { } func computeHardwareFingerprint(hw telemetry.HardwareInfo) (string, error) { - desc := brevcloud.HardwareDescriptor{ + desc := agent.HardwareDescriptor{ CPUs: hw.CPUCount, RAM: hw.RAMBytes, } @@ -290,13 +290,13 @@ func computeHardwareFingerprint(hw telemetry.HardwareInfo) (string, error) { count = 1 } for i := 0; i < count; i++ { - desc.GPUs = append(desc.GPUs, brevcloud.GPUDescriptor{ + desc.GPUs = append(desc.GPUs, agent.GPUDescriptor{ Model: gpu.Model, Memory: gpu.MemoryBytes, }) } } - fp, err := brevcloud.ComputeHardwareFingerprint(desc) + fp, err := agent.ComputeHardwareFingerprint(desc) if err != nil { return "", errors.WrapAndTrace(err) } diff --git a/pkg/brevdaemon/agent/tunnel/ingress.go b/pkg/brevdaemon/agent/tunnel/ingress.go index 808cd754..d87b813a 100644 --- a/pkg/brevdaemon/agent/tunnel/ingress.go +++ b/pkg/brevdaemon/agent/tunnel/ingress.go @@ -11,6 +11,7 @@ import ( "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/client" "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/telemetry" + "github.com/brevdev/dev-plane/pkg/brevcloud/appaccess" "github.com/brevdev/dev-plane/pkg/errors" ) diff --git a/pkg/brevdaemon/agent/tunnel/ingress_test.go b/pkg/brevdaemon/agent/tunnel/ingress_test.go index 1164a42c..966dc150 100644 --- a/pkg/brevdaemon/agent/tunnel/ingress_test.go +++ b/pkg/brevdaemon/agent/tunnel/ingress_test.go @@ -8,7 +8,7 @@ import ( "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/dev-plane/internal/appaccess" + "github.com/brevdev/dev-plane/pkg/brevcloud/appaccess" "github.com/brevdev/dev-plane/pkg/errors" "github.com/stretchr/testify/require" "go.uber.org/zap" diff --git a/pkg/brevdaemon/agent/tunnel/tunnel.go b/pkg/brevdaemon/agent/tunnel/tunnel.go index c7e0cda1..b3ae33a8 100644 --- a/pkg/brevdaemon/agent/tunnel/tunnel.go +++ b/pkg/brevdaemon/agent/tunnel/tunnel.go @@ -13,7 +13,8 @@ import ( "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/brevdaemon/provider" + "github.com/brevdev/dev-plane/pkg/brevcloud/appaccess" + "github.com/brevdev/dev-plane/pkg/brevcloud/tunnel" "github.com/brevdev/dev-plane/pkg/errors" "go.uber.org/zap" ) @@ -100,7 +101,7 @@ func (m *Manager) Start(ctx context.Context) error { //nolint:gocognit,gocyclo,f BrevCloudNodeID: m.Identity.InstanceID, DeviceToken: m.Identity.DeviceToken, TunnelName: defaultTunnelName, - Ports: []provider.TunnelPortMapping{ + Ports: []tunnel.TunnelPortMapping{ { LocalPort: m.Cfg.SSHPort, }, diff --git a/pkg/brevdaemon/provider/tunnel.go b/pkg/brevdaemon/provider/tunnel.go deleted file mode 100644 index f72e8c4d..00000000 --- a/pkg/brevdaemon/provider/tunnel.go +++ /dev/null @@ -1,8 +0,0 @@ -package provider - -// TunnelPortMapping describes how a local port is exposed through the tunnel. -type TunnelPortMapping struct { - LocalPort int - RemotePort int - Protocol string -} From ff7be4e0011905da291f751404b397086a8295c5 Mon Sep 17 00:00:00 2001 From: Pratik Patel Date: Wed, 7 Jan 2026 15:34:57 -0800 Subject: [PATCH 05/22] fix make --- Makefile | 18 ++++++++-------- pkg/brevdaemon/agent/heartbeat/heartbeat.go | 24 ++++++++++----------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Makefile b/Makefile index f459f671..90f747de 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 diff --git a/pkg/brevdaemon/agent/heartbeat/heartbeat.go b/pkg/brevdaemon/agent/heartbeat/heartbeat.go index 44791237..14015378 100644 --- a/pkg/brevdaemon/agent/heartbeat/heartbeat.go +++ b/pkg/brevdaemon/agent/heartbeat/heartbeat.go @@ -44,10 +44,10 @@ func (r *Runner) Run(ctx context.Context) error { //nolint:gocyclo,funlen // loo return errors.WrapAndTrace(err) } - sampleFn := r.SampleUtilization - if sampleFn == nil { - sampleFn = telemetry.SampleUtilization - } + // sampleFn := r.SampleUtilization + // if sampleFn == nil { + // sampleFn = telemetry.SampleUtilization + // } sleepFn := r.Sleep if sleepFn == nil { sleepFn = defaultSleep @@ -90,19 +90,19 @@ func (r *Runner) Run(ctx context.Context) error { //nolint:gocyclo,funlen // loo } } - 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 - } + // 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 + // } params := client.HeartbeatParams{ BrevCloudNodeID: r.Identity.InstanceID, DeviceToken: r.Identity.DeviceToken, ObservedAt: nowFn(), - Utilization: util.ToClient(), + // Utilization: util.ToClient(), AgentVersion: "", // set by orchestrator later Status: currentStatusPtr(currentStatus), DeviceFingerprintHash: r.Identity.DeviceFingerprintHash, From 2b43f947ad180c7252816cdf6832eb47286dedb7 Mon Sep 17 00:00:00 2001 From: Pratik Patel Date: Wed, 7 Jan 2026 19:19:03 -0800 Subject: [PATCH 06/22] fixing enroll --- .gitignore | 1 + brevcloud-scripts/brevd/brevcloud-user.sh | 22 -- brevcloud-scripts/brevd/enroll.sh | 159 --------- pkg/brevcloud/client.go | 27 +- pkg/brevdaemon/agent.go | 2 +- pkg/brevdaemon/agent/client/client.go | 2 +- pkg/brevdaemon/agent/heartbeat/heartbeat.go | 6 +- pkg/brevdaemon/agent/telemetry/os_consts.go | 6 +- pkg/brevdaemon/agent/tunnel/tunnel.go | 10 +- pkg/cmd/register/register.go | 2 +- pkg/cmd/spark/enroll.go | 353 ++++---------------- pkg/cmd/spark/install-binary.sh | 1 - pkg/cmd/spark/install-user.sh | 34 ++ pkg/cmd/spark/spark.go | 1 - pkg/cmd/spark/ssh.go | 67 ---- pkg/spark/ssh_runner.go | 88 ----- pkg/spark/ssh_runner_test.go | 76 ----- 17 files changed, 131 insertions(+), 726 deletions(-) delete mode 100644 brevcloud-scripts/brevd/brevcloud-user.sh delete mode 100644 brevcloud-scripts/brevd/enroll.sh create mode 100644 pkg/cmd/spark/install-user.sh delete mode 100644 pkg/cmd/spark/ssh.go delete mode 100644 pkg/spark/ssh_runner.go delete mode 100644 pkg/spark/ssh_runner_test.go 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/brevcloud-scripts/brevd/brevcloud-user.sh b/brevcloud-scripts/brevd/brevcloud-user.sh deleted file mode 100644 index 0d1de992..00000000 --- a/brevcloud-scripts/brevd/brevcloud-user.sh +++ /dev/null @@ -1,22 +0,0 @@ -export BREV_USER=brevcloud -id -u $BREV_USER >/dev/null 2>&1 || sudo useradd -m -s /bin/bash $BREV_USER -sudo chmod 0700 /home/$BREV_USER -export BREV_HOME=/home/$BREV_USER -echo "$BREV_USER ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/$BREV_USER >/dev/null -sudo chmod 0440 /etc/sudoers.d/$BREV_USER -sudo visudo -c -f /etc/sudoers.d/$BREV_USER -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 mkdir $BREV_HOME/.ssh -sudo touch $BREV_HOME/.ssh/authorized_keys -sudo chmod 700 $BREV_HOME/.ssh -sudo chmod 600 $BREV_HOME/.ssh/authorized_keys -sudo chown -R $BREV_USER:$BREV_USER /home/$BREV_USER/.ssh -export BREV_KEY='' -sudo grep -qxF "$BREV_KEY" $BREV_HOME/.ssh/authorized_keys || echo "$BREV_KEY" | sudo tee -a $BREV_HOME/.ssh/authorized_keys >/dev/null -cat /home/$BREV_USER/.ssh/authorized_keys -sudo cat /home/$BREV_USER/.ssh/authorized_keys -sudo chown -R $BREV_USER:$BREV_USER $BREV_HOME -sudo -u $BREV_USER whoami -sudo -u $BREV_USER ls -la $BREV_HOME \ No newline at end of file diff --git a/brevcloud-scripts/brevd/enroll.sh b/brevcloud-scripts/brevd/enroll.sh deleted file mode 100644 index 04e9eab3..00000000 --- a/brevcloud-scripts/brevd/enroll.sh +++ /dev/null @@ -1,159 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -LOG_DIR=/tmp/brevd -LOG_FILE="${LOG_DIR}/enroll.log" -ENV_FILE="/etc/default/brevd" -SERVICE="brevd" -MOCK=%t - -log() { - local msg="$1" - mkdir -p "${LOG_DIR}" - logger -t brev-enroll "${msg}" 2>/dev/null || true - printf '%s\n' "${msg}" >>"${LOG_FILE}" -} - -run_cmd() { - local cmd="$1" - bash -c "${cmd}" >>"${LOG_FILE}" 2>&1 -} - -ensure_dir() { - local dir="$1" - local cmds=( - "sudo -n mkdir -p \"${dir}\"" - "sudo mkdir -p \"${dir}\"" - "mkdir -p \"${dir}\"" - ) - for c in "${cmds[@]}"; do - if run_cmd "${c}"; then - return 0 - fi - done - log "failed to create dir ${dir}" - exit 1 -} - -write_file() { - local content="$1" - local dest="$2" - local tmp="${dest}.tmp" - - printf '%s\n' "${content}" >"${tmp}" - - local cmds=( - "sudo -n tee \"${dest}\" >/dev/null" - "sudo tee \"${dest}\" >/dev/null" - "tee \"${dest}\" >/dev/null" - ) - for c in "${cmds[@]}"; do - if bash -c "${c}" <"${tmp}" >>"${LOG_FILE}" 2>&1; then - rm -f "${tmp}" - return 0 - fi - done - - log "failed to write file to ${dest}" - rm -f "${tmp}" - exit 1 -} - -set_file_mode() { - local mode="$1" - local path="$2" - local cmds=( - "sudo -n chmod ${mode} \"${path}\"" - "sudo chmod ${mode} \"${path}\"" - "chmod ${mode} \"${path}\"" - ) - for c in "${cmds[@]}"; do - if run_cmd "${c}"; then - return 0 - fi - done - log "failed to chmod ${mode} ${path}" - exit 1 -} - -extract_env_value() { - # Extracts an env value from a string containing KEY=VALUE lines. - # Leaves quoting intact only long enough to strip it; always succeeds. - local key="$1" - local content="$2" - while IFS= read -r line; do - # Skip comments and blank lines. - if [[ -z "${line}" || "${line}" =~ ^[[:space:]]*# ]]; then - continue - fi - # Trim leading spaces. - line="${line#"${line%%[![:space:]]*}"}" - if [[ "${line}" == "${key}="* ]]; then - local val="${line#${key}=}" - # Strip matching quotes. - if [[ "${val}" =~ ^\".*\"$ || "${val}" =~ ^\'.*\'$ ]]; then - val="${val:1:${#val}-2}" - fi - printf '%s' "${val}" - break - fi - done <<<"${content}" - return 0 -} - -restart_service() { - local svc="$1" - local cmds=( - "sudo -n systemctl restart \"${svc}\"" - "sudo systemctl restart \"${svc}\"" - "systemctl restart \"${svc}\"" - ) - for c in "${cmds[@]}"; do - if run_cmd "${c}"; then - return 0 - fi - done - log "failed to restart service ${svc}" - exit 1 -} - -# Populate from caller (brev CLI) via string substitution. -ENV_CONTENT=$(cat <<'EOF' -%s -EOF -) - -log "enroll start" -log "probe: $(uname -a)" -log "user: $(whoami)" -log "host: $(hostname)" -log "env file target: ${ENV_FILE}" - -ensure_dir "$(dirname "${ENV_FILE}")" -write_file "${ENV_CONTENT}" "${ENV_FILE}" -set_file_mode 600 "${ENV_FILE}" - -state_dir="$(extract_env_value "BREV_AGENT_STATE_DIR" "${ENV_CONTENT}")" -device_token_path="$(extract_env_value "BREV_AGENT_DEVICE_TOKEN_PATH" "${ENV_CONTENT}")" - -if [[ -n "${state_dir}" ]]; then - log "ensuring state dir ${state_dir}" - ensure_dir "${state_dir}" -fi - -if [[ -n "${device_token_path}" ]]; then - log "ensuring device token parent dir for ${device_token_path}" - ensure_dir "$(dirname "${device_token_path}")" -elif [[ -n "${state_dir}" ]]; then - # Default device token lives under the state dir. - ensure_dir "${state_dir}" -fi - -if [[ "${MOCK}" == "true" ]]; then - log "mock mode: skipping agent restart" - exit 0 -fi - -restart_service "${SERVICE}" -log "enroll success" - diff --git a/pkg/brevcloud/client.go b/pkg/brevcloud/client.go index 48261686..6f4aafd1 100644 --- a/pkg/brevcloud/client.go +++ b/pkg/brevcloud/client.go @@ -2,7 +2,6 @@ package brevcloud import ( "context" - "fmt" "net/http" "strings" "time" @@ -10,7 +9,9 @@ import ( "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" @@ -20,8 +21,9 @@ import ( // // It intentionally exposes small, CLI-focused types instead of raw protos. type Client struct { - operator brevapiv2connect.BrevCloudOperatorServiceClient - store *store.AuthHTTPStore + operator brevapiv2connect.BrevCloudOperatorServiceClient + cloudCreds devplaneapiv1connect.CloudCredServiceClient + store *store.AuthHTTPStore } // NewClient constructs a BrevCloud client backed by brevapiv2.BrevCloudOperatorService. @@ -179,9 +181,22 @@ type CloudCred struct { // ListCloudCred is a placeholder for future integration with a BrevCloud // cloud credential listing API. Today it returns an error so callers can // fall back to explicit --cloud-cred-id flags. -func (c *Client) ListCloudCred(ctx context.Context) ([]CloudCred, error) { - _ = ctx - return nil, fmt.Errorf("ListCloudCred not implemented") +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 diff --git a/pkg/brevdaemon/agent.go b/pkg/brevdaemon/agent.go index c35bff03..794902a3 100644 --- a/pkg/brevdaemon/agent.go +++ b/pkg/brevdaemon/agent.go @@ -55,4 +55,4 @@ func run(ctx context.Context) (int, error) { } return exitCodeOK, nil -} \ No newline at end of file +} diff --git a/pkg/brevdaemon/agent/client/client.go b/pkg/brevdaemon/agent/client/client.go index 007f8472..1186b2e0 100644 --- a/pkg/brevdaemon/agent/client/client.go +++ b/pkg/brevdaemon/agent/client/client.go @@ -11,8 +11,8 @@ import ( devplaneapiv1 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/devplaneapi/v1" "connectrpc.com/connect" "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/config" - "github.com/brevdev/dev-plane/pkg/brevcloud/tunnel" "github.com/brevdev/brev-cli/pkg/errors" + "github.com/brevdev/dev-plane/pkg/brevcloud/tunnel" "google.golang.org/protobuf/types/known/timestamppb" ) diff --git a/pkg/brevdaemon/agent/heartbeat/heartbeat.go b/pkg/brevdaemon/agent/heartbeat/heartbeat.go index 14015378..1188e63e 100644 --- a/pkg/brevdaemon/agent/heartbeat/heartbeat.go +++ b/pkg/brevdaemon/agent/heartbeat/heartbeat.go @@ -99,9 +99,9 @@ func (r *Runner) Run(ctx context.Context) error { //nolint:gocyclo,funlen // loo // } params := client.HeartbeatParams{ - BrevCloudNodeID: r.Identity.InstanceID, - DeviceToken: r.Identity.DeviceToken, - ObservedAt: nowFn(), + BrevCloudNodeID: r.Identity.InstanceID, + DeviceToken: r.Identity.DeviceToken, + ObservedAt: nowFn(), // Utilization: util.ToClient(), AgentVersion: "", // set by orchestrator later Status: currentStatusPtr(currentStatus), diff --git a/pkg/brevdaemon/agent/telemetry/os_consts.go b/pkg/brevdaemon/agent/telemetry/os_consts.go index fb812702..66b8a49f 100644 --- a/pkg/brevdaemon/agent/telemetry/os_consts.go +++ b/pkg/brevdaemon/agent/telemetry/os_consts.go @@ -1,4 +1,6 @@ package telemetry -const goosLinux = "linux" -const darwin = "darwin" +const ( + goosLinux = "linux" + darwin = "darwin" +) diff --git a/pkg/brevdaemon/agent/tunnel/tunnel.go b/pkg/brevdaemon/agent/tunnel/tunnel.go index b3ae33a8..cd12a5fa 100644 --- a/pkg/brevdaemon/agent/tunnel/tunnel.go +++ b/pkg/brevdaemon/agent/tunnel/tunnel.go @@ -101,7 +101,7 @@ func (m *Manager) Start(ctx context.Context) error { //nolint:gocognit,gocyclo,f BrevCloudNodeID: m.Identity.InstanceID, DeviceToken: m.Identity.DeviceToken, TunnelName: defaultTunnelName, - Ports: []tunnel.TunnelPortMapping{ + Ports: []tunnel.TunnelPortMapping{ { LocalPort: m.Cfg.SSHPort, }, @@ -241,10 +241,10 @@ func (m *Manager) configureCloudflaredService(ctx context.Context, token client. name: "sudo", args: []string{"cloudflared", "service", "install", token.Token}, }, - { - name: "sudo", - args: []string{"systemctl", "restart", "cloudflared.service"}, - }, + // { + // name: "sudo", + // args: []string{"systemctl", "restart", "cloudflared.service"}, + // }, } for _, cmdSpec := range commands { diff --git a/pkg/cmd/register/register.go b/pkg/cmd/register/register.go index 05e8acfe..7a0a9f04 100644 --- a/pkg/cmd/register/register.go +++ b/pkg/cmd/register/register.go @@ -45,4 +45,4 @@ func runRegister(t *terminal.Terminal) { // func runRegisterReal(t *terminal.Terminal) *cobra.Command { // return spark.NewCmdSpark(t, loginCmdStore, noLoginCmdStore) -// } \ No newline at end of file +// } diff --git a/pkg/cmd/spark/enroll.go b/pkg/cmd/spark/enroll.go index 1f34a56b..db4fbf67 100644 --- a/pkg/cmd/spark/enroll.go +++ b/pkg/cmd/spark/enroll.go @@ -6,7 +6,6 @@ import ( "encoding/json" "errors" "fmt" - "net/http" "net/url" "os" "strings" @@ -16,7 +15,6 @@ import ( "github.com/spf13/cobra" brevcloud "github.com/brevdev/brev-cli/pkg/brevcloud" - "github.com/brevdev/brev-cli/pkg/config" breverrors "github.com/brevdev/brev-cli/pkg/errors" "github.com/brevdev/brev-cli/pkg/files" sparklib "github.com/brevdev/brev-cli/pkg/spark" @@ -30,19 +28,20 @@ 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 = "/var/lib/devplane/brevd" serviceName = "brevd" binaryPath = "/usr/local/bin/brevd" + binaryInstallScriptPath = "/tmp/install-brevd-binary.sh" + serviceInstallScriptPath = "/tmp/install-brevd-service.sh" ) type enrollOptions struct { - cloudCredID string - user string - noSudoers bool - systemd bool agentVersion string wait bool timeout time.Duration @@ -75,32 +74,16 @@ func NewCmdSparkEnroll(t *terminal.Terminal, loginCmdStore *store.AuthHTTPStore) return breverrors.WrapAndTrace(errors.New("authenticated store unavailable")) } - if opts.cloudCredID == "" { - if cid, err := resolveDefaultCloudCred(cmd.Context(), loginCmdStore); err == nil && cid != "" { - opts.cloudCredID = cid - logStep(t, fmt.Sprintf("resolved default brev cloud cred: %s", opts.cloudCredID)) - } else { - logStep(t, "no default brev cloud cred found; proceeding without cloud cred") - } - } else { - logStep(t, fmt.Sprintf("using provided brevcloud cloud cred: %s", opts.cloudCredID)) - } - return runSparkEnroll(cmd.Context(), t, loginCmdStore, alias, opts) }, } - cmd.Flags().StringVar(&opts.cloudCredID, "cloud-cred-id", "", "Brev cloud credential id") - cmd.Flags().StringVar(&opts.user, "user", "brevcloud", "Remote user to ensure exists") - cmd.Flags().BoolVar(&opts.noSudoers, "no-sudoers", false, "Skip sudoers entry for the user") - cmd.Flags().BoolVar(&opts.systemd, "systemd", true, "Manage systemd unit (set false to skip)") 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") - cmd.Flags().BoolVar(&opts.assumeInstalled, "assume-installed", false, "Assume brevd binary and systemd unit already exist") return cmd } @@ -157,13 +140,11 @@ func runSparkEnroll(ctx context.Context, t *terminal.Terminal, loginStore *store sp.Start() } - logStep(t, "resolving spark host via Sync ssh_config") host, err := resolveSparkHost(t, alias) if err != nil { return fail(err) } - logStep(t, fmt.Sprintf("target host: %s@%s:%d", host.User, host.Hostname, host.Port)) aliasLabel := host.Alias if aliasLabel == "" { aliasLabel = sparklib.HostLabel(host) @@ -176,11 +157,10 @@ func runSparkEnroll(ctx context.Context, t *terminal.Terminal, loginStore *store sp.Start() } + cloudCredID, err := resolveDefaultCloudCred(ctx, loginStore) + if opts.dryRun { - logStep(t, fmt.Sprintf("dry-run: would connect to %s with cloud_cred_id=%s", host.Alias, opts.cloudCredID)) - if uiEnabled { - t.Print(fmt.Sprintf("Dry-run: would connect to %s with cloud_cred_id=%s", sparklib.HostLabel(host), opts.cloudCredID)) - } + t.Print(fmt.Sprintf("Dry-run: would connect to %s with cloud_cred_id=%s", sparklib.HostLabel(host), cloudCredID)) return nil } @@ -201,27 +181,23 @@ func runSparkEnroll(ctx context.Context, t *terminal.Terminal, loginStore *store sp.Start() } - logStep(t, "probing SSH connectivity (uname/whoami/hostname)") if err := probeConnectivity(ctx, remote, host, opts.printCmd); err != nil { return fail(err) } - logStep(t, "SSH connectivity probe succeeded") // Minimal path: ensure agent and unit pre-exist. - logStep(t, "checking for existing brevd binary and systemd unit on remote") if err := ensureAgentPresent(ctx, remote, host, opts.printCmd); err != nil { return fail(err) } - logStep(t, "verified brevd binary and unit are present") var intent brevcloud.CreateRegistrationIntentResponse if opts.mockRegistration { logStep(t, "mocking registration intent (no API call)") - intent = brevcloud.MockRegistrationIntent(opts.cloudCredID) + intent = brevcloud.MockRegistrationIntent(cloudCredID) } else { - logStep(t, fmt.Sprintf("requesting registration intent for cloud cred %s", opts.cloudCredID)) + logStep(t, fmt.Sprintf("requesting registration intent for cloud cred %s", cloudCredID)) req := brevcloud.CreateRegistrationIntentRequest{ - CloudCredID: opts.cloudCredID, + CloudCredID: cloudCredID, OrgID: orgID, } ref := "" @@ -246,28 +222,6 @@ func runSparkEnroll(ctx context.Context, t *terminal.Terminal, loginStore *store } } - logStep(t, "generating remote enroll script") - brevCloudBase := buildAgentAPIBase(config.GlobalConfig.GetDevplaneAPIURL()) - script := buildEnrollScript(intent.BrevCloudNodeID, intent.RegistrationToken, opts.cloudCredID, brevCloudBase, opts.mockRegistration) - writeCmd := fmt.Sprintf("mkdir -p /tmp/brevd && cat <<'ENROLL_EOF' >/tmp/brevd/enroll.sh\n%s\nENROLL_EOF\nchmod +x /tmp/brevd/enroll.sh", script) - if opts.printCmd { - fmt.Printf("[remote] write enroll script to /tmp/brevd/enroll.sh\n") - } - out, err := remote.Run(ctx, host, writeCmd) - if err != nil { - return fail(fmt.Errorf("failed to write enroll script on %s: err=%v output=%s", sparklib.HostLabel(host), err, strings.TrimSpace(out))) - } - - logStep(t, "executing remote enroll script") - if opts.printCmd { - fmt.Printf("[remote] sudo /tmp/brevd/enroll.sh || /tmp/brevd/enroll.sh\n") - } - runEnrollCmd := "sudo /tmp/brevd/enroll.sh || /tmp/brevd/enroll.sh" - out, err = remote.Run(ctx, host, runEnrollCmd) - if err != nil { - return fail(fmt.Errorf("remote enroll script failed on %s: err=%v output=%s", sparklib.HostLabel(host), err, strings.TrimSpace(out))) - } - var result enrollResult result.BrevCloudNodeID = intent.BrevCloudNodeID result.CloudCredID = intent.CloudCredID @@ -311,7 +265,7 @@ func runSparkEnroll(ctx context.Context, t *terminal.Terminal, loginStore *store return nil } -func resolveSparkHost(t *terminal.Terminal, alias string) (sparklib.Host, error) { +func resolveSparkHost(_ *terminal.Terminal, alias string) (sparklib.Host, error) { locator := sparklib.NewSyncSSHConfigLocator() resolver := sparklib.NewDefaultSyncConfigResolver(files.AppFs, locator) hosts, err := resolver.ResolveHosts() @@ -327,33 +281,13 @@ func resolveSparkHost(t *terminal.Terminal, alias string) (sparklib.Host, error) func resolveDefaultCloudCred(ctx context.Context, loginStore *store.AuthHTTPStore) (string, error) { client := brevcloud.NewClient(loginStore) - creds, err := client.ListCloudCred(ctx) + org, err := loginStore.GetActiveOrganizationOrDefault() + cred, err := client.ListCloudCredID(ctx, org.ID) if err != nil { return "", err } - matchAll := map[string]string{ - "name": "brevcloud-default", - "managedBy": "system", - "provider": "brevcloud", - } - - for _, cc := range creds { - if cc.ProviderID != "" && strings.ToLower(cc.ProviderID) != "brevcloud" { - continue - } - if hasAllLabels(cc.Labels, matchAll) { - return cc.ID, nil - } - } - - for _, cc := range creds { - if hasAllLabels(cc.Labels, matchAll) { - return cc.ID, nil - } - } - - return "", fmt.Errorf("no default brevcloud cloud credential found; provide --cloud-cred-id or BREV_CLOUD_CRED_ID") + return cred, nil } func hasAllLabels(labels map[string]string, required map[string]string) bool { @@ -372,6 +306,12 @@ func hasAllLabels(labels map[string]string, required map[string]string) bool { } 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 { @@ -385,20 +325,18 @@ func ensureAgentPresent(ctx context.Context, remote sparklib.RemoteRunner, host } // Write the install script to remote and execute it - writeScriptCmd := fmt.Sprintf("cat > /tmp/install-brevd-binary.sh <<'SCRIPT_EOF'\n%s\nSCRIPT_EOF\nchmod +x /tmp/install-brevd-binary.sh", installBinaryScript) if printCmd { - fmt.Printf("[remote] Writing install script to /tmp/install-brevd-binary.sh\n") + fmt.Printf("[remote] Writing install script to %s\n", binaryInstallScriptPath) } - if _, err := remote.Run(ctx, host, writeScriptCmd); err != nil { + if _, err := remote.Run(ctx, host, binaryWriteCmd); err != nil { return fmt.Errorf("failed to write install script on %s: %w", sparklib.HostLabel(host), err) } // Execute the install script - executeCmd := "/tmp/install-brevd-binary.sh && rm -f /tmp/install-brevd-binary.sh" if printCmd { - fmt.Printf("[remote] %s\n", executeCmd) + fmt.Printf("[remote] %s\n", binaryExecuteCmd) } - out, err := remote.Run(ctx, host, executeCmd) + out, err := remote.Run(ctx, host, binaryExecuteCmd) if err != nil { return fmt.Errorf("failed to install brevd on %s: err=%v output=%s", sparklib.HostLabel(host), err, strings.TrimSpace(out)) } @@ -420,20 +358,18 @@ func ensureAgentPresent(ctx context.Context, remote sparklib.RemoteRunner, host } // Write the service install script to remote and execute it with STATE_DIR env var - writeScriptCmd := fmt.Sprintf("cat > /tmp/install-brevd-service.sh <<'SCRIPT_EOF'\n%s\nSCRIPT_EOF\nchmod +x /tmp/install-brevd-service.sh", installServiceScript) if printCmd { - fmt.Printf("[remote] Writing service install script to /tmp/install-brevd-service.sh\n") + fmt.Printf("[remote] Writing service install script to %s\n", serviceInstallScriptPath) } - if _, err := remote.Run(ctx, host, writeScriptCmd); err != nil { + if _, err := remote.Run(ctx, host, serviceWriteCmd); err != nil { return fmt.Errorf("failed to write service install script on %s: %w", sparklib.HostLabel(host), err) } // Execute the service install script with STATE_DIR environment variable - executeCmd := fmt.Sprintf("STATE_DIR=%s /tmp/install-brevd-service.sh && rm -f /tmp/install-brevd-service.sh", stateDirDefault) if printCmd { - fmt.Printf("[remote] %s\n", executeCmd) + fmt.Printf("[remote] %s\n", serviceExecuteCmd) } - out, err := remote.Run(ctx, host, executeCmd) + out, err := remote.Run(ctx, host, serviceExecuteCmd) if err != nil { return fmt.Errorf("failed to install brevd systemd service on %s: err=%v output=%s", sparklib.HostLabel(host), err, strings.TrimSpace(out)) } @@ -447,6 +383,17 @@ func ensureAgentPresent(ctx context.Context, remote sparklib.RemoteRunner, host 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 { + if envPrefix != "" && !strings.HasSuffix(envPrefix, " ") { + envPrefix += " " + } + return fmt.Sprintf("%s%s && rm -f %s", envPrefix, remotePath, remotePath) +} + func ensureConfigDir(ctx context.Context, remote sparklib.RemoteRunner, host sparklib.Host, printCmd bool) error { cmds := []string{ fmt.Sprintf("sudo -n mkdir -p %s", stateDirDefault), @@ -528,12 +475,6 @@ func waitForBrevCloudNode(ctx context.Context, client *brevcloud.Client, brevClo } } -func logStep(t *terminal.Terminal, msg string) { - // step logging suppressed for quieter output - _ = t - _ = msg -} - func formatEnrollError(err error, opts enrollOptions) string { if err == nil { return "" @@ -544,15 +485,6 @@ func formatEnrollError(err error, opts enrollOptions) string { return "Sudo required on target; rerun with a TTY or configure passwordless sudo." } - var httpErr *store.HTTPResponseError - if errors.As(err, &httpErr) && httpErr.Response != nil && httpErr.Response.StatusCode() == http.StatusNotFound { - hint := "" - if !opts.mockRegistration { - hint = " Use --mock-registration for demos." - } - return fmt.Sprintf("Failed to create registration intent (cloud cred %s): endpoint not available (404).%s", opts.cloudCredID, hint) - } - if errors.Is(err, context.DeadlineExceeded) { return "Timed out waiting for node to register" } @@ -583,191 +515,26 @@ func enrollmentManageURL(loginStore *store.AuthHTTPStore, alias string) string { return fmt.Sprintf("https://brev.nvidia.com/org/%s/compute/%s", meta.OrganizationID, url.PathEscape(alias)) } -func buildAgentAPIBase(raw string) string { - base := strings.TrimSpace(raw) - if base == "" { - return "" - } - if !strings.HasPrefix(base, "http://") && !strings.HasPrefix(base, "https://") { - base = "https://" + strings.TrimPrefix(base, "//") - } - base = strings.TrimRight(base, "/") - return base + "/agent/v1" -} - -func buildEnrollScript(brevCloudNodeID, registrationToken, cloudCredID, brevCloudBase string, mock bool) string { - var envContent strings.Builder - envContent.WriteString("BREV_AGENT_BREV_CLOUD_NODE_ID=") - envContent.WriteString(brevCloudNodeID) - envContent.WriteString("\nBREV_AGENT_REGISTRATION_TOKEN=") - envContent.WriteString(registrationToken) - envContent.WriteString("\nBREV_AGENT_STATE_DIR=") - envContent.WriteString(stateDirDefault) - if brevCloudBase != "" { - envContent.WriteString("\nBREV_AGENT_BREV_CLOUD_URL=") - envContent.WriteString(brevCloudBase) - } - if cloudCredID != "" { - envContent.WriteString("\nBREV_AGENT_CLOUD_CRED_ID=") - envContent.WriteString(cloudCredID) - } - envContent.WriteString("\n") - - script := fmt.Sprintf(`#!/usr/bin/env bash -set -euo pipefail - -LOG_DIR=/tmp/brevd -LOG_FILE="${LOG_DIR}/enroll.log" -ENV_FILE="%s" -SERVICE="%s" -MOCK=%t - -log() { - local msg="$1" - mkdir -p "${LOG_DIR}" - logger -t brev-enroll "${msg}" 2>/dev/null || true - printf '%%s\n' "${msg}" >>"${LOG_FILE}" -} - -run_cmd() { - local cmd="$1" - bash -c "${cmd}" >>"${LOG_FILE}" 2>&1 -} - -ensure_dir() { - local dir="$1" - local cmds=( - "sudo -n mkdir -p \"${dir}\"" - "sudo mkdir -p \"${dir}\"" - "mkdir -p \"${dir}\"" - ) - for c in "${cmds[@]}"; do - if run_cmd "${c}"; then - return 0 - fi - done - log "failed to create dir ${dir}" - exit 1 -} - -write_file() { - local content="$1" - local dest="$2" - local tmp="${dest}.tmp" - - printf '%%s\n' "${content}" >"${tmp}" - - local cmds=( - "sudo -n tee \"${dest}\" >/dev/null" - "sudo tee \"${dest}\" >/dev/null" - "tee \"${dest}\" >/dev/null" - ) - for c in "${cmds[@]}"; do - if bash -c "${c}" <"${tmp}" >>"${LOG_FILE}" 2>&1; then - rm -f "${tmp}" - return 0 - fi - done - - log "failed to write file to ${dest}" - rm -f "${tmp}" - exit 1 -} - -set_file_mode() { - local mode="$1" - local path="$2" - local cmds=( - "sudo -n chmod ${mode} \"${path}\"" - "sudo chmod ${mode} \"${path}\"" - "chmod ${mode} \"${path}\"" - ) - for c in "${cmds[@]}"; do - if run_cmd "${c}"; then - return 0 - fi - done - log "failed to chmod ${mode} ${path}" - exit 1 -} - -extract_env_value() { - local key="$1" - local content="$2" - while IFS= read -r line; do - if [[ -z "${line}" || "${line}" =~ ^[[:space:]]*# ]]; then - continue - fi - line="${line#"${line%%[![:space:]]*}"}" - if [[ "${line}" == "${key}="* ]]; then - local val="${line#${key}=}" - if [[ "${val}" =~ ^\".*\"$ || "${val}" =~ ^\'.*\'$ ]]; then - val="${val:1:${#val}-2}" - fi - printf '%%s' "${val}" - break - fi - done <<<"${content}" - return 0 -} - -restart_service() { - local svc="$1" - local cmds=( - "sudo -n systemctl restart \"${svc}\"" - "sudo systemctl restart \"${svc}\"" - "systemctl restart \"${svc}\"" - ) - for c in "${cmds[@]}"; do - if run_cmd "${c}"; then - return 0 - fi - done - log "failed to restart service ${svc}" - exit 1 -} - -ENV_CONTENT=$(cat <<'EOF' -%s -EOF -) - -log "enroll start" -log "probe: $(uname -a)" -log "user: $(whoami)" -log "host: $(hostname)" -log "env file target: ${ENV_FILE}" - -ensure_dir "$(dirname "${ENV_FILE}")" -write_file "${ENV_CONTENT}" "${ENV_FILE}" -set_file_mode 600 "${ENV_FILE}" - -state_dir="$(extract_env_value "BREV_AGENT_STATE_DIR" "${ENV_CONTENT}")" -device_token_path="$(extract_env_value "BREV_AGENT_DEVICE_TOKEN_PATH" "${ENV_CONTENT}")" - -if [[ -n "${state_dir}" ]]; then - log "ensuring state dir ${state_dir}" - ensure_dir "${state_dir}" -fi - -if [[ -n "${device_token_path}" ]]; then - log "ensuring device token parent dir for ${device_token_path}" - ensure_dir "$(dirname "${device_token_path}")" -elif [[ -n "${state_dir}" ]]; then - ensure_dir "${state_dir}" -fi - -if [[ "${MOCK}" == "true" ]]; then - log "mock mode: skipping agent restart" - exit 0 -fi - -restart_service "${SERVICE}" -log "enroll success" -`, envFilePath, serviceName, mock, envContent.String()) - - return script -} +//func buildEnrollScript(brevCloudNodeID, registrationToken, cloudCredID, brevCloudBase string, mock bool) string { +// var envContent strings.Builder +// envContent.WriteString("BREV_AGENT_BREV_CLOUD_NODE_ID=") +// envContent.WriteString(brevCloudNodeID) +// envContent.WriteString("\nBREV_AGENT_REGISTRATION_TOKEN=") +// envContent.WriteString(registrationToken) +// envContent.WriteString("\nBREV_AGENT_STATE_DIR=") +// envContent.WriteString(stateDirDefault) +// if brevCloudBase != "" { +// envContent.WriteString("\nBREV_AGENT_BREV_CLOUD_URL=") +// envContent.WriteString(brevCloudBase) +// } +// if cloudCredID != "" { +// envContent.WriteString("\nBREV_AGENT_CLOUD_CRED_ID=") +// envContent.WriteString(cloudCredID) +// } +// envContent.WriteString("\n") +// +// return script +//} func probeConnectivity(ctx context.Context, remote sparklib.RemoteRunner, host sparklib.Host, printCmd bool) error { cmd := "uname -a && whoami && hostname" diff --git a/pkg/cmd/spark/install-binary.sh b/pkg/cmd/spark/install-binary.sh index 34231110..36d8fa17 100644 --- a/pkg/cmd/spark/install-binary.sh +++ b/pkg/cmd/spark/install-binary.sh @@ -33,4 +33,3 @@ 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-user.sh b/pkg/cmd/spark/install-user.sh new file mode 100644 index 00000000..236cacf0 --- /dev/null +++ b/pkg/cmd/spark/install-user.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Creates the brev service user with passwordless sudo and an SSH directory. + +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 the user if it does not already exist. +if ! id -u "${BREV_USER}" >/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 index 11e78d59..88457c4e 100644 --- a/pkg/cmd/spark/spark.go +++ b/pkg/cmd/spark/spark.go @@ -25,7 +25,6 @@ func NewCmdSpark(t *terminal.Terminal, loginCmdStore *store.AuthHTTPStore, noLog Long: sparkLong, } - cmd.AddCommand(NewCmdSparkSSH(t)) cmd.AddCommand(NewCmdSparkEnroll(t, loginCmdStore)) cmd.AddCommand(NewCmdSparkAgent(t)) diff --git a/pkg/cmd/spark/ssh.go b/pkg/cmd/spark/ssh.go deleted file mode 100644 index 2bb72a4a..00000000 --- a/pkg/cmd/spark/ssh.go +++ /dev/null @@ -1,67 +0,0 @@ -package spark - -import ( - "strings" - - "github.com/spf13/cobra" - - 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/terminal" -) - -const ( - sparkSSHShort = "SSH into a Spark host discovered by NVIDIA Sync" - sparkSSHLong = "Locate the NVIDIA Sync ssh_config, discover Spark hosts, and open an SSH session." -) - -func NewCmdSparkSSH(t *terminal.Terminal) *cobra.Command { - var printCmd bool - - cmd := &cobra.Command{ - Use: "ssh [host-alias]", - Short: sparkSSHShort, - Long: sparkSSHLong, - Args: cobra.MaximumNArgs(1), - DisableFlagsInUseLine: true, - RunE: func(cmd *cobra.Command, args []string) error { - alias := "" - if len(args) > 0 { - alias = args[0] - } - - err := runSparkSSH(t, alias, printCmd) - if err != nil { - return breverrors.WrapAndTrace(err) - } - return nil - }, - } - - cmd.Flags().BoolVar(&printCmd, "print-cmd", false, "Print the resolved ssh command before running") - - return cmd -} - -func runSparkSSH(t *terminal.Terminal, alias string, printCmd bool) error { - locator := sparklib.NewSyncSSHConfigLocator() - resolver := sparklib.NewDefaultSyncConfigResolver(files.AppFs, locator) - hosts, err := resolver.ResolveHosts() - if err != nil { - return breverrors.WrapAndTrace(err) - } - - selected, err := sparklib.SelectHost(hosts, alias, sparklib.TerminalPrompter{}) - if err != nil { - return breverrors.WrapAndTrace(err) - } - - if printCmd { - args := sparklib.BuildSSHArgs(selected) - t.Vprint(strings.Join(args, " ")) - } - - runner := sparklib.NewDefaultSSHRunner(files.AppFs) - return breverrors.WrapAndTrace(runner.Run(selected)) -} diff --git a/pkg/spark/ssh_runner.go b/pkg/spark/ssh_runner.go deleted file mode 100644 index 26b077bf..00000000 --- a/pkg/spark/ssh_runner.go +++ /dev/null @@ -1,88 +0,0 @@ -package spark - -import ( - "fmt" - "os" - "os/exec" - "sort" - "strconv" - - "github.com/spf13/afero" - - breverrors "github.com/brevdev/brev-cli/pkg/errors" -) - -type SSHRunner struct { - executor Executor - fs afero.Fs -} - -func NewDefaultSSHRunner(fs afero.Fs) SSHRunner { - return NewSSHRunner(fs, ProcessExecutor{}) -} - -func NewSSHRunner(fs afero.Fs, executor Executor) SSHRunner { - return SSHRunner{ - executor: executor, - fs: fs, - } -} - -func (r SSHRunner) Run(host Host) error { - if host.IdentityFile == "" { - return breverrors.WrapAndTrace(fmt.Errorf("missing identity file for %s", host.Alias)) - } - - exists, err := afero.Exists(r.fs, host.IdentityFile) - if err != nil { - return breverrors.WrapAndTrace(err) - } - if !exists { - return breverrors.WrapAndTrace(fmt.Errorf("identity file not found at %s", host.IdentityFile)) - } - - argv := BuildSSHArgs(host) - return breverrors.WrapAndTrace(r.executor.Run(argv)) -} - -func BuildSSHArgs(host Host) []string { - args := []string{ - "ssh", - "-i", host.IdentityFile, - "-p", strconv.Itoa(host.Port), - } - - 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])) - } - } - - target := host.Hostname - if host.Alias != "" { - target = host.Alias - } - args = append(args, fmt.Sprintf("%s@%s", host.User, target)) - - return args -} - -type ProcessExecutor struct{} - -func (ProcessExecutor) Run(argv []string) error { - if len(argv) == 0 { - return breverrors.WrapAndTrace(fmt.Errorf("no command provided")) - } - - cmd := exec.Command(argv[0], argv[1:]...) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - return cmd.Run() -} diff --git a/pkg/spark/ssh_runner_test.go b/pkg/spark/ssh_runner_test.go deleted file mode 100644 index b3ad8757..00000000 --- a/pkg/spark/ssh_runner_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package spark - -import ( - "testing" - - "github.com/spf13/afero" - "github.com/stretchr/testify/require" -) - -type capturingExecutor struct { - argv []string - err error -} - -func (c *capturingExecutor) Run(argv []string) error { - c.argv = argv - return c.err -} - -func TestBuildSSHArgsOrdersOptions(t *testing.T) { - host := Host{ - Alias: "spark-one", - Hostname: "h1", - User: "u1", - Port: 2222, - IdentityFile: "/id", - Options: map[string]string{ - "ProxyJump": "jump", - "StrictHostKeyChecking": "no", - }, - } - - args := BuildSSHArgs(host) - require.Equal(t, []string{ - "ssh", - "-i", "/id", - "-p", "2222", - "-o", "ProxyJump=jump", - "-o", "StrictHostKeyChecking=no", - "u1@h1", - }, args) -} - -func TestSSHRunnerRequiresIdentityFile(t *testing.T) { - fs := afero.NewMemMapFs() - exec := &capturingExecutor{} - - runner := NewSSHRunner(fs, exec) - err := runner.Run(Host{Alias: "spark-one"}) - require.Error(t, err) -} - -func TestSSHRunnerRunsCommand(t *testing.T) { - fs := afero.NewMemMapFs() - exec := &capturingExecutor{} - err := afero.WriteFile(fs, "/keys/id", []byte("pem"), 0o600) - require.NoError(t, err) - - host := Host{ - Alias: "spark-one", - Hostname: "h1", - User: "u1", - Port: 22, - IdentityFile: "/keys/id", - Options: map[string]string{ - "ProxyJump": "jump", - }, - } - - runner := NewSSHRunner(fs, exec) - err = runner.Run(host) - require.NoError(t, err) - - expected := []string{"ssh", "-i", "/keys/id", "-p", "22", "-o", "ProxyJump=jump", "u1@h1"} - require.Equal(t, expected, exec.argv) -} From 93e53f342eccdef3fb5aa1ca92a95dfe8272a29d Mon Sep 17 00:00:00 2001 From: Pratik Patel Date: Wed, 7 Jan 2026 19:52:34 -0800 Subject: [PATCH 07/22] simplify --- pkg/cmd/spark/enroll.go | 160 +++++++++------------------------------- 1 file changed, 33 insertions(+), 127 deletions(-) diff --git a/pkg/cmd/spark/enroll.go b/pkg/cmd/spark/enroll.go index db4fbf67..b85f9210 100644 --- a/pkg/cmd/spark/enroll.go +++ b/pkg/cmd/spark/enroll.go @@ -6,7 +6,6 @@ import ( "encoding/json" "errors" "fmt" - "net/url" "os" "strings" "time" @@ -32,11 +31,11 @@ var installServiceScript string var installUserScript string const ( - defaultEnrollTimeout = 10 * time.Minute - envFilePath = "/etc/default/brevd" - stateDirDefault = "/var/lib/devplane/brevd" - serviceName = "brevd" - binaryPath = "/usr/local/bin/brevd" + defaultEnrollTimeout = 10 * time.Minute + envFilePath = "/etc/default/brevd" + stateDirDefault = "/var/lib/devplane/brevd" + serviceName = "brevd" + binaryPath = "/usr/local/bin/brevd" binaryInstallScriptPath = "/tmp/install-brevd-binary.sh" serviceInstallScriptPath = "/tmp/install-brevd-service.sh" ) @@ -48,7 +47,6 @@ type enrollOptions struct { printCmd bool dryRun bool json bool - assumeInstalled bool mockRegistration bool } @@ -66,10 +64,6 @@ func NewCmdSparkEnroll(t *terminal.Terminal, loginCmdStore *store.AuthHTTPStore) alias = args[0] } - if env := os.Getenv("BREVD_ASSUME_INSTALLED"); strings.EqualFold(env, "true") { - opts.assumeInstalled = true - } - if loginCmdStore == nil { return breverrors.WrapAndTrace(errors.New("authenticated store unavailable")) } @@ -192,34 +186,17 @@ func runSparkEnroll(ctx context.Context, t *terminal.Terminal, loginStore *store var intent brevcloud.CreateRegistrationIntentResponse if opts.mockRegistration { - logStep(t, "mocking registration intent (no API call)") intent = brevcloud.MockRegistrationIntent(cloudCredID) } else { - logStep(t, fmt.Sprintf("requesting registration intent for cloud cred %s", cloudCredID)) req := brevcloud.CreateRegistrationIntentRequest{ CloudCredID: cloudCredID, OrgID: orgID, } - ref := "" - switch { - case req.CloudCredID != "" && req.OrgID != "": - ref = fmt.Sprintf("cloud cred %s org %s", req.CloudCredID, req.OrgID) - case req.CloudCredID != "": - ref = fmt.Sprintf("cloud cred %s", req.CloudCredID) - case req.OrgID != "": - ref = fmt.Sprintf("org %s", req.OrgID) - default: - ref = "no reference" - } resp, err := brevCloudClient.CreateRegistrationIntent(ctx, req) if err != nil { - return fail(fmt.Errorf("failed to create registration intent (%s): %w", ref, err)) + return fail(err) } intent = *resp - logStep(t, fmt.Sprintf("created registration intent: brev_cloud_node_id=%s expires_at=%s", intent.BrevCloudNodeID, intent.ExpiresAt)) - if strings.TrimSpace(intent.RegistrationToken) == "" { - return fail(fmt.Errorf("registration intent returned empty registration token for %s", ref)) - } } var result enrollResult @@ -227,7 +204,6 @@ func runSparkEnroll(ctx context.Context, t *terminal.Terminal, loginStore *store result.CloudCredID = intent.CloudCredID if opts.wait && !opts.mockRegistration { - logStep(t, fmt.Sprintf("waiting for brev cloud node %s to report active...", intent.BrevCloudNodeID)) node, err := waitForBrevCloudNode(ctx, brevCloudClient, intent.BrevCloudNodeID, t) if err != nil { return fail(err) @@ -246,9 +222,7 @@ func runSparkEnroll(ctx context.Context, t *terminal.Terminal, loginStore *store } else { t.Print("\n" + t.Green("✓ Registration complete")) } - if manageURL := enrollmentManageURL(loginStore, aliasLabel); manageURL != "" { - t.Print(fmt.Sprintf("Manage your %s %s", t.Yellow("Spark"), t.Blue(manageURL))) - } + 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 { @@ -290,21 +264,6 @@ func resolveDefaultCloudCred(ctx context.Context, loginStore *store.AuthHTTPStor return cred, nil } -func hasAllLabels(labels map[string]string, required map[string]string) bool { - if len(required) == 0 { - return true - } - for k, v := range required { - if labels == nil { - return false - } - if val, ok := labels[k]; !ok || val != v { - return false - } - } - return true -} - func ensureAgentPresent(ctx context.Context, remote sparklib.RemoteRunner, host sparklib.Host, printCmd bool) error { binaryWriteCmd := buildWriteScriptCmd(binaryInstallScriptPath, installBinaryScript) binaryExecuteCmd := buildExecuteScriptCmd(binaryInstallScriptPath, "") @@ -324,24 +283,8 @@ func ensureAgentPresent(ctx context.Context, remote sparklib.RemoteRunner, host fmt.Printf("[remote] Installing brevd from GitHub releases...\n") } - // Write the install script to remote and execute it - if printCmd { - fmt.Printf("[remote] Writing install script to %s\n", binaryInstallScriptPath) - } - if _, err := remote.Run(ctx, host, binaryWriteCmd); err != nil { - return fmt.Errorf("failed to write install script on %s: %w", sparklib.HostLabel(host), err) - } - - // Execute the install script - if printCmd { - fmt.Printf("[remote] %s\n", binaryExecuteCmd) - } - out, err := remote.Run(ctx, host, binaryExecuteCmd) - if err != nil { - return fmt.Errorf("failed to install brevd on %s: err=%v output=%s", sparklib.HostLabel(host), err, strings.TrimSpace(out)) - } - if printCmd { - fmt.Printf("[remote] brevd install output: %s\n", strings.TrimSpace(out)) + if err := runInstallScript(ctx, remote, host, printCmd, "brevd", binaryInstallScriptPath, binaryWriteCmd, binaryExecuteCmd); err != nil { + return err } } @@ -357,24 +300,8 @@ func ensureAgentPresent(ctx context.Context, remote sparklib.RemoteRunner, host fmt.Printf("[remote] Installing brevd systemd service...\n") } - // Write the service install script to remote and execute it with STATE_DIR env var - if printCmd { - fmt.Printf("[remote] Writing service install script to %s\n", serviceInstallScriptPath) - } - if _, err := remote.Run(ctx, host, serviceWriteCmd); err != nil { - return fmt.Errorf("failed to write service install script on %s: %w", sparklib.HostLabel(host), err) - } - - // Execute the service install script with STATE_DIR environment variable - if printCmd { - fmt.Printf("[remote] %s\n", serviceExecuteCmd) - } - out, err := remote.Run(ctx, host, serviceExecuteCmd) - if err != nil { - return fmt.Errorf("failed to install brevd systemd service on %s: err=%v output=%s", sparklib.HostLabel(host), err, strings.TrimSpace(out)) - } - if printCmd { - fmt.Printf("[remote] systemd service install output: %s\n", strings.TrimSpace(out)) + 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)) @@ -391,7 +318,28 @@ func buildExecuteScriptCmd(remotePath, envPrefix string) string { if envPrefix != "" && !strings.HasSuffix(envPrefix, " ") { envPrefix += " " } - return fmt.Sprintf("%s%s && rm -f %s", envPrefix, remotePath, remotePath) + 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 ensureConfigDir(ctx context.Context, remote sparklib.RemoteRunner, host sparklib.Host, printCmd bool) error { @@ -445,15 +393,6 @@ func writeAgentConfig(ctx context.Context, remote sparklib.RemoteRunner, host sp return fmt.Errorf("failed to write config on %s; attempts: %s", sparklib.HostLabel(host), strings.Join(errs, " | ")) } -func restartService(ctx context.Context, remote sparklib.RemoteRunner, host sparklib.Host, printCmd bool) error { - cmd := fmt.Sprintf("sudo systemctl restart %s", serviceName) - if printCmd { - fmt.Printf("[remote] %s\n", cmd) - } - _, err := remote.Run(ctx, host, cmd) - return breverrors.WrapAndTrace(err) -} - func waitForBrevCloudNode(ctx context.Context, client *brevcloud.Client, brevCloudNodeID string, t *terminal.Terminal) (*brevcloud.BrevCloudNode, error) { interval := 3 * time.Second ticker := time.NewTicker(interval) @@ -470,7 +409,6 @@ func waitForBrevCloudNode(ctx context.Context, client *brevcloud.Client, brevClo case <-ctx.Done(): return nil, ctx.Err() case <-ticker.C: - logStep(t, fmt.Sprintf("still waiting for brev cloud node %s (phase=%s last_seen_at=%s)", brevCloudNodeID, node.Phase, node.LastSeenAt)) } } } @@ -504,38 +442,6 @@ func isSudoError(msg string) bool { strings.Contains(lower, "sudo: sorry, you must have a tty") } -func enrollmentManageURL(loginStore *store.AuthHTTPStore, alias string) string { - if loginStore == nil { - return "" - } - meta, err := loginStore.GetCurrentWorkspaceMeta() - if err != nil || meta == nil || meta.OrganizationID == "" || alias == "" { - return "" - } - return fmt.Sprintf("https://brev.nvidia.com/org/%s/compute/%s", meta.OrganizationID, url.PathEscape(alias)) -} - -//func buildEnrollScript(brevCloudNodeID, registrationToken, cloudCredID, brevCloudBase string, mock bool) string { -// var envContent strings.Builder -// envContent.WriteString("BREV_AGENT_BREV_CLOUD_NODE_ID=") -// envContent.WriteString(brevCloudNodeID) -// envContent.WriteString("\nBREV_AGENT_REGISTRATION_TOKEN=") -// envContent.WriteString(registrationToken) -// envContent.WriteString("\nBREV_AGENT_STATE_DIR=") -// envContent.WriteString(stateDirDefault) -// if brevCloudBase != "" { -// envContent.WriteString("\nBREV_AGENT_BREV_CLOUD_URL=") -// envContent.WriteString(brevCloudBase) -// } -// if cloudCredID != "" { -// envContent.WriteString("\nBREV_AGENT_CLOUD_CRED_ID=") -// envContent.WriteString(cloudCredID) -// } -// envContent.WriteString("\n") -// -// return script -//} - func probeConnectivity(ctx context.Context, remote sparklib.RemoteRunner, host sparklib.Host, printCmd bool) error { cmd := "uname -a && whoami && hostname" if printCmd { From e330e86990ee5a1b3fc8a0f9a9638bd5fadbe111 Mon Sep 17 00:00:00 2001 From: Pratik Patel Date: Wed, 7 Jan 2026 19:59:50 -0800 Subject: [PATCH 08/22] cleanup/simplify --- brevcloud-scripts/brevd/README.md | 76 ------------------------------ brevcloud-scripts/brevd/install.sh | 1 - pkg/brevcloud/client.go | 3 -- pkg/cmd/spark/agent.go | 1 + pkg/cmd/spark/install-service.sh | 6 --- 5 files changed, 1 insertion(+), 86 deletions(-) delete mode 100644 brevcloud-scripts/brevd/README.md diff --git a/brevcloud-scripts/brevd/README.md b/brevcloud-scripts/brevd/README.md deleted file mode 100644 index f3cbb461..00000000 --- a/brevcloud-scripts/brevd/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# brevd service artifacts - -This directory contains minimal assets for running the DevPlane `brevd` daemon as a background service on Linux hosts. - -## Files - -- `systemd/brevd.service` - systemd unit referencing `/usr/local/bin/brevd` and `/etc/default/brevd`. -- `install.sh` - helper script to copy the agent binary, drop configuration stubs, and enable the service. - -## Building the `brevd` binary - -Use `go build` from the repo root. For Linux target nodes (including OrbStack), cross-compile for the desired architecture: - -``` -GOOS=linux GOARCH=amd64 go build -o brevd ./cmd/brevd -``` - -Adjust `GOARCH` (e.g., `arm64`) to match the target hardware. The resulting `brevd` binary can be copied directly into the VM or passed to `install.sh`. - -## Environment file (`/etc/default/brevd`) - -The unit loads configuration from `/etc/default/brevd`. Set the same environment variables used when running the binary manually: - -- `BREV_AGENT_BREVCLOUD_URL` -- `BREV_AGENT_REGISTRATION_TOKEN` -- `BREV_AGENT_DISPLAY_NAME` -- `BREV_AGENT_CLOUD_NAME` -- `BREV_AGENT_CLOUD_CRED_ID` -- `BREV_AGENT_STATE_DIR` -- `BREV_AGENT_DEVICE_TOKEN_PATH` -- `BREV_AGENT_HEARTBEAT_INTERVAL` -- `BREV_AGENT_ENABLE_TUNNEL` -- `BREV_AGENT_TUNNEL_SSH_PORT` - -Unset values fall back to the agent defaults (see `internal/agent/config`). - -> **Important:** `BREV_AGENT_BREVCLOUD_URL` must target the agent ingress exposed by the control plane. The public server mounts the Connect handler under `/agent/v1` (see `internal/cmd/devplane/public_server.go`), so your URL should look like `https:///agent/v1`. The agent will append the Connect RPC paths on top of that base. - -## Manual install - -1. Build or download the `brevd` binary. -2. Copy it to `/usr/local/bin/brevd` and ensure it is executable. -3. Copy `systemd/brevd.service` to `/etc/systemd/system/brevd.service`. -4. Create `/etc/default/brevd`, populate the environment variables above, and protect it (`chmod 600`). -5. Reload systemd: `sudo systemctl daemon-reload`. -6. Enable and start: `sudo systemctl enable --now brevd`. - -The `install.sh` script automates these steps and can be re-run safely to update the binary or unit. - -## Monitoring and logs - -- Inspect current status and last few log lines: - - ``` - sudo systemctl status brevd - ``` - -- Stream live logs from the agent: - - ``` - sudo journalctl -u brevd -f - ``` - -- Show logs for a specific boot/session or time window (example: last hour): - - ``` - sudo journalctl -u brevd --since "1 hour ago" - ``` - -- If you installed using `install.sh`, the environment file resides at `/etc/default/brevd`. You can check the active configuration with: - - ``` - sudo cat /etc/default/brevd - ``` - -These commands work the same on OrbStack VMs and physical Linux hosts. diff --git a/brevcloud-scripts/brevd/install.sh b/brevcloud-scripts/brevd/install.sh index a37def7e..9aa27b35 100644 --- a/brevcloud-scripts/brevd/install.sh +++ b/brevcloud-scripts/brevd/install.sh @@ -113,7 +113,6 @@ BREV_AGENT_CLOUD_CRED_ID="replace-me" # Optional overrides: # BREV_AGENT_DISPLAY_NAME="my-node" # BREV_AGENT_CLOUD_NAME="edge-cluster-a" -# BREV_AGENT_STATE_DIR="/var/lib/brevd" # BREV_AGENT_DEVICE_TOKEN_PATH="/var/lib/brevd/device_token" # BREV_AGENT_HEARTBEAT_INTERVAL="30s" # BREV_AGENT_ENABLE_TUNNEL="true" diff --git a/pkg/brevcloud/client.go b/pkg/brevcloud/client.go index 6f4aafd1..b2c64f15 100644 --- a/pkg/brevcloud/client.go +++ b/pkg/brevcloud/client.go @@ -178,9 +178,6 @@ type CloudCred struct { Labels map[string]string } -// ListCloudCred is a placeholder for future integration with a BrevCloud -// cloud credential listing API. Today it returns an error so callers can -// fall back to explicit --cloud-cred-id flags. func (c *Client) ListCloudCredID(ctx context.Context, orgID string) (string, error) { resp, err := c.cloudCreds.ListCloudCred(ctx, connect.NewRequest(&devplaneapiv1.ListCloudCredRequest{ Options: &devplaneapiv1.ListCloudCredOptions{ diff --git a/pkg/cmd/spark/agent.go b/pkg/cmd/spark/agent.go index ff56b2f4..c4535be3 100644 --- a/pkg/cmd/spark/agent.go +++ b/pkg/cmd/spark/agent.go @@ -35,6 +35,7 @@ func runSparkAgent(t *terminal.Terminal) error { // 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: diff --git a/pkg/cmd/spark/install-service.sh b/pkg/cmd/spark/install-service.sh index 162bfb6c..76df7341 100644 --- a/pkg/cmd/spark/install-service.sh +++ b/pkg/cmd/spark/install-service.sh @@ -1,11 +1,6 @@ #!/usr/bin/env bash set -eo pipefail -# Installs the brevd systemd service -# Expects STATE_DIR to be set (defaults to /var/lib/devplane/brevd) - -STATE_DIR="${STATE_DIR:-/var/lib/devplane/brevd}" - # Create systemd service file sudo tee /etc/systemd/system/brevd.service > /dev/null <<'EOF' [Unit] @@ -33,7 +28,6 @@ if [ ! -f /etc/default/brevd ]; then BREV_AGENT_BREV_CLOUD_NODE_ID="" BREV_AGENT_BREV_CLOUD_URL="" BREV_AGENT_REGISTRATION_TOKEN="" -BREV_AGENT_STATE_DIR="${STATE_DIR}" EOF sudo chmod 600 /etc/default/brevd fi From 085bea1229ef48a6ab3025f5d76f53dfde1da93b Mon Sep 17 00:00:00 2001 From: Pratik Patel Date: Thu, 8 Jan 2026 15:09:05 -0800 Subject: [PATCH 09/22] added state_dir back and fixed lint --- .golangci.yml | 151 +++-- Makefile | 4 + pkg/brevdaemon/agent/agent.go | 18 - pkg/brevdaemon/agent/config/config.go | 60 +- pkg/brevdaemon/agent/config/config_test.go | 59 +- pkg/cmd/cmd.go | 3 +- pkg/cmd/register/register.go | 5 +- pkg/cmd/spark/enroll.go | 75 ++- pkg/cmd/spark/install-service.sh | 14 +- pkg/cmd/spark/spark.go | 4 +- tools/go.mod | 142 +---- tools/go.sum | 668 --------------------- tools/tools.go | 1 - 13 files changed, 216 insertions(+), 988 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 09b12371..f2527502 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,102 +1,97 @@ -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 - dupl - - exportloopref + - errcheck - forcetypeassert - funlen - # - gci - gocognit - # - goconst - gocritic - gocyclo - # - godot - - gofumpt - # - revive - # - gomnd - goprintffuncname - gosec - # - ifshort + - govet + - ineffassign - misspell - noctx - nolintlint - rowserrcheck - sqlclosecheck - - stylecheck + - staticcheck - thelper - tparallel - unconvert - unparam - # - whitespace - # - errorlint - # - goerr113 + - unused - wrapcheck -issues: - # enable issues excluded by default - exclude-use-default: false + settings: + funlen: + lines: 100 + gocyclo: + min-complexity: 16 + misspell: + locale: US + nolintlint: + 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: 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 + staticcheck: + checks: + - all + - -ST1000 + - -ST1020 + wrapcheck: + ignore-sigs: + - .WrapAndTrace + - .Errorf + - .Wrap + - .New + 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 90f747de..5964064c 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/pkg/brevdaemon/agent/agent.go b/pkg/brevdaemon/agent/agent.go index c5bffd23..fce28961 100644 --- a/pkg/brevdaemon/agent/agent.go +++ b/pkg/brevdaemon/agent/agent.go @@ -155,24 +155,6 @@ func (a *agent) Run(ctx context.Context) error { return nil } -// Run preserves the legacy entrypoint used by cmd/devplane/agent.go so we do -// not break existing workflows while the new standalone agent binary is built. -func Run(ctx context.Context) error { - agentLogger := zap.L().Named("legacy-agent") - cfg, err := agentconfig.Load() - if err != nil { - return errors.WrapAndTrace(err) - } - a, err := NewAgent(cfg, agentLogger) - if err != nil { - return errors.WrapAndTrace(err) - } - if err := a.Run(ctx); err != nil { - return errors.WrapAndTrace(err) - } - return nil -} - func (a *agent) startStatusBridge(ctx context.Context) func() { if a.statusReporter == nil || a.statusUpdates == nil { return func() {} diff --git a/pkg/brevdaemon/agent/config/config.go b/pkg/brevdaemon/agent/config/config.go index e04b698e..2e5e3b98 100644 --- a/pkg/brevdaemon/agent/config/config.go +++ b/pkg/brevdaemon/agent/config/config.go @@ -11,17 +11,18 @@ import ( ) const ( - envPrefix = "BREV_AGENT_" - envBrevCloudURL = envPrefix + "BREV_CLOUD_URL" - envRegistrationToken = envPrefix + "REGISTRATION_TOKEN" - envDisplayName = envPrefix + "DISPLAY_NAME" - envCloudName = envPrefix + "CLOUD_NAME" - envStateDir = envPrefix + "STATE_DIR" - envDeviceTokenPath = envPrefix + "DEVICE_TOKEN_PATH" - envHeartbeatInterval = envPrefix + "HEARTBEAT_INTERVAL" - envEnableTunnel = envPrefix + "ENABLE_TUNNEL" - envTunnelSSHPort = envPrefix + "TUNNEL_SSH_PORT" - envTunnelCritical = envPrefix + "TUNNEL_CRITICAL" + EnvBrevCloudURL = "BREV_AGENT_BREV_CLOUD_URL" + EnvRegistrationToken = "BREV_AGENT_REGISTRATION_TOKEN" + EnvDisplayName = "BREV_AGENT_DISPLAY_NAME" + EnvCloudName = "BREV_AGENT_CLOUD_NAME" + EnvCloudCredID = "BREV_AGENT_CLOUD_CRED_ID" + EnvBrevCloudNodeID = "BREV_AGENT_BREV_CLOUD_NODE_ID" + EnvStateDir = "BREV_AGENT_STATE_DIR" + EnvDeviceTokenPath = "BREV_AGENT_DEVICE_TOKEN_PATH" + 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 @@ -48,8 +49,7 @@ type Config struct { TunnelCritical bool } -// Load constructs a Config from environment variables, applying defaults and -// validation as defined in the MVP plan. +// Load constructs a Config from environment variables, applying defaults and validations func Load() (Config, error) { cfg := Config{ HeartbeatInterval: defaultHeartbeatInterval, @@ -58,60 +58,60 @@ func Load() (Config, error) { TunnelCritical: defaultTunnelCritical, } - cfg.BrevCloudAgentURL = strings.TrimSpace(os.Getenv(envBrevCloudURL)) + cfg.BrevCloudAgentURL = strings.TrimSpace(os.Getenv(EnvBrevCloudURL)) if cfg.BrevCloudAgentURL == "" { - return Config{}, errors.Errorf("%s is required", envBrevCloudURL) + 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)) + 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) + 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 != "" { + 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)) + 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) + return Config{}, errors.Errorf("%s must be positive", EnvHeartbeatInterval) } cfg.HeartbeatInterval = interval } - if enableTunnelRaw := strings.TrimSpace(os.Getenv(envEnableTunnel)); enableTunnelRaw != "" { + 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)) + 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 != "" { + if portRaw := strings.TrimSpace(os.Getenv(EnvTunnelSSHPort)); portRaw != "" { port, err := strconv.Atoi(portRaw) if err != nil { - return Config{}, errors.WrapAndTrace(errors.Errorf("%s must be an integer: %v", envTunnelSSHPort, err)) + 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) + return Config{}, errors.Errorf("%s must be between 1 and 65535", EnvTunnelSSHPort) } cfg.TunnelSSHPort = port } - if tunnelCriticalRaw := strings.TrimSpace(os.Getenv(envTunnelCritical)); tunnelCriticalRaw != "" { + 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)) + return Config{}, errors.WrapAndTrace(errors.Errorf("%s must be a boolean: %v", EnvTunnelCritical, err)) } cfg.TunnelCritical = val } diff --git a/pkg/brevdaemon/agent/config/config_test.go b/pkg/brevdaemon/agent/config/config_test.go index 1899d159..8589b15c 100644 --- a/pkg/brevdaemon/agent/config/config_test.go +++ b/pkg/brevdaemon/agent/config/config_test.go @@ -9,7 +9,7 @@ import ( func TestLoadDefaults(t *testing.T) { unsetConfigEnv(t) - t.Setenv(envBrevCloudURL, "https://example.dev/v1/brevcloudagent") + t.Setenv(EnvBrevCloudURL, "https://example.dev/v1/brevcloudagent") cfg, err := Load() if err != nil { @@ -46,16 +46,16 @@ func TestLoadDefaults(t *testing.T) { 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") + 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 { @@ -104,27 +104,27 @@ func TestLoadMissingBrevCloudURL(t *testing.T) { func TestLoadInvalidValues(t *testing.T) { unsetConfigEnv(t) - t.Setenv(envBrevCloudURL, "https://example.dev/v1") - t.Setenv(envHeartbeatInterval, "abc") + 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") + 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") + 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") + t.Setenv(EnvTunnelSSHPort, "22") + t.Setenv(EnvTunnelCritical, "not-bool") if _, err := Load(); err == nil { t.Fatalf("expected invalid tunnel critical bool error") } @@ -133,15 +133,18 @@ func TestLoadInvalidValues(t *testing.T) { func unsetConfigEnv(t *testing.T) { t.Helper() envs := []string{ - envRegistrationToken, - envDisplayName, - envCloudName, - envStateDir, - envDeviceTokenPath, - envHeartbeatInterval, - envEnableTunnel, - envTunnelSSHPort, - envTunnelCritical, + EnvBrevCloudURL, + EnvRegistrationToken, + EnvDisplayName, + EnvCloudName, + EnvCloudCredID, + EnvBrevCloudNodeID, + EnvStateDir, + EnvDeviceTokenPath, + EnvHeartbeatInterval, + EnvEnableTunnel, + EnvTunnelSSHPort, + EnvTunnelCritical, } for _, key := range envs { t.Setenv(key, "") diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index 05cf722a..1026fb5a 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -40,7 +40,6 @@ import ( "github.com/brevdev/brev-cli/pkg/cmd/set" "github.com/brevdev/brev-cli/pkg/cmd/setupworkspace" "github.com/brevdev/brev-cli/pkg/cmd/shell" - "github.com/brevdev/brev-cli/pkg/cmd/spark" "github.com/brevdev/brev-cli/pkg/cmd/sshkeys" "github.com/brevdev/brev-cli/pkg/cmd/start" "github.com/brevdev/brev-cli/pkg/cmd/status" @@ -297,7 +296,7 @@ func createCmdTree(cmd *cobra.Command, t *terminal.Terminal, loginCmdStore *stor cmd.AddCommand(recreate.NewCmdRecreate(t, loginCmdStore)) cmd.AddCommand(writeconnectionevent.NewCmdwriteConnectionEvent(t, loginCmdStore)) cmd.AddCommand(updatemodel.NewCmdupdatemodel(t, loginCmdStore)) - cmd.AddCommand(spark.NewCmdSpark(t, loginCmdStore, noLoginCmdStore)) + // cmd.AddCommand(spark.NewCmdSpark(t, loginCmdStore, noLoginCmdStore)) } func hasWorkspaceCommands(cmd *cobra.Command) bool { diff --git a/pkg/cmd/register/register.go b/pkg/cmd/register/register.go index 7a0a9f04..17a275ac 100644 --- a/pkg/cmd/register/register.go +++ b/pkg/cmd/register/register.go @@ -43,6 +43,5 @@ func runRegister(t *terminal.Terminal) { t.Vprint("\n") } -// func runRegisterReal(t *terminal.Terminal) *cobra.Command { -// return spark.NewCmdSpark(t, loginCmdStore, noLoginCmdStore) -// } +// TODO +// this should use spark/enroll.go diff --git a/pkg/cmd/spark/enroll.go b/pkg/cmd/spark/enroll.go index b85f9210..aae3de21 100644 --- a/pkg/cmd/spark/enroll.go +++ b/pkg/cmd/spark/enroll.go @@ -14,6 +14,7 @@ import ( "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" @@ -33,11 +34,12 @@ var installUserScript string const ( defaultEnrollTimeout = 10 * time.Minute envFilePath = "/etc/default/brevd" - stateDirDefault = "/var/lib/devplane/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 { @@ -152,6 +154,9 @@ func runSparkEnroll(ctx context.Context, t *terminal.Terminal, loginStore *store } 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)) @@ -179,6 +184,14 @@ func runSparkEnroll(ctx context.Context, t *terminal.Terminal, loginStore *store 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) @@ -199,9 +212,24 @@ func runSparkEnroll(ctx context.Context, t *terminal.Terminal, loginStore *store 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) @@ -264,6 +292,13 @@ func resolveDefaultCloudCred(ctx context.Context, loginStore *store.AuthHTTPStor 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, "") @@ -315,9 +350,6 @@ func buildWriteScriptCmd(remotePath, script string) string { } func buildExecuteScriptCmd(remotePath, envPrefix string) string { - if envPrefix != "" && !strings.HasSuffix(envPrefix, " ") { - envPrefix += " " - } return fmt.Sprintf("%s %s && rm -f %s", envPrefix, remotePath, remotePath) } @@ -342,10 +374,10 @@ func runInstallScript(ctx context.Context, remote sparklib.RemoteRunner, host sp return nil } -func ensureConfigDir(ctx context.Context, remote sparklib.RemoteRunner, host sparklib.Host, printCmd bool) error { +func ensureStateDir(ctx context.Context, remote sparklib.RemoteRunner, host sparklib.Host, printCmd bool) error { cmds := []string{ - fmt.Sprintf("sudo -n mkdir -p %s", stateDirDefault), - fmt.Sprintf("sudo mkdir -p %s", stateDirDefault), + 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 @@ -359,20 +391,39 @@ func ensureConfigDir(ctx context.Context, remote sparklib.RemoteRunner, host spa } errs = append(errs, fmt.Sprintf("cmd=%s err=%v output=%s", cmd, err, strings.TrimSpace(out))) } - return fmt.Errorf("failed to create config dir on %s; attempts: %s", sparklib.HostLabel(host), strings.Join(errs, " | ")) + 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("brev_cloud_node_id: ") - b.WriteString(brevCloudNodeID) - b.WriteString("\nregistration_token: ") + 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("\ncloud_cred_id: ") + 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), diff --git a/pkg/cmd/spark/install-service.sh b/pkg/cmd/spark/install-service.sh index 76df7341..7838a333 100644 --- a/pkg/cmd/spark/install-service.sh +++ b/pkg/cmd/spark/install-service.sh @@ -1,6 +1,8 @@ #!/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] @@ -14,8 +16,8 @@ EnvironmentFile=-/etc/default/brevd ExecStart=/usr/local/bin/brevd spark agent Restart=on-failure RestartSec=10s -User=root -Group=root +User=brevcloud +Group=brevcloud [Install] WantedBy=multi-user.target @@ -28,12 +30,14 @@ if [ ! -f /etc/default/brevd ]; then BREV_AGENT_BREV_CLOUD_NODE_ID="" BREV_AGENT_BREV_CLOUD_URL="" BREV_AGENT_REGISTRATION_TOKEN="" +BREV_AGENT_CLOUD_CRED_ID="" +BREV_AGENT_STATE_DIR="${STATE_DIR}" EOF - sudo chmod 600 /etc/default/brevd fi +sudo chmod 600 /etc/default/brevd + # Reload systemd sudo systemctl daemon-reload -echo "Successfully installed brevd systemd service" - +echo "Successfully installed brevd systemd service" \ No newline at end of file diff --git a/pkg/cmd/spark/spark.go b/pkg/cmd/spark/spark.go index 88457c4e..0ceccb20 100644 --- a/pkg/cmd/spark/spark.go +++ b/pkg/cmd/spark/spark.go @@ -25,8 +25,8 @@ func NewCmdSpark(t *terminal.Terminal, loginCmdStore *store.AuthHTTPStore, noLog Long: sparkLong, } - cmd.AddCommand(NewCmdSparkEnroll(t, loginCmdStore)) - cmd.AddCommand(NewCmdSparkAgent(t)) + // cmd.AddCommand(NewCmdSparkEnroll(t, loginCmdStore)) + // cmd.AddCommand(NewCmdSparkAgent(t)) return cmd } 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" ) From 7f87554d481b310ebe77dac324f8686ba2befa04 Mon Sep 17 00:00:00 2001 From: Pratik Patel Date: Thu, 8 Jan 2026 15:55:15 -0800 Subject: [PATCH 10/22] cleanup --- brevcloud-scripts/brevd/install.sh | 140 ------------------ brevcloud-scripts/brevd/systemd/brevd.service | 17 --- pkg/brevdaemon/agent/README.md | 0 pkg/brevdaemon/agent/config/config.go | 6 +- pkg/brevdaemon/agent/heartbeat/heartbeat.go | 2 +- pkg/brevdaemon/agent/telemetry/utilization.go | 2 +- pkg/brevdaemon/agent/tunnel/tunnel.go | 6 +- pkg/cmd/spark/agent.go | 8 +- pkg/cmd/spark/enroll.go | 55 +++---- 9 files changed, 33 insertions(+), 203 deletions(-) delete mode 100644 brevcloud-scripts/brevd/install.sh delete mode 100644 brevcloud-scripts/brevd/systemd/brevd.service delete mode 100644 pkg/brevdaemon/agent/README.md diff --git a/brevcloud-scripts/brevd/install.sh b/brevcloud-scripts/brevd/install.sh deleted file mode 100644 index 9aa27b35..00000000 --- a/brevcloud-scripts/brevd/install.sh +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" - -BIN_SOURCE_DEFAULT="${ROOT_DIR}/brevd" -BIN_TARGET_DEFAULT="/usr/local/bin/brevd" -ENV_FILE_DEFAULT="/etc/default/brevd" -UNIT_SOURCE="${SCRIPT_DIR}/systemd/brevd.service" -UNIT_TARGET_DEFAULT="/etc/systemd/system/brevd.service" -AUTO_ENABLE=true - -usage() { - cat <&2 - exit 1 - fi -} - -ensure_cmd() { - if ! command -v "$1" >/dev/null 2>&1; then - echo "Missing required command: $1" >&2 - exit 1 - fi -} - -BIN_SOURCE="${BIN_SOURCE_DEFAULT}" -BIN_TARGET="${BIN_TARGET_DEFAULT}" -ENV_FILE="${ENV_FILE_DEFAULT}" -UNIT_TARGET="${UNIT_TARGET_DEFAULT}" - -while [[ $# -gt 0 ]]; do - case "$1" in - --binary) - BIN_SOURCE="$2" - shift 2 - ;; - --bin-target) - BIN_TARGET="$2" - shift 2 - ;; - --env-file) - ENV_FILE="$2" - shift 2 - ;; - --unit-path) - UNIT_TARGET="$2" - shift 2 - ;; - --skip-enable) - AUTO_ENABLE=false - shift 1 - ;; - -h|--help) - usage - exit 0 - ;; - *) - echo "Unknown option: $1" >&2 - usage - exit 1 - ;; - esac -done - -main() { - require_root - ensure_cmd install - ensure_cmd systemctl - - if [[ ! -f "${BIN_SOURCE}" ]]; then - echo "brevd binary not found at ${BIN_SOURCE}" >&2 - exit 1 - fi - - echo "Installing brevd binary to ${BIN_TARGET}" - install -m 0755 "${BIN_SOURCE}" "${BIN_TARGET}" - - if [[ ! -f "${UNIT_SOURCE}" ]]; then - echo "unit file missing at ${UNIT_SOURCE}" >&2 - exit 1 - fi - - echo "Installing systemd unit to ${UNIT_TARGET}" - install -m 0644 "${UNIT_SOURCE}" "${UNIT_TARGET}" - - if [[ ! -f "${ENV_FILE}" ]]; then - echo "Creating default env file at ${ENV_FILE}" - cat <<"EOF" > "${ENV_FILE}" -# Env vars consumed by brevd. Replace placeholder values before starting. -BREV_AGENT_BREVCLOUD_URL="https://controlplane.example.com/agent/v1" -BREV_AGENT_REGISTRATION_TOKEN="replace-me" -BREV_AGENT_CLOUD_CRED_ID="replace-me" -# Optional overrides: -# BREV_AGENT_DISPLAY_NAME="my-node" -# BREV_AGENT_CLOUD_NAME="edge-cluster-a" -# BREV_AGENT_DEVICE_TOKEN_PATH="/var/lib/brevd/device_token" -# BREV_AGENT_HEARTBEAT_INTERVAL="30s" -# BREV_AGENT_ENABLE_TUNNEL="true" -# BREV_AGENT_TUNNEL_SSH_PORT="22" -EOF - else - echo "Env file already exists at ${ENV_FILE}; leaving as-is" - fi - chmod 600 "${ENV_FILE}" - - echo "Reloading systemd units" - systemctl daemon-reload - - if [[ "${AUTO_ENABLE}" == true ]]; then - echo "Enabling and starting brevd" - systemctl enable --now "$(basename "${UNIT_TARGET}")" - else - echo "Skipping enable/start per --skip-enable" - fi - - echo "Done." -} - -main "$@" - diff --git a/brevcloud-scripts/brevd/systemd/brevd.service b/brevcloud-scripts/brevd/systemd/brevd.service deleted file mode 100644 index 452ebe59..00000000 --- a/brevcloud-scripts/brevd/systemd/brevd.service +++ /dev/null @@ -1,17 +0,0 @@ -[Unit] -Description=Brev Daemon -After=network-online.target -Wants=network-online.target - -[Service] -Type=simple -EnvironmentFile=-/etc/default/brevd -ExecStart=/usr/local/bin/brevd -Restart=on-failure -RestartSec=10s -User=root -Group=root - -[Install] -WantedBy=multi-user.target - diff --git a/pkg/brevdaemon/agent/README.md b/pkg/brevdaemon/agent/README.md deleted file mode 100644 index e69de29b..00000000 diff --git a/pkg/brevdaemon/agent/config/config.go b/pkg/brevdaemon/agent/config/config.go index 2e5e3b98..7e04a4bf 100644 --- a/pkg/brevdaemon/agent/config/config.go +++ b/pkg/brevdaemon/agent/config/config.go @@ -12,13 +12,13 @@ import ( const ( EnvBrevCloudURL = "BREV_AGENT_BREV_CLOUD_URL" - EnvRegistrationToken = "BREV_AGENT_REGISTRATION_TOKEN" + 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" + 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" + 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" diff --git a/pkg/brevdaemon/agent/heartbeat/heartbeat.go b/pkg/brevdaemon/agent/heartbeat/heartbeat.go index 1188e63e..387263ca 100644 --- a/pkg/brevdaemon/agent/heartbeat/heartbeat.go +++ b/pkg/brevdaemon/agent/heartbeat/heartbeat.go @@ -39,7 +39,7 @@ type Runner struct { // Run executes the heartbeat loop until the context is canceled or an // unrecoverable error occurs. -func (r *Runner) Run(ctx context.Context) error { //nolint:gocyclo,funlen // loop coordinates retries, telemetry, and server responses +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) } diff --git a/pkg/brevdaemon/agent/telemetry/utilization.go b/pkg/brevdaemon/agent/telemetry/utilization.go index 4cb87cb0..c286414f 100644 --- a/pkg/brevdaemon/agent/telemetry/utilization.go +++ b/pkg/brevdaemon/agent/telemetry/utilization.go @@ -119,7 +119,7 @@ func readLinuxMemory() (used, total int64, err error) { return total - available, total, nil } -func parseMeminfo(data []byte) (total, available int64, err error) { //nolint:gocyclo // parser handles multiple meminfo keys +func parseMeminfo(data []byte) (total, available int64, err error) { scanner := bufio.NewScanner(bytes.NewReader(data)) var ( memFree int64 diff --git a/pkg/brevdaemon/agent/tunnel/tunnel.go b/pkg/brevdaemon/agent/tunnel/tunnel.go index cd12a5fa..93bfaa9d 100644 --- a/pkg/brevdaemon/agent/tunnel/tunnel.go +++ b/pkg/brevdaemon/agent/tunnel/tunnel.go @@ -186,7 +186,7 @@ type execCmdWrapper struct { } func (w *execCmdWrapper) SetEnv(env []string) { - w.Cmd.Env = env + w.Env = env } func (w *execCmdWrapper) CombinedOutput() ([]byte, error) { @@ -241,10 +241,6 @@ func (m *Manager) configureCloudflaredService(ctx context.Context, token client. name: "sudo", args: []string{"cloudflared", "service", "install", token.Token}, }, - // { - // name: "sudo", - // args: []string{"systemctl", "restart", "cloudflared.service"}, - // }, } for _, cmdSpec := range commands { diff --git a/pkg/cmd/spark/agent.go b/pkg/cmd/spark/agent.go index c4535be3..93d9e39d 100644 --- a/pkg/cmd/spark/agent.go +++ b/pkg/cmd/spark/agent.go @@ -36,10 +36,8 @@ func runSparkAgent(t *terminal.Terminal) error { 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))) - } + 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 index aae3de21..732b1a45 100644 --- a/pkg/cmd/spark/enroll.go +++ b/pkg/cmd/spark/enroll.go @@ -93,7 +93,7 @@ type enrollResult struct { AgentVersion string `json:"agent_version,omitempty"` } -func runSparkEnroll(ctx context.Context, t *terminal.Terminal, loginStore *store.AuthHTTPStore, alias string, opts enrollOptions) error { +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() @@ -108,7 +108,7 @@ func runSparkEnroll(ctx context.Context, t *terminal.Terminal, loginStore *store defer stopSpinner() fail := func(err error) error { - msg := formatEnrollError(err, opts) + msg := formatEnrollError(err) stopSpinner() t.Eprint(t.Red("\n Failed: " + msg)) return errors.New(msg) @@ -232,7 +232,7 @@ func runSparkEnroll(ctx context.Context, t *terminal.Terminal, loginStore *store } if opts.wait && !opts.mockRegistration { - node, err := waitForBrevCloudNode(ctx, brevCloudClient, intent.BrevCloudNodeID, t) + node, err := waitForBrevCloudNode(ctx, brevCloudClient, intent.BrevCloudNodeID) if err != nil { return fail(err) } @@ -284,9 +284,16 @@ func resolveSparkHost(_ *terminal.Terminal, alias string) (sparklib.Host, error) 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 "", err + return "", breverrors.WrapAndTrace(err) } return cred, nil @@ -399,32 +406,18 @@ func writeAgentConfig(ctx context.Context, remote sparklib.RemoteRunner, host sp 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) + lines := []string{ + fmt.Sprintf("%s=%s", agentconfig.EnvBrevCloudURL, brevCloudURL), + fmt.Sprintf("%s=%s", agentconfig.EnvRegistrationToken, registrationToken), + } if brevCloudNodeID != "" { - b.WriteString("\n") - b.WriteString(agentconfig.EnvBrevCloudNodeID) - b.WriteString("=") - b.WriteString(brevCloudNodeID) + lines = append(lines, fmt.Sprintf("%s=%s", agentconfig.EnvBrevCloudNodeID, 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() + 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), @@ -444,27 +437,27 @@ func writeAgentConfig(ctx context.Context, remote sparklib.RemoteRunner, host sp 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) { +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, err + return nil, breverrors.WrapAndTrace(err) } if strings.EqualFold(node.Phase, "ACTIVE") || node.LastSeenAt != "" { return node, nil } select { case <-ctx.Done(): - return nil, ctx.Err() + return nil, breverrors.WrapAndTrace(ctx.Err()) case <-ticker.C: } } } -func formatEnrollError(err error, opts enrollOptions) string { +func formatEnrollError(err error) string { if err == nil { return "" } From 9b5165da152ff54d1d839e840060e95b06fef460 Mon Sep 17 00:00:00 2001 From: Pratik Patel Date: Thu, 8 Jan 2026 16:48:32 -0800 Subject: [PATCH 11/22] fixing linter --- .github/workflows/lint.yml | 8 +-- .golangci.bck.yml | 102 +++++++++++++++++++++++++++++++++++++ .golangci.yml | 24 +++++++-- pkg/brevdaemon/agent.go | 2 +- 4 files changed, 126 insertions(+), 10 deletions(-) create mode 100644 .golangci.bck.yml 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/.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 f2527502..e2f06163 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -6,11 +6,13 @@ linters: default: none enable: - bodyclose + - copyloopvar - dupl - errcheck - forcetypeassert - funlen - gocognit + - goconst - gocritic - gocyclo - goprintffuncname @@ -20,23 +22,30 @@ linters: - misspell - noctx - nolintlint + - revive - rowserrcheck - sqlclosecheck - staticcheck - - thelper - tparallel - unconvert - unparam - unused + - whitespace - wrapcheck settings: + errcheck: + exclude-functions: + - (*encoding/json.Encoder).Encode funlen: lines: 100 gocyclo: - min-complexity: 16 + 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 @@ -54,7 +63,6 @@ linters: - name: increment-decrement - name: var-naming - name: var-declaration - - name: package-comments - name: range - name: receiver-naming - name: time-naming @@ -68,14 +76,20 @@ linters: staticcheck: checks: - all - - -ST1000 - - -ST1020 + - -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: + exclude-use-default: false exclusions: generated: lax paths: diff --git a/pkg/brevdaemon/agent.go b/pkg/brevdaemon/agent.go index 794902a3..f47982f6 100644 --- a/pkg/brevdaemon/agent.go +++ b/pkg/brevdaemon/agent.go @@ -18,7 +18,7 @@ const ( exitCodeError = 3 ) -func main() { +func Main() { os.Exit(runMain()) } From 98798005dd036b3d3e3cce7fa5b4b28480bba9e9 Mon Sep 17 00:00:00 2001 From: Pratik Patel Date: Fri, 9 Jan 2026 11:26:44 -0800 Subject: [PATCH 12/22] linting --- .../workflows/cli-output-compatibility.yml | 20 ++++++------ .golangci.yml | 2 ++ .../configureenvvars/configureenvvars_test.go | 4 +-- pkg/cmd/connect/connect.go | 8 ++--- pkg/cmd/sshkeys/sshkeys.go | 12 +++---- pkg/cmd/start/start.go | 18 ++++++----- pkg/cmd/updatemodel/updatemodel.go | 2 +- pkg/files/files.go | 3 +- pkg/mergeshells/mergeshells.go | 32 +++---------------- pkg/spark/parser_test.go | 10 +++--- pkg/spark/remote.go | 2 +- pkg/store/basic.go | 4 ++- pkg/terminal/terminal.go | 10 +++--- .../containermanager_test.go | 2 +- pkg/workspacemanagerv2/volumes.go | 2 +- 15 files changed, 58 insertions(+), 73 deletions(-) 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/.golangci.yml b/.golangci.yml index e2f06163..11ed8dc9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -76,6 +76,8 @@ linters: 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 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/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/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_test.go b/pkg/spark/parser_test.go index e32f47f0..ad83fc48 100644 --- a/pkg/spark/parser_test.go +++ b/pkg/spark/parser_test.go @@ -7,6 +7,8 @@ import ( "github.com/stretchr/testify/require" ) +const testerHome = "/home/tester" + type staticLocator struct { path string } @@ -39,7 +41,7 @@ Host spark-two err := afero.WriteFile(fs, configPath, []byte(config), 0o600) require.NoError(t, err) - resolver := NewSyncConfigResolver(fs, staticLocator{path: configPath}, func() (string, error) { return "/home/tester", nil }) + 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) @@ -48,7 +50,7 @@ Host spark-two require.Equal(t, "spark-alt", hosts[1].Alias) require.Equal(t, "spark-two", hosts[2].Alias) - require.Equal(t, "/home/tester/.ssh/spark_one", hosts[0].IdentityFile) + 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) @@ -60,7 +62,7 @@ Host spark-two func TestResolveHostsMissingFile(t *testing.T) { fs := afero.NewMemMapFs() - resolver := NewSyncConfigResolver(fs, staticLocator{path: "/tmp/missing"}, func() (string, error) { return "/home/tester", nil }) + resolver := NewSyncConfigResolver(fs, staticLocator{path: "/tmp/missing"}, func() (string, error) { return testerHome, nil }) _, err := resolver.ResolveHosts() require.Error(t, err) @@ -77,7 +79,7 @@ Host spark-one err := afero.WriteFile(fs, configPath, []byte(config), 0o600) require.NoError(t, err) - resolver := NewSyncConfigResolver(fs, staticLocator{path: configPath}, func() (string, error) { return "/home/tester", nil }) + 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/remote.go b/pkg/spark/remote.go index 635a702c..412e4a00 100644 --- a/pkg/spark/remote.go +++ b/pkg/spark/remote.go @@ -37,7 +37,7 @@ func (r RemoteRunner) Run(ctx context.Context, host Host, remoteCmd string) (str } argv := buildSSHCommand(host, remoteCmd) - cmd := exec.CommandContext(ctx, argv[0], argv[1:]...) + cmd := exec.CommandContext(ctx, argv[0], argv[1:]...) //nolint:gosec // command args constructed from validated host data var buf bytes.Buffer cmd.Stdout = &buf 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/workspacemanagerv2/containermanager_test.go b/pkg/workspacemanagerv2/containermanager_test.go index 01363154..37e7ef1d 100644 --- a/pkg/workspacemanagerv2/containermanager_test.go +++ b/pkg/workspacemanagerv2/containermanager_test.go @@ -111,7 +111,7 @@ func Test_Volumes(t *testing.T) { localPath := fmt.Sprintf("/tmp/brevcli-test-volume/%s", uuid.New().String()) fmt.Println(localPath) - err := os.MkdirAll(localPath, os.ModePerm) + err := os.MkdirAll(localPath, 0o750) assert.Nil(t, err) _, err = os.OpenFile(filepath.Join(localPath, "original"), os.O_CREATE, 0o600) //nolint:gosec // test diff --git a/pkg/workspacemanagerv2/volumes.go b/pkg/workspacemanagerv2/volumes.go index 14ce1294..86beab4f 100644 --- a/pkg/workspacemanagerv2/volumes.go +++ b/pkg/workspacemanagerv2/volumes.go @@ -40,7 +40,7 @@ func (s StaticFiles) GetMountFromPath() string { func (s StaticFiles) Setup(_ context.Context) error { path := s.GetMountFromPath() - err := os.MkdirAll(path, os.ModePerm) + err := os.MkdirAll(path, 0o750) if err != nil { return breverrors.WrapAndTrace(err) } From 4e5c3692ca24b3ddf0bccc9af288fe3cf0b22e94 Mon Sep 17 00:00:00 2001 From: Pratik Patel Date: Fri, 9 Jan 2026 11:37:25 -0800 Subject: [PATCH 13/22] removing dead code --- pkg/workspacemanager/workspacemanager.go | 240 ------------- pkg/workspacemanager/workspacemanager_test.go | 28 -- pkg/workspacemanagerv2/containermanager.go | 335 ------------------ .../containermanager_test.go | 141 -------- pkg/workspacemanagerv2/volumes.go | 114 ------ pkg/workspacemanagerv2/volumes_test.go | 12 - pkg/workspacemanagerv2/workspacemanagerv2.go | 311 ---------------- .../workspacemanagerv2_test.go | 104 ------ 8 files changed, 1285 deletions(-) delete mode 100644 pkg/workspacemanager/workspacemanager.go delete mode 100644 pkg/workspacemanager/workspacemanager_test.go delete mode 100644 pkg/workspacemanagerv2/containermanager.go delete mode 100644 pkg/workspacemanagerv2/containermanager_test.go delete mode 100644 pkg/workspacemanagerv2/volumes.go delete mode 100644 pkg/workspacemanagerv2/volumes_test.go delete mode 100644 pkg/workspacemanagerv2/workspacemanagerv2.go delete mode 100644 pkg/workspacemanagerv2/workspacemanagerv2_test.go 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 37e7ef1d..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, 0o750) - 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 86beab4f..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, 0o750) - 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) -} From af3d3c5f1946f91fbb5ebc5d9c7acbe669740093 Mon Sep 17 00:00:00 2001 From: Pratik Patel Date: Fri, 9 Jan 2026 11:49:42 -0800 Subject: [PATCH 14/22] remove devplane pkg dep --- go.mod | 2 -- go.sum | 4 --- pkg/brevdaemon/agent/identity/identity.go | 43 +---------------------- 3 files changed, 1 insertion(+), 48 deletions(-) diff --git a/go.mod b/go.mod index 3dda0356..3970cc53 100644 --- a/go.mod +++ b/go.mod @@ -55,7 +55,6 @@ require ( 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/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/bytedance/sonic v1.11.6 // indirect @@ -83,7 +82,6 @@ require ( 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-ozzo/ozzo-validation/v4 v4.3.0 // 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 diff --git a/go.sum b/go.sum index 6d282632..d611a86f 100644 --- a/go.sum +++ b/go.sum @@ -25,8 +25,6 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI 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= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0= -github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= 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/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= @@ -139,8 +137,6 @@ github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3 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-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= -github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= 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= diff --git a/pkg/brevdaemon/agent/identity/identity.go b/pkg/brevdaemon/agent/identity/identity.go index 15db0566..31bbbbf3 100644 --- a/pkg/brevdaemon/agent/identity/identity.go +++ b/pkg/brevdaemon/agent/identity/identity.go @@ -3,9 +3,7 @@ package identity import ( "context" "crypto/rand" - "crypto/sha256" "encoding/base64" - "encoding/hex" "os" "path/filepath" "strings" @@ -13,7 +11,6 @@ import ( "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/dev-plane/pkg/brevcloud/agent" "github.com/brevdev/dev-plane/pkg/errors" "go.uber.org/zap" ) @@ -185,19 +182,12 @@ func EnsureIdentity( if err != nil { return Identity{}, errors.WrapAndTrace(err) } - hardwareFP, err := computeHardwareFingerprint(hw) - if err != nil { - return Identity{}, errors.WrapAndTrace(err) - } - deviceFPHash := computeDeviceFingerprintHash(hardwareFP, salt) params := client.RegisterParams{ RegistrationToken: cfg.RegistrationToken, DisplayName: cfg.DisplayName, CloudName: cfg.CloudName, Hardware: hw.ToClient(), - HardwareFingerprint: hardwareFP, - DeviceFingerprintHash: deviceFPHash, } log.Info("registering device with brevcloud agent service") @@ -210,9 +200,7 @@ func EnsureIdentity( InstanceID: res.BrevCloudNodeID, DeviceToken: res.DeviceToken, DeviceSalt: salt, - DeviceFingerprintHash: deviceFPHash, DeviceFingerprintStored: res.DeviceFingerprint, - HardwareFingerprint: hardwareFP, } if err := store.Save(newIdentity); err != nil { return Identity{}, errors.WrapAndTrace(err) @@ -277,33 +265,4 @@ func ensureDeviceSalt(path string) (string, error) { return "", errors.WrapAndTrace(err) } return salt, nil -} - -func computeHardwareFingerprint(hw telemetry.HardwareInfo) (string, error) { - desc := agent.HardwareDescriptor{ - CPUs: hw.CPUCount, - RAM: hw.RAMBytes, - } - for _, gpu := range hw.GPUs { - count := gpu.Count - if count <= 0 { - count = 1 - } - for i := 0; i < count; i++ { - desc.GPUs = append(desc.GPUs, agent.GPUDescriptor{ - Model: gpu.Model, - Memory: gpu.MemoryBytes, - }) - } - } - fp, err := agent.ComputeHardwareFingerprint(desc) - if err != nil { - return "", errors.WrapAndTrace(err) - } - return fp, nil -} - -func computeDeviceFingerprintHash(hardwareFP, deviceSalt string) string { - sum := sha256.Sum256([]byte(strings.Join([]string{hardwareFP, deviceSalt}, "|"))) - return hex.EncodeToString(sum[:]) -} +} \ No newline at end of file From e702e85e4a06b272c38cfb9330f9c2a2d1455009 Mon Sep 17 00:00:00 2001 From: Pratik Patel Date: Fri, 9 Jan 2026 12:10:03 -0800 Subject: [PATCH 15/22] remove client wrapper --- pkg/brevdaemon/agent/agent.go | 21 +- pkg/brevdaemon/agent/agent_test.go | 19 +- pkg/brevdaemon/agent/client/client.go | 564 +----------------- pkg/brevdaemon/agent/client/client_test.go | 278 +-------- pkg/brevdaemon/agent/health/reporter.go | 8 +- pkg/brevdaemon/agent/health/reporter_test.go | 12 +- pkg/brevdaemon/agent/heartbeat/heartbeat.go | 84 +-- .../agent/heartbeat/heartbeat_test.go | 90 ++- pkg/brevdaemon/agent/identity/identity.go | 31 +- .../agent/identity/identity_test.go | 46 +- pkg/brevdaemon/agent/telemetry/hardware.go | 69 ++- .../agent/telemetry/hardware_test.go | 22 +- pkg/brevdaemon/agent/telemetry/utilization.go | 57 +- .../agent/telemetry/utilization_test.go | 12 +- pkg/brevdaemon/agent/tunnel/ingress.go | 24 +- pkg/brevdaemon/agent/tunnel/ingress_test.go | 68 ++- pkg/brevdaemon/agent/tunnel/tunnel.go | 68 ++- pkg/brevdaemon/agent/tunnel/tunnel_test.go | 32 +- 18 files changed, 444 insertions(+), 1061 deletions(-) diff --git a/pkg/brevdaemon/agent/agent.go b/pkg/brevdaemon/agent/agent.go index fce28961..ab0946bf 100644 --- a/pkg/brevdaemon/agent/agent.go +++ b/pkg/brevdaemon/agent/agent.go @@ -5,6 +5,7 @@ import ( "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" @@ -15,6 +16,7 @@ import ( "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. @@ -37,7 +39,7 @@ type agent struct { tunnel tunnelProcess statusReporter *health.Reporter - statusUpdates chan client.HeartbeatStatus + statusUpdates chan *brevapiv2.BrevCloudNodeStatus } var ( @@ -72,13 +74,13 @@ func NewAgent(cfg agentconfig.Config, log *zap.Logger) (Agent, error) { } statusReporter := health.NewReporter(health.Status{ - Phase: client.NodePhaseActive, + Phase: brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ACTIVE, LastTransitionTime: time.Now(), }) - defaultStatus := client.HeartbeatStatus{ - Phase: client.NodePhaseActive, + defaultStatus := &brevapiv2.BrevCloudNodeStatus{ + Phase: brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ACTIVE, } - statusUpdates := make(chan client.HeartbeatStatus, 1) + statusUpdates := make(chan *brevapiv2.BrevCloudNodeStatus, 1) hbRunner := &heartbeat.Runner{ Client: cli, @@ -88,7 +90,7 @@ func NewAgent(cfg agentconfig.Config, log *zap.Logger) (Agent, error) { MaxInterval: defaultHeartbeatMaxInterval, }, Log: log.Named("heartbeat"), - DefaultStatus: &defaultStatus, + DefaultStatus: defaultStatus, StatusUpdates: statusUpdates, } @@ -185,14 +187,13 @@ func (a *agent) startStatusBridge(ctx context.Context) func() { return cancel } -func toHeartbeatStatus(status health.Status) client.HeartbeatStatus { - hbStatus := client.HeartbeatStatus{ +func toHeartbeatStatus(status health.Status) *brevapiv2.BrevCloudNodeStatus { + hbStatus := &brevapiv2.BrevCloudNodeStatus{ Phase: status.Phase, Detail: status.Detail, } if !status.LastTransitionTime.IsZero() { - t := status.LastTransitionTime - hbStatus.LastTransitionTime = &t + hbStatus.LastTransitionTime = timestamppb.New(status.LastTransitionTime) } return hbStatus } diff --git a/pkg/brevdaemon/agent/agent_test.go b/pkg/brevdaemon/agent/agent_test.go index 8b29619f..22f898a5 100644 --- a/pkg/brevdaemon/agent/agent_test.go +++ b/pkg/brevdaemon/agent/agent_test.go @@ -7,6 +7,9 @@ import ( "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" @@ -28,7 +31,7 @@ func TestNewAgentBuildsDependencies(t *testing.T) { ensureIdentity = origEnsure }) - newBrevCloudAgentClient = func(agentconfig.Config, ...client.Option) (client.BrevCloudAgentClient, error) { + newBrevCloudAgentClient = func(agentconfig.Config, ...client.Option) (brevapiv2connect.BrevCloudAgentServiceClient, error) { return &stubBrevCloudClient{}, nil } @@ -39,7 +42,7 @@ func TestNewAgentBuildsDependencies(t *testing.T) { } var ensureCalled bool - ensureIdentity = func(_ context.Context, _ agentconfig.Config, _ client.BrevCloudAgentClient, _ *identity.IdentityStore, _ telemetry.HardwareInfo, _ *zap.Logger) (identity.Identity, error) { + 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", @@ -166,16 +169,16 @@ func TestAgentRunIgnoresTunnelError(t *testing.T) { type stubBrevCloudClient struct{} -func (s *stubBrevCloudClient) Register(context.Context, client.RegisterParams) (client.RegisterResult, error) { - return client.RegisterResult{}, nil +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, client.HeartbeatParams) (client.HeartbeatResult, error) { - return client.HeartbeatResult{}, 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, client.TunnelTokenParams) (client.TunnelTokenResult, error) { - return client.TunnelTokenResult{}, 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 { diff --git a/pkg/brevdaemon/agent/client/client.go b/pkg/brevdaemon/agent/client/client.go index 1186b2e0..276d9700 100644 --- a/pkg/brevdaemon/agent/client/client.go +++ b/pkg/brevdaemon/agent/client/client.go @@ -1,166 +1,17 @@ package client import ( - "context" - "math" "net/http" - "time" brevapiv2connect "buf.build/gen/go/brevdev/devplane/connectrpc/go/brevapi/v2/brevapiv2connect" brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" - devplaneapiv1 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/devplaneapi/v1" "connectrpc.com/connect" "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/config" "github.com/brevdev/brev-cli/pkg/errors" - "github.com/brevdev/dev-plane/pkg/brevcloud/tunnel" - "google.golang.org/protobuf/types/known/timestamppb" ) -// BrevCloudAgentClient defines the BrevCloud agent RPCs exercised by the brev-agent binary. -type BrevCloudAgentClient interface { - Register(ctx context.Context, req RegisterParams) (RegisterResult, error) - Heartbeat(ctx context.Context, req HeartbeatParams) (HeartbeatResult, error) - GetTunnelToken(ctx context.Context, req TunnelTokenParams) (TunnelTokenResult, error) -} - -// RegisterParams carries the payload for BrevCloudAgentService.Register. -type RegisterParams struct { - RegistrationToken string - DisplayName string - CloudName string - Capabilities []string - Hardware *HardwareInfo - AgentVersion string - HardwareFingerprint string - DeviceFingerprintHash string -} - -// RegisterResult captures the subset of response fields the agent cares about. -type RegisterResult struct { - BrevCloudNodeID string - DeviceToken string - HeartbeatInterval time.Duration - DisplayName string - CloudName string - CloudCredID string - DeviceFingerprint string -} - -// HeartbeatParams drives BrevCloudAgentService.Heartbeat. -type HeartbeatParams struct { - BrevCloudNodeID string - DeviceToken string - ObservedAt time.Time - Status *HeartbeatStatus - Utilization *UtilizationInfo - AgentVersion string - DisplayName string - CloudName string - DeviceFingerprintHash string - HardwareFingerprint string -} - -// HeartbeatStatus mirrors the minimal metadata fields surfaced by the proto. -type HeartbeatStatus struct { - Phase NodePhase - Detail string - LastTransitionTime *time.Time -} - -// HeartbeatResult returns server guidance after a heartbeat. -type HeartbeatResult struct { - ServerTime time.Time - NextHeartbeatInterval time.Duration - NodeConfig *brevapiv2.BrevCloudNodeConfig - Commands []*brevapiv2.BrevCloudCommand -} - -// TunnelTokenParams drives BrevCloudAgentService.GetTunnelToken. -type TunnelTokenParams struct { - BrevCloudNodeID string - DeviceToken string - TunnelName string - Ports []tunnel.TunnelPortMapping - AppIngresses []AppIngress -} - -// AppIngress describes a single HTTP ingress request from the agent to the control plane. -type AppIngress struct { - AppID string - Protocol string - LocalPort int - RemotePort int - HostnamePrefix string - PathPrefix string - ForceHTTPS bool -} - -// TunnelTokenResult returns tunnel connection metadata. -type TunnelTokenResult struct { - Token string - Endpoint string - TTL time.Duration - ExpiresAt *time.Time - SecondsToExp *time.Duration - PortMappings []*brevapiv2.TunnelPortMapping -} - -// HardwareInfo is the DTO the agent uses when registering. -type HardwareInfo struct { - CPUCount int - RAMBytes int64 - GPUs []GPUInfo - MachineModel string - Architecture string - Storage []StorageInfo -} - -// GPUInfo captures high-level GPU specs. -type GPUInfo struct { - Model string - MemoryBytes int64 - Count int -} - -// StorageInfo captures block device capacity for registration. -type StorageInfo struct { - Name string - Capacity int64 - Type string -} - -// UtilizationInfo wraps the runtime metrics included in heartbeats. -type UtilizationInfo struct { - CPUPercent float32 - MemoryUsedBytes int64 - MemoryTotalBytes int64 - DiskPercent float32 - DiskUsedBytes int64 - DiskTotalBytes int64 - GPUs []GPUUtilization -} - -// GPUUtilization mirrors the proto payload for GPU metrics. -type GPUUtilization struct { - Index int - Model string - UtilizationPercent float32 - MemoryUsedBytes int64 - MemoryTotalBytes int64 - TemperatureCelsius *float32 -} - -// NodePhase aligns with devplaneapi.v1.BrevCloudNodePhase without exposing proto enums to callers. -type NodePhase int - -const ( - NodePhaseUnspecified NodePhase = iota - NodePhaseWaitingForRegistration - NodePhaseActive - NodePhaseOffline - NodePhaseStopped - NodePhaseError -) +// Client is an alias to the generated BrevCloud agent RPC client. +type Client = brevapiv2connect.BrevCloudAgentServiceClient // Option configures client construction. type Option func(*options) @@ -192,25 +43,8 @@ func WithRPCClient(rpc brevapiv2connect.BrevCloudAgentServiceClient) Option { } } -// 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 -} - -// New constructs a BrevCloudAgentClient backed by the Connect RPC client. -func New(cfg config.Config, opts ...Option) (BrevCloudAgentClient, error) { +// 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") } @@ -221,370 +55,38 @@ func New(cfg config.Config, opts ...Option) (BrevCloudAgentClient, error) { opt(&merged) } } + httpClient := merged.httpClient if httpClient == nil { httpClient = http.DefaultClient } - var rpcClient brevapiv2connect.BrevCloudAgentServiceClient if merged.customRPC != nil { - rpcClient = merged.customRPC - } else { - rpcClient = brevapiv2connect.NewBrevCloudAgentServiceClient(httpClient, cfg.BrevCloudAgentURL, merged.clientOpts...) - } - - return &brevcloudAgentClient{ - rpc: rpcClient, - }, nil -} - -type brevcloudAgentClient struct { - rpc brevapiv2connect.BrevCloudAgentServiceClient -} - -func (c *brevcloudAgentClient) Register(ctx context.Context, params RegisterParams) (RegisterResult, error) { - if params.RegistrationToken == "" { - return RegisterResult{}, errors.Errorf("registration token is required") - } - if params.DeviceFingerprintHash == "" { - return RegisterResult{}, errors.Errorf("device fingerprint hash is required") - } - - req := &brevapiv2.RegisterRequest{ - RegistrationToken: params.RegistrationToken, - Capabilities: params.Capabilities, - Hardware: hardwareInfoToProto(params.Hardware), - DeviceFingerprintHash: params.DeviceFingerprintHash, - HardwareFingerprint: params.HardwareFingerprint, - } - if params.DisplayName != "" { - req.DisplayName = protoString(params.DisplayName) - } - if params.CloudName != "" { - req.CloudName = protoString(params.CloudName) - } - if params.AgentVersion != "" { - req.Agent = &brevapiv2.AgentInfo{ - Version: params.AgentVersion, - } - } - - resp, err := c.rpc.Register(ctx, connect.NewRequest(req)) - if err != nil { - return RegisterResult{}, classifyError(err) - } - - result := RegisterResult{ - BrevCloudNodeID: resp.Msg.GetBrevCloudNodeId(), - DeviceToken: resp.Msg.GetDeviceToken(), - DisplayName: resp.Msg.GetDisplayName(), - CloudName: resp.Msg.GetCloudName(), - CloudCredID: resp.Msg.GetCloudCredId(), - DeviceFingerprint: resp.Msg.GetDeviceFingerprint(), - } - if interval := resp.Msg.GetHeartbeatInterval(); interval != nil { - result.HeartbeatInterval = interval.AsDuration() - } - return result, nil -} - -func (c *brevcloudAgentClient) Heartbeat(ctx context.Context, params HeartbeatParams) (HeartbeatResult, error) { - if params.BrevCloudNodeID == "" { - return HeartbeatResult{}, errors.Errorf("brevcloud node id is required") - } - if params.DeviceToken == "" { - return HeartbeatResult{}, errors.Errorf("device token is required") + return merged.customRPC, nil } - req := &brevapiv2.HeartbeatRequest{ - BrevCloudNodeId: params.BrevCloudNodeID, - Utilization: utilizationToProto(params.Utilization), - } - - if !params.ObservedAt.IsZero() { - req.ObservedAt = timestamppbNew(params.ObservedAt) - } - if params.AgentVersion != "" { - req.Agent = &brevapiv2.AgentInfo{ - Version: params.AgentVersion, - } - } - if params.Status != nil { - req.Status = heartbeatStatusToProto(params.Status) - } - if params.DisplayName != "" { - req.DisplayName = protoString(params.DisplayName) - } - if params.CloudName != "" { - req.CloudName = protoString(params.CloudName) - } - if params.DeviceFingerprintHash != "" { - req.DeviceFingerprintHash = params.DeviceFingerprintHash - } - if params.HardwareFingerprint != "" { - req.HardwareFingerprint = params.HardwareFingerprint - } - - connectReq := connect.NewRequest(req) - connectReq.Header().Set("Authorization", bearerToken(params.DeviceToken)) - - resp, err := c.rpc.Heartbeat(ctx, connectReq) - if err != nil { - return HeartbeatResult{}, classifyError(err) - } - - result := HeartbeatResult{ - NodeConfig: resp.Msg.GetNodeConfig(), - Commands: resp.Msg.GetCommands(), - } - if ts := resp.Msg.GetServerTime(); ts != nil { - result.ServerTime = ts.AsTime() - } - if interval := resp.Msg.GetNextHeartbeatInterval(); interval != nil { - result.NextHeartbeatInterval = interval.AsDuration() - } - return result, nil + return brevapiv2connect.NewBrevCloudAgentServiceClient(httpClient, cfg.BrevCloudAgentURL, merged.clientOpts...), nil } -func (c *brevcloudAgentClient) GetTunnelToken(ctx context.Context, params TunnelTokenParams) (TunnelTokenResult, error) { - if params.BrevCloudNodeID == "" { - return TunnelTokenResult{}, errors.Errorf("brevcloud node id is required") - } - if params.DeviceToken == "" { - return TunnelTokenResult{}, errors.Errorf("device token is required") - } - - req := &brevapiv2.GetTunnelTokenRequest{ - BrevCloudNodeId: params.BrevCloudNodeID, - RequestedPorts: tunnelPortsToProto(params.Ports), - } - if params.TunnelName != "" { - req.TunnelName = protoString(params.TunnelName) - } - if ingresses := appIngressesToProto(params.AppIngresses); len(ingresses) > 0 { - req.AppIngresses = ingresses - } - - connectReq := connect.NewRequest(req) - connectReq.Header().Set("Authorization", bearerToken(params.DeviceToken)) - - resp, err := c.rpc.GetTunnelToken(ctx, connectReq) - if err != nil { - return TunnelTokenResult{}, classifyError(err) - } - - result := TunnelTokenResult{ - Token: resp.Msg.GetToken(), - Endpoint: resp.Msg.GetEndpoint(), - PortMappings: resp.Msg.GetPortMappings(), - } - if ttl := resp.Msg.GetTtl(); ttl != nil { - result.TTL = ttl.AsDuration() - } - if expires := resp.Msg.GetExpiresAt(); expires != nil { - t := expires.AsTime() - result.ExpiresAt = &t - if now := time.Now(); t.After(now) { - d := t.Sub(now) - result.SecondsToExp = &d - } - } - return result, nil -} - -func hardwareInfoToProto(info *HardwareInfo) *brevapiv2.HardwareInfo { - if info == nil { - return nil - } - - cpuCount := clampToInt32(info.CPUCount) - out := &brevapiv2.HardwareInfo{ - CpuCount: cpuCount, - } - if info.RAMBytes > 0 { - out.RamBytes = bytesValue(info.RAMBytes) - } - if info.MachineModel != "" { - out.SystemModel = protoString(info.MachineModel) - } - if info.Architecture != "" { - out.Architecture = protoString(info.Architecture) - } - if len(info.Storage) > 0 { - out.Storage = make([]*brevapiv2.StorageInfo, 0, len(info.Storage)) - for _, s := range info.Storage { - entry := &brevapiv2.StorageInfo{ - Name: s.Name, - Type: s.Type, - } - if s.Capacity > 0 { - entry.Capacity = bytesValue(s.Capacity) - } - out.Storage = append(out.Storage, entry) - } - } - if len(info.GPUs) > 0 { - out.Gpus = make([]*brevapiv2.GPUInfo, 0, len(info.GPUs)) - for _, gpu := range info.GPUs { - out.Gpus = append(out.Gpus, &brevapiv2.GPUInfo{ - Model: gpu.Model, - Count: clampToInt32(gpu.Count), - MemoryBytes: bytesValue(gpu.MemoryBytes), - }) - } - } - return out -} - -func utilizationToProto(info *UtilizationInfo) *brevapiv2.ResourceUtilization { - if info == nil { - return nil - } - out := &brevapiv2.ResourceUtilization{ - CpuPercent: info.CPUPercent, - DiskPercent: info.DiskPercent, - } - if info.MemoryUsedBytes > 0 { - out.MemoryUsed = bytesValue(info.MemoryUsedBytes) - } - if info.MemoryTotalBytes > 0 { - out.MemoryTotal = bytesValue(info.MemoryTotalBytes) - } - if info.DiskUsedBytes > 0 { - out.DiskUsed = bytesValue(info.DiskUsedBytes) - } - if info.DiskTotalBytes > 0 { - out.DiskTotal = bytesValue(info.DiskTotalBytes) - } - if len(info.GPUs) > 0 { - out.Gpus = make([]*brevapiv2.GPUUtilization, 0, len(info.GPUs)) - for _, gpu := range info.GPUs { - out.Gpus = append(out.Gpus, gpuUtilizationToProto(gpu)) - } - } - return out -} - -func heartbeatStatusToProto(status *HeartbeatStatus) *brevapiv2.BrevCloudNodeStatus { - if status == nil { - return nil - } - out := &brevapiv2.BrevCloudNodeStatus{ - Phase: convertNodePhase(status.Phase), - Detail: status.Detail, - } - if status.LastTransitionTime != nil && !status.LastTransitionTime.IsZero() { - out.LastTransitionTime = timestamppbNew(*status.LastTransitionTime) - } - return out -} - -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 -} - -func tunnelPortsToProto(ports []tunnel.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: int32(lp), - RemotePort: int32(rp), - Protocol: port.Protocol, - }) - } - if len(out) == 0 { - return nil - } - return out -} - -func appIngressesToProto(ingresses []AppIngress) []*brevapiv2.AppIngressRequest { - if len(ingresses) == 0 { - return nil - } - - out := make([]*brevapiv2.AppIngressRequest, 0, len(ingresses)) - for _, ingress := range ingresses { - lp := ingress.LocalPort - rp := ingress.RemotePort - - if lp <= 0 || lp > math.MaxInt32 || rp < 0 || rp > math.MaxInt32 { - continue - } - - out = append(out, &brevapiv2.AppIngressRequest{ - AppId: ingress.AppID, - Protocol: ingress.Protocol, - LocalPort: int32(lp), //nolint:gosec // G115: range checked above. - RemotePort: int32(rp), - HostnamePrefix: ingress.HostnamePrefix, - PathPrefix: ingress.PathPrefix, - ForceHttps: ingress.ForceHTTPS, - }) - } - - if len(out) == 0 { - return nil - } - return out -} +// ErrUnauthenticated indicates the control plane rejected a token. +var ErrUnauthenticated = errors.New("brevcloudagent: unauthenticated request") -func bytesValue(v int64) *devplaneapiv1.Bytes { - if v <= 0 { - return nil - } - return &devplaneapiv1.Bytes{Value: v} +// RegistrationError provides structured context when registration is rejected. +type RegistrationError struct { + Reason brevapiv2.BrevCloudRegistrationErrorReason + Msg string } -func convertNodePhase(phase NodePhase) brevapiv2.BrevCloudNodePhase { - switch phase { - case NodePhaseWaitingForRegistration: - return brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_WAITING_FOR_REGISTRATION - case NodePhaseActive: - return brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ACTIVE - case NodePhaseOffline: - return brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_OFFLINE - case NodePhaseStopped: - return brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_STOPPED - case NodePhaseError: - return brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ERROR - default: - return brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_UNSPECIFIED +// Error satisfies the error interface. +func (r *RegistrationError) Error() string { + if r == nil { + return "registration error" } + return r.Msg } -func classifyError(err error) error { +// ClassifyError unwraps Connect errors to return richer error types used by callers. +func ClassifyError(err error) error { if err == nil { return nil } @@ -621,27 +123,15 @@ func registrationErrorFromConnect(err *connect.Error) error { return nil } -func protoString(s string) *string { - if s == "" { - return nil - } - return &s -} - -func timestamppbNew(t time.Time) *timestamppb.Timestamp { - return timestamppb.New(t) -} - -func bearerToken(token string) string { +// BearerToken returns the HTTP Authorization header value for the provided token. +func BearerToken(token string) string { return "Bearer " + token } -func clampToInt32(v int) int32 { - if v > math.MaxInt32 { - return math.MaxInt32 - } - if v < math.MinInt32 { - return math.MinInt32 +// ProtoString returns a protobuf-compatible optional string pointer when the value is non-empty. +func ProtoString(value string) *string { + if value == "" { + return nil } - return int32(v) + return &value } diff --git a/pkg/brevdaemon/agent/client/client_test.go b/pkg/brevdaemon/agent/client/client_test.go index c15c60e2..b4dd9511 100644 --- a/pkg/brevdaemon/agent/client/client_test.go +++ b/pkg/brevdaemon/agent/client/client_test.go @@ -2,289 +2,61 @@ package client import ( "context" - stderrors "errors" - "net/http" - "net/http/httptest" + stderrs "errors" "testing" - "time" - "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/dev-plane/pkg/brevcloud/tunnel" "github.com/stretchr/testify/require" - "google.golang.org/protobuf/types/known/durationpb" - "google.golang.org/protobuf/types/known/timestamppb" ) -func TestRegisterSuccess(t *testing.T) { - var captured *brevapiv2.RegisterRequest - svc := &testBrevCloudAgentService{ - registerFn: func(_ context.Context, req *connect.Request[brevapiv2.RegisterRequest]) (*connect.Response[brevapiv2.RegisterResponse], error) { - captured = req.Msg - return connect.NewResponse(&brevapiv2.RegisterResponse{ - BrevCloudNodeId: "fn-123", - DeviceToken: "device-token", - HeartbeatInterval: durationpb.New(45 * time.Second), - DisplayName: "stored-name", - CloudName: "stored-cloud", - CloudCredId: "cc-123", - DeviceFingerprint: "scoped-device-fp", - }), nil - }, - } - - client := newTestClient(t, svc) - - params := RegisterParams{ - RegistrationToken: "reg-token", - DisplayName: "node-one", - CloudName: "private-cloud", - AgentVersion: "v1.2.3", - Capabilities: []string{"docker"}, - HardwareFingerprint: "shape-fp", - DeviceFingerprintHash: "device-fp-hash", - Hardware: &HardwareInfo{ - CPUCount: 16, - RAMBytes: 64 << 30, - Architecture: "amd64", - GPUs: []GPUInfo{ - {Model: "A100", MemoryBytes: 40 << 30, Count: 2}, - }, - Storage: []StorageInfo{ - {Name: "nvme0n1", Capacity: 1 << 40, Type: "nvme"}, - }, - }, - } - - result, err := client.Register(context.Background(), params) +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, "fn-123", result.BrevCloudNodeID) - require.Equal(t, "device-token", result.DeviceToken) - require.Equal(t, 45*time.Second, result.HeartbeatInterval) - require.Equal(t, "stored-name", result.DisplayName) - require.Equal(t, "stored-cloud", result.CloudName) - require.Equal(t, "cc-123", result.CloudCredID) - require.Equal(t, "scoped-device-fp", result.DeviceFingerprint) - - require.Equal(t, "node-one", captured.GetDisplayName()) - require.Equal(t, "private-cloud", captured.GetCloudName()) - require.Equal(t, "reg-token", captured.GetRegistrationToken()) - require.Equal(t, []string{"docker"}, captured.GetCapabilities()) - require.Equal(t, "shape-fp", captured.GetHardwareFingerprint()) - require.Equal(t, "device-fp-hash", captured.GetDeviceFingerprintHash()) - require.Equal(t, int32(16), captured.GetHardware().GetCpuCount()) - require.Equal(t, int64(64<<30), captured.GetHardware().GetRamBytes().GetValue()) - require.Len(t, captured.GetHardware().GetGpus(), 1) - require.Equal(t, "A100", captured.GetHardware().GetGpus()[0].GetModel()) - require.Equal(t, int64(40<<30), captured.GetHardware().GetGpus()[0].GetMemoryBytes().GetValue()) - require.Equal(t, int32(2), captured.GetHardware().GetGpus()[0].GetCount()) - require.Equal(t, "amd64", captured.GetHardware().GetArchitecture()) - require.Len(t, captured.GetHardware().GetStorage(), 1) - require.Equal(t, "nvme0n1", captured.GetHardware().GetStorage()[0].GetName()) - require.Equal(t, int64(1<<40), captured.GetHardware().GetStorage()[0].GetCapacity().GetValue()) - require.Equal(t, "nvme", captured.GetHardware().GetStorage()[0].GetType()) - require.Equal(t, "v1.2.3", captured.GetAgent().GetVersion()) + require.Equal(t, custom, cli) } -func TestHeartbeatSendsAuthorizationAndPayload(t *testing.T) { - var ( - capturedHeader string - capturedReq *brevapiv2.HeartbeatRequest - ) - now := time.Now().UTC().Truncate(time.Second) - lastTransition := now.Add(-time.Hour) - temp := float32(65.5) - - svc := &testBrevCloudAgentService{ - heartbeatFn: func(_ context.Context, req *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) { - capturedHeader = req.Header().Get("Authorization") - capturedReq = req.Msg - return connect.NewResponse(&brevapiv2.HeartbeatResponse{ - ServerTime: timestamppb.New(now.Add(5 * time.Second)), - NextHeartbeatInterval: durationpb.New(90 * time.Second), - }), nil - }, - } - - client := newTestClient(t, svc) - - params := HeartbeatParams{ - BrevCloudNodeID: "fn-1", - DeviceToken: "device-token", - ObservedAt: now, - AgentVersion: "v2.0.0", - DisplayName: "node-one", - CloudName: "edge", - DeviceFingerprintHash: "device-fp-hash", - HardwareFingerprint: "shape-fp", - Status: &HeartbeatStatus{ - Phase: NodePhaseActive, - Detail: "running", - LastTransitionTime: &lastTransition, - }, - Utilization: &UtilizationInfo{ - CPUPercent: 25.5, - MemoryUsedBytes: 4 << 30, - MemoryTotalBytes: 16 << 30, - DiskPercent: 50, - DiskUsedBytes: 100 << 30, - DiskTotalBytes: 200 << 30, - GPUs: []GPUUtilization{ - { - Index: 0, - Model: "A100", - UtilizationPercent: 80, - MemoryUsedBytes: 10 << 30, - MemoryTotalBytes: 40 << 30, - TemperatureCelsius: &temp, - }, - }, - }, - } - - result, err := client.Heartbeat(context.Background(), params) - require.NoError(t, err) - require.Equal(t, "Bearer device-token", capturedHeader) - require.Equal(t, "fn-1", capturedReq.GetBrevCloudNodeId()) - require.Equal(t, now, capturedReq.GetObservedAt().AsTime()) - require.Equal(t, "v2.0.0", capturedReq.GetAgent().GetVersion()) - require.Equal(t, brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ACTIVE, capturedReq.GetStatus().GetPhase()) - require.Equal(t, "running", capturedReq.GetStatus().GetDetail()) - require.Equal(t, lastTransition.UTC(), capturedReq.GetStatus().GetLastTransitionTime().AsTime()) - require.Equal(t, float32(25.5), capturedReq.GetUtilization().GetCpuPercent()) - require.Equal(t, int64(4<<30), capturedReq.GetUtilization().GetMemoryUsed().GetValue()) - require.Equal(t, "node-one", capturedReq.GetDisplayName()) - require.Equal(t, "edge", capturedReq.GetCloudName()) - require.Equal(t, "device-fp-hash", capturedReq.GetDeviceFingerprintHash()) - require.Equal(t, "shape-fp", capturedReq.GetHardwareFingerprint()) - - require.Equal(t, now.Add(5*time.Second), result.ServerTime) - require.Equal(t, 90*time.Second, result.NextHeartbeatInterval) +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 TestGetTunnelTokenSendsPortsAndAuth(t *testing.T) { - var capturedReq *brevapiv2.GetTunnelTokenRequest - svc := &testBrevCloudAgentService{ - tunnelFn: func(_ context.Context, req *connect.Request[brevapiv2.GetTunnelTokenRequest]) (*connect.Response[brevapiv2.GetTunnelTokenResponse], error) { - capturedReq = req.Msg - require.Equal(t, "Bearer device-token", req.Header().Get("Authorization")) - return connect.NewResponse(&brevapiv2.GetTunnelTokenResponse{ - Token: "tunnel-token", - Endpoint: "example.dev:443", - Ttl: durationpb.New(2 * time.Minute), - }), nil - }, - } - - client := newTestClient(t, svc) - result, err := client.GetTunnelToken(context.Background(), TunnelTokenParams{ - BrevCloudNodeID: "fn-1", - DeviceToken: "device-token", - TunnelName: "default", - Ports: []tunnel.TunnelPortMapping{ - {LocalPort: 22}, - {RemotePort: 8080}, - }, +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, err) - require.Equal(t, "fn-1", capturedReq.GetBrevCloudNodeId()) - require.Equal(t, "default", capturedReq.GetTunnelName()) - require.Len(t, capturedReq.GetRequestedPorts(), 2) - require.Equal(t, int32(22), capturedReq.GetRequestedPorts()[0].GetLocalPort()) - require.Equal(t, int32(22), capturedReq.GetRequestedPorts()[0].GetRemotePort()) - require.Equal(t, int32(8080), capturedReq.GetRequestedPorts()[1].GetRemotePort()) - - require.Equal(t, "tunnel-token", result.Token) - require.Equal(t, "example.dev:443", result.Endpoint) - require.Equal(t, 2*time.Minute, result.TTL) -} - -func TestRegisterMapsRegistrationError(t *testing.T) { - svc := &testBrevCloudAgentService{ - registerFn: func(context.Context, *connect.Request[brevapiv2.RegisterRequest]) (*connect.Response[brevapiv2.RegisterResponse], error) { - connectErr := connect.NewError(connect.CodeInvalidArgument, stderrors.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) - connectErr.AddDetail(detail) - return nil, connectErr - }, - } + require.NoError(t, detailErr) + raw.AddDetail(detail) - client := newTestClient(t, svc) - _, err := client.Register(context.Background(), RegisterParams{ - RegistrationToken: "token", - DeviceFingerprintHash: "device-fp-hash", - HardwareFingerprint: "shape-fp", - }) + err := ClassifyError(raw) require.Error(t, err) var regErr *RegistrationError - require.True(t, stderrors.As(err, ®Err)) + 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 TestHeartbeatUnauthenticated(t *testing.T) { - svc := &testBrevCloudAgentService{ - heartbeatFn: func(context.Context, *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) { - return nil, connect.NewError(connect.CodeUnauthenticated, stderrors.New("invalid token")) - }, - } - - client := newTestClient(t, svc) - _, err := client.Heartbeat(context.Background(), HeartbeatParams{ - BrevCloudNodeID: "fn-1", - DeviceToken: "bad-token", - }) - require.Error(t, err) - require.True(t, stderrors.Is(err, ErrUnauthenticated)) +func TestBearerToken(t *testing.T) { + require.Equal(t, "Bearer abc", BearerToken("abc")) } -type testBrevCloudAgentService struct { - registerFn func(ctx context.Context, req *connect.Request[brevapiv2.RegisterRequest]) (*connect.Response[brevapiv2.RegisterResponse], error) - heartbeatFn func(ctx context.Context, req *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) - tunnelFn func(ctx context.Context, req *connect.Request[brevapiv2.GetTunnelTokenRequest]) (*connect.Response[brevapiv2.GetTunnelTokenResponse], error) -} +type stubRPC struct{} -func (s *testBrevCloudAgentService) Register(ctx context.Context, req *connect.Request[brevapiv2.RegisterRequest]) (*connect.Response[brevapiv2.RegisterResponse], error) { - if s.registerFn != nil { - return s.registerFn(ctx, req) - } +func (s *stubRPC) Register(context.Context, *connect.Request[brevapiv2.RegisterRequest]) (*connect.Response[brevapiv2.RegisterResponse], error) { return connect.NewResponse(&brevapiv2.RegisterResponse{}), nil } -func (s *testBrevCloudAgentService) Heartbeat(ctx context.Context, req *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) { - if s.heartbeatFn != nil { - return s.heartbeatFn(ctx, req) - } +func (s *stubRPC) Heartbeat(context.Context, *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) { return connect.NewResponse(&brevapiv2.HeartbeatResponse{}), nil } -func (s *testBrevCloudAgentService) GetTunnelToken(ctx context.Context, req *connect.Request[brevapiv2.GetTunnelTokenRequest]) (*connect.Response[brevapiv2.GetTunnelTokenResponse], error) { - if s.tunnelFn != nil { - return s.tunnelFn(ctx, req) - } +func (s *stubRPC) GetTunnelToken(context.Context, *connect.Request[brevapiv2.GetTunnelTokenRequest]) (*connect.Response[brevapiv2.GetTunnelTokenResponse], error) { return connect.NewResponse(&brevapiv2.GetTunnelTokenResponse{}), nil } - -func newTestClient(t *testing.T, handler brevapiv2connect.BrevCloudAgentServiceHandler) BrevCloudAgentClient { - t.Helper() - - path, h := brevapiv2connect.NewBrevCloudAgentServiceHandler(handler) - mux := http.NewServeMux() - mux.Handle(path, h) - - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client, err := New(config.Config{ - BrevCloudAgentURL: server.URL, - }) - require.NoError(t, err) - return client -} diff --git a/pkg/brevdaemon/agent/health/reporter.go b/pkg/brevdaemon/agent/health/reporter.go index 82621e4f..93d7317f 100644 --- a/pkg/brevdaemon/agent/health/reporter.go +++ b/pkg/brevdaemon/agent/health/reporter.go @@ -4,12 +4,12 @@ import ( "sync" "time" - "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/client" + 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 client.NodePhase + Phase brevapiv2.BrevCloudNodePhase Detail string LastTransitionTime time.Time } @@ -92,7 +92,7 @@ func (r *Reporter) Publish(next Status) Status { // MarkActive marks the subsystem as healthy. func (r *Reporter) MarkActive(detail string) Status { return r.Publish(Status{ - Phase: client.NodePhaseActive, + Phase: brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ACTIVE, Detail: detail, }) } @@ -100,7 +100,7 @@ func (r *Reporter) MarkActive(detail string) Status { // MarkError marks the subsystem as unhealthy with additional detail. func (r *Reporter) MarkError(detail string) Status { return r.Publish(Status{ - Phase: client.NodePhaseError, + Phase: brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ERROR, Detail: detail, }) } diff --git a/pkg/brevdaemon/agent/health/reporter_test.go b/pkg/brevdaemon/agent/health/reporter_test.go index 49ad4677..4ca5e51b 100644 --- a/pkg/brevdaemon/agent/health/reporter_test.go +++ b/pkg/brevdaemon/agent/health/reporter_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/client" + brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" ) func TestNewReporterSeedsTimestamp(t *testing.T) { @@ -39,8 +39,8 @@ func TestReporterTransitionsEmitUpdates(t *testing.T) { reporter.MarkError("missing binary") select { case got := <-updates: - if got.Phase != client.NodePhaseError { - t.Fatalf("Phase = %v, want NodePhaseError", got.Phase) + 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") @@ -55,8 +55,8 @@ func TestReporterTransitionsEmitUpdates(t *testing.T) { reporter.MarkActive("recovered") select { case got := <-updates: - if got.Phase != client.NodePhaseActive { - t.Fatalf("Phase = %v, want NodePhaseActive", got.Phase) + 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") @@ -71,7 +71,7 @@ func TestReporterTransitionsEmitUpdates(t *testing.T) { func TestReporterPublishNoChangeDoesNotEmit(t *testing.T) { initial := Status{ - Phase: client.NodePhaseActive, + Phase: brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ACTIVE, Detail: "ok", LastTransitionTime: time.Unix(42, 0), } diff --git a/pkg/brevdaemon/agent/heartbeat/heartbeat.go b/pkg/brevdaemon/agent/heartbeat/heartbeat.go index 387263ca..9ffcf386 100644 --- a/pkg/brevdaemon/agent/heartbeat/heartbeat.go +++ b/pkg/brevdaemon/agent/heartbeat/heartbeat.go @@ -4,11 +4,15 @@ 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/dev-plane/pkg/errors" "go.uber.org/zap" + "google.golang.org/protobuf/types/known/timestamppb" ) const ( @@ -24,7 +28,7 @@ type HeartbeatConfig struct { // Runner drives the periodic heartbeat loop. type Runner struct { - Client client.BrevCloudAgentClient + Client brevapiv2connect.BrevCloudAgentServiceClient Identity identity.Identity Cfg HeartbeatConfig Log *zap.Logger @@ -33,8 +37,8 @@ type Runner struct { Sleep func(context.Context, time.Duration) error Now func() time.Time - DefaultStatus *client.HeartbeatStatus - StatusUpdates <-chan client.HeartbeatStatus + DefaultStatus *brevapiv2.BrevCloudNodeStatus + StatusUpdates <-chan *brevapiv2.BrevCloudNodeStatus } // Run executes the heartbeat loop until the context is canceled or an @@ -59,11 +63,11 @@ func (r *Runner) Run(ctx context.Context) error { //nolint:funlen // loop coordi baseStatus := r.DefaultStatus if baseStatus == nil { - baseStatus = &client.HeartbeatStatus{ - Phase: client.NodePhaseActive, + baseStatus = &brevapiv2.BrevCloudNodeStatus{ + Phase: brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ACTIVE, } } - currentStatus := cloneStatus(*baseStatus) + currentStatus := cloneStatus(baseStatus) base := r.Cfg.BaseInterval if base <= 0 { @@ -98,28 +102,33 @@ func (r *Runner) Run(ctx context.Context) error { //nolint:funlen // loop coordi // continue // } - params := client.HeartbeatParams{ - BrevCloudNodeID: r.Identity.InstanceID, - DeviceToken: r.Identity.DeviceToken, - ObservedAt: nowFn(), - // Utilization: util.ToClient(), - AgentVersion: "", // set by orchestrator later - Status: currentStatusPtr(currentStatus), + 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 - params.Status = currentStatusPtr(currentStatus) + req.Status = cloneStatus(currentStatus) } - res, err := r.Client.Heartbeat(ctx, params) + connectReq := connect.NewRequest(req) + connectReq.Header().Set("Authorization", client.BearerToken(r.Identity.DeviceToken)) + + resp, err := r.Client.Heartbeat(ctx, connectReq) if err != nil { - if errors.Is(err, client.ErrUnauthenticated) { - return errors.WrapAndTrace(err) + if classified := client.ClassifyError(err); classified != nil { + if errors.Is(classified, client.ErrUnauthenticated) { + return classified + } + r.Log.Warn("heartbeat failed", zap.Error(classified)) + err = classified + } else { + r.Log.Warn("heartbeat failed", zap.Error(err)) } - r.Log.Warn("heartbeat failed", zap.Error(err)) currentBackoff = minDurationValue(currentBackoff*2, maxInterval) nextDelay = currentBackoff continue @@ -127,8 +136,8 @@ func (r *Runner) Run(ctx context.Context) error { //nolint:funlen // loop coordi currentBackoff = base nextDelay = base - if res.NextHeartbeatInterval > 0 { - nextDelay = clampDuration(res.NextHeartbeatInterval, base, maxInterval) + if interval := resp.Msg.GetNextHeartbeatInterval(); interval != nil { + nextDelay = clampDuration(interval.AsDuration(), base, maxInterval) } } } @@ -149,12 +158,12 @@ func (r *Runner) validate() error { return nil } -func (r *Runner) nextStatus() (client.HeartbeatStatus, bool) { +func (r *Runner) nextStatus() (*brevapiv2.BrevCloudNodeStatus, bool) { if r.StatusUpdates == nil { - return client.HeartbeatStatus{}, false + return nil, false } - var updated client.HeartbeatStatus + var updated *brevapiv2.BrevCloudNodeStatus changed := false for { select { @@ -172,25 +181,26 @@ func (r *Runner) nextStatus() (client.HeartbeatStatus, bool) { } } -func normalizeStatus(status client.HeartbeatStatus) client.HeartbeatStatus { - if status.Phase == client.NodePhaseUnspecified { - status.Phase = client.NodePhaseActive +func normalizeStatus(status *brevapiv2.BrevCloudNodeStatus) *brevapiv2.BrevCloudNodeStatus { + if status == nil { + return nil } - return cloneStatus(status) -} - -func cloneStatus(status client.HeartbeatStatus) client.HeartbeatStatus { - out := status - if status.LastTransitionTime != nil { - t := *status.LastTransitionTime - out.LastTransitionTime = &t + 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 currentStatusPtr(status client.HeartbeatStatus) *client.HeartbeatStatus { - s := cloneStatus(status) - return &s +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 { diff --git a/pkg/brevdaemon/agent/heartbeat/heartbeat_test.go b/pkg/brevdaemon/agent/heartbeat/heartbeat_test.go index f17aa016..c384c807 100644 --- a/pkg/brevdaemon/agent/heartbeat/heartbeat_test.go +++ b/pkg/brevdaemon/agent/heartbeat/heartbeat_test.go @@ -2,29 +2,33 @@ package heartbeat import ( "context" - "errors" + 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 client.HeartbeatParams + var captured *connect.Request[brevapiv2.HeartbeatRequest] clientStub := &stubClient{ - heartbeatFn: func(_ context.Context, params client.HeartbeatParams) (client.HeartbeatResult, error) { - captured = params + heartbeatFn: func(_ context.Context, req *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) { + captured = req cancel() - return client.HeartbeatResult{ - NextHeartbeatInterval: 45 * time.Second, - }, nil + return connect.NewResponse(&brevapiv2.HeartbeatResponse{ + NextHeartbeatInterval: durationpb.New(45 * time.Second), + }), nil }, } @@ -45,22 +49,16 @@ func TestRunnerSendsHeartbeatAndUsesServerInterval(t *testing.T) { BaseInterval: 30 * time.Second, MaxInterval: time.Minute, }, - Log: zaptest.NewLogger(t), - SampleUtilization: func(context.Context) (telemetry.UtilizationInfo, error) { - return telemetry.UtilizationInfo{ - CPUPercent: 10, - }, nil - }, + Log: zaptest.NewLogger(t), Sleep: sleepFn, Now: func() time.Time { return time.Unix(0, 0) }, } err := runner.Run(ctx) require.NoError(t, err) - require.Equal(t, "fn-1", captured.BrevCloudNodeID) - require.Equal(t, "token", captured.DeviceToken) - require.NotNil(t, captured.Status) - require.Equal(t, client.NodePhaseActive, captured.Status.Phase) + 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]) } @@ -71,13 +69,13 @@ func TestRunnerBackoffOnFailures(t *testing.T) { callCount := 0 clientStub := &stubClient{ - heartbeatFn: func(_ context.Context, _ client.HeartbeatParams) (client.HeartbeatResult, error) { + heartbeatFn: func(_ context.Context, _ *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) { callCount++ if callCount < 3 { - return client.HeartbeatResult{}, errors.New("temporary failure") + return nil, stderrs.New("temporary failure") } cancel() - return client.HeartbeatResult{}, nil + return connect.NewResponse(&brevapiv2.HeartbeatResponse{}), nil }, } @@ -102,10 +100,7 @@ func TestRunnerBackoffOnFailures(t *testing.T) { BaseInterval: time.Second, MaxInterval: 10 * time.Second, }, - Log: zaptest.NewLogger(t), - SampleUtilization: func(context.Context) (telemetry.UtilizationInfo, error) { - return telemetry.UtilizationInfo{}, nil - }, + Log: zaptest.NewLogger(t), Sleep: sleepFn, Now: time.Now, } @@ -121,19 +116,19 @@ func TestRunnerAppliesStatusUpdates(t *testing.T) { defer cancel() transition := time.Unix(100, 0) - updates := make(chan client.HeartbeatStatus, 2) - updates <- client.HeartbeatStatus{ - Phase: client.NodePhaseError, + updates := make(chan *brevapiv2.BrevCloudNodeStatus, 2) + updates <- &brevapiv2.BrevCloudNodeStatus{ + Phase: brevapiv2.BrevCloudNodePhase_BREV_CLOUD_NODE_PHASE_ERROR, Detail: "tunnel missing", - LastTransitionTime: &transition, + LastTransitionTime: timestamppb.New(transition), } - var captured []client.HeartbeatParams + var captured []*connect.Request[brevapiv2.HeartbeatRequest] clientStub := &stubClient{ - heartbeatFn: func(_ context.Context, params client.HeartbeatParams) (client.HeartbeatResult, error) { - captured = append(captured, params) + heartbeatFn: func(_ context.Context, req *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) { + captured = append(captured, req) cancel() - return client.HeartbeatResult{}, nil + return connect.NewResponse(&brevapiv2.HeartbeatResponse{}), nil }, } @@ -144,9 +139,6 @@ func TestRunnerAppliesStatusUpdates(t *testing.T) { DeviceToken: "token", }, Log: zaptest.NewLogger(t), - SampleUtilization: func(context.Context) (telemetry.UtilizationInfo, error) { - return telemetry.UtilizationInfo{}, nil - }, Sleep: func(ctx context.Context, _ time.Duration) error { select { case <-ctx.Done(): @@ -162,17 +154,17 @@ func TestRunnerAppliesStatusUpdates(t *testing.T) { err := runner.Run(ctx) require.NoError(t, err) require.Len(t, captured, 1) - require.NotNil(t, captured[0].Status) - require.Equal(t, client.NodePhaseError, captured[0].Status.Phase) - require.Equal(t, "tunnel missing", captured[0].Status.Detail) - require.True(t, captured[0].Status.LastTransitionTime.Equal(transition)) + 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, client.HeartbeatParams) (client.HeartbeatResult, error) { - return client.HeartbeatResult{}, client.ErrUnauthenticated + heartbeatFn: func(context.Context, *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) { + return nil, connect.NewError(connect.CodeUnauthenticated, stderrs.New("invalid token")) }, } @@ -190,24 +182,24 @@ func TestRunnerStopsOnUnauthenticated(t *testing.T) { err := runner.Run(ctx) require.Error(t, err) - require.True(t, errors.Is(err, client.ErrUnauthenticated)) + require.True(t, stderrs.Is(err, client.ErrUnauthenticated)) } type stubClient struct { - heartbeatFn func(ctx context.Context, req client.HeartbeatParams) (client.HeartbeatResult, error) + heartbeatFn func(ctx context.Context, req *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) } -func (s *stubClient) Register(_ context.Context, _ client.RegisterParams) (client.RegisterResult, error) { - return client.RegisterResult{}, nil +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 client.HeartbeatParams) (client.HeartbeatResult, error) { +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 client.HeartbeatResult{}, nil + return connect.NewResponse(&brevapiv2.HeartbeatResponse{}), nil } -func (s *stubClient) GetTunnelToken(_ context.Context, _ client.TunnelTokenParams) (client.TunnelTokenResult, error) { - return client.TunnelTokenResult{}, 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 index 31bbbbf3..c1a1c127 100644 --- a/pkg/brevdaemon/agent/identity/identity.go +++ b/pkg/brevdaemon/agent/identity/identity.go @@ -8,6 +8,9 @@ import ( "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" @@ -152,7 +155,7 @@ func writeOptional(path, value string) error { func EnsureIdentity( ctx context.Context, cfg config.Config, - agentClient client.BrevCloudAgentClient, + agentClient brevapiv2connect.BrevCloudAgentServiceClient, store *IdentityStore, hw telemetry.HardwareInfo, log *zap.Logger, @@ -183,24 +186,28 @@ func EnsureIdentity( return Identity{}, errors.WrapAndTrace(err) } - params := client.RegisterParams{ - RegistrationToken: cfg.RegistrationToken, - DisplayName: cfg.DisplayName, - CloudName: cfg.CloudName, - Hardware: hw.ToClient(), + req := &brevapiv2.RegisterRequest{ + RegistrationToken: cfg.RegistrationToken, + Hardware: hw.ToProto(), + } + 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") - res, err := agentClient.Register(ctx, params) + resp, err := agentClient.Register(ctx, connect.NewRequest(req)) if err != nil { - return Identity{}, errors.WrapAndTrace(err) + return Identity{}, errors.WrapAndTrace(client.ClassifyError(err)) } newIdentity := Identity{ - InstanceID: res.BrevCloudNodeID, - DeviceToken: res.DeviceToken, + InstanceID: resp.Msg.GetBrevCloudNodeId(), + DeviceToken: resp.Msg.GetDeviceToken(), DeviceSalt: salt, - DeviceFingerprintStored: res.DeviceFingerprint, + DeviceFingerprintStored: resp.Msg.GetDeviceFingerprint(), } if err := store.Save(newIdentity); err != nil { return Identity{}, errors.WrapAndTrace(err) @@ -265,4 +272,4 @@ func ensureDeviceSalt(path string) (string, error) { return "", errors.WrapAndTrace(err) } return salt, nil -} \ No newline at end of file +} diff --git a/pkg/brevdaemon/agent/identity/identity_test.go b/pkg/brevdaemon/agent/identity/identity_test.go index 1a6e1e8b..3d0857c6 100644 --- a/pkg/brevdaemon/agent/identity/identity_test.go +++ b/pkg/brevdaemon/agent/identity/identity_test.go @@ -6,7 +6,8 @@ import ( "path/filepath" "testing" - "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/client" + 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" @@ -74,9 +75,9 @@ func TestEnsureIdentityUsesStoredToken(t *testing.T) { var registerCalled bool client := &stubClient{ - registerFn: func(context.Context, client.RegisterParams) (client.RegisterResult, error) { + registerFn: func(context.Context, *connect.Request[brevapiv2.RegisterRequest]) (*connect.Response[brevapiv2.RegisterResponse], error) { registerCalled = true - return client.RegisterResult{}, nil + return connect.NewResponse(&brevapiv2.RegisterResponse{}), nil }, } @@ -100,19 +101,15 @@ func TestEnsureIdentityRegistersWhenMissing(t *testing.T) { store := NewIdentityStore(cfg) + var captured *brevapiv2.RegisterRequest client := &stubClient{ - registerFn: func(_ context.Context, params client.RegisterParams) (client.RegisterResult, error) { - require.Equal(t, "reg-token", params.RegistrationToken) - require.Equal(t, "node", params.DisplayName) - require.Equal(t, "cloud", params.CloudName) - require.NotEmpty(t, params.DeviceFingerprintHash) - require.NotEmpty(t, params.HardwareFingerprint) - return client.RegisterResult{ - BrevCloudNodeID: "fn-new", + 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", - CloudCredID: "cc-123", DeviceFingerprint: "scoped-device-fp", - }, nil + }), nil }, } @@ -124,7 +121,12 @@ func TestEnsureIdentityRegistersWhenMissing(t *testing.T) { require.Equal(t, "fn-new", id.InstanceID) require.Equal(t, "new-token", id.DeviceToken) require.Equal(t, "scoped-device-fp", id.DeviceFingerprintStored) - require.NotEmpty(t, id.DeviceFingerprintHash) + + 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) @@ -146,20 +148,20 @@ func TestEnsureIdentityRequiresRegistrationToken(t *testing.T) { } type stubClient struct { - registerFn func(ctx context.Context, params client.RegisterParams) (client.RegisterResult, error) + registerFn func(ctx context.Context, req *connect.Request[brevapiv2.RegisterRequest]) (*connect.Response[brevapiv2.RegisterResponse], error) } -func (s *stubClient) Register(ctx context.Context, params client.RegisterParams) (client.RegisterResult, 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, params) + return s.registerFn(ctx, req) } - return client.RegisterResult{}, nil + return connect.NewResponse(&brevapiv2.RegisterResponse{}), nil } -func (s *stubClient) Heartbeat(context.Context, client.HeartbeatParams) (client.HeartbeatResult, error) { - return client.HeartbeatResult{}, 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, client.TunnelTokenParams) (client.TunnelTokenResult, error) { - return client.TunnelTokenResult{}, 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 index 210e31c0..df37db3f 100644 --- a/pkg/brevdaemon/agent/telemetry/hardware.go +++ b/pkg/brevdaemon/agent/telemetry/hardware.go @@ -9,7 +9,8 @@ import ( "strconv" "strings" - "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/client" + 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/dev-plane/pkg/errors" "golang.org/x/sys/unix" ) @@ -63,33 +64,40 @@ func DetectHardware(ctx context.Context) (HardwareInfo, error) { return hw, nil } -// ToClient converts the telemetry DTO into the client package structure. -func (h HardwareInfo) ToClient() *client.HardwareInfo { - out := &client.HardwareInfo{ - CPUCount: h.CPUCount, - RAMBytes: h.RAMBytes, - Architecture: h.Architecture, +// 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.MachineModel = h.MachineModel + out.SystemModel = protoString(h.MachineModel) + } + if h.Architecture != "" { + out.Architecture = protoString(h.Architecture) } if len(h.Storage) > 0 { - out.Storage = make([]client.StorageInfo, 0, len(h.Storage)) + out.Storage = make([]*brevapiv2.StorageInfo, 0, len(h.Storage)) for _, s := range h.Storage { - out.Storage = append(out.Storage, client.StorageInfo{ - Name: s.Name, - Capacity: s.SizeBytes, - Type: s.Type, - }) + 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([]client.GPUInfo, 0, len(h.GPUs)) + out.Gpus = make([]*brevapiv2.GPUInfo, 0, len(h.GPUs)) for _, gpu := range h.GPUs { - out.GPUs = append(out.GPUs, client.GPUInfo{ + out.Gpus = append(out.Gpus, &brevapiv2.GPUInfo{ Model: gpu.Model, - MemoryBytes: gpu.MemoryBytes, - Count: gpu.Count, + Count: clampToInt32(gpu.Count), + MemoryBytes: bytesValue(gpu.MemoryBytes), }) } } @@ -316,3 +324,28 @@ func runCommand(ctx context.Context, name string, args ...string) ([]byte, error } 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 index 6c0108e0..6875ca96 100644 --- a/pkg/brevdaemon/agent/telemetry/hardware_test.go +++ b/pkg/brevdaemon/agent/telemetry/hardware_test.go @@ -76,7 +76,7 @@ RTX 6000, 24576 require.Equal(t, 1, results["RTX 6000"].Count) } -func TestHardwareToClientConversion(t *testing.T) { +func TestHardwareToProtoConversion(t *testing.T) { info := HardwareInfo{ CPUCount: 8, RAMBytes: 32 << 30, @@ -88,14 +88,14 @@ func TestHardwareToClientConversion(t *testing.T) { {Name: "nvme0n1", SizeBytes: 512 << 30, Type: "nvme"}, }, } - clientInfo := info.ToClient() - require.Equal(t, 8, clientInfo.CPUCount) - require.Equal(t, int64(32<<30), clientInfo.RAMBytes) - require.Equal(t, "arm64", clientInfo.Architecture) - require.Len(t, clientInfo.GPUs, 1) - require.Equal(t, 2, clientInfo.GPUs[0].Count) - require.Len(t, clientInfo.Storage, 1) - require.Equal(t, "nvme0n1", clientInfo.Storage[0].Name) - require.Equal(t, int64(512<<30), clientInfo.Storage[0].Capacity) - require.Equal(t, "nvme", clientInfo.Storage[0].Type) + 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/utilization.go b/pkg/brevdaemon/agent/telemetry/utilization.go index c286414f..1f18964e 100644 --- a/pkg/brevdaemon/agent/telemetry/utilization.go +++ b/pkg/brevdaemon/agent/telemetry/utilization.go @@ -12,7 +12,7 @@ import ( "strings" "time" - "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/client" + brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" "github.com/brevdev/dev-plane/pkg/errors" "golang.org/x/sys/unix" ) @@ -38,27 +38,28 @@ type GPUUtilization struct { TemperatureCelsius *float32 } -// ToClient converts the telemetry DTO into the client payload type. -func (u UtilizationInfo) ToClient() *client.UtilizationInfo { - out := &client.UtilizationInfo{ - CPUPercent: u.CPUPercent, - MemoryUsedBytes: u.MemoryUsedBytes, - MemoryTotalBytes: u.MemoryTotalBytes, - DiskPercent: u.DiskPercent, - DiskUsedBytes: u.DiskUsedBytes, - DiskTotalBytes: u.DiskTotalBytes, +// 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([]client.GPUUtilization, 0, len(u.GPUs)) + out.Gpus = make([]*brevapiv2.GPUUtilization, 0, len(u.GPUs)) for _, gpu := range u.GPUs { - out.GPUs = append(out.GPUs, client.GPUUtilization{ - Index: gpu.Index, - Model: gpu.Model, - UtilizationPercent: gpu.UtilizationPercent, - MemoryUsedBytes: gpu.MemoryUsedBytes, - MemoryTotalBytes: gpu.MemoryTotalBytes, - TemperatureCelsius: gpu.TemperatureCelsius, - }) + out.Gpus = append(out.Gpus, gpuUtilizationToProto(gpu)) } } return out @@ -379,3 +380,21 @@ func safeMulInt64(a, b int64) (int64, error) { } 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 index 953c385f..f12385d7 100644 --- a/pkg/brevdaemon/agent/telemetry/utilization_test.go +++ b/pkg/brevdaemon/agent/telemetry/utilization_test.go @@ -11,7 +11,7 @@ import ( "golang.org/x/sys/unix" ) -func TestUtilizationToClientConversion(t *testing.T) { +func TestUtilizationToProtoConversion(t *testing.T) { temp := float32(65) util := UtilizationInfo{ CPUPercent: 55.5, @@ -32,11 +32,11 @@ func TestUtilizationToClientConversion(t *testing.T) { }, } - clientUtil := util.ToClient() - require.Equal(t, util.CPUPercent, clientUtil.CPUPercent) - require.Equal(t, util.MemoryUsedBytes, clientUtil.MemoryUsedBytes) - require.Len(t, clientUtil.GPUs, 1) - require.Equal(t, temp, *clientUtil.GPUs[0].TemperatureCelsius) + 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) { diff --git a/pkg/brevdaemon/agent/tunnel/ingress.go b/pkg/brevdaemon/agent/tunnel/ingress.go index d87b813a..dac86026 100644 --- a/pkg/brevdaemon/agent/tunnel/ingress.go +++ b/pkg/brevdaemon/agent/tunnel/ingress.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/client" + brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/telemetry" "github.com/brevdev/dev-plane/pkg/brevcloud/appaccess" "github.com/brevdev/dev-plane/pkg/errors" @@ -39,7 +39,7 @@ func detectInstanceTypeFromHardware(hw telemetry.HardwareInfo) appaccess.Instanc return appaccess.InstanceTypeUnknown } -func buildAppIngresses(ctx context.Context, cfg appaccess.Config, tcpProbe probeFunc, httpProbe httpProbeFunc, systemdCheck systemdStatusFunc, timeout time.Duration, instanceType appaccess.InstanceType) []client.AppIngress { +func buildAppIngresses(ctx context.Context, cfg appaccess.Config, tcpProbe probeFunc, httpProbe httpProbeFunc, systemdCheck systemdStatusFunc, timeout time.Duration, instanceType appaccess.InstanceType) []*brevapiv2.AppIngressRequest { if instanceType != appaccess.InstanceTypeDGXSpark { return nil } @@ -58,7 +58,7 @@ func buildAppIngresses(ctx context.Context, cfg appaccess.Config, tcpProbe probe } timeout = clampProbeTimeout(timeout) - ingresses := make([]client.AppIngress, 0, len(apps)) + ingresses := make([]*brevapiv2.AppIngressRequest, 0, len(apps)) for _, spec := range apps { if spec.DefaultPort <= 0 { continue @@ -85,18 +85,24 @@ func buildAppIngresses(ctx context.Context, cfg appaccess.Config, tcpProbe probe return ingresses } -func appIngressFromSpec(spec appaccess.AppSpec) client.AppIngress { +func appIngressFromSpec(spec appaccess.AppSpec) *brevapiv2.AppIngressRequest { pathPrefix := spec.PathPrefix if pathPrefix == "" { pathPrefix = "/" } - return client.AppIngress{ - AppID: string(spec.ID), - Protocol: spec.Protocol, - LocalPort: spec.DefaultPort, + protocol := spec.Protocol + if spec.ForceHTTPS { + protocol = "https" + } else if protocol == "" { + protocol = "http" + } + return &brevapiv2.AppIngressRequest{ + AppId: string(spec.ID), + Protocol: protocol, + LocalPort: int32(spec.DefaultPort), // defaultPort validated above (>0) HostnamePrefix: string(spec.ID), PathPrefix: pathPrefix, - ForceHTTPS: spec.ForceHTTPS, + ForceHttps: spec.ForceHTTPS, } } diff --git a/pkg/brevdaemon/agent/tunnel/ingress_test.go b/pkg/brevdaemon/agent/tunnel/ingress_test.go index 966dc150..d4623bc6 100644 --- a/pkg/brevdaemon/agent/tunnel/ingress_test.go +++ b/pkg/brevdaemon/agent/tunnel/ingress_test.go @@ -5,7 +5,8 @@ import ( "testing" "time" - "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/client" + brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" + "connectrpc.com/connect" "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/identity" "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/telemetry" "github.com/brevdev/dev-plane/pkg/brevcloud/appaccess" @@ -120,15 +121,15 @@ func failingHTTPProbe(context.Context, string, int, string, time.Duration) error return errors.New("probe failed") } -func requireAppIngress(t *testing.T, ingresses []client.AppIngress, appID string, port int) { +func requireAppIngress(t *testing.T, ingresses []*brevapiv2.AppIngressRequest, appID string, port int) { t.Helper() for _, ingress := range ingresses { - if ingress.AppID == appID { - require.Equal(t, port, ingress.LocalPort) - require.Equal(t, "https", ingress.Protocol) - require.Equal(t, "/", ingress.PathPrefix) - require.Equal(t, appID, ingress.HostnamePrefix) - require.True(t, ingress.ForceHTTPS) + if ingress.GetAppId() == appID { + require.Equal(t, int32(port), ingress.GetLocalPort()) + require.Equal(t, "https", ingress.GetProtocol()) + require.Equal(t, "/", ingress.GetPathPrefix()) + require.Equal(t, appID, ingress.GetHostnamePrefix()) + require.True(t, ingress.GetForceHttps()) return } } @@ -140,7 +141,7 @@ func TestManagerRequestsSSHWithoutAppIngressesForNonSpark(t *testing.T) { ctx := context.Background() clientStub := &stubAgentClient{ - resp: client.TunnelTokenResult{Token: "token"}, + resp: &brevapiv2.GetTunnelTokenResponse{Token: "token"}, } manager := Manager{ Client: clientStub, @@ -160,9 +161,10 @@ func TestManagerRequestsSSHWithoutAppIngressesForNonSpark(t *testing.T) { } require.NoError(t, manager.Start(ctx)) - require.Len(t, clientStub.lastReq.Ports, 1) - require.Equal(t, 22, clientStub.lastReq.Ports[0].LocalPort) - require.Empty(t, clientStub.lastReq.AppIngresses) + require.NotNil(t, clientStub.lastReq) + require.Len(t, clientStub.lastReq.Msg.GetRequestedPorts(), 1) + require.Equal(t, int32(22), clientStub.lastReq.Msg.GetRequestedPorts()[0].GetLocalPort()) + require.Empty(t, clientStub.lastReq.Msg.GetAppIngresses()) } func TestManagerIncludesAppIngressesForSparkWhenProbesPass(t *testing.T) { @@ -170,7 +172,7 @@ func TestManagerIncludesAppIngressesForSparkWhenProbesPass(t *testing.T) { ctx := context.Background() clientStub := &stubAgentClient{ - resp: client.TunnelTokenResult{Token: "token"}, + resp: &brevapiv2.GetTunnelTokenResponse{Token: "token"}, } manager := Manager{ Client: clientStub, @@ -190,20 +192,21 @@ func TestManagerIncludesAppIngressesForSparkWhenProbesPass(t *testing.T) { } require.NoError(t, manager.Start(ctx)) - require.Len(t, clientStub.lastReq.Ports, 1) - require.Len(t, clientStub.lastReq.AppIngresses, 2) - requireAppIngressParams(t, clientStub.lastReq.AppIngresses, string(appaccess.AppIDDGXDashboard), 11000) - requireAppIngressParams(t, clientStub.lastReq.AppIngresses, string(appaccess.AppIDJupyter), 8888) + require.NotNil(t, clientStub.lastReq) + require.Len(t, clientStub.lastReq.Msg.GetRequestedPorts(), 1) + require.Len(t, clientStub.lastReq.Msg.GetAppIngresses(), 2) + requireAppIngressParams(t, clientStub.lastReq.Msg.GetAppIngresses(), string(appaccess.AppIDDGXDashboard), 11000) + requireAppIngressParams(t, clientStub.lastReq.Msg.GetAppIngresses(), string(appaccess.AppIDJupyter), 8888) } -func requireAppIngressParams(t *testing.T, ingresses []client.AppIngress, appID string, port int) { +func requireAppIngressParams(t *testing.T, ingresses []*brevapiv2.AppIngressRequest, appID string, port int) { t.Helper() for _, ingress := range ingresses { - if ingress.AppID == appID { - require.Equal(t, port, ingress.LocalPort) - require.Equal(t, appID, ingress.HostnamePrefix) - require.Equal(t, "https", ingress.Protocol) - require.True(t, ingress.ForceHTTPS) + if ingress.GetAppId() == appID { + require.Equal(t, int32(port), ingress.GetLocalPort()) + require.Equal(t, appID, ingress.GetHostnamePrefix()) + require.Equal(t, "https", ingress.GetProtocol()) + require.True(t, ingress.GetForceHttps()) return } } @@ -211,21 +214,24 @@ func requireAppIngressParams(t *testing.T, ingresses []client.AppIngress, appID } type stubAgentClient struct { - lastReq client.TunnelTokenParams - resp client.TunnelTokenResult + lastReq *connect.Request[brevapiv2.GetTunnelTokenRequest] + resp *brevapiv2.GetTunnelTokenResponse } -func (s *stubAgentClient) Register(context.Context, client.RegisterParams) (client.RegisterResult, error) { - return client.RegisterResult{}, nil +func (s *stubAgentClient) Register(context.Context, *connect.Request[brevapiv2.RegisterRequest]) (*connect.Response[brevapiv2.RegisterResponse], error) { + return connect.NewResponse(&brevapiv2.RegisterResponse{}), nil } -func (s *stubAgentClient) Heartbeat(context.Context, client.HeartbeatParams) (client.HeartbeatResult, error) { - return client.HeartbeatResult{}, nil +func (s *stubAgentClient) Heartbeat(context.Context, *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) { + return connect.NewResponse(&brevapiv2.HeartbeatResponse{}), nil } -func (s *stubAgentClient) GetTunnelToken(_ context.Context, req client.TunnelTokenParams) (client.TunnelTokenResult, error) { +func (s *stubAgentClient) GetTunnelToken(_ context.Context, req *connect.Request[brevapiv2.GetTunnelTokenRequest]) (*connect.Response[brevapiv2.GetTunnelTokenResponse], error) { s.lastReq = req - return s.resp, nil + if s.resp == nil { + s.resp = &brevapiv2.GetTunnelTokenResponse{} + } + return connect.NewResponse(s.resp), nil } type stubCommand struct{} diff --git a/pkg/brevdaemon/agent/tunnel/tunnel.go b/pkg/brevdaemon/agent/tunnel/tunnel.go index 93bfaa9d..661ee4c4 100644 --- a/pkg/brevdaemon/agent/tunnel/tunnel.go +++ b/pkg/brevdaemon/agent/tunnel/tunnel.go @@ -4,11 +4,15 @@ 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" @@ -34,7 +38,7 @@ type TunnelConfig struct { // Manager fetches tunnel tokens and boots the tunnel client. type Manager struct { - Client client.BrevCloudAgentClient + Client brevapiv2connect.BrevCloudAgentServiceClient Identity identity.Identity Cfg TunnelConfig Log *zap.Logger @@ -97,15 +101,14 @@ func (m *Manager) Start(ctx context.Context) error { //nolint:gocognit,gocyclo,f } instanceType := detectInstanceTypeFromHardware(hw) - req := client.TunnelTokenParams{ - BrevCloudNodeID: m.Identity.InstanceID, - DeviceToken: m.Identity.DeviceToken, - TunnelName: defaultTunnelName, - Ports: []tunnel.TunnelPortMapping{ + req := &brevapiv2.GetTunnelTokenRequest{ + BrevCloudNodeId: m.Identity.InstanceID, + RequestedPorts: tunnelPortsToProto([]tunnel.TunnelPortMapping{ { LocalPort: m.Cfg.SSHPort, }, - }, + }), + TunnelName: client.ProtoString(defaultTunnelName), } retryDelay := minRetryDelay @@ -197,12 +200,15 @@ func (w *execCmdWrapper) CombinedOutput() ([]byte, error) { return out, nil } -func (m *Manager) requestTunnelToken(ctx context.Context, req client.TunnelTokenParams) (client.TunnelTokenResult, error) { - res, err := m.Client.GetTunnelToken(ctx, req) +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 client.TunnelTokenResult{}, errors.WrapAndTrace(err) + return nil, errors.WrapAndTrace(client.ClassifyError(err)) } - return res, nil + return resp.Msg, nil } func (m *Manager) delay(ctx context.Context, sleepFn func(context.Context, time.Duration) error, delay time.Duration) error { @@ -232,14 +238,18 @@ func (m *Manager) markActive(detail string) { } } -func (m *Manager) configureCloudflaredService(ctx context.Context, token client.TunnelTokenResult) error { +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.Token}, + args: []string{"cloudflared", "service", "install", token.GetToken()}, + }, + { + name: "sudo", + args: []string{"systemctl", "restart", "cloudflared.service"}, }, } @@ -297,6 +307,38 @@ func addJitter(delay time.Duration) time.Duration { return time.Duration(float64(delay) * factor) } +func tunnelPortsToProto(ports []tunnel.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: int32(lp), + RemotePort: int32(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 diff --git a/pkg/brevdaemon/agent/tunnel/tunnel_test.go b/pkg/brevdaemon/agent/tunnel/tunnel_test.go index 8e83b63c..f3d4cb3e 100644 --- a/pkg/brevdaemon/agent/tunnel/tunnel_test.go +++ b/pkg/brevdaemon/agent/tunnel/tunnel_test.go @@ -6,7 +6,8 @@ import ( "testing" "time" - "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/client" + 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" @@ -19,13 +20,12 @@ func TestManagerConfiguresCloudflaredService(t *testing.T) { requested := false stubClient := &stubClient{ - getTunnelFn: func(context.Context, client.TunnelTokenParams) (client.TunnelTokenResult, error) { + getTunnelFn: func(context.Context, *connect.Request[brevapiv2.GetTunnelTokenRequest]) (*connect.Response[brevapiv2.GetTunnelTokenResponse], error) { requested = true - return client.TunnelTokenResult{ + return connect.NewResponse(&brevapiv2.GetTunnelTokenResponse{ Token: "token", Endpoint: "endpoint", - TTL: time.Minute, - }, nil + }), nil }, } @@ -78,11 +78,11 @@ func TestManagerRetriesOnConfigureError(t *testing.T) { reporter := &fakeReporter{} stubClient := &stubClient{ - getTunnelFn: func(context.Context, client.TunnelTokenParams) (client.TunnelTokenResult, error) { - return client.TunnelTokenResult{ + getTunnelFn: func(context.Context, *connect.Request[brevapiv2.GetTunnelTokenRequest]) (*connect.Response[brevapiv2.GetTunnelTokenResponse], error) { + return connect.NewResponse(&brevapiv2.GetTunnelTokenResponse{ Token: "token", Endpoint: "endpoint", - }, nil + }), nil }, } @@ -175,20 +175,20 @@ func (f *fakeReporter) MarkError(detail string) health.Status { } type stubClient struct { - getTunnelFn func(context.Context, client.TunnelTokenParams) (client.TunnelTokenResult, error) + getTunnelFn func(context.Context, *connect.Request[brevapiv2.GetTunnelTokenRequest]) (*connect.Response[brevapiv2.GetTunnelTokenResponse], error) } -func (s *stubClient) Register(context.Context, client.RegisterParams) (client.RegisterResult, error) { - return client.RegisterResult{}, nil +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, client.HeartbeatParams) (client.HeartbeatResult, error) { - return client.HeartbeatResult{}, 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, params client.TunnelTokenParams) (client.TunnelTokenResult, error) { +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, params) + return s.getTunnelFn(ctx, req) } - return client.TunnelTokenResult{}, nil + return connect.NewResponse(&brevapiv2.GetTunnelTokenResponse{}), nil } From 97b5c8a5bfa6b4ec160d36266e146de1fee4d527 Mon Sep 17 00:00:00 2001 From: Pratik Patel Date: Fri, 9 Jan 2026 12:26:36 -0800 Subject: [PATCH 16/22] remove cloudlfared restart --- pkg/brevdaemon/agent/tunnel/tunnel.go | 4 ---- pkg/brevdaemon/agent/tunnel/tunnel_test.go | 4 +--- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/pkg/brevdaemon/agent/tunnel/tunnel.go b/pkg/brevdaemon/agent/tunnel/tunnel.go index 661ee4c4..0ab5528b 100644 --- a/pkg/brevdaemon/agent/tunnel/tunnel.go +++ b/pkg/brevdaemon/agent/tunnel/tunnel.go @@ -247,10 +247,6 @@ func (m *Manager) configureCloudflaredService(ctx context.Context, token *brevap name: "sudo", args: []string{"cloudflared", "service", "install", token.GetToken()}, }, - { - name: "sudo", - args: []string{"systemctl", "restart", "cloudflared.service"}, - }, } for _, cmdSpec := range commands { diff --git a/pkg/brevdaemon/agent/tunnel/tunnel_test.go b/pkg/brevdaemon/agent/tunnel/tunnel_test.go index f3d4cb3e..3d999f77 100644 --- a/pkg/brevdaemon/agent/tunnel/tunnel_test.go +++ b/pkg/brevdaemon/agent/tunnel/tunnel_test.go @@ -61,11 +61,9 @@ func TestManagerConfiguresCloudflaredService(t *testing.T) { err := manager.Start(ctx) require.NoError(t, err) require.True(t, requested) - require.Len(t, executed, 2) + require.Len(t, executed, 1) require.Equal(t, "sudo", executed[0].name) require.Equal(t, []string{"cloudflared", "service", "install", "token"}, executed[0].args) - require.Equal(t, "sudo", executed[1].name) - require.Equal(t, []string{"systemctl", "restart", "cloudflared.service"}, executed[1].args) require.NotEmpty(t, reporter.actives) require.Contains(t, reporter.actives[0], "cloudflared service configured") } From 2237a8a640ce7e43f57d0ceea6b7bc71ff10320c Mon Sep 17 00:00:00 2001 From: Pratik Patel Date: Fri, 9 Jan 2026 12:54:51 -0800 Subject: [PATCH 17/22] goprivate --- .github/workflows/legacy.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) 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' From 9246b0e7961451d1c5158f0557c166914a49c91d Mon Sep 17 00:00:00 2001 From: Pratik Patel Date: Fri, 9 Jan 2026 13:13:13 -0800 Subject: [PATCH 18/22] lint --- pkg/brevdaemon/agent/heartbeat/heartbeat.go | 2 +- pkg/spark/remote.go | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/brevdaemon/agent/heartbeat/heartbeat.go b/pkg/brevdaemon/agent/heartbeat/heartbeat.go index 9ffcf386..e4b18509 100644 --- a/pkg/brevdaemon/agent/heartbeat/heartbeat.go +++ b/pkg/brevdaemon/agent/heartbeat/heartbeat.go @@ -122,7 +122,7 @@ func (r *Runner) Run(ctx context.Context) error { //nolint:funlen // loop coordi if err != nil { if classified := client.ClassifyError(err); classified != nil { if errors.Is(classified, client.ErrUnauthenticated) { - return classified + return classified //nolint:wrapcheck // classification intentionally returned verbatim for caller handling } r.Log.Warn("heartbeat failed", zap.Error(classified)) err = classified diff --git a/pkg/spark/remote.go b/pkg/spark/remote.go index 412e4a00..c7921f9b 100644 --- a/pkg/spark/remote.go +++ b/pkg/spark/remote.go @@ -11,6 +11,7 @@ import ( "time" "github.com/spf13/afero" + "github.com/brevdev/brev-cli/pkg/errors" ) // RemoteRunner executes commands on a Spark host over ssh and returns stdout/stderr. @@ -25,7 +26,7 @@ func NewRemoteRunner(fs afero.Fs) RemoteRunner { // 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 "", fmt.Errorf("missing identity file for %s", host.Alias) + return "", errors.WrapAndTrace(fmt.Errorf("missing identity file for %s", host.Alias)) } exists, err := afero.Exists(r.fs, host.IdentityFile) @@ -33,7 +34,7 @@ func (r RemoteRunner) Run(ctx context.Context, host Host, remoteCmd string) (str return "", err } if !exists { - return "", fmt.Errorf("identity file not found at %s", host.IdentityFile) + return "", errors.WrapAndTrace(fmt.Errorf("identity file not found at %s", host.IdentityFile)) } argv := buildSSHCommand(host, remoteCmd) @@ -44,7 +45,7 @@ func (r RemoteRunner) Run(ctx context.Context, host Host, remoteCmd string) (str cmd.Stderr = &buf if err := cmd.Run(); err != nil { - return buf.String(), fmt.Errorf("ssh to %s failed for command %q: %w\noutput:\n%s", hostLabel(host), remoteCmd, err, buf.String()) + 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 } From 4cb2941b4e44d95bcf29e04fd5ef1060245a48af Mon Sep 17 00:00:00 2001 From: Drew Malin Date: Fri, 9 Jan 2026 13:13:51 -0800 Subject: [PATCH 19/22] register --- Makefile | 6 + byon.env | 4 + pkg/cmd/cmd.go | 56 ++- pkg/cmd/register/agent.go | 45 ++ pkg/cmd/register/install-binary.sh | 35 ++ pkg/cmd/register/install-service.sh | 43 ++ pkg/cmd/register/install-user.sh | 93 ++++ pkg/cmd/register/register.go | 663 ++++++++++++++++++++++++++-- pkg/cmd/register/spark.go | 32 ++ pkg/cmd/register/uninstall-user.sh | 66 +++ scripts/.gitignore | 1 + scripts/ec2/ec2.go | 405 +++++++++++++++++ scripts/ec2/go.mod | 24 + scripts/ec2/go.sum | 30 ++ 14 files changed, 1474 insertions(+), 29 deletions(-) create mode 100644 byon.env create mode 100644 pkg/cmd/register/agent.go create mode 100644 pkg/cmd/register/install-binary.sh create mode 100644 pkg/cmd/register/install-service.sh create mode 100644 pkg/cmd/register/install-user.sh create mode 100644 pkg/cmd/register/spark.go create mode 100644 pkg/cmd/register/uninstall-user.sh create mode 100644 scripts/.gitignore create mode 100644 scripts/ec2/ec2.go create mode 100644 scripts/ec2/go.mod create mode 100644 scripts/ec2/go.sum diff --git a/Makefile b/Makefile index 5964064c..717d3247 100644 --- a/Makefile +++ b/Makefile @@ -180,6 +180,12 @@ 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: build-linux +build-linux: + $(call print-target) + echo ${VERSION} + GOOS=linux GOARCH=amd64 go build -o brev_linux_amd64 -ldflags "-X github.com/brevdev/brev-cli/pkg/cmd/version.Version=${VERSION}" + GOOS=linux GOARCH=arm64 go build -o brev_linux_arm64 -ldflags "-X github.com/brevdev/brev-cli/pkg/cmd/version.Version=${VERSION}" .PHONY: setup-workspace-repo setup-workspace-repo: build-linux-amd 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/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index 1026fb5a..e16a24ed 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,8 @@ 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.NewCmdUnregister(t, loginCmdStore)) cmd.AddCommand(runtasks.NewCmdRunTasks(t, noLoginCmdStore)) cmd.AddCommand(proxy.NewCmdProxy(t, noLoginCmdStore)) cmd.AddCommand(healthcheck.NewCmdHealthcheck(t, noLoginCmdStore)) @@ -296,7 +301,6 @@ func createCmdTree(cmd *cobra.Command, t *terminal.Terminal, loginCmdStore *stor cmd.AddCommand(recreate.NewCmdRecreate(t, loginCmdStore)) cmd.AddCommand(writeconnectionevent.NewCmdwriteConnectionEvent(t, loginCmdStore)) cmd.AddCommand(updatemodel.NewCmdupdatemodel(t, loginCmdStore)) - // cmd.AddCommand(spark.NewCmdSpark(t, loginCmdStore, noLoginCmdStore)) } func hasWorkspaceCommands(cmd *cobra.Command) bool { @@ -327,6 +331,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() { @@ -397,6 +405,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 @@ -432,6 +462,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}} @@ -490,8 +530,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/register/agent.go b/pkg/cmd/register/agent.go new file mode 100644 index 00000000..3340caa6 --- /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 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 { + 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..7838a333 --- /dev/null +++ b/pkg/cmd/register/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 + # 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 17a275ac..9dadb5d7 100644 --- a/pkg/cmd/register/register.go +++ b/pkg/cmd/register/register.go @@ -1,47 +1,658 @@ -// 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 install-user.sh +var installUserScript string - registerExample = ` brev register` +//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{"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: "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{"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 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") +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 { + if err := runEmbeddedScriptLocally("brevcloud-install-user", installUserScript); err != nil { + return fmt.Errorf("failed to install 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 runUnregisterLocal(ctx context.Context, t *terminal.Terminal, loginStore *store.AuthHTTPStore, args []string) error { + if err := runEmbeddedScriptLocally("brevcloud-uninstall-user", uninstallUserScript); err != nil { + return fmt.Errorf("failed to uninstall brevcloud user: %w", err) + } + + 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 +} + +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 } -// TODO -// this should use spark/enroll.go +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-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/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= From 9fdb50e2dbd4ae993604fedf289566567d06cd89 Mon Sep 17 00:00:00 2001 From: Drew Malin Date: Fri, 9 Jan 2026 13:18:52 -0800 Subject: [PATCH 20/22] revert makefile --- Makefile | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Makefile b/Makefile index 717d3247..7e04490d 100644 --- a/Makefile +++ b/Makefile @@ -180,13 +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: build-linux -build-linux: - $(call print-target) - echo ${VERSION} - GOOS=linux GOARCH=amd64 go build -o brev_linux_amd64 -ldflags "-X github.com/brevdev/brev-cli/pkg/cmd/version.Version=${VERSION}" - GOOS=linux GOARCH=arm64 go build -o brev_linux_arm64 -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 From 1e63e8f38dbe46f4d7af319e24692847c8650251 Mon Sep 17 00:00:00 2001 From: Drew Malin Date: Fri, 9 Jan 2026 13:55:32 -0800 Subject: [PATCH 21/22] install and uninstall service --- pkg/cmd/cmd.go | 1 + pkg/cmd/register/agent.go | 8 +-- pkg/cmd/register/install-service.sh | 78 ++++++++++++++++++++++----- pkg/cmd/register/register.go | 22 ++++++-- pkg/cmd/register/uninstall-service.sh | 68 +++++++++++++++++++++++ 5 files changed, 156 insertions(+), 21 deletions(-) create mode 100644 pkg/cmd/register/uninstall-service.sh diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index e16a24ed..61c4a26b 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -292,6 +292,7 @@ func createCmdTree(cmd *cobra.Command, t *terminal.Terminal, loginCmdStore *stor cmd.AddCommand(profile.NewCmdProfile(t, loginCmdStore, noLoginCmdStore)) cmd.AddCommand(refresh.NewCmdRefresh(t, loginCmdStore)) 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)) diff --git a/pkg/cmd/register/agent.go b/pkg/cmd/register/agent.go index 3340caa6..7500e3c4 100644 --- a/pkg/cmd/register/agent.go +++ b/pkg/cmd/register/agent.go @@ -10,23 +10,23 @@ import ( ) const ( - agentShort = "Run the Brev agent daemon on the Spark node" + agentShort = "Run the Brev agent daemon" agentLong = "Runs the Brev agent daemon in a continuous loop, reporting status and handling workloads." ) -func NewCmdSparkAgent(t *terminal.Terminal) *cobra.Command { +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 runSparkAgent(t) + return runBrevAgent(t) }, } return cmd } -func runSparkAgent(t *terminal.Terminal) error { +func runBrevAgent(t *terminal.Terminal) error { t.Vprint(t.Green("Starting Brev agent daemon...\n")) ticker := time.NewTicker(1 * time.Minute) diff --git a/pkg/cmd/register/install-service.sh b/pkg/cmd/register/install-service.sh index 7838a333..6e98f867 100644 --- a/pkg/cmd/register/install-service.sh +++ b/pkg/cmd/register/install-service.sh @@ -1,19 +1,26 @@ #!/usr/bin/env bash -set -eo pipefail +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..." -# Create systemd service file -sudo tee /etc/systemd/system/brevd.service > /dev/null <<'EOF' +# Define the desired service file content +read -r -d '' SERVICE_CONTENT <<'EOF' || true [Unit] -Description=Brev Daemon +Description=Brev Agent After=network-online.target Wants=network-online.target [Service] Type=simple -EnvironmentFile=-/etc/default/brevd -ExecStart=/usr/local/bin/brevd spark agent +EnvironmentFile=-/etc/default/brev-agent +ExecStart=/usr/local/bin/brev agent Restart=on-failure RestartSec=10s User=brevcloud @@ -23,10 +30,30 @@ Group=brevcloud 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 [ ! -f /etc/default/brevd ]; then - sudo tee /etc/default/brevd > /dev/null < /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 "Successfully installed brevd systemd service" \ No newline at end of file +echo "Brev-agent systemd service is ready and running" \ No newline at end of file diff --git a/pkg/cmd/register/register.go b/pkg/cmd/register/register.go index 9dadb5d7..859c9df7 100644 --- a/pkg/cmd/register/register.go +++ b/pkg/cmd/register/register.go @@ -29,6 +29,9 @@ var installBinaryScript string //go:embed install-service.sh var installServiceScript string +//go:embed uninstall-service.sh +var uninstallServiceScript string + //go:embed install-user.sh var installUserScript string @@ -135,23 +138,34 @@ func NewCmdUnregisterRemoteHost(t *terminal.Terminal, loginCmdStore *store.AuthH } 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) } - return nil -} + // 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) + } -func runRegisterRemote(ctx context.Context, t *terminal.Terminal, loginStore *store.AuthHTTPStore, args []string) error { - fmt.Println("register remote") 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 } 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}" From 5b9bde4d5e424d7e15b614c6ddb834068cb756ca Mon Sep 17 00:00:00 2001 From: Pratik Patel Date: Fri, 9 Jan 2026 17:10:24 -0800 Subject: [PATCH 22/22] simplify appingress, do this in dev-plane --- go.sum | 2 - pkg/brevdaemon/agent/config/config.go | 8 +- pkg/brevdaemon/agent/heartbeat/heartbeat.go | 2 +- pkg/brevdaemon/agent/identity/identity.go | 15 +- pkg/brevdaemon/agent/telemetry/hardware.go | 2 +- .../agent/telemetry/hardware_test.go | 2 +- pkg/brevdaemon/agent/telemetry/utilization.go | 2 +- pkg/brevdaemon/agent/tunnel/ingress.go | 202 --------------- pkg/brevdaemon/agent/tunnel/ingress_test.go | 242 ------------------ pkg/brevdaemon/agent/tunnel/tunnel.go | 69 +---- pkg/spark/remote.go | 2 +- 11 files changed, 27 insertions(+), 521 deletions(-) delete mode 100644 pkg/brevdaemon/agent/tunnel/ingress.go delete mode 100644 pkg/brevdaemon/agent/tunnel/ingress_test.go diff --git a/go.sum b/go.sum index d611a86f..a64b590b 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -braces.dev/errtrace v0.3.0 h1:pzfd6LcWgfWtXLaNFWRnxV/7NP+FSOlIjRLwDuHfPxs= -braces.dev/errtrace v0.3.0/go.mod h1:YQpXdo+u5iimgQdZzFoic8AjedEDncXGpp6/2SfazzI= 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= diff --git a/pkg/brevdaemon/agent/config/config.go b/pkg/brevdaemon/agent/config/config.go index 7e04a4bf..cbfd751d 100644 --- a/pkg/brevdaemon/agent/config/config.go +++ b/pkg/brevdaemon/agent/config/config.go @@ -7,7 +7,7 @@ import ( "strings" "time" - "github.com/brevdev/dev-plane/pkg/errors" + "github.com/brevdev/brev-cli/pkg/errors" ) const ( @@ -45,7 +45,7 @@ type Config struct { HeartbeatInterval time.Duration EnableTunnel bool - TunnelSSHPort int + TunnelSSHPort int32 TunnelCritical bool } @@ -98,14 +98,14 @@ func Load() (Config, error) { } if portRaw := strings.TrimSpace(os.Getenv(EnvTunnelSSHPort)); portRaw != "" { - port, err := strconv.Atoi(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 = port + cfg.TunnelSSHPort = int32(port) } if tunnelCriticalRaw := strings.TrimSpace(os.Getenv(EnvTunnelCritical)); tunnelCriticalRaw != "" { diff --git a/pkg/brevdaemon/agent/heartbeat/heartbeat.go b/pkg/brevdaemon/agent/heartbeat/heartbeat.go index e4b18509..2f5fa594 100644 --- a/pkg/brevdaemon/agent/heartbeat/heartbeat.go +++ b/pkg/brevdaemon/agent/heartbeat/heartbeat.go @@ -10,7 +10,7 @@ import ( "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/dev-plane/pkg/errors" + "github.com/brevdev/brev-cli/pkg/errors" "go.uber.org/zap" "google.golang.org/protobuf/types/known/timestamppb" ) diff --git a/pkg/brevdaemon/agent/identity/identity.go b/pkg/brevdaemon/agent/identity/identity.go index c1a1c127..0c9ff7ab 100644 --- a/pkg/brevdaemon/agent/identity/identity.go +++ b/pkg/brevdaemon/agent/identity/identity.go @@ -14,7 +14,7 @@ import ( "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/dev-plane/pkg/errors" + "github.com/brevdev/brev-cli/pkg/errors" "go.uber.org/zap" ) @@ -186,9 +186,14 @@ func EnsureIdentity( 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(), + RegistrationToken: cfg.RegistrationToken, + Hardware: hw.ToProto(), + HardwareFingerprint: hardwareFingerprint, } if cfg.DisplayName != "" { req.DisplayName = client.ProtoString(cfg.DisplayName) @@ -273,3 +278,7 @@ func ensureDeviceSalt(path string) (string, error) { } return salt, nil } + +func computeHardwareFingerprint(_ telemetry.HardwareInfo) (string, error) { + return rand.Text(), nil +} diff --git a/pkg/brevdaemon/agent/telemetry/hardware.go b/pkg/brevdaemon/agent/telemetry/hardware.go index df37db3f..0a4cafcd 100644 --- a/pkg/brevdaemon/agent/telemetry/hardware.go +++ b/pkg/brevdaemon/agent/telemetry/hardware.go @@ -11,7 +11,7 @@ import ( 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/dev-plane/pkg/errors" + "github.com/brevdev/brev-cli/pkg/errors" "golang.org/x/sys/unix" ) diff --git a/pkg/brevdaemon/agent/telemetry/hardware_test.go b/pkg/brevdaemon/agent/telemetry/hardware_test.go index 6875ca96..693b4960 100644 --- a/pkg/brevdaemon/agent/telemetry/hardware_test.go +++ b/pkg/brevdaemon/agent/telemetry/hardware_test.go @@ -6,7 +6,7 @@ import ( "runtime" "testing" - "github.com/brevdev/dev-plane/pkg/errors" + "github.com/brevdev/brev-cli/pkg/errors" "github.com/stretchr/testify/require" ) diff --git a/pkg/brevdaemon/agent/telemetry/utilization.go b/pkg/brevdaemon/agent/telemetry/utilization.go index 1f18964e..9baacbbe 100644 --- a/pkg/brevdaemon/agent/telemetry/utilization.go +++ b/pkg/brevdaemon/agent/telemetry/utilization.go @@ -13,7 +13,7 @@ import ( "time" brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" - "github.com/brevdev/dev-plane/pkg/errors" + "github.com/brevdev/brev-cli/pkg/errors" "golang.org/x/sys/unix" ) diff --git a/pkg/brevdaemon/agent/tunnel/ingress.go b/pkg/brevdaemon/agent/tunnel/ingress.go deleted file mode 100644 index dac86026..00000000 --- a/pkg/brevdaemon/agent/tunnel/ingress.go +++ /dev/null @@ -1,202 +0,0 @@ -package tunnel - -import ( - "context" - "net" - "net/http" - "os/exec" - "strconv" - "strings" - "time" - - brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" - "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/telemetry" - "github.com/brevdev/dev-plane/pkg/brevcloud/appaccess" - "github.com/brevdev/dev-plane/pkg/errors" -) - -const ( - dashboardServiceName = "dgx-dashboard.service" - loopbackHost = "127.0.0.1" -) - -type ( - probeFunc func(ctx context.Context, host string, port int, timeout time.Duration) error - httpProbeFunc func(ctx context.Context, host string, port int, protocol string, timeout time.Duration) error - systemdStatusFunc func(ctx context.Context, service string, timeout time.Duration) (active bool, supported bool, err error) -) - -func detectInstanceTypeFromHardware(hw telemetry.HardwareInfo) appaccess.InstanceType { - model := strings.ToUpper(hw.MachineModel) - if strings.Contains(model, "DGX") { - return appaccess.InstanceTypeDGXSpark - } - for _, gpu := range hw.GPUs { - if strings.Contains(strings.ToUpper(gpu.Model), "DGX") { - return appaccess.InstanceTypeDGXSpark - } - } - return appaccess.InstanceTypeUnknown -} - -func buildAppIngresses(ctx context.Context, cfg appaccess.Config, tcpProbe probeFunc, httpProbe httpProbeFunc, systemdCheck systemdStatusFunc, timeout time.Duration, instanceType appaccess.InstanceType) []*brevapiv2.AppIngressRequest { - if instanceType != appaccess.InstanceTypeDGXSpark { - return nil - } - apps := cfg.AllowedAppsForInstance(appaccess.InstanceTypeDGXSpark) - if len(apps) == 0 { - return nil - } - if tcpProbe == nil { - tcpProbe = defaultIngressProbe - } - if httpProbe == nil { - httpProbe = defaultHTTPProbe - } - if systemdCheck == nil { - systemdCheck = defaultSystemdStatus - } - - timeout = clampProbeTimeout(timeout) - ingresses := make([]*brevapiv2.AppIngressRequest, 0, len(apps)) - for _, spec := range apps { - if spec.DefaultPort <= 0 { - continue - } - - switch spec.ID { - case appaccess.AppIDDGXDashboard: - if dashboardHealthy(ctx, systemdCheck, httpProbe, spec.Protocol, spec.DefaultPort, timeout) { - ingresses = append(ingresses, appIngressFromSpec(spec)) - } - case appaccess.AppIDJupyter: - if err := httpProbe(ctx, loopbackHost, spec.DefaultPort, spec.Protocol, timeout); err == nil { - ingresses = append(ingresses, appIngressFromSpec(spec)) - } - default: - if err := tcpProbe(ctx, loopbackHost, spec.DefaultPort, timeout); err == nil { - ingresses = append(ingresses, appIngressFromSpec(spec)) - } - } - } - if len(ingresses) == 0 { - return nil - } - return ingresses -} - -func appIngressFromSpec(spec appaccess.AppSpec) *brevapiv2.AppIngressRequest { - pathPrefix := spec.PathPrefix - if pathPrefix == "" { - pathPrefix = "/" - } - protocol := spec.Protocol - if spec.ForceHTTPS { - protocol = "https" - } else if protocol == "" { - protocol = "http" - } - return &brevapiv2.AppIngressRequest{ - AppId: string(spec.ID), - Protocol: protocol, - LocalPort: int32(spec.DefaultPort), // defaultPort validated above (>0) - HostnamePrefix: string(spec.ID), - PathPrefix: pathPrefix, - ForceHttps: spec.ForceHTTPS, - } -} - -func dashboardHealthy(ctx context.Context, systemdCheck systemdStatusFunc, httpProbe httpProbeFunc, protocol string, port int, timeout time.Duration) bool { - active, supported, err := systemdCheck(ctx, dashboardServiceName, timeout) - if err != nil { - return false - } - if supported { - return active - } - if httpProbe == nil { - return false - } - return httpProbe(ctx, loopbackHost, port, protocol, timeout) == nil -} - -func defaultSystemdStatus(ctx context.Context, service string, timeout time.Duration) (bool, bool, error) { - if service == "" { - return false, false, errors.Errorf("service name is required") - } - timeout = clampProbeTimeout(timeout) - statusCtx, cancel := context.WithTimeout(ctx, timeout) - defer cancel() - - cmd := exec.CommandContext(statusCtx, "systemctl", "show", "-p", "ActiveState", "--value", service) - out, err := cmd.Output() - if err != nil { - if errors.Is(err, exec.ErrNotFound) { - return false, false, nil - } - var exitErr *exec.ExitError - if errors.As(err, &exitErr) { - stderr := strings.ToLower(string(exitErr.Stderr)) - if strings.Contains(stderr, "system has not been booted with systemd") || strings.Contains(stderr, "failed to connect to bus") { - return false, false, nil - } - return false, true, nil - } - return false, false, errors.WrapAndTrace(err) - } - - state := strings.TrimSpace(string(out)) - return strings.EqualFold(state, "active"), true, nil -} - -func defaultHTTPProbe(ctx context.Context, host string, port int, protocol string, timeout time.Duration) error { - if host == "" { - host = loopbackHost - } - if port <= 0 { - return errors.Errorf("invalid port %d", port) - } - - if protocol == "" { - protocol = "http" - } - - timeout = clampProbeTimeout(timeout) - client := &http.Client{Timeout: timeout} - req, err := http.NewRequestWithContext(ctx, http.MethodGet, protocol+"://"+net.JoinHostPort(host, strconv.Itoa(port)), nil) - if err != nil { - return errors.WrapAndTrace(err) - } - - resp, err := client.Do(req) - if err != nil { - return errors.WrapAndTrace(err) - } - return errors.WrapAndTrace(resp.Body.Close()) -} - -func defaultIngressProbe(ctx context.Context, host string, port int, timeout time.Duration) error { - if host == "" { - host = loopbackHost - } - if port <= 0 { - return errors.Errorf("invalid port %d", port) - } - dialer := &net.Dialer{Timeout: timeout} - conn, err := dialer.DialContext(ctx, "tcp", net.JoinHostPort(host, strconv.Itoa(port))) - if err != nil { - return errors.WrapAndTrace(err) - } - return errors.WrapAndTrace(conn.Close()) -} - -func clampProbeTimeout(timeout time.Duration) time.Duration { - switch { - case timeout <= 0: - return 750 * time.Millisecond - case timeout > 10*time.Second: - return 10 * time.Second - default: - return timeout - } -} diff --git a/pkg/brevdaemon/agent/tunnel/ingress_test.go b/pkg/brevdaemon/agent/tunnel/ingress_test.go deleted file mode 100644 index d4623bc6..00000000 --- a/pkg/brevdaemon/agent/tunnel/ingress_test.go +++ /dev/null @@ -1,242 +0,0 @@ -package tunnel - -import ( - "context" - "testing" - "time" - - brevapiv2 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/brevapi/v2" - "connectrpc.com/connect" - "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/identity" - "github.com/brevdev/brev-cli/pkg/brevdaemon/agent/telemetry" - "github.com/brevdev/dev-plane/pkg/brevcloud/appaccess" - "github.com/brevdev/dev-plane/pkg/errors" - "github.com/stretchr/testify/require" - "go.uber.org/zap" -) - -func TestDetectInstanceTypeFromHardware(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - hw telemetry.HardwareInfo - want appaccess.InstanceType - }{ - { - name: "machine model DGX", - hw: telemetry.HardwareInfo{MachineModel: "DGX GH200"}, - want: appaccess.InstanceTypeDGXSpark, - }, - { - name: "gpu model DGX", - hw: telemetry.HardwareInfo{ - GPUs: []telemetry.GPUInfo{{Model: "DGX A100"}}, - }, - want: appaccess.InstanceTypeDGXSpark, - }, - { - name: "non DGX", - hw: telemetry.HardwareInfo{MachineModel: "c2-standard"}, - want: appaccess.InstanceTypeUnknown, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - got := detectInstanceTypeFromHardware(tt.hw) - require.Equal(t, tt.want, got) - }) - } -} - -func TestBuildAppIngressesSparkHealthy(t *testing.T) { - t.Parallel() - - cfg := appaccess.DefaultConfig() - ingresses := buildAppIngresses( - context.Background(), - cfg, - nil, - successHTTPProbe, - stubSystemd(true, true), - 500*time.Millisecond, - appaccess.InstanceTypeDGXSpark, - ) - - require.Len(t, ingresses, 2) - requireAppIngress(t, ingresses, string(appaccess.AppIDDGXDashboard), 11000) - requireAppIngress(t, ingresses, string(appaccess.AppIDJupyter), 8888) -} - -func TestBuildAppIngressesNonSpark(t *testing.T) { - t.Parallel() - - cfg := appaccess.DefaultConfig() - ingresses := buildAppIngresses(context.Background(), cfg, nil, successHTTPProbe, stubSystemd(true, true), 500*time.Millisecond, appaccess.InstanceTypeUnknown) - require.Nil(t, ingresses) -} - -func TestBuildAppIngressesDashboardInactiveSkips(t *testing.T) { - t.Parallel() - - cfg := appaccess.DefaultConfig() - ingresses := buildAppIngresses(context.Background(), cfg, nil, successHTTPProbe, stubSystemd(false, true), 500*time.Millisecond, appaccess.InstanceTypeDGXSpark) - - require.Len(t, ingresses, 1) - requireAppIngress(t, ingresses, string(appaccess.AppIDJupyter), 8888) -} - -func TestBuildAppIngressesDashboardFallsBackWhenSystemdUnavailable(t *testing.T) { - t.Parallel() - - cfg := appaccess.DefaultConfig() - ingresses := buildAppIngresses(context.Background(), cfg, nil, successHTTPProbe, stubSystemd(false, false), 500*time.Millisecond, appaccess.InstanceTypeDGXSpark) - - require.Len(t, ingresses, 2) - requireAppIngress(t, ingresses, string(appaccess.AppIDDGXDashboard), 11000) - requireAppIngress(t, ingresses, string(appaccess.AppIDJupyter), 8888) -} - -func TestBuildAppIngressesProbeFailureSkips(t *testing.T) { - t.Parallel() - - cfg := appaccess.DefaultConfig() - ingresses := buildAppIngresses(context.Background(), cfg, nil, failingHTTPProbe, stubSystemd(false, false), 500*time.Millisecond, appaccess.InstanceTypeDGXSpark) - require.Nil(t, ingresses) -} - -func stubSystemd(active bool, supported bool) systemdStatusFunc { - return func(context.Context, string, time.Duration) (bool, bool, error) { - return active, supported, nil - } -} - -func successHTTPProbe(context.Context, string, int, string, time.Duration) error { - return nil -} - -func failingHTTPProbe(context.Context, string, int, string, time.Duration) error { - return errors.New("probe failed") -} - -func requireAppIngress(t *testing.T, ingresses []*brevapiv2.AppIngressRequest, appID string, port int) { - t.Helper() - for _, ingress := range ingresses { - if ingress.GetAppId() == appID { - require.Equal(t, int32(port), ingress.GetLocalPort()) - require.Equal(t, "https", ingress.GetProtocol()) - require.Equal(t, "/", ingress.GetPathPrefix()) - require.Equal(t, appID, ingress.GetHostnamePrefix()) - require.True(t, ingress.GetForceHttps()) - return - } - } - require.Fail(t, "expected ingress not found", "app_id=%s", appID) -} - -func TestManagerRequestsSSHWithoutAppIngressesForNonSpark(t *testing.T) { - t.Parallel() - - ctx := context.Background() - clientStub := &stubAgentClient{ - resp: &brevapiv2.GetTunnelTokenResponse{Token: "token"}, - } - manager := Manager{ - Client: clientStub, - Identity: identity.Identity{InstanceID: "brev-node", DeviceToken: "device-token"}, - Cfg: TunnelConfig{SSHPort: 22}, - Log: zap.NewNop(), - DetectHardware: func(context.Context) (telemetry.HardwareInfo, error) { - return telemetry.HardwareInfo{MachineModel: "c2-standard"}, nil - }, - HTTPProbe: successHTTPProbe, - SystemdStatus: stubSystemd(true, true), - CommandFactory: func(context.Context, string, ...string) Command { return stubCommand{} }, - ProbeTimeout: 500 * time.Millisecond, - AppConfig: appaccess.DefaultConfig(), - Sleep: func(context.Context, time.Duration) error { return nil }, - Probe: defaultIngressProbe, - } - - require.NoError(t, manager.Start(ctx)) - require.NotNil(t, clientStub.lastReq) - require.Len(t, clientStub.lastReq.Msg.GetRequestedPorts(), 1) - require.Equal(t, int32(22), clientStub.lastReq.Msg.GetRequestedPorts()[0].GetLocalPort()) - require.Empty(t, clientStub.lastReq.Msg.GetAppIngresses()) -} - -func TestManagerIncludesAppIngressesForSparkWhenProbesPass(t *testing.T) { - t.Parallel() - - ctx := context.Background() - clientStub := &stubAgentClient{ - resp: &brevapiv2.GetTunnelTokenResponse{Token: "token"}, - } - manager := Manager{ - Client: clientStub, - Identity: identity.Identity{InstanceID: "brev-node", DeviceToken: "device-token"}, - Cfg: TunnelConfig{SSHPort: 22}, - Log: zap.NewNop(), - DetectHardware: func(context.Context) (telemetry.HardwareInfo, error) { - return telemetry.HardwareInfo{MachineModel: "DGX Station"}, nil - }, - HTTPProbe: successHTTPProbe, - SystemdStatus: stubSystemd(true, true), - CommandFactory: func(context.Context, string, ...string) Command { return stubCommand{} }, - ProbeTimeout: 500 * time.Millisecond, - AppConfig: appaccess.DefaultConfig(), - Sleep: func(context.Context, time.Duration) error { return nil }, - Probe: defaultIngressProbe, - } - - require.NoError(t, manager.Start(ctx)) - require.NotNil(t, clientStub.lastReq) - require.Len(t, clientStub.lastReq.Msg.GetRequestedPorts(), 1) - require.Len(t, clientStub.lastReq.Msg.GetAppIngresses(), 2) - requireAppIngressParams(t, clientStub.lastReq.Msg.GetAppIngresses(), string(appaccess.AppIDDGXDashboard), 11000) - requireAppIngressParams(t, clientStub.lastReq.Msg.GetAppIngresses(), string(appaccess.AppIDJupyter), 8888) -} - -func requireAppIngressParams(t *testing.T, ingresses []*brevapiv2.AppIngressRequest, appID string, port int) { - t.Helper() - for _, ingress := range ingresses { - if ingress.GetAppId() == appID { - require.Equal(t, int32(port), ingress.GetLocalPort()) - require.Equal(t, appID, ingress.GetHostnamePrefix()) - require.Equal(t, "https", ingress.GetProtocol()) - require.True(t, ingress.GetForceHttps()) - return - } - } - require.Fail(t, "expected app ingress missing", "app_id=%s", appID) -} - -type stubAgentClient struct { - lastReq *connect.Request[brevapiv2.GetTunnelTokenRequest] - resp *brevapiv2.GetTunnelTokenResponse -} - -func (s *stubAgentClient) Register(context.Context, *connect.Request[brevapiv2.RegisterRequest]) (*connect.Response[brevapiv2.RegisterResponse], error) { - return connect.NewResponse(&brevapiv2.RegisterResponse{}), nil -} - -func (s *stubAgentClient) Heartbeat(context.Context, *connect.Request[brevapiv2.HeartbeatRequest]) (*connect.Response[brevapiv2.HeartbeatResponse], error) { - return connect.NewResponse(&brevapiv2.HeartbeatResponse{}), nil -} - -func (s *stubAgentClient) GetTunnelToken(_ context.Context, req *connect.Request[brevapiv2.GetTunnelTokenRequest]) (*connect.Response[brevapiv2.GetTunnelTokenResponse], error) { - s.lastReq = req - if s.resp == nil { - s.resp = &brevapiv2.GetTunnelTokenResponse{} - } - return connect.NewResponse(s.resp), nil -} - -type stubCommand struct{} - -func (stubCommand) Start() error { return nil } -func (stubCommand) Wait() error { return nil } -func (stubCommand) SetEnv([]string) {} -func (stubCommand) CombinedOutput() ([]byte, error) { return nil, nil } diff --git a/pkg/brevdaemon/agent/tunnel/tunnel.go b/pkg/brevdaemon/agent/tunnel/tunnel.go index 0ab5528b..9c9edfdb 100644 --- a/pkg/brevdaemon/agent/tunnel/tunnel.go +++ b/pkg/brevdaemon/agent/tunnel/tunnel.go @@ -17,9 +17,8 @@ import ( "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" - "github.com/brevdev/dev-plane/pkg/brevcloud/tunnel" - "github.com/brevdev/dev-plane/pkg/errors" "go.uber.org/zap" ) @@ -32,7 +31,7 @@ const ( // TunnelConfig configures tunnel management. type TunnelConfig struct { - SSHPort int + SSHPort int32 ClientBinary string } @@ -49,9 +48,6 @@ type Manager struct { LookPath func(string) (string, error) DetectHardware func(context.Context) (telemetry.HardwareInfo, error) - Probe probeFunc - HTTPProbe httpProbeFunc - SystemdStatus systemdStatusFunc ProbeTimeout time.Duration AppConfig appaccess.Config @@ -88,22 +84,9 @@ func (m *Manager) Start(ctx context.Context) error { //nolint:gocognit,gocyclo,f jitterFn = addJitter } - detectFn := m.detectHardwareFunc() - probeFn := m.probeFunc() - httpProbe := m.httpProbeFunc() - systemdStatus := m.systemdStatusFunc() - probeTimeout := m.probeTimeout() - appCfg := m.appAccessConfig() - - hw, hwErr := detectFn(ctx) - if hwErr != nil { - m.Log.Info("skipping app ingress discovery: hardware detection failed", zap.Error(hwErr)) - } - instanceType := detectInstanceTypeFromHardware(hw) - req := &brevapiv2.GetTunnelTokenRequest{ BrevCloudNodeId: m.Identity.InstanceID, - RequestedPorts: tunnelPortsToProto([]tunnel.TunnelPortMapping{ + RequestedPorts: tunnelPortsToProto([]brevapiv2.TunnelPortMapping{ { LocalPort: m.Cfg.SSHPort, }, @@ -119,11 +102,6 @@ func (m *Manager) Start(ctx context.Context) error { //nolint:gocognit,gocyclo,f } req.AppIngresses = nil - if instanceType == appaccess.InstanceTypeDGXSpark { - if ingresses := buildAppIngresses(ctx, appCfg, probeFn, httpProbe, systemdStatus, probeTimeout, instanceType); len(ingresses) > 0 { - req.AppIngresses = ingresses - } - } token, err := m.requestTunnelToken(ctx, req) if err != nil { @@ -303,7 +281,7 @@ func addJitter(delay time.Duration) time.Duration { return time.Duration(float64(delay) * factor) } -func tunnelPortsToProto(ports []tunnel.TunnelPortMapping) []*brevapiv2.TunnelPortMapping { +func tunnelPortsToProto(ports []brevapiv2.TunnelPortMapping) []*brevapiv2.TunnelPortMapping { if len(ports) == 0 { return nil } @@ -324,8 +302,8 @@ func tunnelPortsToProto(ports []tunnel.TunnelPortMapping) []*brevapiv2.TunnelPor continue } out = append(out, &brevapiv2.TunnelPortMapping{ - LocalPort: int32(lp), - RemotePort: int32(rp), + LocalPort: lp, + RemotePort: rp, Protocol: port.Protocol, }) } @@ -355,38 +333,3 @@ func (m *Manager) detectHardwareFunc() func(context.Context) (telemetry.Hardware } return telemetry.DetectHardware } - -func (m *Manager) probeFunc() probeFunc { - if m.Probe != nil { - return m.Probe - } - return defaultIngressProbe -} - -func (m *Manager) httpProbeFunc() httpProbeFunc { - if m.HTTPProbe != nil { - return m.HTTPProbe - } - return defaultHTTPProbe -} - -func (m *Manager) systemdStatusFunc() systemdStatusFunc { - if m.SystemdStatus != nil { - return m.SystemdStatus - } - return defaultSystemdStatus -} - -func (m *Manager) probeTimeout() time.Duration { - if m.ProbeTimeout > 0 { - return m.ProbeTimeout - } - return 750 * time.Millisecond -} - -func (m *Manager) appAccessConfig() appaccess.Config { - if len(m.AppConfig.Allowlist) == 0 { - return appaccess.DefaultConfig() - } - return m.AppConfig -} diff --git a/pkg/spark/remote.go b/pkg/spark/remote.go index c7921f9b..de490ed8 100644 --- a/pkg/spark/remote.go +++ b/pkg/spark/remote.go @@ -10,8 +10,8 @@ import ( "strings" "time" - "github.com/spf13/afero" "github.com/brevdev/brev-cli/pkg/errors" + "github.com/spf13/afero" ) // RemoteRunner executes commands on a Spark host over ssh and returns stdout/stderr.