50 Commits

Author SHA1 Message Date
skamboj
7935a11f9d Merge pull request #164 from cooperlees/master
Add UDP probe metrics: packet loss, hop count, and RTT
2026-04-03 13:04:31 -04:00
Sachin Kamboj
de7f4e9004 Bump the version to 3.11.0
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2026-04-03 12:57:41 -04:00
Cooper Ry Lees
145d2bf000 Rename PathLength to HopCount in swagger model and UI
Rename the swagger field from path-length to hop-count so the
generated Go struct field (PathLength → HopCount) and JSON key
(path-length → hop-count) align with the Prometheus metric rename
to goldpinger_peers_hop_count from the previous commit.

Signed-off-by: Cooper Ry Lees <me@cooperlees.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 19:45:31 +00:00
Cooper Ry Lees
641b658f23 Address PR #164 review feedback
Concurrent HTTP + UDP pings:
  HTTP ping and UDP probe now run in separate goroutines via
  sync.WaitGroup, so UDP timeout doesn't add to the ping cycle
  latency. (skamboj on pinger.go:124)

Remove duplicate log:
  Removed the "UDP echo listener started" log from main.go since
  StartUDPListener already logs it. (skamboj on main.go:191)

Prometheus base units (seconds):
  Renamed goldpinger_peers_udp_rtt_ms back to goldpinger_peers_udp_rtt_s
  with sub-millisecond histogram buckets (.0001s to 1s), per Prometheus
  naming conventions. RTT is computed in seconds internally and only
  converted to ms for the JSON API. (skamboj on stats.go:150)

Rename path_length to hop_count:
  goldpinger_peers_path_length → goldpinger_peers_hop_count, and
  SetPeerPathLength → SetPeerHopCount. (skamboj on stats.go:139)

UDP buffer constant and packet size clamping:
  Added udpMaxPacketSize=1500 constant, documented as standard Ethernet
  MTU — the largest UDP payload that survives most networks without
  fragmentation. Used for both listener and prober receive buffers.
  ProbeUDP now clamps UDP_PACKET_SIZE to udpMaxPacketSize to prevent
  silent truncation if someone configures a size > MTU.
  (skamboj on udp_probe.go:54)

Guard count=0:
  ProbeUDP returns an error immediately if count <= 0 instead of
  dividing by zero. (skamboj on udp_probe.go:176)

UDP error counter:
  Added goldpinger_udp_errors_total counter (labels: goldpinger_instance,
  host). CountUDPError is called on dial failures and send errors.
  (skamboj on udp_probe.go:115)

Test: random source port for full loss:
  TestProbeUDP_FullLoss now binds an ephemeral port and closes it,
  instead of assuming port 19999 is free. (skamboj on udp_probe_test.go:56)

Test: partial loss validation:
  New TestProbeUDP_PartialLoss uses a lossy echo listener that drops
  every Nth packet to validate loss calculations are exact:
    drop every 2nd → 50.0%, every 3rd → 33.3%,
    every 5th → 20.0%, every 10th → 10.0%
  (skamboj on udp_probe_test.go:96)

Test: zero count:
  New TestProbeUDP_ZeroCount verifies error is returned for count=0.

Test results:
```
=== RUN   TestProbeUDP_NoLoss
    udp_probe_test.go:88: avg UDP RTT: 0.0816 ms
--- PASS: TestProbeUDP_NoLoss (0.00s)
=== RUN   TestProbeUDP_FullLoss
--- PASS: TestProbeUDP_FullLoss (0.00s)
=== RUN   TestProbeUDP_PartialLoss
=== RUN   TestProbeUDP_PartialLoss/drop_every_2nd_(50%)
    udp_probe_test.go:134: loss: 50.0% (expected 50.0%)
=== RUN   TestProbeUDP_PartialLoss/drop_every_3rd_(33.3%)
    udp_probe_test.go:134: loss: 33.3% (expected 33.3%)
=== RUN   TestProbeUDP_PartialLoss/drop_every_5th_(20%)
    udp_probe_test.go:134: loss: 20.0% (expected 20.0%)
=== RUN   TestProbeUDP_PartialLoss/drop_every_10th_(10%)
    udp_probe_test.go:134: loss: 10.0% (expected 10.0%)
--- PASS: TestProbeUDP_PartialLoss (8.00s)
=== RUN   TestProbeUDP_ZeroCount
--- PASS: TestProbeUDP_ZeroCount (0.00s)
=== RUN   TestProbeUDP_PacketFormat
--- PASS: TestProbeUDP_PacketFormat (0.00s)
=== RUN   TestEstimateHops
--- PASS: TestEstimateHops (0.00s)
PASS
```

Signed-off-by: Cooper Ry Lees <me@cooperlees.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 19:37:52 +00:00
Cooper Ry Lees
832bc7b598 Add UDP probe metrics: packet loss, hop count, and RTT
Add an opt-in UDP echo probe that runs alongside the existing HTTP
ping. Each goldpinger pod listens on a configurable UDP port (default
6969). During each ping cycle, the prober sends N sequenced packets
to the peer's listener, which echoes them back. From the replies we
compute packet loss percentage, path hop count (from IPv4 TTL / IPv6
HopLimit), and average round-trip time.

New Prometheus metrics:
  - goldpinger_peers_loss_pct      (gauge)     — per-peer UDP loss %
  - goldpinger_peers_path_length   (gauge)     — estimated hop count
  - goldpinger_peers_udp_rtt_ms    (histogram) — UDP RTT in milliseconds

The graph UI shows yellow edges for links with partial loss, and
displays sub-millisecond UDP RTT instead of HTTP latency when UDP
is enabled. Stale metric labels are cleaned up when a pinger is
destroyed so rolled pods don't leave ghost entries.

Configuration (all via env vars, disabled by default):
  UDP_ENABLED=true      enable UDP probing and listener
  UDP_PORT=6969         listener port
  UDP_PACKET_COUNT=10   packets per probe
  UDP_PACKET_SIZE=64    bytes per packet
  UDP_TIMEOUT=1s        probe timeout

New files:
  pkg/goldpinger/udp_probe.go       — echo listener + probe client
  pkg/goldpinger/udp_probe_test.go  — unit tests

Unit tests:
```
=== RUN   TestProbeUDP_NoLoss
    udp_probe_test.go:51: avg UDP RTT: 0.0823 ms
--- PASS: TestProbeUDP_NoLoss (0.00s)
=== RUN   TestProbeUDP_FullLoss
--- PASS: TestProbeUDP_FullLoss (0.00s)
=== RUN   TestProbeUDP_PacketFormat
--- PASS: TestProbeUDP_PacketFormat (0.00s)
=== RUN   TestEstimateHops
--- PASS: TestEstimateHops (0.00s)
PASS
```

Cluster test (6-node IPv6 k8s, UDP_ENABLED=true):
```
Prometheus metrics (healthy cluster, 0% loss):
  goldpinger_peers_loss_pct{...,pod_ip="fd00:4:69:3::3746"} 0
  goldpinger_peers_path_length{...,pod_ip="fd00:4:69:3::3746"} 0

Simulated 50% loss via ip6tables DROP in pod netns on node-0:
  goldpinger_peers_loss_pct{instance="server",...} 60
  goldpinger_peers_loss_pct{instance="node-1",...} 30
  goldpinger_peers_loss_pct{instance="server2",...} 30

UDP RTT vs HTTP RTT (check_all API):
  node-0 -> server:  udp=2.18ms  http=2ms
  node-2 -> node-2:  udp=0.40ms  http=1ms
  server -> node-0:  udp=0.55ms  http=2ms

Post-rollout stale metrics cleanup verified:
  All 36 edges show 0% loss, no stale pod IPs.
```

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Cooper Ry Lees <me@cooperlees.com>
2026-03-27 16:05:32 +00:00
skamboj
8d63d44fe2 Merge pull request #162 from skamboj/update-golang
Some checks failed
Helm Publish / helm_publish (push) Failing after 9s
CI / build (push) Successful in 5m56s
Update golang to 1.25 and update all dependencies
2026-01-28 21:12:18 -05:00
Sachin Kamboj
4392ae9f09 Update chart versions as well
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2026-01-28 20:37:47 -05:00
Sachin Kamboj
cb9c8ae248 Update goldpinger version
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2026-01-28 20:28:04 -05:00
Sachin Kamboj
b54e3feea6 Update dependencies
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2026-01-28 20:27:05 -05:00
Sachin Kamboj
0dfa55880c Update to golang 1.25
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2026-01-28 20:26:41 -05:00
skamboj
a93c8040a1 Merge pull request #161 from skamboj/update-workflows
All checks were successful
Helm Publish / helm_publish (push) Successful in 52s
CI / build (push) Successful in 6m19s
Update versions of the various actions
2026-01-28 20:11:48 -05:00
Sachin Kamboj
3ce341330b Attempt to fix the bake step
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2026-01-28 16:30:22 -05:00
Sachin Kamboj
a85572f799 More updates to the versions
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2026-01-28 08:50:01 -05:00
Sachin Kamboj
f29301ed41 Merge remote-tracking branch 'upstream/master' into update-workflows
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2026-01-28 08:37:45 -05:00
Sachin Kamboj
7379914781 Update versions of the various actions
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2026-01-27 21:35:26 -05:00
skamboj
52e86c25f5 Merge pull request #152 from Leundai/add-deepwiki
Some checks failed
Helm Publish / helm_publish (push) Successful in 55s
CI / build (push) Failing after 47m53s
feat: Add deepwiki badge
2025-10-04 11:21:14 -04:00
leundai
ba779f50e7 feat: Add deepwiki badge
Small enhancement to improve quick onboarding for the curious

Signed-off-by: leundai <leogalindofrias@gmail.com>
2025-07-12 14:48:02 -04:00
skamboj
02065cf812 Merge pull request #148 from scoof/improvement-metricrelabelings
improvement: support relabelings in ServiceMonitor
2024-11-11 09:30:59 -05:00
skamboj
98bee8cc4e Merge branch 'master' into improvement-metricrelabelings 2024-11-11 09:17:09 -05:00
Sachin Kamboj
41680b856a Up the app version
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2024-11-11 09:16:18 -05:00
Sachin Kamboj
8db3d2f2de Fix typo
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2024-11-11 09:14:07 -05:00
skamboj
d1c60472df Merge pull request #147 from avnes/fix/typo-in-chart-description
Fix small typo i Chart description. Change troublshoot to troubleshoot
2024-11-11 09:05:44 -05:00
skamboj
65cf0cab7c Merge branch 'master' into fix/typo-in-chart-description 2024-11-11 09:04:52 -05:00
skamboj
438c5d0739 Merge branch 'master' into improvement-metricrelabelings 2024-11-11 09:02:41 -05:00
skamboj
259ab8f22a Merge pull request #150 from laverya/build-with-go-1.23
build with go 1.23
2024-11-11 09:02:27 -05:00
skamboj
31a851fbb0 Merge branch 'master' into fix/typo-in-chart-description 2024-11-11 08:56:16 -05:00
skamboj
6401b59cb8 Merge branch 'master' into build-with-go-1.23 2024-11-11 08:54:55 -05:00
skamboj
1577ae84b8 Merge pull request #149 from laverya/update-x-image-for-cve-2024-24792
update golang.org/x/image to resolve cve-2024-24792
2024-11-11 08:54:26 -05:00
Andrew Lavery
e1b06a5236 build with go 1.23
Signed-off-by: Andrew Lavery <laverya@umich.edu>
2024-10-11 17:07:24 +02:00
Andrew Lavery
2f77117b89 update golang.org/x/image to resolve cve-2024-24792
Signed-off-by: Andrew Lavery <laverya@umich.edu>
2024-10-11 17:00:52 +02:00
Andreas Plesner
d8819d6d6d Fix datatype
Signed-off-by: Andreas Plesner <apj@mutt.dk>
2024-09-09 20:40:26 +02:00
Sachin Kamboj
f7ab34e462 Update the chart version
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2024-09-09 13:38:55 -04:00
Andreas Plesner
b07803d8c6 fix: move metricRelabelings to correct section
Signed-off-by: Andreas Plesner <apj@mutt.dk>
2024-08-26 12:24:08 +02:00
Andreas Plesner
876b3f4068 improvement: support relabelings in ServiceMonitor
Signed-Off-By: Andreas Plesner <apj@mutt.dk>
2024-08-12 09:13:29 +02:00
Audun Nes
2addb57cb4 iFix small typo i Chart description. Change troublshoot to troubleshoot
Signed-off-by: Audun Nes <audun.nes@gmail.com>
2024-06-13 13:12:47 +02:00
skamboj
36b0aed3b1 Merge pull request #137 from DerekTBrown/add-helm-chart
feat: add helm chart
2024-05-14 10:46:43 -04:00
Sachin Kamboj
a909e03de9 The appVersion should not have a v
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2024-05-14 10:33:36 -04:00
Sachin Kamboj
b8035264ed Update the publishing workflow
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2024-05-14 10:16:45 -04:00
Sachin Kamboj
6a3794f3d6 Secure by default - set the security context and pod security context
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2024-05-14 10:05:07 -04:00
Sachin Kamboj
f514bac57c Remove kubernetes version to use the default image
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2024-05-14 10:01:00 -04:00
Sachin Kamboj
a1a481ffe9 Update to kube 1.30 for the kind cluster as well
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2024-05-14 08:42:55 -04:00
Sachin Kamboj
aed183926e Update the versions to the latest
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2024-05-14 08:36:28 -04:00
skamboj
dbd1f5f295 Merge branch 'master' into add-helm-chart 2024-05-13 15:41:59 -04:00
skamboj
a8f1a76691 Merge pull request #143 from pettersolberg88/master
Upgrade golang to 1.22 and update dependencies
2024-05-13 14:40:08 -04:00
Sachin Kamboj
f4aa170407 Update the version
Signed-off-by: Sachin Kamboj <skamboj1@bloomberg.net>
2024-05-13 14:32:23 -04:00
Petter Solberg
c740646bc2 Upgrade golang to 1.22 and update dependencies
Signed-off-by: Petter Solberg <pettersolberg88@gmail.com>
2024-04-16 21:27:40 +02:00
skamboj
41af078647 Merge pull request #142 from abctaylor/abctaylor-serviceaccount
Add default namespace `default` to ServiceAccount definition in example yaml
2024-04-12 09:21:17 -04:00
ABC Taylor
c70d8a6a8a Merge branch 'master' into abctaylor-serviceaccount 2024-04-11 08:41:04 +01:00
ABC Taylor
562df92c3a Add default namespace default to ServiceAccount definition, to catch case where users find-replace default with another namespace but don't change it for the ServiceAccount
Signed-Off-By: ABC Taylor <abc@abctaylor.com>
2024-04-11 08:37:09 +01:00
Derek Brown
4af6666853 feat: add helm chart
Signed-off-by: Derek Brown <derektbrown@users.noreply.github.com>
2023-09-25 15:51:14 -07:00
84 changed files with 2479 additions and 688 deletions

33
.github/workflows/helm-publish.yaml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: Helm Publish
on:
push:
branches:
- master
jobs:
helm_publish:
# depending on default permission settings for your org (contents being read-only or read-write for workloads), you will have to add permissions
# see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Install Helm
uses: azure/setup-helm@v4
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
- name: Run chart-releaser
uses: helm/chart-releaser-action@v1.7.0
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

74
.github/workflows/helm-test.yaml vendored Normal file
View File

@@ -0,0 +1,74 @@
name: Helm Test
on:
pull_request:
jobs:
helm_test:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
- name: Install Tools
shell: bash
id: tool-versions
env:
TILT_TOOL_CTLPTL_VERSION: "0.8.28"
TILT_TOOL_KIND_VERSION: "0.23.0"
TILT_TOOL_KUBECTL_VERSION: "1.30.0"
TILT_TOOL_HELM_VERSION: "3.14.4"
TILT_TOOL_TILT_VERSION: "0.33.13"
run: |
# Create Tools Directory
TOOLS_DIR=/opt/helm_tools
mkdir -p "${TOOLS_DIR}"
# Download ctlptl
echo "Downloading ctlptl"
curl -fsSL https://github.com/tilt-dev/ctlptl/releases/download/v${TILT_TOOL_CTLPTL_VERSION}/ctlptl.${TILT_TOOL_CTLPTL_VERSION}.linux.x86_64.tar.gz | sudo tar -xzv -C "${TOOLS_DIR}" ctlptl
# Download kind
echo "Downloading kind"
curl -fsSL https://kind.sigs.k8s.io/dl/v${TILT_TOOL_KIND_VERSION}/kind-linux-amd64 -o "${TOOLS_DIR}/kind"
# Download kubectl
echo "Downloading kubectl"
curl -fsSL https://dl.k8s.io/release/v${TILT_TOOL_KUBECTL_VERSION}/bin/linux/amd64/kubectl -o "${TOOLS_DIR}/kubectl"
# Download helm
echo "Downloading helm"
curl -fsSL https://get.helm.sh/helm-v${TILT_TOOL_HELM_VERSION}-linux-amd64.tar.gz | tar -xzv -C "${TOOLS_DIR}" --strip-components=1 linux-amd64/helm
# Download tilt
echo "Downloading tilt"
curl -fsSL https://github.com/tilt-dev/tilt/releases/download/v${TILT_TOOL_TILT_VERSION}/tilt.${TILT_TOOL_TILT_VERSION}.linux.x86_64.tar.gz | tar -xzv -C "${TOOLS_DIR}" tilt
# Make the binaries runnable
echo "Making binaries executable"
sudo chmod -R +x "${TOOLS_DIR}"
# Add tools to path
echo "PATH=${PATH}:${TOOLS_DIR}" >> $GITHUB_ENV
- name: Start Kind Cluster
shell: bash
run: |
ctlptl apply -f kind.yaml
- name: Run Tilt Tests
shell: bash
run: |
tilt ci --debug --output-snapshot-on-exit=/tmp/tilt-snapshot.json
- name: Upload Tilt Snapshot
if: success() || failure()
uses: actions/upload-artifact@v6
with:
name: tilt-snapshot
path: |
/tmp/tilt-snapshot.json

View File

@@ -15,9 +15,9 @@ jobs:
packages: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
@@ -45,7 +45,6 @@ jobs:
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
@@ -63,13 +62,13 @@ jobs:
org.opencontainers.image.vendor=${{ github.repository_owner }}
- name: Build regular image
uses: docker/bake-action@v4
uses: docker/bake-action@v6
with:
targets: ci
push: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
files: |
./docker-bake.hcl
${{ steps.meta.outputs.bake-file }}
cwd://${{ steps.meta.outputs.bake-file }}
# https://github.com/docker/buildx/issues/2105
- name: Create manifest

1
.gitignore vendored
View File

@@ -91,3 +91,4 @@ ENV/
# Rope project settings
.ropeproject
.secrets

View File

@@ -1,6 +1,6 @@
ARG WINDOWS_BASE_IMAGE=mcr.microsoft.com/windows/nanoserver:ltcs2022
FROM --platform=$BUILDPLATFORM golang:1.21 as builder
FROM --platform=$BUILDPLATFORM golang:1.25 as builder
ARG TARGETARCH
ARG TARGETOS

View File

@@ -1,5 +1,5 @@
name ?= goldpinger
version ?= v3.10.0
version ?= v3.11.0
bin ?= goldpinger
pkg ?= "github.com/bloomberg/goldpinger"
tag = $(name):$(version)

View File

@@ -1,6 +1,7 @@
# Goldpinger
[![Publish](https://github.com/bloomberg/goldpinger/actions/workflows/publish.yml/badge.svg)](https://github.com/bloomberg/goldpinger/actions/workflows/publish.yml)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/bloomberg/goldpinger)
__Goldpinger__ makes calls between its instances to monitor your networking.
It runs as a [`DaemonSet`](#example-yaml) on `Kubernetes` and produces `Prometheus` metrics that can be [scraped](#prometheus), [visualised](#grafana) and [alerted](#alert-manager) on.
@@ -24,6 +25,7 @@ Oh, and it gives you the graph below for your cluster. Check out the [video expl
- [Authentication with Kubernetes API](#authentication-with-kubernetes-api)
- [Example YAML](#example-yaml)
- [Note on DNS](#note-on-dns)
- [UDP probe for packet loss, hop count, and RTT](#udp-probe-for-packet-loss-hop-count-and-rtt)
- [Usage](#usage)
- [UI](#ui)
- [API](#api)
@@ -101,14 +103,25 @@ docker push $(namespace="docker.io/myhandle/" make version)
```
## Installation
`Goldpinger` works by asking `Kubernetes` for pods with particular labels (`app=goldpinger`). While you can deploy `Goldpinger` in a variety of ways, it works very nicely as a `DaemonSet` out of the box.
### Authentication with Kubernetes API
### Helm Installation
Goldpinger can be installed via [Helm](https://helm.sh/) using the following:
```
helm repo add goldpinger https://bloomberg.github.io/goldpinger
helm repo update
helm install goldpinger goldpinger/goldpinger
```
### Manual Installation
`Goldpinger` can be installed manually via configuration similar to the following:
#### Authentication with Kubernetes API
`Goldpinger` supports using a `kubeconfig` (specify with `--kubeconfig-path`) or service accounts.
### Example YAML
#### Example YAML
Here's an example of what you can do (using the in-cluster authentication to `Kubernetes` apiserver).
@@ -118,6 +131,7 @@ apiVersion: v1
kind: ServiceAccount
metadata:
name: goldpinger-serviceaccount
namespace: default
---
apiVersion: apps/v1
kind: DaemonSet
@@ -274,10 +288,56 @@ Instances can also be configured to do simple TCP or HTTP checks on external tar
value: 10.34.5.141:5000 10.34.195.193:6442
```
the timeouts for the TCP, DNS and HTTP checks can be configured via `TCP_TARGETS_TIMEOUT`, `DNS_TARGETS_TIMEOUT` and `HTTP_TARGETS_TIMEOUT` respectively.
the timeouts for the TCP, DNS and HTTP checks can be configured via `TCP_TARGETS_TIMEOUT`, `DNS_TARGETS_TIMEOUT` and `HTTP_TARGETS_TIMEOUT` respectively.
![screenshot-tcp-http-checks](./extras/tcp-checks-screenshot.png)
### UDP probe for packet loss, hop count, and RTT
In natively routed Kubernetes environments (e.g. Cilium, Calico in BGP mode), the existing HTTP ping can mask network issues: TCP retransmits hide packet loss, and HTTP latency includes the 3-way handshake, TLS, and application overhead. The UDP probe gives you visibility into the actual network layer.
When enabled, each goldpinger pod runs a UDP echo listener. During each ping cycle, the prober sends a configurable number of sequenced UDP packets to each peer; the peer echoes them back. From the replies, goldpinger computes:
- **Packet loss** — percentage of packets that were not returned, surfacing degraded links before they impact applications
- **Hop count** — estimated from the IPv4 TTL or IPv6 HopLimit on received replies, useful for detecting asymmetric routing or unexpected topology changes
- **UDP RTT** — average round-trip time with sub-millisecond precision, isolating network latency from TCP/HTTP overhead
The feature is disabled by default and can be enabled with the following environment variables:
```sh
UDP_ENABLED=true # enable UDP probing and echo listener
UDP_PORT=6969 # listener port (default: 6969)
UDP_PACKET_COUNT=10 # packets per probe (default: 10)
UDP_PACKET_SIZE=64 # bytes per packet (default: 64)
UDP_TIMEOUT=1s # probe timeout (default: 1s)
```
Or via the Helm chart:
```yaml
goldpinger:
udp:
enabled: true
port: 6969
```
This adds three Prometheus metrics:
```sh
goldpinger_peers_loss_pct # gauge: UDP packet loss percentage (0-100)
goldpinger_peers_hop_count # gauge: estimated hop count
goldpinger_peers_udp_rtt_s # histogram: UDP round-trip time in seconds
goldpinger_udp_errors_total # counter: UDP probe errors
```
Links with partial loss are shown as yellow edges in the graph UI, and edge labels display the UDP RTT instead of HTTP latency when available.
![screenshot-udp-yellow-edges](./extras/udp-yellow-edges.png)
![screenshot-udp-grafana](./extras/udp-grafana-dashboards.png)
No new dependencies are required (`golang.org/x/net` is already in go.mod), and no additional container capabilities are needed.
## Usage
### UI
@@ -304,10 +364,12 @@ These are probably the droids you are looking for:
```sh
goldpinger_peers_response_time_s_*
goldpinger_peers_response_time_s_*
goldpinger_nodes_health_total
goldpinger_stats_total
goldpinger_errors_total
goldpinger_peers_loss_pct # (UDP probe, when enabled)
goldpinger_peers_hop_count # (UDP probe, when enabled)
goldpinger_peers_udp_rtt_s_* # (UDP probe, when enabled)
```
### Grafana

30
Tiltfile Normal file
View File

@@ -0,0 +1,30 @@
# -*- mode: bazel-starlark -*-
# Build the image
docker_build('goldpinger-local', '.')
# Deploy with Helm
k8s_yaml(
helm(
'charts/goldpinger',
set = [
# Set the image to the one built by Tilt
'image.repository=goldpinger-local',
],
)
)
# Track Goldpinger Resource
k8s_resource(
'chart-goldpinger',
port_forwards = [8080],
)
# Validate that all 2 nodes can talk to eachother
local_resource(
'check_reachability',
cmd='./extras/check_reachability.sh 2',
resource_deps = [
'chart-goldpinger',
],
)

View File

@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
OWNERS

View File

@@ -0,0 +1,8 @@
apiVersion: v1
name: goldpinger
appVersion: "3.11.0"
version: 1.1.0
description: Goldpinger is a tool to help debug, troubleshoot and visualize network connectivity and slowness issues.
home: https://github.com/bloomberg/goldpinger
sources:
- https://github.com/bloomberg/goldpinger

View File

@@ -0,0 +1,62 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "goldpinger.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "goldpinger.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "goldpinger.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "goldpinger.labels" -}}
helm.sh/chart: {{ include "goldpinger.chart" . }}
{{ include "goldpinger.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "goldpinger.selectorLabels" -}}
app.kubernetes.io/name: {{ include "goldpinger.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "goldpinger.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "goldpinger.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,12 @@
{{- if and .Values.rbac.create .Values.rbac.clusterscoped }}
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: {{ include "goldpinger.fullname" . }}-clusterrole
labels:
{{- include "goldpinger.labels" . | nindent 4 }}
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["list"]
{{- end }}

View File

@@ -0,0 +1,16 @@
{{- if and .Values.rbac.create .Values.rbac.clusterscoped }}
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: {{ include "goldpinger.fullname" . }}-clusterrolebinding
labels:
{{- include "goldpinger.labels" . | nindent 4 }}
subjects:
- kind: ServiceAccount
name: {{ include "goldpinger.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
roleRef:
kind: ClusterRole
name: {{ include "goldpinger.fullname" . }}-clusterrole
apiGroup: rbac.authorization.k8s.io
{{- end }}

View File

@@ -0,0 +1,8 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "goldpinger.fullname" . }}-zap
labels:
{{- include "goldpinger.labels" . | nindent 4 }}
data:
zap.json: {{ .Values.goldpinger.zapConfig | toJson }}

View File

@@ -0,0 +1,114 @@
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: {{ include "goldpinger.fullname" . }}
labels:
{{- include "goldpinger.labels" . | nindent 4 }}
spec:
{{- with .Values.updateStrategy }}
updateStrategy:
{{- toYaml . | nindent 4 }}
{{- end }}
selector:
matchLabels:
{{- include "goldpinger.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{ toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "goldpinger.selectorLabels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{ toYaml . | nindent 8 }}
{{- end }}
spec:
priorityClassName: {{ .Values.priorityClassName }}
serviceAccountName: {{ include "goldpinger.serviceAccountName" . }}
{{- if .Values.image.pullSecrets }}
imagePullSecrets:
{{- range .Values.image.pullSecrets }}
- name: {{ . }}
{{- end }}
{{- end }}
containers:
- name: goldpinger-daemon
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
volumeMounts:
- name: zap
mountPath: /config
env:
- name: HOSTNAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: HOST
value: "0.0.0.0"
- name: PORT
value: "{{ .Values.goldpinger.port }}"
- name: LABEL_SELECTOR
value: "app.kubernetes.io/name={{ include "goldpinger.name" . }}"
{{- if .Values.goldpinger.udp.enabled }}
- name: UDP_ENABLED
value: "true"
- name: UDP_PORT
value: "{{ .Values.goldpinger.udp.port }}"
{{- end }}
{{- if .Values.extraEnv -}}
{{ toYaml .Values.extraEnv | nindent 12 }}
{{- end }}
{{- with .Values.containerSecurityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
ports:
- name: http
containerPort: {{ .Values.goldpinger.port }}
protocol: TCP
{{- range $k := .Values.extraEnv }}
{{- if and (eq $k.name "USE_HOST_IP") (eq $k.value "true") }}
hostPort: {{ $.Values.goldpinger.port }}
{{- end }}
{{- end }}
{{- if .Values.goldpinger.udp.enabled }}
- name: udp-probe
containerPort: {{ .Values.goldpinger.udp.port }}
protocol: UDP
{{- end }}
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumes:
- name: zap
configMap:
name: {{ include "goldpinger.fullname" . }}-zap
{{- range $k := .Values.extraEnv }}
{{- if and (eq $k.name "USE_HOST_IP") (eq $k.value "true") }}
hostNetwork: true
{{- end }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@@ -0,0 +1,61 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "goldpinger.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
{{- end }}
{{- end }}
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{- include "goldpinger.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
{{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
pathType: {{ .pathType }}
{{- end }}
backend:
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
service:
name: {{ $fullName }}
port:
number: {{ $svcPort }}
{{- else }}
serviceName: {{ $fullName }}
servicePort: {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,19 @@
{{- if .Values.prometheusRule.enabled }}
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: {{ template "goldpinger.fullname" . }}
{{- if .Values.prometheusRule.namespace }}
namespace: {{ .Values.prometheusRule.namespace }}
{{- else }}
namespace: {{ .Release.Namespace | quote }}
{{- end }}
labels:
{{- include "goldpinger.labels" . | nindent 4 }}
spec:
{{- with .Values.prometheusRule.rules }}
groups:
- name: {{ template "goldpinger.name" $ }}
rules: {{- tpl (toYaml .) $ | nindent 8 }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,20 @@
{{- if or .Values.podSecurityPolicy.enabled (not .Values.rbac.clusterscoped) }}
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: {{ include "goldpinger.fullname" . }}-pod-security-policy
labels:
{{- include "goldpinger.labels" . | nindent 4 }}
rules:
{{- if not .Values.rbac.clusterscoped }}
- apiGroups: [""]
resources: ["pods"]
verbs: ["list"]
{{- end }}
{{- if .Values.podSecurityPolicy.enabled }}
- apiGroups: ["extensions"]
resources: ["podsecuritypolicies"]
resourceNames: [{{ .Values.podSecurityPolicy.policyName | quote }}]
verbs: ["use"]
{{- end }}
{{- end }}

View File

@@ -0,0 +1,16 @@
{{- if or .Values.podSecurityPolicy.enabled (not .Values.rbac.clusterscoped) }}
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: {{ include "goldpinger.fullname" . }}-pod-security-policy
labels:
{{- include "goldpinger.labels" . | nindent 4 }}
roleRef:
kind: Role
name: {{ include "goldpinger.fullname" . }}-pod-security-policy
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: {{ include "goldpinger.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end }}

View File

@@ -0,0 +1,32 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "goldpinger.fullname" . }}
labels:
{{- include "goldpinger.labels" . | nindent 4 }}
{{- with .Values.service.labels }}
{{ toYaml . | indent 4 }}
{{- end }}
{{- with .Values.service.annotations }}
annotations:
{{ toYaml . | indent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.goldpinger.port }}
protocol: TCP
name: http
{{- if .Values.goldpinger.udp.enabled }}
- port: {{ .Values.goldpinger.udp.port }}
targetPort: {{ .Values.goldpinger.udp.port }}
protocol: UDP
name: udp-probe
{{- end }}
selector:
{{- include "goldpinger.selectorLabels" . | nindent 4 }}
{{- if .Values.service.loadBalancerSourceRanges }}
loadBalancerSourceRanges:
{{- toYaml .Values.service.loadBalancerSourceRanges | nindent 4 }}
{{- end }}

View File

@@ -0,0 +1,8 @@
{{- if .Values.serviceAccount.create }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "goldpinger.serviceAccountName" . }}
labels:
{{- include "goldpinger.labels" . | nindent 4 }}
{{- end }}

View File

@@ -0,0 +1,32 @@
{{- if .Values.serviceMonitor.enabled }}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ include "goldpinger.fullname" . }}
{{- if .Values.serviceMonitor.namespace }}
namespace: {{ .Values.serviceMonitor.namespace }}
{{- end }}
labels:
{{- include "goldpinger.labels" . | nindent 4 }}
{{- range $key, $value := .Values.serviceMonitor.selector }}
{{ $key }}: {{ $value | quote }}
{{- end }}
spec:
endpoints:
- port: http
interval: {{ .Values.serviceMonitor.interval }}
{{- if .Values.serviceMonitor.honorLabels }}
honorLabels: true
{{- end }}
{{- with .Values.serviceMonitor.metricRelabelings }}
metricRelabelings:
{{- toYaml . | nindent 8 }}
{{- end }}
jobLabel: name
namespaceSelector:
matchNames:
- {{ .Release.Namespace }}
selector:
matchLabels:
{{- include "goldpinger.selectorLabels" . | nindent 6 }}
{{- end -}}

View File

@@ -0,0 +1,169 @@
# Default values for goldpinger.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
image:
repository: bloomberg/goldpinger
# Overrides the image tag whose default is the chart appVersion.
tag: ""
pullPolicy: IfNotPresent
## Optionally specify an array of imagePullSecrets.
## Secrets must be manually created in the namespace.
## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
##
# pullSecrets:
# - myRegistryKeySecretName
rbac:
create: true
clusterscoped: true
serviceAccount:
create: true
name:
goldpinger:
port: 8080
udp:
enabled: false
port: 6969
zapConfig: |
{
"level": "info",
"encoding": "json",
"outputPaths": [
"stdout"
],
"errorOutputPaths": [
"stderr"
],
"initialFields": {
},
"encoderConfig": {
"messageKey": "message",
"levelKey": "level",
"levelEncoder": "lowercase",
"timeKey": "ts",
"timeEncoder": "ISO8601",
"callerKey": "caller",
"callerEncoder": "Short"
}
}
extraEnv: []
service:
type: ClusterIP
port: 8081
annotations: {}
labels: {}
loadBalancerSourceRanges: {}
ingress:
enabled: false
className: ""
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: chart-example.local
paths:
- path: /
pathType: ImplementationSpecific
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
## Set a priorityClassName for the pod. If left blank a default priority will be set.
priorityClassName:
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
podAnnotations: {}
podLabels: {}
updateStrategy: {}
# type: RollingUpdate
# rollingUpdate:
# maxUnavailable: 1
## Node labels for pod assignment
## Ref: https://kubernetes.io/docs/user-guide/node-selection/
##
nodeSelector: {}
## Tolerations for pod assignment
## Ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
##
tolerations: []
## Affinity for pod assignment
## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity
##
affinity: {}
## Enable this if pod security policy enabled in your cluster
## It will bind ServiceAccount with unrestricted podSecurityPolicy
## Ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/
podSecurityPolicy:
enabled: false
policyName: unrestricted-psp
## Set security context of the goldpinger container
## Ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/
containerSecurityContext:
capabilities:
drop:
- ALL
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
## Set security context of the pod
## Ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
serviceMonitor:
enabled: false
selector:
prometheus: "kube-prometheus"
# namespace: monitoring
interval: 30s
# honorLabels: true
metricRelabelings: []
# - action: drop
# source_labels: [__name__]
# regex: goldpinger_peers_response_time_s_bucket
## Custom PrometheusRule to be defined
## ref: https://github.com/coreos/prometheus-operator#customresourcedefinitions
prometheusRule:
enabled: false
rules:
- alert: goldpinger_nodes_unhealthy
expr: |
sum(goldpinger_nodes_health_total{job="{{ template "goldpinger.fullname" . }}", status="unhealthy"})
BY (instance, goldpinger_instance) > 0
for: 5m
annotations:
description: |
Goldpinger instance {{ "{{ $labels.goldpinger_instance }}" }} has been reporting unhealthy nodes for at least 5 minutes.
summary: Instance {{ "{{ $labels.instance }}" }} down
labels:
severity: warning

View File

@@ -186,6 +186,10 @@ func main() {
server.ConfigureAPI()
goldpinger.StartUpdater()
if goldpinger.GoldpingerConfig.UDPEnabled {
go goldpinger.StartUDPListener(goldpinger.GoldpingerConfig.UDPPort)
}
logger.Info("All good, starting serving the API")
// serve API

84
extras/check_reachability.sh Executable file
View File

@@ -0,0 +1,84 @@
#!/bin/bash
# This is a simple script, used in the Tilt GitHub Action
# to validate that all worker nodes can inter-communicate.
function print_help() {
echo "Usage: $0 [EXPECTED_HOST_COUNT]"
echo "Arguments:"
echo " EXPECTED_HOST_COUNT: The number of expected hosts in the Goldpinger output."
echo "Examples:"
echo " $0 2"
}
if [ "$#" -ne 1 ]; then
echo "Error: Invalid number of arguments."
print_help
exit 1
fi
if ! [[ $1 =~ ^[0-9]+$ ]]; then
echo "Error: EXPECTED_HOST_COUNT must be a number."
print_help
exit 1
fi
expected_host_count=$1
goldpinger_output=""
retry_count=0
host_count=0
while :; do
if [ "$retry_count" -ge 20 ]; then
echo "Error: Failed to fetch Goldpinger output after 10 attempts."
exit 1
fi
echo "Sleeping for 8s..."
let retry_count++
sleep 8
echo "Attempt $((retry_count)) to fetch Goldpinger output."
goldpinger_output=$(curl -s http://localhost:8080/check_all)
echo "Goldpinger output: $goldpinger_output"
if [ "$goldpinger_output" == "null" ] || [ -z "$goldpinger_output" ]; then
echo "Goldpinger output is null or empty, retrying..."
continue
fi
host_count=$(echo "$goldpinger_output" | jq '.hosts | length')
if [ "$host_count" -ne "$expected_host_count" ]; then
echo "Goldpinger has not identified all hosts, retrying..."
continue
fi
for host in $(echo $goldpinger_output | jq -r '.responses | keys[]'); do
checksForPod=$(echo "$goldpinger_output" | jq -r --arg host "$host" '.responses[$host].response.podResults | length')
if [ "$checksForPod" -ne "$expected_host_count" ]; then
echo "Check for $host is not OK, retrying..."
continue 2
fi
done
break
done
all_hosts_can_talk=true
for host in $(echo $goldpinger_output | jq -r '.responses | keys[]'); do
for target in $(echo $goldpinger_output | jq -r --arg host $host '.responses[$host].response.podResults | keys[]'); do
ok=$(echo $goldpinger_output | jq -r --arg host $host --arg target $target '.responses[$host].response.podResults[$target].OK')
if [ "$ok" != "true" ]; then
all_hosts_can_talk=false
break 2
fi
done
done
if [[ $host_count -eq $expected_host_count ]] && [[ $all_hosts_can_talk == "true" ]]; then
echo "Validation successful. There are $expected_host_count hosts and they can talk to each other."
else
echo "Validation failed. Expected $expected_host_count hosts but found $host_count, or not all hosts can talk to each other."
echo "Goldpinger Output: $goldpinger_output"
exit 1
fi

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

BIN
extras/udp-yellow-edges.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

118
go.mod
View File

@@ -1,79 +1,97 @@
module github.com/bloomberg/goldpinger/v3
go 1.21
go 1.25.0
require (
github.com/cespare/xxhash v1.1.0
github.com/go-openapi/errors v0.20.4
github.com/go-openapi/loads v0.21.2
github.com/go-openapi/runtime v0.26.0
github.com/go-openapi/spec v0.20.9
github.com/go-openapi/strfmt v0.21.7
github.com/go-openapi/swag v0.22.4
github.com/go-openapi/validate v0.22.1
github.com/jessevdk/go-flags v1.5.0
github.com/prometheus/client_golang v1.17.0
github.com/go-openapi/errors v0.22.6
github.com/go-openapi/loads v0.23.2
github.com/go-openapi/runtime v0.29.2
github.com/go-openapi/spec v0.22.3
github.com/go-openapi/strfmt v0.25.0
github.com/go-openapi/swag v0.25.4
github.com/go-openapi/validate v0.25.1
github.com/jessevdk/go-flags v1.6.1
github.com/prometheus/client_golang v1.23.2
github.com/stuartnelson3/go-rendezvous v0.2.0
go.uber.org/zap v1.26.0
golang.org/x/image v0.13.0
golang.org/x/net v0.17.0
k8s.io/api v0.28.3
k8s.io/apimachinery v0.28.3
k8s.io/client-go v0.28.3
k8s.io/utils v0.0.0-20230726121419-3b25d923346b
go.uber.org/zap v1.27.1
golang.org/x/image v0.35.0
golang.org/x/net v0.49.0
k8s.io/api v0.35.0
k8s.io/apimachinery v0.35.0
k8s.io/client-go v0.35.0
k8s.io/utils v0.0.0-20260108192941-914a6e750570
)
require (
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.21.4 // indirect
github.com/go-openapi/jsonpointer v0.20.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/analysis v0.24.2 // indirect
github.com/go-openapi/jsonpointer v0.22.4 // indirect
github.com/go-openapi/jsonreference v0.21.4 // indirect
github.com/go-openapi/swag/cmdutils v0.25.4 // indirect
github.com/go-openapi/swag/conv v0.25.4 // indirect
github.com/go-openapi/swag/fileutils v0.25.4 // indirect
github.com/go-openapi/swag/jsonname v0.25.4 // indirect
github.com/go-openapi/swag/jsonutils v0.25.4 // indirect
github.com/go-openapi/swag/loading v0.25.4 // indirect
github.com/go-openapi/swag/mangling v0.25.4 // indirect
github.com/go-openapi/swag/netutils v0.25.4 // indirect
github.com/go-openapi/swag/stringutils v0.25.4 // indirect
github.com/go-openapi/swag/typeutils v0.25.4 // indirect
github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.7.1 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/mailru/easyjson v0.9.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
go.mongodb.org/mongo-driver v1.12.1 // indirect
go.opentelemetry.io/otel v1.19.0 // indirect
go.opentelemetry.io/otel/metric v1.19.0 // indirect
go.opentelemetry.io/otel/trace v1.19.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.19.2 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.mongodb.org/mongo-driver v1.17.7 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel v1.39.0 // indirect
go.opentelemetry.io/otel/metric v1.39.0 // indirect
go.opentelemetry.io/otel/trace v1.39.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/oauth2 v0.13.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/protobuf v1.31.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/term v0.39.0 // indirect
golang.org/x/text v0.33.0 // indirect
golang.org/x/time v0.14.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.100.1 // indirect
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
)

482
go.sum
View File

@@ -1,108 +1,105 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk=
github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY=
github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc=
github.com/go-openapi/analysis v0.21.4/go.mod h1:4zQ35W4neeZTqh3ol0rv/O8JBbka9QyAgQRPp9y3pfo=
github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/errors v0.20.4 h1:unTcVm6PispJsMECE3zWgvG4xTiKda1LIR5rCRWLG6M=
github.com/go-openapi/errors v0.20.4/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ=
github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g=
github.com/go-openapi/loads v0.21.2 h1:r2a/xFIYeZ4Qd2TnGpWDIQNcP80dIaZgf704za8enro=
github.com/go-openapi/loads v0.21.2/go.mod h1:Jq58Os6SSGz0rzh62ptiu8Z31I+OTHqmULx5e/gJbNw=
github.com/go-openapi/runtime v0.26.0 h1:HYOFtG00FM1UvqrcxbEJg/SwvDRvYLQKGhw2zaQjTcc=
github.com/go-openapi/runtime v0.26.0/go.mod h1:QgRGeZwrUcSHdeh4Ka9Glvo0ug1LC5WyE+EV88plZrQ=
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8=
github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg=
github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k=
github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg=
github.com/go-openapi/strfmt v0.21.7 h1:rspiXgNWgeUzhjo1YU01do6qsahtJNByjLVbPLNHb8k=
github.com/go-openapi/strfmt v0.21.7/go.mod h1:adeGTkxE44sPyLk0JV235VQAO/ZXUr8KAzYjclFs3ew=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/validate v0.22.1 h1:G+c2ub6q47kfX1sOBLwIQwzBVt8qmOAARyo/9Fqs9NU=
github.com/go-openapi/validate v0.22.1/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
github.com/go-openapi/analysis v0.24.2 h1:6p7WXEuKy1llDgOH8FooVeO+Uq2za9qoAOq4ZN08B50=
github.com/go-openapi/analysis v0.24.2/go.mod h1:x27OOHKANE0lutg2ml4kzYLoHGMKgRm1Cj2ijVOjJuE=
github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w=
github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE=
github.com/go-openapi/errors v0.22.6 h1:eDxcf89O8odEnohIXwEjY1IB4ph5vmbUsBMsFNwXWPo=
github.com/go-openapi/errors v0.22.6/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4=
github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8=
github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4=
github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco=
github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs=
github.com/go-openapi/loads v0.23.2 h1:rJXAcP7g1+lWyBHC7iTY+WAF0rprtM+pm8Jxv1uQJp4=
github.com/go-openapi/loads v0.23.2/go.mod h1:IEVw1GfRt/P2Pplkelxzj9BYFajiWOtY2nHZNj4UnWY=
github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ=
github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc=
github.com/go-openapi/runtime v0.29.2 h1:UmwSGWNmWQqKm1c2MGgXVpC2FTGwPDQeUsBMufc5Yj0=
github.com/go-openapi/runtime v0.29.2/go.mod h1:biq5kJXRJKBJxTDJXAa00DOTa/anflQPhT0/wmjuy+0=
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/go-openapi/spec v0.22.3 h1:qRSmj6Smz2rEBxMnLRBMeBWxbbOvuOoElvSvObIgwQc=
github.com/go-openapi/spec v0.22.3/go.mod h1:iIImLODL2loCh3Vnox8TY2YWYJZjMAKYyLH2Mu8lOZs=
github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c=
github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
github.com/go-openapi/strfmt v0.25.0 h1:7R0RX7mbKLa9EYCTHRcCuIPcaqlyQiWNPTXwClK0saQ=
github.com/go-openapi/strfmt v0.25.0/go.mod h1:nNXct7OzbwrMY9+5tLX4I21pzcmE6ccMGXl3jFdPfn8=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU=
github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ=
github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4=
github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0=
github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4=
github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU=
github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y=
github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk=
github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI=
github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=
github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA=
github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY=
github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s=
github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE=
github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48=
github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg=
github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0=
github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg=
github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8=
github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0=
github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw=
github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE=
github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw=
github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc=
github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
github.com/go-openapi/validate v0.25.1 h1:sSACUI6Jcnbo5IWqbYHgjibrhhmt3vR6lCzKZnmAgBw=
github.com/go-openapi/validate v0.25.1/go.mod h1:RMVyVFYte0gbSTaZ0N4KmTn6u/kClvAFp+mAVfS/DQc=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs=
github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo=
github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk=
github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw=
github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg=
github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE=
github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c=
github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@@ -111,45 +108,28 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.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/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4=
github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
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=
@@ -157,211 +137,201 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE=
github.com/onsi/ginkgo/v2 v2.9.4/go.mod h1:gCQYp2Q+kSoIj7ykSVb9nskRSsR6PUj4AiLywzIhbKM=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
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.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
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.52.3 h1:5f8uj6ZwHSscOGNdIQg6OiZv/ybiK2CO2q2drVZAQSA=
github.com/prometheus/common v0.52.3/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U=
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.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o=
github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stuartnelson3/go-rendezvous v0.2.0 h1:H5IexrsptBzCMQEjTRrNH20MVXGqpFf1JUCPglaxd6I=
github.com/stuartnelson3/go-rendezvous v0.2.0/go.mod h1:njfgP6zISyRnZ3iQN13NSEILfSNLN4ysxBoGxHs5PJ0=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg=
go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng=
go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8=
go.mongodb.org/mongo-driver v1.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecqgE=
go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ=
go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs=
go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY=
go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE=
go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8=
go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY=
go.opentelemetry.io/otel/sdk v1.14.0/go.mod h1:bwIC5TjrNG6QDCHNWvW4HLHtUQ4I+VQDsnjhvyZCALM=
go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg=
go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc=
go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
go.mongodb.org/mongo-driver v1.17.7 h1:a9w+U3Vt67eYzcfq3k/OAv284/uUUkL0uP75VE5rCOU=
go.mongodb.org/mongo-driver v1.17.7/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k=
go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA=
go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM=
go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg=
golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78=
golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I=
golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk=
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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-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-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY=
golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/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-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
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.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/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=
k8s.io/api v0.28.3 h1:Gj1HtbSdB4P08C8rs9AR94MfSGpRhJgsS+GF9V26xMM=
k8s.io/api v0.28.3/go.mod h1:MRCV/jr1dW87/qJnZ57U5Pak65LGmQVkKTzf3AtKFHc=
k8s.io/apimachinery v0.28.3 h1:B1wYx8txOaCQG0HmYF6nbpU8dg6HvA06x5tEffvOe7A=
k8s.io/apimachinery v0.28.3/go.mod h1:uQTKmIqs+rAYaq+DFaoD2X7pcjLOqbQX2AOiO0nIpb8=
k8s.io/client-go v0.28.3 h1:2OqNb72ZuTZPKCl+4gTKvqao0AMOl9f3o2ijbAj3LI4=
k8s.io/client-go v0.28.3/go.mod h1:LTykbBp9gsA7SwqirlCXBWtK0guzfhpoW4qSm7i9dxo=
k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw=
k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80=
k8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY=
k8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA=
k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU=
k8s.io/apimachinery v0.29.3/go.mod h1:hx/S4V2PNW4OMg3WizRrHutyB5la0iCUbZym+W0EQIU=
k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8=
k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
k8s.io/client-go v0.29.3 h1:R/zaZbEAxqComZ9FHeQwOh3Y1ZUs7FaHKZdQtIc2WZg=
k8s.io/client-go v0.29.3/go.mod h1:tkDisCvgPfiRpxGnOORfkljmS+UrW+WtXAy2fTvXJB0=
k8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE=
k8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o=
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3 h1:SbdLaI6mM6ffDSJCadEaD4IkuPzepLDGlkd2xV0t1uA=
k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4 h1:HhDfevmPS+OalTjQRKbTHppRIz01AWi8s45TMXStgYY=
k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
k8s.io/utils v0.0.0-20240310230437-4693a0247e57 h1:gbqbevonBh57eILzModw6mrkbwM0gQBEuevE/AaBsHY=
k8s.io/utils v0.0.0-20240310230437-4693a0247e57/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
k8s.io/utils v0.0.0-20260108192941-914a6e750570 h1:JT4W8lsdrGENg9W+YwwdLJxklIuKWdRm+BC+xt33FOY=
k8s.io/utils v0.0.0-20260108192941-914a6e750570/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk=
sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E=
sigs.k8s.io/structured-merge-diff/v6 v6.3.1/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

17
kind.yaml Normal file
View File

@@ -0,0 +1,17 @@
apiVersion: ctlptl.dev/v1alpha1
kind: Registry
name: ctlptl-registry
port: 20021
---
apiVersion: ctlptl.dev/v1alpha1
kind: Cluster
product: kind
registry: ctlptl-registry
kindV1Alpha4Cluster:
name: goldpinger-test
nodes:
- role: control-plane
- role: worker
- role: worker
networking:
apiServerPort: 30022

View File

@@ -2,14 +2,12 @@
package client
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"github.com/bloomberg/goldpinger/v3/pkg/client/operations"
"github.com/go-openapi/runtime"
httptransport "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
"github.com/bloomberg/goldpinger/v3/pkg/client/operations"
)
// Default goldpinger HTTP client.

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
@@ -52,10 +49,12 @@ func NewCheckAllPodsParamsWithHTTPClient(client *http.Client) *CheckAllPodsParam
}
}
/* CheckAllPodsParams contains all the parameters to send to the API endpoint
for the check all pods operation.
/*
CheckAllPodsParams contains all the parameters to send to the API endpoint
Typically these are written to a http.Request.
for the check all pods operation.
Typically these are written to a http.Request.
*/
type CheckAllPodsParams struct {
timeout time.Duration

View File

@@ -2,10 +2,9 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
stderrors "errors"
"fmt"
"io"
@@ -21,7 +20,7 @@ type CheckAllPodsReader struct {
}
// ReadResponse reads a server response into the received o.
func (o *CheckAllPodsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
func (o *CheckAllPodsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) {
switch response.Code() {
case 200:
result := NewCheckAllPodsOK()
@@ -30,7 +29,7 @@ func (o *CheckAllPodsReader) ReadResponse(response runtime.ClientResponse, consu
}
return result, nil
default:
return nil, runtime.NewAPIError("response status code does not match any response statuses defined for this endpoint in the swagger spec", response, response.Code())
return nil, runtime.NewAPIError("[GET /check_all] checkAllPods", response, response.Code())
}
}
@@ -39,7 +38,8 @@ func NewCheckAllPodsOK() *CheckAllPodsOK {
return &CheckAllPodsOK{}
}
/* CheckAllPodsOK describes a response with status code 200, with default header values.
/*
CheckAllPodsOK describes a response with status code 200, with default header values.
Success, return response
*/
@@ -47,9 +47,46 @@ type CheckAllPodsOK struct {
Payload *models.CheckAllResults
}
func (o *CheckAllPodsOK) Error() string {
return fmt.Sprintf("[GET /check_all][%d] checkAllPodsOK %+v", 200, o.Payload)
// IsSuccess returns true when this check all pods o k response has a 2xx status code
func (o *CheckAllPodsOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this check all pods o k response has a 3xx status code
func (o *CheckAllPodsOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this check all pods o k response has a 4xx status code
func (o *CheckAllPodsOK) IsClientError() bool {
return false
}
// IsServerError returns true when this check all pods o k response has a 5xx status code
func (o *CheckAllPodsOK) IsServerError() bool {
return false
}
// IsCode returns true when this check all pods o k response a status code equal to that given
func (o *CheckAllPodsOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the check all pods o k response
func (o *CheckAllPodsOK) Code() int {
return 200
}
func (o *CheckAllPodsOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /check_all][%d] checkAllPodsOK %s", 200, payload)
}
func (o *CheckAllPodsOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /check_all][%d] checkAllPodsOK %s", 200, payload)
}
func (o *CheckAllPodsOK) GetPayload() *models.CheckAllResults {
return o.Payload
}
@@ -59,7 +96,7 @@ func (o *CheckAllPodsOK) readResponse(response runtime.ClientResponse, consumer
o.Payload = new(models.CheckAllResults)
// response payload
if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) {
return err
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
@@ -52,10 +49,12 @@ func NewCheckServicePodsParamsWithHTTPClient(client *http.Client) *CheckServiceP
}
}
/* CheckServicePodsParams contains all the parameters to send to the API endpoint
for the check service pods operation.
/*
CheckServicePodsParams contains all the parameters to send to the API endpoint
Typically these are written to a http.Request.
for the check service pods operation.
Typically these are written to a http.Request.
*/
type CheckServicePodsParams struct {
timeout time.Duration

View File

@@ -2,10 +2,9 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
stderrors "errors"
"fmt"
"io"
@@ -21,7 +20,7 @@ type CheckServicePodsReader struct {
}
// ReadResponse reads a server response into the received o.
func (o *CheckServicePodsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
func (o *CheckServicePodsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) {
switch response.Code() {
case 200:
result := NewCheckServicePodsOK()
@@ -30,7 +29,7 @@ func (o *CheckServicePodsReader) ReadResponse(response runtime.ClientResponse, c
}
return result, nil
default:
return nil, runtime.NewAPIError("response status code does not match any response statuses defined for this endpoint in the swagger spec", response, response.Code())
return nil, runtime.NewAPIError("[GET /check] checkServicePods", response, response.Code())
}
}
@@ -39,7 +38,8 @@ func NewCheckServicePodsOK() *CheckServicePodsOK {
return &CheckServicePodsOK{}
}
/* CheckServicePodsOK describes a response with status code 200, with default header values.
/*
CheckServicePodsOK describes a response with status code 200, with default header values.
Success, return response
*/
@@ -47,9 +47,46 @@ type CheckServicePodsOK struct {
Payload *models.CheckResults
}
func (o *CheckServicePodsOK) Error() string {
return fmt.Sprintf("[GET /check][%d] checkServicePodsOK %+v", 200, o.Payload)
// IsSuccess returns true when this check service pods o k response has a 2xx status code
func (o *CheckServicePodsOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this check service pods o k response has a 3xx status code
func (o *CheckServicePodsOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this check service pods o k response has a 4xx status code
func (o *CheckServicePodsOK) IsClientError() bool {
return false
}
// IsServerError returns true when this check service pods o k response has a 5xx status code
func (o *CheckServicePodsOK) IsServerError() bool {
return false
}
// IsCode returns true when this check service pods o k response a status code equal to that given
func (o *CheckServicePodsOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the check service pods o k response
func (o *CheckServicePodsOK) Code() int {
return 200
}
func (o *CheckServicePodsOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /check][%d] checkServicePodsOK %s", 200, payload)
}
func (o *CheckServicePodsOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /check][%d] checkServicePodsOK %s", 200, payload)
}
func (o *CheckServicePodsOK) GetPayload() *models.CheckResults {
return o.Payload
}
@@ -59,7 +96,7 @@ func (o *CheckServicePodsOK) readResponse(response runtime.ClientResponse, consu
o.Payload = new(models.CheckResults)
// response payload
if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) {
return err
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
@@ -52,10 +49,12 @@ func NewClusterHealthParamsWithHTTPClient(client *http.Client) *ClusterHealthPar
}
}
/* ClusterHealthParams contains all the parameters to send to the API endpoint
for the cluster health operation.
/*
ClusterHealthParams contains all the parameters to send to the API endpoint
Typically these are written to a http.Request.
for the cluster health operation.
Typically these are written to a http.Request.
*/
type ClusterHealthParams struct {
timeout time.Duration

View File

@@ -2,10 +2,9 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
stderrors "errors"
"fmt"
"io"
@@ -21,7 +20,7 @@ type ClusterHealthReader struct {
}
// ReadResponse reads a server response into the received o.
func (o *ClusterHealthReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
func (o *ClusterHealthReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) {
switch response.Code() {
case 200:
result := NewClusterHealthOK()
@@ -36,7 +35,7 @@ func (o *ClusterHealthReader) ReadResponse(response runtime.ClientResponse, cons
}
return nil, result
default:
return nil, runtime.NewAPIError("response status code does not match any response statuses defined for this endpoint in the swagger spec", response, response.Code())
return nil, runtime.NewAPIError("[GET /cluster_health] clusterHealth", response, response.Code())
}
}
@@ -45,7 +44,8 @@ func NewClusterHealthOK() *ClusterHealthOK {
return &ClusterHealthOK{}
}
/* ClusterHealthOK describes a response with status code 200, with default header values.
/*
ClusterHealthOK describes a response with status code 200, with default header values.
Healthy cluster
*/
@@ -53,9 +53,46 @@ type ClusterHealthOK struct {
Payload *models.ClusterHealthResults
}
func (o *ClusterHealthOK) Error() string {
return fmt.Sprintf("[GET /cluster_health][%d] clusterHealthOK %+v", 200, o.Payload)
// IsSuccess returns true when this cluster health o k response has a 2xx status code
func (o *ClusterHealthOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this cluster health o k response has a 3xx status code
func (o *ClusterHealthOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this cluster health o k response has a 4xx status code
func (o *ClusterHealthOK) IsClientError() bool {
return false
}
// IsServerError returns true when this cluster health o k response has a 5xx status code
func (o *ClusterHealthOK) IsServerError() bool {
return false
}
// IsCode returns true when this cluster health o k response a status code equal to that given
func (o *ClusterHealthOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the cluster health o k response
func (o *ClusterHealthOK) Code() int {
return 200
}
func (o *ClusterHealthOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /cluster_health][%d] clusterHealthOK %s", 200, payload)
}
func (o *ClusterHealthOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /cluster_health][%d] clusterHealthOK %s", 200, payload)
}
func (o *ClusterHealthOK) GetPayload() *models.ClusterHealthResults {
return o.Payload
}
@@ -65,7 +102,7 @@ func (o *ClusterHealthOK) readResponse(response runtime.ClientResponse, consumer
o.Payload = new(models.ClusterHealthResults)
// response payload
if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) {
return err
}
@@ -77,7 +114,8 @@ func NewClusterHealthIMATeapot() *ClusterHealthIMATeapot {
return &ClusterHealthIMATeapot{}
}
/* ClusterHealthIMATeapot describes a response with status code 418, with default header values.
/*
ClusterHealthIMATeapot describes a response with status code 418, with default header values.
Unhealthy cluster
*/
@@ -85,9 +123,46 @@ type ClusterHealthIMATeapot struct {
Payload *models.ClusterHealthResults
}
func (o *ClusterHealthIMATeapot) Error() string {
return fmt.Sprintf("[GET /cluster_health][%d] clusterHealthIMATeapot %+v", 418, o.Payload)
// IsSuccess returns true when this cluster health i m a teapot response has a 2xx status code
func (o *ClusterHealthIMATeapot) IsSuccess() bool {
return false
}
// IsRedirect returns true when this cluster health i m a teapot response has a 3xx status code
func (o *ClusterHealthIMATeapot) IsRedirect() bool {
return false
}
// IsClientError returns true when this cluster health i m a teapot response has a 4xx status code
func (o *ClusterHealthIMATeapot) IsClientError() bool {
return true
}
// IsServerError returns true when this cluster health i m a teapot response has a 5xx status code
func (o *ClusterHealthIMATeapot) IsServerError() bool {
return false
}
// IsCode returns true when this cluster health i m a teapot response a status code equal to that given
func (o *ClusterHealthIMATeapot) IsCode(code int) bool {
return code == 418
}
// Code gets the status code for the cluster health i m a teapot response
func (o *ClusterHealthIMATeapot) Code() int {
return 418
}
func (o *ClusterHealthIMATeapot) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /cluster_health][%d] clusterHealthIMATeapot %s", 418, payload)
}
func (o *ClusterHealthIMATeapot) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /cluster_health][%d] clusterHealthIMATeapot %s", 418, payload)
}
func (o *ClusterHealthIMATeapot) GetPayload() *models.ClusterHealthResults {
return o.Payload
}
@@ -97,7 +172,7 @@ func (o *ClusterHealthIMATeapot) readResponse(response runtime.ClientResponse, c
o.Payload = new(models.ClusterHealthResults)
// response payload
if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) {
return err
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
@@ -52,10 +49,12 @@ func NewHealthzParamsWithHTTPClient(client *http.Client) *HealthzParams {
}
}
/* HealthzParams contains all the parameters to send to the API endpoint
for the healthz operation.
/*
HealthzParams contains all the parameters to send to the API endpoint
Typically these are written to a http.Request.
for the healthz operation.
Typically these are written to a http.Request.
*/
type HealthzParams struct {
timeout time.Duration

View File

@@ -2,10 +2,9 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
stderrors "errors"
"fmt"
"io"
@@ -21,7 +20,7 @@ type HealthzReader struct {
}
// ReadResponse reads a server response into the received o.
func (o *HealthzReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
func (o *HealthzReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) {
switch response.Code() {
case 200:
result := NewHealthzOK()
@@ -36,7 +35,7 @@ func (o *HealthzReader) ReadResponse(response runtime.ClientResponse, consumer r
}
return nil, result
default:
return nil, runtime.NewAPIError("response status code does not match any response statuses defined for this endpoint in the swagger spec", response, response.Code())
return nil, runtime.NewAPIError("[GET /healthz] healthz", response, response.Code())
}
}
@@ -45,7 +44,8 @@ func NewHealthzOK() *HealthzOK {
return &HealthzOK{}
}
/* HealthzOK describes a response with status code 200, with default header values.
/*
HealthzOK describes a response with status code 200, with default header values.
Health check report
*/
@@ -53,9 +53,46 @@ type HealthzOK struct {
Payload *models.HealthCheckResults
}
func (o *HealthzOK) Error() string {
return fmt.Sprintf("[GET /healthz][%d] healthzOK %+v", 200, o.Payload)
// IsSuccess returns true when this healthz o k response has a 2xx status code
func (o *HealthzOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this healthz o k response has a 3xx status code
func (o *HealthzOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this healthz o k response has a 4xx status code
func (o *HealthzOK) IsClientError() bool {
return false
}
// IsServerError returns true when this healthz o k response has a 5xx status code
func (o *HealthzOK) IsServerError() bool {
return false
}
// IsCode returns true when this healthz o k response a status code equal to that given
func (o *HealthzOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the healthz o k response
func (o *HealthzOK) Code() int {
return 200
}
func (o *HealthzOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /healthz][%d] healthzOK %s", 200, payload)
}
func (o *HealthzOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /healthz][%d] healthzOK %s", 200, payload)
}
func (o *HealthzOK) GetPayload() *models.HealthCheckResults {
return o.Payload
}
@@ -65,7 +102,7 @@ func (o *HealthzOK) readResponse(response runtime.ClientResponse, consumer runti
o.Payload = new(models.HealthCheckResults)
// response payload
if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) {
return err
}
@@ -77,7 +114,8 @@ func NewHealthzServiceUnavailable() *HealthzServiceUnavailable {
return &HealthzServiceUnavailable{}
}
/* HealthzServiceUnavailable describes a response with status code 503, with default header values.
/*
HealthzServiceUnavailable describes a response with status code 503, with default header values.
Unhealthy service
*/
@@ -85,9 +123,46 @@ type HealthzServiceUnavailable struct {
Payload *models.HealthCheckResults
}
func (o *HealthzServiceUnavailable) Error() string {
return fmt.Sprintf("[GET /healthz][%d] healthzServiceUnavailable %+v", 503, o.Payload)
// IsSuccess returns true when this healthz service unavailable response has a 2xx status code
func (o *HealthzServiceUnavailable) IsSuccess() bool {
return false
}
// IsRedirect returns true when this healthz service unavailable response has a 3xx status code
func (o *HealthzServiceUnavailable) IsRedirect() bool {
return false
}
// IsClientError returns true when this healthz service unavailable response has a 4xx status code
func (o *HealthzServiceUnavailable) IsClientError() bool {
return false
}
// IsServerError returns true when this healthz service unavailable response has a 5xx status code
func (o *HealthzServiceUnavailable) IsServerError() bool {
return true
}
// IsCode returns true when this healthz service unavailable response a status code equal to that given
func (o *HealthzServiceUnavailable) IsCode(code int) bool {
return code == 503
}
// Code gets the status code for the healthz service unavailable response
func (o *HealthzServiceUnavailable) Code() int {
return 503
}
func (o *HealthzServiceUnavailable) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /healthz][%d] healthzServiceUnavailable %s", 503, payload)
}
func (o *HealthzServiceUnavailable) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /healthz][%d] healthzServiceUnavailable %s", 503, payload)
}
func (o *HealthzServiceUnavailable) GetPayload() *models.HealthCheckResults {
return o.Payload
}
@@ -97,7 +172,7 @@ func (o *HealthzServiceUnavailable) readResponse(response runtime.ClientResponse
o.Payload = new(models.HealthCheckResults)
// response payload
if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) {
return err
}

View File

@@ -2,13 +2,11 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"fmt"
"github.com/go-openapi/runtime"
httptransport "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
)
@@ -17,6 +15,31 @@ func New(transport runtime.ClientTransport, formats strfmt.Registry) ClientServi
return &Client{transport: transport, formats: formats}
}
// New creates a new operations API client with basic auth credentials.
// It takes the following parameters:
// - host: http host (github.com).
// - basePath: any base path for the API client ("/v1", "/v3").
// - scheme: http scheme ("http", "https").
// - user: user for basic authentication header.
// - password: password for basic authentication header.
func NewClientWithBasicAuth(host, basePath, scheme, user, password string) ClientService {
transport := httptransport.New(host, basePath, []string{scheme})
transport.DefaultAuthentication = httptransport.BasicAuth(user, password)
return &Client{transport: transport, formats: strfmt.Default}
}
// New creates a new operations API client with a bearer token for authentication.
// It takes the following parameters:
// - host: http host (github.com).
// - basePath: any base path for the API client ("/v1", "/v3").
// - scheme: http scheme ("http", "https").
// - bearerToken: bearer token for Bearer authentication header.
func NewClientWithBearerToken(host, basePath, scheme, bearerToken string) ClientService {
transport := httptransport.New(host, basePath, []string{scheme})
transport.DefaultAuthentication = httptransport.BearerToken(bearerToken)
return &Client{transport: transport, formats: strfmt.Default}
}
/*
Client for operations API
*/
@@ -25,7 +48,7 @@ type Client struct {
formats strfmt.Registry
}
// ClientOption is the option for Client methods
// ClientOption may be used to customize the behavior of Client methods.
type ClientOption func(*runtime.ClientOperation)
// ClientService is the interface for Client methods
@@ -44,10 +67,10 @@ type ClientService interface {
}
/*
CheckAllPods Queries the API server for all other pods in this service, and makes all of them query all of their neighbours, using their pods IPs. Calls their /check endpoint.
CheckAllPods Queries the API server for all other pods in this service, and makes all of them query all of their neighbours, using their pods IPs. Calls their /check endpoint.
*/
func (a *Client) CheckAllPods(params *CheckAllPodsParams, opts ...ClientOption) (*CheckAllPodsOK, error) {
// TODO: Validate the params before sending
// NOTE: parameters are not validated before sending
if params == nil {
params = NewCheckAllPodsParams()
}
@@ -66,26 +89,31 @@ func (a *Client) CheckAllPods(params *CheckAllPodsParams, opts ...ClientOption)
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
// only one success response has to be checked
success, ok := result.(*CheckAllPodsOK)
if ok {
return success, nil
}
// unexpected success response
// safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue
// unexpected success response.
// no default response is defined.
//
// safeguard: normally, in the absence of a default response, unknown success responses return an error above: so this is a codegen issue
msg := fmt.Sprintf("unexpected success response for checkAllPods: API contract not enforced by server. Client expected to get an error, but got: %T", result)
panic(msg)
}
/*
CheckServicePods Queries the API server for all other pods in this service, and pings them via their pods IPs. Calls their /ping endpoint
CheckServicePods Queries the API server for all other pods in this service, and pings them via their pods IPs. Calls their /ping endpoint
*/
func (a *Client) CheckServicePods(params *CheckServicePodsParams, opts ...ClientOption) (*CheckServicePodsOK, error) {
// TODO: Validate the params before sending
// NOTE: parameters are not validated before sending
if params == nil {
params = NewCheckServicePodsParams()
}
@@ -104,26 +132,31 @@ func (a *Client) CheckServicePods(params *CheckServicePodsParams, opts ...Client
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
// only one success response has to be checked
success, ok := result.(*CheckServicePodsOK)
if ok {
return success, nil
}
// unexpected success response
// safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue
// unexpected success response.
// no default response is defined.
//
// safeguard: normally, in the absence of a default response, unknown success responses return an error above: so this is a codegen issue
msg := fmt.Sprintf("unexpected success response for checkServicePods: API contract not enforced by server. Client expected to get an error, but got: %T", result)
panic(msg)
}
/*
ClusterHealth Checks the full graph. Returns a binary OK or not OK.
ClusterHealth Checks the full graph. Returns a binary OK or not OK.
*/
func (a *Client) ClusterHealth(params *ClusterHealthParams, opts ...ClientOption) (*ClusterHealthOK, error) {
// TODO: Validate the params before sending
// NOTE: parameters are not validated before sending
if params == nil {
params = NewClusterHealthParams()
}
@@ -142,26 +175,31 @@ func (a *Client) ClusterHealth(params *ClusterHealthParams, opts ...ClientOption
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
// only one success response has to be checked
success, ok := result.(*ClusterHealthOK)
if ok {
return success, nil
}
// unexpected success response
// safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue
// unexpected success response.
// no default response is defined.
//
// safeguard: normally, in the absence of a default response, unknown success responses return an error above: so this is a codegen issue
msg := fmt.Sprintf("unexpected success response for clusterHealth: API contract not enforced by server. Client expected to get an error, but got: %T", result)
panic(msg)
}
/*
Healthz The healthcheck endpoint provides detailed information about the health of a web service. If each of the components required by the service are healthy, then the service is considered healthy and will return a 200 OK response. If any of the components needed by the service are unhealthy, then a 503 Service Unavailable response will be provided.
Healthz The healthcheck endpoint provides detailed information about the health of a web service. If each of the components required by the service are healthy, then the service is considered healthy and will return a 200 OK response. If any of the components needed by the service are unhealthy, then a 503 Service Unavailable response will be provided.
*/
func (a *Client) Healthz(params *HealthzParams, opts ...ClientOption) (*HealthzOK, error) {
// TODO: Validate the params before sending
// NOTE: parameters are not validated before sending
if params == nil {
params = NewHealthzParams()
}
@@ -180,26 +218,31 @@ func (a *Client) Healthz(params *HealthzParams, opts ...ClientOption) (*HealthzO
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
// only one success response has to be checked
success, ok := result.(*HealthzOK)
if ok {
return success, nil
}
// unexpected success response
// safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue
// unexpected success response.
// no default response is defined.
//
// safeguard: normally, in the absence of a default response, unknown success responses return an error above: so this is a codegen issue
msg := fmt.Sprintf("unexpected success response for healthz: API contract not enforced by server. Client expected to get an error, but got: %T", result)
panic(msg)
}
/*
Ping return query stats
Ping return query stats
*/
func (a *Client) Ping(params *PingParams, opts ...ClientOption) (*PingOK, error) {
// TODO: Validate the params before sending
// NOTE: parameters are not validated before sending
if params == nil {
params = NewPingParams()
}
@@ -218,17 +261,22 @@ func (a *Client) Ping(params *PingParams, opts ...ClientOption) (*PingOK, error)
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
// only one success response has to be checked
success, ok := result.(*PingOK)
if ok {
return success, nil
}
// unexpected success response
// safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue
// unexpected success response.
// no default response is defined.
//
// safeguard: normally, in the absence of a default response, unknown success responses return an error above: so this is a codegen issue
msg := fmt.Sprintf("unexpected success response for ping: API contract not enforced by server. Client expected to get an error, but got: %T", result)
panic(msg)
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
@@ -52,10 +49,12 @@ func NewPingParamsWithHTTPClient(client *http.Client) *PingParams {
}
}
/* PingParams contains all the parameters to send to the API endpoint
for the ping operation.
/*
PingParams contains all the parameters to send to the API endpoint
Typically these are written to a http.Request.
for the ping operation.
Typically these are written to a http.Request.
*/
type PingParams struct {
timeout time.Duration

View File

@@ -2,10 +2,9 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
stderrors "errors"
"fmt"
"io"
@@ -21,7 +20,7 @@ type PingReader struct {
}
// ReadResponse reads a server response into the received o.
func (o *PingReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
func (o *PingReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) {
switch response.Code() {
case 200:
result := NewPingOK()
@@ -30,7 +29,7 @@ func (o *PingReader) ReadResponse(response runtime.ClientResponse, consumer runt
}
return result, nil
default:
return nil, runtime.NewAPIError("response status code does not match any response statuses defined for this endpoint in the swagger spec", response, response.Code())
return nil, runtime.NewAPIError("[GET /ping] ping", response, response.Code())
}
}
@@ -39,7 +38,8 @@ func NewPingOK() *PingOK {
return &PingOK{}
}
/* PingOK describes a response with status code 200, with default header values.
/*
PingOK describes a response with status code 200, with default header values.
return success
*/
@@ -47,9 +47,46 @@ type PingOK struct {
Payload *models.PingResults
}
func (o *PingOK) Error() string {
return fmt.Sprintf("[GET /ping][%d] pingOK %+v", 200, o.Payload)
// IsSuccess returns true when this ping o k response has a 2xx status code
func (o *PingOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this ping o k response has a 3xx status code
func (o *PingOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this ping o k response has a 4xx status code
func (o *PingOK) IsClientError() bool {
return false
}
// IsServerError returns true when this ping o k response has a 5xx status code
func (o *PingOK) IsServerError() bool {
return false
}
// IsCode returns true when this ping o k response a status code equal to that given
func (o *PingOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the ping o k response
func (o *PingOK) Code() int {
return 200
}
func (o *PingOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /ping][%d] pingOK %s", 200, payload)
}
func (o *PingOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /ping][%d] pingOK %s", 200, payload)
}
func (o *PingOK) GetPayload() *models.PingResults {
return o.Payload
}
@@ -59,7 +96,7 @@ func (o *PingOK) readResponse(response runtime.ClientResponse, consumer runtime.
o.Payload = new(models.PingResults)
// response payload
if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) {
return err
}

View File

@@ -44,6 +44,13 @@ var GoldpingerConfig = struct {
IPVersions []string `long:"ip-versions" description:"The IP versions to use (space delimited). Possible values are 4 and 6 (defaults to 4)." env:"IP_VERSIONS" env-delim:" "`
// UDP probe settings
UDPEnabled bool `long:"udp-enabled" description:"Enable UDP probe for loss and hop metrics" env:"UDP_ENABLED"`
UDPPort int `long:"udp-port" description:"Port for UDP echo listener" env:"UDP_PORT" default:"6969"`
UDPPacketCount int `long:"udp-packet-count" description:"Number of UDP packets to send per probe" env:"UDP_PACKET_COUNT" default:"10"`
UDPPacketSize int `long:"udp-packet-size" description:"Size of each UDP probe packet in bytes" env:"UDP_PACKET_SIZE" default:"64"`
UDPTimeout time.Duration `long:"udp-timeout" description:"Timeout for UDP probe" env:"UDP_TIMEOUT" default:"1s"`
// Timeouts
PingTimeoutMs int64 `long:"ping-timeout-ms" description:"The timeout in milliseconds for a ping call to other goldpinger pods(deprecated)" env:"PING_TIMEOUT_MS" default:"300"`
CheckTimeoutMs int64 `long:"check-timeout-ms" description:"The timeout in milliseconds for a check call to other goldpinger pods(deprecated)" env:"CHECK_TIMEOUT_MS" default:"1000"`

View File

@@ -16,6 +16,7 @@ package goldpinger
import (
"context"
"sync"
"time"
"go.uber.org/zap"
@@ -111,6 +112,24 @@ func (p *Pinger) Ping() {
CountCall("made", "ping")
start := time.Now()
// Run HTTP ping and UDP probe concurrently
var udpResult UDPProbeResult
var wg sync.WaitGroup
if GoldpingerConfig.UDPEnabled {
wg.Add(1)
go func() {
defer wg.Done()
targetIP := pickPodHostIP(p.pod.PodIP, p.pod.HostIP)
udpResult = ProbeUDP(
targetIP,
GoldpingerConfig.UDPPort,
GoldpingerConfig.UDPPacketCount,
GoldpingerConfig.UDPPacketSize,
GoldpingerConfig.UDPTimeout,
)
}()
}
ctx, cancel := context.WithTimeout(context.Background(), p.timeout)
defer cancel()
@@ -120,7 +139,34 @@ func (p *Pinger) Ping() {
responseTimeMs := responseTime.Nanoseconds() / int64(time.Millisecond)
p.histogram.Observe(responseTime.Seconds())
// Wait for UDP probe to complete
wg.Wait()
OK := (err == nil)
var lossPct float64
var hopCount int32
var udpRttMs float64
if GoldpingerConfig.UDPEnabled {
lossPct = udpResult.LossPct
hopCount = udpResult.HopCount
udpRttMs = udpResult.AvgRttS * 1000.0
if udpResult.Err != nil {
p.logger.Warn("UDP probe error", zap.Error(udpResult.Err))
} else {
p.logger.Debug("UDP probe complete",
zap.Float64("lossPct", lossPct),
zap.Int32("hopCount", hopCount),
zap.Float64("udpRttMs", udpRttMs),
)
}
SetPeerLossPct(p.pod.HostIP, p.pod.PodIP, lossPct)
SetPeerHopCount(p.pod.HostIP, p.pod.PodIP, hopCount)
if udpResult.AvgRttS > 0 {
ObservePeerUDPRtt(p.pod.HostIP, p.pod.PodIP, udpResult.AvgRttS)
}
}
if OK {
p.resultsChan <- PingAllPodsResult{
podName: p.pod.Name,
@@ -132,6 +178,9 @@ func (p *Pinger) Ping() {
Response: resp.Payload,
StatusCode: 200,
ResponseTimeMs: responseTimeMs,
LossPct: lossPct,
HopCount: hopCount,
UDPRttMs: udpRttMs,
},
}
p.logger.Debug("Success pinging pod", zap.Duration("responseTime", responseTime))
@@ -146,6 +195,9 @@ func (p *Pinger) Ping() {
Error: err.Error(),
StatusCode: 504,
ResponseTimeMs: responseTimeMs,
LossPct: lossPct,
HopCount: hopCount,
UDPRttMs: udpRttMs,
},
}
p.logger.Warn("Ping returned error", zap.Duration("responseTime", responseTime), zap.Error(err))

View File

@@ -123,6 +123,50 @@ var (
"host",
},
)
goldpingerPeersLossPct = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "goldpinger_peers_loss_pct",
Help: "UDP packet loss percentage to peer (0-100)",
},
[]string{
"goldpinger_instance",
"host_ip",
"pod_ip",
},
)
goldpingerPeersHopCount = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "goldpinger_peers_hop_count",
Help: "Estimated network hop count to peer from UDP TTL",
},
[]string{
"goldpinger_instance",
"host_ip",
"pod_ip",
},
)
goldpingerPeersUDPRtt = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "goldpinger_peers_udp_rtt_s",
Help: "Histogram of UDP round-trip times to peers in seconds",
Buckets: []float64{.0001, .00025, .0005, .001, .0025, .005, .01, .025, .05, .1, .25, .5, 1},
},
[]string{
"goldpinger_instance",
"host_ip",
"pod_ip",
},
)
goldpingerUDPErrorsCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "goldpinger_udp_errors_total",
Help: "Statistics of UDP probe errors per instance",
},
[]string{
"goldpinger_instance",
"host",
},
)
bootTime = time.Now()
)
@@ -136,6 +180,10 @@ func init() {
prometheus.MustRegister(goldpingerDnsErrorsCounter)
prometheus.MustRegister(goldPingerHttpErrorsCounter)
prometheus.MustRegister(goldPingerTcpErrorsCounter)
prometheus.MustRegister(goldpingerPeersLossPct)
prometheus.MustRegister(goldpingerPeersHopCount)
prometheus.MustRegister(goldpingerPeersUDPRtt)
prometheus.MustRegister(goldpingerUDPErrorsCounter)
zap.L().Info("Metrics setup - see /metrics")
}
@@ -210,6 +258,48 @@ func CountHttpError(host string) {
).Inc()
}
// SetPeerLossPct sets the UDP packet loss percentage gauge for a peer
func SetPeerLossPct(hostIP, podIP string, lossPct float64) {
goldpingerPeersLossPct.WithLabelValues(
GoldpingerConfig.Hostname,
hostIP,
podIP,
).Set(lossPct)
}
// SetPeerHopCount sets the estimated hop count gauge for a peer
func SetPeerHopCount(hostIP, podIP string, hopCount int32) {
goldpingerPeersHopCount.WithLabelValues(
GoldpingerConfig.Hostname,
hostIP,
podIP,
).Set(float64(hopCount))
}
// DeletePeerUDPMetrics removes stale UDP metric labels for a destroyed peer
func DeletePeerUDPMetrics(hostIP, podIP string) {
goldpingerPeersLossPct.DeleteLabelValues(GoldpingerConfig.Hostname, hostIP, podIP)
goldpingerPeersHopCount.DeleteLabelValues(GoldpingerConfig.Hostname, hostIP, podIP)
goldpingerPeersUDPRtt.DeleteLabelValues(GoldpingerConfig.Hostname, hostIP, podIP)
}
// ObservePeerUDPRtt records a UDP RTT observation in seconds
func ObservePeerUDPRtt(hostIP, podIP string, rttS float64) {
goldpingerPeersUDPRtt.WithLabelValues(
GoldpingerConfig.Hostname,
hostIP,
podIP,
).Observe(rttS)
}
// CountUDPError counts instances of UDP probe errors
func CountUDPError(host string) {
goldpingerUDPErrorsCounter.WithLabelValues(
GoldpingerConfig.Hostname,
host,
).Inc()
}
// returns a timer for easy observing of the durations of calls to kubernetes API
func GetLabeledKubernetesCallsTimer() *prometheus.Timer {
return prometheus.NewTimer(

225
pkg/goldpinger/udp_probe.go Normal file
View File

@@ -0,0 +1,225 @@
// Copyright 2018 Bloomberg Finance L.P.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package goldpinger
import (
"encoding/binary"
"fmt"
"net"
"strconv"
"time"
"go.uber.org/zap"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
const (
udpMagic = 0x47504E47 // "GPNG"
udpHeaderSize = 16 // 4 magic + 4 seq + 8 timestamp
// udpMaxPacketSize is the maximum UDP packet we will read. Sized to the
// standard Ethernet MTU (1500 bytes) which is the largest packet that
// will survive most networks without fragmentation. Any configured
// UDP_PACKET_SIZE larger than this will be clamped to this value to
// avoid silent truncation on the receive side.
udpMaxPacketSize = 1500
)
// UDPProbeResult holds the results of a UDP probe to a peer
type UDPProbeResult struct {
LossPct float64
HopCount int32
AvgRttS float64
Err error
}
// StartUDPListener starts a UDP echo listener on the given port.
// It echoes back any packet that starts with the GPNG magic number.
func StartUDPListener(port int) {
addr := net.JoinHostPort("::", strconv.Itoa(port))
pc, err := net.ListenPacket("udp", addr)
if err != nil {
zap.L().Fatal("Failed to start UDP listener", zap.String("addr", addr), zap.Error(err))
}
defer pc.Close()
zap.L().Info("UDP echo listener started", zap.String("addr", addr))
buf := make([]byte, udpMaxPacketSize)
for {
n, remoteAddr, err := pc.ReadFrom(buf)
if err != nil {
zap.L().Warn("UDP read error", zap.Error(err))
continue
}
if n < udpHeaderSize {
continue
}
magic := binary.BigEndian.Uint32(buf[0:4])
if magic != udpMagic {
continue
}
// Echo back the packet as-is
_, err = pc.WriteTo(buf[:n], remoteAddr)
if err != nil {
zap.L().Warn("UDP write error", zap.Error(err))
}
}
}
// ProbeUDP sends count UDP packets to the target and measures loss and hop count.
func ProbeUDP(targetIP string, port, count, size int, timeout time.Duration) UDPProbeResult {
if count <= 0 {
return UDPProbeResult{Err: fmt.Errorf("packet count must be > 0, got %d", count)}
}
if size < udpHeaderSize {
size = udpHeaderSize
}
if size > udpMaxPacketSize {
size = udpMaxPacketSize
}
addr := net.JoinHostPort(targetIP, strconv.Itoa(port))
conn, err := net.Dial("udp", addr)
if err != nil {
CountUDPError(targetIP)
return UDPProbeResult{LossPct: 100, Err: fmt.Errorf("dial: %w", err)}
}
defer conn.Close()
// Determine if this is IPv4 or IPv6 and set up TTL/HopLimit reading
isIPv6 := net.ParseIP(targetIP).To4() == nil
var ttlValue int
ttlFound := false
if isIPv6 {
p := ipv6.NewPacketConn(conn.(*net.UDPConn))
if err := p.SetControlMessage(ipv6.FlagHopLimit, true); err != nil {
zap.L().Debug("Cannot set IPv6 hop limit flag", zap.Error(err))
}
} else {
p := ipv4.NewPacketConn(conn.(*net.UDPConn))
if err := p.SetControlMessage(ipv4.FlagTTL, true); err != nil {
zap.L().Debug("Cannot set IPv4 TTL flag", zap.Error(err))
}
}
// Build packet
pkt := make([]byte, size)
binary.BigEndian.PutUint32(pkt[0:4], udpMagic)
// Send all packets
for i := 0; i < count; i++ {
binary.BigEndian.PutUint32(pkt[4:8], uint32(i))
binary.BigEndian.PutUint64(pkt[8:16], uint64(time.Now().UnixNano()))
_, err := conn.Write(pkt)
if err != nil {
CountUDPError(targetIP)
zap.L().Debug("UDP send error", zap.Int("seq", i), zap.Error(err))
}
}
// Receive replies
received := 0
var totalRttNs int64
deadline := time.Now().Add(timeout)
conn.SetReadDeadline(deadline)
recvBuf := make([]byte, udpMaxPacketSize)
if isIPv6 {
p := ipv6.NewPacketConn(conn.(*net.UDPConn))
for received < count {
n, cm, _, err := p.ReadFrom(recvBuf)
now := time.Now()
if err != nil {
break
}
if n < udpHeaderSize {
continue
}
magic := binary.BigEndian.Uint32(recvBuf[0:4])
if magic != udpMagic {
continue
}
sentNs := int64(binary.BigEndian.Uint64(recvBuf[8:16]))
totalRttNs += now.UnixNano() - sentNs
received++
if cm != nil && cm.HopLimit > 0 && !ttlFound {
ttlValue = cm.HopLimit
ttlFound = true
}
}
} else {
p := ipv4.NewPacketConn(conn.(*net.UDPConn))
for received < count {
n, cm, _, err := p.ReadFrom(recvBuf)
now := time.Now()
if err != nil {
break
}
if n < udpHeaderSize {
continue
}
magic := binary.BigEndian.Uint32(recvBuf[0:4])
if magic != udpMagic {
continue
}
sentNs := int64(binary.BigEndian.Uint64(recvBuf[8:16]))
totalRttNs += now.UnixNano() - sentNs
received++
if cm != nil && cm.TTL > 0 && !ttlFound {
ttlValue = cm.TTL
ttlFound = true
}
}
}
lossPct := float64(count-received) / float64(count) * 100.0
var hopCount int32
if ttlFound {
hopCount = estimateHops(ttlValue)
}
var avgRttS float64
if received > 0 {
avgRttS = float64(totalRttNs) / float64(received) / 1e9
}
return UDPProbeResult{
LossPct: lossPct,
HopCount: hopCount,
AvgRttS: avgRttS,
}
}
// estimateHops estimates the number of hops from the received TTL.
// Common initial TTL values are 64 (Linux) and 128 (Windows).
func estimateHops(receivedTTL int) int32 {
if receivedTTL <= 0 {
return 0
}
initialTTL := 64
if receivedTTL > 64 {
initialTTL = 128
}
hops := initialTTL - receivedTTL
if hops < 0 {
hops = 0
}
return int32(hops)
}

View File

@@ -0,0 +1,180 @@
package goldpinger
import (
"encoding/binary"
"net"
"sync/atomic"
"testing"
"time"
)
func startTestEchoListener(t *testing.T) (int, func()) {
t.Helper()
pc, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
port := pc.LocalAddr().(*net.UDPAddr).Port
go func() {
buf := make([]byte, udpMaxPacketSize)
for {
n, addr, err := pc.ReadFrom(buf)
if err != nil {
return
}
if n >= udpHeaderSize {
magic := binary.BigEndian.Uint32(buf[0:4])
if magic == udpMagic {
pc.WriteTo(buf[:n], addr)
}
}
}
}()
return port, func() { pc.Close() }
}
// startLossyEchoListener echoes back packets but drops every dropEveryN-th
// packet (1-indexed). For example, dropEveryN=3 drops packets 3, 6, 9, etc.
func startLossyEchoListener(t *testing.T, dropEveryN int) (int, func()) {
t.Helper()
pc, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
port := pc.LocalAddr().(*net.UDPAddr).Port
var counter atomic.Int64
go func() {
buf := make([]byte, udpMaxPacketSize)
for {
n, addr, err := pc.ReadFrom(buf)
if err != nil {
return
}
if n >= udpHeaderSize {
magic := binary.BigEndian.Uint32(buf[0:4])
if magic == udpMagic {
seq := counter.Add(1)
if seq%int64(dropEveryN) == 0 {
// Drop this packet — don't echo
continue
}
pc.WriteTo(buf[:n], addr)
}
}
}
}()
return port, func() { pc.Close() }
}
func TestProbeUDP_NoLoss(t *testing.T) {
port, cleanup := startTestEchoListener(t)
defer cleanup()
result := ProbeUDP("127.0.0.1", port, 10, 64, 2*time.Second)
if result.Err != nil {
t.Fatalf("unexpected error: %v", result.Err)
}
if result.LossPct != 0 {
t.Errorf("expected 0%% loss, got %.1f%%", result.LossPct)
}
if result.AvgRttS <= 0 {
t.Errorf("expected positive RTT, got %.6f s", result.AvgRttS)
}
t.Logf("avg UDP RTT: %.4f ms", result.AvgRttS*1000)
}
func TestProbeUDP_FullLoss(t *testing.T) {
// Bind a port then close it so nothing is listening.
// This avoids assuming a hardcoded port like 19999 is free.
pc, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
port := pc.LocalAddr().(*net.UDPAddr).Port
pc.Close()
result := ProbeUDP("127.0.0.1", port, 5, 64, 200*time.Millisecond)
if result.LossPct != 100 {
t.Errorf("expected 100%% loss, got %.1f%%", result.LossPct)
}
}
func TestProbeUDP_PartialLoss(t *testing.T) {
tests := []struct {
name string
count int
dropEveryN int
expectedPct float64
}{
{"drop every 2nd (50%)", 10, 2, 50.0},
{"drop every 3rd (33.3%)", 9, 3, 100.0 / 3.0},
{"drop every 5th (20%)", 10, 5, 20.0},
{"drop every 10th (10%)", 10, 10, 10.0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
port, cleanup := startLossyEchoListener(t, tt.dropEveryN)
defer cleanup()
result := ProbeUDP("127.0.0.1", port, tt.count, 64, 2*time.Second)
if result.Err != nil {
t.Fatalf("unexpected error: %v", result.Err)
}
// Allow small floating point tolerance
diff := result.LossPct - tt.expectedPct
if diff < -0.1 || diff > 0.1 {
t.Errorf("expected %.1f%% loss, got %.1f%%", tt.expectedPct, result.LossPct)
}
t.Logf("loss: %.1f%% (expected %.1f%%)", result.LossPct, tt.expectedPct)
})
}
}
func TestProbeUDP_ZeroCount(t *testing.T) {
result := ProbeUDP("127.0.0.1", 12345, 0, 64, 200*time.Millisecond)
if result.Err == nil {
t.Error("expected error for count=0, got nil")
}
}
func TestProbeUDP_PacketFormat(t *testing.T) {
pkt := make([]byte, 64)
binary.BigEndian.PutUint32(pkt[0:4], udpMagic)
binary.BigEndian.PutUint32(pkt[4:8], 42)
binary.BigEndian.PutUint64(pkt[8:16], uint64(time.Now().UnixNano()))
magic := binary.BigEndian.Uint32(pkt[0:4])
if magic != 0x47504E47 {
t.Errorf("expected magic 0x47504E47, got 0x%X", magic)
}
seq := binary.BigEndian.Uint32(pkt[4:8])
if seq != 42 {
t.Errorf("expected seq 42, got %d", seq)
}
}
func TestEstimateHops(t *testing.T) {
tests := []struct {
ttl int
want int32
}{
{64, 0}, // same host, Linux
{63, 1}, // 1 hop, Linux
{56, 8}, // 8 hops, Linux
{128, 0}, // same host, Windows (TTL > 64 → initial=128)
{127, 1}, // 1 hop, Windows
{0, 0}, // invalid
}
for _, tt := range tests {
got := estimateHops(tt.ttl)
if got != tt.want {
t.Errorf("estimateHops(%d) = %d, want %d", tt.ttl, got, tt.want)
}
}
}

View File

@@ -93,10 +93,11 @@ func updatePingers(resultsChan chan<- PingAllPodsResult) {
// createPingers allocates a new pinger object for each new goldpinger Pod that's been discovered
// It also:
// (a) initializes a result object in checkResults to store info on that pod
// (b) starts a new goroutines to continuously ping the given pod.
// Each new goroutine waits for a given time before starting the continuous ping
// to prevent a thundering herd
//
// (a) initializes a result object in checkResults to store info on that pod
// (b) starts a new goroutines to continuously ping the given pod.
// Each new goroutine waits for a given time before starting the continuous ping
// to prevent a thundering herd
func createPingers(pingers map[string]*Pinger, newPods map[string]*GoldpingerPod, resultsChan chan<- PingAllPodsResult, refreshPeriod time.Duration) {
if len(newPods) == 0 {
// I have nothing to do
@@ -136,6 +137,11 @@ func destroyPingers(pingers map[string]*Pinger, deletedPods map[string]*Goldping
// Close the channel to stop pinging
close(pinger.stopChan)
// Clean up stale UDP metric labels for this peer
if GoldpingerConfig.UDPEnabled {
DeletePeerUDPMetrics(pod.HostIP, pod.PodIP)
}
// delete from pingers
delete(pingers, podName)
}

View File

@@ -2,9 +2,6 @@
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"

View File

@@ -2,11 +2,9 @@
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
stderrors "errors"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
@@ -93,11 +91,15 @@ func (m *CheckAllPodResult) validateResponse(formats strfmt.Registry) error {
if m.Response != nil {
if err := m.Response.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName("response")
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName("response")
}
return err
}
}
@@ -122,12 +124,21 @@ func (m *CheckAllPodResult) ContextValidate(ctx context.Context, formats strfmt.
func (m *CheckAllPodResult) contextValidateResponse(ctx context.Context, formats strfmt.Registry) error {
if m.Response != nil {
if swag.IsZero(m.Response) { // not required
return nil
}
if err := m.Response.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName("response")
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName("response")
}
return err
}
}

View File

@@ -2,11 +2,9 @@
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
stderrors "errors"
"strconv"
"github.com/go-openapi/errors"
@@ -73,11 +71,15 @@ func (m *CheckAllResults) validateHosts(formats strfmt.Registry) error {
if m.Hosts[i] != nil {
if err := m.Hosts[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName("hosts" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName("hosts" + "." + strconv.Itoa(i))
}
return err
}
}
@@ -117,11 +119,15 @@ func (m *CheckAllResults) validateResponses(formats strfmt.Registry) error {
}
if val, ok := m.Responses[k]; ok {
if err := val.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName("responses" + "." + k)
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName("responses" + "." + k)
}
return err
}
}
@@ -158,12 +164,21 @@ func (m *CheckAllResults) contextValidateHosts(ctx context.Context, formats strf
for i := 0; i < len(m.Hosts); i++ {
if m.Hosts[i] != nil {
if swag.IsZero(m.Hosts[i]) { // not required
return nil
}
if err := m.Hosts[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName("hosts" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName("hosts" + "." + strconv.Itoa(i))
}
return err
}
}

View File

@@ -2,11 +2,9 @@
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
stderrors "errors"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
@@ -56,11 +54,15 @@ func (m *CheckResults) validatePodResults(formats strfmt.Registry) error {
}
if val, ok := m.PodResults[k]; ok {
if err := val.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName("podResults" + "." + k)
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName("podResults" + "." + k)
}
return err
}
}
@@ -77,11 +79,15 @@ func (m *CheckResults) validateProbeResults(formats strfmt.Registry) error {
if m.ProbeResults != nil {
if err := m.ProbeResults.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName("probeResults")
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName("probeResults")
}
return err
}
}
@@ -124,12 +130,20 @@ func (m *CheckResults) contextValidatePodResults(ctx context.Context, formats st
func (m *CheckResults) contextValidateProbeResults(ctx context.Context, formats strfmt.Registry) error {
if swag.IsZero(m.ProbeResults) { // not required
return nil
}
if err := m.ProbeResults.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName("probeResults")
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName("probeResults")
}
return err
}

View File

@@ -2,9 +2,6 @@
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
@@ -60,7 +57,7 @@ func (m *ClusterHealthResults) Validate(formats strfmt.Registry) error {
func (m *ClusterHealthResults) validateOK(formats strfmt.Registry) error {
if err := validate.Required("OK", "body", bool(m.OK)); err != nil {
if err := validate.Required("OK", "body", m.OK); err != nil {
return err
}

View File

@@ -2,9 +2,6 @@
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"

View File

@@ -2,11 +2,9 @@
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
stderrors "errors"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
@@ -64,11 +62,15 @@ func (m *PingResults) validateReceived(formats strfmt.Registry) error {
if m.Received != nil {
if err := m.Received.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName("received")
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName("received")
}
return err
}
}
@@ -93,12 +95,21 @@ func (m *PingResults) ContextValidate(ctx context.Context, formats strfmt.Regist
func (m *PingResults) contextValidateReceived(ctx context.Context, formats strfmt.Registry) error {
if m.Received != nil {
if swag.IsZero(m.Received) { // not required
return nil
}
if err := m.Received.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName("received")
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName("received")
}
return err
}
}

View File

@@ -2,11 +2,9 @@
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
stderrors "errors"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
@@ -37,6 +35,12 @@ type PodResult struct {
// error
Error string `json:"error,omitempty"`
// estimated network hop count from UDP TTL
HopCount int32 `json:"hop-count,omitempty"`
// UDP packet loss percentage (0-100)
LossPct float64 `json:"loss-pct,omitempty"`
// response
Response *PingResults `json:"response,omitempty"`
@@ -45,6 +49,9 @@ type PodResult struct {
// status code
StatusCode int32 `json:"status-code,omitempty"`
// average UDP round-trip time in milliseconds
UDPRttMs float64 `json:"udp-rtt-ms,omitempty"`
}
// Validate validates this pod result
@@ -116,11 +123,15 @@ func (m *PodResult) validateResponse(formats strfmt.Registry) error {
if m.Response != nil {
if err := m.Response.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName("response")
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName("response")
}
return err
}
}
@@ -145,12 +156,21 @@ func (m *PodResult) ContextValidate(ctx context.Context, formats strfmt.Registry
func (m *PodResult) contextValidateResponse(ctx context.Context, formats strfmt.Registry) error {
if m.Response != nil {
if swag.IsZero(m.Response) { // not required
return nil
}
if err := m.Response.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName("response")
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName("response")
}
return err
}
}

View File

@@ -2,9 +2,6 @@
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"

View File

@@ -2,15 +2,14 @@
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
stderrors "errors"
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
@@ -32,11 +31,15 @@ func (m ProbeResults) Validate(formats strfmt.Registry) error {
for i := 0; i < len(m[k]); i++ {
if err := m[k][i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName(k + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName(k + "." + strconv.Itoa(i))
}
return err
}
@@ -58,12 +61,20 @@ func (m ProbeResults) ContextValidate(ctx context.Context, formats strfmt.Regist
for i := 0; i < len(m[k]); i++ {
if swag.IsZero(m[k][i]) { // not required
return nil
}
if err := m[k][i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
ve := new(errors.Validation)
if stderrors.As(err, &ve) {
return ve.ValidateName(k + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
}
ce := new(errors.CompositeError)
if stderrors.As(err, &ce) {
return ce.ValidateName(k + "." + strconv.Itoa(i))
}
return err
}

View File

@@ -2,17 +2,17 @@
// Package restapi Goldpinger
//
// Schemes:
// http
// Host: localhost
// BasePath: /
// Version: 3.0.0
// Schemes:
// http
// Host: localhost
// BasePath: /
// Version: 3.0.0
//
// Consumes:
// - application/json
// Consumes:
// - application/json
//
// Produces:
// - application/json
// Produces:
// - application/json
//
// swagger:meta
package restapi

View File

@@ -2,9 +2,6 @@
package restapi
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
)
@@ -170,12 +167,6 @@ func init() {
"type": "boolean",
"default": false
},
"dnsResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/DnsResults"
}
},
"hosts": {
"type": "array",
"items": {
@@ -203,10 +194,10 @@ func init() {
"type": "integer",
"format": "int32"
},
"httpResults": {
"probeResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/HttpResults"
"$ref": "#/definitions/ProbeResults"
}
},
"responses": {
@@ -214,32 +205,20 @@ func init() {
"additionalProperties": {
"$ref": "#/definitions/CheckAllPodResult"
}
},
"tcpResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/TcpResults"
}
}
}
},
"CheckResults": {
"type": "object",
"properties": {
"dnsResults": {
"$ref": "#/definitions/DnsResults"
},
"httpResults": {
"$ref": "#/definitions/HttpResults"
},
"podResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/PodResult"
}
},
"tcpResults": {
"$ref": "#/definitions/TcpResults"
"probeResults": {
"$ref": "#/definitions/ProbeResults"
}
}
},
@@ -279,12 +258,6 @@ func init() {
}
}
},
"DnsResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/ProbeResult"
}
},
"HealthCheckResults": {
"type": "object",
"properties": {
@@ -302,12 +275,6 @@ func init() {
}
}
},
"HttpResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/ProbeResult"
}
},
"PingResults": {
"type": "object",
"properties": {
@@ -342,6 +309,16 @@ func init() {
"error": {
"type": "string"
},
"hop-count": {
"description": "estimated network hop count from UDP TTL",
"type": "integer",
"format": "int32"
},
"loss-pct": {
"description": "UDP packet loss percentage (0-100)",
"type": "number",
"format": "double"
},
"response": {
"$ref": "#/definitions/PingResults"
},
@@ -353,6 +330,11 @@ func init() {
"status-code": {
"type": "integer",
"format": "int32"
},
"udp-rtt-ms": {
"description": "average UDP round-trip time in milliseconds",
"type": "number",
"format": "double"
}
}
},
@@ -361,16 +343,22 @@ func init() {
"error": {
"type": "string"
},
"protocol": {
"type": "string"
},
"response-time-ms": {
"type": "number",
"format": "int64"
}
}
},
"TcpResults": {
"ProbeResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/ProbeResult"
"type": "array",
"items": {
"$ref": "#/definitions/ProbeResult"
}
}
}
}
@@ -528,12 +516,6 @@ func init() {
"type": "boolean",
"default": false
},
"dnsResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/DnsResults"
}
},
"hosts": {
"type": "array",
"items": {
@@ -548,10 +530,10 @@ func init() {
"type": "integer",
"format": "int32"
},
"httpResults": {
"probeResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/HttpResults"
"$ref": "#/definitions/ProbeResults"
}
},
"responses": {
@@ -559,12 +541,6 @@ func init() {
"additionalProperties": {
"$ref": "#/definitions/CheckAllPodResult"
}
},
"tcpResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/TcpResults"
}
}
}
},
@@ -587,20 +563,14 @@ func init() {
"CheckResults": {
"type": "object",
"properties": {
"dnsResults": {
"$ref": "#/definitions/DnsResults"
},
"httpResults": {
"$ref": "#/definitions/HttpResults"
},
"podResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/PodResult"
}
},
"tcpResults": {
"$ref": "#/definitions/TcpResults"
"probeResults": {
"$ref": "#/definitions/ProbeResults"
}
}
},
@@ -640,12 +610,6 @@ func init() {
}
}
},
"DnsResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/ProbeResult"
}
},
"HealthCheckResults": {
"type": "object",
"properties": {
@@ -663,12 +627,6 @@ func init() {
}
}
},
"HttpResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/ProbeResult"
}
},
"PingResults": {
"type": "object",
"properties": {
@@ -703,6 +661,16 @@ func init() {
"error": {
"type": "string"
},
"hop-count": {
"description": "estimated network hop count from UDP TTL",
"type": "integer",
"format": "int32"
},
"loss-pct": {
"description": "UDP packet loss percentage (0-100)",
"type": "number",
"format": "double"
},
"response": {
"$ref": "#/definitions/PingResults"
},
@@ -714,6 +682,11 @@ func init() {
"status-code": {
"type": "integer",
"format": "int32"
},
"udp-rtt-ms": {
"description": "average UDP round-trip time in milliseconds",
"type": "number",
"format": "double"
}
}
},
@@ -722,16 +695,22 @@ func init() {
"error": {
"type": "string"
},
"protocol": {
"type": "string"
},
"response-time-ms": {
"type": "number",
"format": "int64"
}
}
},
"TcpResults": {
"ProbeResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/ProbeResult"
"type": "array",
"items": {
"$ref": "#/definitions/ProbeResult"
}
}
}
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
@@ -29,10 +26,10 @@ func NewCheckAllPods(ctx *middleware.Context, handler CheckAllPodsHandler) *Chec
return &CheckAllPods{Context: ctx, Handler: handler}
}
/* CheckAllPods swagger:route GET /check_all checkAllPods
/*
CheckAllPods swagger:route GET /check_all checkAllPods
Queries the API server for all other pods in this service, and makes all of them query all of their neighbours, using their pods IPs. Calls their /check endpoint.
*/
type CheckAllPods struct {
Context *middleware.Context
@@ -51,6 +48,7 @@ func (o *CheckAllPods) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
}
res := o.Handler.Handle(Params) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
@@ -25,7 +22,6 @@ func NewCheckAllPodsParams() CheckAllPodsParams {
//
// swagger:parameters checkAllPods
type CheckAllPodsParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
@@ -16,7 +13,8 @@ import (
// CheckAllPodsOKCode is the HTTP code returned for type CheckAllPodsOK
const CheckAllPodsOKCode int = 200
/*CheckAllPodsOK Success, return response
/*
CheckAllPodsOK Success, return response
swagger:response checkAllPodsOK
*/

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
@@ -29,10 +26,10 @@ func NewCheckServicePods(ctx *middleware.Context, handler CheckServicePodsHandle
return &CheckServicePods{Context: ctx, Handler: handler}
}
/* CheckServicePods swagger:route GET /check checkServicePods
/*
CheckServicePods swagger:route GET /check checkServicePods
Queries the API server for all other pods in this service, and pings them via their pods IPs. Calls their /ping endpoint
*/
type CheckServicePods struct {
Context *middleware.Context
@@ -51,6 +48,7 @@ func (o *CheckServicePods) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
}
res := o.Handler.Handle(Params) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
@@ -25,7 +22,6 @@ func NewCheckServicePodsParams() CheckServicePodsParams {
//
// swagger:parameters checkServicePods
type CheckServicePodsParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
@@ -16,7 +13,8 @@ import (
// CheckServicePodsOKCode is the HTTP code returned for type CheckServicePodsOK
const CheckServicePodsOKCode int = 200
/*CheckServicePodsOK Success, return response
/*
CheckServicePodsOK Success, return response
swagger:response checkServicePodsOK
*/

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
@@ -29,10 +26,10 @@ func NewClusterHealth(ctx *middleware.Context, handler ClusterHealthHandler) *Cl
return &ClusterHealth{Context: ctx, Handler: handler}
}
/* ClusterHealth swagger:route GET /cluster_health clusterHealth
/*
ClusterHealth swagger:route GET /cluster_health clusterHealth
Checks the full graph. Returns a binary OK or not OK.
*/
type ClusterHealth struct {
Context *middleware.Context
@@ -51,6 +48,7 @@ func (o *ClusterHealth) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
}
res := o.Handler.Handle(Params) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
@@ -25,7 +22,6 @@ func NewClusterHealthParams() ClusterHealthParams {
//
// swagger:parameters clusterHealth
type ClusterHealthParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
@@ -16,7 +13,8 @@ import (
// ClusterHealthOKCode is the HTTP code returned for type ClusterHealthOK
const ClusterHealthOKCode int = 200
/*ClusterHealthOK Healthy cluster
/*
ClusterHealthOK Healthy cluster
swagger:response clusterHealthOK
*/
@@ -60,7 +58,8 @@ func (o *ClusterHealthOK) WriteResponse(rw http.ResponseWriter, producer runtime
// ClusterHealthIMATeapotCode is the HTTP code returned for type ClusterHealthIMATeapot
const ClusterHealthIMATeapotCode int = 418
/*ClusterHealthIMATeapot Unhealthy cluster
/*
ClusterHealthIMATeapot Unhealthy cluster
swagger:response clusterHealthIMATeapot
*/

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"fmt"
"net/http"
@@ -43,18 +40,32 @@ func NewGoldpingerAPI(spec *loads.Document) *GoldpingerAPI {
JSONProducer: runtime.JSONProducer(),
CheckAllPodsHandler: CheckAllPodsHandlerFunc(func(params CheckAllPodsParams) middleware.Responder {
_ = params
return middleware.NotImplemented("operation CheckAllPods has not yet been implemented")
}),
CheckServicePodsHandler: CheckServicePodsHandlerFunc(func(params CheckServicePodsParams) middleware.Responder {
_ = params
return middleware.NotImplemented("operation CheckServicePods has not yet been implemented")
}),
ClusterHealthHandler: ClusterHealthHandlerFunc(func(params ClusterHealthParams) middleware.Responder {
_ = params
return middleware.NotImplemented("operation ClusterHealth has not yet been implemented")
}),
HealthzHandler: HealthzHandlerFunc(func(params HealthzParams) middleware.Responder {
_ = params
return middleware.NotImplemented("operation Healthz has not yet been implemented")
}),
PingHandler: PingHandlerFunc(func(params PingParams) middleware.Responder {
_ = params
return middleware.NotImplemented("operation Ping has not yet been implemented")
}),
}
@@ -120,7 +131,7 @@ type GoldpingerAPI struct {
CommandLineOptionsGroups []swag.CommandLineOptionsGroup
// User defined logger function.
Logger func(string, ...interface{})
Logger func(string, ...any)
}
// UseRedoc for documentation at /docs
@@ -219,12 +230,12 @@ func (o *GoldpingerAPI) Authorizer() runtime.Authorizer {
}
// ConsumersFor gets the consumers for the specified media types.
//
// MIME type parameters are ignored here.
func (o *GoldpingerAPI) ConsumersFor(mediaTypes []string) map[string]runtime.Consumer {
result := make(map[string]runtime.Consumer, len(mediaTypes))
for _, mt := range mediaTypes {
switch mt {
case "application/json":
if mt == "application/json" {
result["application/json"] = o.JSONConsumer
}
@@ -232,16 +243,17 @@ func (o *GoldpingerAPI) ConsumersFor(mediaTypes []string) map[string]runtime.Con
result[mt] = c
}
}
return result
}
// ProducersFor gets the producers for the specified media types.
//
// MIME type parameters are ignored here.
func (o *GoldpingerAPI) ProducersFor(mediaTypes []string) map[string]runtime.Producer {
result := make(map[string]runtime.Producer, len(mediaTypes))
for _, mt := range mediaTypes {
switch mt {
case "application/json":
if mt == "application/json" {
result["application/json"] = o.JSONProducer
}
@@ -249,6 +261,7 @@ func (o *GoldpingerAPI) ProducersFor(mediaTypes []string) map[string]runtime.Pro
result[mt] = p
}
}
return result
}
@@ -344,6 +357,6 @@ func (o *GoldpingerAPI) AddMiddlewareFor(method, path string, builder middleware
}
o.Init()
if h, ok := o.handlers[um][path]; ok {
o.handlers[method][path] = builder(h)
o.handlers[um][path] = builder(h)
}
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
@@ -29,10 +26,10 @@ func NewHealthz(ctx *middleware.Context, handler HealthzHandler) *Healthz {
return &Healthz{Context: ctx, Handler: handler}
}
/* Healthz swagger:route GET /healthz healthz
/*
Healthz swagger:route GET /healthz healthz
The healthcheck endpoint provides detailed information about the health of a web service. If each of the components required by the service are healthy, then the service is considered healthy and will return a 200 OK response. If any of the components needed by the service are unhealthy, then a 503 Service Unavailable response will be provided.
*/
type Healthz struct {
Context *middleware.Context
@@ -51,6 +48,7 @@ func (o *Healthz) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
}
res := o.Handler.Handle(Params) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
@@ -25,7 +22,6 @@ func NewHealthzParams() HealthzParams {
//
// swagger:parameters healthz
type HealthzParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
@@ -16,7 +13,8 @@ import (
// HealthzOKCode is the HTTP code returned for type HealthzOK
const HealthzOKCode int = 200
/*HealthzOK Health check report
/*
HealthzOK Health check report
swagger:response healthzOK
*/
@@ -60,7 +58,8 @@ func (o *HealthzOK) WriteResponse(rw http.ResponseWriter, producer runtime.Produ
// HealthzServiceUnavailableCode is the HTTP code returned for type HealthzServiceUnavailable
const HealthzServiceUnavailableCode int = 503
/*HealthzServiceUnavailable Unhealthy service
/*
HealthzServiceUnavailable Unhealthy service
swagger:response healthzServiceUnavailable
*/

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
@@ -29,10 +26,10 @@ func NewPing(ctx *middleware.Context, handler PingHandler) *Ping {
return &Ping{Context: ctx, Handler: handler}
}
/* Ping swagger:route GET /ping ping
/*
Ping swagger:route GET /ping ping
return query stats
*/
type Ping struct {
Context *middleware.Context
@@ -51,6 +48,7 @@ func (o *Ping) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
}
res := o.Handler.Handle(Params) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
@@ -25,7 +22,6 @@ func NewPingParams() PingParams {
//
// swagger:parameters ping
type PingParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
}

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
@@ -16,7 +13,8 @@ import (
// PingOKCode is the HTTP code returned for type PingOK
const PingOKCode int = 200
/*PingOK return success
/*
PingOK return success
swagger:response pingOK
*/

View File

@@ -2,9 +2,6 @@
package operations
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"

View File

@@ -7,8 +7,6 @@ import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
@@ -20,11 +18,12 @@ import (
"syscall"
"time"
"github.com/go-openapi/runtime/flagext"
"github.com/go-openapi/swag"
flags "github.com/jessevdk/go-flags"
"golang.org/x/net/netutil"
"github.com/go-openapi/runtime/flagext"
"github.com/go-openapi/swag"
"github.com/bloomberg/goldpinger/v3/pkg/restapi/operations"
)
@@ -81,7 +80,7 @@ type Server struct {
ListenLimit int `long:"listen-limit" description:"limit the number of outstanding requests"`
KeepAlive time.Duration `long:"keep-alive" description:"sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)" default:"3m"`
ReadTimeout time.Duration `long:"read-timeout" description:"maximum duration before timing out read of the request" default:"30s"`
WriteTimeout time.Duration `long:"write-timeout" description:"maximum duration before timing out write of the response" default:"60s"`
WriteTimeout time.Duration `long:"write-timeout" description:"maximum duration before timing out write of the response" default:"30s"`
httpServerL net.Listener
TLSHost string `long:"tls-host" description:"the IP to listen on for tls, when not specified it's the same as --host" env:"TLS_HOST"`
@@ -105,7 +104,7 @@ type Server struct {
}
// Logf logs message either via defined user logger or via system one if no user logger is defined.
func (s *Server) Logf(f string, args ...interface{}) {
func (s *Server) Logf(f string, args ...any) {
if s.api != nil && s.api.Logger != nil {
s.api.Logger(f, args...)
} else {
@@ -115,7 +114,7 @@ func (s *Server) Logf(f string, args ...interface{}) {
// Fatalf logs message either via defined user logger or via system one if no user logger is defined.
// Exits with non-zero status after printing
func (s *Server) Fatalf(f string, args ...interface{}) {
func (s *Server) Fatalf(f string, args ...any) {
if s.api != nil && s.api.Logger != nil {
s.api.Logger(f, args...)
os.Exit(1)
@@ -189,8 +188,8 @@ func (s *Server) Serve() (err error) {
s.Logf("Serving goldpinger at unix://%s", s.SocketPath)
go func(l net.Listener) {
defer wg.Done()
if err := domainSocket.Serve(l); err != nil && err != http.ErrServerClosed {
s.Fatalf("%v", err)
if errServe := domainSocket.Serve(l); errServe != nil && !errors.Is(errServe, http.ErrServerClosed) {
s.Fatalf("%v", errServe)
}
s.Logf("Stopped serving goldpinger at unix://%s", s.SocketPath)
}(s.domainSocketL)
@@ -219,8 +218,8 @@ func (s *Server) Serve() (err error) {
s.Logf("Serving goldpinger at http://%s", s.httpServerL.Addr())
go func(l net.Listener) {
defer wg.Done()
if err := httpServer.Serve(l); err != nil && err != http.ErrServerClosed {
s.Fatalf("%v", err)
if errServe := httpServer.Serve(l); errServe != nil && !errors.Is(errServe, http.ErrServerClosed) {
s.Fatalf("%v", errServe)
}
s.Logf("Stopped serving goldpinger at http://%s", l.Addr())
}(s.httpServerL)
@@ -274,14 +273,14 @@ func (s *Server) Serve() (err error) {
if s.TLSCACertificate != "" {
// include specified CA certificate
caCert, caCertErr := ioutil.ReadFile(string(s.TLSCACertificate))
caCert, caCertErr := os.ReadFile(string(s.TLSCACertificate))
if caCertErr != nil {
return caCertErr
}
caCertPool := x509.NewCertPool()
ok := caCertPool.AppendCertsFromPEM(caCert)
if !ok {
return fmt.Errorf("cannot parse CA certificate")
return errors.New("cannot parse CA certificate")
}
httpsServer.TLSConfig.ClientCAs = caCertPool
httpsServer.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
@@ -312,8 +311,8 @@ func (s *Server) Serve() (err error) {
s.Logf("Serving goldpinger at https://%s", s.httpsServerL.Addr())
go func(l net.Listener) {
defer wg.Done()
if err := httpsServer.Serve(l); err != nil && err != http.ErrServerClosed {
s.Fatalf("%v", err)
if errServe := httpsServer.Serve(l); errServe != nil && !errors.Is(errServe, http.ErrServerClosed) {
s.Fatalf("%v", errServe)
}
s.Logf("Stopped serving goldpinger at https://%s", l.Addr())
}(tls.NewListener(s.httpsServerL, httpsServer.TLSConfig))

View File

@@ -378,16 +378,33 @@ var main = function(timeout){
if (!edge._data.OK) {
color = "red";
}
if (edge._data.OK && edge._data["loss-pct"] > 0) {
color = "#f0ad4e";
}
if ("isprobeResultsNode" in edge._data) {
type = "dashed";
}
var label = "";
if (edge._data["udp-rtt-ms"] > 0) {
label = edge._data["udp-rtt-ms"].toFixed(2) + "ms udp";
} else if (edge._data["response-time-ms"]) {
label = edge._data["response-time-ms"] + "ms";
} else if (edge._data.elapsed) {
label = edge._data.elapsed;
}
if (edge._data["loss-pct"] > 0) {
label += " " + edge._data["loss-pct"].toFixed(1) + "% loss";
}
if (edge._data["hop-count"] > 0) {
label += " " + edge._data["hop-count"] + " hops";
}
var edge = {
id: "e" + i,
source: edge.source,
target: edge.target,
type: type,
color: color,
label: edge._data.elapsed,
label: label,
size: 7
}
//console.log(edge);

View File

@@ -61,6 +61,18 @@ definitions:
type: number
format: int64
description: wall clock time in milliseconds
loss-pct:
type: number
format: double
description: UDP packet loss percentage (0-100)
hop-count:
type: integer
format: int32
description: estimated network hop count from UDP TTL
udp-rtt-ms:
type: number
format: double
description: average UDP round-trip time in milliseconds
CheckResults:
type: object
properties: