Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions charts/openstack-hypervisor-operator/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,6 @@ controllerManager:
- --metrics-bind-address=:8443
- --leader-elect
- --health-probe-bind-address=:8081
- --certificate-namespace=$(CERTIFICATE_NAMESPACE)
- --certificate-issuer-name=$(CERTIFICATE_ISSUER_NAME)
- --label-selector=$(LABEL_SELECTOR)
- --agent-namespaces=$(AGENT_NAMESPACES)
- --eviction-concurrency=$(EVICTION_CONCURRENCY)
- --eviction-trait-concurrency=$(EVICTION_TRAIT_CONCURRENCY)
containerSecurityContext:
allowPrivilegeEscalation: false
capabilities:
Expand Down
161 changes: 80 additions & 81 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@ package main
import (
"crypto/sha256"
"crypto/tls"
"errors"
"flag"
"fmt"
"os"
gruntime "runtime"
"strconv"
"strings"

"github.com/sapcc/go-api-declarations/bininfo"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"

// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
_ "k8s.io/client-go/plugin/pkg/client/auth"
Expand All @@ -52,10 +54,10 @@ import (
"sigs.k8s.io/controller-runtime/pkg/webhook"

kvmv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1"
"github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/config"
"github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/controller"
"github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/controller/eviction"
"github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/controller/ready"
"github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/global"
"github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/logger"

// +kubebuilder:scaffold:imports
Expand All @@ -77,19 +79,20 @@ func init() {
utilruntime.Must(cmapi.AddToScheme(scheme))
}

type options struct {
Shared config.Shared
Certificate controller.CertificateOptions
Eviction eviction.Options
}

func main() {
var version bool
var metricsAddr string
var enableLeaderElection bool
var probeAddr string
var secureMetrics bool
var enableHTTP2 bool
var tlsOpts []func(*tls.Config)
var certificateNamespace string
var certificateIssuerName string

flag.BoolVar(&version, "version", false,
"Print the version and exit.")
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
Expand All @@ -100,23 +103,8 @@ func main() {
"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.")
flag.BoolVar(&enableHTTP2, "enable-http2", false,
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
flag.StringVar(&global.LabelSelector, "label-selector", "", "Label selector to filter watched resources (namely nodes).")

var agentNamespacesFlag string
flag.StringVar(&agentNamespacesFlag, "agent-namespaces", "",
"Comma-separated list of namespaces to search for agent pods (nova-compute, neutron) during offboarding.")

flag.IntVar(&global.EvictionConcurrency, "eviction-concurrency", 1,
"Default maximum number of VM migrations to run concurrently while draining a hypervisor. Defaults to 1 (serial).")

var evictionTraitConcurrencyFlag string
flag.StringVar(&evictionTraitConcurrencyFlag, "eviction-trait-concurrency", "",
"Comma-separated TRAIT=N overrides for per-host migration concurrency, e.g. "+
"CUSTOM_HANA_EXCLUSIVE_HOST=1,CUSTOM_FOO=2. The lowest matching trait limit wins.")

flag.StringVar(&certificateNamespace, "certificate-namespace", "monsoon3", "The namespace for the certificates. ")
flag.StringVar(&certificateIssuerName, "certificate-issuer-name", "nova-hypervisor-agents-ca-issuer",
"Name of the certificate issuer.")
var cfg options

opts := ctrlzap.Options{
Development: true,
Expand All @@ -126,73 +114,84 @@ func main() {
}

opts.BindFlags(flag.CommandLine)
flag.Parse()

// Handle --version before validating required runtime flags so that
// `./manager --version` works without providing --agent-namespaces.
if version {
fmt.Printf("%s %s (%s/%s) %s\n",
bininfo.Component(), bininfo.VersionOr("devel"), gruntime.GOOS, gruntime.GOARCH,
bininfo.CommitOr("edge"))
os.Exit(0)

rootCmd := &cobra.Command{
Use: "manager",
Version: bininfo.VersionOr("devel"),
SilenceUsage: true,
Run: func(cmd *cobra.Command, args []string) {
run(cfg, metricsAddr, probeAddr, enableLeaderElection, secureMetrics, enableHTTP2, tlsOpts, opts)
},
}

if agentNamespacesFlag != "" {
// Deduplicate. Without this, repeated entries (e.g. from templated
// values) would cause redundant pod-list calls and inflate the
// AgentPodsEvicted condition message with duplicate pod names.
seen := map[string]struct{}{}
for ns := range strings.SplitSeq(agentNamespacesFlag, ",") {
ns = strings.TrimSpace(ns)
if ns == "" {
continue
rootCmd.SetVersionTemplate(fmt.Sprintf("%s %s (%s/%s) %s\n",
bininfo.Component(), bininfo.VersionOr("devel"), gruntime.GOOS, gruntime.GOARCH,
bininfo.CommitOr("edge")))

flags := rootCmd.Flags()
cfg.Shared.AddFlags(flags)
cfg.Certificate.AddFlags(flags)
cfg.Eviction.AddFlags(flags)
// Import the flags registered on the standard flag set (manager and
// logging flags) into the command.
flags.AddGoFlagSet(flag.CommandLine)

viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
viper.AutomaticEnv()
if err := viper.BindPFlags(flags); err != nil {
fmt.Fprintf(os.Stderr, "invalid configuration: %v\n", err)
os.Exit(1)
}

// Document each flag's environment variable.
flags.VisitAll(func(f *pflag.Flag) {
f.Usage += fmt.Sprintf(" (env %s)", strings.ToUpper(strings.ReplaceAll(f.Name, "-", "_")))
})

// Apply environment values to flags not given on the command line.
rootCmd.PreRun = func(cmd *cobra.Command, args []string) {
flags.VisitAll(func(f *pflag.Flag) {
if f.Name == "version" || f.Name == "help" {
return
}
if _, ok := seen[ns]; ok {
continue
if f.Changed {
return
}
seen[ns] = struct{}{}
global.AgentNamespaces = append(global.AgentNamespaces, ns)
}
val := viper.GetString(f.Name)
if val == "" || val == f.DefValue {
return
}
if err := flags.Set(f.Name, val); err != nil {
fmt.Fprintf(os.Stderr, "invalid configuration: %v\n", err)
os.Exit(1)
}
})
}

if len(global.AgentNamespaces) == 0 {
setupLog.Error(errors.New("--agent-namespaces is required"), "invalid configuration")
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}

if global.EvictionConcurrency < 1 {
setupLog.Error(errors.New("--eviction-concurrency must be >= 1"), "invalid configuration")
os.Exit(1)
}
// run validates the configuration and runs the controller manager.
func run(cfg options, metricsAddr, probeAddr string, enableLeaderElection, secureMetrics, enableHTTP2 bool, tlsOpts []func(*tls.Config), opts ctrlzap.Options) {
ctrl.SetLogger(ctrlzap.New(ctrlzap.UseFlagOptions(&opts)))

for entry := range strings.SplitSeq(evictionTraitConcurrencyFlag, ",") {
entry = strings.TrimSpace(entry)
if entry == "" {
continue
}
trait, value, ok := strings.Cut(entry, "=")
trait = strings.TrimSpace(trait)
n, err := strconv.Atoi(strings.TrimSpace(value))
if !ok || trait == "" || err != nil || n < 1 {
setupLog.Error(errors.New("invalid --eviction-trait-concurrency entry: "+entry),
"invalid configuration", "expected", "TRAIT=N with N >= 1")
os.Exit(1)
}
global.EvictionTraitConcurrency[trait] = n
if err := cfg.Shared.Validate(); err != nil {
setupLog.Error(err, "invalid configuration")
os.Exit(1)
}

if certificateIssuerName == "" {
setupLog.Error(errors.New("certificate-issuer-name cannot be empty"), "invalid certificate issuer name")
if err := cfg.Eviction.Validate(); err != nil {
setupLog.Error(err, "invalid configuration")
os.Exit(1)
}

if certificateNamespace == "" {
setupLog.Error(errors.New("certificate-namespace cannot be empty"), "invalid certificate namespace")
if err := cfg.Certificate.Validate(); err != nil {
setupLog.Error(err, "invalid configuration")
os.Exit(1)
}

ctrl.SetLogger(ctrlzap.New(ctrlzap.UseFlagOptions(&opts)))

// if the enable-http2 flag is false (the default), http/2 should be disabled
// due to its vulnerabilities. More specifically, disabling http/2 will
// prevent from being vulnerable to the HTTP/2 Stream Cancellation and
Expand Down Expand Up @@ -260,9 +259,9 @@ func main() {
&policyv1.PodDisruptionBudget{}: maintenanceCacheConfig,
},
}
if global.LabelSelector != "" {
setupLog.Info("setting up cache with label selector", "selector", global.LabelSelector)
selector, err := labels.Parse(global.LabelSelector)
if cfg.Shared.LabelSelector != "" {
setupLog.Info("setting up cache with label selector", "selector", cfg.Shared.LabelSelector)
selector, err := labels.Parse(cfg.Shared.LabelSelector)
if err != nil {
setupLog.Error(err, "unable to parse label selector")
os.Exit(1)
Expand All @@ -273,7 +272,7 @@ func main() {

h := sha256.New()
h.Write([]byte(leaderElectionID)) // Seed it with something "unique" to the project
h.Write([]byte(global.LabelSelector))
h.Write([]byte(cfg.Shared.LabelSelector))
leaderElectionID = fmt.Sprintf("%x.cloud.sap", h.Sum(nil))
}

Expand Down Expand Up @@ -315,7 +314,7 @@ func main() {
if err = (&controller.HypervisorController{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
}).SetupWithManager(mgr, cfg.Shared); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Hypervisor")
os.Exit(1)
}
Expand All @@ -331,7 +330,7 @@ func main() {
if err = (&eviction.EvictionReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
}).SetupWithManager(mgr, cfg.Eviction); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Eviction")
os.Exit(1)
}
Expand All @@ -355,15 +354,15 @@ func main() {
if err = (&controller.NodeCertificateController{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr, certificateNamespace, certificateIssuerName); err != nil {
}).SetupWithManager(mgr, cfg.Certificate); err != nil {
setupLog.Error(err, "unable to create controller", "certificate", "NodeCertificate")
os.Exit(1)
}

if err = (&controller.GardenerNodeLifecycleController{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr, certificateNamespace); err != nil {
}).SetupWithManager(mgr, cfg.Certificate.Namespace); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Maintenance")
os.Exit(1)
}
Expand Down
12 changes: 10 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ require (
github.com/onsi/ginkgo/v2 v2.33.1
github.com/onsi/gomega v1.44.0
github.com/sapcc/go-api-declarations v1.25.1
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
github.com/spf13/viper v1.21.0
go.uber.org/zap v1.28.0
golang.org/x/sync v0.24.0
k8s.io/api v0.37.1
Expand Down Expand Up @@ -54,6 +57,7 @@ require (
github.com/go-openapi/swag/typeutils v0.27.1 // indirect
github.com/go-openapi/swag/yamlutils v0.27.1 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gofrs/uuid/v5 v5.5.1 // indirect
github.com/google/cel-go v0.30.0 // indirect
github.com/google/gnostic-models v0.7.1 // indirect
Expand All @@ -68,13 +72,17 @@ require (
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pandatix/go-abnf v0.5.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.25.0 // indirect
github.com/prometheus/client_model v0.6.3 // indirect
github.com/prometheus/common v0.73.0 // indirect
github.com/prometheus/procfs v0.22.0 // indirect
github.com/spf13/cobra v1.10.2 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect
Expand Down
18 changes: 18 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjT
github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
Expand Down Expand Up @@ -80,6 +82,8 @@ github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAg
github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/gofrs/uuid/v5 v5.5.1 h1:z1Ce19/JwNidXpy3tOQc3241lnJLKdKyq/xlNvlD4Ng=
Expand Down Expand Up @@ -139,6 +143,8 @@ github.com/onsi/gomega v1.44.0 h1:eAiGl3Pw5jz5GQdDff0BcxYpAX1JxW8xD7mFUuwNfZQ=
github.com/onsi/gomega v1.44.0/go.mod h1:e/C2HwaZ1DhvjzXXuFhcR7hY7Sh9pl7MmoWKEjzwcdA=
github.com/pandatix/go-abnf v0.5.1 h1:iyLvSu2Aq12iE0r4n948Qw21BHDuckLq/8WIXAAF+HI=
github.com/pandatix/go-abnf v0.5.1/go.mod h1:VBmbtpFgQNEaQpyAvmi+Ecd9wAYJKzA3TmiB3L93sVg=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand All @@ -155,19 +161,31 @@ github.com/prometheus/procfs v0.22.0/go.mod h1:CvmFr/GVhIjIvWJZW3tgkODBQMRIf0EyW
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
github.com/sapcc/go-api-declarations v1.25.1 h1:Lrbf8TZY/ARg0J9IrlrObqnXjrzSzmYKQd/L/a71Lsg=
github.com/sapcc/go-api-declarations v1.25.1/go.mod h1:bJkK8wphzlmUmN5lxBv8BbfxUXMOkW+UiC10xBHXsFs=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
Expand Down
Loading
Loading