api: Add remote API with write client; add remote handler.
Signed-off-by: bwplotka <bwplotka@gmail.com>
This commit is contained in:
parent
e1675ced80
commit
bffa92259b
|
@ -1,4 +1,4 @@
|
||||||
# Auto generated binary variables helper managed by https://github.com/bwplotka/bingo v0.8. DO NOT EDIT.
|
# Auto generated binary variables helper managed by https://github.com/bwplotka/bingo v0.9. DO NOT EDIT.
|
||||||
# All tools are designed to be build inside $GOBIN.
|
# All tools are designed to be build inside $GOBIN.
|
||||||
BINGO_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
|
BINGO_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
|
||||||
GOPATH ?= $(shell go env GOPATH)
|
GOPATH ?= $(shell go env GOPATH)
|
||||||
|
@ -7,16 +7,22 @@ GO ?= $(shell which go)
|
||||||
|
|
||||||
# Below generated variables ensure that every time a tool under each variable is invoked, the correct version
|
# Below generated variables ensure that every time a tool under each variable is invoked, the correct version
|
||||||
# will be used; reinstalling only if needed.
|
# will be used; reinstalling only if needed.
|
||||||
# For example for goimports variable:
|
# For example for buf variable:
|
||||||
#
|
#
|
||||||
# In your main Makefile (for non array binaries):
|
# In your main Makefile (for non array binaries):
|
||||||
#
|
#
|
||||||
#include .bingo/Variables.mk # Assuming -dir was set to .bingo .
|
#include .bingo/Variables.mk # Assuming -dir was set to .bingo .
|
||||||
#
|
#
|
||||||
#command: $(GOIMPORTS)
|
#command: $(BUF)
|
||||||
# @echo "Running goimports"
|
# @echo "Running buf"
|
||||||
# @$(GOIMPORTS) <flags/args..>
|
# @$(BUF) <flags/args..>
|
||||||
#
|
#
|
||||||
|
BUF := $(GOBIN)/buf-v1.39.0
|
||||||
|
$(BUF): $(BINGO_DIR)/buf.mod
|
||||||
|
@# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies.
|
||||||
|
@echo "(re)installing $(GOBIN)/buf-v1.39.0"
|
||||||
|
@cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=buf.mod -o=$(GOBIN)/buf-v1.39.0 "github.com/bufbuild/buf/cmd/buf"
|
||||||
|
|
||||||
GOIMPORTS := $(GOBIN)/goimports-v0.9.3
|
GOIMPORTS := $(GOBIN)/goimports-v0.9.3
|
||||||
$(GOIMPORTS): $(BINGO_DIR)/goimports.mod
|
$(GOIMPORTS): $(BINGO_DIR)/goimports.mod
|
||||||
@# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies.
|
@# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies.
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT
|
||||||
|
|
||||||
|
go 1.22.6
|
||||||
|
|
||||||
|
require github.com/bufbuild/buf v1.39.0 // cmd/buf
|
|
@ -0,0 +1,336 @@
|
||||||
|
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240717164558-a6c49f84cc0f.2 h1:SZRVx928rbYZ6hEKUIN+vtGDkl7uotABRWGY4OAg5gM=
|
||||||
|
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240717164558-a6c49f84cc0f.2/go.mod h1:ylS4c28ACSI59oJrOdW4pHS4n0Hw4TgSPHn8rpHl4Yw=
|
||||||
|
buf.build/gen/go/bufbuild/registry/connectrpc/go v1.16.2-20240821192916-45ba72cdd479.1 h1:QaJ6UkpvlGo4dBXR41vLRfPiKungbg7brjmbBC/k6Ig=
|
||||||
|
buf.build/gen/go/bufbuild/registry/connectrpc/go v1.16.2-20240821192916-45ba72cdd479.1/go.mod h1:oQsMFNU3YzxxjRS6O68UkcF/A+pXdXqQNcUfQEBTWcw=
|
||||||
|
buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.34.2-20240821192916-45ba72cdd479.2 h1:C3CTZTucEUm7i0O2tAM8GSlg23GnQYcljX1b1Jcpsro=
|
||||||
|
buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.34.2-20240821192916-45ba72cdd479.2/go.mod h1:psseUmlKRo9v5LZJtR/aTpdTLuyp9o3X7rnLT87SZEo=
|
||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE=
|
||||||
|
connectrpc.com/connect v1.16.2/go.mod h1:n2kgwskMHXC+lVqb18wngEpF95ldBHXjZYJussz5FRc=
|
||||||
|
connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY=
|
||||||
|
connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms=
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
|
github.com/Microsoft/hcsshim v0.12.6 h1:qEnZjoHXv+4/s0LmKZWE0/AiZmMWEIkFfWBSf1a0wlU=
|
||||||
|
github.com/Microsoft/hcsshim v0.12.6/go.mod h1:ZABCLVcvLMjIkzr9rUGcQ1QA0p0P3Ps+d3N1g2DsFfk=
|
||||||
|
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
|
||||||
|
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
|
||||||
|
github.com/bufbuild/buf v1.39.0 h1:f8bpK/8+cpgbppSyK4RKe0L1FxLqWcbgnHnWgXpVM7s=
|
||||||
|
github.com/bufbuild/buf v1.39.0/go.mod h1:1P0U+x/ky1KhpK7o7mGraDAYjQUG7710wk5lEZFWsTA=
|
||||||
|
github.com/bufbuild/protocompile v0.14.0 h1:z3DW4IvXE5G/uTOnSQn+qwQQxvhckkTWLS/0No/o7KU=
|
||||||
|
github.com/bufbuild/protocompile v0.14.0/go.mod h1:N6J1NYzkspJo3ZwyL4Xjvli86XOj1xq4qAasUFxGups=
|
||||||
|
github.com/bufbuild/protoplugin v0.0.0-20240323223605-e2735f6c31ee h1:E6ET8YUcYJ1lAe6ctR3as7yqzW2BNItDFnaB5zQq/8M=
|
||||||
|
github.com/bufbuild/protoplugin v0.0.0-20240323223605-e2735f6c31ee/go.mod h1:HjGFxsck9RObrTJp2hXQZfWhPgZqnR6sR1U5fCA/Kus=
|
||||||
|
github.com/bufbuild/protovalidate-go v0.6.4 h1:QtNIz4LGclM3UArQv/R1AKNF7MO8wriT9v7b8Gnmqak=
|
||||||
|
github.com/bufbuild/protovalidate-go v0.6.4/go.mod h1:HlkVnkE/zVYZvHIG/a7QZuzqC9bSqHaOOTeRomYF0Q8=
|
||||||
|
github.com/bufbuild/protoyaml-go v0.1.11 h1:Iyixd6Y5dx6ws6Uh8APgC1lMyvXt710NayoY8cY0Vj8=
|
||||||
|
github.com/bufbuild/protoyaml-go v0.1.11/go.mod h1:KCBItkvZOK/zwGueLdH1Wx1RLyFn5rCH7YjQrdty2Wc=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||||
|
github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
|
||||||
|
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
||||||
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
|
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
||||||
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
|
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
|
||||||
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
|
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
|
github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0=
|
||||||
|
github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0=
|
||||||
|
github.com/containerd/containerd v1.7.20 h1:Sl6jQYk3TRavaU83h66QMbI2Nqg9Jm6qzwX57Vsn1SQ=
|
||||||
|
github.com/containerd/containerd v1.7.20/go.mod h1:52GsS5CwquuqPuLncsXwG0t2CiUce+KsNHJZQJvAgR0=
|
||||||
|
github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8=
|
||||||
|
github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
|
||||||
|
github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM=
|
||||||
|
github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0=
|
||||||
|
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||||
|
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||||
|
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
|
||||||
|
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
|
||||||
|
github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G/ZW/0kEe2oEKCdS/ZxIyoCU=
|
||||||
|
github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk=
|
||||||
|
github.com/containerd/ttrpc v1.2.5 h1:IFckT1EFQoFBMG4c3sMdT8EP3/aKfumK1msY+Ze4oLU=
|
||||||
|
github.com/containerd/ttrpc v1.2.5/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o=
|
||||||
|
github.com/containerd/typeurl/v2 v2.2.0 h1:6NBDbQzr7I5LHgp34xAXYF5DOTQDn05X58lsPEmzLso=
|
||||||
|
github.com/containerd/typeurl/v2 v2.2.0/go.mod h1:8XOOxnyatxSWuG8OfsZXVnAF4iZfedjS/8UHSPJnX4g=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
|
github.com/docker/cli v27.1.2+incompatible h1:nYviRv5Y+YAKx3dFrTvS1ErkyVVunKOhoweCTE1BsnI=
|
||||||
|
github.com/docker/cli v27.1.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
|
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||||
|
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
|
github.com/docker/docker v27.1.2+incompatible h1:AhGzR1xaQIy53qCkxARaFluI00WPGtXn0AJuoQsVYTY=
|
||||||
|
github.com/docker/docker v27.1.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
|
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
|
||||||
|
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
||||||
|
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||||
|
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||||
|
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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
||||||
|
github.com/felixge/fgprof v0.9.4 h1:ocDNwMFlnA0NU0zSB3I52xkO4sFXk80VK9lXjLClu88=
|
||||||
|
github.com/felixge/fgprof v0.9.4/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
|
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||||
|
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||||
|
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||||
|
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||||
|
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
|
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||||
|
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
|
||||||
|
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
|
||||||
|
github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk=
|
||||||
|
github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||||
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI=
|
||||||
|
github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-containerregistry v0.20.2 h1:B1wPJ1SN/S7pB+ZAimcciVD+r+yV/l/DSArMxlbwseo=
|
||||||
|
github.com/google/go-containerregistry v0.20.2/go.mod h1:z38EKdKh4h7IP2gSfUUqEvalZBqs6AoLeWfUy34nQC8=
|
||||||
|
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||||
|
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||||
|
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
|
||||||
|
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||||
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||||
|
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/jdx/go-netrc v1.0.0 h1:QbLMLyCZGj0NA8glAhxUpf1zDg6cxnWgMBbjq40W0gQ=
|
||||||
|
github.com/jdx/go-netrc v1.0.0/go.mod h1:Gh9eFQJnoTNIRHXl2j5bJXA1u84hQWJWgGh569zF3v8=
|
||||||
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
|
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.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||||
|
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
|
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||||
|
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
|
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||||
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
|
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
|
||||||
|
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
||||||
|
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||||
|
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||||
|
github.com/moby/sys/mount v0.3.4 h1:yn5jq4STPztkkzSKpZkLcmjue+bZJ0u2AuQY1iNI1Ww=
|
||||||
|
github.com/moby/sys/mount v0.3.4/go.mod h1:KcQJMbQdJHPlq5lcYT+/CjatWM4PuxKe+XLSVS4J6Os=
|
||||||
|
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
|
||||||
|
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
|
||||||
|
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||||
|
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||||
|
github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=
|
||||||
|
github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||||
|
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||||
|
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||||
|
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||||
|
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||||
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.20.1 h1:YlVIbqct+ZmnEph770q9Q7NVAz4wwIiVNahee6JyUzo=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
|
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||||
|
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||||
|
github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk=
|
||||||
|
github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||||
|
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
|
||||||
|
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||||
|
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||||
|
github.com/quic-go/quic-go v0.46.0 h1:uuwLClEEyk1DNvchH8uCByQVjo3yKL9opKulExNDs7Y=
|
||||||
|
github.com/quic-go/quic-go v0.46.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
|
||||||
|
github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po=
|
||||||
|
github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||||
|
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
|
||||||
|
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
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/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts=
|
||||||
|
github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk=
|
||||||
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||||
|
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
|
||||||
|
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
|
||||||
|
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
|
||||||
|
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
|
||||||
|
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok=
|
||||||
|
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
|
||||||
|
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
|
||||||
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
|
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||||
|
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||||
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||||
|
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA=
|
||||||
|
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/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.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||||
|
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-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-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||||
|
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-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.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
|
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||||
|
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||||
|
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||||
|
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.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||||
|
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||||
|
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||||
|
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.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240823204242-4ba0660f739c h1:e0zB268kOca6FbuJkYUGxfwG4DKFZG/8DLyv9Zv66cE=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240823204242-4ba0660f739c/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240823204242-4ba0660f739c h1:Kqjm4WpoWvwhMPcrAczoTyMySQmYa9Wy2iL6Con4zn8=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240823204242-4ba0660f739c/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
|
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||||
|
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
|
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||||
|
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
@ -1,4 +1,4 @@
|
||||||
# Auto generated binary variables helper managed by https://github.com/bwplotka/bingo v0.8. DO NOT EDIT.
|
# Auto generated binary variables helper managed by https://github.com/bwplotka/bingo v0.9. DO NOT EDIT.
|
||||||
# All tools are designed to be build inside $GOBIN.
|
# All tools are designed to be build inside $GOBIN.
|
||||||
# Those variables will work only until 'bingo get' was invoked, or if tools were installed via Makefile's Variables.mk.
|
# Those variables will work only until 'bingo get' was invoked, or if tools were installed via Makefile's Variables.mk.
|
||||||
GOBIN=${GOBIN:=$(go env GOBIN)}
|
GOBIN=${GOBIN:=$(go env GOBIN)}
|
||||||
|
@ -8,5 +8,7 @@ if [ -z "$GOBIN" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
BUF="${GOBIN}/buf-v1.39.0"
|
||||||
|
|
||||||
GOIMPORTS="${GOBIN}/goimports-v0.9.3"
|
GOIMPORTS="${GOBIN}/goimports-v0.9.3"
|
||||||
|
|
||||||
|
|
12
Makefile
12
Makefile
|
@ -48,5 +48,15 @@ generate-go-collector-test-files:
|
||||||
go mod tidy
|
go mod tidy
|
||||||
|
|
||||||
.PHONY: fmt
|
.PHONY: fmt
|
||||||
fmt: common-format
|
fmt: common-format $(GOIMPORTS)
|
||||||
$(GOIMPORTS) -local github.com/prometheus/client_golang -w .
|
$(GOIMPORTS) -local github.com/prometheus/client_golang -w .
|
||||||
|
|
||||||
|
.PHONY: proto
|
||||||
|
proto: ## Regenerate Go from remote write proto.
|
||||||
|
proto: $(BUF)
|
||||||
|
@echo ">> regenerating Prometheus Remote Write proto"
|
||||||
|
@cd api/prometheus/v1/genproto && $(BUF) generate
|
||||||
|
@cd api/prometheus/v1 && find genproto/ -type f -exec sed -i '' 's/protohelpers "github.com\/planetscale\/vtprotobuf\/protohelpers"/protohelpers "github.com\/prometheus\/client_golang\/internal\/github.com\/planetscale\/vtprotobuf\/protohelpers"/g' {} \;
|
||||||
|
# For some reasons buf generates this unused import, kill it manually for now and reformat.
|
||||||
|
@cd api/prometheus/v1 && find genproto/ -type f -exec sed -i '' 's/_ "github.com\/gogo\/protobuf\/gogoproto"//g' {} \;
|
||||||
|
@cd api/prometheus/v1 && go fmt ./genproto/...
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -133,7 +134,8 @@ func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response,
|
||||||
resp, err := c.client.Do(req)
|
resp, err := c.client.Do(req)
|
||||||
defer func() {
|
defer func() {
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
resp.Body.Close()
|
_, _ = io.Copy(io.Discard, resp.Body)
|
||||||
|
_ = resp.Body.Close()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -145,6 +147,7 @@ func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response,
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
// TODO(bwplotka): Add LimitReader for too long err messages (e.g. limit by 1KB)
|
||||||
_, err = buf.ReadFrom(resp.Body)
|
_, err = buf.ReadFrom(resp.Body)
|
||||||
body = buf.Bytes()
|
body = buf.Bytes()
|
||||||
close(done)
|
close(done)
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
# buf.gen.yaml
|
||||||
|
version: v2
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
- remote: buf.build/protocolbuffers/go:v1.31.0
|
||||||
|
out: .
|
||||||
|
opt:
|
||||||
|
- Mio/prometheus/write/v2/types.proto=./v2
|
||||||
|
|
||||||
|
# vtproto for efficiency utilities like pooling etc.
|
||||||
|
# https://buf.build/community/planetscale-vtprotobuf?version=v0.6.0
|
||||||
|
- remote: buf.build/community/planetscale-vtprotobuf:v0.6.0
|
||||||
|
out: .
|
||||||
|
opt:
|
||||||
|
- Mio/prometheus/write/v2/types.proto=./v2
|
||||||
|
- features=marshal+unmarshal+size
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
- module: buf.build/prometheus/prometheus:5b212ab78fb7460e831cf7ff2d83e385
|
||||||
|
types:
|
||||||
|
- "io.prometheus.write.v2.Request"
|
|
@ -0,0 +1,97 @@
|
||||||
|
// Copyright (c) Bartłomiej Płotka @bwplotka
|
||||||
|
// Licensed under the Apache License 2.0.
|
||||||
|
|
||||||
|
// Copyright 2024 Google LLC
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
// https://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.
|
||||||
|
|
||||||
|
// Copyright 2024 Prometheus Team
|
||||||
|
// 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 writev2
|
||||||
|
|
||||||
|
// SymbolsTable implements table for easy symbol use.
|
||||||
|
type SymbolsTable struct {
|
||||||
|
strings []string
|
||||||
|
symbolsMap map[string]uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSymbolTable returns a symbol table.
|
||||||
|
func NewSymbolTable() SymbolsTable {
|
||||||
|
return SymbolsTable{
|
||||||
|
// Empty string is required as a first element.
|
||||||
|
symbolsMap: map[string]uint32{"": 0},
|
||||||
|
strings: []string{""},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Symbolize adds (if not added before) a string to the symbols table,
|
||||||
|
// while returning its reference number.
|
||||||
|
func (t *SymbolsTable) Symbolize(str string) uint32 {
|
||||||
|
if ref, ok := t.symbolsMap[str]; ok {
|
||||||
|
return ref
|
||||||
|
}
|
||||||
|
ref := uint32(len(t.strings))
|
||||||
|
t.strings = append(t.strings, str)
|
||||||
|
t.symbolsMap[str] = ref
|
||||||
|
return ref
|
||||||
|
}
|
||||||
|
|
||||||
|
// SymbolizeLabels symbolize Prometheus labels.
|
||||||
|
func (t *SymbolsTable) SymbolizeLabels(lbls []string, buf []uint32) []uint32 {
|
||||||
|
result := buf[:0]
|
||||||
|
for i := 0; i < len(lbls); i += 2 {
|
||||||
|
off := t.Symbolize(lbls[i])
|
||||||
|
result = append(result, off)
|
||||||
|
off = t.Symbolize(lbls[i+1])
|
||||||
|
result = append(result, off)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Symbols returns computes symbols table to put in e.g. Request.Symbols.
|
||||||
|
// As per spec, order does not matter.
|
||||||
|
func (t *SymbolsTable) Symbols() []string {
|
||||||
|
return t.strings
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset clears symbols table.
|
||||||
|
func (t *SymbolsTable) Reset() {
|
||||||
|
// NOTE: Make sure to keep empty symbol.
|
||||||
|
t.strings = t.strings[:1]
|
||||||
|
for k := range t.symbolsMap {
|
||||||
|
if k == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
delete(t.symbolsMap, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DesymbolizeLabels decodes label references, with given symbols to labels.
|
||||||
|
func DesymbolizeLabels(labelRefs []uint32, symbols, buf []string) []string {
|
||||||
|
result := buf[:0]
|
||||||
|
for i := 0; i < len(labelRefs); i += 2 {
|
||||||
|
result = append(result, symbols[labelRefs[i]], symbols[labelRefs[i+1]])
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
// Copyright (c) Bartłomiej Płotka @bwplotka
|
||||||
|
// Licensed under the Apache License 2.0.
|
||||||
|
|
||||||
|
// Copyright 2024 Google LLC
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
// https://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.
|
||||||
|
|
||||||
|
// Copyright 2024 Prometheus Team
|
||||||
|
// 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 writev2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func requireEqual(t testing.TB, expected, got any) {
|
||||||
|
if diff := cmp.Diff(expected, got); diff != "" {
|
||||||
|
t.Fatal(diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSymbolsTable(t *testing.T) {
|
||||||
|
s := NewSymbolTable()
|
||||||
|
requireEqual(t, []string{""}, s.Symbols())
|
||||||
|
requireEqual(t, uint32(0), s.Symbolize(""))
|
||||||
|
requireEqual(t, []string{""}, s.Symbols())
|
||||||
|
|
||||||
|
requireEqual(t, uint32(1), s.Symbolize("abc"))
|
||||||
|
requireEqual(t, []string{"", "abc"}, s.Symbols())
|
||||||
|
|
||||||
|
requireEqual(t, uint32(2), s.Symbolize("__name__"))
|
||||||
|
requireEqual(t, []string{"", "abc", "__name__"}, s.Symbols())
|
||||||
|
|
||||||
|
requireEqual(t, uint32(3), s.Symbolize("foo"))
|
||||||
|
requireEqual(t, []string{"", "abc", "__name__", "foo"}, s.Symbols())
|
||||||
|
|
||||||
|
s.Reset()
|
||||||
|
requireEqual(t, []string{""}, s.Symbols())
|
||||||
|
requireEqual(t, uint32(0), s.Symbolize(""))
|
||||||
|
|
||||||
|
requireEqual(t, uint32(1), s.Symbolize("__name__"))
|
||||||
|
requireEqual(t, []string{"", "__name__"}, s.Symbols())
|
||||||
|
|
||||||
|
requireEqual(t, uint32(2), s.Symbolize("abc"))
|
||||||
|
requireEqual(t, []string{"", "__name__", "abc"}, s.Symbols())
|
||||||
|
|
||||||
|
ls := []string{"__name__", "qwer", "zxcv", "1234"}
|
||||||
|
encoded := s.SymbolizeLabels(ls, nil)
|
||||||
|
requireEqual(t, []uint32{1, 3, 4, 5}, encoded)
|
||||||
|
decoded := DesymbolizeLabels(encoded, s.Symbols(), nil)
|
||||||
|
requireEqual(t, ls, decoded)
|
||||||
|
|
||||||
|
// Different buf.
|
||||||
|
ls = []string{"__name__", "qwer", "zxcv2222", "1234"}
|
||||||
|
encoded = s.SymbolizeLabels(ls, []uint32{1, 3, 4, 5})
|
||||||
|
requireEqual(t, []uint32{1, 3, 6, 5}, encoded)
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,376 @@
|
||||||
|
// Copyright 2024 The Prometheus Authors
|
||||||
|
// 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 remote implements bindings for Prometheus Remote APIs.
|
||||||
|
package remote
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/klauspost/compress/snappy"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/api"
|
||||||
|
"github.com/prometheus/client_golang/internal/github.com/efficientgo/core/backoff"
|
||||||
|
)
|
||||||
|
|
||||||
|
// API is a client for Prometheus Remote Protocols.
|
||||||
|
// NOTE(bwplotka): Only https://prometheus.io/docs/specs/remote_write_spec_2_0/ is currently implemented,
|
||||||
|
// read protocols to be implemented if there will be a demand.
|
||||||
|
type API struct {
|
||||||
|
client api.Client
|
||||||
|
opts apiOpts
|
||||||
|
|
||||||
|
reqBuf, comprBuf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// APIOption represents a remote API option.
|
||||||
|
type APIOption func(o *apiOpts) error
|
||||||
|
|
||||||
|
// TODO(bwplotka): Add "too old sample" handling one day.
|
||||||
|
type apiOpts struct {
|
||||||
|
logger *slog.Logger
|
||||||
|
backoff backoff.Config
|
||||||
|
compression Compression
|
||||||
|
retryOnRateLimit bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultAPIOpts = &apiOpts{
|
||||||
|
backoff: backoff.Config{
|
||||||
|
Min: 1 * time.Second,
|
||||||
|
Max: 10 * time.Second,
|
||||||
|
MaxRetries: 10,
|
||||||
|
},
|
||||||
|
// Hardcoded for now.
|
||||||
|
retryOnRateLimit: true,
|
||||||
|
compression: SnappyBlockCompression,
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAPILogger returns APIOption that allows providing slog logger.
|
||||||
|
// By default, nothing is logged.
|
||||||
|
func WithAPILogger(logger *slog.Logger) APIOption {
|
||||||
|
return func(o *apiOpts) error {
|
||||||
|
o.logger = logger
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type nopSlogHandler struct{}
|
||||||
|
|
||||||
|
func (n nopSlogHandler) Enabled(context.Context, slog.Level) bool { return false }
|
||||||
|
func (n nopSlogHandler) Handle(context.Context, slog.Record) error { return nil }
|
||||||
|
func (n nopSlogHandler) WithAttrs([]slog.Attr) slog.Handler { return n }
|
||||||
|
func (n nopSlogHandler) WithGroup(string) slog.Handler { return n }
|
||||||
|
|
||||||
|
// NewAPI returns a new API for the clients of Remote Write Protocol.
|
||||||
|
//
|
||||||
|
// It is not safe to use the returned API from multiple goroutines, create a
|
||||||
|
// separate *API for each goroutine.
|
||||||
|
func NewAPI(c api.Client, opts ...APIOption) (*API, error) {
|
||||||
|
o := *defaultAPIOpts
|
||||||
|
for _, opt := range opts {
|
||||||
|
if err := opt(&o); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.logger == nil {
|
||||||
|
o.logger = slog.New(nopSlogHandler{})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &API{
|
||||||
|
client: c,
|
||||||
|
opts: o,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type retryableError struct {
|
||||||
|
error
|
||||||
|
retryAfter time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r retryableError) RetryAfter() time.Duration {
|
||||||
|
return r.retryAfter
|
||||||
|
}
|
||||||
|
|
||||||
|
type vtProtoEnabled interface {
|
||||||
|
SizeVT() int
|
||||||
|
MarshalToSizedBufferVT(dAtA []byte) (int, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes given, non-empty, protobuf message to a remote storage.
|
||||||
|
// The https://github.com/planetscale/vtprotobuf methods will be used if your msg
|
||||||
|
// supports those (e.g. SizeVT() and MarshalToSizedBufferVT(...)), for efficiency.
|
||||||
|
func (r *API) Write(ctx context.Context, msg proto.Message) (_ WriteResponseStats, err error) {
|
||||||
|
// Detect content-type.
|
||||||
|
cType := WriteProtoFullName(proto.MessageName(msg))
|
||||||
|
if err := cType.Validate(); err != nil {
|
||||||
|
return WriteResponseStats{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode the payload.
|
||||||
|
if emsg, ok := msg.(vtProtoEnabled); ok {
|
||||||
|
// Use optimized vtprotobuf if supported.
|
||||||
|
size := emsg.SizeVT()
|
||||||
|
if len(r.reqBuf) < size {
|
||||||
|
r.reqBuf = make([]byte, size)
|
||||||
|
}
|
||||||
|
if _, err := emsg.MarshalToSizedBufferVT(r.reqBuf[:size]); err != nil {
|
||||||
|
return WriteResponseStats{}, fmt.Errorf("encoding request %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Generic proto.
|
||||||
|
r.reqBuf = r.reqBuf[:0]
|
||||||
|
r.reqBuf, err = (proto.MarshalOptions{}).MarshalAppend(r.reqBuf, msg)
|
||||||
|
if err != nil {
|
||||||
|
return WriteResponseStats{}, fmt.Errorf("encoding request %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := compressPayload(&r.comprBuf, r.opts.compression, r.reqBuf)
|
||||||
|
if err != nil {
|
||||||
|
return WriteResponseStats{}, fmt.Errorf("compressing %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we retry writes we need to track the total amount of accepted data
|
||||||
|
// across the various attempts.
|
||||||
|
accumulatedStats := WriteResponseStats{}
|
||||||
|
|
||||||
|
b := backoff.New(ctx, r.opts.backoff)
|
||||||
|
for {
|
||||||
|
rs, err := r.attemptWrite(ctx, r.opts.compression, cType, payload, b.NumRetries())
|
||||||
|
accumulatedStats = accumulatedStats.Add(rs)
|
||||||
|
if err == nil {
|
||||||
|
// Check the case mentioned in PRW 2.0.
|
||||||
|
// https://prometheus.io/docs/specs/remote_write_spec_2_0/#required-written-response-headers.
|
||||||
|
if cType == WriteProtoFullNameV2 && !accumulatedStats.Confirmed && accumulatedStats.NoDataWritten() {
|
||||||
|
// TODO(bwplotka): Allow users to disable this check or provide their stats for us to know if it's empty.
|
||||||
|
return accumulatedStats, fmt.Errorf("sent v2 request; "+
|
||||||
|
"got 2xx, but PRW 2.0 response header statistics indicate %v samples, %v histograms "+
|
||||||
|
"and %v exemplars were accepted; assumining failure e.g. the target only supports "+
|
||||||
|
"PRW 1.0 prometheus.WriteRequest, but does not check the Content-Type header correctly",
|
||||||
|
accumulatedStats.Samples, accumulatedStats.Histograms, accumulatedStats.Exemplars,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Success!
|
||||||
|
// TODO(bwplotka): Debug log with retry summary?
|
||||||
|
return accumulatedStats, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var retryableErr retryableError
|
||||||
|
if !errors.As(err, &retryableErr) {
|
||||||
|
// TODO(bwplotka): More context in the error e.g. about retries.
|
||||||
|
return accumulatedStats, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !b.Ongoing() {
|
||||||
|
// TODO(bwplotka): More context in the error e.g. about retries.
|
||||||
|
return accumulatedStats, err
|
||||||
|
}
|
||||||
|
|
||||||
|
backoffDelay := b.NextDelay() + retryableErr.RetryAfter()
|
||||||
|
r.opts.logger.Error("failed to send remote write request; retrying after backoff", "err", err, "backoff", backoffDelay)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
// TODO(bwplotka): More context in the error e.g. about retries.
|
||||||
|
return WriteResponseStats{}, ctx.Err()
|
||||||
|
case <-time.After(backoffDelay):
|
||||||
|
// Retry.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func compressPayload(tmpbuf *[]byte, enc Compression, inp []byte) (compressed []byte, _ error) {
|
||||||
|
switch enc {
|
||||||
|
case SnappyBlockCompression:
|
||||||
|
compressed = snappy.Encode(*tmpbuf, inp)
|
||||||
|
if n := snappy.MaxEncodedLen(len(inp)); n > len(*tmpbuf) {
|
||||||
|
// grow the buffer for the next time.
|
||||||
|
*tmpbuf = make([]byte, n)
|
||||||
|
}
|
||||||
|
return compressed, nil
|
||||||
|
default:
|
||||||
|
return compressed, fmt.Errorf("unknown compression scheme [%v]", enc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *API) attemptWrite(ctx context.Context, compr Compression, proto WriteProtoFullName, payload []byte, attempt int) (WriteResponseStats, error) {
|
||||||
|
u := r.client.URL("api/v1/write", nil)
|
||||||
|
req, err := http.NewRequest(http.MethodPost, u.String(), bytes.NewReader(payload))
|
||||||
|
if err != nil {
|
||||||
|
// Errors from NewRequest are from unparsable URLs, so are not
|
||||||
|
// recoverable.
|
||||||
|
return WriteResponseStats{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("Content-Encoding", string(compr))
|
||||||
|
req.Header.Set("Content-Type", contentTypeHeader(proto))
|
||||||
|
if proto == WriteProtoFullNameV1 {
|
||||||
|
// Compatibility mode for 1.0.
|
||||||
|
req.Header.Set(versionHeader, version1HeaderValue)
|
||||||
|
} else {
|
||||||
|
req.Header.Set(versionHeader, version20HeaderValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
if attempt > 0 {
|
||||||
|
req.Header.Set("Retry-Attempt", strconv.Itoa(attempt))
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, body, err := r.client.Do(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
// Errors from Client.Do are likely network errors, so recoverable.
|
||||||
|
return WriteResponseStats{}, retryableError{err, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
rs, err := parseWriteResponseStats(resp)
|
||||||
|
if err != nil {
|
||||||
|
r.opts.logger.Warn("parsing rw write statistics failed; partial or no stats", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode/100 == 2 {
|
||||||
|
return rs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = fmt.Errorf("server returned HTTP status %s: %s", resp.Status, body)
|
||||||
|
if resp.StatusCode/100 == 5 ||
|
||||||
|
(r.opts.retryOnRateLimit && resp.StatusCode == http.StatusTooManyRequests) {
|
||||||
|
return rs, retryableError{err, retryAfterDuration(resp.Header.Get("Retry-After"))}
|
||||||
|
}
|
||||||
|
return rs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// retryAfterDuration returns the duration for the Retry-After header. In case of any errors, it
|
||||||
|
// returns 0 as if the header was never supplied.
|
||||||
|
func retryAfterDuration(t string) time.Duration {
|
||||||
|
parsedDuration, err := time.Parse(http.TimeFormat, t)
|
||||||
|
if err == nil {
|
||||||
|
return time.Until(parsedDuration)
|
||||||
|
}
|
||||||
|
// The duration can be in seconds.
|
||||||
|
d, err := strconv.Atoi(t)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return time.Duration(d) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeStorage represents the storage for RemoteWriteHandler.
|
||||||
|
// This interface is intentionally private due its experimental state.
|
||||||
|
type writeStorage interface {
|
||||||
|
Store(ctx context.Context, proto WriteProtoFullName, serializedRequest []byte) (_ WriteResponseStats, code int, _ error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type handler struct {
|
||||||
|
logger *slog.Logger
|
||||||
|
store writeStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRemoteWriteHandler returns HTTP handler that receives Remote Write 2.0
|
||||||
|
// protocol https://prometheus.io/docs/specs/remote_write_spec_2_0/.
|
||||||
|
func NewRemoteWriteHandler(logger *slog.Logger, store writeStorage) http.Handler {
|
||||||
|
return &handler{logger: logger, store: store}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseProtoMsg(contentType string) (WriteProtoFullName, error) {
|
||||||
|
contentType = strings.TrimSpace(contentType)
|
||||||
|
|
||||||
|
parts := strings.Split(contentType, ";")
|
||||||
|
if parts[0] != appProtoContentType {
|
||||||
|
return "", fmt.Errorf("expected %v as the first (media) part, got %v content-type", appProtoContentType, contentType)
|
||||||
|
}
|
||||||
|
// Parse potential https://www.rfc-editor.org/rfc/rfc9110#parameter
|
||||||
|
for _, p := range parts[1:] {
|
||||||
|
pair := strings.Split(p, "=")
|
||||||
|
if len(pair) != 2 {
|
||||||
|
return "", fmt.Errorf("as per https://www.rfc-editor.org/rfc/rfc9110#parameter expected parameters to be key-values, got %v in %v content-type", p, contentType)
|
||||||
|
}
|
||||||
|
if pair[0] == "proto" {
|
||||||
|
ret := WriteProtoFullName(pair[1])
|
||||||
|
if err := ret.Validate(); err != nil {
|
||||||
|
return "", fmt.Errorf("got %v content type; %w", contentType, err)
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No "proto=" parameter, assuming v1.
|
||||||
|
return WriteProtoFullNameV1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
contentType := r.Header.Get("Content-Type")
|
||||||
|
if contentType == "" {
|
||||||
|
// Don't break yolo 1.0 clients if not needed.
|
||||||
|
// We could give http.StatusUnsupportedMediaType, but let's assume 1.0 message by default.
|
||||||
|
contentType = appProtoContentType
|
||||||
|
}
|
||||||
|
|
||||||
|
msgType, err := parseProtoMsg(contentType)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("Error decoding remote write request", "err", err)
|
||||||
|
http.Error(w, err.Error(), http.StatusUnsupportedMediaType)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
enc := r.Header.Get("Content-Encoding")
|
||||||
|
if enc == "" {
|
||||||
|
// Don't break yolo 1.0 clients if not needed. This is similar to what we did
|
||||||
|
// before 2.0: https://github.com/prometheus/prometheus/blob/d78253319daa62c8f28ed47e40bafcad2dd8b586/storage/remote/write_handler.go#L62
|
||||||
|
// We could give http.StatusUnsupportedMediaType, but let's assume snappy by default.
|
||||||
|
} else if enc != string(SnappyBlockCompression) {
|
||||||
|
err := fmt.Errorf("%v encoding (compression) is not accepted by this server; only %v is acceptable", enc, SnappyBlockCompression)
|
||||||
|
h.logger.Error("Error decoding remote write request", "err", err)
|
||||||
|
http.Error(w, err.Error(), http.StatusUnsupportedMediaType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the request body.
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("Error decoding remote write request", "err", err.Error())
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
decompressed, err := snappy.Decode(nil, body)
|
||||||
|
if err != nil {
|
||||||
|
// TODO(bwplotka): Add more context to responded error?
|
||||||
|
h.logger.Error("Error decompressing remote write request", "err", err.Error())
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stats, code, storeErr := h.store.Store(r.Context(), msgType, decompressed)
|
||||||
|
|
||||||
|
// Set required X-Prometheus-Remote-Write-Written-* response headers, in all cases.
|
||||||
|
stats.SetHeaders(w)
|
||||||
|
|
||||||
|
if storeErr != nil {
|
||||||
|
if code == 0 {
|
||||||
|
code = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
if code/5 == 100 { // 5xx
|
||||||
|
h.logger.Error("Error while remote writing the v2 request", "err", storeErr.Error())
|
||||||
|
}
|
||||||
|
http.Error(w, storeErr.Error(), code)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
// Copyright 2024 The Prometheus Authors
|
||||||
|
// 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 remote
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/prometheus/common/model"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
"google.golang.org/protobuf/testing/protocmp"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/api"
|
||||||
|
writev2 "github.com/prometheus/client_golang/api/prometheus/v1/remote/genproto/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRetryAfterDuration(t *testing.T) {
|
||||||
|
tc := []struct {
|
||||||
|
name string
|
||||||
|
tInput string
|
||||||
|
expected model.Duration
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "seconds",
|
||||||
|
tInput: "120",
|
||||||
|
expected: model.Duration(time.Second * 120),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "date-time default",
|
||||||
|
tInput: time.RFC1123, // Expected layout is http.TimeFormat, hence an error.
|
||||||
|
expected: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "retry-after not provided",
|
||||||
|
tInput: "", // Expected layout is http.TimeFormat, hence an error.
|
||||||
|
expected: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, c := range tc {
|
||||||
|
if got := retryAfterDuration(c.tInput); got != time.Duration(c.expected) {
|
||||||
|
t.Fatal("expected", c.expected, "got", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockStorage struct {
|
||||||
|
v2Reqs []*writev2.Request
|
||||||
|
protos []WriteProtoFullName
|
||||||
|
|
||||||
|
mockCode *int
|
||||||
|
mockErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockStorage) Store(_ context.Context, msgFullName WriteProtoFullName, serializedRequest []byte) (w WriteResponseStats, code int, _ error) {
|
||||||
|
if m.mockErr != nil {
|
||||||
|
return w, *m.mockCode, m.mockErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test expects v2 only.
|
||||||
|
r := &writev2.Request{}
|
||||||
|
if err := proto.Unmarshal(serializedRequest, r); err != nil {
|
||||||
|
return WriteResponseStats{}, http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
m.v2Reqs = append(m.v2Reqs, r)
|
||||||
|
m.protos = append(m.protos, msgFullName)
|
||||||
|
return stats(r), http.StatusOK, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testV2() *writev2.Request {
|
||||||
|
s := writev2.NewSymbolTable()
|
||||||
|
return &writev2.Request{
|
||||||
|
Timeseries: []*writev2.TimeSeries{
|
||||||
|
{
|
||||||
|
Metadata: &writev2.Metadata{
|
||||||
|
Type: writev2.Metadata_METRIC_TYPE_COUNTER,
|
||||||
|
HelpRef: s.Symbolize("My lovely counter"),
|
||||||
|
},
|
||||||
|
LabelsRefs: s.SymbolizeLabels([]string{"__name__", "metric1", "foo", "bar1"}, nil),
|
||||||
|
Samples: []*writev2.Sample{
|
||||||
|
{Value: 1.1, Timestamp: 1214141},
|
||||||
|
{Value: 1.5, Timestamp: 1214180},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Metadata: &writev2.Metadata{
|
||||||
|
Type: writev2.Metadata_METRIC_TYPE_COUNTER,
|
||||||
|
HelpRef: s.Symbolize("My lovely counter"),
|
||||||
|
},
|
||||||
|
LabelsRefs: s.SymbolizeLabels([]string{"__name__", "metric1", "foo", "bar2"}, nil),
|
||||||
|
Samples: []*writev2.Sample{
|
||||||
|
{Value: 1231311, Timestamp: 1214141},
|
||||||
|
{Value: 1310001, Timestamp: 1214180},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stats(req *writev2.Request) (s WriteResponseStats) {
|
||||||
|
s.Confirmed = true
|
||||||
|
for _, ts := range req.Timeseries {
|
||||||
|
s.Samples += len(ts.Samples)
|
||||||
|
s.Histograms += len(ts.Histograms)
|
||||||
|
s.Exemplars += len(ts.Exemplars)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoteAPI_Write_WithHandler(t *testing.T) {
|
||||||
|
tLogger := slog.Default()
|
||||||
|
mStore := &mockStorage{}
|
||||||
|
srv := httptest.NewServer(NewRemoteWriteHandler(tLogger, mStore))
|
||||||
|
t.Cleanup(srv.Close)
|
||||||
|
|
||||||
|
cl, err := api.NewClient(api.Config{
|
||||||
|
Address: srv.URL,
|
||||||
|
RoundTripper: srv.Client().Transport,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
client, err := NewAPI(cl, WithAPILogger(tLogger))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := testV2()
|
||||||
|
s, err := client.Write(context.Background(), req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(stats(req), s); diff != "" {
|
||||||
|
t.Fatal("unexpected stats", diff)
|
||||||
|
}
|
||||||
|
if len(mStore.v2Reqs) != 1 {
|
||||||
|
t.Fatal("expected 1 request stored, got", mStore.v2Reqs)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(req, mStore.v2Reqs[0], protocmp.Transform()); diff != "" {
|
||||||
|
t.Fatal("unexpected request received", diff)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,178 @@
|
||||||
|
// Copyright 2024 The Prometheus Authors
|
||||||
|
// 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 remote
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
versionHeader = "X-Prometheus-Remote-Write-Version"
|
||||||
|
version1HeaderValue = "0.1.0"
|
||||||
|
version20HeaderValue = "2.0.0"
|
||||||
|
appProtoContentType = "application/x-protobuf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Compression represents the encoding. Currently remote storage supports only
|
||||||
|
// one, but we experiment with more, thus leaving the compression scaffolding
|
||||||
|
// for now.
|
||||||
|
type Compression string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SnappyBlockCompression represents https://github.com/google/snappy/blob/2c94e11145f0b7b184b831577c93e5a41c4c0346/format_description.txt
|
||||||
|
SnappyBlockCompression Compression = "snappy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WriteProtoFullName represents the fully qualified name of the protobuf message
|
||||||
|
// to use in Remote write 1.0 and 2.0 protocols.
|
||||||
|
// See https://prometheus.io/docs/specs/remote_write_spec_2_0/#protocol.
|
||||||
|
type WriteProtoFullName protoreflect.FullName
|
||||||
|
|
||||||
|
const (
|
||||||
|
// WriteProtoFullNameV1 represents the `prometheus.WriteRequest` protobuf
|
||||||
|
// message introduced in the https://prometheus.io/docs/specs/remote_write_spec/.
|
||||||
|
// DEPRECATED: Use WriteProtoFullNameV2 instead.
|
||||||
|
WriteProtoFullNameV1 WriteProtoFullName = "prometheus.WriteRequest"
|
||||||
|
// WriteProtoFullNameV2 represents the `io.prometheus.write.v2.Request` protobuf
|
||||||
|
// message introduced in https://prometheus.io/docs/specs/remote_write_spec_2_0/
|
||||||
|
WriteProtoFullNameV2 WriteProtoFullName = "io.prometheus.write.v2.Request"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Validate returns error if the given reference for the protobuf message is not supported.
|
||||||
|
func (n WriteProtoFullName) Validate() error {
|
||||||
|
switch n {
|
||||||
|
case WriteProtoFullNameV1, WriteProtoFullNameV2:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown remote write protobuf message %v, supported: %v", n, protoMsgs{WriteProtoFullNameV1, WriteProtoFullNameV2}.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type protoMsgs []WriteProtoFullName
|
||||||
|
|
||||||
|
func (m protoMsgs) Strings() []string {
|
||||||
|
ret := make([]string, 0, len(m))
|
||||||
|
for _, typ := range m {
|
||||||
|
ret = append(ret, string(typ))
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m protoMsgs) String() string {
|
||||||
|
return strings.Join(m.Strings(), ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
var contentTypeHeaders = map[WriteProtoFullName]string{
|
||||||
|
WriteProtoFullNameV1: appProtoContentType, // Also application/x-protobuf;proto=prometheus.WriteRequest but simplified for compatibility with 1.x spec.
|
||||||
|
WriteProtoFullNameV2: appProtoContentType + ";proto=io.prometheus.write.v2.Request",
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentTypeHeader returns content type header value for the given proto message
|
||||||
|
// or empty string for unknown proto message.
|
||||||
|
func contentTypeHeader(m WriteProtoFullName) string {
|
||||||
|
return contentTypeHeaders[m]
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
writtenSamplesHeader = "X-Prometheus-Remote-Write-Samples-Written"
|
||||||
|
writtenHistogramsHeader = "X-Prometheus-Remote-Write-Histograms-Written"
|
||||||
|
writtenExemplarsHeader = "X-Prometheus-Remote-Write-Exemplars-Written"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WriteResponseStats represents the response, remote write statistics.
|
||||||
|
type WriteResponseStats struct {
|
||||||
|
// Samples represents X-Prometheus-Remote-Write-Written-Samples
|
||||||
|
Samples int
|
||||||
|
// Histograms represents X-Prometheus-Remote-Write-Written-Histograms
|
||||||
|
Histograms int
|
||||||
|
// Exemplars represents X-Prometheus-Remote-Write-Written-Exemplars
|
||||||
|
Exemplars int
|
||||||
|
|
||||||
|
// Confirmed means we can trust those statistics from the point of view
|
||||||
|
// of the PRW 2.0 spec. When parsed from headers, it means we got at least one
|
||||||
|
// response header from the Receiver to confirm those numbers, meaning it must
|
||||||
|
// be at least 2.0 Receiver. See ParseWriteResponseStats for details.
|
||||||
|
Confirmed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoDataWritten returns true if statistics indicate no data was written.
|
||||||
|
func (s WriteResponseStats) NoDataWritten() bool {
|
||||||
|
return (s.Samples + s.Histograms + s.Exemplars) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllSamples returns both float and histogram sample numbers.
|
||||||
|
func (s WriteResponseStats) AllSamples() int {
|
||||||
|
return s.Samples + s.Histograms
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add returns the sum of this WriteResponseStats plus the given WriteResponseStats.
|
||||||
|
func (s WriteResponseStats) Add(rs WriteResponseStats) WriteResponseStats {
|
||||||
|
s.Confirmed = rs.Confirmed
|
||||||
|
s.Samples += rs.Samples
|
||||||
|
s.Histograms += rs.Histograms
|
||||||
|
s.Exemplars += rs.Exemplars
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHeaders sets response headers in a given response writer.
|
||||||
|
// Make sure to use it before http.ResponseWriter.WriteHeader and .Write.
|
||||||
|
func (s WriteResponseStats) SetHeaders(w http.ResponseWriter) {
|
||||||
|
h := w.Header()
|
||||||
|
h.Set(writtenSamplesHeader, strconv.Itoa(s.Samples))
|
||||||
|
h.Set(writtenHistogramsHeader, strconv.Itoa(s.Histograms))
|
||||||
|
h.Set(writtenExemplarsHeader, strconv.Itoa(s.Exemplars))
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseWriteResponseStats returns WriteResponseStats parsed from the response headers.
|
||||||
|
//
|
||||||
|
// As per 2.0 spec, missing header means 0. However, abrupt HTTP errors, 1.0 Receivers
|
||||||
|
// or buggy 2.0 Receivers might result in no response headers specified and that
|
||||||
|
// might NOT necessarily mean nothing was written. To represent that we set
|
||||||
|
// s.Confirmed = true only when see at least on response header.
|
||||||
|
//
|
||||||
|
// Error is returned when any of the header fails to parse as int64.
|
||||||
|
func parseWriteResponseStats(r *http.Response) (s WriteResponseStats, err error) {
|
||||||
|
var (
|
||||||
|
errs []error
|
||||||
|
h = r.Header
|
||||||
|
)
|
||||||
|
if v := h.Get(writtenSamplesHeader); v != "" { // Empty means zero.
|
||||||
|
s.Confirmed = true
|
||||||
|
if s.Samples, err = strconv.Atoi(v); err != nil {
|
||||||
|
s.Samples = 0
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v := h.Get(writtenHistogramsHeader); v != "" { // Empty means zero.
|
||||||
|
s.Confirmed = true
|
||||||
|
if s.Histograms, err = strconv.Atoi(v); err != nil {
|
||||||
|
s.Histograms = 0
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v := h.Get(writtenExemplarsHeader); v != "" { // Empty means zero.
|
||||||
|
s.Confirmed = true
|
||||||
|
if s.Exemplars, err = strconv.Atoi(v); err != nil {
|
||||||
|
s.Exemplars = 0
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s, errors.Join(errs...)
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
// Copyright (c) The EfficientGo Authors.
|
||||||
|
// Licensed under the Apache License 2.0.
|
||||||
|
|
||||||
|
// Initially copied from Cortex project.
|
||||||
|
|
||||||
|
// Package backoff implements backoff timers which increases wait time on every retry, incredibly useful
|
||||||
|
// in distributed system timeout functionalities.
|
||||||
|
package backoff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config configures a Backoff.
|
||||||
|
type Config struct {
|
||||||
|
Min time.Duration `yaml:"min_period"` // Start backoff at this level
|
||||||
|
Max time.Duration `yaml:"max_period"` // Increase exponentially to this level
|
||||||
|
MaxRetries int `yaml:"max_retries"` // Give up after this many; zero means infinite retries
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backoff implements exponential backoff with randomized wait times.
|
||||||
|
type Backoff struct {
|
||||||
|
cfg Config
|
||||||
|
ctx context.Context
|
||||||
|
numRetries int
|
||||||
|
nextDelayMin time.Duration
|
||||||
|
nextDelayMax time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a Backoff object. Pass a Context that can also terminate the operation.
|
||||||
|
func New(ctx context.Context, cfg Config) *Backoff {
|
||||||
|
return &Backoff{
|
||||||
|
cfg: cfg,
|
||||||
|
ctx: ctx,
|
||||||
|
nextDelayMin: cfg.Min,
|
||||||
|
nextDelayMax: doubleDuration(cfg.Min, cfg.Max),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the Backoff back to its initial condition.
|
||||||
|
func (b *Backoff) Reset() {
|
||||||
|
b.numRetries = 0
|
||||||
|
b.nextDelayMin = b.cfg.Min
|
||||||
|
b.nextDelayMax = doubleDuration(b.cfg.Min, b.cfg.Max)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ongoing returns true if caller should keep going.
|
||||||
|
func (b *Backoff) Ongoing() bool {
|
||||||
|
// Stop if Context has errored or max retry count is exceeded.
|
||||||
|
return b.ctx.Err() == nil && (b.cfg.MaxRetries == 0 || b.numRetries < b.cfg.MaxRetries)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns the reason for terminating the backoff, or nil if it didn't terminate.
|
||||||
|
func (b *Backoff) Err() error {
|
||||||
|
if b.ctx.Err() != nil {
|
||||||
|
return b.ctx.Err()
|
||||||
|
}
|
||||||
|
if b.cfg.MaxRetries != 0 && b.numRetries >= b.cfg.MaxRetries {
|
||||||
|
return fmt.Errorf("terminated after %d retries", b.numRetries)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumRetries returns the number of retries so far.
|
||||||
|
func (b *Backoff) NumRetries() int {
|
||||||
|
return b.numRetries
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait sleeps for the backoff time then increases the retry count and backoff time.
|
||||||
|
// Returns immediately if Context is terminated.
|
||||||
|
func (b *Backoff) Wait() {
|
||||||
|
// Increase the number of retries and get the next delay.
|
||||||
|
sleepTime := b.NextDelay()
|
||||||
|
|
||||||
|
if b.Ongoing() {
|
||||||
|
select {
|
||||||
|
case <-b.ctx.Done():
|
||||||
|
case <-time.After(sleepTime):
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Backoff) NextDelay() time.Duration {
|
||||||
|
b.numRetries++
|
||||||
|
|
||||||
|
// Handle the edge case the min and max have the same value
|
||||||
|
// (or due to some misconfig max is < min).
|
||||||
|
if b.nextDelayMin >= b.nextDelayMax {
|
||||||
|
return b.nextDelayMin
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a jitter within the next exponential backoff range.
|
||||||
|
sleepTime := b.nextDelayMin + time.Duration(rand.Int63n(int64(b.nextDelayMax-b.nextDelayMin)))
|
||||||
|
|
||||||
|
// Apply the exponential backoff to calculate the next jitter
|
||||||
|
// range, unless we've already reached the max.
|
||||||
|
if b.nextDelayMax < b.cfg.Max {
|
||||||
|
b.nextDelayMin = doubleDuration(b.nextDelayMin, b.cfg.Max)
|
||||||
|
b.nextDelayMax = doubleDuration(b.nextDelayMax, b.cfg.Max)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sleepTime
|
||||||
|
}
|
||||||
|
|
||||||
|
func doubleDuration(value, maxValue time.Duration) time.Duration {
|
||||||
|
value = value * 2
|
||||||
|
if value <= maxValue {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return maxValue
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
// Copyright (c) The EfficientGo Authors.
|
||||||
|
// Licensed under the Apache License 2.0.
|
||||||
|
|
||||||
|
// Initially copied from Cortex project.
|
||||||
|
|
||||||
|
package backoff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBackoff_NextDelay(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := map[string]struct {
|
||||||
|
minBackoff time.Duration
|
||||||
|
maxBackoff time.Duration
|
||||||
|
expectedRanges [][]time.Duration
|
||||||
|
}{
|
||||||
|
"exponential backoff with jitter honoring min and max": {
|
||||||
|
minBackoff: 100 * time.Millisecond,
|
||||||
|
maxBackoff: 10 * time.Second,
|
||||||
|
expectedRanges: [][]time.Duration{
|
||||||
|
{100 * time.Millisecond, 200 * time.Millisecond},
|
||||||
|
{200 * time.Millisecond, 400 * time.Millisecond},
|
||||||
|
{400 * time.Millisecond, 800 * time.Millisecond},
|
||||||
|
{800 * time.Millisecond, 1600 * time.Millisecond},
|
||||||
|
{1600 * time.Millisecond, 3200 * time.Millisecond},
|
||||||
|
{3200 * time.Millisecond, 6400 * time.Millisecond},
|
||||||
|
{6400 * time.Millisecond, 10000 * time.Millisecond},
|
||||||
|
{6400 * time.Millisecond, 10000 * time.Millisecond},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"exponential backoff with max equal to the end of a range": {
|
||||||
|
minBackoff: 100 * time.Millisecond,
|
||||||
|
maxBackoff: 800 * time.Millisecond,
|
||||||
|
expectedRanges: [][]time.Duration{
|
||||||
|
{100 * time.Millisecond, 200 * time.Millisecond},
|
||||||
|
{200 * time.Millisecond, 400 * time.Millisecond},
|
||||||
|
{400 * time.Millisecond, 800 * time.Millisecond},
|
||||||
|
{400 * time.Millisecond, 800 * time.Millisecond},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"exponential backoff with max equal to the end of a range + 1": {
|
||||||
|
minBackoff: 100 * time.Millisecond,
|
||||||
|
maxBackoff: 801 * time.Millisecond,
|
||||||
|
expectedRanges: [][]time.Duration{
|
||||||
|
{100 * time.Millisecond, 200 * time.Millisecond},
|
||||||
|
{200 * time.Millisecond, 400 * time.Millisecond},
|
||||||
|
{400 * time.Millisecond, 800 * time.Millisecond},
|
||||||
|
{800 * time.Millisecond, 801 * time.Millisecond},
|
||||||
|
{800 * time.Millisecond, 801 * time.Millisecond},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"exponential backoff with max equal to the end of a range - 1": {
|
||||||
|
minBackoff: 100 * time.Millisecond,
|
||||||
|
maxBackoff: 799 * time.Millisecond,
|
||||||
|
expectedRanges: [][]time.Duration{
|
||||||
|
{100 * time.Millisecond, 200 * time.Millisecond},
|
||||||
|
{200 * time.Millisecond, 400 * time.Millisecond},
|
||||||
|
{400 * time.Millisecond, 799 * time.Millisecond},
|
||||||
|
{400 * time.Millisecond, 799 * time.Millisecond},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"min backoff is equal to max": {
|
||||||
|
minBackoff: 100 * time.Millisecond,
|
||||||
|
maxBackoff: 100 * time.Millisecond,
|
||||||
|
expectedRanges: [][]time.Duration{
|
||||||
|
{100 * time.Millisecond, 100 * time.Millisecond},
|
||||||
|
{100 * time.Millisecond, 100 * time.Millisecond},
|
||||||
|
{100 * time.Millisecond, 100 * time.Millisecond},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"min backoff is greater then max": {
|
||||||
|
minBackoff: 200 * time.Millisecond,
|
||||||
|
maxBackoff: 100 * time.Millisecond,
|
||||||
|
expectedRanges: [][]time.Duration{
|
||||||
|
{200 * time.Millisecond, 200 * time.Millisecond},
|
||||||
|
{200 * time.Millisecond, 200 * time.Millisecond},
|
||||||
|
{200 * time.Millisecond, 200 * time.Millisecond},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for testName, testData := range tests {
|
||||||
|
testData := testData
|
||||||
|
|
||||||
|
t.Run(testName, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
b := New(context.Background(), Config{
|
||||||
|
Min: testData.minBackoff,
|
||||||
|
Max: testData.maxBackoff,
|
||||||
|
MaxRetries: len(testData.expectedRanges),
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, expectedRange := range testData.expectedRanges {
|
||||||
|
delay := b.NextDelay()
|
||||||
|
|
||||||
|
if delay < expectedRange[0] || delay > expectedRange[1] {
|
||||||
|
t.Errorf("%d expected to be within %d and %d", delay, expectedRange[0], expectedRange[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
// Copyright (c) 2022 PlanetScale Inc. All rights reserved.
|
||||||
|
|
||||||
|
// Package protohelpers provides helper functions for encoding and decoding protobuf messages.
|
||||||
|
// The spec can be found at https://protobuf.dev/programming-guides/encoding/.
|
||||||
|
package protohelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrInvalidLength is returned when decoding a negative length.
|
||||||
|
ErrInvalidLength = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||||
|
// ErrIntOverflow is returned when decoding a varint representation of an integer that overflows 64 bits.
|
||||||
|
ErrIntOverflow = fmt.Errorf("proto: integer overflow")
|
||||||
|
// ErrUnexpectedEndOfGroup is returned when decoding a group end without a corresponding group start.
|
||||||
|
ErrUnexpectedEndOfGroup = fmt.Errorf("proto: unexpected end of group")
|
||||||
|
)
|
||||||
|
|
||||||
|
// EncodeVarint encodes a uint64 into a varint-encoded byte slice and returns the offset of the encoded value.
|
||||||
|
// The provided offset is the offset after the last byte of the encoded value.
|
||||||
|
func EncodeVarint(dAtA []byte, offset int, v uint64) int {
|
||||||
|
offset -= SizeOfVarint(v)
|
||||||
|
base := offset
|
||||||
|
for v >= 1<<7 {
|
||||||
|
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||||
|
v >>= 7
|
||||||
|
offset++
|
||||||
|
}
|
||||||
|
dAtA[offset] = uint8(v)
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
// SizeOfVarint returns the size of the varint-encoded value.
|
||||||
|
func SizeOfVarint(x uint64) (n int) {
|
||||||
|
return (bits.Len64(x|1) + 6) / 7
|
||||||
|
}
|
||||||
|
|
||||||
|
// SizeOfZigzag returns the size of the zigzag-encoded value.
|
||||||
|
func SizeOfZigzag(x uint64) (n int) {
|
||||||
|
return SizeOfVarint((x << 1) ^ uint64(int64(x)>>63))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the first record of the byte slice and return the offset of the next record.
|
||||||
|
func Skip(dAtA []byte) (n int, err error) {
|
||||||
|
l := len(dAtA)
|
||||||
|
iNdEx := 0
|
||||||
|
depth := 0
|
||||||
|
for iNdEx < l {
|
||||||
|
var wire uint64
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return 0, ErrIntOverflow
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
wire |= (uint64(b) & 0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wireType := int(wire & 0x7)
|
||||||
|
switch wireType {
|
||||||
|
case 0:
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return 0, ErrIntOverflow
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
iNdEx++
|
||||||
|
if dAtA[iNdEx-1] < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
iNdEx += 8
|
||||||
|
case 2:
|
||||||
|
var length int
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return 0, ErrIntOverflow
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
length |= (int(b) & 0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if length < 0 {
|
||||||
|
return 0, ErrInvalidLength
|
||||||
|
}
|
||||||
|
iNdEx += length
|
||||||
|
case 3:
|
||||||
|
depth++
|
||||||
|
case 4:
|
||||||
|
if depth == 0 {
|
||||||
|
return 0, ErrUnexpectedEndOfGroup
|
||||||
|
}
|
||||||
|
depth--
|
||||||
|
case 5:
|
||||||
|
iNdEx += 4
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
|
||||||
|
}
|
||||||
|
if iNdEx < 0 {
|
||||||
|
return 0, ErrInvalidLength
|
||||||
|
}
|
||||||
|
if depth == 0 {
|
||||||
|
return iNdEx, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
Loading…
Reference in New Issue