diff --git a/.github/workflows/tests-deployment.yml b/.github/workflows/tests-deployment.yml index 805da13e..bb06ea82 100644 --- a/.github/workflows/tests-deployment.yml +++ b/.github/workflows/tests-deployment.yml @@ -43,7 +43,10 @@ jobs: {"enabled": false, "infra": "azure", "os": "macos-latest", "task": "tests-deployment-infrastructure"}, {"enabled": false, "infra": "exoscale", "os": "ubuntu-latest", "task": "tests-deployment-infrastructure"}, {"enabled": false, "infra": "exoscale", "os": "windows-latest", "task": "tests-deployment-infrastructure"}, - {"enabled": true, "infra": "exoscale", "os": "macos-latest", "task": "tests-deployment-infrastructure"} + {"enabled": true, "infra": "exoscale", "os": "macos-latest", "task": "tests-deployment-infrastructure"}, + {"enabled": false, "infra": "hetzner", "os": "ubuntu-latest", "task": "tests-deployment-infrastructure"}, + {"enabled": false, "infra": "hetzner", "os": "windows-latest", "task": "tests-deployment-infrastructure"}, + {"enabled": false, "infra": "hetzner", "os": "macos-latest", "task": "tests-deployment-infrastructure"} ]' filtered_plan_json="$(jq -c --arg os "${selected_os}" ' @@ -122,6 +125,12 @@ jobs: echo "AWS_ACCESS_KEY_ID=${{ secrets.EXOSCALE_API_KEY }}" >> "$GITHUB_ENV" echo "AWS_SECRET_ACCESS_KEY=${{ secrets.EXOSCALE_API_SECRET }}" >> "$GITHUB_ENV" + - name: Setup Hetzner Cloud credentials + if: ${{ matrix.infra == 'hetzner' }} + shell: bash + run: | + echo "HCLOUD_TOKEN=${{ secrets.HCLOUD_TOKEN }}" >> "$GITHUB_ENV" + # Doing a second build here again. In theory, we could # upload the build artifact from the ci.yml workflow # and fetch it here. However, for now the build run time diff --git a/.gitignore b/.gitignore index 43176480..6b61f6f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Build and dependency directories /vendor/ /bin/ +/tools/bin/ # IDE/editor files .vscode/ diff --git a/HOWTO_SETUP_HETZNER_ACCOUNT.md b/HOWTO_SETUP_HETZNER_ACCOUNT.md new file mode 100644 index 00000000..35b49d89 --- /dev/null +++ b/HOWTO_SETUP_HETZNER_ACCOUNT.md @@ -0,0 +1,62 @@ +# Set up a Hetzner Cloud account for Exasol Personal + +This document explains how to set up a Hetzner Cloud account to deploy Exasol Personal. + +## βœ… Prerequisites + +The following procedure assumes that you have a basic understanding of how Hetzner Cloud works and how to manage projects and API tokens. For more information, refer to the official Hetzner Cloud documentation: https://docs.hetzner.com/cloud/ + +The Hetzner Cloud project must have sufficient quota to create servers, volumes, networks, and firewalls in the location that you want to use. + +## πŸ›  Procedure + +### πŸ†• Create a Hetzner Cloud account + +If you do not have a Hetzner Cloud account, visit the Hetzner Cloud home page: https://www.hetzner.com/cloud/ to create one. + +In the Hetzner Cloud Console, create a new project or choose an existing one for Exasol Personal deployments. + +### πŸ” In the Hetzner Cloud Console, do the following: + +1. Open the project that you want to use for Exasol Personal. +2. Navigate to `Security` β†’ `API Tokens`. +3. Click `Generate API Token`. +4. Give the token a descriptive name (e.g., `exasol-personal`). +5. Select **Read & Write** permissions. +6. Click `Generate API Token` and copy the token β€” it is only shown once. + +**Note:** SSH keys are automatically created during deployment β€” you do not need to pre-add any SSH keys to your Hetzner project. + +### πŸ’» On your local machine, do the following: + +1. Set the Hetzner Cloud API token as an environment variable: + + ```bash + # Linux / macOS (Bash) + export HCLOUD_TOKEN= + ``` + ```powershell + # Windows (PowerShell) + $env:HCLOUD_TOKEN = "" + ``` + ```powershell + # Windows (cmd) + set HCLOUD_TOKEN= + ``` + +2. Run the launcher with the Hetzner preset. The default location is `fsn1`. To deploy to a different location, pass `--location`: + + ```bash + exasol install hetzner # deploy to fsn1 (default) + exasol install hetzner --location nbg1 # deploy to Nuremberg + exasol install hetzner --location hel1 # deploy to Helsinki + exasol install hetzner --location ash # deploy to Ashburn, US + exasol install hetzner --location hil # deploy to Hillsboro, US + ``` + + Available locations: `fsn1` (Falkenstein), `nbg1` (Nuremberg), `hel1` (Helsinki), `ash` (Ashburn), `hil` (Hillsboro). + +For more information on Hetzner Cloud API tokens and authentication, see: + +- https://docs.hetzner.com/cloud/api/getting-started/generating-api-token/ +- https://docs.hetzner.com/cloud/api/getting-started/ diff --git a/README.md b/README.md index 87f97a79..15dc0d84 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ - ♾️ **Unlimited Data** β€” Store and analyze unlimited amounts of data with no artificial limits - πŸ“ˆ **Scalable Architecture** β€” Scale up to any number of nodes using Exasol's MPP (Massively Parallel Processing) architecture - πŸ€– **Built-in AI Functions** β€” Leverage native AI/ML capabilities with GPU acceleration -- βš™οΈ **Simple Deployment** β€” Spin up a distributed cluster on AWS, Azure, or Exoscale with just a few commands +- βš™οΈ **Simple Deployment** β€” Spin up a distributed cluster on AWS, Azure, Exoscale, or Hetzner Cloud with just a few commands - πŸ–₯️ **Cross-Platform CLI** β€” Install and manage your cluster using the Exasol Launcher on Linux, macOS, or Windows @@ -35,6 +35,7 @@ A cloud account on one of the supported platforms with permission to provision c - **AWS** β€” [Set up an AWS account for Exasol Personal](./HOWTO_SETUP_AWS_ACCOUNT.md) - **Azure** β€” [Set up an Azure account for Exasol Personal](./HOWTO_SETUP_AZURE_ACCOUNT.md) - **Exoscale** β€” [Set up an Exoscale account for Exasol Personal](./HOWTO_SETUP_EXOSCALE_ACCOUNT.md) +- **Hetzner Cloud** β€” [Set up a Hetzner Cloud account for Exasol Personal](./HOWTO_SETUP_HETZNER_ACCOUNT.md) ## 🏎️ Quick Start (macOS / Linux) @@ -63,6 +64,10 @@ exasol install azure # Microsoft Azure exasol install exoscale # Exoscale ``` +```bash +exasol install hetzner # Hetzner Cloud +``` + Read on for Windows instructions and full details. @@ -92,6 +97,7 @@ Read on for Windows instructions and full details. exasol install aws # Amazon Web Services exasol install azure # Microsoft Azure exasol install exoscale # Exoscale + exasol install hetzner # Hetzner Cloud ``` The `exasol install` command does the following: - Generates OpenTofu files in the deployment directory diff --git a/assets/infrastructure/hetzner/.terraform.lock.hcl b/assets/infrastructure/hetzner/.terraform.lock.hcl new file mode 100644 index 00000000..59e05548 --- /dev/null +++ b/assets/infrastructure/hetzner/.terraform.lock.hcl @@ -0,0 +1,115 @@ +# This file is maintained automatically by "tofu init". +# Manual edits may be lost in future updates. + +provider "registry.opentofu.org/hashicorp/cloudinit" { + version = "2.4.0" + constraints = "~> 2.3" + hashes = [ + "h1:PMRgZR+2rkcyYT4GTZ6aQN0YWGRGXX/mk+cq6m6pZac=", + "zh:1100d08add4583fa0c72ae61a4d90fe7e0e4c912800e8ce40736d5491cca6b94", + "zh:25671e5909082cdf564476a309ab958f4948a50819a6faea7f4768d77d4b2866", + "zh:27036ecde32c5f2fae4c965938554fc9085e4c04dc9a74f5aae6a91c06f3d738", + "zh:2eabfb9a357e21d906e6a4a3cba1299a4494694e90816c5d1c94253161244e4d", + "zh:46aff469c48be99db25a17b8aceff980d3253b5c43fe90e11df710d01ed15ef5", + "zh:58381b46c91f4dfea37c701896f1b7144373e9bd6fd1a2aaec561f9b69573156", + "zh:5e93d4b1600c3af7929eefccf092bf9a4f2a94bd2f29f5a2cebc98eede3fd88c", + "zh:67ad0360d17d5a2ade305bd3083caddd3c63b6d692434d249d3b71c6f7fcd0e4", + "zh:6e51f558aeb4271923f8822d900f2c8fe56f155f56f022366c22ec80c633e05e", + "zh:8be11e71a35964eecbcc36ad47a82dde0f51425567658cb21c9c6d5a9fce0494", + "zh:90de8354c3fffb6d0b9b663312652c98011022d0ccfac52900ce148bb263d200", + "zh:94c409a472d76e8a90f634dd804ff8399301bca103a7456f74d007d1fb22913f", + "zh:9e24975b2c085e1aee74b83799f9f0a3644ffcd9ae0c7d2840929d82153be167", + "zh:d24742682a35b618b4012c52a6201fda845dd706fe46e6c08a4611ea6abd6fc4", + "zh:ed6b04017882f68e0342b951e5c008e4852c7ed32c7d6fb81ced0437fa0a9742", + ] +} + +provider "registry.opentofu.org/hashicorp/local" { + version = "2.9.0" + constraints = "~> 2.6" + hashes = [ + "h1:rxomJjDwOo+YZ+WIPc25FqEgsz9orh/2MCyUcZmFjvw=", + "zh:13ef7ecd1e397ec5b20ea588508dd3e3b8d6c50d809ae76b079abf9dd8d02e4b", + "zh:2190c9325980076489ce02b0f5dd2c0b91fc8711cefa99e714d8619a32827ad1", + "zh:2a0cfc5600730093705071707e4a4e4e953e7d9091859e0f66b46daa1060dd5d", + "zh:2ff53eac1af43ab9a2248a0e53c963d46e19cf04bc4c3f323591cfcebb218252", + "zh:4ebc3dee700f60af9da29970052fd02fa947813162b224716862dc9d7f1f7542", + "zh:5fe6dab84ceeaa8eb3f1567c5f05578333370c472240ca5c5bfc25e92d4d5586", + "zh:66bbec16367bbf440045502c9779b11f4ac5b022c8d8d17afe12d431950838b5", + "zh:7641e5c2e4b529e869cde29ab5b1de2fd1091489eb745b19ac2709bd7f4dfd84", + "zh:855bfba0756d17ce07595ff57d7cf664443d1495127cb88fb063362734b8b22a", + "zh:aaec10f237921d60c581d1b7a66f0a8a8019d9802dc04af11b5b981f6682e01d", + "zh:e460835a38ffa1e74f6929904bfd14ef473d217fd537b7ce834abe5ce5e2ce07", + "zh:ecc4295215db0e4aea3c9329611c31e09a853e1ae207d56742403bd4f5516703", + "zh:ee6d9fae63a612072e00402894e14826af7a3351c235b9c5b423b7629a77ca29", + "zh:f2b5c8db74aa7ebcf7cd423672358437d42401675069ef67b01ff910054e49d5", + "zh:f5aff74d3eb96d4592c7bca5cd3ea89b469e84efbf382944bd0f844a57059c09", + ] +} + +provider "registry.opentofu.org/hashicorp/random" { + version = "3.9.0" + constraints = "~> 3.7" + hashes = [ + "h1:8EQU5KSxezcjo/phRSe69rDOI0lk4pSaggj7FsskYp8=", + "zh:03f1114cc20b8913523735ab76e0f0a2b16ce13c92923a53304bf85f07fc0dbc", + "zh:105b678ee72322a3067f105d7e05e940f6143238f377f6e87ff4ec909246ac2a", + "zh:55f3bbf13ea18cbace61a706566a80f25f33fe2b1780b6f3d7b582af2a05b6d2", + "zh:63adf996db48f082f7a6351eb485e219cd88795fc71e6ec60a837263ab0d2cb1", + "zh:7e99550738a4e3cc68b8a467714b0d69371025fe95e3326d5323d026d55653e9", + "zh:8342b54af3a18a37e075eeae61be57f4de2ba71b35d95c5075d402dd2c1f289d", + "zh:83ee18e32ac9dd5fc91298554b7c4cfa4c3a1db50f4c797945637cc93c0844ae", + "zh:993ecc0adbf6bd535a59fbc9b735d8c33950e6f6eb5e621d750da9b71d65d80a", + "zh:ad722bc59d4edbf1415e827fc007c0efe6e0e9462d5568bae20b34be1058a261", + "zh:ae9448e1f87b2f9a6c5197a0e9862162ec6b137cb3a3835e11522995d8939e7c", + "zh:bc9cdd3aac784f759125c6627f6f6416e8726a1c184eb9cf3e55b9edbc94c627", + "zh:c8e35b89572ba1c40a9b20022e033a3395fb8d42e7604d50c900f193ba10382e", + "zh:e2deaa8a9975ef81d9f62baed12c41286918b0a10908e0e031f13f69a3b730a1", + "zh:ee39707557210a0ab1098aa357d2cdfe502e5a312d0dbdffb09d08facc4d3fc5", + "zh:f81afe4eb63e8aa9e0ea71be6c990f0dc69cb360e7191c0742a991f4a5081b64", + ] +} + +provider "registry.opentofu.org/hashicorp/tls" { + version = "4.3.0" + constraints = "~> 4.1" + hashes = [ + "h1:SGBiqFFxGryTOiaNNWlC7CCba1hjSsSwcJeg6rOLJrc=", + "zh:07bb8c6e64124dada7dff57a38a46f2f323b3fd77920404c0c550293d1cf6188", + "zh:0b3bfda2df39c52f1c5452d05cf3107bedd5d20ab6977c90ede540c695fb6c3e", + "zh:110a055289f0400a63ac172bedb0e671d059b7a5ba22d4a3f5f246ccac0ad676", + "zh:15e532d8c711377499dece832e60170a8bef39830125b8154f4bda81d9721d29", + "zh:22ca65d96e9fc1be5605372d855c9e1eba2d86d510f7ac8593968f5649435e47", + "zh:36df38dfd03e8c1298c5704fd85e28b69a3927ed0b339f9628d0b56dac99c6b5", + "zh:429e2bfcb81656e1fe90b7b284767d1453c1a4100b16d27e4b29c34aa12f0ce1", + "zh:5b6679953065f0279bf018426c6fb06dd93a851a7a9369f2e3a1fec5bc417e83", + "zh:6a72c88d5aa945ddb32041350755377c96681563136decfe7e05c7cdea7988f1", + "zh:6f05757c50da9f8354a735b5756bd63a71126fcd142129525b90c56bfd081d61", + "zh:751703b7a4d40c3a111c4ed0d5da3ec91c14f880faf6f010a5000a2eb5366011", + "zh:87a5279e61b8198798a2fe86cfe3b74e5340bb486f4e148bb5b4d46f860cf1db", + "zh:942af95e9fd73327a7e9ab0803c4d701b782ddacd78c9b7ce9c91e38b3051522", + "zh:a457d0efea3c404178a182d240ba21cdeb0c620ffabeeb9a8977b024a85e1360", + "zh:d5eac8f4f0ae1ff41cbcc1008e6a74a8491dc27f4c6e5a0c32c5c4b6ef2e4087", + ] +} + +provider "registry.opentofu.org/hetznercloud/hcloud" { + version = "1.63.0" + constraints = "~> 1.58" + hashes = [ + "h1:1Yxj4ZifWjSYntT/IK08jX71yeO6cwLBJ2r1YdrOP+0=", + "zh:0510f27825d28b065615c6e715a4aae38a50633195a8a88d4507476bb6484c33", + "zh:0fba95c8fc048155c2211f15f850b75fe33cac96201bcaaa28467b32f6ac568e", + "zh:11d917ac92ca30fe82d362465a1685b3a9f9a3abcd486b0114ff680746d0af08", + "zh:3fccfeb0c12041223edfe8f4e6d53c285c050dad5611e57e3e27a3233ab41459", + "zh:4cd4b626f2ce0f2cc49a1eaf62e1a8b4a0868cd902f3181019215cabfd841aae", + "zh:57baa59c7e80fa5612347a7208aa8e2b1fd88563e91b6a68fe154f507d75d210", + "zh:695cf459e16fabfa25348cf00299ff57b100992c2e56e27ebebb91923acdd7f3", + "zh:6a68c3280930f06158b4b6cbbc4b6329a3b28de42bd5ca284e8b4f1a141f5899", + "zh:9b860ac44e673a3901f782730929c99c229437d9020214cd786952f8de94a10d", + "zh:b8704c7d7f76b1bf8ef7fd722e174d1cd5a0c9901c4fdee1145623583f83629a", + "zh:e3f1e222886b4f48ab102f6e898fd69a0a084fb5e0e3b5de4245b842ed96b459", + "zh:f3b9fc88a0ffed753e5c17589177aa3ab524909f568506b04c348f93ca3c4e5c", + "zh:f9c3fb21c9f64e82841811e7ef51e3a102c1af801153a670bffd005d89476f0e", + ] +} diff --git a/assets/infrastructure/hetzner/README.md b/assets/infrastructure/hetzner/README.md new file mode 100644 index 00000000..54c61b57 --- /dev/null +++ b/assets/infrastructure/hetzner/README.md @@ -0,0 +1,60 @@ +# Hetzner Cloud Infrastructure as Code Architecture + +## Overview +This document describes the Infrastructure as Code (IaC) implementation for Exasol Personal on Hetzner Cloud. It supports both single-node and multi-node (cluster) deployments with a simple, opinionated setup for networking, storage, and installation. + +## Prerequisites and Hetzner Cloud Provider +- Hetzner Cloud API token is taken from the environment variable `HCLOUD_TOKEN`. +- Location selection is available via `--location` (e.g., `fsn1`, `nbg1`, `hel1`, `ash`, `hil`). Defaults to `fsn1` (Falkenstein). +- The provider configuration in `providers.tf` uses the environment variable and does not define credentials inline. + +## Infrastructure Components + +### Compute +- Hetzner Cloud servers named after their node IDs (e.g., `n11`, `n12`, ...). +- Default server type `ccx33` (8 vCPU / 16 GB RAM; configurable via `--server-type`). +- Ubuntu 22.04 image from Hetzner's public image catalog. +- Cluster support: one server per node; controlled by `--cluster-size` (default: 1). +- Servers get public IPv4 addresses automatically. + +### Storage +- Separate volumes for OS/root and database data. +- OS disk is part of the server (size configurable via `--os-volume-size`, minimum 20 GB). +- Data volumes are separate block storage volumes attached via `hcloud_volume_attachment`. +- Data volume size configurable via `--data-volume-size` (in GB). +- Remote archive volume on Hetzner Object Storage (S3-compatible) can be enabled via `--s3-archive-enabled` (default: disabled). Available in EU locations only. + +### Networking +- Servers receive public IPv4 addresses automatically. +- A private network (`hcloud_network`) with a subnet is created for inter-node communication. +- Private IPs are assigned from the `172.16.0.0/24` range starting at `.20`. +- A firewall (`hcloud_firewall`) controls inbound/outbound traffic. + +## Access and Security +The following ports are exposed via firewall rules: + +1. 22 β€” SSH access (public) +2. 2581 β€” Default bucketfs (public) +3. 8443 β€” Admin UI HTTPS (public) +4. 8563 β€” Default database port (public) +5. 20002 β€” Exasol container SSH (public) +6. 20003 β€” Exasol confd API (public) + +Additionally, internal traffic (TCP/UDP/ICMP) is allowed between cluster nodes via the private network CIDR range. + +## Resource Organization (Labels) +- A unique deployment ID is generated at apply time (e.g., `exasol-`). +- All resources carry labels: `managed_by=opentofu`, `project=exasol-personal`, `deployment_id=exasol-`. +- The cleanup tool uses these labels to discover and manage deployments. + +## Power State Management + +`exasol stop` and `exasol start` control VM power state directly via the Hetzner Cloud API using the server IDs stored in `deployment.json`. No Terraform resources are involved in stop/start β€” the Go launcher calls the Hetzner API (`/v1/servers/{id}/actions/shutdown` and `/v1/servers/{id}/actions/poweron`) and waits for the action to complete. This avoids Terraform state drift that would occur with provisioner-based approaches. + +`HCLOUD_TOKEN` must be set in the environment when running `exasol stop` or `exasol start`. + +## Deployment Artifacts +After a successful apply, the following files are written to the deployment directory: +- `deployment.json` β€” connection info (IPs, ports, instance IDs) +- `secrets.json` β€” credentials (DB password, Admin UI password) +- `node_access.pem` β€” SSH private key for node access diff --git a/assets/infrastructure/hetzner/cloudconf/10-create-ubuntu-user.yaml b/assets/infrastructure/hetzner/cloudconf/10-create-ubuntu-user.yaml new file mode 100644 index 00000000..24ae8b40 --- /dev/null +++ b/assets/infrastructure/hetzner/cloudconf/10-create-ubuntu-user.yaml @@ -0,0 +1,28 @@ +#cloud-config +# Hetzner Ubuntu images use 'root' as the default user, but the Exasol +# installation scripts expect a 'ubuntu' user. Create it here. + +# The public SSH key is injected via cloud-config to avoid timing issues +# with Hetzner's SSH key injection mechanism. +ssh_authorized_keys: + - ${admin_ssh_public_key} + +users: + - name: ubuntu + gecos: Ubuntu User + sudo: ALL=(ALL) NOPASSWD:ALL + shell: /bin/bash + groups: sudo + ssh_authorized_keys: + - ${admin_ssh_public_key} + +# Populate known_hosts so the barrier client can SSH to itself without host key prompts. +# The private network may not be up immediately, so retry with a delay. +runcmd: + - | + for i in $(seq 1 30); do + ssh-keyscan -H 172.16.0.20 >> /home/ubuntu/.ssh/known_hosts 2>/dev/null && break + sleep 2 + done + - "ssh-keyscan -H localhost >> /home/ubuntu/.ssh/known_hosts 2>/dev/null || true" + - "chown ubuntu:ubuntu /home/ubuntu/.ssh/known_hosts" diff --git a/assets/infrastructure/hetzner/cloudinit.tf b/assets/infrastructure/hetzner/cloudinit.tf new file mode 100644 index 00000000..92cdce93 --- /dev/null +++ b/assets/infrastructure/hetzner/cloudinit.tf @@ -0,0 +1,195 @@ +# Cloud Init +# +# This file builds per-node cloud-init user data. The structure is intentionally split into: +# 1) static cloud-config (from the installation preset) +# 2) dynamically generated write_files entries: +# - deployment metadata JSON (common for all nodes) +# - node metadata JSON (specific to the current node) +# - installation preset files (embedded directly) +# - infrastructure preset files (optional; embedded directly) + +locals { + # --- Path "constants" --- + # Changing these paths will break the deployment processes as we use them to find the installation preset contents + installation_cloudconf_dir = "${local.installation_preset_dir}/cloudconf" + installation_files_dir = "${local.installation_preset_dir}/files" + + # Optional infrastructure-preset-provided host file overlay. + # This is used for infrastructure-specific scripts or configs that should not live in provider-agnostic installation presets. + infrastructure_files_dir = "${path.module}/files" + + # Infrastructure-specific cloud-config (runs before installation cloud-config). + # Used for provider-specific setup like creating the 'ubuntu' user on Hetzner. + infrastructure_cloudconf_dir = "${path.module}/cloudconf" + + # Changing these paths will break the installation processes on the hosts as they look for these files + launcher_config_dir = "/etc/exasol_launcher" + infrastructure_json_target_path = "${local.launcher_config_dir}/infrastructure.json" + node_json_target_path = "${local.launcher_config_dir}/node.json" + + # Read the static cloud-config from the installation preset. + # The preset may provide one or more YAML files in a flat directory. We include each file + # as its own cloud-init "part" (stable lexicographic order) to avoid Terraform-side YAML + # merging logic and keep the behavior easy to understand. + installation_cloudconf_files = sort( + fileset(local.installation_cloudconf_dir, "*") + ) + + # Infrastructure-specific cloud-config files (optional, e.g., for creating ubuntu user on Hetzner). + # These run before installation cloud-config (05- prefix vs 10- prefix). + infrastructure_cloudconf_files = sort( + fileset(local.infrastructure_cloudconf_dir, "*") + ) + + # Materialize metadata of files to be installed. + installation_node_files = [ + for rel in fileset(local.installation_files_dir, "**") : { + src_path = "${local.installation_files_dir}/${rel}" + dest_path = "/${rel}" + permissions = endswith(rel, ".sh") ? "0755" : "0644" + } + ] + + # Infrastructure preset files to be installed on the node (optional overlay). + # These are applied after installation preset files, so infra presets can override paths when necessary. + infrastructure_node_files = [ + for rel in fileset(local.infrastructure_files_dir, "**") : { + src_path = "${local.infrastructure_files_dir}/${rel}" + dest_path = "/${rel}" + permissions = endswith(rel, ".sh") ? "0755" : "0644" + } + ] + + # Cluster addressing helpers (also used for JSON payload values). + node_ips = [for n in local.nodes : n.ip] + node_ips_space_sep = join(" ", local.node_ips) + n11_ip = one([for n in local.nodes : n.ip if n.name == "n11"]) + + # Deployment metadata written to disk. + # Keep this focused: only include information that is plausibly useful on the node + # at runtime or for diagnostics. Operator-only settings (e.g. desired power state) should + # not be written to the instance. + # These values are intended for consumption on the node (scripts read them). + infrastructure_payload = { + + # Keep only fields currently consumed by host-side scripts. + deploymentId = local.deployment_id + numNodes = length(local.nodes) + n11Ip = local.n11_ip + + adminPrivateKey = tls_private_key.ssh_key.private_key_pem + hostAddrs = local.node_ips_space_sep + hostExternalAddrs = local.node_ips_space_sep + dbPasswordB64 = base64encode(local.db_password_final) + adminUiPasswordB64 = base64encode(local.adminui_password_final) + tlsKey = tls_private_key.tls_key.private_key_pem + tlsCa = tls_self_signed_cert.tls_ca_cert.cert_pem + tlsCert = tls_locally_signed_cert.tls_cert.cert_pem + + # Optional infrastructure-specific hook scripts. + preInstall = { + # preInstall hooks run on *all* nodes + root = { + scripts = [] + } + user = { + scripts = [] + } + } + postInstall = { + # postInstall hooks run on the *access node (n11) only* + scripts = [] + } + + # Cloud-provider specific values needed by optional infra hooks. + hetzner = { + location = var.location + archive = { + enabled = var.s3_archive_enabled + } + } + } + + # Per-node metadata written to disk (separate from infrastructure.json for clarity). + node_payload_by_name = { + for n in local.nodes : n.name => { + name = n.name + privateIp = n.ip + myId = n.name + + # Exasol always uses the same final disk alias across providers. + # The udev match clause identifies the data disk so prepareExasol.sh can + # create a persistent udev rule with the /dev/exasol_data_01 alias. + # On Hetzner, volumes are attached as SCSI devices with a serial matching the volume ID. + hostDatadisk = "/dev/exasol_data_01" + hostDatadiskMatch = "ENV{ID_SERIAL_SHORT}==\"${try(hcloud_volume.data_disks[n.name].id, "")}\"" + } + } +} + +data "cloudinit_config" "cloud_config" { + for_each = { for node in local.nodes : node.name => node } + gzip = true + base64_encode = true + + # Infrastructure-specific cloud-config runs first (05- prefix). + # Used for provider-specific setup like creating the 'ubuntu' user on Hetzner. + dynamic "part" { + for_each = local.infrastructure_cloudconf_files + + content { + content_type = "text/cloud-config" + filename = "05-infra-cloudconf-${part.value}" + content = templatefile("${local.infrastructure_cloudconf_dir}/${part.value}", { + admin_ssh_public_key = tls_private_key.ssh_key.public_key_openssh + }) + } + } + + dynamic "part" { + for_each = local.installation_cloudconf_files + + content { + content_type = "text/cloud-config" + # Numeric prefix makes ordering/precedence explicit when debugging multipart user-data. + filename = "10-cloudconf-${part.value}" + content = file("${local.installation_cloudconf_dir}/${part.value}") + } + } + + part { + content_type = "text/cloud-config" + # Keep this last so it can intentionally override earlier preset cloud-config values. + filename = "99-write-files.yaml" + content = yamlencode({ + write_files = concat( + [ + { + path = local.infrastructure_json_target_path + permissions = "0644" + content = jsonencode(local.infrastructure_payload) + }, + { + path = local.node_json_target_path + permissions = "0644" + content = jsonencode(local.node_payload_by_name[each.key]) + } + ], + [ + for f in local.installation_node_files : { + path = f.dest_path + permissions = f.permissions + content = file(f.src_path) + } + ], + [ + for f in local.infrastructure_node_files : { + path = f.dest_path + permissions = f.permissions + content = file(f.src_path) + } + ] + ) + }) + } +} diff --git a/assets/infrastructure/hetzner/files/.gitkeep b/assets/infrastructure/hetzner/files/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/assets/infrastructure/hetzner/infrastructure.yaml b/assets/infrastructure/hetzner/infrastructure.yaml new file mode 100644 index 00000000..6fd36c88 --- /dev/null +++ b/assets/infrastructure/hetzner/infrastructure.yaml @@ -0,0 +1,9 @@ +name: Exasol on Hetzner Cloud with Ubuntu +description: Provisioning on Hetzner Cloud. Ubuntu, block storage data volume, public IPs. + +tofu: + variablesFile: variables_public.tf + varsOutputFile: vars.tfvars + +powerControl: + provider: hetzner diff --git a/assets/infrastructure/hetzner/main.tf b/assets/infrastructure/hetzner/main.tf new file mode 100644 index 00000000..b378e87d --- /dev/null +++ b/assets/infrastructure/hetzner/main.tf @@ -0,0 +1,79 @@ +locals { + deployment_id = "exasol-${var.deployment_id}" + + common_labels = { + managed_by = "opentofu" + project = "exasol-personal" + deployment_id = local.deployment_id + } + + # Node configuration + node_start_num = 11 + ip_start_octet = 20 # IPs start at .20 + nodes = [ + for i in range(var.cluster_size) : { + name = "n${local.node_start_num + i}" + ip = "172.16.0.${local.ip_start_octet + i}" + } + ] + + # Final passwords: prefer user-provided over generated + db_password_final = var.db_password != "" ? var.db_password : random_password.db.result + adminui_password_final = var.adminui_password != "" ? var.adminui_password : random_password.adminui.result +} + +# Private network for inter-node communication +resource "hcloud_network" "cluster" { + name = "${local.deployment_id}-network" + ip_range = var.network_ip_range + labels = local.common_labels +} + +resource "hcloud_network_subnet" "cluster" { + network_id = hcloud_network.cluster.id + type = "cloud" + network_zone = "eu-central" + ip_range = var.subnet_ip_range +} + +# Servers +resource "hcloud_server" "nodes" { + for_each = { for node in local.nodes : node.name => node } + + name = "${local.deployment_id}-${each.key}" + server_type = var.server_type + location = var.location + image = var.os_image + ssh_keys = [hcloud_ssh_key.instance_key.id] + labels = merge(local.common_labels, { node = each.key }) + + user_data = data.cloudinit_config.cloud_config[each.key].rendered + + firewall_ids = [hcloud_firewall.exasol_instance.id] + + network { + network_id = hcloud_network.cluster.id + ip = each.value.ip + } + + depends_on = [hcloud_network_subnet.cluster] +} + +# Data volumes +resource "hcloud_volume" "data_disks" { + for_each = { for node in local.nodes : node.name => node } + + name = "${local.deployment_id}-${each.key}-data" + size = var.data_volume_size + location = var.location + format = null + labels = merge(local.common_labels, { node = each.key, role = "data" }) +} + +resource "hcloud_volume_attachment" "data_disks" { + for_each = { for node in local.nodes : node.name => node } + + volume_id = hcloud_volume.data_disks[each.key].id + server_id = hcloud_server.nodes[each.key].id + automount = false +} diff --git a/assets/infrastructure/hetzner/outputs.tf b/assets/infrastructure/hetzner/outputs.tf new file mode 100644 index 00000000..5b673bb1 --- /dev/null +++ b/assets/infrastructure/hetzner/outputs.tf @@ -0,0 +1,88 @@ +locals { + infrastructure_artifact_dir = abspath(var.infrastructure_artifact_dir) + installation_preset_dir = abspath(var.installation_preset_dir) + key_file_name = "node_access.pem" + key_file_relative_path = local.key_file_name + key_file_path = "${local.infrastructure_artifact_dir}/${local.key_file_name}" + + deployment_info = { + deploymentId = local.deployment_id + region = var.location + clusterSize = var.cluster_size + clusterState = "running" + instanceType = var.server_type + vpcId = hcloud_network.cluster.id + subnetId = hcloud_network_subnet.cluster.id + nodes = { + for k, node_config in local.nodes : + node_config.name => { + publicIp = hcloud_server.nodes[node_config.name].ipv4_address + privateIp = node_config.ip + instanceId = hcloud_server.nodes[node_config.name].id + dnsName = hcloud_server.nodes[node_config.name].ipv4_address + ssh = { + username = "root" + keyName = hcloud_ssh_key.instance_key.name + keyFile = local.key_file_relative_path + port = "22" + command = "ssh -i ${local.key_file_relative_path} root@${hcloud_server.nodes[node_config.name].ipv4_address} -p 22" + } + tlsCert = tls_locally_signed_cert.tls_cert.cert_pem + database = { + dbPort = "8563" + uiPort = "8443" + url = "https://${hcloud_server.nodes[node_config.name].ipv4_address}:8443" + } + } + } + } + deployment_secrets = { + adminUiUsername = "admin" + adminUiPassword = local.adminui_password_final + dbUsername = "sys" + dbPassword = local.db_password_final + } +} + +output "deployment_info" { + description = "Deployment information for all nodes" + value = local.deployment_info +} + +# Sensitive outputs +output "deployment_secrets" { + description = "Deployment secrets" + value = local.deployment_secrets + sensitive = true +} + +output "ssh_private_key" { + description = "The private key for SSH access" + value = tls_private_key.ssh_key.private_key_pem + sensitive = true +} + +output "region" { + description = "The Hetzner Cloud location where resources are deployed" + value = var.location +} + +# Save deployment_info to a local JSON file (for consumption by the Exasol Launcher or other tool) +resource "local_file" "deployment_info" { + filename = "${local.infrastructure_artifact_dir}/deployment.json" + content = jsonencode(local.deployment_info) +} + +# Save deployment_secrets to a local JSON file (for secure storage) +resource "local_file" "deployment_secrets" { + filename = "${local.infrastructure_artifact_dir}/secrets.json" + content = jsonencode(local.deployment_secrets) + file_permission = "0600" +} + +# Save the SSH private key to a local PEM file (for SSH access to the nodes) +resource "local_file" "private_key" { + filename = local.key_file_path + content = tls_private_key.ssh_key.private_key_pem + file_permission = "0600" # Required for SSH key files +} diff --git a/assets/infrastructure/hetzner/providers.tf b/assets/infrastructure/hetzner/providers.tf new file mode 100644 index 00000000..472c3ce7 --- /dev/null +++ b/assets/infrastructure/hetzner/providers.tf @@ -0,0 +1,3 @@ +provider "hcloud" { + # Authentication via HCLOUD_TOKEN environment variable +} diff --git a/assets/infrastructure/hetzner/secrets.tf b/assets/infrastructure/hetzner/secrets.tf new file mode 100644 index 00000000..e3e64c73 --- /dev/null +++ b/assets/infrastructure/hetzner/secrets.tf @@ -0,0 +1,29 @@ +resource "random_password" "db" { + length = 20 + special = true + override_special = "!#$%&*-_=+?" + min_upper = 2 + min_lower = 2 + min_numeric = 2 + min_special = 2 +} + +resource "random_password" "adminui" { + length = 20 + special = true + override_special = "!#$%&*-_=+?" + min_upper = 2 + min_lower = 2 + min_numeric = 2 + min_special = 2 +} + +resource "tls_private_key" "tls_ca_key" { + algorithm = "ECDSA" + ecdsa_curve = "P256" +} + +resource "tls_private_key" "tls_key" { + algorithm = "ECDSA" + ecdsa_curve = "P256" +} diff --git a/assets/infrastructure/hetzner/security.tf b/assets/infrastructure/hetzner/security.tf new file mode 100644 index 00000000..85e3d443 --- /dev/null +++ b/assets/infrastructure/hetzner/security.tf @@ -0,0 +1,102 @@ +# Firewall for the Exasol instances +resource "hcloud_firewall" "exasol_instance" { + name = "${local.deployment_id}-fw" + labels = local.common_labels + + # External access rules + rule { + direction = "in" + protocol = "tcp" + port = "22" + source_ips = [var.allowed_cidr] + description = "SSH access" + } + + rule { + direction = "in" + protocol = "tcp" + port = "2581" + source_ips = [var.allowed_cidr] + description = "Default bucketfs" + } + + rule { + direction = "in" + protocol = "tcp" + port = "8443" + source_ips = [var.allowed_cidr] + description = "Exasol Admin UI" + } + + rule { + direction = "in" + protocol = "tcp" + port = "8563" + source_ips = [var.allowed_cidr] + description = "Default Exasol database connection" + } + + rule { + direction = "in" + protocol = "tcp" + port = "20002" + source_ips = [var.allowed_cidr] + description = "Exasol container ssh" + } + + rule { + direction = "in" + protocol = "tcp" + port = "20003" + source_ips = [var.allowed_cidr] + description = "Exasol confd API" + } + + # Allow all internal cluster traffic via private network range + rule { + direction = "in" + protocol = "tcp" + port = "any" + source_ips = [var.network_ip_range] + description = "All internal TCP traffic between cluster nodes" + } + + rule { + direction = "in" + protocol = "udp" + port = "any" + source_ips = [var.network_ip_range] + description = "All internal UDP traffic between cluster nodes" + } + + rule { + direction = "in" + protocol = "icmp" + source_ips = [var.network_ip_range] + description = "ICMP ping between cluster nodes" + } + + # Allow all outbound traffic + rule { + direction = "out" + protocol = "tcp" + port = "any" + destination_ips = ["0.0.0.0/0", "::/0"] + description = "Allow all outbound TCP" + } + + rule { + direction = "out" + protocol = "udp" + port = "any" + destination_ips = ["0.0.0.0/0", "::/0"] + description = "Allow all outbound UDP" + } + + rule { + direction = "out" + protocol = "icmp" + destination_ips = ["0.0.0.0/0", "::/0"] + description = "Allow all outbound ICMP" + } +} diff --git a/assets/infrastructure/hetzner/ssh.tf b/assets/infrastructure/hetzner/ssh.tf new file mode 100644 index 00000000..b2ea16ea --- /dev/null +++ b/assets/infrastructure/hetzner/ssh.tf @@ -0,0 +1,11 @@ +resource "tls_private_key" "ssh_key" { + algorithm = "RSA" + rsa_bits = 4096 +} + +resource "hcloud_ssh_key" "instance_key" { + name = "${local.deployment_id}-key" + public_key = tls_private_key.ssh_key.public_key_openssh + + labels = local.common_labels +} diff --git a/assets/infrastructure/hetzner/tls.tf b/assets/infrastructure/hetzner/tls.tf new file mode 100644 index 00000000..a6d71288 --- /dev/null +++ b/assets/infrastructure/hetzner/tls.tf @@ -0,0 +1,44 @@ +resource "tls_self_signed_cert" "tls_ca_cert" { + private_key_pem = tls_private_key.tls_ca_key.private_key_pem + + subject { + common_name = "exacluster.local" + } + + validity_period_hours = 24 * 365 * 10 + + is_ca_certificate = true + set_subject_key_id = true + + allowed_uses = [ + "cert_signing", + "key_encipherment", + ] +} + +resource "tls_cert_request" "tls_request" { + private_key_pem = tls_private_key.tls_key.private_key_pem + + subject { + common_name = "exacluster.local" + } + + dns_names = [ + "*.exacluster.local", + "exacluster.local", + ] +} + +resource "tls_locally_signed_cert" "tls_cert" { + cert_request_pem = tls_cert_request.tls_request.cert_request_pem + ca_private_key_pem = tls_private_key.tls_ca_key.private_key_pem + ca_cert_pem = tls_self_signed_cert.tls_ca_cert.cert_pem + + validity_period_hours = 24 * 365 * 10 + + allowed_uses = [ + "key_encipherment", + "digital_signature", + "server_auth", + ] +} diff --git a/assets/infrastructure/hetzner/variables_internal.tf b/assets/infrastructure/hetzner/variables_internal.tf new file mode 100644 index 00000000..8b2e3bd4 --- /dev/null +++ b/assets/infrastructure/hetzner/variables_internal.tf @@ -0,0 +1,35 @@ +# Variables required for terraform/tofu integration of an infrastructure preset with the Exasol Personal launcher. +# Ensure all deployment artifacts which required for Exasol Personal launcher integration are written to this directory +# These are: +# deployment.json -- meta information about the deployed infrastructure +# secrets.json -- secrets to access the database services +# node_access.pem -- a ssh key to access all deployed hosts +# +# A change here might break the interaction with the Exasol Personal launcher CLI +# The Exasol Personal launcher expected some files to be present at the root of the deployment directory + +variable "deployment_id" { + description = "Launcher-generated deployment identifier" + type = string + # The Exasol Personal Launcher injects an randomly generated identifier like "a3e712fa" +} + +// tflint-ignore: terraform_unused_declarations +variable "cluster_identity" { + description = "Launcher-generated opaque cluster identity token" + type = string + default = "" + # The Exasol Personal Launcher injects a combination of deployment id and selected preset names +} + +variable "infrastructure_artifact_dir" { + description = "Directory where deployment artifacts for the Exasol Personal launcher (JSON, PEM) are written; launcher supplies a path relative to the extracted infrastructure preset directory" + type = string + default = ".." +} + +variable "installation_preset_dir" { + description = "Directory where installation preset can be found; launcher supplies a path relative to the extracted infrastructure preset directory" + type = string + default = ".." +} diff --git a/assets/infrastructure/hetzner/variables_private.tf b/assets/infrastructure/hetzner/variables_private.tf new file mode 100644 index 00000000..9ff6574e --- /dev/null +++ b/assets/infrastructure/hetzner/variables_private.tf @@ -0,0 +1,38 @@ +# Private variables we don't expose to the Exasol Personal launcher CLI +# If you want to make them public on Exasol Personal launcher CLI, move them to variables_public.tf +# Some of those values are just here for visibility so their values aren't hidden deep in some source file + +variable "network_ip_range" { + description = "IP range for the Hetzner Cloud private network (CIDR notation)" + type = string + default = "172.16.0.0/22" +} + +variable "subnet_ip_range" { + description = "IP range for the subnet within the private network (CIDR notation)" + type = string + default = "172.16.0.0/24" +} + +variable "allowed_cidr" { + description = "CIDR block allowed to access the instance (e.g., your IP address)" + type = string + default = "0.0.0.0/0" # Warning: Should be restricted in production +} + +variable "os_image" { + description = "Hetzner Cloud OS image to use for servers" + type = string + default = "ubuntu-22.04" +} + +variable "power_state" { + description = "Target power state for instances. Accepted for API compatibility; power control is handled externally via the Hetzner Cloud API." + type = string + default = "running" + validation { + condition = contains(["running", "stopped"], var.power_state) + error_message = "Allowed values are: running, stopped" + } +} + diff --git a/assets/infrastructure/hetzner/variables_public.tf b/assets/infrastructure/hetzner/variables_public.tf new file mode 100644 index 00000000..b4b3a7e9 --- /dev/null +++ b/assets/infrastructure/hetzner/variables_public.tf @@ -0,0 +1,46 @@ +# Public variables exposed to the Exasol Personal launcher as command line interface flags +# They are shown in the order in which they are declared here + +variable "location" { + description = "Hetzner Cloud location to deploy into (e.g. fsn1, nbg1, hel1, ash, hil)" + type = string + default = "fsn1" +} + +variable "cluster_size" { + description = "Number of nodes in the cluster (use 1 for single-node deployment)" + type = number + default = 1 +} + +variable "server_type" { + description = "Hetzner Cloud server type (e.g. ccx23, ccx33, ccx43, cpx32, cx33)" + type = string + default = "ccx33" +} + +variable "data_volume_size" { + description = "Size in GB for the database data volume" + type = number + default = 100 +} + +variable "db_password" { + description = "Optional database password. If empty, a random password is generated" + type = string + default = "" + sensitive = true +} + +variable "adminui_password" { + description = "Optional Admin UI password. If empty, a random password is generated" + type = string + default = "" + sensitive = true +} + +variable "s3_archive_enabled" { + description = "Enable remote archive/backup integration using Hetzner Object Storage (S3-compatible, EU locations only)" + type = bool + default = false +} diff --git a/assets/infrastructure/hetzner/versions.tf b/assets/infrastructure/hetzner/versions.tf new file mode 100644 index 00000000..aa4d10a7 --- /dev/null +++ b/assets/infrastructure/hetzner/versions.tf @@ -0,0 +1,25 @@ +terraform { + required_version = ">= 1.10.0" + required_providers { + local = { + source = "hashicorp/local" + version = "~> 2.6" + } + tls = { + source = "hashicorp/tls" + version = "~> 4.1" + } + random = { + source = "hashicorp/random" + version = "~> 3.7" + } + cloudinit = { + source = "hashicorp/cloudinit" + version = "~> 2.3" + } + hcloud = { + source = "hetznercloud/hcloud" + version = "~> 1.58" + } + } +} diff --git a/go.mod b/go.mod index 0a4bfe3f..889bad14 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,9 @@ require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect + github.com/Masterminds/sprig/v3 v3.2.2 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/alfatraining/structtag v1.0.0 // indirect github.com/apparentlymart/go-cidr v1.1.0 // indirect @@ -78,6 +80,7 @@ require ( github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect github.com/dave/dst v0.27.3 // indirect + github.com/dave/jennifer v1.7.1 // indirect github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect @@ -131,16 +134,23 @@ require ( github.com/hashicorp/terraform-registry-address v0.4.0 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.1.2 // indirect + github.com/hetznercloud/hcloud-go/v2 v2.40.0 // indirect + github.com/hexdigest/gowrap v1.4.3 // indirect + github.com/huandu/xstrings v1.3.2 // indirect + github.com/imdario/mergo v0.3.12 // indirect github.com/in-toto/attestation v1.1.2 // indirect github.com/in-toto/in-toto-golang v0.9.0 // indirect github.com/jessevdk/go-flags v1.6.1 // indirect + github.com/jmattheis/goverter v1.9.4 // indirect github.com/jstemmer/go-junit-report v1.0.0 // indirect github.com/klauspost/compress v1.18.5 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/manuelarte/embeddedstructfieldcheck v0.3.0 // indirect github.com/manuelarte/funcorder v0.5.0 // indirect github.com/maxbrunsfeld/counterfeiter/v6 v6.12.0 // indirect + github.com/mitchellh/copystructure v1.1.2 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/reflectwalk v1.0.1 // indirect github.com/muesli/termenv v0.16.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/oklog/run v1.1.0 // indirect @@ -156,6 +166,7 @@ require ( github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/secure-systems-lab/go-securesystemslib v0.10.0 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect + github.com/shopspring/decimal v1.2.0 // indirect github.com/sigstore/protobuf-specs v0.5.0 // indirect github.com/sigstore/rekor v1.5.0 // indirect github.com/sigstore/rekor-tiles/v2 v2.0.1 // indirect @@ -173,6 +184,7 @@ require ( github.com/transparency-dev/formats v0.0.0-20251017110053-404c0d5b696c // indirect github.com/transparency-dev/merkle v0.0.2 // indirect github.com/ulikunitz/xz v0.5.15 // indirect + github.com/vburenin/ifacemaker v1.3.1-0.20260417025851-7bffe3f42628 // indirect github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect @@ -192,7 +204,7 @@ require ( go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/time v0.15.0 // indirect - golang.org/x/tools v0.43.0 // indirect + golang.org/x/tools v0.44.0 // indirect google.golang.org/api v0.272.0 // indirect google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5 // indirect @@ -364,7 +376,7 @@ require ( go.uber.org/zap v1.27.1 // indirect golang.org/x/crypto v0.50.0 golang.org/x/exp/typeparams v0.0.0-20250620022241-b7579e27df2b // indirect - golang.org/x/mod v0.34.0 // indirect + golang.org/x/mod v0.35.0 // indirect golang.org/x/net v0.53.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.43.0 // indirect diff --git a/go.sum b/go.sum index 37747f7c..7167c166 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,13 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 h1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc= +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/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= +github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4= github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= @@ -429,6 +434,7 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/trillian v1.7.2 h1:EPBxc4YWY4Ak8tcuhyFleY+zYlbCDCa4Sn24e1Ka8Js= github.com/google/trillian v1.7.2/go.mod h1:mfQJW4qRH6/ilABtPYNBerVJAJ/upxHLX81zxNQw05s= +github.com/google/uuid v1.1.1/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/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8= @@ -504,10 +510,20 @@ github.com/hashicorp/vault/api v1.22.0 h1:+HYFquE35/B74fHoIeXlZIP2YADVboaPjaSicH github.com/hashicorp/vault/api v1.22.0/go.mod h1:IUZA2cDvr4Ok3+NtK2Oq/r+lJeXkeCrHRmqdyWfpmGM= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= +github.com/hetznercloud/hcloud-go/v2 v2.40.0 h1:fuP7khfiDQAIXdKyQq7f3LnnOjyZg0PXTafXjUKkqIA= +github.com/hetznercloud/hcloud-go/v2 v2.40.0/go.mod h1:ANz38eerXjPv00dm9dckKhttOGtYeeGmjjvwL5e6c5E= +github.com/hexdigest/gowrap v1.4.3 h1:m+t8aj1pUiFQbEiE8QJg2xdYVH5DAMluLgZ9P/qEF0k= +github.com/hexdigest/gowrap v1.4.3/go.mod h1:XWL8oQW2H3fX5ll8oT3Fduh4mt2H3cUAGQHQLMUbmG4= 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/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM= github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= +github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/in-toto/attestation v1.1.2 h1:MBFn6lsMq6dptQZJBhalXTcWMb/aJy3V+GX3VYj/V1E= github.com/in-toto/attestation v1.1.2/go.mod h1:gYFddHMZj3DiQ0b62ltNi1Vj5rC879bTmBbrv9CRHpM= github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU= @@ -536,6 +552,8 @@ github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjz github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8= github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU= +github.com/jmattheis/goverter v1.9.4 h1:xdBN4TL/ihGkm1V1TqQ7gmTSVA3G4TI3/C/iK26nJqs= +github.com/jmattheis/goverter v1.9.4/go.mod h1:1n3q6zf7j58tXcRWHbLFxK2Jk8WQVzr0d3nuaCcRqeg= github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 h1:liMMTbpW34dhU4az1GN0pTPADwNmvoRSeoZ6PItiqnY= github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jstemmer/go-junit-report v1.0.0 h1:8X1gzZpR+nVQLAht+L/foqOeX2l9DTZoaIPbEQHxsds= @@ -611,12 +629,18 @@ github.com/maxbrunsfeld/counterfeiter/v6 v6.12.0 h1:aOeI7xAOVdK+R6xbVsZuU9HmCZYm github.com/maxbrunsfeld/counterfeiter/v6 v6.12.0/go.mod h1:0hZWbtfeCYUQeAQdPLUzETiBhUSns7O6LDj9vH88xKA= github.com/mgechev/revive v1.11.0 h1:b/gLLpBE427o+Xmd8G58gSA+KtBwxWinH/A565Awh0w= github.com/mgechev/revive v1.11.0/go.mod h1:tI0oLF/2uj+InHCBLrrqfTKfjtFTBCFFfG05auyzgdw= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.1.2 h1:Th2TIvG1+6ma3e/0/bopBKohOTY7s4dA8V2q4EUcBJ0= +github.com/mitchellh/copystructure v1.1.2/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= 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/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= +github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= @@ -734,6 +758,8 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= +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/protobuf-specs v0.5.0 h1:F8YTI65xOHw70NrvPwJ5PhAzsvTnuJMGLkA4FIkofAY= @@ -772,6 +798,7 @@ github.com/sourcegraph/jsonrpc2 v0.2.1 h1:2GtljixMQYUYCmIg7W9aF2dFmniq/mOr2T9tFR github.com/sourcegraph/jsonrpc2 v0.2.1/go.mod h1:ZafdZgk/axhT1cvZAPOhw+95nz2I/Ra5qMlU4gTRwIo= 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.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= @@ -791,8 +818,10 @@ github.com/stbenjam/no-sprintf-host-port v0.2.0/go.mod h1:eL0bQ9PasS0hsyTyfTjjG+ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= +github.com/stretchr/testify v1.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.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= @@ -850,6 +879,8 @@ github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYR github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU= github.com/uudashr/iface v1.4.1 h1:J16Xl1wyNX9ofhpHmQ9h9gk5rnv2A6lX/2+APLTo0zU= github.com/uudashr/iface v1.4.1/go.mod h1:pbeBPlbuU2qkNDn0mmfrxP2X+wjPMIQAy+r1MBXSXtg= +github.com/vburenin/ifacemaker v1.3.1-0.20260417025851-7bffe3f42628 h1:YlB3O4MVoXwHxkU4X75Vs6Ry1M1IGye+j4RfG8tCllE= +github.com/vburenin/ifacemaker v1.3.1-0.20260417025851-7bffe3f42628/go.mod h1:VATsDjLUQfeo2Thkn/OuAuQX5xpGz//yhj7BwF1jAJ0= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= @@ -935,6 +966,7 @@ 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/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= @@ -958,6 +990,8 @@ golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/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= @@ -1055,6 +1089,8 @@ golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58 golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= @@ -1084,6 +1120,7 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 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/yaml.v2 v2.2.2/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/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/deploy/deploymentControl.go b/internal/deploy/deploymentControl.go index 3d308c47..e6dec093 100644 --- a/internal/deploy/deploymentControl.go +++ b/internal/deploy/deploymentControl.go @@ -5,6 +5,7 @@ package deploy import ( "context" + "fmt" "io" "log/slog" "os" @@ -102,6 +103,13 @@ func Start( logBuffer := task_runner.NewLogBuffer() + if err = doPowerControl(ctx, deployment, powerStart); err != nil { + logBuffer.ReplayLogMessages(ctx) + slog.Error("failed to start VMs") + + return err + } + err = applyAction( ctx, deployment, @@ -293,7 +301,14 @@ func Stop(ctx context.Context, deployment config.DeploymentDir, verbose bool) er if err != nil { logBuffer.ReplayLogMessages(ctx) slog.Error("failed to stop the deployment") - // should this be a failure state that requires destroy? + + return err + } + + if err = doPowerControl(ctx, deployment, powerStop); err != nil { + logBuffer.ReplayLogMessages(ctx) + slog.Error("failed to stop VMs") + return err } @@ -311,6 +326,51 @@ func Stop(ctx context.Context, deployment config.DeploymentDir, verbose bool) er }) } +type powerAction int + +const ( + powerStop powerAction = iota + powerStart powerAction = iota +) + +func doPowerControl( + ctx context.Context, + deployment config.DeploymentDir, + action powerAction, +) error { + manifest, err := config.ReadInfrastructureManifest(deployment) + if err != nil { + return fmt.Errorf("failed to read infrastructure manifest for power control: %w", err) + } + + if manifest.PowerControl == nil { + return nil + } + + nodeDetails, err := config.ReadNodeDetails(deployment) + if err != nil { + return fmt.Errorf("failed to read node details for power control: %w", err) + } + + instanceIDs := make([]string, 0, len(nodeDetails.Nodes)) + for _, node := range nodeDetails.Nodes { + if node.InstanceId != "" { + instanceIDs = append(instanceIDs, node.InstanceId) + } + } + + switch manifest.PowerControl.Provider { + case "hetzner": + if action == powerStop { + return hetznerStopServers(ctx, instanceIDs) + } + + return hetznerStartServers(ctx, instanceIDs) + default: + return fmt.Errorf("unknown power control provider: %q", manifest.PowerControl.Provider) + } +} + func applyAction( ctx context.Context, deployment config.DeploymentDir, diff --git a/internal/deploy/hetzner_power.go b/internal/deploy/hetzner_power.go new file mode 100644 index 00000000..a12889d0 --- /dev/null +++ b/internal/deploy/hetzner_power.go @@ -0,0 +1,106 @@ +// Copyright 2026 Exasol AG +// SPDX-License-Identifier: MIT + +package deploy + +import ( + "context" + "fmt" + "log/slog" + "os" + "strconv" + "time" + + "github.com/hetznercloud/hcloud-go/v2/hcloud" +) + +const hetznerActionTimeoutMinutes = 5 + +const hcloudTokenEnvVar = "HCLOUD_TOKEN" + +func hetznerStopServers(ctx context.Context, instanceIDs []string) error { + token := os.Getenv(hcloudTokenEnvVar) + if token == "" { + return fmt.Errorf("%s environment variable is not set", hcloudTokenEnvVar) + } + + client := hcloud.NewClient(hcloud.WithToken(token)) + + for _, idStr := range instanceIDs { + serverID, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + return fmt.Errorf("invalid server ID %q: %w", idStr, err) + } + + server := &hcloud.Server{ID: serverID} + + slog.Info("shutting down Hetzner server", "id", serverID) + result, _, err := client.Server.Shutdown(ctx, server) + if err != nil { + slog.Warn("graceful shutdown failed, forcing poweroff", "id", serverID, "error", err) + result, _, err = client.Server.Poweroff(ctx, server) + if err != nil { + return fmt.Errorf("failed to power off server %d: %w", serverID, err) + } + } + + if err := waitForAction(ctx, client, result); err != nil { + return fmt.Errorf("server %d stop action failed: %w", serverID, err) + } + + slog.Info("Hetzner server stopped", "id", serverID) + } + + return nil +} + +func hetznerStartServers(ctx context.Context, instanceIDs []string) error { + token := os.Getenv(hcloudTokenEnvVar) + if token == "" { + return fmt.Errorf("%s environment variable is not set", hcloudTokenEnvVar) + } + + client := hcloud.NewClient(hcloud.WithToken(token)) + + for _, idStr := range instanceIDs { + serverID, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + return fmt.Errorf("invalid server ID %q: %w", idStr, err) + } + + server := &hcloud.Server{ID: serverID} + + slog.Info("powering on Hetzner server", "id", serverID) + result, _, err := client.Server.Poweron(ctx, server) + if err != nil { + return fmt.Errorf("failed to power on server %d: %w", serverID, err) + } + + if err := waitForAction(ctx, client, result); err != nil { + return fmt.Errorf("server %d start action failed: %w", serverID, err) + } + + slog.Info("Hetzner server started", "id", serverID) + } + + return nil +} + +func waitForAction(ctx context.Context, client *hcloud.Client, action *hcloud.Action) error { + if action == nil { + return nil + } + + timeoutCtx, cancel := context.WithTimeout(ctx, hetznerActionTimeoutMinutes*time.Minute) + defer cancel() + + err := client.Action.WaitForFunc(timeoutCtx, func(update *hcloud.Action) error { + if update.Status == hcloud.ActionStatusError { + return fmt.Errorf("action %d failed: %s", update.ID, update.ErrorMessage) + } + + return nil + }, action) + + return err +} diff --git a/internal/presets/infrastructure_manifest.go b/internal/presets/infrastructure_manifest.go index 4fc275b5..aefdeccf 100644 --- a/internal/presets/infrastructure_manifest.go +++ b/internal/presets/infrastructure_manifest.go @@ -20,11 +20,17 @@ type InfrastructureTofu struct { VarsOutputFile string `yaml:"varsOutputFile"` } +// InfrastructurePowerControl captures optional power control configuration from the manifest. +type InfrastructurePowerControl struct { + Provider string `yaml:"provider"` +} + // InfrastructureManifest represents the infrastructure metadata and optional tofu configuration. type InfrastructureManifest struct { - Name string `yaml:"name"` - Description string `yaml:"description"` - Tofu *InfrastructureTofu `yaml:"tofu,omitempty"` + Name string `yaml:"name"` + Description string `yaml:"description"` + Tofu *InfrastructureTofu `yaml:"tofu,omitempty"` + PowerControl *InfrastructurePowerControl `yaml:"powerControl,omitempty"` } var ( diff --git a/tests/tests/conftest.py b/tests/tests/conftest.py index 9efcfdfe..a5d779af 100644 --- a/tests/tests/conftest.py +++ b/tests/tests/conftest.py @@ -19,7 +19,7 @@ def pytest_addoption(parser: pytest.Parser) -> None: required=False, action="store", default="aws", - choices=["aws", "azure", "exoscale"], + choices=["aws", "azure", "exoscale", "hetzner"], help="Infrastructure preset to use for deployment tests", ) diff --git a/tools/cleanup/cmd/cleanup/cleanup.go b/tools/cleanup/cmd/cleanup/cleanup.go index d553a9e1..296ac718 100644 --- a/tools/cleanup/cmd/cleanup/cleanup.go +++ b/tools/cleanup/cmd/cleanup/cleanup.go @@ -6,15 +6,18 @@ package main import ( "github.com/exasol/exasol-personal/tools/cleanup/internal/aws" "github.com/exasol/exasol-personal/tools/cleanup/internal/exoscale" + "github.com/exasol/exasol-personal/tools/cleanup/internal/hetzner" "github.com/spf13/cobra" ) var cleanupOpts = struct { - AWSRegion string - ExoscaleZone string - Verbose bool - AWS bool - Exoscale bool + AWSRegion string + ExoscaleZone string + HetznerLocation string + Verbose bool + AWS bool + Exoscale bool + Hetzner bool }{} // getSelectedProviders returns a list of provider names that should be used. @@ -29,10 +32,13 @@ func getSelectedProviders() []string { if cleanupOpts.Exoscale { selected = append(selected, exoscale.ProviderName) } + if cleanupOpts.Hetzner { + selected = append(selected, hetzner.ProviderName) + } // If none selected, use all available providers if len(selected) == 0 { - return []string{aws.ProviderName, exoscale.ProviderName} + return []string{aws.ProviderName, exoscale.ProviderName, hetzner.ProviderName} } return selected @@ -67,6 +73,12 @@ func registerRootFlags(cmd *cobra.Command) { cmd.PersistentFlags(). BoolVar(&cleanupOpts.Exoscale, "exoscale", false, "Use Exoscale provider") + cmd.PersistentFlags(). + StringVar(&cleanupOpts.HetznerLocation, "hetzner-location", "fsn1", + "Hetzner Cloud location containing the deployment resources (default: fsn1)") + cmd.PersistentFlags(). + BoolVar(&cleanupOpts.Hetzner, "hetzner", false, + "Use Hetzner provider") } // nolint: gochecknoinits diff --git a/tools/cleanup/cmd/cleanup/cleanup_discover.go b/tools/cleanup/cmd/cleanup/cleanup_discover.go index 21d810df..aff10d37 100644 --- a/tools/cleanup/cmd/cleanup/cleanup_discover.go +++ b/tools/cleanup/cmd/cleanup/cleanup_discover.go @@ -15,6 +15,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/exasol/exasol-personal/tools/cleanup/internal/aws" "github.com/exasol/exasol-personal/tools/cleanup/internal/exoscale" + "github.com/exasol/exasol-personal/tools/cleanup/internal/hetzner" "github.com/exasol/exasol-personal/tools/cleanup/internal/shared" "github.com/spf13/cobra" ) @@ -74,6 +75,15 @@ var cleanupDiscoverCmd = &cobra.Command{ exoscale.NewCollector(cleanupOpts.ExoscaleZone, exoOwnerFilter, cleanupDiscoverOpts.Legacy)) } + // Hetzner Collector - use provided owner filter or empty for all + if shouldUseProvider(hetzner.ProviderName) { + hetznerOwnerFilter := cleanupDiscoverOpts.OwnerFilter + // Hetzner default: empty means all deployments (token is project-scoped) + + collectors = append(collectors, + hetzner.NewCollector(cleanupOpts.HetznerLocation, hetznerOwnerFilter, cleanupDiscoverOpts.Legacy)) + } + // Collect from all providers res, err := shared.CollectAllProviders(cmd.Context(), collectors) if err != nil { diff --git a/tools/cleanup/cmd/cleanup/cleanup_providers.go b/tools/cleanup/cmd/cleanup/cleanup_providers.go index 3f6ff391..97c1fdf8 100644 --- a/tools/cleanup/cmd/cleanup/cleanup_providers.go +++ b/tools/cleanup/cmd/cleanup/cleanup_providers.go @@ -9,6 +9,7 @@ import ( "github.com/exasol/exasol-personal/tools/cleanup/internal/aws" "github.com/exasol/exasol-personal/tools/cleanup/internal/exoscale" + "github.com/exasol/exasol-personal/tools/cleanup/internal/hetzner" "github.com/exasol/exasol-personal/tools/cleanup/internal/shared" "github.com/spf13/cobra" ) @@ -43,6 +44,15 @@ var cleanupProvidersCmd = &cobra.Command{ collectors = append(collectors, exoscale.NewCollector(exoscaleZone, "", false)) } + // Hetzner provider + if shouldUseProvider(hetzner.ProviderName) { + hetznerLocation := cleanupOpts.HetznerLocation + if hetznerLocation == "" { + hetznerLocation = "fsn1" + } + collectors = append(collectors, hetzner.NewCollector(hetznerLocation, "", false)) + } + for _, collector := range collectors { providerName := collector.Name() // Capitalize and pad provider name for alignment @@ -51,6 +61,8 @@ var cleanupProvidersCmd = &cobra.Command{ displayName = "AWS" } else if displayName == "exoscale" { displayName = "Exoscale" + } else if displayName == "hetzner" { + displayName = "Hetzner" } // Pad to 11 characters for alignment diff --git a/tools/cleanup/cmd/cleanup/cleanup_run.go b/tools/cleanup/cmd/cleanup/cleanup_run.go index 12aacff9..3a7645f1 100644 --- a/tools/cleanup/cmd/cleanup/cleanup_run.go +++ b/tools/cleanup/cmd/cleanup/cleanup_run.go @@ -12,6 +12,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/exasol/exasol-personal/tools/cleanup/internal/aws" "github.com/exasol/exasol-personal/tools/cleanup/internal/exoscale" + "github.com/exasol/exasol-personal/tools/cleanup/internal/hetzner" "github.com/exasol/exasol-personal/tools/cleanup/internal/shared" "github.com/spf13/cobra" ) @@ -62,6 +63,12 @@ var cleanupRunCmd = &cobra.Command{ exoscale.NewCollector(cleanupOpts.ExoscaleZone, "", false)) } + // Hetzner Collector + if shouldUseProvider(hetzner.ProviderName) { + collectors = append(collectors, + hetzner.NewCollector(cleanupOpts.HetznerLocation, "", false)) + } + // Find which provider has this deployment collector, err := shared.FindDeployment(cmd.Context(), collectors, deploymentID) if err != nil { diff --git a/tools/cleanup/cmd/cleanup/cleanup_show.go b/tools/cleanup/cmd/cleanup/cleanup_show.go index 4de700a6..81643c1a 100644 --- a/tools/cleanup/cmd/cleanup/cleanup_show.go +++ b/tools/cleanup/cmd/cleanup/cleanup_show.go @@ -12,6 +12,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/exasol/exasol-personal/tools/cleanup/internal/aws" "github.com/exasol/exasol-personal/tools/cleanup/internal/exoscale" + "github.com/exasol/exasol-personal/tools/cleanup/internal/hetzner" "github.com/exasol/exasol-personal/tools/cleanup/internal/shared" "github.com/spf13/cobra" ) @@ -60,6 +61,12 @@ var cleanupShowCmd = &cobra.Command{ exoscale.NewCollector(cleanupOpts.ExoscaleZone, "", false)) } + // Hetzner Collector + if shouldUseProvider(hetzner.ProviderName) { + collectors = append(collectors, + hetzner.NewCollector(cleanupOpts.HetznerLocation, "", false)) + } + // Find which provider has this deployment collector, err := shared.FindDeployment(cmd.Context(), collectors, deploymentID) if err != nil { diff --git a/tools/cleanup/go.mod b/tools/cleanup/go.mod index a3839b1b..04726129 100644 --- a/tools/cleanup/go.mod +++ b/tools/cleanup/go.mod @@ -12,9 +12,10 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssm v1.67.5 github.com/aws/aws-sdk-go-v2/service/sts v1.41.3 github.com/exoscale/egoscale/v3 v3.1.34 + github.com/hetznercloud/hcloud-go/v2 v2.40.0 github.com/lmittmann/tint v1.1.3 github.com/spf13/cobra v1.10.2 - golang.org/x/term v0.41.0 + golang.org/x/term v0.42.0 ) tool github.com/golangci/golangci-lint/v2/cmd/golangci-lint @@ -111,7 +112,6 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/godoc-lint/godoc-lint v0.11.2 // indirect github.com/gofrs/flock v0.13.0 // indirect - github.com/golang/protobuf v1.5.3 // indirect github.com/golangci/asciicheck v0.5.0 // indirect github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect github.com/golangci/go-printf-func-name v0.1.1 // indirect @@ -167,22 +167,22 @@ require ( 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.16 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mgechev/revive v1.15.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moricho/tparallel v0.3.2 // indirect github.com/muesli/termenv v0.16.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // 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.23.0 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.12.1 // indirect - github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.32.1 // indirect - github.com/prometheus/procfs v0.7.3 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.16.1 // indirect github.com/quasilyte/go-ruleguard v0.4.5 // indirect github.com/quasilyte/go-ruleguard/dsl v0.3.23 // indirect github.com/quasilyte/gogrep v0.5.0 // indirect @@ -235,15 +235,17 @@ require ( go.augendre.info/fatcontext v0.9.0 // indirect go.uber.org/multierr v1.10.0 // indirect go.uber.org/zap v1.27.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.48.0 // indirect + golang.org/x/crypto v0.50.0 // indirect golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 // indirect - golang.org/x/mod v0.34.0 // indirect + golang.org/x/mod v0.35.0 // indirect + golang.org/x/net v0.53.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.42.0 // indirect - golang.org/x/text v0.34.0 // indirect - golang.org/x/tools v0.43.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/text v0.36.0 // indirect + golang.org/x/tools v0.44.0 // indirect google.golang.org/protobuf v1.36.8 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/tools/cleanup/go.sum b/tools/cleanup/go.sum index dd537e55..9506727c 100644 --- a/tools/cleanup/go.sum +++ b/tools/cleanup/go.sum @@ -2,38 +2,6 @@ 4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY= 4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= 4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= -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/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= codeberg.org/chavacava/garif v0.2.0 h1:F0tVjhYbuOCnvNcU3YSpO6b3Waw6Bimy4K0mM8y6MfY= codeberg.org/chavacava/garif v0.2.0/go.mod h1:P2BPbVbT4QcvLZrORc2T29szK3xEOlnl0GiPTJmEqBQ= codeberg.org/polyfloyd/go-errorlint v1.9.0 h1:VkdEEmA1VBpH6ecQoMR4LdphVI3fA4RrCh2an7YmodI= @@ -42,7 +10,6 @@ dev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKf dev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88YdiB0Ai4fXOzPI= dev.gaijin.team/go/golib v0.6.0 h1:v6nnznFTs4bppib/NyU1PQxobwDHwCXXl15P7DV5Zgo= dev.gaijin.team/go/golib v0.6.0/go.mod h1:uY1mShx8Z/aNHWDyAkZTkX+uCi5PdX7KsG1eDQa2AVE= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8= github.com/4meepo/tagalign v1.4.3/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c= github.com/Abirdcfly/dupword v0.1.7 h1:2j8sInznrje4I0CMisSL6ipEBkeJUJAmK1/lfoNGWrQ= @@ -57,10 +24,8 @@ github.com/Antonboom/nilnil v1.1.1 h1:9Mdr6BYd8WHCDngQnNVV0b554xyisFioEKi30sksuf github.com/Antonboom/nilnil v1.1.1/go.mod h1:yCyAmSw3doopbOWhJlVci+HuyNRuHJKIv6V2oYQa8II= github.com/Antonboom/testifylint v1.6.4 h1:gs9fUEy+egzxkEbq9P4cpcMB6/G0DYdMeiFS87UiqmQ= github.com/Antonboom/testifylint v1.6.4/go.mod h1:YO33FROXX2OoUfwjz8g+gUxQXio5i9qpVy7nXGbxDD4= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Djarvur/go-err113 v0.1.1 h1:eHfopDqXRwAi+YmCUas75ZE0+hoBHJ2GQNLYRSxao4g= github.com/Djarvur/go-err113 v0.1.1/go.mod h1:IaWJdYFLg76t2ihfflPZnM1LIQszWOsFDh2hhhAVF6k= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= @@ -77,11 +42,6 @@ github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsr github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E= github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= github.com/alecthomas/repr v0.5.2/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/alexkohler/nakedret/v2 v2.0.6 h1:ME3Qef1/KIKr3kWX3nti3hhgNxw6aqN5pZmQiFSsuzQ= github.com/alexkohler/nakedret/v2 v2.0.6/go.mod h1:l3RKju/IzOMQHmsEvXwkqMDzHHvurNQfAgE1eVmT40Q= github.com/alexkohler/prealloc v1.1.0 h1:cKGRBqlXw5iyQGLYhrXrDlcHxugXpTq4tQ5c91wkf8M= @@ -144,8 +104,6 @@ github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -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.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w= @@ -168,9 +126,6 @@ github.com/catenacyber/perfsprint v0.10.1 h1:u7Riei30bk46XsG8nknMhKLXG9BcXz3+3tl github.com/catenacyber/perfsprint v0.10.1/go.mod h1:DJTGsi/Zufpuus6XPGJyKOTMELe347o6akPvWG9Zcsc= github.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc= github.com/ccojocar/zxcvbn-go v1.0.4/go.mod h1:3GxGX+rHmueTUMvm5ium7irpyjmm7ikxYFOSJB21Das= -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.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charithe/durationcheck v0.0.11 h1:g1/EX1eIiKS57NTWsYtHDZ/APfeXKhye1DidBcABctk= @@ -185,13 +140,8 @@ github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0G github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= -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.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7LspvJs= github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= @@ -210,10 +160,6 @@ github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42 github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -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/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/exoscale/egoscale/v3 v3.1.34 h1:POTsCy3IhSR9L8ZN89hiKlfeQ9L+D7NncoKb10WsyaA= @@ -234,15 +180,6 @@ github.com/ghostiam/protogetter v0.3.20 h1:oW7OPFit2FxZOpmMRPP9FffU4uUpfeE/rEdE1 github.com/ghostiam/protogetter v0.3.20/go.mod h1:FjIu5Yfs6FT391m+Fjp3fbAYJ6rkL/J6ySpZBfnODuI= github.com/go-critic/go-critic v0.14.3 h1:5R1qH2iFeo4I/RJU8vTezdqs08Egi4u5p6vOESA0pog= github.com/go-critic/go-critic v0.14.3/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ= -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.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= @@ -255,7 +192,6 @@ github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPg github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= -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-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= @@ -287,36 +223,6 @@ github.com/godoc-lint/godoc-lint v0.11.2 h1:Bp0FkJWoSdNsBikdNgIcgtaoo+xz6I/Y9s5W github.com/godoc-lint/godoc-lint v0.11.2/go.mod h1:iVpGdL1JCikNH2gGeAn3Hh+AgN5Gx/I/cxV+91L41jo= github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -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/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/golangci/asciicheck v0.5.0 h1:jczN/BorERZwK8oiFBOGvlGPknhvq0bjnysTj4nUfo0= github.com/golangci/asciicheck v0.5.0/go.mod h1:5RMNAInbNFw2krqN6ibBxN/zfRFa9S6tA1nPdM0l8qQ= github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw= @@ -339,38 +245,15 @@ github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e h1:ai0EfmVYE2b github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e/go.mod h1:Vrn4B5oR9qRwM+f54koyeH3yzphlecwERs0el27Fr/s= github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqtt0ssnqSJNNndxe69DOQ24A5h7+i3KpM= github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e/go.mod h1:h+wZwLjUTJnm/P2rwlbJdRPZXOzaT36/FwnPnY2inzc= -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.4/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.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/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/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-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 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/gordonklaus/ineffassign v0.2.0 h1:Uths4KnmwxNJNzq87fwQQDDnbNb7De00VOk9Nu0TySs= github.com/gordonklaus/ineffassign v0.2.0/go.mod h1:TIpymnagPSexySzs7F9FnO1XFTy8IT3a59vmZp5Y9Lw= github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= @@ -398,15 +281,14 @@ github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= github.com/hashicorp/go-version v1.8.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/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 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/hetznercloud/hcloud-go/v2 v2.40.0 h1:fuP7khfiDQAIXdKyQq7f3LnnOjyZg0PXTafXjUKkqIA= +github.com/hetznercloud/hcloud-go/v2 v2.40.0/go.mod h1:ANz38eerXjPv00dm9dckKhttOGtYeeGmjjvwL5e6c5E= 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/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jgautheron/goconst v1.8.2 h1:y0XF7X8CikZ93fSNT6WBTb/NElBu9IjaY7CCYQrCMX4= @@ -415,27 +297,16 @@ github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjz github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8= github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU= -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/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.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ= github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY= github.com/karamaru-alpha/copyloopvar v1.2.2 h1:yfNQvP9YaGQR7VaWLYcfZUlRP2eo2vhExWKxD/fP6q0= github.com/karamaru-alpha/copyloopvar v1.2.2/go.mod h1:oY4rGZqZ879JkJMtX3RRkcXRkmUvH0x35ykgaKgsgJY= github.com/kisielk/errcheck v1.10.0 h1:Lvs/YAHP24YKg08LA8oDw2z9fJVme090RAXd90S+rrw= github.com/kisielk/errcheck v1.10.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE= github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg= -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.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/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/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.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= @@ -449,6 +320,8 @@ github.com/kulti/thelper v0.7.1 h1:fI8QITAoFVLx+y+vSyuLBP+rcVIB8jKooNSCT2EiI98= github.com/kulti/thelper v0.7.1/go.mod h1:NsMjfQEy6sd+9Kfw8kCP61W1I0nerGSYSFnGaxQkcbs= github.com/kunwardeep/paralleltest v1.0.15 h1:ZMk4Qt306tHIgKISHWFJAO1IDQJLc6uDyJMLyncOb6w= github.com/kunwardeep/paralleltest v1.0.15/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk= +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/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= github.com/ldez/exptostd v0.4.5 h1:kv2ZGUVI6VwRfp/+bcQ6Nbx0ghFWcGIKInkG/oFn1aQ= @@ -493,25 +366,18 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgechev/revive v1.15.0 h1:vJ0HzSBzfNyPbHKolgiFjHxLek9KUijhqh42yGoqZ8Q= github.com/mgechev/revive v1.15.0/go.mod h1:LlAKO3QQe9OJ0pVZzI2GPa8CbXGZ/9lNpCGvK4T/a8A= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 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/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/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= -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/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/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= @@ -534,34 +400,17 @@ github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_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 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -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 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -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 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -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 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/quasilyte/go-ruleguard v0.4.5 h1:AGY0tiOT5hJX9BTdx/xBdoCubQUAE2grkqY2lSwvZcA= github.com/quasilyte/go-ruleguard v0.4.5/go.mod h1:Vl05zJ538vcEEwu16V/Hdu7IYZWyKSwIy4c88Ro1kRE= github.com/quasilyte/go-ruleguard/dsl v0.3.23 h1:lxjt5B6ZCiBeeNO8/oQsegE6fLeCzuMRoVWSkXC4uvY= @@ -577,7 +426,6 @@ github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtz 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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -605,9 +453,6 @@ 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/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/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -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.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= @@ -635,10 +480,8 @@ github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRk github.com/stbenjam/no-sprintf-host-port v0.3.1 h1:AyX7+dxI4IdLBPtDbsGAyqiTSLpCP9hWRrXQDU4Cm/g= github.com/stbenjam/no-sprintf-host-port v0.3.1/go.mod h1:ODbZesTCHMVKthBHskvUUexdcNHAQRXk9NpSsL8p/HQ= 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.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= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -680,7 +523,6 @@ github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+ 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/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= @@ -698,65 +540,31 @@ go.augendre.info/arangolint v0.4.0 h1:xSCZjRoS93nXazBSg5d0OGCi9APPLNMmmLrC995tR5 go.augendre.info/arangolint v0.4.0/go.mod h1:l+f/b4plABuFISuKnTGD4RioXiCCgghv2xqst/xOvAA= go.augendre.info/fatcontext v0.9.0 h1:Gt5jGD4Zcj8CDMVzjOJITlSb9cEch54hjRRlN3qDojE= go.augendre.info/fatcontext v0.9.0/go.mod h1:L94brOAT1OOUNue6ph/2HnwxoNlds9aXDF2FcUntbNw= -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.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.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= 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/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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= -golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= -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/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= 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-20260209203927-2842357ff358 h1:qWFG1Dj7TBjOjOvhEOkmyGPVoquqUKnIU0lEVLp8xyk= golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms= -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= @@ -766,64 +574,27 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= -golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= -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/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= 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-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-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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= -golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= -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/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/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= 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= @@ -831,49 +602,19 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -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-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-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-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-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-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-20210806184541-e5e7981a1069/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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -881,19 +622,17 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= -golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= -golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= 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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -901,53 +640,12 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= -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/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-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-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-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 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= @@ -956,8 +654,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= -golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= -golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= @@ -966,115 +664,23 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T 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/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/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/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/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.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.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -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.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -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= -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.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU= honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc= mvdan.cc/gofumpt v0.9.2 h1:zsEMWL8SVKGHNztrx6uZrXdp7AX8r421Vvp23sz7ik4= mvdan.cc/gofumpt v0.9.2/go.mod h1:iB7Hn+ai8lPvofHd9ZFGVg2GOr8sBUw1QUWjNbmIL/s= mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 h1:ssMzja7PDPJV8FStj7hq9IKiuiKhgz9ErWw+m68e7DI= mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15/go.mod h1:4M5MMXl2kW6fivUT6yRGpLLPNfuGtU2Z0cPvFquGDYU= -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= diff --git a/tools/cleanup/internal/hetzner/collect.go b/tools/cleanup/internal/hetzner/collect.go new file mode 100644 index 00000000..bc497328 --- /dev/null +++ b/tools/cleanup/internal/hetzner/collect.go @@ -0,0 +1,462 @@ +// Copyright 2026 Exasol AG +// SPDX-License-Identifier: MIT + +package hetzner + +import ( + "context" + "fmt" + "log/slog" + "regexp" + "strconv" + "strings" + "time" + + "github.com/exasol/exasol-personal/tools/cleanup/internal/shared" + "github.com/hetznercloud/hcloud-go/v2/hcloud" +) + +// Constants from shared package +const ( + StateActive = shared.StateActive + StateProvisioning = shared.StateProvisioning + StateStopped = shared.StateStopped + StateTerminated = shared.StateTerminated + StateUnknown = shared.StateUnknown +) + +// deploymentIDLabel is the label key used to identify deployments +const deploymentIDLabel = "deployment_id" + +// CollectDeploymentDetails enumerates resources for a single deployment in Hetzner Cloud +func CollectDeploymentDetails( + ctx context.Context, + deploymentID string, +) (*DeploymentDetails, error) { + client := newHCloudClient() + + details := &DeploymentDetails{ + Summary: DeploymentSummary{ + ID: deploymentID, + Provider: ProviderName, + Region: "", + Owner: "", + CreatedAt: time.Time{}, + State: StateUnknown, + }, + } + + var earliest *time.Time + hasServers := false + hasActive := false + hasStopped := false + + // Discover servers by label + servers, err := client.Server.AllWithOpts(ctx, hcloud.ServerListOpts{ + ListOpts: hcloud.ListOpts{ + LabelSelector: fmt.Sprintf("%s=%s", deploymentIDLabel, deploymentID), + }, + }) + if err != nil { + slog.Debug("list servers failed", "error", err) + } else { + for _, server := range servers { + hasServers = true + state := serverStateToSimple(server.Status) + + meta := ResourceMeta{ + Ref: ResourceRef{ + ARN: serverARN(server.ID), + Type: ResourceServer, + Region: server.Datacenter.Location.Name, + ID: strconv.FormatInt(server.ID, 10), + }, + Tags: server.Labels, + Attr: map[string]any{ + "name": server.Name, + "state": state, + "type": server.ServerType.Name, + }, + } + + if !server.Created.IsZero() { + meta.Attr["createdAt"] = server.Created + earliest = preferEarlier(earliest, &server.Created) + } + + if details.Summary.Region == "" { + details.Summary.Region = server.Datacenter.Location.Name + } + + switch state { + case StateActive: + hasActive = true + case StateStopped: + hasStopped = true + } + + details.Resources = append(details.Resources, meta) + } + } + + // Discover volumes by label + volumes, err := client.Volume.AllWithOpts(ctx, hcloud.VolumeListOpts{ + ListOpts: hcloud.ListOpts{ + LabelSelector: fmt.Sprintf("%s=%s", deploymentIDLabel, deploymentID), + }, + }) + if err != nil { + slog.Debug("list volumes failed", "error", err) + } else { + for _, vol := range volumes { + meta := ResourceMeta{ + Ref: ResourceRef{ + ARN: volumeARN(vol.ID), + Type: ResourceVolume, + Region: vol.Location.Name, + ID: strconv.FormatInt(vol.ID, 10), + }, + Tags: vol.Labels, + Attr: map[string]any{ + "name": vol.Name, + "size": vol.Size, + }, + } + + if !vol.Created.IsZero() { + meta.Attr["createdAt"] = vol.Created + earliest = preferEarlier(earliest, &vol.Created) + } + + details.Resources = append(details.Resources, meta) + } + } + + // Discover networks by label + networks, err := client.Network.AllWithOpts(ctx, hcloud.NetworkListOpts{ + ListOpts: hcloud.ListOpts{ + LabelSelector: fmt.Sprintf("%s=%s", deploymentIDLabel, deploymentID), + }, + }) + if err != nil { + slog.Debug("list networks failed", "error", err) + } else { + for _, net := range networks { + meta := ResourceMeta{ + Ref: ResourceRef{ + ARN: networkARN(net.ID), + Type: ResourceNetwork, + Region: "", + ID: strconv.FormatInt(net.ID, 10), + }, + Tags: net.Labels, + Attr: map[string]any{ + "name": net.Name, + }, + } + + if !net.Created.IsZero() { + meta.Attr["createdAt"] = net.Created + earliest = preferEarlier(earliest, &net.Created) + } + + details.Resources = append(details.Resources, meta) + } + } + + // Discover firewalls by label + firewalls, err := client.Firewall.AllWithOpts(ctx, hcloud.FirewallListOpts{ + ListOpts: hcloud.ListOpts{ + LabelSelector: fmt.Sprintf("%s=%s", deploymentIDLabel, deploymentID), + }, + }) + if err != nil { + slog.Debug("list firewalls failed", "error", err) + } else { + for _, fw := range firewalls { + meta := ResourceMeta{ + Ref: ResourceRef{ + ARN: firewallARN(fw.ID), + Type: ResourceFirewall, + Region: "", + ID: strconv.FormatInt(fw.ID, 10), + }, + Tags: fw.Labels, + Attr: map[string]any{ + "name": fw.Name, + }, + } + + if !fw.Created.IsZero() { + meta.Attr["createdAt"] = fw.Created + earliest = preferEarlier(earliest, &fw.Created) + } + + details.Resources = append(details.Resources, meta) + } + } + + // Discover SSH keys by label + sshKeys, err := client.SSHKey.AllWithOpts(ctx, hcloud.SSHKeyListOpts{ + ListOpts: hcloud.ListOpts{ + LabelSelector: fmt.Sprintf("%s=%s", deploymentIDLabel, deploymentID), + }, + }) + if err != nil { + slog.Debug("list ssh keys failed", "error", err) + } else { + for _, key := range sshKeys { + meta := ResourceMeta{ + Ref: ResourceRef{ + ARN: sshKeyARN(key.ID), + Type: ResourceSSHKey, + Region: "", + ID: strconv.FormatInt(key.ID, 10), + }, + Tags: key.Labels, + Attr: map[string]any{ + "name": key.Name, + }, + } + + if !key.Created.IsZero() { + meta.Attr["createdAt"] = key.Created + earliest = preferEarlier(earliest, &key.Created) + } + + details.Resources = append(details.Resources, meta) + } + } + + // Update summary + details.Summary.Resources = len(details.Resources) + if earliest != nil { + details.Summary.CreatedAt = *earliest + } + + // Determine state + if hasServers { + switch { + case hasActive: + details.Summary.State = StateActive + case hasStopped: + details.Summary.State = StateStopped + case details.Summary.Resources > 0: + details.Summary.State = StateTerminated + } + } else if details.Summary.Resources > 0 { + details.Summary.State = "orphaned" + } + + if details.Summary.Owner == "" { + details.Summary.Owner = "-" + } + + return details, nil +} + +// CollectDeploymentSummaries discovers deployments across Hetzner Cloud +func CollectDeploymentSummaries( + ctx context.Context, + ownerFilter string, +) ([]DeploymentSummary, error) { + client := newHCloudClient() + + summaries := make(map[string]*DeploymentSummary) + deploymentIDRegex := regexp.MustCompile(`^exasol-[a-f0-9]{8}$`) + + // Discover deployments via servers (primary resource) + servers, err := client.Server.AllWithOpts(ctx, hcloud.ServerListOpts{ + ListOpts: hcloud.ListOpts{ + LabelSelector: "project=exasol-personal", + }, + }) + if err != nil { + return nil, fmt.Errorf("failed to list servers: %w", err) + } + + for _, server := range servers { + depID := extractDeploymentIDFromLabels(server.Labels, deploymentIDRegex) + if depID == "" { + depID = extractDeploymentIDFromName(server.Name, deploymentIDRegex) + } + if depID == "" { + continue + } + + sum := summaries[depID] + if sum == nil { + sum = &DeploymentSummary{ + ID: depID, + Provider: ProviderName, + Region: server.Datacenter.Location.Name, + Owner: "-", + CreatedAt: time.Time{}, + State: StateUnknown, + } + summaries[depID] = sum + } + + sum.Resources++ + + if !server.Created.IsZero() && (sum.CreatedAt.IsZero() || server.Created.Before(sum.CreatedAt)) { + sum.CreatedAt = server.Created + } + + state := serverStateToSimple(server.Status) + switch state { + case StateActive: + if sum.State != StateActive { + sum.State = StateActive + } + case StateStopped: + if sum.State != StateActive { + sum.State = StateStopped + } + } + } + + // Count volumes for each deployment + volumes, err := client.Volume.AllWithOpts(ctx, hcloud.VolumeListOpts{ + ListOpts: hcloud.ListOpts{ + LabelSelector: "project=exasol-personal", + }, + }) + if err != nil { + slog.Debug("list volumes failed", "error", err) + } + for _, vol := range volumes { + depID := extractDeploymentIDFromLabels(vol.Labels, deploymentIDRegex) + if sum, ok := summaries[depID]; ok { + sum.Resources++ + } + } + + // Count networks for each deployment + networks, err := client.Network.AllWithOpts(ctx, hcloud.NetworkListOpts{ + ListOpts: hcloud.ListOpts{ + LabelSelector: "project=exasol-personal", + }, + }) + if err != nil { + slog.Debug("list networks failed", "error", err) + } + for _, net := range networks { + depID := extractDeploymentIDFromLabels(net.Labels, deploymentIDRegex) + if sum, ok := summaries[depID]; ok { + sum.Resources++ + } + } + + // Count firewalls for each deployment + firewalls, err := client.Firewall.AllWithOpts(ctx, hcloud.FirewallListOpts{ + ListOpts: hcloud.ListOpts{ + LabelSelector: "project=exasol-personal", + }, + }) + if err != nil { + slog.Debug("list firewalls failed", "error", err) + } + for _, fw := range firewalls { + depID := extractDeploymentIDFromLabels(fw.Labels, deploymentIDRegex) + if sum, ok := summaries[depID]; ok { + sum.Resources++ + } + } + + // Count SSH keys for each deployment + sshKeys, err := client.SSHKey.AllWithOpts(ctx, hcloud.SSHKeyListOpts{ + ListOpts: hcloud.ListOpts{ + LabelSelector: "project=exasol-personal", + }, + }) + if err != nil { + slog.Debug("list ssh keys failed", "error", err) + } + for _, key := range sshKeys { + depID := extractDeploymentIDFromLabels(key.Labels, deploymentIDRegex) + if sum, ok := summaries[depID]; ok { + sum.Resources++ + } + } + + // Convert map to slice + result := make([]DeploymentSummary, 0, len(summaries)) + for _, s := range summaries { + result = append(result, *s) + } + + return result, nil +} + +// Helper functions + +func serverStateToSimple(status hcloud.ServerStatus) string { + switch status { + case hcloud.ServerStatusRunning: + return StateActive + case hcloud.ServerStatusInitializing: + return StateProvisioning + case hcloud.ServerStatusOff: + return StateStopped + case hcloud.ServerStatusDeleting: + return StateTerminated + default: + return StateUnknown + } +} + +func extractDeploymentIDFromLabels(labels map[string]string, regex *regexp.Regexp) string { + if labels == nil { + return "" + } + if depID, ok := labels[deploymentIDLabel]; ok && regex.MatchString(depID) { + return depID + } + return "" +} + +func extractDeploymentIDFromName(name string, regex *regexp.Regexp) string { + // Pattern: exasol-{deployment_id}-suffix or exasol-{deployment_id} + parts := strings.Split(name, "-") + if len(parts) >= 2 { + candidate := parts[0] + "-" + parts[1] + if regex.MatchString(candidate) { + return candidate + } + } + + return "" +} + + +func preferEarlier(existing *time.Time, candidate *time.Time) *time.Time { + if candidate == nil { + return existing + } + if existing == nil || candidate.Before(*existing) { + return candidate + } + return existing +} + +// ARN generators for Hetzner resources +func serverARN(id int64) string { + return fmt.Sprintf("hetzner:server:%d", id) +} + +func volumeARN(id int64) string { + return fmt.Sprintf("hetzner:volume:%d", id) +} + +func networkARN(id int64) string { + return fmt.Sprintf("hetzner:network:%d", id) +} + +func firewallARN(id int64) string { + return fmt.Sprintf("hetzner:firewall:%d", id) +} + +func sshKeyARN(id int64) string { + return fmt.Sprintf("hetzner:ssh-key:%d", id) +} diff --git a/tools/cleanup/internal/hetzner/collector.go b/tools/cleanup/internal/hetzner/collector.go new file mode 100644 index 00000000..d8b78aa6 --- /dev/null +++ b/tools/cleanup/internal/hetzner/collector.go @@ -0,0 +1,83 @@ +// Copyright 2026 Exasol AG +// SPDX-License-Identifier: MIT + +package hetzner + +import ( + "context" + "fmt" + "os" + + "github.com/exasol/exasol-personal/tools/cleanup/internal/shared" + "github.com/hetznercloud/hcloud-go/v2/hcloud" +) + +// ProviderName is the identifier for the Hetzner provider +const ProviderName = "hetzner" + +// Collector implements shared.ProviderCollector for Hetzner Cloud. +type Collector struct { + location string + ownerFilter string + legacy bool +} + +// NewCollector creates a Hetzner collector with the specified configuration. +func NewCollector(location, ownerFilter string, legacy bool) *Collector { + return &Collector{ + location: location, + ownerFilter: ownerFilter, + legacy: legacy, + } +} + +func (c *Collector) Name() string { + return ProviderName +} + +func (c *Collector) IsAvailable(_ context.Context) bool { + token := os.Getenv("HCLOUD_TOKEN") + return token != "" +} + +func (c *Collector) GetAccountInfo(ctx context.Context) (string, error) { + client := newHCloudClient() + + // Use a simple API call to verify connectivity and get project info + // The hcloud client doesn't expose "whoami" directly, but listing servers + // with a limit of 0 confirms the token is valid. + _, resp, err := client.Server.List(ctx, hcloud.ServerListOpts{ + ListOpts: hcloud.ListOpts{PerPage: 1}, + }) + if err != nil { + return "", fmt.Errorf("failed to verify Hetzner Cloud credentials: %w", err) + } + + // Use the response to confirm we're connected; Hetzner tokens are project-scoped + if resp != nil && resp.StatusCode == 200 { + return "[project-scoped-token]", nil + } + + return "[connected]", nil +} + +func (c *Collector) CollectDeploymentSummaries(ctx context.Context) ([]shared.DeploymentSummary, error) { + return CollectDeploymentSummaries(ctx, c.ownerFilter) +} + +func (c *Collector) CollectDeploymentDetails(ctx context.Context, deploymentID string) (*shared.DeploymentDetails, error) { + return CollectDeploymentDetails(ctx, deploymentID) +} + +func (c *Collector) PlanActions(details *shared.DeploymentDetails, typeFilter []shared.ResourceType) ([]shared.Action, error) { + return PlanActions(details, typeFilter) +} + +func (c *Collector) ExecuteActions(ctx context.Context, actions []shared.Action, execute bool) ([]shared.Result, error) { + return ExecuteActions(ctx, actions, execute) +} + +func newHCloudClient() *hcloud.Client { + token := os.Getenv("HCLOUD_TOKEN") + return hcloud.NewClient(hcloud.WithToken(token)) +} diff --git a/tools/cleanup/internal/hetzner/handlers.go b/tools/cleanup/internal/hetzner/handlers.go new file mode 100644 index 00000000..49d353a1 --- /dev/null +++ b/tools/cleanup/internal/hetzner/handlers.go @@ -0,0 +1,202 @@ +// Copyright 2026 Exasol AG +// SPDX-License-Identifier: MIT + +package hetzner + +import ( + "context" + "errors" + "fmt" + "strconv" + "sync" + + "github.com/hetznercloud/hcloud-go/v2/hcloud" +) + +// Handler defines deletion behavior for a resource type. +type Handler interface { + Delete(ctx context.Context, ref ResourceRef) error +} + +var ErrUnsupportedResource = errors.New("unsupported resource type for deletion") + +// registry maps resource types to concrete handlers. +var ( + registry = map[ResourceType]Handler{} + registryOnce sync.Once +) + +// initHandlers initializes handler singletons exactly once, safe for concurrent use. +func initHandlers() { + registryOnce.Do(func() { + client := newHCloudClient() + + registry[ResourceServer] = &serverHandler{client: client} + registry[ResourceVolume] = &volumeHandler{client: client} + registry[ResourceNetwork] = &networkHandler{client: client} + registry[ResourceFirewall] = &firewallHandler{client: client} + registry[ResourceSSHKey] = &sshKeyHandler{client: client} + }) +} + +func deleteResource(ctx context.Context, ref ResourceRef) error { + initHandlers() + h, ok := registry[ref.Type] + if !ok { + return ErrUnsupportedResource + } + + return h.Delete(ctx, ref) +} + +// Server Handler +type serverHandler struct { + client *hcloud.Client +} + +func (h *serverHandler) Delete(ctx context.Context, ref ResourceRef) error { + id, err := strconv.ParseInt(ref.ID, 10, 64) + if err != nil { + return fmt.Errorf("invalid server ID: %w", err) + } + + server := &hcloud.Server{ID: id} + + // Delete the server (also deletes associated primary IPs) + result, _, err := h.client.Server.DeleteWithResult(ctx, server) + if err != nil { + if hcloud.IsError(err, hcloud.ErrorCodeNotFound) { + return nil + } + return fmt.Errorf("failed to delete server: %w", err) + } + + // Wait for the action to complete + if err := h.client.Action.WaitFor(ctx, result.Action); err != nil { + return fmt.Errorf("failed to wait for server deletion: %w", err) + } + + return nil +} + +// Volume Handler +type volumeHandler struct { + client *hcloud.Client +} + +func (h *volumeHandler) Delete(ctx context.Context, ref ResourceRef) error { + id, err := strconv.ParseInt(ref.ID, 10, 64) + if err != nil { + return fmt.Errorf("invalid volume ID: %w", err) + } + + volume := &hcloud.Volume{ID: id} + + // Detach volume first if attached + vol, _, err := h.client.Volume.GetByID(ctx, id) + if err != nil { + if hcloud.IsError(err, hcloud.ErrorCodeNotFound) { + return nil + } + return fmt.Errorf("failed to get volume: %w", err) + } + + if vol == nil { + return nil + } + + if vol.Server != nil { + action, _, err := h.client.Volume.Detach(ctx, volume) + if err != nil { + if !hcloud.IsError(err, hcloud.ErrorCodeNotFound) { + return fmt.Errorf("failed to detach volume: %w", err) + } + } else if action != nil { + if err := h.client.Action.WaitFor(ctx, action); err != nil { + return fmt.Errorf("failed to wait for volume detach: %w", err) + } + } + } + + _, err = h.client.Volume.Delete(ctx, volume) + if err != nil { + if hcloud.IsError(err, hcloud.ErrorCodeNotFound) { + return nil + } + return fmt.Errorf("failed to delete volume: %w", err) + } + + return nil +} + +// Network Handler +type networkHandler struct { + client *hcloud.Client +} + +func (h *networkHandler) Delete(ctx context.Context, ref ResourceRef) error { + id, err := strconv.ParseInt(ref.ID, 10, 64) + if err != nil { + return fmt.Errorf("invalid network ID: %w", err) + } + + network := &hcloud.Network{ID: id} + + _, err = h.client.Network.Delete(ctx, network) + if err != nil { + if hcloud.IsError(err, hcloud.ErrorCodeNotFound) { + return nil + } + return fmt.Errorf("failed to delete network: %w", err) + } + + return nil +} + +// Firewall Handler +type firewallHandler struct { + client *hcloud.Client +} + +func (h *firewallHandler) Delete(ctx context.Context, ref ResourceRef) error { + id, err := strconv.ParseInt(ref.ID, 10, 64) + if err != nil { + return fmt.Errorf("invalid firewall ID: %w", err) + } + + firewall := &hcloud.Firewall{ID: id} + + _, err = h.client.Firewall.Delete(ctx, firewall) + if err != nil { + if hcloud.IsError(err, hcloud.ErrorCodeNotFound) { + return nil + } + return fmt.Errorf("failed to delete firewall: %w", err) + } + + return nil +} + +// SSH Key Handler +type sshKeyHandler struct { + client *hcloud.Client +} + +func (h *sshKeyHandler) Delete(ctx context.Context, ref ResourceRef) error { + id, err := strconv.ParseInt(ref.ID, 10, 64) + if err != nil { + return fmt.Errorf("invalid SSH key ID: %w", err) + } + + sshKey := &hcloud.SSHKey{ID: id} + + _, err = h.client.SSHKey.Delete(ctx, sshKey) + if err != nil { + if hcloud.IsError(err, hcloud.ErrorCodeNotFound) { + return nil + } + return fmt.Errorf("failed to delete SSH key: %w", err) + } + + return nil +} diff --git a/tools/cleanup/internal/hetzner/run.go b/tools/cleanup/internal/hetzner/run.go new file mode 100644 index 00000000..6d228e24 --- /dev/null +++ b/tools/cleanup/internal/hetzner/run.go @@ -0,0 +1,114 @@ +// Copyright 2026 Exasol AG +// SPDX-License-Identifier: MIT + +package hetzner + +import ( + "context" + "errors" + "log/slog" + + "github.com/exasol/exasol-personal/tools/cleanup/internal/shared" +) + +// Constants from shared package +const ( + OpSkip = shared.OpSkip +) + +var ErrNoResourcesPlanned = errors.New("no resources found to plan cleanup") + +// BuildPlan constructs the static ordered cleanup plan. +func BuildPlan() CleanupPlan { + return CleanupPlan{Phases: []Phase{ + {Name: "servers", Types: []ResourceType{ResourceServer}}, + {Name: "volumes", Types: []ResourceType{ResourceVolume}}, + {Name: "networking", Types: []ResourceType{ResourceNetwork, ResourceFirewall}}, + {Name: "ssh-keys", Types: []ResourceType{ResourceSSHKey}}, + }} +} + +// PlanActions creates action list from resources & plan. +func PlanActions(details *DeploymentDetails, typeFilter []ResourceType) ([]Action, error) { + if details == nil || len(details.Resources) == 0 { + return nil, ErrNoResourcesPlanned + } + filter := map[ResourceType]struct{}{} + for _, t := range typeFilter { + filter[t] = struct{}{} + } + plan := BuildPlan() + var actions []Action + for _, phase := range plan.Phases { + for _, resource := range details.Resources { + if !containsType(phase.Types, resource.Ref.Type) { + continue + } + if len(filter) > 0 { + if _, ok := filter[resource.Ref.Type]; !ok { + continue + } + } + act := Action{Ref: resource.Ref, Op: opForResource(resource), Reason: ""} + if resource.Protected { + act.Op = OpSkip + act.Reason = "protected" + } + actions = append(actions, act) + } + } + if len(actions) == 0 { + return nil, ErrNoResourcesPlanned + } + + return actions, nil +} + +func containsType(list []ResourceType, t ResourceType) bool { + for _, v := range list { + if v == t { + return true + } + } + + return false +} + +func opForResource(_ ResourceMeta) string { + // For now everything is delete unless protected + return "delete" +} + +// ExecuteActions runs the actions. Executes deletion if execute=true. +// +//nolint:revive // 'execute' is an intentional flag to control dry-run vs execute behavior. +func ExecuteActions(ctx context.Context, actions []Action, execute bool) ([]Result, error) { + results := make([]Result, 0, len(actions)) + for _, action := range actions { + res := Result{Action: action, Status: "planned"} + if execute && action.Op != OpSkip { + if err := deleteResource(ctx, action.Ref); err != nil { + res.Status = "failed" + res.Error = err.Error() + slog.Error( + "cleanup failed", + "id", + action.Ref.ID, + "type", + action.Ref.Type, + "error", + err, + ) + } else { + res.Status = "success" + slog.Info("cleanup success", "op", action.Op, "id", + action.Ref.ID, "type", action.Ref.Type) + } + } else if action.Op == OpSkip { + res.Status = "skipped" + } + results = append(results, res) + } + + return results, nil +} diff --git a/tools/cleanup/internal/hetzner/types.go b/tools/cleanup/internal/hetzner/types.go new file mode 100644 index 00000000..6544d642 --- /dev/null +++ b/tools/cleanup/internal/hetzner/types.go @@ -0,0 +1,32 @@ +// Copyright 2026 Exasol AG +// SPDX-License-Identifier: MIT + +package hetzner + +import ( + "github.com/exasol/exasol-personal/tools/cleanup/internal/shared" +) + +// Type aliases for shared types used throughout the hetzner package +type ( + DeploymentSummary = shared.DeploymentSummary + DeploymentDetails = shared.DeploymentDetails + ResourceRef = shared.ResourceRef + ResourceMeta = shared.ResourceMeta + Action = shared.Action + Result = shared.Result + CleanupPlan = shared.CleanupPlan + Phase = shared.Phase +) + +// ResourceType is imported from shared package +type ResourceType = shared.ResourceType + +// Hetzner-specific resource type constants +const ( + ResourceServer ResourceType = "hetzner-server" + ResourceVolume ResourceType = "hetzner-volume" + ResourceNetwork ResourceType = "hetzner-network" + ResourceFirewall ResourceType = "hetzner-firewall" + ResourceSSHKey ResourceType = "hetzner-ssh-key" +)