diff --git a/flake.nix b/flake.nix index 99b83bc59..158c8403c 100644 --- a/flake.nix +++ b/flake.nix @@ -49,7 +49,7 @@ # touched (guest-image/default.nix:82-87). vendorHash pins the fetched set — # the whole module graph, so it matches guestd's proxyVendor hash. Recompute # with lib.fakeHash on a go.mod/go.sum move. - vendorHash = "sha256-LmCUIADU45KTiLTuAvq57ZdFzdUqrcGctopPtT/u/yk="; + vendorHash = "sha256-di6nYpDDZblu6TPpendW88l5LMRZNzSE2t2tiWfms9c="; in { packages = forAllSystems ( diff --git a/go/go.mod b/go/go.mod index 53907c2c4..e0abab89b 100644 --- a/go/go.mod +++ b/go/go.mod @@ -31,24 +31,34 @@ require ( github.com/vishvananda/netlink v1.3.1 github.com/wailsapp/wails/v3 v3.0.0-beta.14 github.com/zalando/go-keyring v0.2.8 - go.yaml.in/yaml/v3 v3.0.4 - golang.org/x/net v0.56.0 + go.opentelemetry.io/otel v1.46.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.46.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0 + go.opentelemetry.io/otel/sdk v1.46.0 + go.opentelemetry.io/otel/sdk/metric v1.46.0 + go.opentelemetry.io/otel/trace v1.46.0 + go.yaml.in/yaml/v3 v3.0.5 + golang.org/x/net v0.58.0 golang.org/x/sync v0.22.0 - google.golang.org/protobuf v1.36.11 + google.golang.org/protobuf v1.36.12 ) require ( github.com/adrg/xdg v0.5.3 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coder/websocket v1.8.14 // indirect github.com/danieljoos/wincred v1.2.3 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/ebitengine/purego v0.8.2 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-logr/logr v1.4.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/godbus/dbus/v5 v5.2.2 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect @@ -77,8 +87,15 @@ require ( github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/vishvananda/netns v0.0.5 // indirect github.com/zeebo/xxh3 v1.1.0 // indirect - golang.org/x/crypto v0.53.0 // indirect - golang.org/x/sys v0.46.0 // indirect - golang.org/x/text v0.39.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0 // indirect + go.opentelemetry.io/otel/metric v1.46.0 // indirect + go.opentelemetry.io/proto/otlp v1.11.0 // indirect + golang.org/x/crypto v0.55.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/grpc v1.83.1 // indirect gopkg.in/ini.v1 v1.67.2 // indirect ) diff --git a/go/go.sum b/go/go.sum index d32120a5d..230a32276 100644 --- a/go/go.sum +++ b/go/go.sum @@ -8,6 +8,8 @@ github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= github.com/cachix/secretspec/secretspec-go v0.15.0 h1:DMxh5/hkgZyMysSyFzf9RwUxoj+NfgmlnS6UKJ8n0k4= github.com/cachix/secretspec/secretspec-go v0.15.0/go.mod h1:4QjSax/Qd3JXEqNFUwvov93jnhVD1PmcDbeQBLg2r9Y= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= @@ -16,7 +18,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ= github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= @@ -28,16 +29,25 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU= github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= +github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 h1:/Tnpcb2E0Pz/tN9s3bfEY2Q8ePCEX9iuS+cneUwncnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0/go.mod h1:zOBXOsUaBSjKgmH4OGzV1esUpR3oUSCPYVd2cUBjKYY= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8= @@ -94,7 +104,6 @@ github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= @@ -128,8 +137,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tinylib/msgp v1.6.1 h1:ESRv8eL3u+DNHUoSAAQRE50Hm162zqAnBoGv9PzScPY= @@ -148,12 +158,37 @@ github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.46.0 h1:FHt5/CDyVxi/8IM1CH7VE/rRgq3kLHa2mSTVMO8AWyc= +go.opentelemetry.io/otel v1.46.0/go.mod h1:Gj3SEScelsNC45tp4nSxRYlS+f5iez7W8XPMCt905kE= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.46.0 h1:AP23h/mFgb/lc7tdck1Kfn9qxsM8TAeNPCU5C3pzaps= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.46.0/go.mod h1:K4EqCe1b4kGk5WR690ntg9LaBfsPoV32FwthbyoptuA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0 h1:OFnwLJr+pF3iHrlGSzbxyuo6/6HyBlnlN1CWEJmBVcw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0/go.mod h1:716wFneO0ov19A2beH5hjfh9AK5z/VWNAtDijp1Y0/g= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0 h1:KrC1YrQeSt46ITMWAbgQx1M1eV1/1TKzttrBzymPmss= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0/go.mod h1:zDSEzoEqsOrgBeGvH66KRgxh90VonFyJqBHA0Pk3+rM= +go.opentelemetry.io/otel/metric v1.46.0 h1:yBnkXvgV7AXFILZc5K6IZe/CBFF3OS7BJ8ov6/lj0K8= +go.opentelemetry.io/otel/metric v1.46.0/go.mod h1:iPmdWqifKUdzziPkvvzIJXITl56fQx2mGM/DHLB3/2o= +go.opentelemetry.io/otel/metric/x v0.68.0 h1:TA/cBT23D3MnxYPwHL7YFOdYGdx0A0v+s7Mzotpd1dU= +go.opentelemetry.io/otel/metric/x v0.68.0/go.mod h1:agudOmvWhwUTjgibWDzxD2PoWYnpw5Ht5jISYOD2Hd4= +go.opentelemetry.io/otel/sdk v1.46.0 h1:h5CNQQjEbuQXY/JfZtgt3i7HVFV3aHPO2OAwO2eTYPI= +go.opentelemetry.io/otel/sdk v1.46.0/go.mod h1:GAERFXFt5SYCEB+YiKUbMBeza6UaDH7GmGOZEfh2gSM= +go.opentelemetry.io/otel/sdk/metric v1.46.0 h1:0piZ26EG4RBfebb2jhDH6ERCYHoVWduc3kLgPCwSnSE= +go.opentelemetry.io/otel/sdk/metric v1.46.0/go.mod h1:I1PbKrdVc8Qu8HYVDNtqVIwLwjNrhsV/uFuxfwg8mO4= +go.opentelemetry.io/otel/trace v1.46.0 h1:OULy7ccdJnZtJ0UDYFOIGaCmiWzJ8Vi2G/Rsu60qs1c= +go.opentelemetry.io/otel/trace v1.46.0/go.mod h1:J7GAXweO77XSFkB/rmAqk9D6ihszhFjLU+d9WuUxDLI= +go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk= +go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= -golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= +golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= +golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= +golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -162,17 +197,22 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus= -golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y= +google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/ini.v1 v1.67.2 h1:JtOSMb9OuaCZKr7h5D/h6iii14sK0hLbplTc6frx4Ss= gopkg.in/ini.v1 v1.67.2/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss= 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= diff --git a/go/internal/otel/config.go b/go/internal/otel/config.go new file mode 100644 index 000000000..319242aff --- /dev/null +++ b/go/internal/otel/config.go @@ -0,0 +1,25 @@ +// Package otel is the single Go OTel convention for the Compass backend +// (compass-server and compass-runner). It is the only place a TracerProvider +// or MeterProvider is constructed, so the repo keeps exactly one +// tracer/exporter posture. +// +// Off by default, endpoint-gated: when cfg.Endpoint (the +// OTEL_EXPORTER_OTLP_ENDPOINT value) is empty, Setup* installs NO global +// provider and returns a non-nil no-op shutdown — zero overhead, no network +// egress — mirroring the agent's src/transport/otel-layer.ts. When the +// endpoint is set, exporters read OTEL_EXPORTER_OTLP_ENDPOINT themselves +// (standard OTLP base-endpoint per-signal path suffixing), so cfg.Endpoint +// serves purely as the on/off switch and is never passed as an explicit URL. +package otel + +// Config carries the per-binary identity and the endpoint gate. +type Config struct { + ServiceName string // "compass-server" | "compass-runner" + ServiceVersion string // each main's ldflags var version + Endpoint string // OTEL_EXPORTER_OTLP_ENDPOINT; empty = disabled +} + +// enabled reports whether an export pipeline should be installed. +func (c Config) enabled() bool { + return c.Endpoint != "" +} diff --git a/go/internal/otel/interceptor.go b/go/internal/otel/interceptor.go new file mode 100644 index 000000000..d48735f24 --- /dev/null +++ b/go/internal/otel/interceptor.go @@ -0,0 +1,38 @@ +package otel + +import ( + "context" + + "connectrpc.com/connect" + "go.opentelemetry.io/otel/trace" +) + +// traceResponseHeader is the W3C trace-context response header (still a draft; +// DECIDED by compass-obs to use the standard name + "00-…" grammar anyway). +const traceResponseHeader = "traceresponse" + +// NewTraceResponseInterceptor sets the "traceresponse" response header from the +// handler span's context on every unary response (the UI/PostHog trace_id +// source). It is a no-op when there is no active span (no provider installed, +// or an unsampled/absent span context). +func NewTraceResponseInterceptor() connect.UnaryInterceptorFunc { + return connect.UnaryInterceptorFunc(func(next connect.UnaryFunc) connect.UnaryFunc { + return connect.UnaryFunc(func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) { + resp, err := next(ctx, req) + if err != nil { + return resp, err + } + sc := trace.SpanContextFromContext(ctx) + if resp != nil && sc.IsValid() { + resp.Header().Set(traceResponseHeader, formatTraceResponse(sc)) + } + return resp, err + }) + }) +} + +// formatTraceResponse renders a span context as the W3C "00-…" grammar +// ("00-<32hex traceid>-<16hex spanid>-<2hex flags>"). +func formatTraceResponse(sc trace.SpanContext) string { + return "00-" + sc.TraceID().String() + "-" + sc.SpanID().String() + "-" + sc.TraceFlags().String() +} diff --git a/go/internal/otel/otel_test.go b/go/internal/otel/otel_test.go new file mode 100644 index 000000000..e996b3881 --- /dev/null +++ b/go/internal/otel/otel_test.go @@ -0,0 +1,277 @@ +package otel + +import ( + "context" + "regexp" + "testing" + + "connectrpc.com/connect" + "go.opentelemetry.io/otel" + noopmetric "go.opentelemetry.io/otel/metric/noop" + sdkmetric "go.opentelemetry.io/otel/sdk/metric" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" +) + +// w3cTraceparent matches the W3C traceparent grammar: 00-<32hex>-<16hex>-<2hex>. +var w3cTraceparent = regexp.MustCompile(`^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$`) + +// resetGlobals restores the global tracer/meter providers and propagator so an +// enabled-path test never leaks its installed globals into another test. +func resetGlobals(t *testing.T) { + t.Helper() + prevTP := otel.GetTracerProvider() + prevMP := otel.GetMeterProvider() + prevProp := otel.GetTextMapPropagator() + t.Cleanup(func() { + otel.SetTracerProvider(prevTP) + otel.SetMeterProvider(prevMP) + otel.SetTextMapPropagator(prevProp) + }) +} + +// TestSetupTracerProviderDisabled asserts the empty-endpoint path returns a +// non-nil no-op shutdown and installs NO global tracer provider. +func TestSetupTracerProviderDisabled(t *testing.T) { + resetGlobals(t) + // A sentinel noop provider proves Setup* did not overwrite the global. + sentinel := noop.NewTracerProvider() + otel.SetTracerProvider(sentinel) + + shutdown, err := SetupTracerProvider(context.Background(), Config{ServiceName: "compass-server"}) + if err != nil { + t.Fatalf("disabled SetupTracerProvider err = %v, want nil", err) + } + if shutdown == nil { + t.Fatal("disabled SetupTracerProvider returned nil shutdown, want non-nil no-op") + } + if err := shutdown(context.Background()); err != nil { + t.Fatalf("no-op shutdown err = %v, want nil", err) + } + if _, ok := otel.GetTracerProvider().(*sdktrace.TracerProvider); ok { + t.Fatal("disabled path installed an SDK TracerProvider, want none") + } +} + +// TestSetupMeterProviderDisabled asserts the empty-endpoint path returns a +// non-nil no-op shutdown and installs NO global meter provider. +func TestSetupMeterProviderDisabled(t *testing.T) { + resetGlobals(t) + // A sentinel noop provider proves Setup* did not overwrite the global. + sentinel := noopmetric.NewMeterProvider() + otel.SetMeterProvider(sentinel) + + shutdown, err := SetupMeterProvider(context.Background(), Config{ServiceName: "compass-runner"}) + if err != nil { + t.Fatalf("disabled SetupMeterProvider err = %v, want nil", err) + } + if shutdown == nil { + t.Fatal("disabled SetupMeterProvider returned nil shutdown, want non-nil no-op") + } + if err := shutdown(context.Background()); err != nil { + t.Fatalf("no-op shutdown err = %v, want nil", err) + } + if _, ok := otel.GetMeterProvider().(*sdkmetric.MeterProvider); ok { + t.Fatal("disabled path installed an SDK MeterProvider, want none") + } +} + +// TestSetupTracerProviderEnabledResource asserts the enabled path installs a +// global SDK TracerProvider, a W3C propagator, and a resource carrying +// service.name/service.version. +func TestSetupTracerProviderEnabledResource(t *testing.T) { + resetGlobals(t) + cfg := Config{ServiceName: "compass-server", ServiceVersion: "1.2.3", Endpoint: "http://localhost:4318"} + + shutdown, err := SetupTracerProvider(context.Background(), cfg) + if err != nil { + t.Fatalf("enabled SetupTracerProvider err = %v", err) + } + t.Cleanup(func() { _ = shutdown(context.Background()) }) // best-effort flush; no collector, error not actionable in test + + if _, ok := otel.GetTracerProvider().(*sdktrace.TracerProvider); !ok { + t.Fatalf("enabled path did not install an SDK TracerProvider, got %T", otel.GetTracerProvider()) + } + + // The enabled path installs the global W3C TraceContext propagator. + if fields := otel.GetTextMapPropagator().Fields(); len(fields) == 0 || fields[0] != "traceparent" { + t.Errorf("global propagator fields = %v, want traceparent", fields) + } +} + +// TestNewResourceAttributes asserts the resource the enabled providers carry +// tags service.name/service.version from Config. newResource is the single +// resource builder both SetupTracerProvider and SetupMeterProvider use, so this +// locks the attribute contract at its source. +func TestNewResourceAttributes(t *testing.T) { + res, err := newResource(context.Background(), Config{ServiceName: "compass-server", ServiceVersion: "1.2.3"}) + if err != nil { + t.Fatalf("newResource err = %v", err) + } + attrs := map[string]string{} + for _, kv := range res.Attributes() { + attrs[string(kv.Key)] = kv.Value.AsString() + } + if attrs["service.name"] != "compass-server" { + t.Errorf("resource service.name = %q, want compass-server", attrs["service.name"]) + } + if attrs["service.version"] != "1.2.3" { + t.Errorf("resource service.version = %q, want 1.2.3", attrs["service.version"]) + } +} + +// TestTraceparentNoSpan asserts Traceparent returns "" when ctx has no span. +func TestTraceparentNoSpan(t *testing.T) { + if got := Traceparent(context.Background()); got != "" { + t.Errorf("Traceparent(no span) = %q, want empty", got) + } +} + +// TestTraceparentWithSpan asserts Traceparent returns a W3C-grammar string +// under a started span. +func TestTraceparentWithSpan(t *testing.T) { + tp := sdktrace.NewTracerProvider() + t.Cleanup(func() { _ = tp.Shutdown(context.Background()) }) + ctx, span := tp.Tracer("test").Start(context.Background(), "op") + defer span.End() + + got := Traceparent(ctx) + if !w3cTraceparent.MatchString(got) { + t.Errorf("Traceparent(span) = %q, want W3C grammar 00-<32hex>-<16hex>-<2hex>", got) + } +} + +// TestContextWithTraceparentRoundTrip asserts a valid traceparent round-trips +// into a context carrying the matching remote span context. +func TestContextWithTraceparentRoundTrip(t *testing.T) { + const tp = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" + ctx := ContextWithTraceparent(context.Background(), tp) + + sc := trace.SpanContextFromContext(ctx) + if !sc.IsValid() { + t.Fatal("round-tripped span context is invalid") + } + if got := Traceparent(ctx); got != tp { + t.Errorf("round-trip Traceparent = %q, want %q", got, tp) + } +} + +// TestContextWithTraceparentMalformed asserts a malformed traceparent yields +// the input context unchanged (no span attached). +func TestContextWithTraceparentMalformed(t *testing.T) { + base := context.Background() + for _, bad := range []string{"", "not-a-traceparent", "00-xyz-abc-01", "00-00000000000000000000000000000000-0000000000000000-00"} { + got := ContextWithTraceparent(base, bad) + if sc := trace.SpanContextFromContext(got); sc.IsValid() { + t.Errorf("ContextWithTraceparent(%q) attached a valid span context, want unchanged", bad) + } + } +} + +// TestSetupMeterProviderEnabled asserts the enabled path installs a global SDK +// MeterProvider (mirrors the tracer enabled-path assertion). +func TestSetupMeterProviderEnabled(t *testing.T) { + resetGlobals(t) + cfg := Config{ServiceName: "compass-server", ServiceVersion: "1.2.3", Endpoint: "http://localhost:4318"} + shutdown, err := SetupMeterProvider(context.Background(), cfg) + if err != nil { + t.Fatalf("enabled SetupMeterProvider err = %v, want nil", err) + } + t.Cleanup(func() { _ = shutdown(context.Background()) }) // best-effort flush; no collector, error not actionable in test + if _, ok := otel.GetMeterProvider().(*sdkmetric.MeterProvider); !ok { + t.Fatalf("enabled path did not install an SDK MeterProvider, got %T", otel.GetMeterProvider()) + } +} + +// TestFormatTraceResponse asserts the pure header formatter emits the exact W3C +// traceresponse grammar for a known span context. +func TestFormatTraceResponse(t *testing.T) { + traceID, err := trace.TraceIDFromHex("0af7651916cd43dd8448eb211c80319c") + if err != nil { + t.Fatalf("TraceIDFromHex err = %v", err) + } + spanID, err := trace.SpanIDFromHex("b7ad6b7169203331") + if err != nil { + t.Fatalf("SpanIDFromHex err = %v", err) + } + sc := trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.FlagsSampled, + }) + got := formatTraceResponse(sc) + want := "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01" + if got != want { + t.Errorf("formatTraceResponse = %q, want %q", got, want) + } +} + +// TestTraceResponseInterceptor asserts the interceptor sets the traceresponse +// header when the ctx carries a valid span context, sets none otherwise, and +// never alters the handler's error. +func TestTraceResponseInterceptor(t *testing.T) { + interceptor := NewTraceResponseInterceptor() + + traceID, err := trace.TraceIDFromHex("0af7651916cd43dd8448eb211c80319c") + if err != nil { + t.Fatalf("TraceIDFromHex err = %v", err) + } + spanID, err := trace.SpanIDFromHex("b7ad6b7169203331") + if err != nil { + t.Fatalf("SpanIDFromHex err = %v", err) + } + validSC := trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.FlagsSampled, + }) + + t.Run("valid span sets header", func(t *testing.T) { + ctx := trace.ContextWithSpanContext(context.Background(), validSC) + resp := connect.NewResponse(&struct{}{}) + next := connect.UnaryFunc(func(context.Context, connect.AnyRequest) (connect.AnyResponse, error) { + return resp, nil + }) + got, err := interceptor(next)(ctx, connect.NewRequest(&struct{}{})) + if err != nil { + t.Fatalf("interceptor err = %v, want nil", err) + } + want := "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01" + if h := got.Header().Get(traceResponseHeader); h != want { + t.Errorf("%s = %q, want %q", traceResponseHeader, h, want) + } + }) + + t.Run("no span sets no header", func(t *testing.T) { + resp := connect.NewResponse(&struct{}{}) + next := connect.UnaryFunc(func(context.Context, connect.AnyRequest) (connect.AnyResponse, error) { + return resp, nil + }) + got, err := interceptor(next)(context.Background(), connect.NewRequest(&struct{}{})) + if err != nil { + t.Fatalf("interceptor err = %v, want nil", err) + } + if h := got.Header().Get(traceResponseHeader); h != "" { + t.Errorf("%s = %q, want empty", traceResponseHeader, h) + } + }) + + t.Run("handler error passthrough", func(t *testing.T) { + ctx := trace.ContextWithSpanContext(context.Background(), validSC) + wantErr := connect.NewError(connect.CodeInternal, errSentinel) + next := connect.UnaryFunc(func(context.Context, connect.AnyRequest) (connect.AnyResponse, error) { + return nil, wantErr + }) + _, err := interceptor(next)(ctx, connect.NewRequest(&struct{}{})) + if err != wantErr { + t.Errorf("interceptor err = %v, want the handler's error unchanged", err) + } + }) +} + +var errSentinel = &sentinelError{} + +type sentinelError struct{} + +func (*sentinelError) Error() string { return "boom" } diff --git a/go/internal/otel/provider.go b/go/internal/otel/provider.go new file mode 100644 index 000000000..8aa16bdd1 --- /dev/null +++ b/go/internal/otel/provider.go @@ -0,0 +1,90 @@ +package otel + +import ( + "context" + "fmt" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.26.0" +) + +// noopShutdown is the shutdown returned by the disabled (empty-endpoint) path: +// non-nil, and a no-op flush that always succeeds. +func noopShutdown(context.Context) error { return nil } + +// newResource builds the OTel resource tagging every span/metric with the +// binary's service.name and service.version. +func newResource(ctx context.Context, cfg Config) (*resource.Resource, error) { + return resource.New(ctx, + resource.WithAttributes( + semconv.ServiceName(cfg.ServiceName), + semconv.ServiceVersion(cfg.ServiceVersion), + ), + ) +} + +// SetupTracerProvider installs the global TracerProvider + W3C propagator when +// cfg.Endpoint is non-empty; otherwise it is a no-op. The returned shutdown +// flushes and stops the provider, bounded by the ctx it is given (callers +// derive a timeout from their own ctx — never Background()). +func SetupTracerProvider(ctx context.Context, cfg Config) (shutdown func(context.Context) error, err error) { + if !cfg.enabled() { + return noopShutdown, nil + } + + res, err := newResource(ctx, cfg) + if err != nil { + return nil, fmt.Errorf("otel: build resource: %w", err) + } + + // The exporter reads OTEL_EXPORTER_OTLP_ENDPOINT itself (standard OTLP + // base-endpoint per-signal path suffixing); passing an explicit URL would + // defeat the /v1/traces suffixing, so no endpoint option is set here. + exporter, err := otlptracehttp.New(ctx) + if err != nil { + return nil, fmt.Errorf("otel: new trace exporter: %w", err) + } + + tp := sdktrace.NewTracerProvider( + sdktrace.WithBatcher(exporter), + sdktrace.WithResource(res), + ) + otel.SetTracerProvider(tp) + otel.SetTextMapPropagator(propagation.TraceContext{}) + + return tp.Shutdown, nil +} + +// SetupMeterProvider installs the global MeterProvider (OTLP/http metrics +// exporter) under the same endpoint gate; a no-op when cfg.Endpoint is empty. +// With it installed, otelconnect emits RPC duration/count metrics from the same +// interceptor. Returns a shutdown that flushes and stops the provider. +func SetupMeterProvider(ctx context.Context, cfg Config) (shutdown func(context.Context) error, err error) { + if !cfg.enabled() { + return noopShutdown, nil + } + + res, err := newResource(ctx, cfg) + if err != nil { + return nil, fmt.Errorf("otel: build resource: %w", err) + } + + exporter, err := otlpmetrichttp.New(ctx) + if err != nil { + return nil, fmt.Errorf("otel: new metric exporter: %w", err) + } + + mp := metric.NewMeterProvider( + metric.WithReader(metric.NewPeriodicReader(exporter)), + metric.WithResource(res), + ) + otel.SetMeterProvider(mp) + + return mp.Shutdown, nil +} diff --git a/go/internal/otel/traceparent.go b/go/internal/otel/traceparent.go new file mode 100644 index 000000000..e1b69aac5 --- /dev/null +++ b/go/internal/otel/traceparent.go @@ -0,0 +1,46 @@ +package otel + +import ( + "context" + + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/trace" +) + +// spanContextIsValid reports whether ctx carries a valid (parseable, +// non-zero-id) remote span context — the signal that a traceparent parsed. +func spanContextIsValid(ctx context.Context) bool { + return trace.SpanContextFromContext(ctx).IsValid() +} + +// traceparentKey is the single W3C trace-context carrier key. +const traceparentKey = "traceparent" + +// tcPropagator is the W3C TraceContext propagator backing Traceparent and +// ContextWithTraceparent. It is the same propagator installed globally in the +// enabled Setup* path, so serialization is identical in and out of process. +var tcPropagator = propagation.TraceContext{} + +// Traceparent serializes ctx's active span context to the W3C string +// ("00-<32hex>-<16hex>-<2hex>"), or "" when there is no valid active span. +func Traceparent(ctx context.Context) string { + carrier := propagation.MapCarrier{} + tcPropagator.Inject(ctx, carrier) + return carrier.Get(traceparentKey) +} + +// ContextWithTraceparent returns ctx carrying the remote span context parsed +// from tp; malformed or empty tp returns ctx unchanged. +func ContextWithTraceparent(ctx context.Context, tp string) context.Context { + if tp == "" { + return ctx + } + out := tcPropagator.Extract(ctx, propagation.MapCarrier{traceparentKey: tp}) + // A malformed traceparent yields no valid remote span context, so the + // extracted context carries no span — indistinguishable from the input for + // callers, but returning ctx keeps the "unchanged" contract explicit. + if !spanContextIsValid(out) { + return ctx + } + return out +} diff --git a/guest-image/default.nix b/guest-image/default.nix index f07a4148f..9b061fef4 100644 --- a/guest-image/default.nix +++ b/guest-image/default.nix @@ -96,7 +96,7 @@ let }; subPackages = [ "cmd/compass-guestd" ]; proxyVendor = true; - vendorHash = "sha256-LmCUIADU45KTiLTuAvq57ZdFzdUqrcGctopPtT/u/yk="; + vendorHash = "sha256-di6nYpDDZblu6TPpendW88l5LMRZNzSE2t2tiWfms9c="; env.CGO_ENABLED = 0; ldflags = [ "-s"