Files
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Mario
a739cb2805 Bump github.com/twmb/franz-go from 1.18.1 to 1.20.0 (#5766)
* Bump github.com/twmb/franz-go from 1.18.1 to 1.20.0

Bumps [github.com/twmb/franz-go](https://github.com/twmb/franz-go) from 1.18.1 to 1.20.0.
- [Changelog](https://github.com/twmb/franz-go/blob/master/CHANGELOG.md)
- [Commits](https://github.com/twmb/franz-go/compare/v1.18.1...v1.20.0)

---
updated-dependencies:
- dependency-name: github.com/twmb/franz-go
  dependency-version: 1.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>

* Update kfake

---------

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Mario <[email protected]>
2025-10-20 13:55:55 +02:00

352 lines
9.5 KiB
Go

// Package kversion specifies versions for Kafka request keys.
//
// Kafka technically has internal broker versions that bump multiple times per
// release. This package only defines releases and tip.
package kversion
import (
"bytes"
"fmt"
"maps"
"regexp"
"slices"
"sync"
"text/tabwriter"
"github.com/twmb/franz-go/pkg/kmsg"
)
// Versions is a list of versions, with each item corresponding to a Kafka key
// and each item's value corresponding to the max version supported.
type Versions struct {
reqs map[int16]req
}
func (vs *Versions) lazyInit() {
if vs.reqs == nil {
vs.reqs = make(map[int16]req)
}
}
var (
reFromString *regexp.Regexp
reFromStringOnce sync.Once
)
// VersionStrings returns all recognized versions, minus any patch, that can be
// used as input to FromString.
func VersionStrings() []string {
var vs []string
b := btip()
for b != nil && b.major >= 4 {
vs = append(vs, b.name())
b = b.prior
}
zk := ztip()
for zk != nil {
vs = append(vs, zk.name())
zk = zk.prior
}
return vs
}
// FromString returns a Versions from v.
// The expected input is:
// - for v0, v0.#.# or v0.#.#.#
// - for v1, v1.# or v1.#.#
//
// The "v" is optional.
func FromString(v string) *Versions {
reFromStringOnce.Do(func() {
// 0: entire string
// 1: v1+ match, minus patch
// 2: v0 match, minus subpatch
reFromString = regexp.MustCompile(`^(?:(v?[1-9]+\.\d+)(?:\.\d+)?|(v?0\.\d+\.\d+)(?:\.\d+)?)$`)
})
m := reFromString.FindStringSubmatch(v)
if m == nil {
return nil
}
v = m[1]
if m[2] != "" {
v = m[2]
}
withv := "v" + v
b := btip()
for b != nil && b.major >= 4 {
if n := b.name(); n == v || n == withv {
return &Versions{reqs: b.reqs}
}
b = b.prior
}
zk := ztip()
for zk != nil {
if n := zk.name(); n == v || n == withv {
return &Versions{reqs: zk.reqs}
}
zk = zk.prior
}
return nil
}
// FromApiVersionsResponse returns a Versions from a kmsg.ApiVersionsResponse.
func FromApiVersionsResponse(r *kmsg.ApiVersionsResponse) *Versions {
return &Versions{reqs: reqsFromApiVersions(r)}
}
// HasKey returns true if the versions contains the given key.
func (vs *Versions) HasKey(k int16) bool {
_, has := vs.LookupMaxKeyVersion(k)
return has
}
// LookupMaxKeyVersion returns the version for the given key and whether the
// key exists. If the key does not exist, this returns (-1, false).
func (vs *Versions) LookupMaxKeyVersion(k int16) (int16, bool) {
vs.lazyInit()
req, ok := vs.reqs[k]
if !ok {
return -1, false
}
return req.vmax, true
}
// SetMaxKeyVersion sets the max version for the given key. Setting a version
// to -1 removes the key entirely.
func (vs *Versions) SetMaxKeyVersion(k, v int16) {
vs.lazyInit()
if k < 0 || v < 0 {
delete(vs.reqs, k)
return
}
req := vs.reqs[k]
req.vmax = v
req.key = k // in case the key did not exist
vs.reqs[k] = req
}
// Equal returns whether two versions are equal.
func (vs *Versions) Equal(other *Versions) bool {
vs.lazyInit()
mereqs := maps.Clone(vs.reqs)
for k, oreq := range other.reqs {
mreq, ok := mereqs[k]
if !ok || mreq != oreq {
return false
}
delete(mereqs, k)
}
return len(mereqs) == 0
}
// EachMaxKeyVersion calls fn for each key and max version
func (vs *Versions) EachMaxKeyVersion(fn func(k, v int16)) {
vs.lazyInit()
keys := make([]int16, 0, len(vs.reqs))
for k := range vs.reqs {
keys = append(keys, k)
}
slices.Sort(keys)
for _, k := range keys {
fn(k, vs.reqs[k].vmax)
}
}
// VersionGuessOpt is an option to change how version guessing is done.
type VersionGuessOpt interface {
apply(*guessCfg)
}
type guessOpt struct{ fn func(*guessCfg) }
func (opt guessOpt) apply(cfg *guessCfg) { opt.fn(cfg) }
// SkipKeys skips the given keys while guessing versions.
func SkipKeys(keys ...int16) VersionGuessOpt {
return guessOpt{func(cfg *guessCfg) { cfg.skipKeys = keys }}
}
// TryRaftBroker previously attempted to version guess selecting for a raft
// broker.
//
// Deprecated: Zookeeper, KRaft broker, and KRaft controller are all checked
// and the best pick is chosen.
func TryRaftBroker() VersionGuessOpt {
return guessOpt{func(*guessCfg) {}}
}
// TryRaftController previously attempted to version guest selecting for a
// raft controller.
//
// Deprecated: Zookeeper, KRaft broker, and KRaft controller are all checked
// and the best pick is chosen.
func TryRaftController() VersionGuessOpt {
return guessOpt{func(*guessCfg) {}}
}
type guessCfg struct {
skipKeys []int16
}
// VersionGuess attempts to guess which version of Kafka these versions belong
// to. If an exact match can be determined, this returns a string in the format
// v0.#.# or v#.# (depending on whether Kafka is pre-1.0 or post). For
// example, v0.8.0 or v2.7.
//
// Patch numbers are not included in the guess as it is not possible to
// determine the Kafka patch version being used as a client.
//
// If the version is determined to be higher than kversion knows of or is tip,
// this package returns "at least v#.#".
//
// Custom versions, or in-between versions, are detected and return slightly
// more verbose strings.
//
// Options can be specified to change how version guessing is performed, for
// example, certain keys can be skipped, or the guessing can try evaluating the
// versions as Raft broker based versions.
//
// Internally, this function tries guessing the version against both KRaft and
// Kafka APIs. The more exact match is returned.
func (vs *Versions) VersionGuess(opts ...VersionGuessOpt) string {
zk := vs.versionGuess2(ztip(), opts...)
broker := vs.versionGuess2(btip(), opts...)
controller := vs.versionGuess2(ctip(), opts...)
ord := []guess{broker, zk, controller}
for _, g := range ord {
if g.how == guessExact {
return g.String()
}
}
for _, g := range ord {
if g.how == guessAtLeast {
return g.String()
}
}
// This is a custom version. We could do some advanced logic to try to
// return highest of all three guesses, but that may be inaccurate:
// KRaft may detect a higher guess because not all requests exist in
// KRaft. Instead, we just return our standard guess.
return zk.String()
}
type guess struct {
v1 string
v2 string // for between
how int8
}
const (
guessExact = iota
guessAtLeast
guessCustomUnknown
guessCustomAtLeast
guessBetween
guessNotEven
)
func (g guess) String() string {
switch g.how {
case guessExact:
return g.v1
case guessAtLeast:
return "at least " + g.v1
case guessCustomUnknown:
return "unknown custom version"
case guessCustomAtLeast:
return "unknown custom version at least " + g.v1
case guessBetween:
return "between " + g.v1 + " and " + g.v2
case guessNotEven:
return "not even " + g.v1
}
return g.v1
}
// String returns a string representation of the versions; the format may
// change.
func (vs *Versions) String() string {
var buf bytes.Buffer
w := tabwriter.NewWriter(&buf, 0, 0, 2, ' ', 0)
keys := make([]int16, 0, len(vs.reqs))
for k := range vs.reqs {
keys = append(keys, k)
}
for _, k := range keys {
name := kmsg.NameForKey(k)
if name == "" {
name = "Unknown"
}
fmt.Fprintf(w, "%s\t%d\n", name, vs.reqs[k].vmax)
}
w.Flush()
return buf.String()
}
func relversion(fns ...func() *release) *Versions {
// For 4.0+, we merge the Raft broker, Raft controller, and Zk broker
// requests. We merge *in order*: any key that exists is kept, any key
// that does not exist is merged.
var reqs map[int16]req
for _, fn := range fns {
if reqs == nil {
reqs = fn().reqs
continue
}
merge := fn().reqs
for k, req := range merge {
if _, ok := reqs[k]; ok {
continue
}
reqs[k] = req
}
}
return &Versions{reqs: reqs}
}
// Stable is a shortcut for the latest _released_ Kafka versions.
//
// This is the default version used in kgo to avoid breaking tip changes.
// The stable version is only bumped once kgo internally supports all
// features in the release.
func Stable() *Versions { return relversion(b41, c41, z39) }
// Tip is the latest defined Kafka key versions; this may be slightly out of date.
func Tip() *Versions { return relversion(ztip) }
func V0_8_0() *Versions { return relversion(z080) }
func V0_8_1() *Versions { return relversion(z081) }
func V0_8_2() *Versions { return relversion(z082) }
func V0_9_0() *Versions { return relversion(z090) }
func V0_10_0() *Versions { return relversion(z0100) }
func V0_10_1() *Versions { return relversion(z0101) }
func V0_10_2() *Versions { return relversion(z0102) }
func V0_11_0() *Versions { return relversion(z0110) }
func V1_0_0() *Versions { return relversion(z10) }
func V1_1_0() *Versions { return relversion(z11) }
func V2_0_0() *Versions { return relversion(z20) }
func V2_1_0() *Versions { return relversion(z21) }
func V2_2_0() *Versions { return relversion(z22) }
func V2_3_0() *Versions { return relversion(z23) }
func V2_4_0() *Versions { return relversion(z24) }
func V2_5_0() *Versions { return relversion(z25) }
func V2_6_0() *Versions { return relversion(z26) }
func V2_7_0() *Versions { return relversion(z27) }
func V2_8_0() *Versions { return relversion(z28) }
func V3_0_0() *Versions { return relversion(z30) }
func V3_1_0() *Versions { return relversion(z31) }
func V3_2_0() *Versions { return relversion(z32) }
func V3_3_0() *Versions { return relversion(z33) }
func V3_4_0() *Versions { return relversion(z34) }
func V3_5_0() *Versions { return relversion(z35) }
func V3_6_0() *Versions { return relversion(z36) }
func V3_7_0() *Versions { return relversion(z37) }
func V3_8_0() *Versions { return relversion(z38) }
func V3_9_0() *Versions { return relversion(z39) }
func V4_0_0() *Versions { return relversion(b40) }
func V4_1_0() *Versions { return relversion(b41) }