reva-bump-2.42.2

This commit is contained in:
Viktor Scharf
2026-02-05 12:29:10 +01:00
committed by Ralf Haferkamp
parent f21207ed96
commit 2bf70a6f70
102 changed files with 20907 additions and 1540 deletions
+17 -17
View File
@@ -35,14 +35,14 @@ require (
github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry v1.2.0
github.com/go-playground/validator/v10 v10.30.1
github.com/go-resty/resty/v2 v2.17.1
github.com/golang-jwt/jwt/v5 v5.3.0
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/golang/protobuf v1.5.4
github.com/google/go-cmp v0.7.0
github.com/google/go-tika v0.3.1
github.com/google/uuid v1.6.0
github.com/gookit/config/v2 v2.2.7
github.com/gorilla/mux v1.8.1
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.6
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7
github.com/invopop/validation v0.8.0
github.com/jellydator/ttlcache/v2 v2.11.1
github.com/jellydator/ttlcache/v3 v3.4.0
@@ -55,17 +55,17 @@ require (
github.com/mitchellh/mapstructure v1.5.0
github.com/mna/pigeon v1.3.0
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
github.com/nats-io/nats-server/v2 v2.12.3
github.com/nats-io/nats-server/v2 v2.12.4
github.com/nats-io/nats.go v1.48.0
github.com/oklog/run v1.2.0
github.com/olekukonko/tablewriter v1.1.3
github.com/onsi/ginkgo v1.16.5
github.com/onsi/ginkgo/v2 v2.27.5
github.com/onsi/gomega v1.39.0
github.com/onsi/ginkgo/v2 v2.28.0
github.com/onsi/gomega v1.39.1
github.com/open-policy-agent/opa v1.12.3
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76
github.com/opencloud-eu/reva/v2 v2.42.1
github.com/opencloud-eu/reva/v2 v2.42.2
github.com/opensearch-project/opensearch-go/v4 v4.6.0
github.com/orcaman/concurrent-map v1.0.0
github.com/pkg/errors v0.9.1
@@ -98,11 +98,11 @@ require (
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0
go.opentelemetry.io/contrib/zpages v0.64.0
go.opentelemetry.io/otel v1.39.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0
go.opentelemetry.io/otel v1.40.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0
go.opentelemetry.io/otel/sdk v1.39.0
go.opentelemetry.io/otel/trace v1.39.0
go.opentelemetry.io/otel/sdk v1.40.0
go.opentelemetry.io/otel/trace v1.40.0
golang.org/x/crypto v0.47.0
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac
golang.org/x/image v0.35.0
@@ -241,8 +241,8 @@ require (
github.com/golang/snappy v0.0.4 // indirect
github.com/gomodule/redigo v1.9.3 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/go-tpm v0.9.7 // indirect
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
github.com/google/go-tpm v0.9.8 // indirect
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect
github.com/google/renameio/v2 v2.0.1 // indirect
github.com/gookit/goutil v0.7.1 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
@@ -261,7 +261,7 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/juliangruber/go-intersect v1.1.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/klauspost/compress v1.18.3 // indirect
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
github.com/klauspost/crc32 v1.3.0 // indirect
github.com/kovidgoyal/go-parallel v1.1.1 // indirect
@@ -384,17 +384,17 @@ require (
go.etcd.io/etcd/client/v3 v3.6.7 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect
go.opentelemetry.io/otel/metric v1.39.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect
go.opentelemetry.io/otel/metric v1.40.0 // indirect
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/mod v0.31.0 // indirect
golang.org/x/mod v0.32.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.40.0 // indirect
golang.org/x/tools v0.41.0 // indirect
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
+36 -36
View File
@@ -504,8 +504,8 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -575,8 +575,8 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/go-tika v0.3.1 h1:l+jr10hDhZjcgxFRfcQChRLo1bPXQeLFluMyvDhXTTA=
github.com/google/go-tika v0.3.1/go.mod h1:DJh5N8qxXIl85QkqmXknd+PeeRkUOTbvwyYf7ieDz6c=
github.com/google/go-tpm v0.9.7 h1:u89J4tUUeDTlH8xxC3CTW7OHZjbjKoHdQ9W7gCUhtxA=
github.com/google/go-tpm v0.9.7/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -587,8 +587,8 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/renameio/v2 v2.0.1 h1:HyOM6qd9gF9sf15AvhbptGHUnaLTpEI9akAFFU3VyW0=
github.com/google/renameio/v2 v2.0.1/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4=
@@ -624,8 +624,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vb
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.6 h1:1ufTZkFXIQQ9EmgPjcIPIi2krfxG03lQ8OLoY1MJ3UM=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.6/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
@@ -730,8 +730,8 @@ github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
@@ -916,8 +916,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
github.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g=
github.com/nats-io/jwt/v2 v2.8.0/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA=
github.com/nats-io/nats-server/v2 v2.12.3 h1:KRv+1n7lddMVgkJPQer+pt36TcO0ENxjilBmeWdjcHs=
github.com/nats-io/nats-server/v2 v2.12.3/go.mod h1:MQXjG9WjyXKz9koWzUc3jYUMKD8x3CLmTNy91IQQz3Y=
github.com/nats-io/nats-server/v2 v2.12.4 h1:ZnT10v2LU2Xcoiy8ek9X6Se4YG8EuMfIfvAEuFVx1Ts=
github.com/nats-io/nats-server/v2 v2.12.4/go.mod h1:5MCp/pqm5SEfsvVZ31ll1088ZTwEUdvRX1Hmh/mTTDg=
github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U=
github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
@@ -952,13 +952,13 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.27.5 h1:ZeVgZMx2PDMdJm/+w5fE/OyG6ILo1Y3e+QX4zSR0zTE=
github.com/onsi/ginkgo/v2 v2.27.5/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/ginkgo/v2 v2.28.0 h1:Rrf+lVLmtlBIKv6KrIGJCjyY8N36vDVcutbGJkyqjJc=
github.com/onsi/ginkgo/v2 v2.28.0/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q=
github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4=
github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=
github.com/open-policy-agent/opa v1.12.3 h1:qe3m/w52baKC/HJtippw+hYBUKCzuBCPjB+D5P9knfc=
github.com/open-policy-agent/opa v1.12.3/go.mod h1:RnDgm04GA1RjEXJvrsG9uNT/+FyBNmozcPvA2qz60M4=
github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a h1:Sakl76blJAaM6NxylVkgSzktjo2dS504iDotEFJsh3M=
@@ -969,8 +969,8 @@ github.com/opencloud-eu/inotifywaitgo v0.0.0-20251111171128-a390bae3c5e9 h1:dIft
github.com/opencloud-eu/inotifywaitgo v0.0.0-20251111171128-a390bae3c5e9/go.mod h1:JWyDC6H+5oZRdUJUgKuaye+8Ph5hEs6HVzVoPKzWSGI=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76 h1:vD/EdfDUrv4omSFjrinT8Mvf+8D7f9g4vgQ2oiDrVUI=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76/go.mod h1:pzatilMEHZFT3qV7C/X3MqOa3NlRQuYhlRhZTL+hN6Q=
github.com/opencloud-eu/reva/v2 v2.42.1 h1:QUZOLSfAhb7bw+qsVSFMFY644rUz4/NtnOiJ0QQxj2o=
github.com/opencloud-eu/reva/v2 v2.42.1/go.mod h1:pv+w23JG0/qJweZbTzNNev//YEvlUML1L/2iXgKGkkg=
github.com/opencloud-eu/reva/v2 v2.42.2 h1:2v2RXsD9qX3L4vhu3Pl4QEXgx6jANeKz3FSFhZ3oU5E=
github.com/opencloud-eu/reva/v2 v2.42.2/go.mod h1:LrMYMcSrH9nvTywiE1ry0i2w38yGLFKUPYxWjvKulBo=
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.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
@@ -1317,24 +1317,24 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGN
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
go.opentelemetry.io/contrib/zpages v0.64.0 h1:iMybqKVR8AHHxFX4DuEWJ9dY75+9E7+IPwUK3Ll7NxM=
go.opentelemetry.io/contrib/zpages v0.64.0/go.mod h1:DnkiyoQ7Yx/NmmKn10b6M2YBXreUqq0qhFa/kYgSZME=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@@ -1420,8 +1420,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1680,8 +1680,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/tools/godoc v0.1.0-deprecated h1:o+aZ1BOj6Hsx/GBdJO/s815sqftjSnrZZwyYTHODvtk=
golang.org/x/tools/godoc v0.1.0-deprecated/go.mod h1:qM63CriJ961IHWmnWa9CjZnBndniPt4a3CK0PVB9bIg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+1 -1
View File
@@ -34,7 +34,7 @@ var (
// LatestTag is the latest released version plus the dev meta version.
// Will be overwritten by the release pipeline
// Needs a manual change for every tagged release
LatestTag = "5.0.1+dev"
LatestTag = "5.0.2+dev"
// Date indicates the build date.
// This has been removed, it looks like you can only replace static strings with recent go versions
+6 -5
View File
@@ -140,11 +140,12 @@ A common use case would be integrating with different 3rd party signature
providers, like key management services from various cloud providers or Hardware
Security Modules (HSMs) or to implement additional standards.
| Extension | Purpose | Repo |
| --------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
| GCP | Integrates with multiple Google Cloud Platform signing tools (AppEngine, IAM API, Cloud KMS) | https://github.com/someone1/gcp-jwt-go |
| AWS | Integrates with AWS Key Management Service, KMS | https://github.com/matelang/jwt-go-aws-kms |
| JWKS | Provides support for JWKS ([RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517)) as a `jwt.Keyfunc` | https://github.com/MicahParks/keyfunc |
| Extension | Purpose | Repo |
| --------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
| GCP | Integrates with multiple Google Cloud Platform signing tools (AppEngine, IAM API, Cloud KMS) | https://github.com/someone1/gcp-jwt-go |
| AWS | Integrates with AWS Key Management Service, KMS | https://github.com/matelang/jwt-go-aws-kms |
| JWKS | Provides support for JWKS ([RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517)) as a `jwt.Keyfunc` | https://github.com/MicahParks/keyfunc |
| TPM | Integrates with Trusted Platform Module (TPM) | https://github.com/salrashid123/golang-jwt-tpm |
*Disclaimer*: Unless otherwise specified, these integrations are maintained by
third parties and should not be considered as a primary offer by any of the
+1 -1
View File
@@ -97,7 +97,7 @@ Backwards compatible API change that was missed in 2.0.0.
There were two major reasons for breaking backwards compatibility with this update. The first was a refactor required to expand the width of the RSA and HMAC-SHA signing implementations. There will likely be no required code changes to support this change.
The second update, while unfortunately requiring a small change in integration, is required to open up this library to other signing methods. Not all keys used for all signing methods have a single standard on-disk representation. Requiring `[]byte` as the type for all keys proved too limiting. Additionally, this implementation allows for pre-parsed tokens to be reused, which might matter in an application that parses a high volume of tokens with a small set of keys. Backwards compatibilty has been maintained for passing `[]byte` to the RSA signing methods, but they will also accept `*rsa.PublicKey` and `*rsa.PrivateKey`.
The second update, while unfortunately requiring a small change in integration, is required to open up this library to other signing methods. Not all keys used for all signing methods have a single standard on-disk representation. Requiring `[]byte` as the type for all keys proved too limiting. Additionally, this implementation allows for pre-parsed tokens to be reused, which might matter in an application that parses a high volume of tokens with a small set of keys. Backwards compatibility has been maintained for passing `[]byte` to the RSA signing methods, but they will also accept `*rsa.PublicKey` and `*rsa.PrivateKey`.
It is likely the only integration change required here will be to change `func(t *jwt.Token) ([]byte, error)` to `func(t *jwt.Token) (interface{}, error)` when calling `Parse`.
+13 -11
View File
@@ -76,13 +76,6 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
}
}
// Decode signature
token.Signature, err = p.DecodeSegment(parts[2])
if err != nil {
return token, newError("could not base64 decode signature", ErrTokenMalformed, err)
}
text := strings.Join(parts[0:2], ".")
// Lookup key(s)
if keyFunc == nil {
// keyFunc was not provided. short circuiting validation
@@ -94,11 +87,14 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
return token, newError("error while executing keyfunc", ErrTokenUnverifiable, err)
}
// Join together header and claims in order to verify them with the signature
text := strings.Join(parts[0:2], ".")
switch have := got.(type) {
case VerificationKeySet:
if len(have.Keys) == 0 {
return token, newError("keyfunc returned empty verification key set", ErrTokenUnverifiable)
}
// Iterate through keys and verify signature, skipping the rest when a match is found.
// Return the last error if no match is found.
for _, key := range have.Keys {
@@ -131,7 +127,7 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
return token, nil
}
// ParseUnverified parses the token but doesn't validate the signature.
// ParseUnverified parses the token but does not validate the signature.
//
// WARNING: Don't use this method unless you know what you're doing.
//
@@ -146,7 +142,7 @@ func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Toke
token = &Token{Raw: tokenString}
// parse Header
// Parse Header
var headerBytes []byte
if headerBytes, err = p.DecodeSegment(parts[0]); err != nil {
return token, parts, newError("could not base64 decode header", ErrTokenMalformed, err)
@@ -155,7 +151,7 @@ func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Toke
return token, parts, newError("could not JSON decode header", ErrTokenMalformed, err)
}
// parse Claims
// Parse Claims
token.Claims = claims
claimBytes, err := p.DecodeSegment(parts[1])
@@ -196,6 +192,12 @@ func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Toke
return token, parts, newError("signing method (alg) is unspecified", ErrTokenUnverifiable)
}
// Parse token signature
token.Signature, err = p.DecodeSegment(parts[2])
if err != nil {
return token, parts, newError("could not base64 decode signature", ErrTokenMalformed, err)
}
return token, parts, nil
}
@@ -216,7 +218,7 @@ func splitToken(token string) ([]string, bool) {
parts[1] = claims
// One more cut to ensure the signature is the last part of the token and there are no more
// delimiters. This avoids an issue where malicious input could contain additional delimiters
// causing unecessary overhead parsing tokens.
// causing unnecessary overhead parsing tokens.
signature, _, unexpected := strings.Cut(remain, tokenDelimiter)
if unexpected {
return nil, false
+9 -3
View File
@@ -3,9 +3,7 @@ package jwt
import "time"
// ParserOption is used to implement functional-style options that modify the
// behavior of the parser. To add new options, just create a function (ideally
// beginning with With or Without) that returns an anonymous function that takes
// a *Parser type as input and manipulates its configuration accordingly.
// behavior of the parser.
type ParserOption func(*Parser)
// WithValidMethods is an option to supply algorithm methods that the parser
@@ -66,6 +64,14 @@ func WithExpirationRequired() ParserOption {
}
}
// WithNotBeforeRequired returns the ParserOption to make nbf claim required.
// By default nbf claim is optional.
func WithNotBeforeRequired() ParserOption {
return func(p *Parser) {
p.validator.requireNbf = true
}
}
// WithAudience configures the validator to require any of the specified
// audiences in the `aud` claim. Validation will fail if the audience is not
// listed in the token or the `aud` claim is missing.
+4 -2
View File
@@ -32,8 +32,8 @@ type Token struct {
Method SigningMethod // Method is the signing method used or to be used
Header map[string]any // Header is the first segment of the token in decoded form
Claims Claims // Claims is the second segment of the token in decoded form
Signature []byte // Signature is the third segment of the token in decoded form. Populated when you Parse a token
Valid bool // Valid specifies if the token is valid. Populated when you Parse/Verify a token
Signature []byte // Signature is the third segment of the token in decoded form. Populated when you [Parse] or sign a token
Valid bool // Valid specifies if the token is valid. Populated when you [Parse] a token
}
// New creates a new [Token] with the specified signing method and an empty map
@@ -71,6 +71,8 @@ func (t *Token) SignedString(key any) (string, error) {
return "", err
}
t.Signature = sig
return sstr + "." + t.EncodeSegment(sig), nil
}
+6 -2
View File
@@ -44,6 +44,9 @@ type Validator struct {
// requireExp specifies whether the exp claim is required
requireExp bool
// requireNbf specifies whether the nbf claim is required
requireNbf bool
// verifyIat specifies whether the iat (Issued At) claim will be verified.
// According to https://www.rfc-editor.org/rfc/rfc7519#section-4.1.6 this
// only specifies the age of the token, but no validation check is
@@ -111,8 +114,9 @@ func (v *Validator) Validate(claims Claims) error {
}
// We always need to check not-before, but usage of the claim itself is
// OPTIONAL.
if err = v.verifyNotBefore(claims, now, false); err != nil {
// OPTIONAL by default. requireNbf overrides this behavior and makes
// the nbf claim mandatory.
if err = v.verifyNotBefore(claims, now, v.requireNbf); err != nil {
errs = append(errs, err)
}
+5 -6
View File
@@ -17,6 +17,7 @@ package profile
import (
"encoding/binary"
"fmt"
"slices"
"sort"
"strconv"
"strings"
@@ -78,12 +79,10 @@ func Merge(srcs []*Profile) (*Profile, error) {
}
}
for _, s := range p.Sample {
if isZeroSample(s) {
// If there are any zero samples, re-merge the profile to GC
// them.
return Merge([]*Profile{p})
}
if slices.ContainsFunc(p.Sample, isZeroSample) {
// If there are any zero samples, re-merge the profile to GC
// them.
return Merge([]*Profile{p})
}
return p, nil
+14 -8
View File
@@ -24,6 +24,7 @@ import (
"math"
"path/filepath"
"regexp"
"slices"
"sort"
"strings"
"sync"
@@ -277,7 +278,7 @@ func (p *Profile) massageMappings() {
// Use heuristics to identify main binary and move it to the top of the list of mappings
for i, m := range p.Mapping {
file := strings.TrimSpace(strings.Replace(m.File, "(deleted)", "", -1))
file := strings.TrimSpace(strings.ReplaceAll(m.File, "(deleted)", ""))
if len(file) == 0 {
continue
}
@@ -734,12 +735,7 @@ func (p *Profile) RemoveLabel(key string) {
// HasLabel returns true if a sample has a label with indicated key and value.
func (s *Sample) HasLabel(key, value string) bool {
for _, v := range s.Label[key] {
if v == value {
return true
}
}
return false
return slices.Contains(s.Label[key], value)
}
// SetNumLabel sets the specified key to the specified value for all samples in the
@@ -852,7 +848,17 @@ func (p *Profile) HasFileLines() bool {
// "[vdso]", "[vsyscall]" and some others, see the code.
func (m *Mapping) Unsymbolizable() bool {
name := filepath.Base(m.File)
return strings.HasPrefix(name, "[") || strings.HasPrefix(name, "linux-vdso") || strings.HasPrefix(m.File, "/dev/dri/") || m.File == "//anon"
switch {
case strings.HasPrefix(name, "["):
case strings.HasPrefix(name, "linux-vdso"):
case strings.HasPrefix(m.File, "/dev/dri/"):
case m.File == "//anon":
case m.File == "":
case strings.HasPrefix(m.File, "/memfd:"):
default:
return false
}
return true
}
// Copy makes a fully independent copy of a profile.
+18 -1
View File
@@ -36,6 +36,7 @@ package profile
import (
"errors"
"fmt"
"slices"
)
type buffer struct {
@@ -187,6 +188,16 @@ func le32(p []byte) uint32 {
return uint32(p[0]) | uint32(p[1])<<8 | uint32(p[2])<<16 | uint32(p[3])<<24
}
func peekNumVarints(data []byte) (numVarints int) {
for ; len(data) > 0; numVarints++ {
var err error
if _, data, err = decodeVarint(data); err != nil {
break
}
}
return numVarints
}
func decodeVarint(data []byte) (uint64, []byte, error) {
var u uint64
for i := 0; ; i++ {
@@ -286,6 +297,9 @@ func decodeInt64(b *buffer, x *int64) error {
func decodeInt64s(b *buffer, x *[]int64) error {
if b.typ == 2 {
// Packed encoding
dataLen := peekNumVarints(b.data)
*x = slices.Grow(*x, dataLen)
data := b.data
for len(data) > 0 {
var u uint64
@@ -316,8 +330,11 @@ func decodeUint64(b *buffer, x *uint64) error {
func decodeUint64s(b *buffer, x *[]uint64) error {
if b.typ == 2 {
data := b.data
// Packed encoding
dataLen := peekNumVarints(b.data)
*x = slices.Grow(*x, dataLen)
data := b.data
for len(data) > 0 {
var u uint64
var err error
+2 -7
View File
@@ -19,6 +19,7 @@ package profile
import (
"fmt"
"regexp"
"slices"
"strings"
)
@@ -40,13 +41,7 @@ func simplifyFunc(f string) string {
// Account for unsimplified names -- try to remove the argument list by trimming
// starting from the first '(', but skipping reserved names that have '('.
for _, ind := range bracketRx.FindAllStringSubmatchIndex(funcName, -1) {
foundReserved := false
for _, res := range reservedNames {
if funcName[ind[0]:ind[1]] == res {
foundReserved = true
break
}
}
foundReserved := slices.Contains(reservedNames, funcName[ind[0]:ind[1]])
if !foundReserved {
funcName = funcName[:ind[0]]
break
+46 -15
View File
@@ -60,6 +60,9 @@ type parser struct {
// pedantic reports error when configuration is not correct.
pedantic bool
// Tracks environment variable references, to avoid cycles
envVarReferences map[string]bool
}
// Parse will return a map of keys to any, although concrete types
@@ -180,16 +183,37 @@ func (t *token) Position() int {
return t.item.pos
}
func parse(data, fp string, pedantic bool) (p *parser, err error) {
p = &parser{
mapping: make(map[string]any),
lx: lex(data),
ctxs: make([]any, 0, 4),
keys: make([]string, 0, 4),
ikeys: make([]item, 0, 4),
fp: filepath.Dir(fp),
pedantic: pedantic,
func newParser(data, fp string, pedantic bool) *parser {
return &parser{
mapping: make(map[string]any),
lx: lex(data),
ctxs: make([]any, 0, 4),
keys: make([]string, 0, 4),
ikeys: make([]item, 0, 4),
fp: filepath.Dir(fp),
pedantic: pedantic,
envVarReferences: make(map[string]bool),
}
}
func parse(data, fp string, pedantic bool) (*parser, error) {
p := newParser(data, fp, pedantic)
if err := p.parse(fp); err != nil {
return nil, err
}
return p, nil
}
func parseEnv(data string, parent *parser) (*parser, error) {
p := newParser(data, "", false)
p.envVarReferences = parent.envVarReferences
if err := p.parse(""); err != nil {
return nil, err
}
return p, nil
}
func (p *parser) parse(fp string) error {
p.pushContext(p.mapping)
var prevItem item
@@ -199,16 +223,16 @@ func parse(data, fp string, pedantic bool) (p *parser, err error) {
// Here we allow the final character to be a bracket '}'
// in order to support JSON like configurations.
if prevItem.typ == itemKey && prevItem.val != mapEndString {
return nil, fmt.Errorf("config is invalid (%s:%d:%d)", fp, it.line, it.pos)
return fmt.Errorf("config is invalid (%s:%d:%d)", fp, it.line, it.pos)
}
break
}
prevItem = it
if err := p.processItem(it, fp); err != nil {
return nil, err
return err
}
}
return p, nil
return nil
}
func (p *parser) next() item {
@@ -453,11 +477,18 @@ func (p *parser) lookupVariable(varReference string) (any, bool, error) {
}
// If we are here, we have exhausted our context maps and still not found anything.
// Parse from the environment.
// Detect reference cycles
if p.envVarReferences[varReference] {
return nil, false, fmt.Errorf("variable reference cycle for '%s'", varReference)
}
p.envVarReferences[varReference] = true
defer delete(p.envVarReferences, varReference)
// Parse from the environment
if vStr, ok := os.LookupEnv(varReference); ok {
// Everything we get here will be a string value, so we need to process as a parser would.
if vmap, err := Parse(fmt.Sprintf("%s=%s", pkey, vStr)); err == nil {
v, ok := vmap[pkey]
if subp, err := parseEnv(fmt.Sprintf("%s=%s", pkey, vStr), p); err == nil {
v, ok := subp.mapping[pkey]
return v, ok, nil
} else {
return nil, false, err
+1
View File
@@ -299,6 +299,7 @@ func (a *Account) shallowCopy(na *Account) {
na.Nkey = a.Nkey
na.Issuer = a.Issuer
na.traceDest, na.traceDestSampling = a.traceDest, a.traceDestSampling
na.nrgAccount = a.nrgAccount
if a.imports.streams != nil {
na.imports.streams = make([]*streamImport, 0, len(a.imports.streams))
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2023-2024 The NATS Authors
// Copyright 2023-2025 The NATS 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
+54 -9
View File
@@ -1,4 +1,4 @@
// Copyright 2012-2025 The NATS Authors
// Copyright 2012-2026 The NATS 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
@@ -23,6 +23,7 @@ import (
"errors"
"fmt"
"io"
"math"
"math/rand"
"net"
"net/http"
@@ -35,8 +36,6 @@ import (
"sync/atomic"
"time"
"slices"
"github.com/klauspost/compress/s2"
"github.com/nats-io/jwt/v2"
"github.com/nats-io/nats-server/v2/internal/fastrand"
@@ -2732,9 +2731,12 @@ func (c *client) updateS2AutoCompressionLevel(co *CompressionOpts, compression *
}
// Will return the parts from the raw wire msg.
// We return the `hdr` as a slice that is capped to the length of the headers
// so that if the caller later tries to append to the returned header slice it
// does not affect the message content.
func (c *client) msgParts(data []byte) (hdr []byte, msg []byte) {
if c != nil && c.pa.hdr > 0 {
return data[:c.pa.hdr], data[c.pa.hdr:]
return data[:c.pa.hdr:c.pa.hdr], data[c.pa.hdr:]
}
return nil, data
}
@@ -3337,7 +3339,7 @@ func (c *client) unsubscribe(acc *Account, sub *subscription, force, remove bool
sub.shadow = nil
if len(shadowSubs) > 0 {
isSpokeLeaf = c.isSpokeLeafNode()
updateRoute = !isSpokeLeaf && (c.kind == CLIENT || c.kind == SYSTEM || c.kind == LEAF) && c.srv != nil
updateRoute = !isSpokeLeaf && (c.kind == CLIENT || c.kind == SYSTEM || c.kind == LEAF || c.kind == JETSTREAM) && c.srv != nil
}
sub.close()
c.mu.Unlock()
@@ -4565,6 +4567,19 @@ func getHeaderKeyIndex(key string, hdr []byte) int {
}
}
// setHeader will replace the value of the first existing key `key`
// with the given value `val`, or add this new key at the end of
// the headers.
//
// Note: If the key does not exist, or if it exists but the new value
// would make the resulting byte slice larger than the original one,
// a new byte slice is returned and the original is left untouched.
// This is to prevent situations where caller may have a `hdr` and
// `msg` that are the parts of an underlying buffer. Extending the
// `hdr` would otherwise overwrite the `msg` part.
//
// If the new value is smaller, then the original `hdr` byte slice
// is modified.
func setHeader(key, val string, hdr []byte) []byte {
start := getHeaderKeyIndex(key, hdr)
if start >= 0 {
@@ -4579,15 +4594,45 @@ func setHeader(key, val string, hdr []byte) []byte {
return hdr // malformed headers
}
valEnd += valStart
suffix := slices.Clone(hdr[valEnd:])
newHdr := append(hdr[:valStart], val...)
return append(newHdr, suffix...)
// Length of the existing value (before the `\r`)
oldValLen := valEnd - valStart
// This is how many extra bytes we need for the new value.
// If <= 0, it means that we need less and so will reuse the `hdr` buffer.
if extra := len(val) - oldValLen; extra > 0 {
// Check that we don't overflow an "int".
if rem := math.MaxInt - hdrLen; rem < extra {
// We don't grow, and return the existing header.
return hdr
}
// The new size is the old size plus the extra bytes.
newHdrSize := hdrLen + extra
newHdr := make([]byte, newHdrSize)
// Copy the parts from `hdr` and `val` into the new buffer.
n := copy(newHdr, hdr[:valStart])
n += copy(newHdr[n:], val)
copy(newHdr[n:], hdr[valEnd:])
return newHdr
}
// We can write in place since it fits in the existing `hdr` buffer.
n := copy(hdr[valStart:], val)
n += copy(hdr[valStart+n:], hdr[valEnd:])
hdr = hdr[:valStart+n]
return hdr
}
if len(hdr) > 0 && bytes.HasSuffix(hdr, []byte("\r\n")) {
hdr = hdr[:len(hdr)-2]
val += "\r\n"
}
return fmt.Appendf(hdr, "%s: %s\r\n", key, val)
// Create the new buffer based on length of existing one and
// length of the new "<key>: <value>\r\n". Protect against "int" overflow.
newSize := uint64(len(hdr)) + uint64(len(key)) + 1 + 1 + uint64(len(val)) + 2
if newSize > uint64(math.MaxInt) {
// We don't grow, and return the existing header.
return hdr
}
newHdr := make([]byte, 0, int(newSize))
newHdr = append(newHdr, hdr...)
return fmt.Appendf(newHdr, "%s: %s\r\n", key, val)
}
// For bytes.HasPrefix below.
+1 -1
View File
@@ -66,7 +66,7 @@ func init() {
const (
// VERSION is the current version for the server.
VERSION = "2.12.3"
VERSION = "2.12.4"
// PROTO is the currently supported protocol.
// 0 was the original
+49 -27
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2025 The NATS Authors
// Copyright 2019-2026 The NATS 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
@@ -1030,11 +1030,11 @@ func (mset *stream) addConsumerWithAssignment(config *ConsumerConfig, oname stri
}
if cName != _EMPTY_ {
if eo, ok := mset.consumers[cName]; ok {
mset.mu.Unlock()
if action == ActionCreate {
ocfg := eo.config()
copyConsumerMetadata(config, &ocfg)
if !reflect.DeepEqual(config, &ocfg) {
mset.mu.Unlock()
return nil, NewJSConsumerAlreadyExistsError()
}
}
@@ -1042,9 +1042,11 @@ func (mset *stream) addConsumerWithAssignment(config *ConsumerConfig, oname stri
if cfg.Retention == WorkQueuePolicy {
subjects := gatherSubjectFilters(config.FilterSubject, config.FilterSubjects)
if !mset.partitionUnique(cName, subjects) {
mset.mu.Unlock()
return nil, NewJSConsumerWQConsumerNotUniqueError()
}
}
mset.mu.Unlock()
err := eo.updateConfig(config)
if err == nil {
return eo, nil
@@ -1542,7 +1544,6 @@ func (o *consumer) setLeader(isLeader bool) {
if o.cfg.AckPolicy != AckNone {
if o.ackSub, err = o.subscribeInternal(o.ackSubj, o.pushAck); err != nil {
o.mu.Unlock()
o.deleteWithoutAdvisory()
return
}
}
@@ -1551,7 +1552,6 @@ func (o *consumer) setLeader(isLeader bool) {
// Will error if wrong mode to provide feedback to users.
if o.reqSub, err = o.subscribeInternal(o.nextMsgSubj, o.processNextMsgReq); err != nil {
o.mu.Unlock()
o.deleteWithoutAdvisory()
return
}
@@ -1561,7 +1561,6 @@ func (o *consumer) setLeader(isLeader bool) {
fcsubj := fmt.Sprintf(jsFlowControl, stream, o.name)
if o.fcSub, err = o.subscribeInternal(fcsubj, o.processFlowControl); err != nil {
o.mu.Unlock()
o.deleteWithoutAdvisory()
return
}
}
@@ -2401,7 +2400,8 @@ func (o *consumer) updateConfig(cfg *ConsumerConfig) error {
// Check for Subject Filters update.
newSubjects := gatherSubjectFilters(cfg.FilterSubject, cfg.FilterSubjects)
if !subjectSliceEqual(newSubjects, o.subjf.subjects()) {
updatedFilters := !subjectSliceEqual(newSubjects, o.subjf.subjects())
if updatedFilters {
newSubjf := make(subjectFilters, 0, len(newSubjects))
for _, newFilter := range newSubjects {
fs := &subjectFilter{
@@ -2440,15 +2440,17 @@ func (o *consumer) updateConfig(cfg *ConsumerConfig) error {
// Allowed but considered no-op, [Description, SampleFrequency, MaxWaiting, HeadersOnly]
o.cfg = *cfg
// Cleanup messages that lost interest.
if o.retention == InterestPolicy {
o.mu.Unlock()
o.cleanupNoInterestMessages(o.mset, false)
o.mu.Lock()
}
if updatedFilters {
// Cleanup messages that lost interest.
if o.retention == InterestPolicy {
o.mu.Unlock()
o.cleanupNoInterestMessages(o.mset, false)
o.mu.Lock()
}
// Re-calculate num pending on update.
o.streamNumPending()
// Re-calculate num pending on update.
o.streamNumPending()
}
return nil
}
@@ -5115,9 +5117,14 @@ func (o *consumer) checkNumPending() (uint64, error) {
var state StreamState
o.mset.store.FastState(&state)
npc := o.numPending()
if o.sseq > state.LastSeq && npc > 0 || npc > state.Msgs {
// Re-calculate.
return o.streamNumPending()
// Make sure we can't report more messages than there are.
// TODO(nat): It's not great that this means consumer info has side effects,
// since we can't know whether anyone will call it or not. The previous num
// pending calculation that this replaces had the same problem though.
if o.sseq > state.LastSeq {
o.npc = 0
} else if npc > 0 {
o.npc = int64(min(npc, state.Msgs, state.LastSeq-o.sseq+1))
}
}
return o.numPending(), nil
@@ -5365,6 +5372,15 @@ func (o *consumer) trackPending(sseq, dseq uint64) {
o.pending = make(map[uint64]*Pending)
}
now := time.Now()
if p, ok := o.pending[sseq]; ok {
// Update timestamp but keep original consumer delivery sequence.
// So do not update p.Sequence.
p.Timestamp = now.UnixNano()
} else {
o.pending[sseq] = &Pending{dseq, now.UnixNano()}
}
// We could have a backoff that set a timer higher than what we need for this message.
// In that case, reset to lowest backoff required for a message redelivery.
minDelay := o.ackWait(0)
@@ -5377,18 +5393,10 @@ func (o *consumer) trackPending(sseq, dseq uint64) {
}
minDelay = o.ackWait(o.cfg.BackOff[bi])
}
minDeadline := time.Now().Add(minDelay)
minDeadline := now.Add(minDelay)
if o.ptmr == nil || o.ptmrEnd.After(minDeadline) {
o.resetPtmr(minDelay)
}
if p, ok := o.pending[sseq]; ok {
// Update timestamp but keep original consumer delivery sequence.
// So do not update p.Sequence.
p.Timestamp = time.Now().UnixNano()
} else {
o.pending[sseq] = &Pending{dseq, time.Now().UnixNano()}
}
}
// Credit back a failed delivery.
@@ -6503,6 +6511,10 @@ func (o *consumer) checkStateForInterestStream(ss *StreamState) error {
retryAsflr = seq
}
} else if seq <= dflr {
// Store the first entry above our ack floor, so we don't need to look it up again on retryAsflr=0.
if retryAsflr == 0 {
retryAsflr = seq
}
// If we have pending, we will need to walk through to delivered in case we missed any of those acks as well.
if _, ok := state.Pending[seq]; !ok {
// The filters are already taken into account,
@@ -6514,8 +6526,18 @@ func (o *consumer) checkStateForInterestStream(ss *StreamState) error {
}
}
// If retry floor was not overwritten, set to ack floor+1, we don't need to account for any retries below it.
// However, our ack floor may be lower than the next message we can receive, so we correct it upward if needed.
if retryAsflr == 0 {
retryAsflr = asflr + 1
if filters != nil {
_, nseq, err = store.LoadNextMsgMulti(filters, asflr+1, &smv)
} else {
_, nseq, err = store.LoadNextMsg(filter, wc, asflr+1, &smv)
}
if err == nil {
retryAsflr = max(asflr+1, nseq)
} else if err == ErrStoreEOF {
retryAsflr = ss.LastSeq + 1
}
}
o.mu.Lock()
+268 -103
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2025 The NATS Authors
// Copyright 2019-2026 The NATS 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
@@ -201,7 +201,7 @@ type fileStore struct {
sips int
dirty int
closing bool
closed bool
closed atomic.Bool // Atomic to reduce contention on ConsumerStores.
fip bool
receivedAny bool
firstMoved bool
@@ -473,10 +473,18 @@ func newFileStoreWithCreated(fcfg FileStoreConfig, cfg StreamConfig, created tim
}
keyFile := filepath.Join(fs.fcfg.StoreDir, JetStreamMetaFileKey)
_, err = os.Stat(keyFile)
// Either the file should exist (err=nil), or it shouldn't. Any other error is reported.
if err != nil && !os.IsNotExist(err) {
return nil, err
}
// Make sure we do not have an encrypted store underneath of us but no main key.
if fs.prf == nil {
if _, err := os.Stat(keyFile); err == nil {
return nil, errNoMainKey
if fs.prf == nil && err == nil {
return nil, errNoMainKey
} else if fs.prf != nil && err == nil {
// If encryption is configured and the key file exists, recover our keys.
if err = fs.recoverAEK(); err != nil {
return nil, err
}
}
@@ -1784,16 +1792,14 @@ func (fs *fileStore) recoverFullState() (rerr error) {
}
// Decrypt if needed.
if fs.prf != nil {
// We can be setup for encryption but if this is a snapshot restore we will be missing the keyfile
// since snapshots strip encryption.
if err := fs.recoverAEK(); err == nil {
ns := fs.aek.NonceSize()
buf, err = fs.aek.Open(nil, buf[:ns], buf[ns:], nil)
if err != nil {
fs.warn("Stream state error reading encryption key: %v", err)
return err
}
// We can be setup for encryption but if this is a snapshot restore we will be missing the keyfile
// since snapshots strip encryption.
if fs.prf != nil && fs.aek != nil {
ns := fs.aek.NonceSize()
buf, err = fs.aek.Open(nil, buf[:ns], buf[ns:], nil)
if err != nil {
fs.warn("Stream state error reading encryption key: %v", err)
return err
}
}
@@ -2346,7 +2352,7 @@ func (fs *fileStore) recoverMsgs() error {
if fs.ld != nil {
var emptyBlks []*msgBlock
for _, mb := range fs.blks {
if mb.msgs == 0 && mb.rbytes == 0 {
if mb.msgs == 0 && mb.rbytes == 0 && mb != fs.lmb {
emptyBlks = append(emptyBlks, mb)
}
}
@@ -2588,7 +2594,7 @@ func copyMsgBlocks(src []*msgBlock) []*msgBlock {
func (fs *fileStore) GetSeqFromTime(t time.Time) uint64 {
fs.mu.RLock()
lastSeq := fs.state.LastSeq
closed := fs.closed
closed := fs.isClosed()
fs.mu.RUnlock()
if closed {
@@ -2603,20 +2609,51 @@ func (fs *fileStore) GetSeqFromTime(t time.Time) uint64 {
fseq := atomic.LoadUint64(&mb.first.seq)
lseq := atomic.LoadUint64(&mb.last.seq)
var smv StoreMsg
var (
smv StoreMsg
cts int64
cseq uint64
off uint64
)
ts := t.UnixNano()
// Because sort.Search expects range [0,off), we have to manually
// calculate the offset from the first sequence.
off := int(lseq - fseq + 1)
i := sort.Search(off, func(i int) bool {
sm, _, _ := mb.fetchMsgNoCopy(fseq+uint64(i), &smv)
return sm != nil && sm.ts >= ts
})
if i < off {
return fseq + uint64(i)
// Using a binary search, but need to be aware of interior deletes in the block.
seq := lseq + 1
loop:
for fseq <= lseq {
mid := fseq + (lseq-fseq)/2
off = 0
// Potentially skip over gaps. We keep the original middle but keep track of a
// potential delete range with an offset.
for {
sm, _, err := mb.fetchMsgNoCopy(mid+off, &smv)
if err != nil || sm == nil {
off++
if mid+off <= lseq {
continue
} else {
// Continue search to the left. Purposely ignore the skipped deletes here.
lseq = mid - 1
continue loop
}
}
cts = sm.ts
cseq = sm.seq
break
}
if cts >= ts {
seq = cseq
if mid == fseq {
break
}
// Continue search to the left.
lseq = mid - 1
} else {
// Continue search to the right (potentially skipping over interior deletes).
fseq = mid + off + 1
}
}
return 0
return seq
}
// Find the first matching message against a sublist.
@@ -2632,13 +2669,15 @@ func (mb *msgBlock) firstMatchingMulti(sl *gsl.SimpleSublist, start uint64, sm *
mb.mu.Unlock()
}()
// Need messages loaded from here on out.
if mb.cacheNotLoaded() {
if mb.fssNotLoaded() {
// Make sure we have fss loaded.
if err := mb.loadMsgsWithLock(); err != nil {
return nil, false, err
}
didLoad = true
}
// Mark fss activity.
mb.lsts = ats.AccessTime()
// Make sure to start at mb.first.seq if fseq < mb.first.seq
if seq := atomic.LoadUint64(&mb.first.seq); seq > start {
@@ -2646,10 +2685,6 @@ func (mb *msgBlock) firstMatchingMulti(sl *gsl.SimpleSublist, start uint64, sm *
}
lseq := atomic.LoadUint64(&mb.last.seq)
if sm == nil {
sm = new(StoreMsg)
}
// If the FSS state has fewer entries than sequences in the linear scan,
// then use intersection instead as likely going to be cheaper. This will
// often be the case with high numbers of deletes, as well as a smaller
@@ -2657,7 +2692,11 @@ func (mb *msgBlock) firstMatchingMulti(sl *gsl.SimpleSublist, start uint64, sm *
if uint64(mb.fss.Size()) < lseq-start {
// If there are no subject matches then this is effectively no-op.
hseq := uint64(math.MaxUint64)
gsl.IntersectStree(mb.fss, sl, func(subj []byte, ss *SimpleState) {
var ierr error
stree.IntersectGSL(mb.fss, sl, func(subj []byte, ss *SimpleState) {
if ierr != nil {
return
}
if ss.firstNeedsUpdate || ss.lastNeedsUpdate {
// mb is already loaded into the cache so should be fast-ish.
mb.recalculateForSubj(bytesToString(subj), ss)
@@ -2669,6 +2708,16 @@ func (mb *msgBlock) firstMatchingMulti(sl *gsl.SimpleSublist, start uint64, sm *
// than our first seq for this subject.
return
}
// Need messages loaded from here on out.
if mb.cacheNotLoaded() {
if ierr = mb.loadMsgsWithLock(); ierr != nil {
return
}
didLoad = true
}
if sm == nil {
sm = new(StoreMsg)
}
if first == ss.First {
// If the start floor is below where this subject starts then we can
// short-circuit, avoiding needing to scan for the next message.
@@ -2703,10 +2752,24 @@ func (mb *msgBlock) firstMatchingMulti(sl *gsl.SimpleSublist, start uint64, sm *
mb.llseq = llseq
}
})
if ierr != nil {
return nil, false, ierr
}
if hseq < uint64(math.MaxUint64) && sm != nil {
return sm, didLoad && start == lseq, nil
}
} else {
// Need messages loaded from here on out.
if mb.cacheNotLoaded() {
if err := mb.loadMsgsWithLock(); err != nil {
return nil, false, err
}
didLoad = true
}
if sm == nil {
sm = new(StoreMsg)
}
for seq := start; seq <= lseq; seq++ {
if mb.dmap.Exists(seq) {
// Optimisation to avoid calling cacheLookup which hits time.Now().
@@ -2773,6 +2836,10 @@ func (mb *msgBlock) firstMatching(filter string, wc bool, start uint64, sm *Stor
isAll = true
})
}
// If the only subject in this block isn't our filter, can simply short-circuit.
if !isAll {
return nil, didLoad, ErrStoreMsgNotFound
}
}
// Make sure to start at mb.first.seq if fseq < mb.first.seq
fseq = max(fseq, atomic.LoadUint64(&mb.first.seq))
@@ -2909,7 +2976,7 @@ func (mb *msgBlock) prevMatchingMulti(sl *gsl.SimpleSublist, start uint64, sm *S
if uint64(mb.fss.Size()) < start-lseq {
// If there are no subject matches then this is effectively no-op.
hseq := uint64(0)
gsl.IntersectStree(mb.fss, sl, func(subj []byte, ss *SimpleState) {
stree.IntersectGSL(mb.fss, sl, func(subj []byte, ss *SimpleState) {
if ss.firstNeedsUpdate || ss.lastNeedsUpdate {
// mb is already loaded into the cache so should be fast-ish.
mb.recalculateForSubj(bytesToString(subj), ss)
@@ -3188,6 +3255,30 @@ func (fs *fileStore) checkSkipFirstBlock(filter string, wc bool, bi int) (int, e
if start == uint32(math.MaxUint32) {
return -1, ErrStoreEOF
}
return fs.selectSkipFirstBlock(bi, start, stop)
}
// This is used to see if we can selectively jump start blocks based on filter subjects and a starting block index.
// Will return -1 and ErrStoreEOF if no matches at all or no more from where we are.
func (fs *fileStore) checkSkipFirstBlockMulti(sl *gsl.SimpleSublist, bi int) (int, error) {
// Move through psim to gather start and stop bounds.
start, stop := uint32(math.MaxUint32), uint32(0)
stree.IntersectGSL(fs.psim, sl, func(subj []byte, psi *psi) {
if psi.fblk < start {
start = psi.fblk
}
if psi.lblk > stop {
stop = psi.lblk
}
})
// Nothing was found.
if start == uint32(math.MaxUint32) {
return -1, ErrStoreEOF
}
return fs.selectSkipFirstBlock(bi, start, stop)
}
func (fs *fileStore) selectSkipFirstBlock(bi int, start, stop uint32) (int, error) {
// Can not be nil so ok to inline dereference.
mbi := fs.blks[bi].getIndex()
// All matching msgs are behind us.
@@ -4006,7 +4097,7 @@ func (fs *fileStore) NumPendingMulti(sseq uint64, sl *gsl.SimpleSublist, lastPer
mb := fs.blks[seqStart]
bi := mb.index
gsl.IntersectStree(fs.psim, sl, func(subj []byte, psi *psi) {
stree.IntersectGSL(fs.psim, sl, func(subj []byte, psi *psi) {
// If the select blk start is greater than entry's last blk skip.
if bi > psi.lblk {
return
@@ -4105,7 +4196,7 @@ func (fs *fileStore) NumPendingMulti(sseq uint64, sl *gsl.SimpleSublist, lastPer
var t uint64
var havePartial bool
var updateLLTS bool
gsl.IntersectStree[SimpleState](mb.fss, sl, func(bsubj []byte, ss *SimpleState) {
stree.IntersectGSL[SimpleState](mb.fss, sl, func(bsubj []byte, ss *SimpleState) {
subj := bytesToString(bsubj)
if havePartial {
// If we already found a partial then don't do anything else.
@@ -4168,7 +4259,7 @@ func (fs *fileStore) NumPendingMulti(sseq uint64, sl *gsl.SimpleSublist, lastPer
// If we are here it's better to calculate totals from psim and adjust downward by scanning less blocks.
start := uint32(math.MaxUint32)
gsl.IntersectStree(fs.psim, sl, func(subj []byte, psi *psi) {
stree.IntersectGSL(fs.psim, sl, func(subj []byte, psi *psi) {
total += psi.total
// Keep track of start index for this subject.
if psi.fblk < start {
@@ -4219,7 +4310,7 @@ func (fs *fileStore) NumPendingMulti(sseq uint64, sl *gsl.SimpleSublist, lastPer
}
// Mark fss activity.
mb.lsts = ats.AccessTime()
gsl.IntersectStree(mb.fss, sl, func(bsubj []byte, ss *SimpleState) {
stree.IntersectGSL(mb.fss, sl, func(bsubj []byte, ss *SimpleState) {
adjust += ss.Msgs
})
}
@@ -4486,7 +4577,7 @@ func (fs *fileStore) genEncryptionKeysForBlock(mb *msgBlock) error {
// Stores a raw message with expected sequence number and timestamp.
// Lock should be held.
func (fs *fileStore) storeRawMsg(subj string, hdr, msg []byte, seq uint64, ts, ttl int64) (err error) {
if fs.closed {
if fs.isClosed() {
return ErrStoreClosed
}
@@ -5159,7 +5250,7 @@ func (fs *fileStore) removeMsg(seq uint64, secure, viaLimits, needFSLock bool) (
fsLock()
if fs.closed {
if fs.isClosed() {
fsUnlock()
return false, ErrStoreClosed
}
@@ -5369,14 +5460,8 @@ func (fs *fileStore) removeMsg(seq uint64, secure, viaLimits, needFSLock bool) (
// If we have a callback registered we need to release lock regardless since cb might need it to lookup msg, etc.
fs.mu.Unlock()
// Storage updates.
if cb != nil {
var subj string
if sm != nil {
subj = sm.subj
}
delta := int64(msz)
cb(-1, -delta, seq, subj)
}
delta := int64(msz)
cb(-1, -delta, seq, sm.subj)
if !needFSLock {
fs.mu.Lock()
@@ -5611,10 +5696,7 @@ func (mb *msgBlock) slotInfo(slot int) (uint32, uint32, bool, error) {
}
func (fs *fileStore) isClosed() bool {
fs.mu.RLock()
closed := fs.closed
fs.mu.RUnlock()
return closed
return fs.closed.Load()
}
// Will spin up our flush loop.
@@ -6995,11 +7077,10 @@ func (mb *msgBlock) ensureRawBytesLoaded() error {
// Sync msg and index files as needed. This is called from a timer.
func (fs *fileStore) syncBlocks() {
fs.mu.Lock()
if fs.closed {
fs.mu.Unlock()
if fs.isClosed() {
return
}
fs.mu.Lock()
blks := append([]*msgBlock(nil), fs.blks...)
lmb, firstMoved, firstSeq := fs.lmb, fs.firstMoved, fs.state.FirstSeq
// Clear first moved.
@@ -7089,11 +7170,10 @@ func (fs *fileStore) syncBlocks() {
}
}
fs.mu.Lock()
if fs.closed {
fs.mu.Unlock()
if fs.isClosed() {
return
}
fs.mu.Lock()
fs.setSyncTimer()
if markDirty {
fs.dirty++
@@ -7939,17 +8019,14 @@ func (fs *fileStore) msgForSeq(seq uint64, sm *StoreMsg) (*StoreMsg, error) {
// Will return message for the given sequence number.
func (fs *fileStore) msgForSeqLocked(seq uint64, sm *StoreMsg, needFSLock bool) (*StoreMsg, error) {
if fs.isClosed() {
return nil, ErrStoreClosed
}
// TODO(dlc) - Since Store, Remove, Skip all hold the write lock on fs this will
// be stalled. Need another lock if want to happen in parallel.
if needFSLock {
fs.mu.RLock()
}
if fs.closed {
if needFSLock {
fs.mu.RUnlock()
}
return nil, ErrStoreClosed
}
// Indicates we want first msg.
if seq == 0 {
seq = fs.state.FirstSeq
@@ -8128,10 +8205,14 @@ func (fs *fileStore) LoadMsg(seq uint64, sm *StoreMsg) (*StoreMsg, error) {
// loadLast will load the last message for a subject. Subject should be non empty and not ">".
func (fs *fileStore) loadLast(subj string, sm *StoreMsg) (lsm *StoreMsg, err error) {
if fs.isClosed() {
return nil, ErrStoreClosed
}
fs.mu.RLock()
defer fs.mu.RUnlock()
if fs.closed || fs.lmb == nil {
if fs.lmb == nil {
return nil, ErrStoreClosed
}
@@ -8228,15 +8309,15 @@ func (fs *fileStore) LoadLastMsg(subject string, smv *StoreMsg) (sm *StoreMsg, e
// LoadNextMsgMulti will find the next message matching any entry in the sublist.
func (fs *fileStore) LoadNextMsgMulti(sl *gsl.SimpleSublist, start uint64, smp *StoreMsg) (sm *StoreMsg, skip uint64, err error) {
if fs.isClosed() {
return nil, 0, ErrStoreClosed
}
if sl == nil {
return fs.LoadNextMsg(_EMPTY_, false, start, smp)
}
fs.mu.RLock()
defer fs.mu.RUnlock()
if fs.closed {
return nil, 0, ErrStoreClosed
}
if fs.state.Msgs == 0 || start > fs.state.LastSeq {
return nil, fs.state.LastSeq, ErrStoreEOF
}
@@ -8244,6 +8325,31 @@ func (fs *fileStore) LoadNextMsgMulti(sl *gsl.SimpleSublist, start uint64, smp *
start = fs.state.FirstSeq
}
// If start is less than or equal to beginning of our stream, meaning our first call,
// let's check the psim to see if we can skip ahead.
if start <= fs.state.FirstSeq {
var total uint64
blkStart := uint32(math.MaxUint32)
stree.IntersectGSL(fs.psim, sl, func(subj []byte, psi *psi) {
total += psi.total
// Keep track of start index for this subject.
if psi.fblk < blkStart {
blkStart = psi.fblk
}
})
// Nothing available.
if total == 0 {
return nil, fs.state.LastSeq, ErrStoreEOF
}
// We can skip ahead.
if mb := fs.bim[blkStart]; mb != nil {
fseq := atomic.LoadUint64(&mb.first.seq)
if fseq > start {
start = fseq
}
}
}
if bi, _ := fs.selectMsgBlockWithIndex(start); bi >= 0 {
for i := bi; i < len(fs.blks); i++ {
mb := fs.blks[i]
@@ -8254,8 +8360,28 @@ func (fs *fileStore) LoadNextMsgMulti(sl *gsl.SimpleSublist, start uint64, smp *
return sm, sm.seq, nil
} else if err != ErrStoreMsgNotFound {
return nil, 0, err
} else if expireOk {
mb.tryForceExpireCache()
} else {
// Nothing found in this block. We missed, if first block (bi) check psim.
// Similar to above if start <= first seq.
// TODO(dlc) - For v2 track these by filter subject since they will represent filtered consumers.
// We should not do this at all if we are already on the last block.
if i == bi && i < len(fs.blks)-1 {
nbi, err := fs.checkSkipFirstBlockMulti(sl, bi)
// Nothing available.
if err == ErrStoreEOF {
return nil, fs.state.LastSeq, ErrStoreEOF
}
// See if we can jump ahead here.
// Right now we can only spin on first, so if we have interior sparseness need to favor checking per block fss if loaded.
// For v2 will track all blocks that have matches for psim.
if nbi > i {
i = nbi - 1 // For the iterator condition i++
}
}
// Check if we can expire.
if expireOk {
mb.tryForceExpireCache()
}
}
}
}
@@ -8265,12 +8391,13 @@ func (fs *fileStore) LoadNextMsgMulti(sl *gsl.SimpleSublist, start uint64, smp *
}
func (fs *fileStore) LoadNextMsg(filter string, wc bool, start uint64, sm *StoreMsg) (*StoreMsg, uint64, error) {
if fs.isClosed() {
return nil, 0, ErrStoreClosed
}
fs.mu.RLock()
defer fs.mu.RUnlock()
if fs.closed {
return nil, 0, ErrStoreClosed
}
if fs.state.Msgs == 0 || start > fs.state.LastSeq {
return nil, fs.state.LastSeq, ErrStoreEOF
}
@@ -8323,7 +8450,7 @@ func (fs *fileStore) LoadNextMsg(filter string, wc bool, start uint64, sm *Store
i = nbi - 1 // For the iterator condition i++
}
}
// Check is we can expire.
// Check if we can expire.
if expireOk {
mb.tryForceExpireCache()
}
@@ -8336,12 +8463,13 @@ func (fs *fileStore) LoadNextMsg(filter string, wc bool, start uint64, sm *Store
// Will load the next non-deleted msg starting at the start sequence and walking backwards.
func (fs *fileStore) LoadPrevMsg(start uint64, smp *StoreMsg) (sm *StoreMsg, err error) {
if fs.isClosed() {
return nil, ErrStoreClosed
}
fs.mu.RLock()
defer fs.mu.RUnlock()
if fs.closed {
return nil, ErrStoreClosed
}
if fs.state.Msgs == 0 || start < fs.state.FirstSeq {
return nil, ErrStoreEOF
}
@@ -8389,6 +8517,10 @@ func (fs *fileStore) LoadPrevMsg(start uint64, smp *StoreMsg) (sm *StoreMsg, err
// LoadPrevMsgMulti will find the previous message matching any entry in the sublist.
func (fs *fileStore) LoadPrevMsgMulti(sl *gsl.SimpleSublist, start uint64, smp *StoreMsg) (sm *StoreMsg, skip uint64, err error) {
if fs.isClosed() {
return nil, 0, ErrStoreClosed
}
if sl == nil {
sm, err = fs.LoadPrevMsg(start, smp)
return
@@ -8396,9 +8528,6 @@ func (fs *fileStore) LoadPrevMsgMulti(sl *gsl.SimpleSublist, start uint64, smp *
fs.mu.RLock()
defer fs.mu.RUnlock()
if fs.closed {
return nil, 0, ErrStoreClosed
}
if fs.state.Msgs == 0 || start < fs.state.FirstSeq {
return nil, fs.state.FirstSeq, ErrStoreEOF
}
@@ -8952,12 +9081,12 @@ func (fs *fileStore) Purge() (uint64, error) {
}
func (fs *fileStore) purge(fseq uint64) (uint64, error) {
fs.mu.Lock()
if fs.closed {
fs.mu.Unlock()
if fs.isClosed() {
return 0, ErrStoreClosed
}
fs.mu.Lock()
purged := fs.state.Msgs
rbytes := int64(fs.state.Bytes)
@@ -9065,6 +9194,11 @@ func (fs *fileStore) compact(seq uint64) (uint64, error) {
fs.mu.Unlock()
return fs.purge(seq)
}
// Short-circuit if the store was already compacted past this point.
if fs.state.FirstSeq > seq {
fs.mu.Unlock()
return purged, nil
}
// We have to delete interior messages.
smb := fs.selectMsgBlock(seq)
if smb == nil {
@@ -9112,7 +9246,12 @@ func (fs *fileStore) compact(seq uint64) (uint64, error) {
if err = smb.loadMsgsWithLock(); err != nil {
goto SKIP
}
defer smb.finishedWithCache()
defer func() {
// The lock is released once we get here, so need to re-acquire.
smb.mu.Lock()
smb.finishedWithCache()
smb.mu.Unlock()
}()
}
for mseq := atomic.LoadUint64(&smb.first.seq); mseq < seq; mseq++ {
sm, err := smb.cacheLookupNoCopy(mseq, &smv)
@@ -9290,12 +9429,12 @@ SKIP:
// Will completely reset our store.
func (fs *fileStore) reset() error {
fs.mu.Lock()
if fs.closed {
fs.mu.Unlock()
if fs.isClosed() {
return ErrStoreClosed
}
fs.mu.Lock()
var purged, bytes uint64
cb := fs.scb
@@ -9381,6 +9520,10 @@ func (mb *msgBlock) tombsLocked() []msgId {
// Truncate will truncate a stream store up to seq. Sequence needs to be valid.
func (fs *fileStore) Truncate(seq uint64) error {
if fs.isClosed() {
return ErrStoreClosed
}
// Check for request to reset.
if seq == 0 {
return fs.reset()
@@ -9388,11 +9531,6 @@ func (fs *fileStore) Truncate(seq uint64) error {
fs.mu.Lock()
if fs.closed {
fs.mu.Unlock()
return ErrStoreClosed
}
// Any existing state file will no longer be applicable. We will force write a new one
// at the end, after we release the lock.
os.Remove(filepath.Join(fs.fcfg.StoreDir, msgDir, streamStreamStateFile))
@@ -9682,6 +9820,14 @@ func (fs *fileStore) purgeMsgBlock(mb *msgBlock) {
mb.finishedWithCache()
mb.mu.Unlock()
fs.selectNextFirst()
if cb := fs.scb; cb != nil {
// If we have a callback registered, we need to release lock regardless since consumers will recalculate pending.
fs.mu.Unlock()
// Storage updates.
cb(-int64(msgs), -int64(bytes), 0, _EMPTY_)
fs.mu.Lock()
}
}
// Called by purge to simply get rid of the cache and close our fds.
@@ -9893,6 +10039,9 @@ func (mb *msgBlock) recalculateForSubj(subj string, ss *SimpleState) {
func (fs *fileStore) resetGlobalPerSubjectInfo() {
// Clear any global subject state.
fs.psim, fs.tsl = fs.psim.Empty(), 0
if fs.noTrackSubjects() {
return
}
for _, mb := range fs.blks {
fs.populateGlobalPerSubjectInfo(mb)
}
@@ -10230,6 +10379,10 @@ func (fs *fileStore) forceWriteFullStateLocked() error {
// 3. MBs - Index, Bytes, First and Last Sequence and Timestamps, and the deleted map (avl.seqset).
// 4. Last block index and hash of record inclusive to this stream state.
func (fs *fileStore) _writeFullState(force, needLock bool) error {
if fs.isClosed() {
return nil
}
fsLock := func() {
if needLock {
fs.mu.Lock()
@@ -10243,7 +10396,7 @@ func (fs *fileStore) _writeFullState(force, needLock bool) error {
start := time.Now()
fsLock()
if fs.closed || fs.dirty == 0 {
if fs.dirty == 0 {
fsUnlock()
return nil
}
@@ -10494,8 +10647,12 @@ func (fs *fileStore) Stop() error {
// Stop the current filestore.
func (fs *fileStore) stop(delete, writeState bool) error {
if fs.isClosed() {
return ErrStoreClosed
}
fs.mu.Lock()
if fs.closed || fs.closing {
if fs.closing {
fs.mu.Unlock()
return ErrStoreClosed
}
@@ -10531,7 +10688,7 @@ func (fs *fileStore) stop(delete, writeState bool) error {
// Mark as closed. Last message block needs to be cleared after
// writeFullState has completed.
fs.closed = true
fs.closed.Store(true)
fs.lmb = nil
// We should update the upper usage layer on a stop.
@@ -10750,11 +10907,12 @@ func (fs *fileStore) streamSnapshot(w io.WriteCloser, includeConsumers bool, err
// Create a snapshot of this stream and its consumer's state along with messages.
func (fs *fileStore) Snapshot(deadline time.Duration, checkMsgs, includeConsumers bool) (*SnapshotResult, error) {
fs.mu.Lock()
if fs.closed {
fs.mu.Unlock()
if fs.isClosed() {
return nil, ErrStoreClosed
}
fs.mu.Lock()
// Only allow one at a time.
if fs.sips > 0 {
fs.mu.Unlock()
@@ -11074,7 +11232,9 @@ func (fs *fileStore) ConsumerStore(name string, created time.Time, cfg *Consumer
go o.flushLoop(o.fch, o.qch)
// Make sure to load in our state from disk if needed.
o.loadState()
if err = o.loadState(); err != nil {
return nil, err
}
// Assign to filestore.
fs.AddConsumer(o)
@@ -11764,10 +11924,15 @@ func (o *consumerFileStore) stateWithCopyLocked(doCopy bool) (*ConsumerState, er
}
// Lock should be held. Called at startup.
func (o *consumerFileStore) loadState() {
func (o *consumerFileStore) loadState() error {
if _, err := os.Stat(o.ifn); err == nil {
// This will load our state in from disk.
o.stateWithCopyLocked(false)
_, err = o.stateWithCopyLocked(false)
return err
} else if os.IsNotExist(err) {
return nil
} else {
return err
}
}
+42 -84
View File
@@ -17,9 +17,6 @@ import (
"errors"
"strings"
"sync"
"unsafe"
"github.com/nats-io/nats-server/v2/server/stree"
)
// Sublist is a routing mechanism to handle subject distribution and
@@ -251,7 +248,9 @@ func matchLevelForAny[T comparable](l *level[T], toks []string, np *int) bool {
if np != nil {
*np += len(n.subs)
}
return len(n.subs) > 0
if len(n.subs) > 0 {
return true
}
}
if pwc != nil {
if np != nil {
@@ -370,6 +369,36 @@ func (s *GenericSublist[T]) Remove(subject string, value T) error {
return s.remove(subject, value, true)
}
// HasInterestStartingIn is a helper for subject tree intersection.
func (s *GenericSublist[T]) HasInterestStartingIn(subj string) bool {
s.RLock()
defer s.RUnlock()
var _tokens [64]string
tokens := tokenizeSubjectIntoSlice(_tokens[:0], subj)
return hasInterestStartingIn(s.root, tokens)
}
func hasInterestStartingIn[T comparable](l *level[T], tokens []string) bool {
if l == nil {
return false
}
if len(tokens) == 0 {
return true
}
token := tokens[0]
if l.fwc != nil {
return true
}
found := false
if pwc := l.pwc; pwc != nil {
found = found || hasInterestStartingIn(pwc.next, tokens[1:])
}
if n := l.nodes[token]; n != nil {
found = found || hasInterestStartingIn(n.next, tokens[1:])
}
return found
}
// pruneNode is used to prune an empty node from the tree.
func (l *level[T]) pruneNode(n *node[T], t string) {
if n == nil {
@@ -463,86 +492,15 @@ func visitLevel[T comparable](l *level[T], depth int) int {
return maxDepth
}
// IntersectStree will match all items in the given subject tree that
// have interest expressed in the given sublist. The callback will only be called
// once for each subject, regardless of overlapping subscriptions in the sublist.
func IntersectStree[T1 any, T2 comparable](st *stree.SubjectTree[T1], sl *GenericSublist[T2], cb func(subj []byte, entry *T1)) {
var _subj [255]byte
intersectStree(st, sl.root, _subj[:0], cb)
}
func intersectStree[T1 any, T2 comparable](st *stree.SubjectTree[T1], r *level[T2], subj []byte, cb func(subj []byte, entry *T1)) {
nsubj := subj
if len(nsubj) > 0 {
nsubj = append(subj, '.')
}
if r.fwc != nil {
// We've reached a full wildcard, do a FWC match on the stree at this point
// and don't keep iterating downward.
nsubj := append(nsubj, '>')
st.Match(nsubj, cb)
return
}
if r.pwc != nil {
// We've found a partial wildcard. We'll keep iterating downwards, but first
// check whether there's interest at this level (without triggering dupes) and
// match if so.
var done bool
nsubj := append(nsubj, '*')
if len(r.pwc.subs) > 0 {
st.Match(nsubj, cb)
done = true
}
if r.pwc.next.numNodes() > 0 {
intersectStree(st, r.pwc.next, nsubj, cb)
}
if done {
return
}
}
// Normal node with subject literals, keep iterating.
for t, n := range r.nodes {
if r.pwc != nil && r.pwc.next.numNodes() > 0 && n.next.numNodes() > 0 {
// A wildcard at the next level will already visit these descendents
// so skip so we don't callback the same subject more than once.
continue
// use similar to append. meaning, the updated slice will be returned
func tokenizeSubjectIntoSlice(tts []string, subject string) []string {
start := 0
for i := 0; i < len(subject); i++ {
if subject[i] == btsep {
tts = append(tts, subject[start:i])
start = i + 1
}
nsubj := append(nsubj, t...)
if len(n.subs) > 0 {
if subjectHasWildcard(bytesToString(nsubj)) {
st.Match(nsubj, cb)
} else {
if e, ok := st.Find(nsubj); ok {
cb(nsubj, e)
}
}
}
if n.next.numNodes() > 0 {
intersectStree(st, n.next, nsubj, cb)
}
}
}
// Determine if a subject has any wildcard tokens.
func subjectHasWildcard(subject string) bool {
// This one exits earlier then !subjectIsLiteral(subject)
for i, c := range subject {
if c == pwc || c == fwc {
if (i == 0 || subject[i-1] == btsep) &&
(i+1 == len(subject) || subject[i+1] == btsep) {
return true
}
}
}
return false
}
// Note this will avoid a copy of the data used for the string, but it will also reference the existing slice's data pointer.
// So this should be used sparingly when we know the encompassing byte slice's lifetime is the same.
func bytesToString(b []byte) string {
if len(b) == 0 {
return _EMPTY_
}
p := unsafe.SliceData(b)
return unsafe.String(p, len(b))
tts = append(tts, subject[start:])
return tts
}
+8 -5
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2025 The NATS Authors
// Copyright 2019-2026 The NATS 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
@@ -1142,6 +1142,12 @@ func (a *Account) EnableJetStream(limits map[string]JetStreamAccountLimits, tq c
}
js.mu.Lock()
// Accounts get reset to nil on shutdown, since we re-acquire the locks here, we need to check again.
if js.accounts == nil {
js.mu.Unlock()
return NewJSNotEnabledError()
}
if jsa, ok := js.accounts[a.Name]; ok {
a.mu.Lock()
a.js = jsa
@@ -1370,7 +1376,7 @@ func (a *Account) EnableJetStream(limits map[string]JetStreamAccountLimits, tq c
}
obs, err := mset.addConsumerWithAssignment(&cfg.ConsumerConfig, _EMPTY_, nil, true, ActionCreateOrUpdate, false)
if err != nil {
s.Warnf(" Error adding consumer %q: %v", cfg.Name, err)
s.Warnf(" Error adding consumer '%s > %s > %s': %v", a.Name, mset.name(), cfg.Name, err)
continue
}
if isEphemeral {
@@ -1379,9 +1385,6 @@ func (a *Account) EnableJetStream(limits map[string]JetStreamAccountLimits, tq c
if !cfg.Created.IsZero() {
obs.setCreatedTime(cfg.Created)
}
if err != nil {
s.Warnf(" Error restoring consumer %q state: %v", cfg.Name, err)
}
}
}
+150 -133
View File
@@ -1,4 +1,4 @@
// Copyright 2020-2025 The NATS Authors
// Copyright 2020-2026 The NATS 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
@@ -1296,11 +1296,6 @@ func (s *Server) jsAccountInfoRequest(sub *subscription, c *client, _ *Account,
}
var resp = JSApiAccountInfoResponse{ApiResponse: ApiResponse{Type: JSApiAccountInfoResponseType}}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
// Determine if we should proceed here when we are in clustered mode.
if s.JetStreamIsClustered() {
@@ -1319,6 +1314,12 @@ func (s *Server) jsAccountInfoRequest(sub *subscription, c *client, _ *Account,
}
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if hasJS, doErr := acc.checkJetStream(); !hasJS {
if !doErr {
return
@@ -1612,11 +1613,6 @@ func (s *Server) jsStreamCreateRequest(sub *subscription, c *client, _ *Account,
}
var resp = JSApiStreamCreateResponse{ApiResponse: ApiResponse{Type: JSApiStreamCreateResponseType}}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
// Determine if we should proceed here when we are in clustered mode.
if s.JetStreamIsClustered() {
@@ -1635,6 +1631,12 @@ func (s *Server) jsStreamCreateRequest(sub *subscription, c *client, _ *Account,
}
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if hasJS, doErr := acc.checkJetStream(); !hasJS {
if doErr {
resp.Error = NewJSNotEnabledForAccountError()
@@ -1729,11 +1731,6 @@ func (s *Server) jsStreamUpdateRequest(sub *subscription, c *client, _ *Account,
}
var resp = JSApiStreamUpdateResponse{ApiResponse: ApiResponse{Type: JSApiStreamUpdateResponseType}}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
// Determine if we should proceed here when we are in clustered mode.
if s.JetStreamIsClustered() {
@@ -1752,6 +1749,12 @@ func (s *Server) jsStreamUpdateRequest(sub *subscription, c *client, _ *Account,
}
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if hasJS, doErr := acc.checkJetStream(); !hasJS {
if doErr {
resp.Error = NewJSNotEnabledForAccountError()
@@ -1832,11 +1835,6 @@ func (s *Server) jsStreamNamesRequest(sub *subscription, c *client, _ *Account,
}
var resp = JSApiStreamNamesResponse{ApiResponse: ApiResponse{Type: JSApiStreamNamesResponseType}}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
// Determine if we should proceed here when we are in clustered mode.
if s.JetStreamIsClustered() {
@@ -1855,6 +1853,12 @@ func (s *Server) jsStreamNamesRequest(sub *subscription, c *client, _ *Account,
}
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if hasJS, doErr := acc.checkJetStream(); !hasJS {
if doErr {
resp.Error = NewJSNotEnabledForAccountError()
@@ -1967,11 +1971,6 @@ func (s *Server) jsStreamListRequest(sub *subscription, c *client, _ *Account, s
ApiResponse: ApiResponse{Type: JSApiStreamListResponseType},
Streams: []*StreamInfo{},
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
// Determine if we should proceed here when we are in clustered mode.
if s.JetStreamIsClustered() {
@@ -1990,6 +1989,12 @@ func (s *Server) jsStreamListRequest(sub *subscription, c *client, _ *Account, s
}
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if hasJS, doErr := acc.checkJetStream(); !hasJS {
if doErr {
resp.Error = NewJSNotEnabledForAccountError()
@@ -2090,11 +2095,6 @@ func (s *Server) jsStreamInfoRequest(sub *subscription, c *client, a *Account, s
if rt := getHeader(JSResponseType, hdr); len(rt) > 0 && string(rt) == jsCreateResponse {
resp.ApiResponse.Type = JSApiStreamCreateResponseType
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
var clusterWideConsCount int
@@ -2184,6 +2184,12 @@ func (s *Server) jsStreamInfoRequest(sub *subscription, c *client, a *Account, s
}
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if hasJS, doErr := acc.checkJetStream(); !hasJS {
if doErr {
resp.Error = NewJSNotEnabledForAccountError()
@@ -2309,11 +2315,6 @@ func (s *Server) jsStreamLeaderStepDownRequest(sub *subscription, c *client, _ *
name := tokenAt(subject, 6)
var resp = JSApiStreamLeaderStepDownResponse{ApiResponse: ApiResponse{Type: JSApiStreamLeaderStepDownResponseType}}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
// If we are not in clustered mode this is a failed request.
if !s.JetStreamIsClustered() {
@@ -2345,6 +2346,12 @@ func (s *Server) jsStreamLeaderStepDownRequest(sub *subscription, c *client, _ *
return
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if hasJS, doErr := acc.checkJetStream(); !hasJS {
if doErr {
resp.Error = NewJSNotEnabledForAccountError()
@@ -2421,11 +2428,6 @@ func (s *Server) jsConsumerLeaderStepDownRequest(sub *subscription, c *client, _
}
var resp = JSApiConsumerLeaderStepDownResponse{ApiResponse: ApiResponse{Type: JSApiConsumerLeaderStepDownResponseType}}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
// If we are not in clustered mode this is a failed request.
if !s.JetStreamIsClustered() {
@@ -2460,6 +2462,13 @@ func (s *Server) jsConsumerLeaderStepDownRequest(sub *subscription, c *client, _
} else if sa == nil {
return
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
var ca *consumerAssignment
if sa.consumers != nil {
ca = sa.consumers[consumer]
@@ -2547,11 +2556,6 @@ func (s *Server) jsStreamRemovePeerRequest(sub *subscription, c *client, _ *Acco
name := tokenAt(subject, 6)
var resp = JSApiStreamRemovePeerResponse{ApiResponse: ApiResponse{Type: JSApiStreamRemovePeerResponseType}}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
// If we are not in clustered mode this is a failed request.
if !s.JetStreamIsClustered() {
@@ -2580,6 +2584,12 @@ func (s *Server) jsStreamRemovePeerRequest(sub *subscription, c *client, _ *Acco
return
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if hasJS, doErr := acc.checkJetStream(); !hasJS {
if doErr {
resp.Error = NewJSNotEnabledForAccountError()
@@ -3072,11 +3082,6 @@ func (s *Server) jsLeaderAccountPurgeRequest(sub *subscription, c *client, _ *Ac
accName := tokenAt(subject, 5)
var resp = JSApiAccountPurgeResponse{ApiResponse: ApiResponse{Type: JSApiAccountPurgeResponseType}}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if !s.JetStreamIsClustered() {
var streams []*stream
@@ -3117,6 +3122,12 @@ func (s *Server) jsLeaderAccountPurgeRequest(sub *subscription, c *client, _ *Ac
return
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if js.isMetaRecovering() {
// While in recovery mode, the data structures are not fully initialized
resp.Error = NewJSClusterNotAvailError()
@@ -3339,11 +3350,6 @@ func (s *Server) jsStreamDeleteRequest(sub *subscription, c *client, _ *Account,
}
var resp = JSApiStreamDeleteResponse{ApiResponse: ApiResponse{Type: JSApiStreamDeleteResponseType}}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
// Determine if we should proceed here when we are in clustered mode.
if s.JetStreamIsClustered() {
@@ -3362,6 +3368,12 @@ func (s *Server) jsStreamDeleteRequest(sub *subscription, c *client, _ *Account,
}
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if hasJS, doErr := acc.checkJetStream(); !hasJS {
if doErr {
resp.Error = NewJSNotEnabledForAccountError()
@@ -3414,11 +3426,6 @@ func (s *Server) jsMsgDeleteRequest(sub *subscription, c *client, _ *Account, su
stream := tokenAt(subject, 6)
var resp = JSApiMsgDeleteResponse{ApiResponse: ApiResponse{Type: JSApiMsgDeleteResponseType}}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
// If we are in clustered mode we need to be the stream leader to proceed.
if s.JetStreamIsClustered() {
@@ -3467,6 +3474,12 @@ func (s *Server) jsMsgDeleteRequest(sub *subscription, c *client, _ *Account, su
}
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if hasJS, doErr := acc.checkJetStream(); !hasJS {
if doErr {
resp.Error = NewJSNotEnabledForAccountError()
@@ -3538,11 +3551,6 @@ func (s *Server) jsMsgGetRequest(sub *subscription, c *client, _ *Account, subje
stream := tokenAt(subject, 6)
var resp = JSApiMsgGetResponse{ApiResponse: ApiResponse{Type: JSApiMsgGetResponseType}}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
// If we are in clustered mode we need to be the stream leader to proceed.
if s.JetStreamIsClustered() {
@@ -3591,6 +3599,12 @@ func (s *Server) jsMsgGetRequest(sub *subscription, c *client, _ *Account, subje
}
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if hasJS, doErr := acc.checkJetStream(); !hasJS {
if doErr {
resp.Error = NewJSNotEnabledForAccountError()
@@ -3695,31 +3709,8 @@ func (s *Server) jsConsumerUnpinRequest(sub *subscription, c *client, _ *Account
stream := streamNameFromSubject(subject)
consumer := consumerNameFromSubject(subject)
var req JSApiConsumerUnpinRequest
var resp = JSApiConsumerUnpinResponse{ApiResponse: ApiResponse{Type: JSApiConsumerUnpinResponseType}}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if err := json.Unmarshal(msg, &req); err != nil {
resp.Error = NewJSInvalidJSONError(err)
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if req.Group == _EMPTY_ {
resp.Error = NewJSInvalidJSONError(errors.New("consumer group not specified"))
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if !validGroupName.MatchString(req.Group) {
resp.Error = NewJSConsumerInvalidGroupNameError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if s.JetStreamIsClustered() {
// Check to make sure the stream is assigned.
js, cc := s.getJetStreamCluster()
@@ -3771,6 +3762,31 @@ func (s *Server) jsConsumerUnpinRequest(sub *subscription, c *client, _ *Account
}
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
var req JSApiConsumerUnpinRequest
if err := json.Unmarshal(msg, &req); err != nil {
resp.Error = NewJSInvalidJSONError(err)
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if req.Group == _EMPTY_ {
resp.Error = NewJSInvalidJSONError(errors.New("consumer group not specified"))
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if !validGroupName.MatchString(req.Group) {
resp.Error = NewJSConsumerInvalidGroupNameError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if hasJS, doErr := acc.checkJetStream(); !hasJS {
if doErr {
resp.Error = NewJSNotEnabledForAccountError()
@@ -3834,11 +3850,6 @@ func (s *Server) jsStreamPurgeRequest(sub *subscription, c *client, _ *Account,
stream := streamNameFromSubject(subject)
var resp = JSApiStreamPurgeResponse{ApiResponse: ApiResponse{Type: JSApiStreamPurgeResponseType}}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
// If we are in clustered mode we need to be the stream leader to proceed.
if s.JetStreamIsClustered() {
@@ -3890,6 +3901,12 @@ func (s *Server) jsStreamPurgeRequest(sub *subscription, c *client, _ *Account,
}
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if hasJS, doErr := acc.checkJetStream(); !hasJS {
if doErr {
resp.Error = NewJSNotEnabledForAccountError()
@@ -4507,11 +4524,6 @@ func (s *Server) jsConsumerCreateRequest(sub *subscription, c *client, a *Accoun
}
var resp = JSApiConsumerCreateResponse{ApiResponse: ApiResponse{Type: JSApiConsumerCreateResponseType}}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
var req CreateConsumerRequest
if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil {
@@ -4548,6 +4560,20 @@ func (s *Server) jsConsumerCreateRequest(sub *subscription, c *client, a *Accoun
}
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if hasJS, doErr := acc.checkJetStream(); !hasJS {
if doErr {
resp.Error = NewJSNotEnabledForAccountError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
}
return
}
var streamName, consumerName, filteredSubject string
var rt ccReqType
@@ -4580,14 +4606,6 @@ func (s *Server) jsConsumerCreateRequest(sub *subscription, c *client, a *Accoun
}
}
if hasJS, doErr := acc.checkJetStream(); !hasJS {
if doErr {
resp.Error = NewJSNotEnabledForAccountError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
}
return
}
if streamName != req.Stream {
resp.Error = NewJSStreamMismatchError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
@@ -4732,11 +4750,6 @@ func (s *Server) jsConsumerNamesRequest(sub *subscription, c *client, _ *Account
ApiResponse: ApiResponse{Type: JSApiConsumerNamesResponseType},
Consumers: []string{},
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
// Determine if we should proceed here when we are in clustered mode.
if s.JetStreamIsClustered() {
@@ -4755,6 +4768,12 @@ func (s *Server) jsConsumerNamesRequest(sub *subscription, c *client, _ *Account
}
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if hasJS, doErr := acc.checkJetStream(); !hasJS {
if doErr {
resp.Error = NewJSNotEnabledForAccountError()
@@ -4859,11 +4878,6 @@ func (s *Server) jsConsumerListRequest(sub *subscription, c *client, _ *Account,
ApiResponse: ApiResponse{Type: JSApiConsumerListResponseType},
Consumers: []*ConsumerInfo{},
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
// Determine if we should proceed here when we are in clustered mode.
if s.JetStreamIsClustered() {
@@ -4883,7 +4897,7 @@ func (s *Server) jsConsumerListRequest(sub *subscription, c *client, _ *Account,
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSClusterNotAvailError()
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
@@ -4973,11 +4987,6 @@ func (s *Server) jsConsumerInfoRequest(sub *subscription, c *client, _ *Account,
consumerName := consumerNameFromSubject(subject)
var resp = JSApiConsumerInfoResponse{ApiResponse: ApiResponse{Type: JSApiConsumerInfoResponseType}}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if !isEmptyRequest(msg) {
resp.Error = NewJSNotEmptyRequestError()
@@ -5128,6 +5137,12 @@ func (s *Server) jsConsumerInfoRequest(sub *subscription, c *client, _ *Account,
}
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if !acc.JetStreamEnabled() {
resp.Error = NewJSNotEnabledForAccountError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
@@ -5175,11 +5190,6 @@ func (s *Server) jsConsumerDeleteRequest(sub *subscription, c *client, _ *Accoun
}
var resp = JSApiConsumerDeleteResponse{ApiResponse: ApiResponse{Type: JSApiConsumerDeleteResponseType}}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
// Determine if we should proceed here when we are in clustered mode.
if s.JetStreamIsClustered() {
@@ -5198,6 +5208,12 @@ func (s *Server) jsConsumerDeleteRequest(sub *subscription, c *client, _ *Accoun
}
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if hasJS, doErr := acc.checkJetStream(); !hasJS {
if doErr {
resp.Error = NewJSNotEnabledForAccountError()
@@ -5253,11 +5269,6 @@ func (s *Server) jsConsumerPauseRequest(sub *subscription, c *client, _ *Account
var req JSApiConsumerPauseRequest
var resp = JSApiConsumerPauseResponse{ApiResponse: ApiResponse{Type: JSApiConsumerPauseResponseType}}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if isJSONObjectOrArray(msg) {
if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil {
@@ -5285,6 +5296,12 @@ func (s *Server) jsConsumerPauseRequest(sub *subscription, c *client, _ *Account
}
}
if errorOnRequiredApiLevel(hdr) {
resp.Error = NewJSRequiredApiLevelError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
if hasJS, doErr := acc.checkJetStream(); !hasJS {
if doErr {
resp.Error = NewJSNotEnabledForAccountError()
+8 -8
View File
@@ -3142,20 +3142,20 @@ func (mset *stream) resetClusteredState(err error) bool {
stype, tierName, replicas := mset.cfg.Storage, mset.tier, mset.cfg.Replicas
mset.mu.RUnlock()
// The stream might already be deleted and not assigned to us anymore.
// In any case, don't revive the stream if it's already closed.
if mset.closed.Load() || (node != nil && node.IsDeleted()) {
s.Warnf("Will not reset stream '%s > %s', stream is closed", acc, mset.name())
// Explicitly returning true here, we want the outside to break out of the monitoring loop as well.
return true
}
assert.Unreachable("Reset clustered state", map[string]any{
"stream": name,
"account": acc.Name,
"err": err,
})
// The stream might already be deleted and not assigned to us anymore.
// In any case, don't revive the stream if it's already closed.
if mset.closed.Load() {
s.Warnf("Will not reset stream '%s > %s', stream is closed", acc, mset.name())
// Explicitly returning true here, we want the outside to break out of the monitoring loop as well.
return true
}
// Stepdown regardless if we are the leader here.
if node != nil {
node.StepDown()
+53 -10
View File
@@ -19,7 +19,6 @@ import (
"fmt"
"math"
"slices"
"sort"
"sync"
"time"
@@ -445,7 +444,6 @@ func (ms *memStore) RegisterProcessJetStreamMsg(cb ProcessJetStreamMsgHandler) {
// GetSeqFromTime looks for the first sequence number that has the message
// with >= timestamp.
// FIXME(dlc) - inefficient.
func (ms *memStore) GetSeqFromTime(t time.Time) uint64 {
ts := t.UnixNano()
ms.mu.RLock()
@@ -469,18 +467,57 @@ func (ms *memStore) GetSeqFromTime(t time.Time) uint64 {
last := lmsg.ts
if ts == last {
return ms.state.LastSeq
return lmsg.seq
}
if ts > last {
return ms.state.LastSeq + 1
}
index := sort.Search(len(ms.msgs), func(i int) bool {
if msg := ms.msgs[ms.state.FirstSeq+uint64(i)]; msg != nil {
return msg.ts >= ts
var (
cts int64
cseq uint64
off uint64
)
// Using a binary search, but need to be aware of interior deletes.
fseq := ms.state.FirstSeq
lseq := ms.state.LastSeq
seq := lseq + 1
loop:
for fseq <= lseq {
mid := fseq + (lseq-fseq)/2
off = 0
// Potentially skip over gaps. We keep the original middle but keep track of a
// potential delete range with an offset.
for {
msg := ms.msgs[mid+off]
if msg == nil {
off++
if mid+off <= lseq {
continue
} else {
// Continue search to the left. Purposely ignore the skipped deletes here.
lseq = mid - 1
continue loop
}
}
cts = msg.ts
cseq = msg.seq
break
}
return false
})
return uint64(index) + ms.state.FirstSeq
if cts >= ts {
seq = cseq
if mid == fseq {
break
}
// Continue search to the left.
lseq = mid - 1
} else {
// Continue search to the right (potentially skipping over interior deletes).
fseq = mid + off + 1
}
}
return seq
}
// FilteredState will return the SimpleState associated with the filtered subject and a proposed starting sequence.
@@ -906,7 +943,7 @@ func (ms *memStore) NumPendingMulti(sseq uint64, sl *gsl.SimpleSublist, lastPerS
var havePartial bool
var totalSkipped uint64
// We will track start and end sequences as we go.
gsl.IntersectStree[SimpleState](ms.fss, sl, func(subj []byte, fss *SimpleState) {
stree.IntersectGSL[SimpleState](ms.fss, sl, func(subj []byte, fss *SimpleState) {
if fss.firstNeedsUpdate || fss.lastNeedsUpdate {
ms.recalculateForSubj(bytesToString(subj), fss)
}
@@ -1463,6 +1500,12 @@ func (ms *memStore) compact(seq uint64) (uint64, error) {
var purged, bytes uint64
ms.mu.Lock()
// Short-circuit if the store was already compacted past this point.
if ms.state.FirstSeq > seq {
ms.mu.Unlock()
return purged, nil
}
cb := ms.scb
if seq <= ms.state.LastSeq {
fseq := ms.state.FirstSeq
+59 -25
View File
@@ -1,4 +1,4 @@
// Copyright 2013-2025 The NATS Authors
// Copyright 2013-2026 The NATS 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
@@ -1279,6 +1279,7 @@ type Varz struct {
SlowConsumersStats *SlowConsumersStats `json:"slow_consumer_stats"` // SlowConsumersStats are statistics about all detected Slow Consumer
StaleConnectionStats *StaleConnectionStats `json:"stale_connection_stats,omitempty"` // StaleConnectionStats are statistics about all detected Stale Connections
Proxies *ProxiesOptsVarz `json:"proxies,omitempty"` // Proxies hold information about network proxy devices
TLSCertNotAfter time.Time `json:"tls_cert_not_after,omitzero"` // TLSCertNotAfter is the expiration date of the TLS certificate of this server
}
// JetStreamVarz contains basic runtime information about jetstream
@@ -1291,34 +1292,36 @@ type JetStreamVarz struct {
// ClusterOptsVarz contains monitoring cluster information
type ClusterOptsVarz struct {
Name string `json:"name,omitempty"` // Name is the configured cluster name
Host string `json:"addr,omitempty"` // Host is the host the cluster listens on for connections
Port int `json:"cluster_port,omitempty"` // Port is the port the cluster listens on for connections
AuthTimeout float64 `json:"auth_timeout,omitempty"` // AuthTimeout is the time cluster connections have to complete authentication
URLs []string `json:"urls,omitempty"` // URLs is the list of cluster URLs
TLSTimeout float64 `json:"tls_timeout,omitempty"` // TLSTimeout is how long TLS operations have to complete
TLSRequired bool `json:"tls_required,omitempty"` // TLSRequired indicates if TLS is required for connections
TLSVerify bool `json:"tls_verify,omitempty"` // TLSVerify indicates if full verification of TLS connections is performed
PoolSize int `json:"pool_size,omitempty"` // PoolSize is the configured route connection pool size
WriteDeadline time.Duration `json:"write_deadline,omitempty"` // WriteDeadline is the maximum time writes to sockets have to complete
WriteTimeout string `json:"write_timeout,omitempty"` // WriteTimeout is the closure policy for write deadline errors
Name string `json:"name,omitempty"` // Name is the configured cluster name
Host string `json:"addr,omitempty"` // Host is the host the cluster listens on for connections
Port int `json:"cluster_port,omitempty"` // Port is the port the cluster listens on for connections
AuthTimeout float64 `json:"auth_timeout,omitempty"` // AuthTimeout is the time cluster connections have to complete authentication
URLs []string `json:"urls,omitempty"` // URLs is the list of cluster URLs
TLSTimeout float64 `json:"tls_timeout,omitempty"` // TLSTimeout is how long TLS operations have to complete
TLSRequired bool `json:"tls_required,omitempty"` // TLSRequired indicates if TLS is required for connections
TLSVerify bool `json:"tls_verify,omitempty"` // TLSVerify indicates if full verification of TLS connections is performed
PoolSize int `json:"pool_size,omitempty"` // PoolSize is the configured route connection pool size
WriteDeadline time.Duration `json:"write_deadline,omitempty"` // WriteDeadline is the maximum time writes to sockets have to complete
WriteTimeout string `json:"write_timeout,omitempty"` // WriteTimeout is the closure policy for write deadline errors
TLSCertNotAfter time.Time `json:"tls_cert_not_after,omitzero"` // TLSCertNotAfter is the expiration date of the TLS certificate
}
// GatewayOptsVarz contains monitoring gateway information
type GatewayOptsVarz struct {
Name string `json:"name,omitempty"` // Name is the configured cluster name
Host string `json:"host,omitempty"` // Host is the host the gateway listens on for connections
Port int `json:"port,omitempty"` // Port is the post gateway connections listens on
AuthTimeout float64 `json:"auth_timeout,omitempty"` // AuthTimeout is the time cluster connections have to complete authentication
TLSTimeout float64 `json:"tls_timeout,omitempty"` // TLSTimeout is how long TLS operations have to complete
TLSRequired bool `json:"tls_required,omitempty"` // TLSRequired indicates if TLS is required for connections
TLSVerify bool `json:"tls_verify,omitempty"` // TLSVerify indicates if full verification of TLS connections is performed
Advertise string `json:"advertise,omitempty"` // Advertise is the URL advertised to remote gateway clients
ConnectRetries int `json:"connect_retries,omitempty"` // ConnectRetries is how many connection attempts the route will make
Gateways []RemoteGatewayOptsVarz `json:"gateways,omitempty"` // Gateways is state of configured gateway remotes
RejectUnknown bool `json:"reject_unknown,omitempty"` // RejectUnknown indicates if unknown cluster connections will be rejected
WriteDeadline time.Duration `json:"write_deadline,omitempty"` // WriteDeadline is the maximum time writes to sockets have to complete
WriteTimeout string `json:"write_timeout,omitempty"` // WriteTimeout is the closure policy for write deadline errors
Name string `json:"name,omitempty"` // Name is the configured cluster name
Host string `json:"host,omitempty"` // Host is the host the gateway listens on for connections
Port int `json:"port,omitempty"` // Port is the post gateway connections listens on
AuthTimeout float64 `json:"auth_timeout,omitempty"` // AuthTimeout is the time cluster connections have to complete authentication
TLSTimeout float64 `json:"tls_timeout,omitempty"` // TLSTimeout is how long TLS operations have to complete
TLSRequired bool `json:"tls_required,omitempty"` // TLSRequired indicates if TLS is required for connections
TLSVerify bool `json:"tls_verify,omitempty"` // TLSVerify indicates if full verification of TLS connections is performed
Advertise string `json:"advertise,omitempty"` // Advertise is the URL advertised to remote gateway clients
ConnectRetries int `json:"connect_retries,omitempty"` // ConnectRetries is how many connection attempts the route will make
Gateways []RemoteGatewayOptsVarz `json:"gateways,omitempty"` // Gateways is state of configured gateway remotes
RejectUnknown bool `json:"reject_unknown,omitempty"` // RejectUnknown indicates if unknown cluster connections will be rejected
WriteDeadline time.Duration `json:"write_deadline,omitempty"` // WriteDeadline is the maximum time writes to sockets have to complete
WriteTimeout string `json:"write_timeout,omitempty"` // WriteTimeout is the closure policy for write deadline errors
TLSCertNotAfter time.Time `json:"tls_cert_not_after,omitzero"` // TLSCertNotAfter is the expiration date of the TLS certificaet
}
// RemoteGatewayOptsVarz contains monitoring remote gateway information
@@ -1340,6 +1343,7 @@ type LeafNodeOptsVarz struct {
TLSOCSPPeerVerify bool `json:"tls_ocsp_peer_verify,omitempty"` // TLSOCSPPeerVerify indicates if OCSP verification will be performed
WriteDeadline time.Duration `json:"write_deadline,omitempty"` // WriteDeadline is the maximum time writes to sockets have to complete
WriteTimeout string `json:"write_timeout,omitempty"` // WriteTimeout is the closure policy for write deadline errors
TLSCertNotAfter time.Time `json:"tls_cert_not_after,omitzero"` // TLSCertNotAfter is the expiration date of the TLS certificate
}
// DenyRules Contains lists of subjects not allowed to be imported/exported
@@ -1370,6 +1374,7 @@ type MQTTOptsVarz struct {
AckWait time.Duration `json:"ack_wait,omitempty"` // AckWait is how long the internal JetStream state store will allow acks to complete
MaxAckPending uint16 `json:"max_ack_pending,omitempty"` // MaxAckPending is how many outstanding acks the internal JetStream state store will allow
TLSOCSPPeerVerify bool `json:"tls_ocsp_peer_verify,omitempty"` // TLSOCSPPeerVerify indicates if OCSP verification will be done
TLSCertNotAfter time.Time `json:"tls_cert_not_after,omitzero"` // TLSCertNotAfter is the expiration date of the TLS certificate
}
// WebsocketOptsVarz contains monitoring websocket information
@@ -1388,6 +1393,7 @@ type WebsocketOptsVarz struct {
AllowedOrigins []string `json:"allowed_origins,omitempty"` // AllowedOrigins list of configured trusted origins
Compression bool `json:"compression,omitempty"` // Compression indicates if compression is supported
TLSOCSPPeerVerify bool `json:"tls_ocsp_peer_verify,omitempty"` // TLSOCSPPeerVerify indicates if OCSP verification will be done
TLSCertNotAfter time.Time `json:"tls_cert_not_after,omitzero"` // TLSCertNotAfter is the expiration date of the TLS certificate
}
// OCSPResponseCacheVarz contains OCSP response cache information
@@ -1454,6 +1460,22 @@ func myUptime(d time.Duration) string {
return fmt.Sprintf("%ds", tsecs)
}
func tlsCertNotAfter(config *tls.Config) time.Time {
if config == nil || len(config.Certificates) == 0 {
return time.Time{}
}
cert := config.Certificates[0]
leaf := cert.Leaf
if leaf == nil {
var err error
leaf, err = x509.ParseCertificate(cert.Certificate[0])
if err != nil {
return time.Time{}
}
}
return leaf.NotAfter
}
// HandleRoot will show basic info and links to others handlers.
func (s *Server) HandleRoot(w http.ResponseWriter, r *http.Request) {
// This feels dumb to me, but is required: https://code.google.com/p/go/issues/detail?id=4799
@@ -1779,6 +1801,13 @@ func (s *Server) updateVarzConfigReloadableFields(v *Varz) {
v.TLSOCSPPeerVerify = s.ocspPeerVerify && v.TLSRequired && s.opts.tlsConfigOpts != nil && s.opts.tlsConfigOpts.OCSPPeerConfig != nil && s.opts.tlsConfigOpts.OCSPPeerConfig.Verify
v.TLSCertNotAfter = tlsCertNotAfter(opts.TLSConfig)
v.Cluster.TLSCertNotAfter = tlsCertNotAfter(opts.Cluster.TLSConfig)
v.Gateway.TLSCertNotAfter = tlsCertNotAfter(opts.Gateway.TLSConfig)
v.LeafNode.TLSCertNotAfter = tlsCertNotAfter(opts.LeafNode.TLSConfig)
v.MQTT.TLSCertNotAfter = tlsCertNotAfter(opts.MQTT.TLSConfig)
v.Websocket.TLSCertNotAfter = tlsCertNotAfter(opts.Websocket.TLSConfig)
if opts.Proxies != nil {
if v.Proxies == nil {
v.Proxies = &ProxiesOptsVarz{}
@@ -3982,6 +4011,11 @@ func (s *Server) healthz(opts *HealthzOptions) *HealthStatus {
return health
}
// Healthz returns the health status of the server.
func (s *Server) Healthz(opts *HealthzOptions) *HealthStatus {
return s.healthz(opts)
}
type ExpvarzStatus struct {
Memstats json.RawMessage `json:"memstats"`
Cmdline json.RawMessage `json:"cmdline"`
+1 -1
View File
@@ -2326,7 +2326,7 @@ func parseJetStreamForAccount(v any, acc *Account, errors *[]error) error {
case "cluster_traffic":
vv, ok := mv.(string)
if !ok {
return &configErr{tk, fmt.Sprintf("Expected either 'system' or 'account' string value for %q, got %v", mk, mv)}
return &configErr{tk, fmt.Sprintf("Expected either 'system' or 'owner' string value for %q, got %v", mk, mv)}
}
switch vv {
case "system", _EMPTY_:
+16 -13
View File
@@ -1,4 +1,4 @@
// Copyright 2020-2025 The NATS Authors
// Copyright 2020-2026 The NATS 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
@@ -83,6 +83,7 @@ type RaftNode interface {
Stop()
WaitForStop()
Delete()
IsDeleted() bool
RecreateInternalSubs() error
IsSystemAccount() bool
GetTrafficAccountName() string
@@ -231,6 +232,7 @@ type raft struct {
initializing bool // The node is new, and "empty log" checks can be temporarily relaxed.
scaleUp bool // The node is part of a scale up, puts us in observer mode until the log contains data.
membChanging bool // There is a membership change proposal in progress
deleted bool // If the node was deleted.
}
type proposedEntry struct {
@@ -1735,11 +1737,6 @@ func (n *raft) StepDown(preferred ...string) error {
}
}
}
// Clear our vote state.
n.vote = noVote
n.writeTermVote()
n.Unlock()
if len(preferred) > 0 && maybeLeader == noLeader {
@@ -1920,6 +1917,7 @@ func (n *raft) Delete() {
n.Lock()
defer n.Unlock()
n.deleted = true
if wal := n.wal; wal != nil {
wal.Delete(false)
}
@@ -1927,6 +1925,12 @@ func (n *raft) Delete() {
n.debug("Deleted")
}
func (n *raft) IsDeleted() bool {
n.RLock()
defer n.RUnlock()
return n.deleted
}
func (n *raft) shutdown() {
// First call to Stop or Delete should close the quit chan
// to notify the runAs goroutines to stop what they're doing.
@@ -3401,9 +3405,9 @@ func (n *raft) runAsCandidate() {
n.requestVote()
// We vote for ourselves.
votes := map[string]struct{}{
n.ID(): {},
}
n.votes.push(&voteResponse{term: n.term, peer: n.ID(), granted: true})
votes := map[string]struct{}{}
emptyVotes := map[string]struct{}{}
for n.State() == Candidate {
@@ -3968,10 +3972,6 @@ CONTINUE:
// Here we can become a leader but need to wait for resume of the apply queue.
n.lxfer = true
}
} else if n.vote != noVote {
// Since we are here we are not the chosen one but we should clear any vote preference.
n.vote = noVote
n.writeTermVote()
}
}
case EntryAddPeer:
@@ -4211,6 +4211,9 @@ func (n *raft) sendAppendEntryLocked(entries []*Entry, checkLeader bool) error {
if !shouldStore {
ae.returnToPool()
}
if n.csz == 1 {
n.tryCommit(n.pindex)
}
return nil
}
+54 -33
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2025 The NATS Authors
// Copyright 2019-2026 The NATS 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
@@ -425,6 +425,7 @@ type stream struct {
active bool // Indicates that there are active internal subscriptions (for the subject filters)
// and/or mirror/sources consumers are scheduled to be established or already started.
closed atomic.Bool // Set to true when stop() is called on the stream.
cisrun atomic.Bool // Indicates one checkInterestState is already running.
// Mirror
mirror *sourceInfo
@@ -837,9 +838,9 @@ func (a *Account) addStreamWithAssignment(config *StreamConfig, fsConfig *FileSt
// Add created timestamp used for the store, must match that of the stream assignment if it exists.
if sa != nil {
js.mu.RLock()
// The following assignment does not require mutex
// protection: sa.Created is immutable.
mset.created = sa.Created
js.mu.RUnlock()
}
// Start our signaling routine to process consumers.
@@ -1821,7 +1822,7 @@ func (s *Server) checkStreamCfg(config *StreamConfig, acc *Account, pedantic boo
// check for duplicates
var iNames = make(map[string]struct{})
for _, src := range cfg.Sources {
if !isValidName(src.Name) {
if src == nil || !isValidName(src.Name) {
return StreamConfig{}, NewJSSourceInvalidStreamNameError()
}
if _, ok := iNames[src.composeIName()]; !ok {
@@ -3162,7 +3163,6 @@ func (mset *stream) setupMirrorConsumer() error {
}
mirror := mset.mirror
mirrorWg := &mirror.wg
// We want to throttle here in terms of how fast we request new consumers,
// or if the previous is still in progress.
@@ -3321,7 +3321,16 @@ func (mset *stream) setupMirrorConsumer() error {
// Wait for previous processMirrorMsgs go routine to be completely done.
// If none is running, this will not block.
mirrorWg.Wait()
mset.mu.Lock()
if mset.mirror == nil {
// Mirror config has been removed.
mset.mu.Unlock()
return
} else {
wg := &mset.mirror.wg
mset.mu.Unlock()
wg.Wait()
}
select {
case ccr := <-respCh:
@@ -6045,13 +6054,6 @@ func (mset *stream) processJetStreamMsg(subject, reply string, hdr, msg []byte,
return nil
}
// If here we will attempt to store the message.
// Assume this will succeed.
olmsgId := mset.lmsgId
mset.lmsgId = msgId
mset.lseq++
tierName := mset.tier
// Republish state if needed.
var tsubj string
var tlseq uint64
@@ -6075,7 +6077,7 @@ func (mset *stream) processJetStreamMsg(subject, reply string, hdr, msg []byte,
// If clustered this was already checked and we do not want to check here and possibly introduce skew.
// Don't error and log if we're tracing when clustered.
if !isClustered {
if exceeded, err := jsa.wouldExceedLimits(stype, tierName, mset.cfg.Replicas, subject, hdr, msg); exceeded {
if exceeded, err := jsa.wouldExceedLimits(stype, mset.tier, mset.cfg.Replicas, subject, hdr, msg); exceeded {
if err == nil {
err = NewJSAccountResourcesExceededError()
}
@@ -6134,11 +6136,7 @@ func (mset *stream) processJetStreamMsg(subject, reply string, hdr, msg []byte,
mset.srv.Warnf("Filesystem permission denied while writing msg, disabling JetStream: %v", err)
return err
}
// If we did not succeed put those values back and increment clfs in case we are clustered.
var state StreamState
mset.store.FastState(&state)
mset.lseq = state.LastSeq
mset.lmsgId = olmsgId
// If we did not succeed increment clfs in case we are clustered.
bumpCLFS()
switch err {
@@ -6159,6 +6157,8 @@ func (mset *stream) processJetStreamMsg(subject, reply string, hdr, msg []byte,
}
// If here we succeeded in storing the message.
mset.lmsgId = msgId
mset.lseq = seq
// If we have a msgId make sure to save.
// This will replace our estimate from the cluster layer if we are clustered.
@@ -7298,11 +7298,30 @@ func (mset *stream) checkInterestState() {
return
}
// Ensure only one of these runs at the same time.
if !mset.cisrun.CompareAndSwap(false, true) {
return
}
defer mset.cisrun.Store(false)
var ss StreamState
mset.store.FastState(&ss)
asflr := uint64(math.MaxUint64)
for _, o := range mset.getConsumers() {
o.checkStateForInterestStream(&ss)
o.mu.RLock()
chkflr := o.chkflr
o.mu.RUnlock()
asflr = min(asflr, chkflr)
}
mset.cfgMu.RLock()
rp := mset.cfg.Retention
mset.cfgMu.RUnlock()
// Remove as many messages from the "head" of the stream if there's no interest anymore.
if rp == InterestPolicy && asflr != math.MaxUint64 {
mset.store.Compact(asflr)
}
}
@@ -7389,20 +7408,18 @@ func (mset *stream) swapSigSubs(o *consumer, newFilters []string) {
o.sigSubs = nil
}
if o.isLeader() {
if mset.csl == nil {
mset.csl = gsl.NewSublist[*consumer]()
}
// If no filters are preset, add fwcs to sublist for that consumer.
if newFilters == nil {
mset.csl.Insert(fwcs, o)
o.sigSubs = append(o.sigSubs, fwcs)
// If there are filters, add their subjects to sublist.
} else {
for _, filter := range newFilters {
mset.csl.Insert(filter, o)
o.sigSubs = append(o.sigSubs, filter)
}
if mset.csl == nil {
mset.csl = gsl.NewSublist[*consumer]()
}
// If no filters are present, add fwcs to sublist for that consumer.
if newFilters == nil {
mset.csl.Insert(fwcs, o)
o.sigSubs = append(o.sigSubs, fwcs)
} else {
// If there are filters, add their subjects to sublist.
for _, filter := range newFilters {
mset.csl.Insert(filter, o)
o.sigSubs = append(o.sigSubs, filter)
}
}
o.mu.Unlock()
@@ -7479,14 +7496,18 @@ func (mset *stream) partitionUnique(name string, partitions []string) bool {
if n == name {
continue
}
o.mu.RLock()
if o.subjf == nil {
o.mu.RUnlock()
return false
}
for _, filter := range o.subjf {
if SubjectsCollide(partition, filter.subject) {
o.mu.RUnlock()
return false
}
}
o.mu.RUnlock()
}
}
return true
+60
View File
@@ -16,6 +16,9 @@ package stree
import (
"bytes"
"slices"
"unsafe"
"github.com/nats-io/nats-server/v2/server/gsl"
)
// SubjectTree is an adaptive radix trie (ART) for storing subject information on literal subjects.
@@ -448,3 +451,60 @@ func LazyIntersect[TL, TR any](tl *SubjectTree[TL], tr *SubjectTree[TR], cb func
})
}
}
// IntersectGSL will match all items in the given subject tree that
// have interest expressed in the given sublist. The callback will only be called
// once for each subject, regardless of overlapping subscriptions in the sublist.
func IntersectGSL[T any, SL comparable](t *SubjectTree[T], sl *gsl.GenericSublist[SL], cb func(subject []byte, val *T)) {
if t == nil || t.root == nil || sl == nil {
return
}
var _pre [256]byte
_intersectGSL(t.root, _pre[:0], sl, cb)
}
func _intersectGSL[T any, SL comparable](n node, pre []byte, sl *gsl.GenericSublist[SL], cb func(subject []byte, val *T)) {
if n.isLeaf() {
ln := n.(*leaf[T])
subj := append(pre, ln.suffix...)
if sl.HasInterest(bytesToString(subj)) {
cb(subj, &ln.value)
}
return
}
bn := n.base()
pre = append(pre, bn.prefix...)
for _, cn := range n.children() {
if cn == nil {
continue
}
subj := append(pre, cn.path()...)
if !hasInterestForTokens(sl, subj, len(pre)) {
continue
}
_intersectGSL(cn, pre, sl, cb)
}
}
// The subject tree can return partial tokens so we need to check starting interest
// only from whole tokens when we encounter a tsep.
func hasInterestForTokens[SL comparable](sl *gsl.GenericSublist[SL], subj []byte, since int) bool {
for i := since; i < len(subj); i++ {
if subj[i] == tsep {
if !sl.HasInterestStartingIn(bytesToString(subj[:i])) {
return false
}
}
}
return true
}
// Note this will avoid a copy of the data used for the string, but it will also reference the existing slice's data pointer.
// So this should be used sparingly when we know the encompassing byte slice's lifetime is the same.
func bytesToString(b []byte) string {
if len(b) == 0 {
return ""
}
p := unsafe.SliceData(b)
return unsafe.String(p, len(b))
}
+3 -63
View File
@@ -21,8 +21,6 @@ import (
"sync"
"sync/atomic"
"unicode/utf8"
"github.com/nats-io/nats-server/v2/server/stree"
)
// Sublist is a routing mechanism to handle subject distribution and
@@ -818,7 +816,9 @@ func matchLevelForAny(l *level, toks []string, np, nq *int) bool {
*nq += len(qsub)
}
}
return len(n.plist) > 0 || len(n.psubs) > 0 || len(n.qsubs) > 0
if len(n.plist) > 0 || len(n.psubs) > 0 || len(n.qsubs) > 0 {
return true
}
}
if pwc != nil {
if np != nil && nq != nil {
@@ -1726,63 +1726,3 @@ func getAllNodes(l *level, results *SublistResult) {
getAllNodes(n.next, results)
}
}
// IntersectStree will match all items in the given subject tree that
// have interest expressed in the given sublist. The callback will only be called
// once for each subject, regardless of overlapping subscriptions in the sublist.
func IntersectStree[T any](st *stree.SubjectTree[T], sl *Sublist, cb func(subj []byte, entry *T)) {
var _subj [255]byte
intersectStree(st, sl.root, _subj[:0], cb)
}
func intersectStree[T any](st *stree.SubjectTree[T], r *level, subj []byte, cb func(subj []byte, entry *T)) {
nsubj := subj
if len(nsubj) > 0 {
nsubj = append(subj, '.')
}
if r.fwc != nil {
// We've reached a full wildcard, do a FWC match on the stree at this point
// and don't keep iterating downward.
nsubj := append(nsubj, '>')
st.Match(nsubj, cb)
return
}
if r.pwc != nil {
// We've found a partial wildcard. We'll keep iterating downwards, but first
// check whether there's interest at this level (without triggering dupes) and
// match if so.
var done bool
nsubj := append(nsubj, '*')
if len(r.pwc.psubs)+len(r.pwc.qsubs) > 0 {
st.Match(nsubj, cb)
done = true
}
if r.pwc.next.numNodes() > 0 {
intersectStree(st, r.pwc.next, nsubj, cb)
}
if done {
return
}
}
// Normal node with subject literals, keep iterating.
for t, n := range r.nodes {
if r.pwc != nil && r.pwc.next.numNodes() > 0 && n.next.numNodes() > 0 {
// A wildcard at the next level will already visit these descendents
// so skip so we don't callback the same subject more than once.
continue
}
nsubj := append(nsubj, t...)
if len(n.psubs)+len(n.qsubs) > 0 {
if subjectHasWildcard(bytesToString(nsubj)) {
st.Match(nsubj, cb)
} else {
if e, ok := st.Find(nsubj); ok {
cb(nsubj, e)
}
}
}
if n.next.numNodes() > 0 {
intersectStree(st, n.next, nsubj, cb)
}
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2012-2024 The NATS Authors
// Copyright 2012-2025 The NATS 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
+18
View File
@@ -1,3 +1,21 @@
## 2.28.0
Ginkgo's SemVer filter now supports filtering multiple components by SemVer version:
```go
It("should work in a specific version range (1.0.0, 2.0.0) and third-party dependency redis in [8.0.0, ~)", SemVerConstraint(">= 3.2.0"), ComponentSemVerConstraint("redis", ">= 8.0.0") func() {
// This test will only run when version is between 1.0.0 (exclusive) and 2.0.0 (exclusive) and redis version is >= 8.0.0
})
```
can be filtered in or out with an invocation like:
```bash
ginkgo --sem-ver-filter="2.1.1, redis=8.2.0"
```
Huge thanks to @Icarus9913 for working on this!
## 2.27.5
### Fixes
+13 -6
View File
@@ -20,6 +20,7 @@ import (
"io"
"os"
"path/filepath"
"slices"
"strings"
"github.com/go-logr/logr"
@@ -268,7 +269,7 @@ func RunSpecs(t GinkgoTestingT, description string, args ...any) bool {
}
defer global.PopClone()
suiteLabels, suiteSemVerConstraints, suiteAroundNodes := extractSuiteConfiguration(args)
suiteLabels, suiteSemVerConstraints, suiteComponentSemVerConstraints, suiteAroundNodes := extractSuiteConfiguration(args)
var reporter reporters.Reporter
if suiteConfig.ParallelTotal == 1 {
@@ -311,7 +312,7 @@ func RunSpecs(t GinkgoTestingT, description string, args ...any) bool {
suitePath, err = filepath.Abs(suitePath)
exitIfErr(err)
passed, hasFocusedTests := global.Suite.Run(description, suiteLabels, suiteSemVerConstraints, suiteAroundNodes, suitePath, global.Failer, reporter, writer, outputInterceptor, interrupt_handler.NewInterruptHandler(client), client, internal.RegisterForProgressSignal, suiteConfig)
passed, hasFocusedTests := global.Suite.Run(description, suiteLabels, suiteSemVerConstraints, suiteComponentSemVerConstraints, suiteAroundNodes, suitePath, global.Failer, reporter, writer, outputInterceptor, interrupt_handler.NewInterruptHandler(client), client, internal.RegisterForProgressSignal, suiteConfig)
outputInterceptor.Shutdown()
flagSet.ValidateDeprecations(deprecationTracker)
@@ -330,9 +331,10 @@ func RunSpecs(t GinkgoTestingT, description string, args ...any) bool {
return passed
}
func extractSuiteConfiguration(args []any) (Labels, SemVerConstraints, types.AroundNodes) {
func extractSuiteConfiguration(args []any) (Labels, SemVerConstraints, ComponentSemVerConstraints, types.AroundNodes) {
suiteLabels := Labels{}
suiteSemVerConstraints := SemVerConstraints{}
suiteComponentSemVerConstraints := ComponentSemVerConstraints{}
aroundNodes := types.AroundNodes{}
configErrors := []error{}
for _, arg := range args {
@@ -345,6 +347,11 @@ func extractSuiteConfiguration(args []any) (Labels, SemVerConstraints, types.Aro
suiteLabels = append(suiteLabels, arg...)
case SemVerConstraints:
suiteSemVerConstraints = append(suiteSemVerConstraints, arg...)
case ComponentSemVerConstraints:
for component, constraints := range arg {
suiteComponentSemVerConstraints[component] = append(suiteComponentSemVerConstraints[component], constraints...)
suiteComponentSemVerConstraints[component] = slices.Compact(suiteComponentSemVerConstraints[component])
}
case types.AroundNodeDecorator:
aroundNodes = append(aroundNodes, arg)
default:
@@ -362,7 +369,7 @@ func extractSuiteConfiguration(args []any) (Labels, SemVerConstraints, types.Aro
os.Exit(1)
}
return suiteLabels, suiteSemVerConstraints, aroundNodes
return suiteLabels, suiteSemVerConstraints, suiteComponentSemVerConstraints, aroundNodes
}
func getwd() (string, error) {
@@ -385,7 +392,7 @@ func PreviewSpecs(description string, args ...any) Report {
}
defer global.PopClone()
suiteLabels, suiteSemVerConstraints, suiteAroundNodes := extractSuiteConfiguration(args)
suiteLabels, suiteSemVerConstraints, suiteComponentSemVerConstraints, suiteAroundNodes := extractSuiteConfiguration(args)
priorDryRun, priorParallelTotal, priorParallelProcess := suiteConfig.DryRun, suiteConfig.ParallelTotal, suiteConfig.ParallelProcess
suiteConfig.DryRun, suiteConfig.ParallelTotal, suiteConfig.ParallelProcess = true, 1, 1
defer func() {
@@ -403,7 +410,7 @@ func PreviewSpecs(description string, args ...any) Report {
suitePath, err = filepath.Abs(suitePath)
exitIfErr(err)
global.Suite.Run(description, suiteLabels, suiteSemVerConstraints, suiteAroundNodes, suitePath, global.Failer, reporter, writer, outputInterceptor, interrupt_handler.NewInterruptHandler(client), client, internal.RegisterForProgressSignal, suiteConfig)
global.Suite.Run(description, suiteLabels, suiteSemVerConstraints, suiteComponentSemVerConstraints, suiteAroundNodes, suitePath, global.Failer, reporter, writer, outputInterceptor, interrupt_handler.NewInterruptHandler(client), client, internal.RegisterForProgressSignal, suiteConfig)
return global.Suite.GetPreviewReport()
}
+21
View File
@@ -117,6 +117,27 @@ You can learn more here: https://onsi.github.io/ginkgo/#spec-semantic-version-fi
*/
type SemVerConstraints = internal.SemVerConstraints
/*
ComponentSemVerConstraint decorates specs with ComponentSemVerConstraints. Multiple components semantic version constraints can be passed to ComponentSemVerConstraint and the component can't be empy, also the version strings must follow the semantic version constraint rules.
ComponentSemVerConstraints can be applied to container and subject nodes, but not setup nodes. You can provide multiple ComponentSemVerConstraints to a given node and a spec's component semantic version constraints is the union of all component semantic version constraints in its node hierarchy.
You can learn more here: https://onsi.github.io/ginkgo/#spec-semantic-version-filtering
You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorator-reference
*/
func ComponentSemVerConstraint(component string, semVerConstraints ...string) ComponentSemVerConstraints {
componentSemVerConstraints := ComponentSemVerConstraints{
component: semVerConstraints,
}
return componentSemVerConstraints
}
/*
ComponentSemVerConstraints are the type for spec ComponentSemVerConstraint decorators. Use ComponentSemVerConstraint(...) to construct ComponentSemVerConstraints.
You can learn more here: https://onsi.github.io/ginkgo/#spec-semantic-version-filtering
*/
type ComponentSemVerConstraints = internal.ComponentSemVerConstraints
/*
PollProgressAfter allows you to override the configured value for --poll-progress-after for a particular node.
+1 -1
View File
@@ -33,7 +33,7 @@ func BuildRunCommand() command.Command {
Usage: "ginkgo run <FLAGS> <PACKAGES> -- <PASS-THROUGHS>",
ShortDoc: "Run the tests in the passed in <PACKAGES> (or the package in the current directory if left blank)",
Documentation: "Any arguments after -- will be passed to the test.",
DocLink: "running-tests",
DocLink: "running-specs",
Command: func(args []string, additionalArgs []string) {
var errors []error
cliConfig, goFlagsConfig, errors = types.VetAndInitializeCLIAndGoConfig(cliConfig, goFlagsConfig)
+19 -2
View File
@@ -56,7 +56,7 @@ This function sets the `Skip` property on specs by applying Ginkgo's focus polic
*Note:* specs with pending nodes are Skipped when created by NewSpec.
*/
func ApplyFocusToSpecs(specs Specs, description string, suiteLabels Labels, suiteSemVerConstraints SemVerConstraints, suiteConfig types.SuiteConfig) (Specs, bool) {
func ApplyFocusToSpecs(specs Specs, description string, suiteLabels Labels, suiteSemVerConstraints SemVerConstraints, suiteComponentSemVerConstraints ComponentSemVerConstraints, suiteConfig types.SuiteConfig) (Specs, bool) {
focusString := strings.Join(suiteConfig.FocusStrings, "|")
skipString := strings.Join(suiteConfig.SkipStrings, "|")
@@ -87,7 +87,24 @@ func ApplyFocusToSpecs(specs Specs, description string, suiteLabels Labels, suit
if suiteConfig.SemVerFilter != "" {
semVerFilter, _ := types.ParseSemVerFilter(suiteConfig.SemVerFilter)
skipChecks = append(skipChecks, func(spec Spec) bool {
return !semVerFilter(UnionOfSemVerConstraints(suiteSemVerConstraints, spec.Nodes.UnionOfSemVerConstraints()))
noRun := false
// non-component-specific constraints
constraints := UnionOfSemVerConstraints(suiteSemVerConstraints, spec.Nodes.UnionOfSemVerConstraints())
if len(constraints) != 0 && semVerFilter("", constraints) == false {
noRun = true
}
// component-specific constraints
componentConstraints := UnionOfComponentSemVerConstraints(suiteComponentSemVerConstraints, spec.Nodes.UnionOfComponentSemVerConstraints())
for component, constraints := range componentConstraints {
if semVerFilter(component, constraints) == false {
noRun = true
break
}
}
return noRun
})
}
+19 -16
View File
@@ -113,22 +113,24 @@ func newGroup(suite *Suite) *group {
// initialReportForSpec constructs a new SpecReport right before running the spec.
func (g *group) initialReportForSpec(spec Spec) types.SpecReport {
return types.SpecReport{
ContainerHierarchyTexts: spec.Nodes.WithType(types.NodeTypeContainer).Texts(),
ContainerHierarchyLocations: spec.Nodes.WithType(types.NodeTypeContainer).CodeLocations(),
ContainerHierarchyLabels: spec.Nodes.WithType(types.NodeTypeContainer).Labels(),
ContainerHierarchySemVerConstraints: spec.Nodes.WithType(types.NodeTypeContainer).SemVerConstraints(),
LeafNodeLocation: spec.FirstNodeWithType(types.NodeTypeIt).CodeLocation,
LeafNodeType: types.NodeTypeIt,
LeafNodeText: spec.FirstNodeWithType(types.NodeTypeIt).Text,
LeafNodeLabels: []string(spec.FirstNodeWithType(types.NodeTypeIt).Labels),
LeafNodeSemVerConstraints: []string(spec.FirstNodeWithType(types.NodeTypeIt).SemVerConstraints),
ParallelProcess: g.suite.config.ParallelProcess,
RunningInParallel: g.suite.isRunningInParallel(),
IsSerial: spec.Nodes.HasNodeMarkedSerial(),
IsInOrderedContainer: !spec.Nodes.FirstNodeMarkedOrdered().IsZero(),
MaxFlakeAttempts: spec.Nodes.GetMaxFlakeAttempts(),
MaxMustPassRepeatedly: spec.Nodes.GetMaxMustPassRepeatedly(),
SpecPriority: spec.Nodes.GetSpecPriority(),
ContainerHierarchyTexts: spec.Nodes.WithType(types.NodeTypeContainer).Texts(),
ContainerHierarchyLocations: spec.Nodes.WithType(types.NodeTypeContainer).CodeLocations(),
ContainerHierarchyLabels: spec.Nodes.WithType(types.NodeTypeContainer).Labels(),
ContainerHierarchySemVerConstraints: spec.Nodes.WithType(types.NodeTypeContainer).SemVerConstraints(),
ContainerHierarchyComponentSemVerConstraints: spec.Nodes.WithType(types.NodeTypeContainer).ComponentSemVerConstraints(),
LeafNodeLocation: spec.FirstNodeWithType(types.NodeTypeIt).CodeLocation,
LeafNodeType: types.NodeTypeIt,
LeafNodeText: spec.FirstNodeWithType(types.NodeTypeIt).Text,
LeafNodeLabels: []string(spec.FirstNodeWithType(types.NodeTypeIt).Labels),
LeafNodeSemVerConstraints: []string(spec.FirstNodeWithType(types.NodeTypeIt).SemVerConstraints),
LeafNodeComponentSemVerConstraints: map[string][]string(spec.FirstNodeWithType(types.NodeTypeIt).ComponentSemVerConstraints),
ParallelProcess: g.suite.config.ParallelProcess,
RunningInParallel: g.suite.isRunningInParallel(),
IsSerial: spec.Nodes.HasNodeMarkedSerial(),
IsInOrderedContainer: !spec.Nodes.FirstNodeMarkedOrdered().IsZero(),
MaxFlakeAttempts: spec.Nodes.GetMaxFlakeAttempts(),
MaxMustPassRepeatedly: spec.Nodes.GetMaxMustPassRepeatedly(),
SpecPriority: spec.Nodes.GetSpecPriority(),
}
}
@@ -152,6 +154,7 @@ func addNodeToReportForNode(report *types.ConstructionNodeReport, node *TreeNode
report.ContainerHierarchyLocations = append(report.ContainerHierarchyLocations, node.Node.CodeLocation)
report.ContainerHierarchyLabels = append(report.ContainerHierarchyLabels, node.Node.Labels)
report.ContainerHierarchySemVerConstraints = append(report.ContainerHierarchySemVerConstraints, node.Node.SemVerConstraints)
report.ContainerHierarchyComponentSemVerConstraints = append(report.ContainerHierarchyComponentSemVerConstraints, node.Node.ComponentSemVerConstraints)
if node.Node.MarkedSerial {
report.IsSerial = true
}
+100 -11
View File
@@ -57,6 +57,7 @@ type Node struct {
MustPassRepeatedly int
Labels Labels
SemVerConstraints SemVerConstraints
ComponentSemVerConstraints ComponentSemVerConstraints
PollProgressAfter time.Duration
PollProgressInterval time.Duration
NodeTimeout time.Duration
@@ -106,7 +107,24 @@ func (l Labels) MatchesLabelFilter(query string) bool {
type SemVerConstraints []string
func (svc SemVerConstraints) MatchesSemVerFilter(version string) bool {
return types.MustParseSemVerFilter(version)(svc)
return types.MustParseSemVerFilter(version)("", svc)
}
type ComponentSemVerConstraints map[string][]string
func (csvc ComponentSemVerConstraints) MatchesSemVerFilter(component, version string) bool {
for comp, constraints := range csvc {
if comp != component {
continue
}
input := version
if len(component) > 0 {
input = fmt.Sprintf("%s=%s", component, version)
}
return types.MustParseSemVerFilter(input)(component, constraints)
}
return false
}
func unionOf[S ~[]E, E comparable](slices ...S) S {
@@ -131,6 +149,16 @@ func UnionOfSemVerConstraints(semVerConstraints ...SemVerConstraints) SemVerCons
return unionOf(semVerConstraints...)
}
func UnionOfComponentSemVerConstraints(componentSemVerConstraintsSlice ...ComponentSemVerConstraints) ComponentSemVerConstraints {
unionComponentSemVerConstraints := ComponentSemVerConstraints{}
for _, componentSemVerConstraints := range componentSemVerConstraintsSlice {
for component, constraints := range componentSemVerConstraints {
unionComponentSemVerConstraints[component] = unionOf(unionComponentSemVerConstraints[component], constraints)
}
}
return unionComponentSemVerConstraints
}
func PartitionDecorations(args ...any) ([]any, []any) {
decorations := []any{}
remainingArgs := []any{}
@@ -174,6 +202,8 @@ func isDecoration(arg any) bool {
return true
case t == reflect.TypeOf(SemVerConstraints{}):
return true
case t == reflect.TypeOf(ComponentSemVerConstraints{}):
return true
case t == reflect.TypeOf(PollProgressInterval(0)):
return true
case t == reflect.TypeOf(PollProgressAfter(0)):
@@ -214,16 +244,17 @@ var specContextType = reflect.TypeOf(new(SpecContext)).Elem()
func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeType, text string, args ...any) (Node, []error) {
baseOffset := 2
node := Node{
ID: UniqueNodeID(),
NodeType: nodeType,
Text: text,
Labels: Labels{},
SemVerConstraints: SemVerConstraints{},
CodeLocation: types.NewCodeLocation(baseOffset),
NestingLevel: -1,
PollProgressAfter: -1,
PollProgressInterval: -1,
GracePeriod: -1,
ID: UniqueNodeID(),
NodeType: nodeType,
Text: text,
Labels: Labels{},
SemVerConstraints: SemVerConstraints{},
ComponentSemVerConstraints: ComponentSemVerConstraints{},
CodeLocation: types.NewCodeLocation(baseOffset),
NestingLevel: -1,
PollProgressAfter: -1,
PollProgressInterval: -1,
GracePeriod: -1,
}
errors := []error{}
@@ -360,6 +391,36 @@ func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeTy
appendError(err)
}
}
case t == reflect.TypeOf(ComponentSemVerConstraints{}):
if !nodeType.Is(types.NodeTypesForContainerAndIt) {
appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "ComponentSemVerConstraint"))
}
for component, semVerConstraints := range arg.(ComponentSemVerConstraints) {
// while using ComponentSemVerConstraints, we should not allow empty component names.
// you should use SemVerConstraints for that.
hasErr := false
if len(component) == 0 {
appendError(types.GinkgoErrors.InvalidEmptyComponentForSemVerConstraint(node.CodeLocation))
hasErr = true
}
for _, semVerConstraint := range semVerConstraints {
_, err := types.ValidateAndCleanupSemVerConstraint(semVerConstraint, node.CodeLocation)
if err != nil {
appendError(err)
hasErr = true
}
}
if !hasErr {
// merge constraints if the component already exists
constraints := slices.Clone(semVerConstraints)
if existingConstraints, exists := node.ComponentSemVerConstraints[component]; exists {
constraints = UnionOfSemVerConstraints([]string(existingConstraints), constraints)
}
node.ComponentSemVerConstraints[component] = slices.Clone(constraints)
}
}
case t.Kind() == reflect.Func:
if nodeType.Is(types.NodeTypeContainer) {
if node.Body != nil {
@@ -899,6 +960,34 @@ func (n Nodes) UnionOfSemVerConstraints() []string {
return out
}
func (n Nodes) ComponentSemVerConstraints() []map[string][]string {
out := make([]map[string][]string, len(n))
for i := range n {
if n[i].ComponentSemVerConstraints == nil {
out[i] = map[string][]string{}
} else {
out[i] = map[string][]string(n[i].ComponentSemVerConstraints)
}
}
return out
}
func (n Nodes) UnionOfComponentSemVerConstraints() map[string][]string {
out := map[string][]string{}
seen := map[string]bool{}
for i := range n {
for component := range n[i].ComponentSemVerConstraints {
if !seen[component] {
seen[component] = true
out[component] = n[i].ComponentSemVerConstraints[component]
} else {
out[component] = UnionOfSemVerConstraints(out[component], n[i].ComponentSemVerConstraints[component])
}
}
}
return out
}
func (n Nodes) CodeLocations() []types.CodeLocation {
out := make([]types.CodeLocation, len(n))
for i := range n {
+30 -17
View File
@@ -83,7 +83,7 @@ func goJSONActionFromSpecState(state types.SpecState) GoJSONAction {
type gojsonReport struct {
o types.Report
// Extra calculated fields
goPkg string
goPkg string
elapsed float64
}
@@ -109,8 +109,8 @@ type gojsonSpecReport struct {
o types.SpecReport
// extra calculated fields
testName string
elapsed float64
action GoJSONAction
elapsed float64
action GoJSONAction
}
func newSpecReport(in types.SpecReport) *gojsonSpecReport {
@@ -141,18 +141,31 @@ func suitePathToPkg(dir string) (string, error) {
}
func createTestName(spec types.SpecReport) string {
name := fmt.Sprintf("[%s]", spec.LeafNodeType)
if spec.FullText() != "" {
name = name + " " + spec.FullText()
}
labels := spec.Labels()
if len(labels) > 0 {
name = name + " [" + strings.Join(labels, ", ") + "]"
}
semVerConstraints := spec.SemVerConstraints()
if len(semVerConstraints) > 0 {
name = name + " [" + strings.Join(semVerConstraints, ", ") + "]"
}
name = strings.TrimSpace(name)
return name
name := fmt.Sprintf("[%s]", spec.LeafNodeType)
if spec.FullText() != "" {
name = name + " " + spec.FullText()
}
labels := spec.Labels()
if len(labels) > 0 {
name = name + " [" + strings.Join(labels, ", ") + "]"
}
semVerConstraints := spec.SemVerConstraints()
if len(semVerConstraints) > 0 {
name = name + " [" + strings.Join(semVerConstraints, ", ") + "]"
}
componentSemVerConstraints := spec.ComponentSemVerConstraints()
if len(componentSemVerConstraints) > 0 {
name = name + " [" + formatComponentSemVerConstraintsToString(componentSemVerConstraints) + "]"
}
name = strings.TrimSpace(name)
return name
}
func formatComponentSemVerConstraintsToString(componentSemVerConstraints map[string][]string) string {
var tmpStr string
for component, semVerConstraints := range componentSemVerConstraints {
tmpStr = tmpStr + fmt.Sprintf("%s: %s, ", component, semVerConstraints)
}
tmpStr = strings.TrimSuffix(tmpStr, ", ")
return tmpStr
}
+11 -10
View File
@@ -108,13 +108,13 @@ func (suite *Suite) BuildTree() error {
return nil
}
func (suite *Suite) Run(description string, suiteLabels Labels, suiteSemVerConstraints SemVerConstraints, suiteAroundNodes types.AroundNodes, suitePath string, failer *Failer, reporter reporters.Reporter, writer WriterInterface, outputInterceptor OutputInterceptor, interruptHandler interrupt_handler.InterruptHandlerInterface, client parallel_support.Client, progressSignalRegistrar ProgressSignalRegistrar, suiteConfig types.SuiteConfig) (bool, bool) {
func (suite *Suite) Run(description string, suiteLabels Labels, suiteSemVerConstraints SemVerConstraints, suiteComponentSemVerConstraints ComponentSemVerConstraints, suiteAroundNodes types.AroundNodes, suitePath string, failer *Failer, reporter reporters.Reporter, writer WriterInterface, outputInterceptor OutputInterceptor, interruptHandler interrupt_handler.InterruptHandlerInterface, client parallel_support.Client, progressSignalRegistrar ProgressSignalRegistrar, suiteConfig types.SuiteConfig) (bool, bool) {
if suite.phase != PhaseBuildTree {
panic("cannot run before building the tree = call suite.BuildTree() first")
}
ApplyNestedFocusPolicyToTree(suite.tree)
specs := GenerateSpecsFromTreeRoot(suite.tree)
specs, hasProgrammaticFocus := ApplyFocusToSpecs(specs, description, suiteLabels, suiteSemVerConstraints, suiteConfig)
specs, hasProgrammaticFocus := ApplyFocusToSpecs(specs, description, suiteLabels, suiteSemVerConstraints, suiteComponentSemVerConstraints, suiteConfig)
specs = ComputeAroundNodes(specs)
suite.phase = PhaseRun
@@ -133,7 +133,7 @@ func (suite *Suite) Run(description string, suiteLabels Labels, suiteSemVerConst
cancelProgressHandler := progressSignalRegistrar(suite.handleProgressSignal)
success := suite.runSpecs(description, suiteLabels, suiteSemVerConstraints, suitePath, hasProgrammaticFocus, specs)
success := suite.runSpecs(description, suiteLabels, suiteSemVerConstraints, suiteComponentSemVerConstraints, suitePath, hasProgrammaticFocus, specs)
cancelProgressHandler()
@@ -456,16 +456,17 @@ func (suite *Suite) processCurrentSpecReport() {
}
}
func (suite *Suite) runSpecs(description string, suiteLabels Labels, suiteSemVerConstraints SemVerConstraints, suitePath string, hasProgrammaticFocus bool, specs Specs) bool {
func (suite *Suite) runSpecs(description string, suiteLabels Labels, suiteSemVerConstraints SemVerConstraints, suiteComponentSemVerConstraints ComponentSemVerConstraints, suitePath string, hasProgrammaticFocus bool, specs Specs) bool {
numSpecsThatWillBeRun := specs.CountWithoutSkip()
suite.report = types.Report{
SuitePath: suitePath,
SuiteDescription: description,
SuiteLabels: suiteLabels,
SuiteSemVerConstraints: suiteSemVerConstraints,
SuiteConfig: suite.config,
SuiteHasProgrammaticFocus: hasProgrammaticFocus,
SuitePath: suitePath,
SuiteDescription: description,
SuiteLabels: suiteLabels,
SuiteSemVerConstraints: suiteSemVerConstraints,
SuiteComponentSemVerConstraints: suiteComponentSemVerConstraints,
SuiteConfig: suite.config,
SuiteHasProgrammaticFocus: hasProgrammaticFocus,
PreRunStats: types.PreRunStats{
TotalSpecs: len(specs),
SpecsThatWillRun: numSpecsThatWillBeRun,
+25 -2
View File
@@ -75,6 +75,9 @@ func (r *DefaultReporter) SuiteWillBegin(report types.Report) {
if len(report.SuiteSemVerConstraints) > 0 {
r.emit(r.f("{{coral}}[%s]{{/}} ", strings.Join(report.SuiteSemVerConstraints, ", ")))
}
if len(report.SuiteComponentSemVerConstraints) > 0 {
r.emit(r.f("{{coral}}[Components: %s]{{/}} ", formatComponentSemVerConstraintsToString(report.SuiteComponentSemVerConstraints)))
}
r.emit(r.f("- %d/%d specs ", report.PreRunStats.SpecsThatWillRun, report.PreRunStats.TotalSpecs))
if report.SuiteConfig.ParallelTotal > 1 {
r.emit(r.f("- %d procs ", report.SuiteConfig.ParallelTotal))
@@ -97,6 +100,13 @@ func (r *DefaultReporter) SuiteWillBegin(report types.Report) {
bannerWidth = len(semVerConstraints) + 2
}
}
if len(report.SuiteComponentSemVerConstraints) > 0 {
componentSemVerConstraints := formatComponentSemVerConstraintsToString(report.SuiteComponentSemVerConstraints)
r.emitBlock(r.f("{{coral}}[Components: %s]{{/}} ", componentSemVerConstraints))
if len(componentSemVerConstraints)+2 > bannerWidth {
bannerWidth = len(componentSemVerConstraints) + 2
}
}
r.emitBlock(strings.Repeat("=", bannerWidth))
out := r.f("Random Seed: {{bold}}%d{{/}}", report.SuiteConfig.RandomSeed)
@@ -725,8 +735,12 @@ func (r *DefaultReporter) cycleJoin(elements []string, joiner string) string {
}
func (r *DefaultReporter) codeLocationBlock(report types.SpecReport, highlightColor string, veryVerbose bool, usePreciseFailureLocation bool) string {
texts, locations, labels, semVerConstraints := []string{}, []types.CodeLocation{}, [][]string{}, [][]string{}
texts, locations, labels, semVerConstraints = append(texts, report.ContainerHierarchyTexts...), append(locations, report.ContainerHierarchyLocations...), append(labels, report.ContainerHierarchyLabels...), append(semVerConstraints, report.ContainerHierarchySemVerConstraints...)
texts, locations, labels, semVerConstraints, componentSemVerConstraints := []string{}, []types.CodeLocation{}, [][]string{}, [][]string{}, []map[string][]string{}
texts = append(texts, report.ContainerHierarchyTexts...)
locations = append(locations, report.ContainerHierarchyLocations...)
labels = append(labels, report.ContainerHierarchyLabels...)
semVerConstraints = append(semVerConstraints, report.ContainerHierarchySemVerConstraints...)
componentSemVerConstraints = append(componentSemVerConstraints, report.ContainerHierarchyComponentSemVerConstraints...)
if report.LeafNodeType.Is(types.NodeTypesForSuiteLevelNodes) {
texts = append(texts, r.f("[%s] %s", report.LeafNodeType, report.LeafNodeText))
@@ -735,6 +749,7 @@ func (r *DefaultReporter) codeLocationBlock(report types.SpecReport, highlightCo
}
labels = append(labels, report.LeafNodeLabels)
semVerConstraints = append(semVerConstraints, report.LeafNodeSemVerConstraints)
componentSemVerConstraints = append(componentSemVerConstraints, report.LeafNodeComponentSemVerConstraints)
locations = append(locations, report.LeafNodeLocation)
failureLocation := report.Failure.FailureNodeLocation
@@ -749,6 +764,7 @@ func (r *DefaultReporter) codeLocationBlock(report types.SpecReport, highlightCo
locations = append([]types.CodeLocation{failureLocation}, locations...)
labels = append([][]string{{}}, labels...)
semVerConstraints = append([][]string{{}}, semVerConstraints...)
componentSemVerConstraints = append([]map[string][]string{{}}, componentSemVerConstraints...)
highlightIndex = 0
case types.FailureNodeInContainer:
i := report.Failure.FailureNodeContainerIndex
@@ -779,6 +795,9 @@ func (r *DefaultReporter) codeLocationBlock(report types.SpecReport, highlightCo
if len(semVerConstraints[i]) > 0 {
out += r.f(" {{coral}}[%s]{{/}}", strings.Join(semVerConstraints[i], ", "))
}
if len(componentSemVerConstraints[i]) > 0 {
out += r.f(" {{coral}}[%s]{{/}}", formatComponentSemVerConstraintsToString(componentSemVerConstraints[i]))
}
out += "\n"
out += r.fi(uint(i), "{{gray}}%s{{/}}\n", locations[i])
}
@@ -806,6 +825,10 @@ func (r *DefaultReporter) codeLocationBlock(report types.SpecReport, highlightCo
if len(flattenedSemVerConstraints) > 0 {
out += r.f(" {{coral}}[%s]{{/}}", strings.Join(flattenedSemVerConstraints, ", "))
}
flattenedComponentSemVerConstraints := report.ComponentSemVerConstraints()
if len(flattenedComponentSemVerConstraints) > 0 {
out += r.f(" {{coral}}[%s]{{/}}", formatComponentSemVerConstraintsToString(flattenedComponentSemVerConstraints))
}
out += "\n"
if usePreciseFailureLocation {
out += r.f("{{gray}}%s{{/}}", failureLocation)
+20
View File
@@ -13,9 +13,11 @@ package reporters
import (
"encoding/xml"
"fmt"
"maps"
"os"
"path"
"regexp"
"slices"
"strings"
"github.com/onsi/ginkgo/v2/config"
@@ -39,6 +41,9 @@ type JunitReportConfig struct {
// Enable OmitSpecSemVerConstraints to prevent semantic version constraints from appearing in the spec name
OmitSpecSemVerConstraints bool
// Enable OmitSpecComponentSemVerConstraints to prevent component semantic version constraints from appearing in the spec name
OmitSpecComponentSemVerConstraints bool
// Enable OmitLeafNodeType to prevent the spec leaf node type from appearing in the spec name
OmitLeafNodeType bool
@@ -173,6 +178,7 @@ func GenerateJUnitReportWithConfig(report types.Report, dst string, config Junit
{"SpecialSuiteFailureReason", strings.Join(report.SpecialSuiteFailureReasons, ",")},
{"SuiteLabels", fmt.Sprintf("[%s]", strings.Join(report.SuiteLabels, ","))},
{"SuiteSemVerConstraints", fmt.Sprintf("[%s]", strings.Join(report.SuiteSemVerConstraints, ","))},
{"SuiteComponentSemVerConstraints", fmt.Sprintf("[%s]", formatComponentSemVerConstraintsToString(report.SuiteComponentSemVerConstraints))},
{"RandomSeed", fmt.Sprintf("%d", report.SuiteConfig.RandomSeed)},
{"RandomizeAllSpecs", fmt.Sprintf("%t", report.SuiteConfig.RandomizeAllSpecs)},
{"LabelFilter", report.SuiteConfig.LabelFilter},
@@ -216,6 +222,10 @@ func GenerateJUnitReportWithConfig(report types.Report, dst string, config Junit
if len(semVerConstraints) > 0 && !config.OmitSpecSemVerConstraints {
name = name + " [" + strings.Join(semVerConstraints, ", ") + "]"
}
componentSemVerConstraints := spec.ComponentSemVerConstraints()
if len(componentSemVerConstraints) > 0 && !config.OmitSpecComponentSemVerConstraints {
name = name + " [" + formatComponentSemVerConstraintsToString(componentSemVerConstraints) + "]"
}
name = strings.TrimSpace(name)
test := JUnitTestCase{
@@ -387,6 +397,16 @@ func systemOutForUnstructuredReporters(spec types.SpecReport) string {
return spec.CapturedStdOutErr
}
func formatComponentSemVerConstraintsToString(componentSemVerConstraints map[string][]string) string {
var tmpStr string
for _, key := range slices.Sorted(maps.Keys(componentSemVerConstraints)) {
tmpStr = tmpStr + fmt.Sprintf("%s: %s, ", key, componentSemVerConstraints[key])
}
tmpStr = strings.TrimSuffix(tmpStr, ", ")
return tmpStr
}
// Deprecated JUnitReporter (so folks can still compile their suites)
type JUnitReporter struct{}
+8
View File
@@ -39,12 +39,16 @@ func GenerateTeamcityReport(report types.Report, dst string) error {
name := report.SuiteDescription
labels := report.SuiteLabels
semVerConstraints := report.SuiteSemVerConstraints
componentSemVerConstraints := report.SuiteComponentSemVerConstraints
if len(labels) > 0 {
name = name + " [" + strings.Join(labels, ", ") + "]"
}
if len(semVerConstraints) > 0 {
name = name + " [" + strings.Join(semVerConstraints, ", ") + "]"
}
if len(componentSemVerConstraints) > 0 {
name = name + " [" + formatComponentSemVerConstraintsToString(componentSemVerConstraints) + "]"
}
fmt.Fprintf(f, "##teamcity[testSuiteStarted name='%s']\n", tcEscape(name))
for _, spec := range report.SpecReports {
name := fmt.Sprintf("[%s]", spec.LeafNodeType)
@@ -59,6 +63,10 @@ func GenerateTeamcityReport(report types.Report, dst string) error {
if len(semVerConstraints) > 0 {
name = name + " [" + strings.Join(semVerConstraints, ", ") + "]"
}
componentSemVerConstraints := spec.ComponentSemVerConstraints()
if len(componentSemVerConstraints) > 0 {
name = name + " [" + formatComponentSemVerConstraintsToString(componentSemVerConstraints) + "]"
}
name = tcEscape(name)
fmt.Fprintf(f, "##teamcity[testStarted name='%s']\n", name)
+9
View File
@@ -450,6 +450,15 @@ func (g ginkgoErrors) InvalidEmptySemVerConstraint(cl CodeLocation) error {
}
}
func (g ginkgoErrors) InvalidEmptyComponentForSemVerConstraint(cl CodeLocation) error {
return GinkgoError{
Heading: "Invalid Empty Component for ComponentSemVerConstraint",
Message: "ComponentSemVerConstraint requires a non-empty component name",
CodeLocation: cl,
DocLink: "spec-semantic-version-filtering",
}
}
/* Table errors */
func (g ginkgoErrors) MultipleEntryBodyFunctionsForTable(cl CodeLocation) error {
return GinkgoError{
+77 -16
View File
@@ -2,11 +2,12 @@ package types
import (
"fmt"
"strings"
"github.com/Masterminds/semver/v3"
)
type SemVerFilter func([]string) bool
type SemVerFilter func(component string, constraints []string) bool
func MustParseSemVerFilter(input string) SemVerFilter {
filter, err := ParseSemVerFilter(input)
@@ -16,30 +17,90 @@ func MustParseSemVerFilter(input string) SemVerFilter {
return filter
}
func ParseSemVerFilter(filterVersion string) (SemVerFilter, error) {
if filterVersion == "" {
return func(_ []string) bool { return true }, nil
// ParseSemVerFilter parses non-component and component-specific semantic version filter string.
// The filter string can contain multiple non-component and component-specific versions separated by commas.
// Each component-specific version is in the format "component=version".
// If a version is specified without a component, it applies to non-component-specific constraints.
func ParseSemVerFilter(componentFilterVersions string) (SemVerFilter, error) {
if componentFilterVersions == "" {
return func(_ string, _ []string) bool { return true }, nil
}
targetVersion, err := semver.NewVersion(filterVersion)
if err != nil {
return nil, fmt.Errorf("invalid filter version: %w", err)
result := map[string]*semver.Version{}
parts := strings.Split(componentFilterVersions, ",")
for _, part := range parts {
part = strings.TrimSpace(part)
if len(part) == 0 {
continue
}
if strings.Contains(part, "=") {
// validate component-specific version string
invalidPart, invalidErr := false, fmt.Errorf("invalid component filter version: %s", part)
subParts := strings.Split(part, "=")
if len(subParts) != 2 {
invalidPart = true
}
component := strings.TrimSpace(subParts[0])
versionStr := strings.TrimSpace(subParts[1])
if len(component) == 0 || len(versionStr) == 0 {
invalidPart = true
}
if invalidPart {
return nil, invalidErr
}
// validate semver
v, err := semver.NewVersion(versionStr)
if err != nil {
return nil, fmt.Errorf("invalid component filter version: %s, error: %w", part, err)
}
result[component] = v
} else {
v, err := semver.NewVersion(part)
if err != nil {
return nil, fmt.Errorf("invalid filter version: %s, error: %w", part, err)
}
result[""] = v
}
}
return func(constraints []string) bool {
return func(component string, constraints []string) bool {
// unconstrained specs always run
if len(constraints) == 0 {
if len(component) == 0 && len(constraints) == 0 {
return true
}
for _, constraintStr := range constraints {
constraint, err := semver.NewConstraint(constraintStr)
if err != nil {
return false
}
// check non-component specific version constraints
if len(component) == 0 && len(constraints) != 0 {
v := result[""]
if v != nil {
for _, constraintStr := range constraints {
constraint, err := semver.NewConstraint(constraintStr)
if err != nil {
return false
}
if !constraint.Check(targetVersion) {
return false
if !constraint.Check(v) {
return false
}
}
}
}
// check component-specific version constraints
if len(component) != 0 && len(constraints) != 0 {
v := result[component]
if v != nil {
for _, constraintStr := range constraints {
constraint, err := semver.NewConstraint(constraintStr)
if err != nil {
return false
}
if !constraint.Check(v) {
return false
}
}
}
}
+108 -51
View File
@@ -38,6 +38,10 @@ type ConstructionNodeReport struct {
// all Describe/Context/When containers in this spec's hierarchy
ContainerHierarchySemVerConstraints [][]string
// ContainerHierarchyComponentSemVerConstraints is a slice containing the component-specific semVerConstraints of
// all Describe/Context/When containers in this spec's hierarchy
ContainerHierarchyComponentSemVerConstraints []map[string][]string
// IsSerial captures whether the any container has the Serial decorator
IsSerial bool
@@ -85,6 +89,9 @@ type Report struct {
//SuiteSemVerConstraints captures any semVerConstraints attached to the suite by the DSL's RunSpecs() function
SuiteSemVerConstraints []string
//SuiteComponentSemVerConstraints captures any component-specific semVerConstraints attached to the suite by the DSL's RunSpecs() function
SuiteComponentSemVerConstraints map[string][]string
//SuiteSucceeded captures the success or failure status of the test run
//If true, the test run is considered successful.
//If false, the test run is considered unsuccessful
@@ -188,14 +195,19 @@ type SpecReport struct {
// all Describe/Context/When containers in this spec's hierarchy
ContainerHierarchySemVerConstraints [][]string
// ContainerHierarchyComponentSemVerConstraints is a slice containing the component-specific semVerConstraints of
// all Describe/Context/When containers in this spec's hierarchy
ContainerHierarchyComponentSemVerConstraints []map[string][]string
// LeafNodeType, LeafNodeLocation, LeafNodeLabels, LeafNodeSemVerConstraints and LeafNodeText capture the NodeType, CodeLocation, and text
// of the Ginkgo node being tested (typically an NodeTypeIt node, though this can also be
// one of the NodeTypesForSuiteLevelNodes node types)
LeafNodeType NodeType
LeafNodeLocation CodeLocation
LeafNodeLabels []string
LeafNodeSemVerConstraints []string
LeafNodeText string
LeafNodeType NodeType
LeafNodeLocation CodeLocation
LeafNodeLabels []string
LeafNodeSemVerConstraints []string
LeafNodeComponentSemVerConstraints map[string][]string
LeafNodeText string
// Captures the Spec Priority
SpecPriority int
@@ -261,52 +273,54 @@ type SpecReport struct {
func (report SpecReport) MarshalJSON() ([]byte, error) {
//All this to avoid emitting an empty Failure struct in the JSON
out := struct {
ContainerHierarchyTexts []string
ContainerHierarchyLocations []CodeLocation
ContainerHierarchyLabels [][]string
ContainerHierarchySemVerConstraints [][]string
LeafNodeType NodeType
LeafNodeLocation CodeLocation
LeafNodeLabels []string
LeafNodeSemVerConstraints []string
LeafNodeText string
State SpecState
StartTime time.Time
EndTime time.Time
RunTime time.Duration
ParallelProcess int
Failure *Failure `json:",omitempty"`
NumAttempts int
MaxFlakeAttempts int
MaxMustPassRepeatedly int
CapturedGinkgoWriterOutput string `json:",omitempty"`
CapturedStdOutErr string `json:",omitempty"`
ReportEntries ReportEntries `json:",omitempty"`
ProgressReports []ProgressReport `json:",omitempty"`
AdditionalFailures []AdditionalFailure `json:",omitempty"`
SpecEvents SpecEvents `json:",omitempty"`
ContainerHierarchyTexts []string
ContainerHierarchyLocations []CodeLocation
ContainerHierarchyLabels [][]string
ContainerHierarchySemVerConstraints [][]string
ContainerHierarchyComponentSemVerConstraints []map[string][]string
LeafNodeType NodeType
LeafNodeLocation CodeLocation
LeafNodeLabels []string
LeafNodeSemVerConstraints []string
LeafNodeText string
State SpecState
StartTime time.Time
EndTime time.Time
RunTime time.Duration
ParallelProcess int
Failure *Failure `json:",omitempty"`
NumAttempts int
MaxFlakeAttempts int
MaxMustPassRepeatedly int
CapturedGinkgoWriterOutput string `json:",omitempty"`
CapturedStdOutErr string `json:",omitempty"`
ReportEntries ReportEntries `json:",omitempty"`
ProgressReports []ProgressReport `json:",omitempty"`
AdditionalFailures []AdditionalFailure `json:",omitempty"`
SpecEvents SpecEvents `json:",omitempty"`
}{
ContainerHierarchyTexts: report.ContainerHierarchyTexts,
ContainerHierarchyLocations: report.ContainerHierarchyLocations,
ContainerHierarchyLabels: report.ContainerHierarchyLabels,
ContainerHierarchySemVerConstraints: report.ContainerHierarchySemVerConstraints,
LeafNodeType: report.LeafNodeType,
LeafNodeLocation: report.LeafNodeLocation,
LeafNodeLabels: report.LeafNodeLabels,
LeafNodeSemVerConstraints: report.LeafNodeSemVerConstraints,
LeafNodeText: report.LeafNodeText,
State: report.State,
StartTime: report.StartTime,
EndTime: report.EndTime,
RunTime: report.RunTime,
ParallelProcess: report.ParallelProcess,
Failure: nil,
ReportEntries: nil,
NumAttempts: report.NumAttempts,
MaxFlakeAttempts: report.MaxFlakeAttempts,
MaxMustPassRepeatedly: report.MaxMustPassRepeatedly,
CapturedGinkgoWriterOutput: report.CapturedGinkgoWriterOutput,
CapturedStdOutErr: report.CapturedStdOutErr,
ContainerHierarchyTexts: report.ContainerHierarchyTexts,
ContainerHierarchyLocations: report.ContainerHierarchyLocations,
ContainerHierarchyLabels: report.ContainerHierarchyLabels,
ContainerHierarchySemVerConstraints: report.ContainerHierarchySemVerConstraints,
ContainerHierarchyComponentSemVerConstraints: report.ContainerHierarchyComponentSemVerConstraints,
LeafNodeType: report.LeafNodeType,
LeafNodeLocation: report.LeafNodeLocation,
LeafNodeLabels: report.LeafNodeLabels,
LeafNodeSemVerConstraints: report.LeafNodeSemVerConstraints,
LeafNodeText: report.LeafNodeText,
State: report.State,
StartTime: report.StartTime,
EndTime: report.EndTime,
RunTime: report.RunTime,
ParallelProcess: report.ParallelProcess,
Failure: nil,
ReportEntries: nil,
NumAttempts: report.NumAttempts,
MaxFlakeAttempts: report.MaxFlakeAttempts,
MaxMustPassRepeatedly: report.MaxMustPassRepeatedly,
CapturedGinkgoWriterOutput: report.CapturedGinkgoWriterOutput,
CapturedStdOutErr: report.CapturedStdOutErr,
}
if !report.Failure.IsZero() {
@@ -404,6 +418,34 @@ func (report SpecReport) SemVerConstraints() []string {
return out
}
// ComponentSemVerConstraints returns a deduped map of all the spec's component-specific SemVerConstraints.
func (report SpecReport) ComponentSemVerConstraints() map[string][]string {
out := map[string][]string{}
seen := map[string]bool{}
for _, compSemVerConstraints := range report.ContainerHierarchyComponentSemVerConstraints {
for component := range compSemVerConstraints {
if !seen[component] {
seen[component] = true
out[component] = compSemVerConstraints[component]
} else {
out[component] = append(out[component], compSemVerConstraints[component]...)
out[component] = slices.Compact(out[component])
}
}
}
for component := range report.LeafNodeComponentSemVerConstraints {
if !seen[component] {
seen[component] = true
out[component] = report.LeafNodeComponentSemVerConstraints[component]
} else {
out[component] = append(out[component], report.LeafNodeComponentSemVerConstraints[component]...)
out[component] = slices.Compact(out[component])
}
}
return out
}
// MatchesLabelFilter returns true if the spec satisfies the passed in label filter query
func (report SpecReport) MatchesLabelFilter(query string) (bool, error) {
filter, err := ParseLabelFilter(query)
@@ -419,7 +461,22 @@ func (report SpecReport) MatchesSemVerFilter(version string) (bool, error) {
if err != nil {
return false, err
}
return filter(report.SemVerConstraints()), nil
semVerConstraints := report.SemVerConstraints()
if len(semVerConstraints) != 0 && filter("", report.SemVerConstraints()) == false {
return false, nil
}
componentSemVerConstraints := report.ComponentSemVerConstraints()
if len(componentSemVerConstraints) != 0 {
for component, constraints := range componentSemVerConstraints {
if filter(component, constraints) == false {
return false, nil
}
}
}
return true, nil
}
// FileName() returns the name of the file containing the spec
+1 -1
View File
@@ -1,3 +1,3 @@
package types
const VERSION = "2.27.5"
const VERSION = "2.28.0"
+4
View File
@@ -1,3 +1,7 @@
## 1.39.1
Update all dependencies. This auto-updated the required version of Go to 1.24, consistent with the fact that Go 1.23 has been out of support for almost six months.
## 1.39.0
### Features
+1 -1
View File
@@ -22,7 +22,7 @@ import (
"github.com/onsi/gomega/types"
)
const GOMEGA_VERSION = "1.39.0"
const GOMEGA_VERSION = "1.39.1"
const nilGomegaPanic = `You are trying to make an assertion, but haven't registered Gomega's fail handler.
If you're using Ginkgo then you probably forgot to put your assertion in an It().
@@ -75,11 +75,6 @@ func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[s
return nil
}
case strings.HasPrefix(k, "share"):
if err = resolveUserShare(ctx, ref, tokenScope[k], client, mgr); err == nil {
return nil
}
case strings.HasPrefix(k, "lightweight"):
if err = resolveLightweightScope(ctx, ref, tokenScope[k], user, client, mgr); err == nil {
return nil
@@ -130,8 +125,13 @@ func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[s
}
func resolveLightweightScope(ctx context.Context, ref *provider.Reference, scope *authpb.Scope, user *userpb.User, client gateway.GatewayAPIClient, mgr token.Manager) error {
refString, err := storagespace.FormatReference(ref)
if err != nil {
// cannot format reference, so cannot be valid
return errtypes.PermissionDenied("invalid reference")
}
// Check if this ref is cached
key := "lw:" + user.Id.OpaqueId + scopeDelimiter + getRefKey(ref)
key := "lw:" + user.Id.OpaqueId + scopeDelimiter + refString
if _, err := scopeExpansionCache.Get(key); err == nil {
return nil
}
@@ -164,13 +164,7 @@ func resolvePublicShare(ctx context.Context, ref *provider.Reference, scope *aut
return err
}
if err := checkCacheForNestedResource(ctx, ref, share.ResourceId, client, mgr); err == nil {
return nil
}
// Some services like wopi don't access the shared resource relative to the
// share root but instead relative to the shared resources parent.
return checkRelativeReference(ctx, ref, share.ResourceId, client)
return checkCacheForNestedResource(ctx, ref, share.ResourceId, client, mgr)
}
func resolveOCMShare(ctx context.Context, ref *provider.Reference, scope *authpb.Scope, client gateway.GatewayAPIClient, mgr token.Manager) error {
@@ -184,69 +178,18 @@ func resolveOCMShare(ctx context.Context, ref *provider.Reference, scope *authpb
ref.ResourceId = share.GetResourceId()
}
if err := checkCacheForNestedResource(ctx, ref, share.ResourceId, client, mgr); err == nil {
return nil
}
// Some services like wopi don't access the shared resource relative to the
// share root but instead relative to the shared resources parent.
return checkRelativeReference(ctx, ref, share.ResourceId, client)
}
// checkRelativeReference checks if the shared resource is being accessed via a relative reference
// e.g.:
// storage: abcd, space: efgh
// /root (id: efgh)
// - New file.txt (id: ijkl) <- shared resource
//
// If the requested reference looks like this:
// Reference{ResourceId: {StorageId: "abcd", SpaceId: "efgh"}, Path: "./New file.txt"}
// then the request is considered relative and this function would return true.
// Only references which are relative to the immediate parent of a resource are considered valid.
func checkRelativeReference(ctx context.Context, requested *provider.Reference, sharedResourceID *provider.ResourceId, client gateway.GatewayAPIClient) error {
sRes, err := client.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: sharedResourceID}})
if err != nil {
return err
}
if sRes.Status.Code != rpc.Code_CODE_OK {
return statuspkg.NewErrorFromCode(sRes.Status.Code, "auth interceptor")
}
sharedResource := sRes.Info
// Is this a shared space
if sharedResource.ParentId == nil {
// Is the requested resource part of the shared space?
if requested.ResourceId.StorageId != sharedResource.Id.StorageId || requested.ResourceId.SpaceId != sharedResource.Id.SpaceId {
return errtypes.PermissionDenied("space access forbidden via public link")
}
} else {
parentID := sharedResource.ParentId
parentID.StorageId = sharedResource.Id.StorageId
if !utils.ResourceIDEqual(parentID, requested.ResourceId) && utils.MakeRelativePath(sharedResource.Path) != requested.Path {
return errtypes.PermissionDenied("access forbidden via public link")
}
}
key := storagespace.FormatResourceID(sharedResourceID) + scopeDelimiter + getRefKey(requested)
_ = scopeExpansionCache.SetWithExpire(key, nil, scopeCacheExpiration*time.Second)
return nil
}
func resolveUserShare(ctx context.Context, ref *provider.Reference, scope *authpb.Scope, client gateway.GatewayAPIClient, mgr token.Manager) error {
var share collaboration.Share
err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &share)
if err != nil {
return err
}
return checkCacheForNestedResource(ctx, ref, share.ResourceId, client, mgr)
}
func checkCacheForNestedResource(ctx context.Context, ref *provider.Reference, resource *provider.ResourceId, client gateway.GatewayAPIClient, mgr token.Manager) error {
refString, err := storagespace.FormatReference(ref)
if err != nil {
// cannot format reference, so cannot be valid
return errtypes.PermissionDenied("invalid reference")
}
// Check if this ref is cached
key := storagespace.FormatResourceID(resource) + scopeDelimiter + getRefKey(ref)
key := storagespace.FormatResourceID(resource) + scopeDelimiter + refString
if _, err := scopeExpansionCache.Get(key); err == nil {
return nil
}
@@ -270,40 +213,25 @@ func checkIfNestedResource(ctx context.Context, ref *provider.Reference, parent
return false, statuspkg.NewErrorFromCode(statResponse.Status.Code, "auth interceptor")
}
pathResp, err := client.GetPath(ctx, &provider.GetPathRequest{ResourceId: statResponse.GetInfo().GetId()})
if err != nil {
return false, err
}
if pathResp.Status.Code != rpc.Code_CODE_OK {
return false, statuspkg.NewErrorFromCode(pathResp.Status.Code, "auth interceptor")
}
parentPath := pathResp.Path
parentInfo := statResponse.GetInfo()
childPath := ref.GetPath()
if childPath != "" && childPath != "." && strings.HasPrefix(childPath, parentPath) {
// if the request is relative from the root, we can return directly
return true, nil
}
// The request is not relative to the root. We need to find out if the requested resource is child of the `parent` (coming from token scope)
// We need to find out if the requested resource is child of the `parent` (coming from token scope)
// We mint a token as the owner of the public share and try to stat the reference
// TODO(ishank011): We need to find a better alternative to this
// NOTE: did somebody say service accounts? ...
var user *userpb.User
if statResponse.GetInfo().GetOwner().GetType() == userpb.UserType_USER_TYPE_SPACE_OWNER {
if parentInfo.GetOwner().GetType() == userpb.UserType_USER_TYPE_SPACE_OWNER {
// fake a space owner user
user = &userpb.User{
Id: statResponse.GetInfo().GetOwner(),
Id: parentInfo.GetOwner(),
}
} else {
userResp, err := client.GetUser(ctx, &userpb.GetUserRequest{UserId: statResponse.Info.Owner, SkipFetchingUserGroups: true})
userResp, err := client.GetUser(ctx, &userpb.GetUserRequest{UserId: parentInfo.GetOwner(), SkipFetchingUserGroups: true})
if err != nil || userResp.Status.Code != rpc.Code_CODE_OK {
return false, err
}
user = userResp.User
}
scope, err := scope.AddOwnerScope(map[string]*authpb.Scope{})
if err != nil {
return false, err
@@ -329,6 +257,24 @@ func checkIfNestedResource(ctx context.Context, ref *provider.Reference, parent
if childStat.GetStatus().GetCode() != rpc.Code_CODE_OK {
return false, statuspkg.NewErrorFromCode(childStat.Status.Code, "auth interceptor")
}
childInfo := childStat.GetInfo()
// child can only be a nested resource if it is within the same space as parent
if childInfo.GetId().GetStorageId() != parentInfo.GetId().GetStorageId() ||
childInfo.GetId().GetSpaceId() != parentInfo.GetId().GetSpaceId() {
return false, nil
}
// Both resources are in the same space, now check paths
pathResp, err := client.GetPath(ctx, &provider.GetPathRequest{ResourceId: statResponse.GetInfo().GetId()})
if err != nil {
return false, err
}
if pathResp.Status.Code != rpc.Code_CODE_OK {
return false, statuspkg.NewErrorFromCode(pathResp.Status.Code, "auth interceptor")
}
parentPath := pathResp.Path
pathResp, err = client.GetPath(ctx, &provider.GetPathRequest{ResourceId: childStat.GetInfo().GetId()})
if err != nil {
return false, err
@@ -336,10 +282,9 @@ func checkIfNestedResource(ctx context.Context, ref *provider.Reference, parent
if pathResp.GetStatus().GetCode() != rpc.Code_CODE_OK {
return false, statuspkg.NewErrorFromCode(pathResp.Status.Code, "auth interceptor")
}
childPath = pathResp.Path
childPath := pathResp.Path
return strings.HasPrefix(childPath, parentPath), nil
}
func extractRefFromListProvidersReq(v *registry.ListStorageProvidersRequest) (*provider.Reference, bool) {
@@ -513,17 +458,3 @@ func extractShareRef(req interface{}) (*collaboration.ShareReference, bool) {
}
return nil, false
}
func getRefKey(ref *provider.Reference) string {
if ref.GetPath() != "" {
return ref.Path
}
if ref.GetResourceId() != nil {
return storagespace.FormatResourceID(ref.ResourceId)
}
// on malicious request both path and rid could be empty
// we still should not panic
return ""
}
@@ -21,6 +21,7 @@ package ocdav
import (
"context"
"fmt"
"io"
"net/http"
"path"
"path/filepath"
@@ -274,7 +275,7 @@ func (s *svc) executePathCopy(ctx context.Context, selector pool.Selectable[gate
var uploadEP, uploadToken string
for _, p := range uRes.Protocols {
if p.Protocol == "simple" {
if p.Protocol == "tus" {
uploadEP, uploadToken = p.UploadEndpoint, p.Token
}
}
@@ -303,24 +304,10 @@ func (s *svc) executePathCopy(ctx context.Context, selector pool.Selectable[gate
}
// 4. do upload
httpUploadReq, err := rhttp.NewRequest(ctx, "PUT", uploadEP, httpDownloadRes.Body)
fileid, err = s.tusUpload(ctx, uploadEP, uploadToken, httpDownloadRes.Body, int64(cp.sourceInfo.GetSize()))
if err != nil {
return err
}
httpUploadReq.Header.Set(datagateway.TokenTransportHeader, uploadToken)
httpUploadReq.ContentLength = int64(cp.sourceInfo.GetSize())
httpUploadRes, err := s.client.Do(httpUploadReq)
if err != nil {
return err
}
defer httpUploadRes.Body.Close()
if httpUploadRes.StatusCode != http.StatusOK {
return err
}
fileid = httpUploadRes.Header.Get(net.HeaderOCFileID)
}
w.Header().Set(net.HeaderOCFileID, fileid)
@@ -498,7 +485,7 @@ func (s *svc) executeSpacesCopy(ctx context.Context, w http.ResponseWriter, sele
var uploadEP, uploadToken string
for _, p := range uRes.Protocols {
if p.Protocol == "simple" {
if p.Protocol == "tus" {
uploadEP, uploadToken = p.UploadEndpoint, p.Token
}
}
@@ -530,24 +517,10 @@ func (s *svc) executeSpacesCopy(ctx context.Context, w http.ResponseWriter, sele
}
// 4. do upload
httpUploadReq, err := rhttp.NewRequest(ctx, http.MethodPut, uploadEP, httpDownloadRes.Body)
fileid, err = s.tusUpload(ctx, uploadEP, uploadToken, httpDownloadRes.Body, int64(cp.sourceInfo.GetSize()))
if err != nil {
return err
}
httpUploadReq.Header.Set(datagateway.TokenTransportHeader, uploadToken)
httpUploadReq.ContentLength = int64(cp.sourceInfo.GetSize())
httpUploadRes, err := s.client.Do(httpUploadReq)
if err != nil {
return err
}
defer httpUploadRes.Body.Close()
if httpUploadRes.StatusCode != http.StatusOK {
return err
}
fileid = httpUploadRes.Header.Get(net.HeaderOCFileID)
}
w.Header().Set(net.HeaderOCFileID, fileid)
@@ -756,3 +729,55 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re
return &copy{source: srcRef, sourceInfo: srcStatRes.Info, depth: depth, successCode: successCode, destination: dstRef}
}
func (s *svc) tusUpload(ctx context.Context, uploadEP, uploadToken string, body io.Reader, size int64) (string, error) {
chunkSize := int64(10000000)
var offset int64
var fileid string
for offset < size {
n := chunkSize
if offset+n > size {
n = size - offset
}
req, err := rhttp.NewRequest(ctx, http.MethodPatch, uploadEP, io.LimitReader(body, n))
if err != nil {
return "", err
}
req.Header.Set(datagateway.TokenTransportHeader, uploadToken)
req.Header.Set(net.HeaderTusResumable, "1.0.0")
req.Header.Set(net.HeaderUploadOffset, strconv.FormatInt(offset, 10))
req.Header.Set(net.HeaderContentType, "application/offset+octet-stream")
req.ContentLength = n
res, err := s.client.Do(req)
if err != nil {
return "", err
}
if res.StatusCode != http.StatusNoContent && res.StatusCode != http.StatusOK && res.StatusCode != http.StatusCreated {
res.Body.Close()
return "", fmt.Errorf("unexpected status code during TUS upload: %d", res.StatusCode)
}
if id := res.Header.Get(net.HeaderOCFileID); id != "" {
fileid = id
}
newOffsetStr := res.Header.Get(net.HeaderUploadOffset)
res.Body.Close()
if newOffsetStr != "" {
newOffset, err := strconv.ParseInt(newOffsetStr, 10, 64)
if err != nil {
return "", fmt.Errorf("invalid Upload-Offset header: %v", err)
}
offset = newOffset
} else {
offset += n
}
}
return fileid, nil
}
-75
View File
@@ -1,75 +0,0 @@
// Copyright 2018-2021 CERN
//
// 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.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package scope
import (
"context"
"fmt"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/utils"
"github.com/rs/zerolog"
)
func receivedShareScope(_ context.Context, scope *authpb.Scope, resource interface{}, logger *zerolog.Logger) (bool, error) {
var share collaboration.ReceivedShare
err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &share)
if err != nil {
return false, err
}
switch v := resource.(type) {
case *collaboration.GetReceivedShareRequest:
return checkShareRef(share.Share, v.GetRef()), nil
case *collaboration.UpdateReceivedShareRequest:
return checkShare(share.Share, v.GetShare().GetShare()), nil
case string:
return checkSharePath(v) || checkResourcePath(v), nil
}
msg := fmt.Sprintf("resource type assertion failed: %+v", resource)
logger.Debug().Str("scope", "receivedShareScope").Msg(msg)
return false, errtypes.InternalError(msg)
}
// AddReceivedShareScope adds the scope to allow access to a received user/group share and
// the shared resource.
func AddReceivedShareScope(share *collaboration.ReceivedShare, role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) {
// Create a new "scope share" to only expose the required fields to the scope.
scopeShare := &collaboration.Share{Id: share.Share.Id, Owner: share.Share.Owner, Creator: share.Share.Creator, ResourceId: share.Share.ResourceId}
val, err := utils.MarshalProtoV1ToJSON(&collaboration.ReceivedShare{Share: scopeShare})
if err != nil {
return nil, err
}
if scopes == nil {
scopes = make(map[string]*authpb.Scope)
}
scopes["receivedshare:"+share.Share.Id.OpaqueId] = &authpb.Scope{
Resource: &types.OpaqueEntry{
Decoder: "json",
Value: val,
},
Role: role,
}
return scopes, nil
}
+5 -7
View File
@@ -31,13 +31,11 @@ import (
type Verifier func(context.Context, *authpb.Scope, interface{}, *zerolog.Logger) (bool, error)
var supportedScopes = map[string]Verifier{
"user": userScope,
"publicshare": publicshareScope,
"resourceinfo": resourceinfoScope,
"share": shareScope,
"receivedshare": receivedShareScope,
"lightweight": lightweightAccountScope,
"ocmshare": ocmShareScope,
"user": userScope,
"publicshare": publicshareScope,
"resourceinfo": resourceinfoScope,
"lightweight": lightweightAccountScope,
"ocmshare": ocmShareScope,
}
// VerifyScope is the function to be called when dismantling tokens to check if
-142
View File
@@ -1,142 +0,0 @@
// Copyright 2018-2021 CERN
//
// 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.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package scope
import (
"context"
"fmt"
"strings"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/utils"
"github.com/rs/zerolog"
)
func shareScope(_ context.Context, scope *authpb.Scope, resource interface{}, logger *zerolog.Logger) (bool, error) {
var share collaboration.Share
err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &share)
if err != nil {
return false, err
}
switch v := resource.(type) {
// Viewer role
case *registry.GetStorageProvidersRequest:
return checkShareStorageRef(&share, v.GetRef()), nil
case *provider.StatRequest:
return checkShareStorageRef(&share, v.GetRef()), nil
case *provider.ListContainerRequest:
return checkShareStorageRef(&share, v.GetRef()), nil
case *provider.InitiateFileDownloadRequest:
return checkShareStorageRef(&share, v.GetRef()), nil
// Editor role
// TODO(ishank011): Add role checks,
// need to return appropriate status codes in the ocs/ocdav layers.
case *provider.CreateContainerRequest:
return checkShareStorageRef(&share, v.GetRef()), nil
case *provider.TouchFileRequest:
return checkShareStorageRef(&share, v.GetRef()), nil
case *provider.DeleteRequest:
return checkShareStorageRef(&share, v.GetRef()), nil
case *provider.MoveRequest:
return checkShareStorageRef(&share, v.GetSource()) && checkShareStorageRef(&share, v.GetDestination()), nil
case *provider.InitiateFileUploadRequest:
return checkShareStorageRef(&share, v.GetRef()), nil
case *collaboration.ListReceivedSharesRequest:
return true, nil
case *collaboration.GetReceivedShareRequest:
return checkShareRef(&share, v.GetRef()), nil
case string:
return checkSharePath(v) || checkResourcePath(v), nil
}
msg := fmt.Sprintf("resource type assertion failed: %+v", resource)
logger.Debug().Str("scope", "shareScope").Msg(msg)
return false, errtypes.InternalError(msg)
}
func checkShareStorageRef(s *collaboration.Share, r *provider.Reference) bool {
// ref: <id:<storage_id:$storageID opaque_id:$opaqueID > >
if r.GetResourceId() != nil && r.Path == "" { // path must be empty
return utils.ResourceIDEqual(s.ResourceId, r.GetResourceId())
}
return false
}
func checkShareRef(s *collaboration.Share, ref *collaboration.ShareReference) bool {
if ref.GetId() != nil {
return ref.GetId().OpaqueId == s.Id.OpaqueId
}
if key := ref.GetKey(); key != nil {
return (utils.UserEqual(key.Owner, s.Owner) || utils.UserEqual(key.Owner, s.Creator)) &&
utils.ResourceIDEqual(key.ResourceId, s.ResourceId) && utils.GranteeEqual(key.Grantee, s.Grantee)
}
return false
}
func checkShare(s1 *collaboration.Share, s2 *collaboration.Share) bool {
if s2.GetId() != nil {
return s2.GetId().OpaqueId == s1.Id.OpaqueId
}
return false
}
func checkSharePath(path string) bool {
paths := []string{
"/ocs/v2.php/apps/files_sharing/api/v1/shares",
"/ocs/v1.php/apps/files_sharing/api/v1/shares",
"/remote.php/webdav",
"/remote.php/dav/files",
}
for _, p := range paths {
if strings.HasPrefix(path, p) {
return true
}
}
return false
}
// AddShareScope adds the scope to allow access to a user/group share and
// the shared resource.
func AddShareScope(share *collaboration.Share, role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) {
// Create a new "scope share" to only expose the required fields to the scope.
scopeShare := &collaboration.Share{Id: share.Id, Owner: share.Owner, Creator: share.Creator, ResourceId: share.ResourceId}
val, err := utils.MarshalProtoV1ToJSON(scopeShare)
if err != nil {
return nil, err
}
if scopes == nil {
scopes = make(map[string]*authpb.Scope)
}
scopes["share:"+share.Id.OpaqueId] = &authpb.Scope{
Resource: &types.OpaqueEntry{
Decoder: "json",
Value: val,
},
Role: role,
}
return scopes, nil
}
+4
View File
@@ -16,6 +16,7 @@ linters:
- govet
- ineffassign
- misspell
- modernize
- perfsprint
- revive
- staticcheck
@@ -111,6 +112,9 @@ linters:
locale: US
ignore-rules:
- cancelled
modernize:
disable:
- omitzero
perfsprint:
int-conversion: true
err-error: true
+39 -1
View File
@@ -11,6 +11,43 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
<!-- Released section -->
<!-- Don't change this section unless doing release -->
## [1.40.0/0.62.0/0.16.0] 2026-02-02
### Added
- Add `AlwaysRecord` sampler in `go.opentelemetry.io/otel/sdk/trace`. (#7724)
- Add `Enabled` method to all synchronous instrument interfaces (`Float64Counter`, `Float64UpDownCounter`, `Float64Histogram`, `Float64Gauge`, `Int64Counter`, `Int64UpDownCounter`, `Int64Histogram`, `Int64Gauge`,) in `go.opentelemetry.io/otel/metric`.
This stabilizes the synchronous instrument enabled feature, allowing users to check if an instrument will process measurements before performing computationally expensive operations. (#7763)
- Add `go.opentelemetry.io/otel/semconv/v1.39.0` package.
The package contains semantic conventions from the `v1.39.0` version of the OpenTelemetry Semantic Conventions.
See the [migration documentation](./semconv/v1.39.0/MIGRATION.md) for information on how to upgrade from `go.opentelemetry.io/otel/semconv/v1.38.0.` (#7783, #7789)
### Changed
- Improve the concurrent performance of `HistogramReservoir` in `go.opentelemetry.io/otel/sdk/metric/exemplar` by 4x. (#7443)
- Improve the concurrent performance of `FixedSizeReservoir` in `go.opentelemetry.io/otel/sdk/metric/exemplar`. (#7447)
- Improve performance of concurrent histogram measurements in `go.opentelemetry.io/otel/sdk/metric`. (#7474)
- Improve performance of concurrent synchronous gauge measurements in `go.opentelemetry.io/otel/sdk/metric`. (#7478)
- Add experimental observability metrics in `go.opentelemetry.io/otel/exporters/stdout/stdoutmetric`. (#7492)
- `Exporter` in `go.opentelemetry.io/otel/exporters/prometheus` ignores metrics with the scope `go.opentelemetry.io/contrib/bridges/prometheus`.
This prevents scrape failures when the Prometheus exporter is misconfigured to get data from the Prometheus bridge. (#7688)
- Improve performance of concurrent exponential histogram measurements in `go.opentelemetry.io/otel/sdk/metric`. (#7702)
- The `rpc.grpc.status_code` attribute in the experimental metrics emitted from `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc` is replaced with the `rpc.response.status_code` attribute to align with the semantic conventions. (#7854)
- The `rpc.grpc.status_code` attribute in the experimental metrics emitted from `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc` is replaced with the `rpc.response.status_code` attribute to align with the semantic conventions. (#7854)
### Fixed
- Fix bad log message when key-value pairs are dropped because of key duplication in `go.opentelemetry.io/otel/sdk/log`. (#7662)
- Fix `DroppedAttributes` on `Record` in `go.opentelemetry.io/otel/sdk/log` to not count the non-attribute key-value pairs dropped because of key duplication. (#7662)
- Fix `SetAttributes` on `Record` in `go.opentelemetry.io/otel/sdk/log` to not log that attributes are dropped when they are actually not dropped. (#7662)
- Fix missing `request.GetBody` in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp` to correctly handle HTTP/2 `GOAWAY` frame. (#7794)
- `WithHostID` detector in `go.opentelemetry.io/otel/sdk/resource` to use full path for `ioreg` command on Darwin (macOS). (#7818)
### Deprecated
- Deprecate `go.opentelemetry.io/otel/exporters/zipkin`.
For more information, see the [OTel blog post deprecating the Zipkin exporter](https://opentelemetry.io/blog/2025/deprecating-zipkin-exporters/). (#7670)
## [1.39.0/0.61.0/0.15.0/0.0.14] 2025-12-05
### Added
@@ -3498,7 +3535,8 @@ It contains api and sdk for trace and meter.
- CircleCI build CI manifest files.
- CODEOWNERS file to track owners of this project.
[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.39.0...HEAD
[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.40.0...HEAD
[1.40.0/0.62.0/0.16.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.40.0
[1.39.0/0.61.0/0.15.0/0.0.14]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.39.0
[1.38.0/0.60.0/0.14.0/0.0.13]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.38.0
[0.59.1]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/exporters/prometheus/v0.59.1
+3 -3
View File
@@ -746,8 +746,8 @@ Encapsulate setup in constructor functions, ensuring clear ownership and scope:
import (
"errors"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
"go.opentelemetry.io/otel/semconv/v1.37.0/otelconv"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
"go.opentelemetry.io/otel/semconv/v1.39.0/otelconv"
)
type SDKComponent struct {
@@ -1039,7 +1039,7 @@ func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan)
All observability metrics should follow the [OpenTelemetry Semantic Conventions for SDK metrics](https://github.com/open-telemetry/semantic-conventions/blob/1cf2476ae5e518225a766990a28a6d5602bd5a30/docs/otel/sdk-metrics.md).
Use the metric semantic conventions convenience package [otelconv](./semconv/v1.37.0/otelconv/metric.go).
Use the metric semantic conventions convenience package [otelconv](./semconv/v1.39.0/otelconv/metric.go).
##### Component Identification
+4 -8
View File
@@ -13,32 +13,28 @@ import (
// BoolSliceValue converts a bool slice into an array with same elements as slice.
func BoolSliceValue(v []bool) any {
var zero bool
cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeOf(zero))).Elem()
cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeFor[bool]())).Elem()
reflect.Copy(cp, reflect.ValueOf(v))
return cp.Interface()
}
// Int64SliceValue converts an int64 slice into an array with same elements as slice.
func Int64SliceValue(v []int64) any {
var zero int64
cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeOf(zero))).Elem()
cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeFor[int64]())).Elem()
reflect.Copy(cp, reflect.ValueOf(v))
return cp.Interface()
}
// Float64SliceValue converts a float64 slice into an array with same elements as slice.
func Float64SliceValue(v []float64) any {
var zero float64
cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeOf(zero))).Elem()
cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeFor[float64]())).Elem()
reflect.Copy(cp, reflect.ValueOf(v))
return cp.Interface()
}
// StringSliceValue converts a string slice into an array with same elements as slice.
func StringSliceValue(v []string) any {
var zero string
cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeOf(zero))).Elem()
cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeFor[string]())).Elem()
reflect.Copy(cp, reflect.ValueOf(v))
return cp.Interface()
}
+1 -1
View File
@@ -58,7 +58,7 @@ func isComparable[T comparable](t T) T { return t }
var (
// keyValueType is used in computeDistinctReflect.
keyValueType = reflect.TypeOf(KeyValue{})
keyValueType = reflect.TypeFor[KeyValue]()
// emptyHash is the hash of an empty set.
emptyHash = xxhash.New().Sum64()
+1 -2
View File
@@ -66,8 +66,7 @@ func IntValue(v int) Value {
// IntSliceValue creates an INTSLICE Value.
func IntSliceValue(v []int) Value {
var int64Val int64
cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeOf(int64Val)))
cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeFor[int64]()))
for i, val := range v {
cp.Elem().Index(i).SetInt(int64(val))
}
+2 -2
View File
@@ -317,7 +317,7 @@ func parseMember(member string) (Member, error) {
keyValue, properties, found := strings.Cut(member, propertyDelimiter)
if found {
// Parse the member properties.
for _, pStr := range strings.Split(properties, propertyDelimiter) {
for pStr := range strings.SplitSeq(properties, propertyDelimiter) {
p, err := parseProperty(pStr)
if err != nil {
return newInvalidMember(), err
@@ -480,7 +480,7 @@ func Parse(bStr string) (Baggage, error) {
}
b := make(baggage.List)
for _, memberStr := range strings.Split(bStr, listDelimiter) {
for memberStr := range strings.SplitSeq(bStr, listDelimiter) {
m, err := parseMember(memberStr)
if err != nil {
return Baggage{}, err
+1 -1
View File
@@ -1,4 +1,4 @@
# This is a renovate-friendly source of Docker images.
FROM python:3.13.6-slim-bullseye@sha256:e98b521460ee75bca92175c16247bdf7275637a8faaeb2bcfa19d879ae5c4b9a AS python
FROM otel/weaver:v0.19.0@sha256:3d20814cef548f1d31f27f054fb4cd6a05125641a9f7cc29fc7eb234e8052cd9 AS weaver
FROM otel/weaver:v0.20.0@sha256:fa4f1c6954ecea78ab1a4e865bd6f5b4aaba80c1896f9f4a11e2c361d04e197e AS weaver
FROM avtodev/markdown-lint:v1@sha256:6aeedc2f49138ce7a1cd0adffc1b1c0321b841dc2102408967d9301c031949ee AS markdown
@@ -18,8 +18,8 @@ import (
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/x"
"go.opentelemetry.io/otel/internal/global"
"go.opentelemetry.io/otel/metric"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
"go.opentelemetry.io/otel/semconv/v1.37.0/otelconv"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
"go.opentelemetry.io/otel/semconv/v1.39.0/otelconv"
)
const (
@@ -116,7 +116,9 @@ func NewInstrumentation(id int64, target string) (*Instrumentation, error) {
// Do not modify attrs (NewSet sorts in-place), make a new slice.
recOpt: metric.WithAttributeSet(attribute.NewSet(append(
// Default to OK status code.
[]attribute.KeyValue{semconv.RPCGRPCStatusCodeOk},
[]attribute.KeyValue{
semconv.RPCResponseStatusCode(codes.OK.String()),
},
attrs...,
)...)),
}
@@ -291,8 +293,7 @@ func (i *Instrumentation) recordOption(err error, code codes.Code) metric.Record
defer put(measureAttrsPool, attrs)
*attrs = append(*attrs, i.attrs...)
c := int64(code) // uint32 -> int64.
*attrs = append(*attrs, semconv.RPCGRPCStatusCodeKey.Int64(c))
*attrs = append(*attrs, semconv.RPCResponseStatusCode(code.String()))
if err != nil {
*attrs = append(*attrs, semconv.ErrorType(err))
}
@@ -5,4 +5,4 @@ package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/ot
// Version is the current release version of the OpenTelemetry OTLP gRPC trace
// exporter in use.
const Version = "1.39.0"
const Version = "1.40.0"
+1 -1
View File
@@ -5,5 +5,5 @@ package otlptrace // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
// Version is the current release version of the OpenTelemetry OTLP trace exporter in use.
func Version() string {
return "1.39.0"
return "1.40.0"
}
+56
View File
@@ -229,6 +229,13 @@ func (i *sfCounter) Add(ctx context.Context, incr float64, opts ...metric.AddOpt
}
}
func (i *sfCounter) Enabled(ctx context.Context) bool {
if ctr := i.delegate.Load(); ctr != nil {
return ctr.(metric.Float64Counter).Enabled(ctx)
}
return false
}
type sfUpDownCounter struct {
embedded.Float64UpDownCounter
@@ -255,6 +262,13 @@ func (i *sfUpDownCounter) Add(ctx context.Context, incr float64, opts ...metric.
}
}
func (i *sfUpDownCounter) Enabled(ctx context.Context) bool {
if ctr := i.delegate.Load(); ctr != nil {
return ctr.(metric.Float64UpDownCounter).Enabled(ctx)
}
return false
}
type sfHistogram struct {
embedded.Float64Histogram
@@ -281,6 +295,13 @@ func (i *sfHistogram) Record(ctx context.Context, x float64, opts ...metric.Reco
}
}
func (i *sfHistogram) Enabled(ctx context.Context) bool {
if ctr := i.delegate.Load(); ctr != nil {
return ctr.(metric.Float64Histogram).Enabled(ctx)
}
return false
}
type sfGauge struct {
embedded.Float64Gauge
@@ -307,6 +328,13 @@ func (i *sfGauge) Record(ctx context.Context, x float64, opts ...metric.RecordOp
}
}
func (i *sfGauge) Enabled(ctx context.Context) bool {
if ctr := i.delegate.Load(); ctr != nil {
return ctr.(metric.Float64Gauge).Enabled(ctx)
}
return false
}
type siCounter struct {
embedded.Int64Counter
@@ -333,6 +361,13 @@ func (i *siCounter) Add(ctx context.Context, x int64, opts ...metric.AddOption)
}
}
func (i *siCounter) Enabled(ctx context.Context) bool {
if ctr := i.delegate.Load(); ctr != nil {
return ctr.(metric.Int64Counter).Enabled(ctx)
}
return false
}
type siUpDownCounter struct {
embedded.Int64UpDownCounter
@@ -359,6 +394,13 @@ func (i *siUpDownCounter) Add(ctx context.Context, x int64, opts ...metric.AddOp
}
}
func (i *siUpDownCounter) Enabled(ctx context.Context) bool {
if ctr := i.delegate.Load(); ctr != nil {
return ctr.(metric.Int64UpDownCounter).Enabled(ctx)
}
return false
}
type siHistogram struct {
embedded.Int64Histogram
@@ -385,6 +427,13 @@ func (i *siHistogram) Record(ctx context.Context, x int64, opts ...metric.Record
}
}
func (i *siHistogram) Enabled(ctx context.Context) bool {
if ctr := i.delegate.Load(); ctr != nil {
return ctr.(metric.Int64Histogram).Enabled(ctx)
}
return false
}
type siGauge struct {
embedded.Int64Gauge
@@ -410,3 +459,10 @@ func (i *siGauge) Record(ctx context.Context, x int64, opts ...metric.RecordOpti
ctr.(metric.Int64Gauge).Record(ctx, x, opts...)
}
}
func (i *siGauge) Enabled(ctx context.Context) bool {
if ctr := i.delegate.Load(); ctr != nil {
return ctr.(metric.Int64Gauge).Enabled(ctx)
}
return false
}
+14 -14
View File
@@ -157,7 +157,7 @@ func (m *meter) Int64Counter(name string, options ...metric.Int64CounterOption)
cfg := metric.NewInt64CounterConfig(options...)
id := instID{
name: name,
kind: reflect.TypeOf((*siCounter)(nil)),
kind: reflect.TypeFor[*siCounter](),
description: cfg.Description(),
unit: cfg.Unit(),
}
@@ -183,7 +183,7 @@ func (m *meter) Int64UpDownCounter(
cfg := metric.NewInt64UpDownCounterConfig(options...)
id := instID{
name: name,
kind: reflect.TypeOf((*siUpDownCounter)(nil)),
kind: reflect.TypeFor[*siUpDownCounter](),
description: cfg.Description(),
unit: cfg.Unit(),
}
@@ -206,7 +206,7 @@ func (m *meter) Int64Histogram(name string, options ...metric.Int64HistogramOpti
cfg := metric.NewInt64HistogramConfig(options...)
id := instID{
name: name,
kind: reflect.TypeOf((*siHistogram)(nil)),
kind: reflect.TypeFor[*siHistogram](),
description: cfg.Description(),
unit: cfg.Unit(),
}
@@ -229,7 +229,7 @@ func (m *meter) Int64Gauge(name string, options ...metric.Int64GaugeOption) (met
cfg := metric.NewInt64GaugeConfig(options...)
id := instID{
name: name,
kind: reflect.TypeOf((*siGauge)(nil)),
kind: reflect.TypeFor[*siGauge](),
description: cfg.Description(),
unit: cfg.Unit(),
}
@@ -255,7 +255,7 @@ func (m *meter) Int64ObservableCounter(
cfg := metric.NewInt64ObservableCounterConfig(options...)
id := instID{
name: name,
kind: reflect.TypeOf((*aiCounter)(nil)),
kind: reflect.TypeFor[*aiCounter](),
description: cfg.Description(),
unit: cfg.Unit(),
}
@@ -281,7 +281,7 @@ func (m *meter) Int64ObservableUpDownCounter(
cfg := metric.NewInt64ObservableUpDownCounterConfig(options...)
id := instID{
name: name,
kind: reflect.TypeOf((*aiUpDownCounter)(nil)),
kind: reflect.TypeFor[*aiUpDownCounter](),
description: cfg.Description(),
unit: cfg.Unit(),
}
@@ -307,7 +307,7 @@ func (m *meter) Int64ObservableGauge(
cfg := metric.NewInt64ObservableGaugeConfig(options...)
id := instID{
name: name,
kind: reflect.TypeOf((*aiGauge)(nil)),
kind: reflect.TypeFor[*aiGauge](),
description: cfg.Description(),
unit: cfg.Unit(),
}
@@ -330,7 +330,7 @@ func (m *meter) Float64Counter(name string, options ...metric.Float64CounterOpti
cfg := metric.NewFloat64CounterConfig(options...)
id := instID{
name: name,
kind: reflect.TypeOf((*sfCounter)(nil)),
kind: reflect.TypeFor[*sfCounter](),
description: cfg.Description(),
unit: cfg.Unit(),
}
@@ -356,7 +356,7 @@ func (m *meter) Float64UpDownCounter(
cfg := metric.NewFloat64UpDownCounterConfig(options...)
id := instID{
name: name,
kind: reflect.TypeOf((*sfUpDownCounter)(nil)),
kind: reflect.TypeFor[*sfUpDownCounter](),
description: cfg.Description(),
unit: cfg.Unit(),
}
@@ -382,7 +382,7 @@ func (m *meter) Float64Histogram(
cfg := metric.NewFloat64HistogramConfig(options...)
id := instID{
name: name,
kind: reflect.TypeOf((*sfHistogram)(nil)),
kind: reflect.TypeFor[*sfHistogram](),
description: cfg.Description(),
unit: cfg.Unit(),
}
@@ -405,7 +405,7 @@ func (m *meter) Float64Gauge(name string, options ...metric.Float64GaugeOption)
cfg := metric.NewFloat64GaugeConfig(options...)
id := instID{
name: name,
kind: reflect.TypeOf((*sfGauge)(nil)),
kind: reflect.TypeFor[*sfGauge](),
description: cfg.Description(),
unit: cfg.Unit(),
}
@@ -431,7 +431,7 @@ func (m *meter) Float64ObservableCounter(
cfg := metric.NewFloat64ObservableCounterConfig(options...)
id := instID{
name: name,
kind: reflect.TypeOf((*afCounter)(nil)),
kind: reflect.TypeFor[*afCounter](),
description: cfg.Description(),
unit: cfg.Unit(),
}
@@ -457,7 +457,7 @@ func (m *meter) Float64ObservableUpDownCounter(
cfg := metric.NewFloat64ObservableUpDownCounterConfig(options...)
id := instID{
name: name,
kind: reflect.TypeOf((*afUpDownCounter)(nil)),
kind: reflect.TypeFor[*afUpDownCounter](),
description: cfg.Description(),
unit: cfg.Unit(),
}
@@ -483,7 +483,7 @@ func (m *meter) Float64ObservableGauge(
cfg := metric.NewFloat64ObservableGaugeConfig(options...)
id := instID{
name: name,
kind: reflect.TypeOf((*afGauge)(nil)),
kind: reflect.TypeFor[*afGauge](),
description: cfg.Description(),
unit: cfg.Unit(),
}
+5 -1
View File
@@ -227,7 +227,11 @@ type Float64Observer interface {
// attributes as another Float64Callbacks also registered for the same
// instrument.
//
// The function needs to be concurrent safe.
// The function needs to be reentrant and concurrent safe.
//
// Note that Go's mutexes are not reentrant, and locking a mutex takes
// an indefinite amount of time. It is therefore advised to avoid
// using mutexes inside callbacks.
type Float64Callback func(context.Context, Float64Observer) error
// Float64ObservableOption applies options to float64 Observer instruments.
+5 -1
View File
@@ -225,7 +225,11 @@ type Int64Observer interface {
// attributes as another Int64Callbacks also registered for the same
// instrument.
//
// The function needs to be concurrent safe.
// The function needs to be reentrant and concurrent safe.
//
// Note that Go's mutexes are not reentrant, and locking a mutex takes
// an indefinite amount of time. It is therefore advised to avoid
// using mutexes inside callbacks.
type Int64Callback func(context.Context, Int64Observer) error
// Int64ObservableOption applies options to int64 Observer instruments.
+5 -1
View File
@@ -244,7 +244,11 @@ type Meter interface {
// Callbacks. Meaning, it should not report measurements for an instrument with
// the same attributes as another Callback will report.
//
// The function needs to be concurrent safe.
// The function needs to be reentrant and concurrent safe.
//
// Note that Go's mutexes are not reentrant, and locking a mutex takes
// an indefinite amount of time. It is therefore advised to avoid
// using mutexes inside callbacks.
type Callback func(context.Context, Observer) error
// Observer records measurements for multiple instruments in a Callback.
+24
View File
@@ -191,6 +191,9 @@ type Int64Counter struct{ embedded.Int64Counter }
// Add performs no operation.
func (Int64Counter) Add(context.Context, int64, ...metric.AddOption) {}
// Enabled performs no operation.
func (Int64Counter) Enabled(context.Context) bool { return false }
// Float64Counter is an OpenTelemetry Counter used to record float64
// measurements. It produces no telemetry.
type Float64Counter struct{ embedded.Float64Counter }
@@ -198,6 +201,9 @@ type Float64Counter struct{ embedded.Float64Counter }
// Add performs no operation.
func (Float64Counter) Add(context.Context, float64, ...metric.AddOption) {}
// Enabled performs no operation.
func (Float64Counter) Enabled(context.Context) bool { return false }
// Int64UpDownCounter is an OpenTelemetry UpDownCounter used to record int64
// measurements. It produces no telemetry.
type Int64UpDownCounter struct{ embedded.Int64UpDownCounter }
@@ -205,6 +211,9 @@ type Int64UpDownCounter struct{ embedded.Int64UpDownCounter }
// Add performs no operation.
func (Int64UpDownCounter) Add(context.Context, int64, ...metric.AddOption) {}
// Enabled performs no operation.
func (Int64UpDownCounter) Enabled(context.Context) bool { return false }
// Float64UpDownCounter is an OpenTelemetry UpDownCounter used to record
// float64 measurements. It produces no telemetry.
type Float64UpDownCounter struct{ embedded.Float64UpDownCounter }
@@ -212,6 +221,9 @@ type Float64UpDownCounter struct{ embedded.Float64UpDownCounter }
// Add performs no operation.
func (Float64UpDownCounter) Add(context.Context, float64, ...metric.AddOption) {}
// Enabled performs no operation.
func (Float64UpDownCounter) Enabled(context.Context) bool { return false }
// Int64Histogram is an OpenTelemetry Histogram used to record int64
// measurements. It produces no telemetry.
type Int64Histogram struct{ embedded.Int64Histogram }
@@ -219,6 +231,9 @@ type Int64Histogram struct{ embedded.Int64Histogram }
// Record performs no operation.
func (Int64Histogram) Record(context.Context, int64, ...metric.RecordOption) {}
// Enabled performs no operation.
func (Int64Histogram) Enabled(context.Context) bool { return false }
// Float64Histogram is an OpenTelemetry Histogram used to record float64
// measurements. It produces no telemetry.
type Float64Histogram struct{ embedded.Float64Histogram }
@@ -226,6 +241,9 @@ type Float64Histogram struct{ embedded.Float64Histogram }
// Record performs no operation.
func (Float64Histogram) Record(context.Context, float64, ...metric.RecordOption) {}
// Enabled performs no operation.
func (Float64Histogram) Enabled(context.Context) bool { return false }
// Int64Gauge is an OpenTelemetry Gauge used to record instantaneous int64
// measurements. It produces no telemetry.
type Int64Gauge struct{ embedded.Int64Gauge }
@@ -233,6 +251,9 @@ type Int64Gauge struct{ embedded.Int64Gauge }
// Record performs no operation.
func (Int64Gauge) Record(context.Context, int64, ...metric.RecordOption) {}
// Enabled performs no operation.
func (Int64Gauge) Enabled(context.Context) bool { return false }
// Float64Gauge is an OpenTelemetry Gauge used to record instantaneous float64
// measurements. It produces no telemetry.
type Float64Gauge struct{ embedded.Float64Gauge }
@@ -240,6 +261,9 @@ type Float64Gauge struct{ embedded.Float64Gauge }
// Record performs no operation.
func (Float64Gauge) Record(context.Context, float64, ...metric.RecordOption) {}
// Enabled performs no operation.
func (Float64Gauge) Enabled(context.Context) bool { return false }
// Int64ObservableCounter is an OpenTelemetry ObservableCounter used to record
// int64 measurements. It produces no telemetry.
type Int64ObservableCounter struct {
+24
View File
@@ -25,6 +25,12 @@ type Float64Counter interface {
// Use the WithAttributeSet (or, if performance is not a concern,
// the WithAttributes) option to include measurement attributes.
Add(ctx context.Context, incr float64, options ...AddOption)
// Enabled reports whether the instrument will process measurements for the given context.
//
// This function can be used in places where measuring an instrument
// would result in computationally expensive operations.
Enabled(context.Context) bool
}
// Float64CounterConfig contains options for synchronous counter instruments that
@@ -78,6 +84,12 @@ type Float64UpDownCounter interface {
// Use the WithAttributeSet (or, if performance is not a concern,
// the WithAttributes) option to include measurement attributes.
Add(ctx context.Context, incr float64, options ...AddOption)
// Enabled reports whether the instrument will process measurements for the given context.
//
// This function can be used in places where measuring an instrument
// would result in computationally expensive operations.
Enabled(context.Context) bool
}
// Float64UpDownCounterConfig contains options for synchronous counter
@@ -131,6 +143,12 @@ type Float64Histogram interface {
// Use the WithAttributeSet (or, if performance is not a concern,
// the WithAttributes) option to include measurement attributes.
Record(ctx context.Context, incr float64, options ...RecordOption)
// Enabled reports whether the instrument will process measurements for the given context.
//
// This function can be used in places where measuring an instrument
// would result in computationally expensive operations.
Enabled(context.Context) bool
}
// Float64HistogramConfig contains options for synchronous histogram
@@ -189,6 +207,12 @@ type Float64Gauge interface {
// Use the WithAttributeSet (or, if performance is not a concern,
// the WithAttributes) option to include measurement attributes.
Record(ctx context.Context, value float64, options ...RecordOption)
// Enabled reports whether the instrument will process measurements for the given context.
//
// This function can be used in places where measuring an instrument
// would result in computationally expensive operations.
Enabled(context.Context) bool
}
// Float64GaugeConfig contains options for synchronous gauge instruments that
+24
View File
@@ -25,6 +25,12 @@ type Int64Counter interface {
// Use the WithAttributeSet (or, if performance is not a concern,
// the WithAttributes) option to include measurement attributes.
Add(ctx context.Context, incr int64, options ...AddOption)
// Enabled reports whether the instrument will process measurements for the given context.
//
// This function can be used in places where measuring an instrument
// would result in computationally expensive operations.
Enabled(context.Context) bool
}
// Int64CounterConfig contains options for synchronous counter instruments that
@@ -78,6 +84,12 @@ type Int64UpDownCounter interface {
// Use the WithAttributeSet (or, if performance is not a concern,
// the WithAttributes) option to include measurement attributes.
Add(ctx context.Context, incr int64, options ...AddOption)
// Enabled reports whether the instrument will process measurements for the given context.
//
// This function can be used in places where measuring an instrument
// would result in computationally expensive operations.
Enabled(context.Context) bool
}
// Int64UpDownCounterConfig contains options for synchronous counter
@@ -131,6 +143,12 @@ type Int64Histogram interface {
// Use the WithAttributeSet (or, if performance is not a concern,
// the WithAttributes) option to include measurement attributes.
Record(ctx context.Context, incr int64, options ...RecordOption)
// Enabled reports whether the instrument will process measurements for the given context.
//
// This function can be used in places where measuring an instrument
// would result in computationally expensive operations.
Enabled(context.Context) bool
}
// Int64HistogramConfig contains options for synchronous histogram instruments
@@ -189,6 +207,12 @@ type Int64Gauge interface {
// Use the WithAttributeSet (or, if performance is not a concern,
// the WithAttributes) option to include measurement attributes.
Record(ctx context.Context, value int64, options ...RecordOption)
// Enabled reports whether the instrument will process measurements for the given context.
//
// This function can be used in places where measuring an instrument
// would result in computationally expensive operations.
Enabled(context.Context) bool
}
// Int64GaugeConfig contains options for synchronous gauge instruments that
+1 -1
View File
@@ -13,7 +13,7 @@ import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
)
type (
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"os"
"regexp"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
)
type containerIDProvider func() (string, error)
+1 -1
View File
@@ -12,7 +12,7 @@ import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
)
const (
+4 -5
View File
@@ -8,7 +8,7 @@ import (
"errors"
"strings"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
)
type hostIDProvider func() (string, error)
@@ -51,17 +51,16 @@ type hostIDReaderDarwin struct {
execCommand commandExecutor
}
// read executes `ioreg -rd1 -c "IOPlatformExpertDevice"` and parses host id
// read executes `/usr/sbin/ioreg -rd1 -c "IOPlatformExpertDevice"` and parses host id
// from the IOPlatformUUID line. If the command fails or the uuid cannot be
// parsed an error will be returned.
func (r *hostIDReaderDarwin) read() (string, error) {
result, err := r.execCommand("ioreg", "-rd1", "-c", "IOPlatformExpertDevice")
result, err := r.execCommand("/usr/sbin/ioreg", "-rd1", "-c", "IOPlatformExpertDevice")
if err != nil {
return "", err
}
lines := strings.Split(result, "\n")
for _, line := range lines {
for line := range strings.SplitSeq(result, "\n") {
if strings.Contains(line, "IOPlatformUUID") {
parts := strings.Split(line, " = ")
if len(parts) == 2 {
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"strings"
"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
)
type osDescriptionProvider func() (string, error)
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"path/filepath"
"runtime"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
)
type (
@@ -13,8 +13,8 @@ import (
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/sdk"
"go.opentelemetry.io/otel/sdk/internal/x"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
"go.opentelemetry.io/otel/semconv/v1.37.0/otelconv"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
"go.opentelemetry.io/otel/semconv/v1.39.0/otelconv"
)
const (
@@ -13,8 +13,8 @@ import (
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/sdk"
"go.opentelemetry.io/otel/sdk/internal/x"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
"go.opentelemetry.io/otel/semconv/v1.37.0/otelconv"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
"go.opentelemetry.io/otel/semconv/v1.39.0/otelconv"
)
var measureAttrsPool = sync.Pool{
+1 -1
View File
@@ -13,7 +13,7 @@ import (
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/sdk"
"go.opentelemetry.io/otel/sdk/internal/x"
"go.opentelemetry.io/otel/semconv/v1.37.0/otelconv"
"go.opentelemetry.io/otel/semconv/v1.39.0/otelconv"
"go.opentelemetry.io/otel/trace"
)
+28
View File
@@ -280,3 +280,31 @@ func (pb parentBased) Description() string {
pb.config.localParentNotSampled.Description(),
)
}
// AlwaysRecord returns a sampler decorator which ensures that every span
// is passed to the SpanProcessor, even those that would be normally dropped.
// It converts `Drop` decisions from the root sampler into `RecordOnly` decisions,
// allowing processors to see all spans without sending them to exporters. This is
// typically used to enable accurate span-to-metrics processing.
func AlwaysRecord(root Sampler) Sampler {
return alwaysRecord{root}
}
type alwaysRecord struct {
root Sampler
}
func (ar alwaysRecord) ShouldSample(p SamplingParameters) SamplingResult {
rootSamplerSamplingResult := ar.root.ShouldSample(p)
if rootSamplerSamplingResult.Decision == Drop {
return SamplingResult{
Decision: RecordOnly,
Tracestate: trace.SpanContextFromContext(p.ParentContext).TraceState(),
}
}
return rootSamplerSamplingResult
}
func (ar alwaysRecord) Description() string {
return "AlwaysRecord{root:" + ar.root.Description() + "}"
}
+1 -1
View File
@@ -20,7 +20,7 @@ import (
"go.opentelemetry.io/otel/internal/global"
"go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/embedded"
)
+1 -1
View File
@@ -6,5 +6,5 @@ package sdk // import "go.opentelemetry.io/otel/sdk"
// Version is the current release version of the OpenTelemetry SDK in use.
func Version() string {
return "1.39.0"
return "1.40.0"
}
+78
View File
@@ -0,0 +1,78 @@
<!-- Generated. DO NOT MODIFY. -->
# Migration from v1.38.0 to v1.39.0
The `go.opentelemetry.io/otel/semconv/v1.39.0` package should be a drop-in replacement for `go.opentelemetry.io/otel/semconv/v1.38.0` with the following exceptions.
## Removed
The following declarations have been removed.
Refer to the [OpenTelemetry Semantic Conventions documentation] for deprecation instructions.
If the type is not listed in the documentation as deprecated, it has been removed in this version due to lack of applicability or use.
If you use any of these non-deprecated declarations in your Go application, please [open an issue] describing your use-case.
- `LinuxMemorySlabStateKey`
- `LinuxMemorySlabStateReclaimable`
- `LinuxMemorySlabStateUnreclaimable`
- `PeerService`
- `PeerServiceKey`
- `RPCConnectRPCErrorCodeAborted`
- `RPCConnectRPCErrorCodeAlreadyExists`
- `RPCConnectRPCErrorCodeCancelled`
- `RPCConnectRPCErrorCodeDataLoss`
- `RPCConnectRPCErrorCodeDeadlineExceeded`
- `RPCConnectRPCErrorCodeFailedPrecondition`
- `RPCConnectRPCErrorCodeInternal`
- `RPCConnectRPCErrorCodeInvalidArgument`
- `RPCConnectRPCErrorCodeKey`
- `RPCConnectRPCErrorCodeNotFound`
- `RPCConnectRPCErrorCodeOutOfRange`
- `RPCConnectRPCErrorCodePermissionDenied`
- `RPCConnectRPCErrorCodeResourceExhausted`
- `RPCConnectRPCErrorCodeUnauthenticated`
- `RPCConnectRPCErrorCodeUnavailable`
- `RPCConnectRPCErrorCodeUnimplemented`
- `RPCConnectRPCErrorCodeUnknown`
- `RPCConnectRPCRequestMetadata`
- `RPCConnectRPCResponseMetadata`
- `RPCGRPCRequestMetadata`
- `RPCGRPCResponseMetadata`
- `RPCGRPCStatusCodeAborted`
- `RPCGRPCStatusCodeAlreadyExists`
- `RPCGRPCStatusCodeCancelled`
- `RPCGRPCStatusCodeDataLoss`
- `RPCGRPCStatusCodeDeadlineExceeded`
- `RPCGRPCStatusCodeFailedPrecondition`
- `RPCGRPCStatusCodeInternal`
- `RPCGRPCStatusCodeInvalidArgument`
- `RPCGRPCStatusCodeKey`
- `RPCGRPCStatusCodeNotFound`
- `RPCGRPCStatusCodeOk`
- `RPCGRPCStatusCodeOutOfRange`
- `RPCGRPCStatusCodePermissionDenied`
- `RPCGRPCStatusCodeResourceExhausted`
- `RPCGRPCStatusCodeUnauthenticated`
- `RPCGRPCStatusCodeUnavailable`
- `RPCGRPCStatusCodeUnimplemented`
- `RPCGRPCStatusCodeUnknown`
- `RPCJSONRPCErrorCode`
- `RPCJSONRPCErrorCodeKey`
- `RPCJSONRPCErrorMessage`
- `RPCJSONRPCErrorMessageKey`
- `RPCJSONRPCRequestID`
- `RPCJSONRPCRequestIDKey`
- `RPCJSONRPCVersion`
- `RPCJSONRPCVersionKey`
- `RPCService`
- `RPCServiceKey`
- `RPCSystemApacheDubbo`
- `RPCSystemConnectRPC`
- `RPCSystemDotnetWcf`
- `RPCSystemGRPC`
- `RPCSystemJSONRPC`
- `RPCSystemJavaRmi`
- `RPCSystemKey`
- `RPCSystemOncRPC`
[OpenTelemetry Semantic Conventions documentation]: https://github.com/open-telemetry/semantic-conventions
[open an issue]: https://github.com/open-telemetry/opentelemetry-go/issues/new?template=Blank+issue
+3
View File
@@ -0,0 +1,3 @@
# Semconv v1.39.0
[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/semconv/v1.39.0)](https://pkg.go.dev/go.opentelemetry.io/otel/semconv/v1.39.0)
File diff suppressed because it is too large Load Diff
+9
View File
@@ -0,0 +1,9 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package semconv implements OpenTelemetry semantic conventions.
//
// OpenTelemetry semantic conventions are agreed standardized naming
// patterns for OpenTelemetry things. This package represents the v1.39.0
// version of the OpenTelemetry semantic conventions.
package semconv // import "go.opentelemetry.io/otel/semconv/v1.39.0"
+56
View File
@@ -0,0 +1,56 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package semconv // import "go.opentelemetry.io/otel/semconv/v1.39.0"
import (
"reflect"
"go.opentelemetry.io/otel/attribute"
)
// ErrorType returns an [attribute.KeyValue] identifying the error type of err.
//
// If err is nil, the returned attribute has the default value
// [ErrorTypeOther].
//
// If err's type has the method
//
// ErrorType() string
//
// then the returned attribute has the value of err.ErrorType(). Otherwise, the
// returned attribute has a value derived from the concrete type of err.
//
// The key of the returned attribute is [ErrorTypeKey].
func ErrorType(err error) attribute.KeyValue {
if err == nil {
return ErrorTypeOther
}
return ErrorTypeKey.String(errorType(err))
}
func errorType(err error) string {
var s string
if et, ok := err.(interface{ ErrorType() string }); ok {
// Prioritize the ErrorType method if available.
s = et.ErrorType()
}
if s == "" {
// Fallback to reflection if the ErrorType method is not supported or
// returns an empty value.
t := reflect.TypeOf(err)
pkg, name := t.PkgPath(), t.Name()
if pkg != "" && name != "" {
s = pkg + "." + name
} else {
// The type has no package path or name (predeclared, not-defined,
// or alias for a not-defined type).
//
// This is not guaranteed to be unique, but is a best effort.
s = t.String()
}
}
return s
}
+9
View File
@@ -0,0 +1,9 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package semconv // import "go.opentelemetry.io/otel/semconv/v1.39.0"
const (
// ExceptionEventName is the name of the Span event representing an exception.
ExceptionEventName = "exception"
)
File diff suppressed because it is too large Load Diff
+9
View File
@@ -0,0 +1,9 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package semconv // import "go.opentelemetry.io/otel/semconv/v1.39.0"
// SchemaURL is the schema URL that matches the version of the semantic conventions
// that this package defines. Semconv packages starting from v1.4.0 must declare
// non-empty schema URL in the form https://opentelemetry.io/schemas/<version>
const SchemaURL = "https://opentelemetry.io/schemas/1.39.0"
+1 -1
View File
@@ -20,7 +20,7 @@ import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
"go.opentelemetry.io/otel/trace/embedded"
"go.opentelemetry.io/otel/trace/internal/telemetry"
)
+1 -1
View File
@@ -5,5 +5,5 @@ package otel // import "go.opentelemetry.io/otel"
// Version is the current release version of OpenTelemetry in use.
func Version() string {
return "1.39.0"
return "1.40.0"
}
+6 -3
View File
@@ -3,7 +3,7 @@
module-sets:
stable-v1:
version: v1.39.0
version: v1.40.0
modules:
- go.opentelemetry.io/otel
- go.opentelemetry.io/otel/bridge/opencensus
@@ -22,11 +22,11 @@ module-sets:
- go.opentelemetry.io/otel/sdk/metric
- go.opentelemetry.io/otel/trace
experimental-metrics:
version: v0.61.0
version: v0.62.0
modules:
- go.opentelemetry.io/otel/exporters/prometheus
experimental-logs:
version: v0.15.0
version: v0.16.0
modules:
- go.opentelemetry.io/otel/log
- go.opentelemetry.io/otel/log/logtest
@@ -46,6 +46,9 @@ modules:
go.opentelemetry.io/otel/exporters/stdout/stdouttrace:
version-refs:
- ./internal/version.go
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric:
version-refs:
- ./internal/version.go
go.opentelemetry.io/otel/exporters/prometheus:
version-refs:
- ./internal/version.go
+10
View File
@@ -453,6 +453,9 @@ func (c Cursor) FindNode(n ast.Node) (Cursor, bool) {
// rooted at c such that n.Pos() <= start && end <= n.End().
// (For an *ast.File, it uses the bounds n.FileStart-n.FileEnd.)
//
// An empty range (start == end) between two adjacent nodes is
// considered to belong to the first node.
//
// It returns zero if none is found.
// Precondition: start <= end.
//
@@ -501,10 +504,17 @@ func (c Cursor) FindByPos(start, end token.Pos) (Cursor, bool) {
break // disjoint, after; stop
}
}
// Inv: node.{Pos,FileStart} <= start
if end <= nodeEnd {
// node fully contains target range
best = i
// Don't search beyond end of the first match.
// This is important only for an empty range (start=end)
// between two adjoining nodes, which would otherwise
// match both nodes; we want to match only the first.
limit = ev.index
} else if nodeEnd < start {
i = ev.index // disjoint, before; skip forward
}
+8 -3
View File
@@ -10,7 +10,6 @@ import (
"encoding/csv"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strconv"
@@ -107,14 +106,14 @@ var IndexDir string = func() string {
var err error
dir, err = os.UserCacheDir()
// shouldn't happen, but TempDir is better than
// creating ./go/imports
// creating ./goimports
if err != nil {
dir = os.TempDir()
}
}
dir = filepath.Join(dir, "goimports")
if err := os.MkdirAll(dir, 0777); err != nil {
log.Printf("failed to create modcache index dir: %v", err)
dir = "" // #75505, people complain about the error message
}
return dir
}()
@@ -127,6 +126,9 @@ func Read(gomodcache string) (*Index, error) {
if err != nil {
return nil, err
}
if IndexDir == "" {
return nil, os.ErrNotExist
}
// Read the "link" file for the specified gomodcache directory.
// It names the payload file.
@@ -227,6 +229,9 @@ func readIndexFrom(gomodcache string, r io.Reader) (*Index, error) {
// write writes the index file and updates the index directory to refer to it.
func write(gomodcache string, ix *Index) error {
if IndexDir == "" {
return os.ErrNotExist
}
// Write the index into a payload file with a fresh name.
f, err := os.CreateTemp(IndexDir, fmt.Sprintf("index-%d-*", CurrentVersion))
if err != nil {
+316 -314
View File
@@ -12,364 +12,366 @@ type pkginfo struct {
}
var deps = [...]pkginfo{
{"archive/tar", "\x03p\x03F=\x01\n\x01$\x01\x01\x02\x05\b\x02\x01\x02\x02\f"},
{"archive/zip", "\x02\x04f\a\x03\x13\x021=\x01+\x05\x01\x0f\x03\x02\x0e\x04"},
{"bufio", "\x03p\x86\x01D\x14"},
{"bytes", "s+[\x03\fG\x02\x02"},
{"archive/tar", "\x03q\x03F=\x01\n\x01$\x01\x01\x02\x05\b\x02\x01\x02\x02\r"},
{"archive/zip", "\x02\x04g\a\x03\x13\x021=\x01+\x05\x01\x0f\x03\x02\x0f\x04"},
{"bufio", "\x03q\x86\x01D\x15"},
{"bytes", "t+[\x03\fH\x02\x02"},
{"cmp", ""},
{"compress/bzip2", "\x02\x02\xf5\x01A"},
{"compress/flate", "\x02q\x03\x83\x01\f\x033\x01\x03"},
{"compress/gzip", "\x02\x04f\a\x03\x15nT"},
{"compress/lzw", "\x02q\x03\x83\x01"},
{"compress/zlib", "\x02\x04f\a\x03\x13\x01o"},
{"container/heap", "\xbb\x02"},
{"compress/bzip2", "\x02\x02\xf6\x01A"},
{"compress/flate", "\x02r\x03\x83\x01\f\x033\x01\x03"},
{"compress/gzip", "\x02\x04g\a\x03\x15nU"},
{"compress/lzw", "\x02r\x03\x83\x01"},
{"compress/zlib", "\x02\x04g\a\x03\x13\x01o"},
{"container/heap", "\xbc\x02"},
{"container/list", ""},
{"container/ring", ""},
{"context", "s\\p\x01\r"},
{"crypto", "\x89\x01pC"},
{"crypto/aes", "\x10\n\t\x99\x02"},
{"crypto/cipher", "\x03 \x01\x01 \x12\x1c,Z"},
{"crypto/des", "\x10\x15 .,\x9d\x01\x03"},
{"crypto/dsa", "E\x04*\x86\x01\r"},
{"crypto/ecdh", "\x03\v\f\x10\x04\x17\x04\x0e\x1c\x86\x01"},
{"crypto/ecdsa", "\x0e\x05\x03\x04\x01\x10\b\v\x06\x01\x04\r\x01\x1c\x86\x01\r\x05K\x01"},
{"crypto/ed25519", "\x0e\x1e\x12\a\v\a\x1c\x86\x01C"},
{"crypto/elliptic", "3@\x86\x01\r9"},
{"crypto/fips140", "\"\x05"},
{"crypto/hkdf", "/\x15\x01.\x16"},
{"crypto/hmac", "\x1a\x16\x14\x01\x122"},
{"crypto/internal/boring", "\x0e\x02\rl"},
{"crypto/internal/boring/bbig", "\x1a\xec\x01M"},
{"crypto/internal/boring/bcache", "\xc0\x02\x13"},
{"context", "t\\p\x01\x0e"},
{"crypto", "\x8a\x01pC"},
{"crypto/aes", "\x10\v\t\x99\x02"},
{"crypto/cipher", "\x03!\x01\x01 \x12\x1c,Z"},
{"crypto/des", "\x10\x16 .,\x9d\x01\x03"},
{"crypto/dsa", "F\x03+\x86\x01\r"},
{"crypto/ecdh", "\x03\v\r\x10\x04\x17\x03\x0f\x1c\x86\x01"},
{"crypto/ecdsa", "\x0e\x05\x03\x05\x01\x10\b\v\x06\x01\x03\x0e\x01\x1c\x86\x01\r\x05L\x01"},
{"crypto/ed25519", "\x0e\x1f\x12\a\x03\b\a\x1cI=C"},
{"crypto/elliptic", "4@\x86\x01\r9"},
{"crypto/fips140", "#\x05\x95\x01\x98\x01"},
{"crypto/hkdf", "0\x15\x01.\x16"},
{"crypto/hmac", "\x1b\x16\x14\x01\x122"},
{"crypto/hpke", "\x03\v\x02\x03\x04\x01\f\x01\x05\x1f\x05\a\x01\x01\x1d\x03\x13\x16\x9b\x01\x1c"},
{"crypto/internal/boring", "\x0e\x02\x0el"},
{"crypto/internal/boring/bbig", "\x1b\xec\x01N"},
{"crypto/internal/boring/bcache", "\xc1\x02\x14"},
{"crypto/internal/boring/sig", ""},
{"crypto/internal/constanttime", ""},
{"crypto/internal/cryptotest", "\x03\r\n\b&\x0f\x19\x06\x13\x12 \x04\x06\t\x19\x01\x11\x11\x1b\x01\a\x05\b\x03\x05\v"},
{"crypto/internal/entropy", "J"},
{"crypto/internal/entropy/v1.0.0", "C0\x95\x018\x13"},
{"crypto/internal/fips140", "B1\xbf\x01\v\x16"},
{"crypto/internal/fips140/aes", "\x03\x1f\x03\x02\x14\x05\x01\x01\x06+\x95\x014"},
{"crypto/internal/fips140/aes/gcm", "\"\x01\x02\x02\x02\x12\x05\x01\a+\x92\x01"},
{"crypto/internal/fips140/alias", "\xd3\x02"},
{"crypto/internal/fips140/bigmod", "'\x19\x01\a+\x95\x01"},
{"crypto/internal/fips140/check", "\"\x0e\a\t\x02\xb7\x01Z"},
{"crypto/internal/fips140/check/checktest", "'\x8b\x02!"},
{"crypto/internal/fips140/drbg", "\x03\x1e\x01\x01\x04\x14\x05\t\x01)\x86\x01\x0f7\x01"},
{"crypto/internal/fips140/ecdh", "\x03\x1f\x05\x02\n\r3\x86\x01\x0f7"},
{"crypto/internal/fips140/ecdsa", "\x03\x1f\x04\x01\x02\a\x03\x06:\x16pF"},
{"crypto/internal/fips140/ed25519", "\x03\x1f\x05\x02\x04\f:\xc9\x01\x03"},
{"crypto/internal/fips140/edwards25519", "\x1e\t\a\x123\x95\x017"},
{"crypto/internal/fips140/edwards25519/field", "'\x14\x053\x95\x01"},
{"crypto/internal/fips140/hkdf", "\x03\x1f\x05\t\a<\x16"},
{"crypto/internal/fips140/hmac", "\x03\x1f\x15\x01\x01:\x16"},
{"crypto/internal/fips140/mldsa", "\x03\x1b\x04\x05\x02\x0e\x01\x03\x053\x95\x017"},
{"crypto/internal/fips140/mlkem", "\x03\x1f\x05\x02\x0f\x03\x053\xcc\x01"},
{"crypto/internal/fips140/nistec", "\x1e\t\r\f3\x95\x01*\r\x14"},
{"crypto/internal/fips140/nistec/fiat", "'\x148\x95\x01"},
{"crypto/internal/fips140/pbkdf2", "\x03\x1f\x05\t\a<\x16"},
{"crypto/internal/fips140/rsa", "\x03\x1b\x04\x04\x01\x02\x0e\x01\x01\x028\x16pF"},
{"crypto/internal/fips140/sha256", "\x03\x1f\x1e\x01\a+\x16\x7f"},
{"crypto/internal/fips140/sha3", "\x03\x1f\x19\x05\x012\x95\x01K"},
{"crypto/internal/fips140/sha512", "\x03\x1f\x1e\x01\a+\x16\x7f"},
{"crypto/internal/fips140/ssh", "'b"},
{"crypto/internal/fips140/subtle", "\x1e\a\x1b\xc8\x01"},
{"crypto/internal/fips140/tls12", "\x03\x1f\x05\t\a\x02:\x16"},
{"crypto/internal/fips140/tls13", "\x03\x1f\x05\b\b\t3\x16"},
{"crypto/internal/fips140cache", "\xb2\x02\r&"},
{"crypto/internal/cryptotest", "\x03\r\v\b%\x10\x19\x06\x13\x12 \x04\x06\t\x19\x01\x11\x11\x1b\x01\a\x05\b\x03\x05\f"},
{"crypto/internal/entropy", "K"},
{"crypto/internal/entropy/v1.0.0", "D0\x95\x018\x14"},
{"crypto/internal/fips140", "C1\xbf\x01\v\x17"},
{"crypto/internal/fips140/aes", "\x03 \x03\x02\x14\x05\x01\x01\x05,\x95\x014"},
{"crypto/internal/fips140/aes/gcm", "#\x01\x02\x02\x02\x12\x05\x01\x06,\x92\x01"},
{"crypto/internal/fips140/alias", "\xd5\x02"},
{"crypto/internal/fips140/bigmod", "(\x19\x01\x06,\x95\x01"},
{"crypto/internal/fips140/check", "#\x0e\a\t\x02\xb7\x01["},
{"crypto/internal/fips140/check/checktest", "(\x8b\x02\""},
{"crypto/internal/fips140/drbg", "\x03\x1f\x01\x01\x04\x14\x05\n)\x86\x01\x0f7\x01"},
{"crypto/internal/fips140/ecdh", "\x03 \x05\x02\n\r3\x86\x01\x0f7"},
{"crypto/internal/fips140/ecdsa", "\x03 \x04\x01\x02\a\x03\x06:\x16pF"},
{"crypto/internal/fips140/ed25519", "\x03 \x05\x02\x04\f:\xc9\x01\x03"},
{"crypto/internal/fips140/edwards25519", "\x1f\t\a\x123\x95\x017"},
{"crypto/internal/fips140/edwards25519/field", "(\x14\x053\x95\x01"},
{"crypto/internal/fips140/hkdf", "\x03 \x05\t\a<\x16"},
{"crypto/internal/fips140/hmac", "\x03 \x15\x01\x01:\x16"},
{"crypto/internal/fips140/mldsa", "\x03\x1c\x04\x05\x02\x0e\x01\x03\x053\x95\x017"},
{"crypto/internal/fips140/mlkem", "\x03 \x05\x02\x0f\x03\x053\xcc\x01"},
{"crypto/internal/fips140/nistec", "\x1f\t\r\f3\x95\x01*\r\x15"},
{"crypto/internal/fips140/nistec/fiat", "(\x148\x95\x01"},
{"crypto/internal/fips140/pbkdf2", "\x03 \x05\t\a<\x16"},
{"crypto/internal/fips140/rsa", "\x03\x1c\x04\x04\x01\x02\x0e\x01\x01\x028\x16pF"},
{"crypto/internal/fips140/sha256", "\x03 \x1e\x01\x06,\x16\x7f"},
{"crypto/internal/fips140/sha3", "\x03 \x19\x05\x012\x95\x01L"},
{"crypto/internal/fips140/sha512", "\x03 \x1e\x01\x06,\x16\x7f"},
{"crypto/internal/fips140/ssh", "(b"},
{"crypto/internal/fips140/subtle", "\x1f\a\x1b\xc8\x01"},
{"crypto/internal/fips140/tls12", "\x03 \x05\t\a\x02:\x16"},
{"crypto/internal/fips140/tls13", "\x03 \x05\b\b\t3\x16"},
{"crypto/internal/fips140cache", "\xb3\x02\r'"},
{"crypto/internal/fips140deps", ""},
{"crypto/internal/fips140deps/byteorder", "\x9f\x01"},
{"crypto/internal/fips140deps/cpu", "\xb4\x01\a"},
{"crypto/internal/fips140deps/godebug", "\xbc\x01"},
{"crypto/internal/fips140deps/time", "\xcd\x02"},
{"crypto/internal/fips140hash", "8\x1d4\xca\x01"},
{"crypto/internal/fips140only", ")\x0e\x01\x01P3="},
{"crypto/internal/fips140deps/byteorder", "\xa0\x01"},
{"crypto/internal/fips140deps/cpu", "\xb5\x01\a"},
{"crypto/internal/fips140deps/godebug", "\xbd\x01"},
{"crypto/internal/fips140deps/time", "\xcf\x02"},
{"crypto/internal/fips140hash", "9\x1d4\xcb\x01"},
{"crypto/internal/fips140only", "\x17\x13\x0e\x01\x01Pp"},
{"crypto/internal/fips140test", ""},
{"crypto/internal/hpke", "\x03\v\x01\x01\x03\x055\x03\x04\x01\x01\x16\a\x03\x13\xcc\x01"},
{"crypto/internal/impl", "\xbd\x02"},
{"crypto/internal/randutil", "\xf9\x01\x12"},
{"crypto/internal/sysrand", "sq! \r\r\x01\x01\f\x06"},
{"crypto/internal/sysrand/internal/seccomp", "s"},
{"crypto/md5", "\x0e7.\x16\x16i"},
{"crypto/mlkem", "\x0e$"},
{"crypto/mlkem/mlkemtest", "2\x1b&"},
{"crypto/pbkdf2", "5\x0f\x01.\x16"},
{"crypto/rand", "\x1a\b\a\x1c\x04\x01)\x86\x01\rM"},
{"crypto/rc4", "% .\xc9\x01"},
{"crypto/rsa", "\x0e\f\x01\v\x10\x0e\x01\x04\a\a\x1c\x03\x133=\f\x01"},
{"crypto/sha1", "\x0e\f+\x03+\x16\x16\x15T"},
{"crypto/sha256", "\x0e\f\x1dR"},
{"crypto/sha3", "\x0e*Q\xca\x01"},
{"crypto/sha512", "\x0e\f\x1fP"},
{"crypto/subtle", "\x1e\x1d\x9f\x01X"},
{"crypto/tls", "\x03\b\x02\x01\x01\x01\x01\x02\x01\x01\x01\x02\x01\x01\t\x01\x0e\n\x01\n\x05\x04\x01\x01\x01\x01\x02\x01\x02\x01\x17\x02\x03\x13\x16\x15\b=\x16\x16\r\b\x01\x01\x01\x02\x01\r\x06\x02\x01\x0f"},
{"crypto/tls/internal/fips140tls", "\x17\xa9\x02"},
{"crypto/x509", "\x03\v\x01\x01\x01\x01\x01\x01\x01\x016\x06\x01\x01\x02\x05\x0e\x06\x02\x02\x03F\x03:\x01\x02\b\x01\x01\x02\a\x10\x05\x01\x06\a\b\x02\x01\x02\x0e\x02\x01\x01\x02\x03\x01"},
{"crypto/x509/pkix", "i\x06\a\x90\x01G"},
{"database/sql", "\x03\nP\x16\x03\x83\x01\v\a\"\x05\b\x02\x03\x01\r\x02\x02\x02"},
{"database/sql/driver", "\rf\x03\xb7\x01\x0f\x11"},
{"debug/buildinfo", "\x03]\x02\x01\x01\b\a\x03g\x1a\x02\x01+\x0f\x1f"},
{"debug/dwarf", "\x03i\a\x03\x83\x011\x11\x01\x01"},
{"debug/elf", "\x03\x06V\r\a\x03g\x1b\x01\f \x17\x01\x16"},
{"debug/gosym", "\x03i\n\xc5\x01\x01\x01\x02"},
{"debug/macho", "\x03\x06V\r\ng\x1c,\x17\x01"},
{"debug/pe", "\x03\x06V\r\a\x03g\x1c,\x17\x01\x16"},
{"debug/plan9obj", "l\a\x03g\x1c,"},
{"embed", "s+B\x19\x01S"},
{"crypto/internal/impl", "\xbe\x02"},
{"crypto/internal/rand", "\x1b\x0f s=["},
{"crypto/internal/randutil", "\xfa\x01\x12"},
{"crypto/internal/sysrand", "tq! \r\r\x01\x01\r\x06"},
{"crypto/internal/sysrand/internal/seccomp", "t"},
{"crypto/md5", "\x0e8.\x16\x16i"},
{"crypto/mlkem", "\x0e%"},
{"crypto/mlkem/mlkemtest", "3\x13\b&"},
{"crypto/pbkdf2", "6\x0f\x01.\x16"},
{"crypto/rand", "\x1b\x0f\x1c\x03+\x86\x01\rN"},
{"crypto/rc4", "& .\xc9\x01"},
{"crypto/rsa", "\x0e\r\x01\v\x10\x0e\x01\x03\b\a\x1c\x03\x133=\f\x01"},
{"crypto/sha1", "\x0e\r+\x02,\x16\x16\x15T"},
{"crypto/sha256", "\x0e\r\x1dR"},
{"crypto/sha3", "\x0e+Q\xcb\x01"},
{"crypto/sha512", "\x0e\r\x1fP"},
{"crypto/subtle", "\x1f\x1d\x9f\x01z"},
{"crypto/tls", "\x03\b\x02\x01\x01\x01\x01\x02\x01\x01\x01\x02\x01\x01\x01\t\x01\x18\x01\x0f\x01\x03\x01\x01\x01\x01\x02\x01\x02\x01\x17\x02\x03\x13\x16\x15\b=\x16\x16\r\b\x01\x01\x01\x02\x01\x0e\x06\x02\x01\x0f"},
{"crypto/tls/internal/fips140tls", "\x17\xaa\x02"},
{"crypto/x509", "\x03\v\x01\x01\x01\x01\x01\x01\x01\x017\x06\x01\x01\x02\x05\x0e\x06\x02\x02\x03F\x03:\x01\x02\b\x01\x01\x02\a\x10\x05\x01\x06\a\b\x02\x01\x02\x0f\x02\x01\x01\x02\x03\x01"},
{"crypto/x509/pkix", "j\x06\a\x90\x01H"},
{"database/sql", "\x03\nQ\x16\x03\x83\x01\v\a\"\x05\b\x02\x03\x01\x0e\x02\x02\x02"},
{"database/sql/driver", "\rg\x03\xb7\x01\x0f\x12"},
{"debug/buildinfo", "\x03^\x02\x01\x01\b\a\x03g\x1a\x02\x01+\x0f "},
{"debug/dwarf", "\x03j\a\x03\x83\x011\x11\x01\x01"},
{"debug/elf", "\x03\x06W\r\a\x03g\x1b\x01\f \x17\x01\x17"},
{"debug/gosym", "\x03j\n$\xa1\x01\x01\x01\x02"},
{"debug/macho", "\x03\x06W\r\ng\x1c,\x17\x01"},
{"debug/pe", "\x03\x06W\r\a\x03g\x1c,\x17\x01\x17"},
{"debug/plan9obj", "m\a\x03g\x1c,"},
{"embed", "t+B\x19\x01T"},
{"embed/internal/embedtest", ""},
{"encoding", ""},
{"encoding/ascii85", "\xf9\x01C"},
{"encoding/asn1", "\x03p\x03g(\x01'\r\x02\x01\x10\x03\x01"},
{"encoding/base32", "\xf9\x01A\x02"},
{"encoding/base64", "\x9f\x01ZA\x02"},
{"encoding/binary", "s\x86\x01\f(\r\x05"},
{"encoding/csv", "\x02\x01p\x03\x83\x01D\x12\x02"},
{"encoding/gob", "\x02e\x05\a\x03g\x1c\v\x01\x03\x1d\b\x12\x01\x0f\x02"},
{"encoding/hex", "s\x03\x83\x01A\x03"},
{"encoding/json", "\x03\x01c\x04\b\x03\x83\x01\f(\r\x02\x01\x02\x10\x01\x01\x02"},
{"encoding/pem", "\x03h\b\x86\x01A\x03"},
{"encoding/xml", "\x02\x01d\f\x03\x83\x014\x05\n\x01\x02\x10\x02"},
{"errors", "\xcf\x01\x84\x01"},
{"expvar", "pLA\b\v\x15\r\b\x02\x03\x01\x11"},
{"flag", "g\f\x03\x83\x01,\b\x05\b\x02\x01\x10"},
{"fmt", "sF'\x19\f \b\r\x02\x03\x12"},
{"go/ast", "\x03\x01r\x0f\x01s\x03)\b\r\x02\x01\x12\x02"},
{"go/build", "\x02\x01p\x03\x01\x02\x02\b\x02\x01\x17\x1f\x04\x02\b\x1c\x13\x01+\x01\x04\x01\a\b\x02\x01\x12\x02\x02"},
{"go/build/constraint", "s\xc9\x01\x01\x12\x02"},
{"go/constant", "v\x10\x7f\x01\x024\x01\x02\x12"},
{"go/doc", "\x04r\x01\x05\n=61\x10\x02\x01\x12\x02"},
{"go/doc/comment", "\x03s\xc4\x01\x01\x01\x01\x12\x02"},
{"go/format", "\x03s\x01\f\x01\x02sD"},
{"go/importer", "x\a\x01\x02\x04\x01r9"},
{"go/internal/gccgoimporter", "\x02\x01]\x13\x03\x04\f\x01p\x02,\x01\x05\x11\x01\f\b"},
{"go/internal/gcimporter", "\x02t\x10\x010\x05\r0,\x15\x03\x02"},
{"go/internal/scannerhooks", "\x86\x01"},
{"go/internal/srcimporter", "v\x01\x01\v\x03\x01r,\x01\x05\x12\x02\x14"},
{"go/parser", "\x03p\x03\x01\x02\b\x04\x01s\x01+\x06\x12"},
{"go/printer", "v\x01\x02\x03\ns\f \x15\x02\x01\x02\v\x05\x02"},
{"go/scanner", "\x03s\v\x05s2\x10\x01\x13\x02"},
{"go/token", "\x04r\x86\x01>\x02\x03\x01\x0f\x02"},
{"go/types", "\x03\x01\x06i\x03\x01\x03\t\x03\x024\x063\x04\x03\t \x06\a\b\x01\x01\x01\x02\x01\x0f\x02\x02"},
{"go/version", "\xc1\x01|"},
{"hash", "\xf9\x01"},
{"hash/adler32", "s\x16\x16"},
{"hash/crc32", "s\x16\x16\x15\x8b\x01\x01\x13"},
{"hash/crc64", "s\x16\x16\xa0\x01"},
{"hash/fnv", "s\x16\x16i"},
{"hash/maphash", "\x89\x01\x11<}"},
{"html", "\xbd\x02\x02\x12"},
{"html/template", "\x03m\x06\x19-=\x01\n!\x05\x01\x02\x03\f\x01\x02\f\x01\x03\x02"},
{"image", "\x02q\x1fg\x0f4\x03\x01"},
{"encoding/ascii85", "\xfa\x01C"},
{"encoding/asn1", "\x03q\x03g(\x01'\r\x02\x01\x11\x03\x01"},
{"encoding/base32", "\xfa\x01A\x02"},
{"encoding/base64", "\xa0\x01ZA\x02"},
{"encoding/binary", "t\x86\x01\f(\r\x05"},
{"encoding/csv", "\x02\x01q\x03\x83\x01D\x13\x02"},
{"encoding/gob", "\x02f\x05\a\x03g\x1c\v\x01\x03\x1d\b\x12\x01\x10\x02"},
{"encoding/hex", "t\x03\x83\x01A\x03"},
{"encoding/json", "\x03\x01d\x04\b\x03\x83\x01\f(\r\x02\x01\x02\x11\x01\x01\x02"},
{"encoding/pem", "\x03i\b\x86\x01A\x03"},
{"encoding/xml", "\x02\x01e\f\x03\x83\x014\x05\n\x01\x02\x11\x02"},
{"errors", "\xd0\x01\x85\x01"},
{"expvar", "qLA\b\v\x15\r\b\x02\x03\x01\x12"},
{"flag", "h\f\x03\x83\x01,\b\x05\b\x02\x01\x11"},
{"fmt", "tF'\x19\f \b\r\x02\x03\x13"},
{"go/ast", "\x03\x01s\x0f\x01s\x03)\b\r\x02\x01\x13\x02"},
{"go/build", "\x02\x01q\x03\x01\x02\x02\b\x02\x01\x17\x1f\x04\x02\b\x1c\x13\x01+\x01\x04\x01\a\b\x02\x01\x13\x02\x02"},
{"go/build/constraint", "t\xc9\x01\x01\x13\x02"},
{"go/constant", "w\x10\x7f\x01\x024\x01\x02\x13"},
{"go/doc", "\x04s\x01\x05\n=61\x10\x02\x01\x13\x02"},
{"go/doc/comment", "\x03t\xc4\x01\x01\x01\x01\x13\x02"},
{"go/format", "\x03t\x01\f\x01\x02sD"},
{"go/importer", "y\a\x01\x02\x04\x01r9"},
{"go/internal/gccgoimporter", "\x02\x01^\x13\x03\x04\f\x01p\x02,\x01\x05\x11\x01\r\b"},
{"go/internal/gcimporter", "\x02u\x10\x010\x05\r0,\x15\x03\x02"},
{"go/internal/scannerhooks", "\x87\x01"},
{"go/internal/srcimporter", "w\x01\x01\v\x03\x01r,\x01\x05\x12\x02\x15"},
{"go/parser", "\x03q\x03\x01\x02\b\x04\x01s\x01+\x06\x12"},
{"go/printer", "w\x01\x02\x03\ns\f \x15\x02\x01\x02\f\x05\x02"},
{"go/scanner", "\x03t\v\x05s2\x10\x01\x14\x02"},
{"go/token", "\x04s\x86\x01>\x02\x03\x01\x10\x02"},
{"go/types", "\x03\x01\x06j\x03\x01\x03\t\x03\x024\x063\x04\x03\t \x06\a\b\x01\x01\x01\x02\x01\x10\x02\x02"},
{"go/version", "\xc2\x01|"},
{"hash", "\xfa\x01"},
{"hash/adler32", "t\x16\x16"},
{"hash/crc32", "t\x16\x16\x15\x8b\x01\x01\x14"},
{"hash/crc64", "t\x16\x16\xa0\x01"},
{"hash/fnv", "t\x16\x16i"},
{"hash/maphash", "\x8a\x01\x11<~"},
{"html", "\xbe\x02\x02\x13"},
{"html/template", "\x03n\x06\x19-=\x01\n!\x05\x01\x02\x03\f\x01\x02\r\x01\x03\x02"},
{"image", "\x02r\x1fg\x0f4\x03\x01"},
{"image/color", ""},
{"image/color/palette", "\x92\x01"},
{"image/draw", "\x91\x01\x01\x04"},
{"image/gif", "\x02\x01\x05k\x03\x1b\x01\x01\x01\vZ\x0f"},
{"image/internal/imageutil", "\x91\x01"},
{"image/jpeg", "\x02q\x1e\x01\x04c"},
{"image/png", "\x02\ac\n\x13\x02\x06\x01gC"},
{"index/suffixarray", "\x03i\a\x86\x01\f+\n\x01"},
{"internal/abi", "\xbb\x01\x98\x01"},
{"internal/asan", "\xd3\x02"},
{"internal/bisect", "\xb2\x02\r\x01"},
{"internal/buildcfg", "vHg\x06\x02\x05\n\x01"},
{"internal/bytealg", "\xb4\x01\x9f\x01"},
{"image/color/palette", "\x93\x01"},
{"image/draw", "\x92\x01\x01\x04"},
{"image/gif", "\x02\x01\x05l\x03\x1b\x01\x01\x01\vZ\x0f"},
{"image/internal/imageutil", "\x92\x01"},
{"image/jpeg", "\x02r\x1e\x01\x04c"},
{"image/png", "\x02\ad\n\x13\x02\x06\x01gC"},
{"index/suffixarray", "\x03j\a\x86\x01\f+\n\x01"},
{"internal/abi", "\xbc\x01\x99\x01"},
{"internal/asan", "\xd5\x02"},
{"internal/bisect", "\xb3\x02\r\x01"},
{"internal/buildcfg", "wHg\x06\x02\x05\n\x01"},
{"internal/bytealg", "\xb5\x01\xa0\x01"},
{"internal/byteorder", ""},
{"internal/cfg", ""},
{"internal/cgrouptest", "v[T\x06\x0f\x02\x01\x04\x01"},
{"internal/chacha8rand", "\x9f\x01\x15\a\x98\x01"},
{"internal/cgrouptest", "w[T\x06\x0f\x02\x01\x04\x01"},
{"internal/chacha8rand", "\xa0\x01\x15\a\x99\x01"},
{"internal/copyright", ""},
{"internal/coverage", ""},
{"internal/coverage/calloc", ""},
{"internal/coverage/cfile", "p\x06\x17\x17\x01\x02\x01\x01\x01\x01\x01\x01\x01\"\x02',\x06\a\n\x01\x03\r\x06"},
{"internal/coverage/cformat", "\x04r.\x04Q\v6\x01\x02\r"},
{"internal/coverage/cmerge", "v.a"},
{"internal/coverage/decodecounter", "l\n.\v\x02H,\x17\x17"},
{"internal/coverage/decodemeta", "\x02j\n\x17\x17\v\x02H,"},
{"internal/coverage/encodecounter", "\x02j\n.\f\x01\x02F\v!\x15"},
{"internal/coverage/encodemeta", "\x02\x01i\n\x13\x04\x17\r\x02F,."},
{"internal/coverage/pods", "\x04r.\x81\x01\x06\x05\n\x02\x01"},
{"internal/coverage/rtcov", "\xd3\x02"},
{"internal/coverage/slicereader", "l\n\x83\x01Z"},
{"internal/coverage/slicewriter", "v\x83\x01"},
{"internal/coverage/stringtab", "v9\x04F"},
{"internal/coverage/cfile", "q\x06\x17\x17\x01\x02\x01\x01\x01\x01\x01\x01\x01\"\x02',\x06\a\n\x01\x03\x0e\x06"},
{"internal/coverage/cformat", "\x04s.\x04Q\v6\x01\x02\x0e"},
{"internal/coverage/cmerge", "w.a"},
{"internal/coverage/decodecounter", "m\n.\v\x02H,\x17\x18"},
{"internal/coverage/decodemeta", "\x02k\n\x17\x17\v\x02H,"},
{"internal/coverage/encodecounter", "\x02k\n.\f\x01\x02F\v!\x15"},
{"internal/coverage/encodemeta", "\x02\x01j\n\x13\x04\x17\r\x02F,/"},
{"internal/coverage/pods", "\x04s.\x81\x01\x06\x05\n\x02\x01"},
{"internal/coverage/rtcov", "\xd5\x02"},
{"internal/coverage/slicereader", "m\n\x83\x01["},
{"internal/coverage/slicewriter", "w\x83\x01"},
{"internal/coverage/stringtab", "w9\x04F"},
{"internal/coverage/test", ""},
{"internal/coverage/uleb128", ""},
{"internal/cpu", "\xd3\x02"},
{"internal/dag", "\x04r\xc4\x01\x03"},
{"internal/diff", "\x03s\xc5\x01\x02"},
{"internal/exportdata", "\x02\x01p\x03\x02e\x1c,\x01\x05\x11\x01\x02"},
{"internal/filepathlite", "s+B\x1a@"},
{"internal/fmtsort", "\x04\xa9\x02\r"},
{"internal/fuzz", "\x03\nG\x18\x04\x03\x03\x01\f\x036=\f\x03\x1d\x01\x05\x02\x05\n\x01\x02\x01\x01\f\x04\x02"},
{"internal/cpu", "\xd5\x02"},
{"internal/dag", "\x04s\xc4\x01\x03"},
{"internal/diff", "\x03t\xc5\x01\x02"},
{"internal/exportdata", "\x02\x01q\x03\x02e\x1c,\x01\x05\x11\x01\x02"},
{"internal/filepathlite", "t+B\x1a@"},
{"internal/fmtsort", "\x04\xaa\x02\r"},
{"internal/fuzz", "\x03\nH\x18\x04\x03\x03\x01\f\x036=\f\x03\x1d\x01\x05\x02\x05\n\x01\x02\x01\x01\r\x04\x02"},
{"internal/goarch", ""},
{"internal/godebug", "\x9c\x01!\x82\x01\x01\x13"},
{"internal/godebug", "\x9d\x01!\x82\x01\x01\x14"},
{"internal/godebugs", ""},
{"internal/goexperiment", ""},
{"internal/goos", ""},
{"internal/goroot", "\xa5\x02\x01\x05\x12\x02"},
{"internal/goroot", "\xa6\x02\x01\x05\x12\x02"},
{"internal/gover", "\x04"},
{"internal/goversion", ""},
{"internal/lazyregexp", "\xa5\x02\v\r\x02"},
{"internal/lazytemplate", "\xf9\x01,\x18\x02\f"},
{"internal/msan", "\xd3\x02"},
{"internal/lazyregexp", "\xa6\x02\v\r\x02"},
{"internal/lazytemplate", "\xfa\x01,\x18\x02\r"},
{"internal/msan", "\xd5\x02"},
{"internal/nettrace", ""},
{"internal/obscuretestdata", "k\x8e\x01,"},
{"internal/oserror", "s"},
{"internal/pkgbits", "\x03Q\x18\a\x03\x04\fs\r\x1f\r\n\x01"},
{"internal/obscuretestdata", "l\x8e\x01,"},
{"internal/oserror", "t"},
{"internal/pkgbits", "\x03R\x18\a\x03\x04\fs\r\x1f\r\n\x01"},
{"internal/platform", ""},
{"internal/poll", "sl\x05\x159\r\x01\x01\f\x06"},
{"internal/profile", "\x03\x04l\x03\x83\x017\n\x01\x01\x01\x10"},
{"internal/poll", "tl\x05\x159\r\x01\x01\r\x06"},
{"internal/profile", "\x03\x04m\x03\x83\x017\n\x01\x01\x01\x11"},
{"internal/profilerecord", ""},
{"internal/race", "\x9a\x01\xb9\x01"},
{"internal/reflectlite", "\x9a\x01!;<!"},
{"internal/runtime/atomic", "\xbb\x01\x98\x01"},
{"internal/runtime/cgroup", "\x9e\x01=\x04t"},
{"internal/runtime/exithook", "\xd0\x01\x83\x01"},
{"internal/runtime/gc", "\xbb\x01"},
{"internal/runtime/gc/internal/gen", "\nb\n\x18k\x04\v\x1d\b\x10\x02"},
{"internal/runtime/gc/scan", "\xb4\x01\a\x18\ay"},
{"internal/runtime/maps", "\x9a\x01\x01 \n\t\t\x03y"},
{"internal/runtime/math", "\xbb\x01"},
{"internal/race", "\x9b\x01\xba\x01"},
{"internal/reflectlite", "\x9b\x01!;<\""},
{"internal/runtime/atomic", "\xbc\x01\x99\x01"},
{"internal/runtime/cgroup", "\x9f\x01=\x04u"},
{"internal/runtime/exithook", "\xd1\x01\x84\x01"},
{"internal/runtime/gc", "\xbc\x01"},
{"internal/runtime/gc/internal/gen", "\nc\n\x18k\x04\v\x1d\b\x10\x02"},
{"internal/runtime/gc/scan", "\xb5\x01\a\x18\az"},
{"internal/runtime/maps", "\x9b\x01\x01 \n\t\t\x03z"},
{"internal/runtime/math", "\xbc\x01"},
{"internal/runtime/pprof/label", ""},
{"internal/runtime/startlinetest", ""},
{"internal/runtime/sys", "\xbb\x01\x04"},
{"internal/runtime/syscall/linux", "\xbb\x01\x98\x01"},
{"internal/runtime/sys", "\xbc\x01\x04"},
{"internal/runtime/syscall/linux", "\xbc\x01\x99\x01"},
{"internal/runtime/wasitest", ""},
{"internal/saferio", "\xf9\x01Z"},
{"internal/singleflight", "\xbf\x02"},
{"internal/strconv", "\x88\x02K"},
{"internal/stringslite", "\x9e\x01\xb5\x01"},
{"internal/sync", "\x9a\x01!\x13r\x13"},
{"internal/synctest", "\x9a\x01\xb9\x01"},
{"internal/syscall/execenv", "\xc1\x02"},
{"internal/syscall/unix", "\xb2\x02\x0e\x01\x12"},
{"internal/sysinfo", "\x02\x01\xb1\x01E,\x18\x02"},
{"internal/saferio", "\xfa\x01["},
{"internal/singleflight", "\xc0\x02"},
{"internal/strconv", "\x89\x02L"},
{"internal/stringslite", "\x9f\x01\xb6\x01"},
{"internal/sync", "\x9b\x01!\x13r\x14"},
{"internal/synctest", "\x9b\x01\xba\x01"},
{"internal/syscall/execenv", "\xc2\x02"},
{"internal/syscall/unix", "\xb3\x02\x0e\x01\x13"},
{"internal/sysinfo", "\x02\x01\xb2\x01E,\x18\x02"},
{"internal/syslist", ""},
{"internal/testenv", "\x03\nf\x02\x01*\x1b\x0f0+\x01\x05\a\n\x01\x02\x02\x01\v"},
{"internal/testhash", "\x03\x86\x01p\x118\v"},
{"internal/testlog", "\xbf\x02\x01\x13"},
{"internal/testpty", "s\x03\xaf\x01"},
{"internal/trace", "\x02\x01\x01\x06b\a\x03w\x03\x03\x06\x03\t5\x01\x01\x01\x10\x06"},
{"internal/trace/internal/testgen", "\x03i\nu\x03\x02\x03\x011\v\r\x10"},
{"internal/trace/internal/tracev1", "\x03\x01h\a\x03}\x06\f5\x01"},
{"internal/trace/raw", "\x02j\nz\x03\x06C\x01\x12"},
{"internal/trace/testtrace", "\x02\x01p\x03q\x04\x03\x05\x01\x05,\v\x02\b\x02\x01\x05"},
{"internal/testenv", "\x03\ng\x02\x01*\x1b\x0f0+\x01\x05\a\n\x01\x02\x02\x01\f"},
{"internal/testhash", "\x03\x87\x01p\x118\f"},
{"internal/testlog", "\xc0\x02\x01\x14"},
{"internal/testpty", "t\x03\xaf\x01"},
{"internal/trace", "\x02\x01\x01\x06c\a\x03w\x03\x03\x06\x03\t+\n\x01\x01\x01\x11\x06"},
{"internal/trace/internal/testgen", "\x03j\nu\x03\x02\x03\x011\v\r\x11"},
{"internal/trace/internal/tracev1", "\x03\x01i\a\x03}\x06\f5\x01"},
{"internal/trace/raw", "\x02k\nz\x03\x06C\x01\x13"},
{"internal/trace/testtrace", "\x02\x01q\x03q\x04\x03\x05\x01\x05,\v\x02\b\x02\x01\x05"},
{"internal/trace/tracev2", ""},
{"internal/trace/traceviewer", "\x02c\v\x06\x1a<\x1f\a\a\x04\b\v\x15\x01\x05\a\n\x01\x02\x0e"},
{"internal/trace/traceviewer", "\x02d\v\x06\x1a<\x1f\a\a\x04\b\v\x15\x01\x05\a\n\x01\x02\x0f"},
{"internal/trace/traceviewer/format", ""},
{"internal/trace/version", "vz\t"},
{"internal/txtar", "\x03s\xaf\x01\x18"},
{"internal/types/errors", "\xbc\x02"},
{"internal/unsafeheader", "\xd3\x02"},
{"internal/xcoff", "_\r\a\x03g\x1c,\x17\x01"},
{"internal/zstd", "l\a\x03\x83\x01\x0f"},
{"io", "s\xcc\x01"},
{"io/fs", "s+*11\x10\x13\x04"},
{"io/ioutil", "\xf9\x01\x01+\x15\x03"},
{"iter", "\xce\x01d!"},
{"log", "v\x83\x01\x05'\r\r\x01\r"},
{"internal/trace/version", "wz\t"},
{"internal/txtar", "\x03t\xaf\x01\x18"},
{"internal/types/errors", "\xbd\x02"},
{"internal/unsafeheader", "\xd5\x02"},
{"internal/xcoff", "`\r\a\x03g\x1c,\x17\x01"},
{"internal/zstd", "m\a\x03\x83\x01\x0f"},
{"io", "t\xcc\x01"},
{"io/fs", "t+*11\x10\x14\x04"},
{"io/ioutil", "\xfa\x01\x01+\x15\x03"},
{"iter", "\xcf\x01d\""},
{"log", "w\x83\x01\x05'\r\r\x01\x0e"},
{"log/internal", ""},
{"log/slog", "\x03\nZ\t\x03\x03\x83\x01\x04\x01\x02\x02\x03(\x05\b\x02\x01\x02\x01\r\x02\x02\x02"},
{"log/slog", "\x03\n[\t\x03\x03\x83\x01\x04\x01\x02\x02\x03(\x05\b\x02\x01\x02\x01\x0e\x02\x02\x02"},
{"log/slog/internal", ""},
{"log/slog/internal/benchmarks", "\rf\x03\x83\x01\x06\x03:\x11"},
{"log/slog/internal/buffer", "\xbf\x02"},
{"log/syslog", "s\x03\x87\x01\x12\x16\x18\x02\x0e"},
{"maps", "\xfc\x01W"},
{"math", "\xb4\x01TK"},
{"math/big", "\x03p\x03)\x15E\f\x03\x020\x02\x01\x02\x14"},
{"math/big/internal/asmgen", "\x03\x01r\x92\x012\x03"},
{"math/bits", "\xd3\x02"},
{"math/cmplx", "\x85\x02\x03"},
{"math/rand", "\xbc\x01I:\x01\x13"},
{"math/rand/v2", "s,\x03c\x03K"},
{"mime", "\x02\x01h\b\x03\x83\x01\v!\x15\x03\x02\x10\x02"},
{"mime/multipart", "\x02\x01M#\x03F=\v\x01\a\x02\x15\x02\x06\x0f\x02\x01\x16"},
{"mime/quotedprintable", "\x02\x01s\x83\x01"},
{"net", "\x04\tf+\x1e\n\x05\x13\x01\x01\x04\x15\x01%\x06\r\b\x05\x01\x01\f\x06\a"},
{"net/http", "\x02\x01\x03\x01\x04\x02C\b\x13\x01\a\x03F=\x01\x03\a\x01\x03\x02\x02\x01\x02\x06\x02\x01\x01\n\x01\x01\x05\x01\x02\x05\b\x01\x01\x01\x02\x01\r\x02\x02\x02\b\x01\x01\x01"},
{"net/http/cgi", "\x02V\x1b\x03\x83\x01\x04\a\v\x01\x13\x01\x01\x01\x04\x01\x05\x02\b\x02\x01\x10\x0e"},
{"net/http/cookiejar", "\x04o\x03\x99\x01\x01\b\a\x05\x16\x03\x02\x0e\x04"},
{"net/http/fcgi", "\x02\x01\n_\a\x03\x83\x01\x16\x01\x01\x14\x18\x02\x0e"},
{"net/http/httptest", "\x02\x01\nK\x02\x1b\x01\x83\x01\x04\x12\x01\n\t\x02\x17\x01\x02\x0e\x0e"},
{"net/http/httptrace", "\rKnI\x14\n "},
{"net/http/httputil", "\x02\x01\nf\x03\x83\x01\x04\x0f\x03\x01\x05\x02\x01\v\x01\x19\x02\x01\r\x0e"},
{"net/http/internal", "\x02\x01p\x03\x83\x01"},
{"net/http/internal/ascii", "\xbd\x02\x12"},
{"net/http/internal/httpcommon", "\rf\x03\x9f\x01\x0e\x01\x17\x01\x01\x02\x1c\x02"},
{"net/http/internal/testcert", "\xbd\x02"},
{"net/http/pprof", "\x02\x01\ni\x19-\x02\x0e-\x04\x13\x14\x01\r\x04\x03\x01\x02\x01\x10"},
{"log/slog/internal/benchmarks", "\rg\x03\x83\x01\x06\x03:\x12"},
{"log/slog/internal/buffer", "\xc0\x02"},
{"log/syslog", "t\x03\x87\x01\x12\x16\x18\x02\x0f"},
{"maps", "\xfd\x01X"},
{"math", "\xb5\x01TL"},
{"math/big", "\x03q\x03)\x15E\f\x03\x020\x02\x01\x02\x15"},
{"math/big/internal/asmgen", "\x03\x01s\x92\x012\x03"},
{"math/bits", "\xd5\x02"},
{"math/cmplx", "\x86\x02\x03"},
{"math/rand", "\xbd\x01I:\x01\x14"},
{"math/rand/v2", "t,\x03c\x03L"},
{"mime", "\x02\x01i\b\x03\x83\x01\v!\x15\x03\x02\x11\x02"},
{"mime/multipart", "\x02\x01N#\x03F=\v\x01\a\x02\x15\x02\x06\x0f\x02\x01\x17"},
{"mime/quotedprintable", "\x02\x01t\x83\x01"},
{"net", "\x04\tg+\x1e\n\x05\x13\x01\x01\x04\x15\x01%\x06\r\b\x05\x01\x01\r\x06\a"},
{"net/http", "\x02\x01\x03\x01\x04\x02D\b\x13\x01\a\x03F=\x01\x03\a\x01\x03\x02\x02\x01\x02\x06\x02\x01\x01\n\x01\x01\x05\x01\x02\x05\b\x01\x01\x01\x02\x01\x0e\x02\x02\x02\b\x01\x01\x01"},
{"net/http/cgi", "\x02W\x1b\x03\x83\x01\x04\a\v\x01\x13\x01\x01\x01\x04\x01\x05\x02\b\x02\x01\x11\x0e"},
{"net/http/cookiejar", "\x04p\x03\x99\x01\x01\b\a\x05\x16\x03\x02\x0f\x04"},
{"net/http/fcgi", "\x02\x01\n`\a\x03\x83\x01\x16\x01\x01\x14\x18\x02\x0f"},
{"net/http/httptest", "\x02\x01\nL\x02\x1b\x01\x83\x01\x04\x12\x01\n\t\x02\x17\x01\x02\x0f\x0e"},
{"net/http/httptrace", "\rLnI\x14\n!"},
{"net/http/httputil", "\x02\x01\ng\x03\x83\x01\x04\x0f\x03\x01\x05\x02\x01\v\x01\x19\x02\x01\x0e\x0e"},
{"net/http/internal", "\x02\x01q\x03\x83\x01"},
{"net/http/internal/ascii", "\xbe\x02\x13"},
{"net/http/internal/httpcommon", "\rg\x03\x9f\x01\x0e\x01\x17\x01\x01\x02\x1d\x02"},
{"net/http/internal/testcert", "\xbe\x02"},
{"net/http/pprof", "\x02\x01\nj\x19-\x02\x0e-\x04\x13\x14\x01\r\x04\x03\x01\x02\x01\x11"},
{"net/internal/cgotest", ""},
{"net/internal/socktest", "v\xc9\x01\x02"},
{"net/mail", "\x02q\x03\x83\x01\x04\x0f\x03\x14\x1a\x02\x0e\x04"},
{"net/netip", "\x04o+\x01f\x034\x16"},
{"net/rpc", "\x02l\x05\x03\x10\ni\x04\x12\x01\x1d\r\x03\x02"},
{"net/rpc/jsonrpc", "p\x03\x03\x83\x01\x16\x11\x1f"},
{"net/smtp", "\x193\f\x13\b\x03\x83\x01\x16\x14\x1a"},
{"net/textproto", "\x02\x01p\x03\x83\x01\f\n-\x01\x02\x14"},
{"net/url", "s\x03Fc\v\x10\x02\x01\x16"},
{"os", "s+\x01\x19\x03\x10\x14\x01\x03\x01\x05\x10\x018\b\x05\x01\x01\f\x06"},
{"os/exec", "\x03\nfI'\x01\x15\x01+\x06\a\n\x01\x04\f"},
{"os/exec/internal/fdtest", "\xc1\x02"},
{"os/signal", "\r\x98\x02\x15\x05\x02"},
{"os/user", "\x02\x01p\x03\x83\x01,\r\n\x01\x02"},
{"path", "s+\xb3\x01"},
{"path/filepath", "s+\x1aB+\r\b\x03\x04\x10"},
{"plugin", "s"},
{"reflect", "s'\x04\x1d\x13\b\x04\x05\x17\x06\t-\n\x03\x10\x02\x02"},
{"net/internal/socktest", "w\xc9\x01\x02"},
{"net/mail", "\x02r\x03\x83\x01\x04\x0f\x03\x14\x1a\x02\x0f\x04"},
{"net/netip", "\x04p+\x01f\x034\x17"},
{"net/rpc", "\x02m\x05\x03\x10\ni\x04\x12\x01\x1d\r\x03\x02"},
{"net/rpc/jsonrpc", "q\x03\x03\x83\x01\x16\x11\x1f"},
{"net/smtp", "\x194\f\x13\b\x03\x83\x01\x16\x14\x1a"},
{"net/textproto", "\x02\x01q\x03\x83\x01\f\n-\x01\x02\x15"},
{"net/url", "t\x03Fc\v\x10\x02\x01\x17"},
{"os", "t+\x01\x19\x03\x10\x14\x01\x03\x01\x05\x10\x018\b\x05\x01\x01\r\x06"},
{"os/exec", "\x03\ngI'\x01\x15\x01+\x06\a\n\x01\x04\r"},
{"os/exec/internal/fdtest", "\xc2\x02"},
{"os/signal", "\r\x99\x02\x15\x05\x02"},
{"os/user", "\x02\x01q\x03\x83\x01,\r\n\x01\x02"},
{"path", "t+\xb4\x01"},
{"path/filepath", "t+\x1aB+\r\b\x03\x04\x11"},
{"plugin", "t"},
{"reflect", "t'\x04\x1d\x13\b\x04\x05\x17\x06\t-\n\x03\x11\x02\x02"},
{"reflect/internal/example1", ""},
{"reflect/internal/example2", ""},
{"regexp", "\x03\xf6\x018\t\x02\x01\x02\x10\x02"},
{"regexp/syntax", "\xba\x02\x01\x01\x01\x02\x10\x02"},
{"runtime", "\x9a\x01\x04\x01\x03\f\x06\a\x02\x01\x01\x0e\x03\x01\x01\x01\x02\x01\x01\x01\x02\x01\x04\x01\x10\x18K"},
{"runtime/coverage", "\xa6\x01S"},
{"runtime/debug", "vUZ\r\b\x02\x01\x10\x06"},
{"runtime/metrics", "\xbd\x01H-!"},
{"runtime/pprof", "\x02\x01\x01\x03\x06_\a\x03$$\x0f\v!\f \r\b\x01\x01\x01\x02\x02\t\x03\x06"},
{"runtime/race", "\xb8\x02"},
{"regexp", "\x03\xf7\x018\t\x02\x01\x02\x11\x02"},
{"regexp/syntax", "\xbb\x02\x01\x01\x01\x02\x11\x02"},
{"runtime", "\x9b\x01\x04\x01\x03\f\x06\a\x02\x01\x01\x0e\x03\x01\x01\x01\x02\x01\x01\x01\x02\x01\x04\x01\x10\x18L"},
{"runtime/coverage", "\xa7\x01S"},
{"runtime/debug", "wUZ\r\b\x02\x01\x11\x06"},
{"runtime/metrics", "\xbe\x01H-\""},
{"runtime/pprof", "\x02\x01\x01\x03\x06`\a\x03$$\x0f\v!\f \r\b\x01\x01\x01\x02\x02\n\x03\x06"},
{"runtime/race", "\xb9\x02"},
{"runtime/race/internal/amd64v1", ""},
{"runtime/trace", "\rf\x03z\t9\b\x05\x01\r\x06"},
{"slices", "\x04\xf8\x01\fK"},
{"sort", "\xcf\x0192"},
{"strconv", "s+A\x01q"},
{"strings", "s'\x04B\x19\x03\f7\x10\x02\x02"},
{"runtime/trace", "\rg\x03z\t9\b\x05\x01\x0e\x06"},
{"slices", "\x04\xf9\x01\fL"},
{"sort", "\xd0\x0192"},
{"strconv", "t+A\x01r"},
{"strings", "t'\x04B\x19\x03\f7\x11\x02\x02"},
{"structs", ""},
{"sync", "\xce\x01\x13\x01P\x0e\x13"},
{"sync/atomic", "\xd3\x02"},
{"syscall", "s(\x03\x01\x1c\n\x03\x06\r\x04S\b\x05\x01\x13"},
{"testing", "\x03\nf\x02\x01X\x17\x14\f\x05\x1b\x06\x02\x05\x02\x05\x01\x02\x01\x02\x01\r\x02\x04"},
{"testing/fstest", "s\x03\x83\x01\x01\n&\x10\x03\b\b"},
{"testing/internal/testdeps", "\x02\v\xad\x01/\x10,\x03\x05\x03\x06\a\x02\x0e"},
{"testing/iotest", "\x03p\x03\x83\x01\x04"},
{"testing/quick", "u\x01\x8f\x01\x05#\x10\x10"},
{"testing/slogtest", "\rf\x03\x89\x01.\x05\x10\v"},
{"testing/synctest", "\xe2\x01`\x11"},
{"text/scanner", "\x03s\x83\x01,*\x02"},
{"text/tabwriter", "v\x83\x01X"},
{"text/template", "s\x03C@\x01\n \x01\x05\x01\x02\x05\v\x02\r\x03\x02"},
{"text/template/parse", "\x03s\xbc\x01\n\x01\x12\x02"},
{"time", "s+\x1e$(*\r\x02\x12"},
{"time/tzdata", "s\xce\x01\x12"},
{"sync", "\xcf\x01\x13\x01P\x0e\x14"},
{"sync/atomic", "\xd5\x02"},
{"syscall", "t(\x03\x01\x1c\n\x03\x06\r\x04S\b\x05\x01\x14"},
{"testing", "\x03\ng\x02\x01X\x17\x14\f\x05\x1b\x06\x02\x05\x02\x05\x01\x02\x01\x02\x01\x0e\x02\x04"},
{"testing/cryptotest", "QOZ\x124\x03\x12"},
{"testing/fstest", "t\x03\x83\x01\x01\n&\x10\x03\t\b"},
{"testing/internal/testdeps", "\x02\v\xae\x01/\x10,\x03\x05\x03\x06\a\x02\x0f"},
{"testing/iotest", "\x03q\x03\x83\x01\x04"},
{"testing/quick", "v\x01\x8f\x01\x05#\x10\x11"},
{"testing/slogtest", "\rg\x03\x89\x01.\x05\x10\f"},
{"testing/synctest", "\xe3\x01`\x12"},
{"text/scanner", "\x03t\x83\x01,+\x02"},
{"text/tabwriter", "w\x83\x01Y"},
{"text/template", "t\x03C@\x01\n \x01\x05\x01\x02\x05\v\x02\x0e\x03\x02"},
{"text/template/parse", "\x03t\xbc\x01\n\x01\x13\x02"},
{"time", "t+\x1e$(*\r\x02\x13"},
{"time/tzdata", "t\xce\x01\x13"},
{"unicode", ""},
{"unicode/utf16", ""},
{"unicode/utf8", ""},
{"unique", "\x9a\x01!%\x01Q\r\x01\x13\x12"},
{"unique", "\x9b\x01!%\x01Q\r\x01\x14\x12"},
{"unsafe", ""},
{"vendor/golang.org/x/crypto/chacha20", "\x10\\\a\x95\x01*&"},
{"vendor/golang.org/x/crypto/chacha20poly1305", "\x10\\\a\xe1\x01\x04\x01\a"},
{"vendor/golang.org/x/crypto/cryptobyte", "i\n\x03\x90\x01' \n"},
{"vendor/golang.org/x/crypto/chacha20", "\x10]\a\x95\x01*'"},
{"vendor/golang.org/x/crypto/chacha20poly1305", "\x10\aV\a\xe2\x01\x04\x01\a"},
{"vendor/golang.org/x/crypto/cryptobyte", "j\n\x03\x90\x01'!\n"},
{"vendor/golang.org/x/crypto/cryptobyte/asn1", ""},
{"vendor/golang.org/x/crypto/internal/alias", "\xd3\x02"},
{"vendor/golang.org/x/crypto/internal/poly1305", "W\x15\x9c\x01"},
{"vendor/golang.org/x/net/dns/dnsmessage", "s\xc7\x01"},
{"vendor/golang.org/x/net/http/httpguts", "\x8f\x02\x14\x1a\x14\r"},
{"vendor/golang.org/x/net/http/httpproxy", "s\x03\x99\x01\x10\x05\x01\x18\x14\r"},
{"vendor/golang.org/x/net/http2/hpack", "\x03p\x03\x83\x01F"},
{"vendor/golang.org/x/net/idna", "v\x8f\x018\x14\x10\x02\x01"},
{"vendor/golang.org/x/net/nettest", "\x03i\a\x03\x83\x01\x11\x05\x16\x01\f\n\x01\x02\x02\x01\v"},
{"vendor/golang.org/x/sys/cpu", "\xa5\x02\r\n\x01\x16"},
{"vendor/golang.org/x/text/secure/bidirule", "s\xde\x01\x11\x01"},
{"vendor/golang.org/x/text/transform", "\x03p\x86\x01X"},
{"vendor/golang.org/x/text/unicode/bidi", "\x03\bk\x87\x01>\x16"},
{"vendor/golang.org/x/text/unicode/norm", "l\n\x83\x01F\x12\x11"},
{"weak", "\x9a\x01\x98\x01!"},
{"vendor/golang.org/x/crypto/internal/alias", "\xd5\x02"},
{"vendor/golang.org/x/crypto/internal/poly1305", "X\x15\x9c\x01"},
{"vendor/golang.org/x/net/dns/dnsmessage", "t\xc7\x01"},
{"vendor/golang.org/x/net/http/httpguts", "\x90\x02\x14\x1a\x15\r"},
{"vendor/golang.org/x/net/http/httpproxy", "t\x03\x99\x01\x10\x05\x01\x18\x15\r"},
{"vendor/golang.org/x/net/http2/hpack", "\x03q\x03\x83\x01F"},
{"vendor/golang.org/x/net/idna", "w\x8f\x018\x15\x10\x02\x01"},
{"vendor/golang.org/x/net/nettest", "\x03j\a\x03\x83\x01\x11\x05\x16\x01\f\n\x01\x02\x02\x01\f"},
{"vendor/golang.org/x/sys/cpu", "\xa6\x02\r\n\x01\x17"},
{"vendor/golang.org/x/text/secure/bidirule", "t\xdf\x01\x11\x01"},
{"vendor/golang.org/x/text/transform", "\x03q\x86\x01Y"},
{"vendor/golang.org/x/text/unicode/bidi", "\x03\bl\x87\x01>\x17"},
{"vendor/golang.org/x/text/unicode/norm", "m\n\x83\x01F\x13\x11"},
{"weak", "\x9b\x01\x98\x01\""},
}
// bootstrap is the list of bootstrap packages extracted from cmd/dist.

Some files were not shown because too many files have changed in this diff Show More