Merge pull request #4486 from RainbowMango/pr_bump_runtime163
Bump controller-runtime to v0.16.3
This commit is contained in:
commit
c218f93ce3
|
@ -10,7 +10,7 @@
|
|||
"version": "unversioned"
|
||||
},
|
||||
"paths": {
|
||||
"/apis": {
|
||||
"/apis/": {
|
||||
"get": {
|
||||
"description": "get available API versions",
|
||||
"consumes": [
|
||||
|
@ -40,7 +40,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/apis/autoscaling.karmada.io": {
|
||||
"/apis/autoscaling.karmada.io/": {
|
||||
"get": {
|
||||
"description": "get information of a group",
|
||||
"consumes": [
|
||||
|
@ -70,7 +70,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/apis/autoscaling.karmada.io/v1alpha1": {
|
||||
"/apis/autoscaling.karmada.io/v1alpha1/": {
|
||||
"get": {
|
||||
"description": "get available resources",
|
||||
"consumes": [
|
||||
|
@ -1986,7 +1986,7 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"/apis/cluster.karmada.io": {
|
||||
"/apis/cluster.karmada.io/": {
|
||||
"get": {
|
||||
"description": "get information of a group",
|
||||
"consumes": [
|
||||
|
@ -2016,7 +2016,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/apis/cluster.karmada.io/v1alpha1": {
|
||||
"/apis/cluster.karmada.io/v1alpha1/": {
|
||||
"get": {
|
||||
"description": "get available resources",
|
||||
"consumes": [
|
||||
|
@ -3285,7 +3285,7 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"/apis/config.karmada.io": {
|
||||
"/apis/config.karmada.io/": {
|
||||
"get": {
|
||||
"description": "get information of a group",
|
||||
"consumes": [
|
||||
|
@ -3315,7 +3315,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/apis/config.karmada.io/v1alpha1": {
|
||||
"/apis/config.karmada.io/v1alpha1/": {
|
||||
"get": {
|
||||
"description": "get available resources",
|
||||
"consumes": [
|
||||
|
@ -4917,7 +4917,7 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"/apis/networking.karmada.io": {
|
||||
"/apis/networking.karmada.io/": {
|
||||
"get": {
|
||||
"description": "get information of a group",
|
||||
"consumes": [
|
||||
|
@ -4947,7 +4947,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/apis/networking.karmada.io/v1alpha1": {
|
||||
"/apis/networking.karmada.io/v1alpha1/": {
|
||||
"get": {
|
||||
"description": "get available resources",
|
||||
"consumes": [
|
||||
|
@ -6863,7 +6863,7 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"/apis/policy.karmada.io": {
|
||||
"/apis/policy.karmada.io/": {
|
||||
"get": {
|
||||
"description": "get information of a group",
|
||||
"consumes": [
|
||||
|
@ -6893,7 +6893,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/apis/policy.karmada.io/v1alpha1": {
|
||||
"/apis/policy.karmada.io/v1alpha1/": {
|
||||
"get": {
|
||||
"description": "get available resources",
|
||||
"consumes": [
|
||||
|
@ -11324,7 +11324,7 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"/apis/search.karmada.io": {
|
||||
"/apis/search.karmada.io/": {
|
||||
"get": {
|
||||
"description": "get information of a group",
|
||||
"consumes": [
|
||||
|
@ -11354,7 +11354,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/apis/search.karmada.io/v1alpha1": {
|
||||
"/apis/search.karmada.io/v1alpha1/": {
|
||||
"get": {
|
||||
"description": "get available resources",
|
||||
"consumes": [
|
||||
|
@ -12170,7 +12170,7 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"/apis/work.karmada.io": {
|
||||
"/apis/work.karmada.io/": {
|
||||
"get": {
|
||||
"description": "get information of a group",
|
||||
"consumes": [
|
||||
|
@ -12200,7 +12200,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/apis/work.karmada.io/v1alpha1": {
|
||||
"/apis/work.karmada.io/v1alpha1/": {
|
||||
"get": {
|
||||
"description": "get available resources",
|
||||
"consumes": [
|
||||
|
@ -13173,7 +13173,7 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"/apis/work.karmada.io/v1alpha2": {
|
||||
"/apis/work.karmada.io/v1alpha2/": {
|
||||
"get": {
|
||||
"description": "get available resources",
|
||||
"consumes": [
|
||||
|
|
|
@ -38,6 +38,7 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/config"
|
||||
"sigs.k8s.io/controller-runtime/pkg/healthz"
|
||||
crtlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"
|
||||
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
|
||||
|
||||
"github.com/karmada-io/karmada/cmd/agent/app/options"
|
||||
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
|
||||
|
@ -196,7 +197,7 @@ func run(ctx context.Context, opts *options.Options) error {
|
|||
|
||||
controllerManager, err := controllerruntime.NewManager(controlPlaneRestConfig, controllerruntime.Options{
|
||||
Scheme: gclient.NewSchema(),
|
||||
Cache: cache.Options{SyncPeriod: &opts.ResyncPeriod.Duration, Namespaces: []string{executionSpace}},
|
||||
Cache: cache.Options{SyncPeriod: &opts.ResyncPeriod.Duration, DefaultNamespaces: map[string]cache.Config{executionSpace: {}}},
|
||||
LeaderElection: opts.LeaderElection.LeaderElect,
|
||||
LeaderElectionID: fmt.Sprintf("karmada-agent-%s", opts.ClusterName),
|
||||
LeaderElectionNamespace: opts.LeaderElection.ResourceNamespace,
|
||||
|
@ -206,7 +207,7 @@ func run(ctx context.Context, opts *options.Options) error {
|
|||
RetryPeriod: &opts.LeaderElection.RetryPeriod.Duration,
|
||||
HealthProbeBindAddress: net.JoinHostPort(opts.BindAddress, strconv.Itoa(opts.SecurePort)),
|
||||
LivenessEndpointName: "/healthz",
|
||||
MetricsBindAddress: opts.MetricsBindAddress,
|
||||
Metrics: metricsserver.Options{BindAddress: opts.MetricsBindAddress},
|
||||
MapperProvider: restmapper.MapperProvider,
|
||||
BaseContext: func() context.Context {
|
||||
return ctx
|
||||
|
|
|
@ -44,6 +44,7 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
"sigs.k8s.io/controller-runtime/pkg/healthz"
|
||||
crtlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"
|
||||
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
|
||||
"github.com/karmada-io/karmada/cmd/controller-manager/app/options"
|
||||
|
@ -155,7 +156,7 @@ func Run(ctx context.Context, opts *options.Options) error {
|
|||
LeaderElectionResourceLock: opts.LeaderElection.ResourceLock,
|
||||
HealthProbeBindAddress: net.JoinHostPort(opts.BindAddress, strconv.Itoa(opts.SecurePort)),
|
||||
LivenessEndpointName: "/healthz",
|
||||
MetricsBindAddress: opts.MetricsBindAddress,
|
||||
Metrics: metricsserver.Options{BindAddress: opts.MetricsBindAddress},
|
||||
MapperProvider: restmapper.MapperProvider,
|
||||
BaseContext: func() context.Context {
|
||||
return ctx
|
||||
|
|
|
@ -29,6 +29,7 @@ import (
|
|||
"k8s.io/klog/v2"
|
||||
controllerruntime "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/healthz"
|
||||
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/conversion"
|
||||
|
@ -142,7 +143,7 @@ func Run(ctx context.Context, opts *options.Options) error {
|
|||
},
|
||||
}),
|
||||
LeaderElection: false,
|
||||
MetricsBindAddress: opts.MetricsBindAddress,
|
||||
Metrics: metricsserver.Options{BindAddress: opts.MetricsBindAddress},
|
||||
HealthProbeBindAddress: opts.HealthProbeBindAddress,
|
||||
})
|
||||
if err != nil {
|
||||
|
|
|
@ -29,6 +29,7 @@ import (
|
|||
"k8s.io/klog/v2"
|
||||
controllerruntime "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/healthz"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
||||
|
||||
"github.com/karmada-io/karmada/examples/customresourceinterpreter/webhook/app/options"
|
||||
"github.com/karmada-io/karmada/pkg/sharedcli"
|
||||
|
@ -86,9 +87,11 @@ func Run(ctx context.Context, opts *options.Options) error {
|
|||
|
||||
hookManager, err := controllerruntime.NewManager(config, controllerruntime.Options{
|
||||
Logger: klog.Background(),
|
||||
WebhookServer: webhook.NewServer(webhook.Options{
|
||||
Host: opts.BindAddress,
|
||||
Port: opts.SecurePort,
|
||||
CertDir: opts.CertDir,
|
||||
}),
|
||||
LeaderElection: false,
|
||||
})
|
||||
if err != nil {
|
||||
|
|
10
go.mod
10
go.mod
|
@ -15,7 +15,7 @@ require (
|
|||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/onsi/ginkgo/v2 v2.11.0
|
||||
github.com/onsi/gomega v1.27.8
|
||||
github.com/onsi/gomega v1.27.10
|
||||
github.com/opensearch-project/opensearch-go v1.1.0
|
||||
github.com/prometheus/client_golang v1.16.0
|
||||
github.com/spf13/cobra v1.7.0
|
||||
|
@ -29,7 +29,7 @@ require (
|
|||
golang.org/x/text v0.14.0
|
||||
golang.org/x/time v0.3.0
|
||||
golang.org/x/tools v0.16.1
|
||||
gomodules.xyz/jsonpatch/v2 v2.3.0
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0
|
||||
google.golang.org/grpc v1.56.3
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.28.5
|
||||
|
@ -50,7 +50,7 @@ require (
|
|||
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2
|
||||
layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf
|
||||
sigs.k8s.io/cluster-api v1.5.0
|
||||
sigs.k8s.io/controller-runtime v0.15.0
|
||||
sigs.k8s.io/controller-runtime v0.16.3
|
||||
sigs.k8s.io/custom-metrics-apiserver v1.27.0
|
||||
sigs.k8s.io/kind v0.20.0
|
||||
sigs.k8s.io/mcs-api v0.1.0
|
||||
|
@ -77,7 +77,7 @@ require (
|
|||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.10.2 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
|
||||
github.com/fatih/camelcase v1.0.0 // indirect
|
||||
|
@ -164,7 +164,7 @@ require (
|
|||
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.24.0 // indirect
|
||||
go.uber.org/zap v1.25.0 // indirect
|
||||
golang.org/x/crypto v0.16.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
|
|
21
go.sum
21
go.sum
|
@ -108,8 +108,8 @@ github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGL
|
|||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go v1.42.27/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
|
@ -203,8 +203,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
|||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/emicklei/go-restful/v3 v3.10.2 h1:hIovbnmBTLjHXkqEBUz3HGpXZdM7ZrE9fJIZIqlJLqE=
|
||||
github.com/emicklei/go-restful/v3 v3.10.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
|
@ -628,8 +628,8 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
|
|||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
|
||||
github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ=
|
||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
|
||||
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.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
|
@ -872,8 +872,9 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
|||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||
go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c=
|
||||
go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
|
@ -1215,8 +1216,8 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
|
|||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU=
|
||||
gomodules.xyz/jsonpatch/v2 v2.3.0 h1:8NFhfS6gzxNqjLIYnZxg319wZ5Qjnx4m/CcX+Klzazc=
|
||||
gomodules.xyz/jsonpatch/v2 v2.3.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
|
||||
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
|
@ -1502,8 +1503,8 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2/go.mod h1:+qG7ISX
|
|||
sigs.k8s.io/cluster-api v1.5.0 h1:pwXvzScbAwnrB7EWHTApzW+VQfrj2OSrWAQDC9+bcbU=
|
||||
sigs.k8s.io/cluster-api v1.5.0/go.mod h1:ZSEP01t8oT6104gB4ljsOwwp5uJcI8SWy8IFp2HUvrc=
|
||||
sigs.k8s.io/controller-runtime v0.6.1/go.mod h1:XRYBPdbf5XJu9kpS84VJiZ7h/u1hF3gEORz0efEja7A=
|
||||
sigs.k8s.io/controller-runtime v0.15.0 h1:ML+5Adt3qZnMSYxZ7gAverBLNPSMQEibtzAgp0UPojU=
|
||||
sigs.k8s.io/controller-runtime v0.15.0/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk=
|
||||
sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4=
|
||||
sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0=
|
||||
sigs.k8s.io/controller-tools v0.3.0/go.mod h1:enhtKGfxZD1GFEoMgP8Fdbu+uKQ/cq1/WGJhdVChfvI=
|
||||
sigs.k8s.io/custom-metrics-apiserver v1.27.0 h1:kaZwUqVBCf9L3cfQ1VtdKPLYo3rp5dN2SbNf1UXHt24=
|
||||
sigs.k8s.io/custom-metrics-apiserver v1.27.0/go.mod h1:204Z2fcsiUjBM0UV6o3TCqfvmunN+607ohKqbnda3q0=
|
||||
|
|
|
@ -31,8 +31,10 @@ import (
|
|||
"k8s.io/component-base/term"
|
||||
"k8s.io/klog/v2"
|
||||
controllerruntime "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/cache"
|
||||
"sigs.k8s.io/controller-runtime/pkg/config"
|
||||
"sigs.k8s.io/controller-runtime/pkg/healthz"
|
||||
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
|
||||
|
||||
"github.com/karmada-io/karmada/operator/cmd/operator/app/options"
|
||||
operatorv1alpha1 "github.com/karmada-io/karmada/operator/pkg/apis/operator/v1alpha1"
|
||||
|
@ -164,7 +166,7 @@ func createControllerManager(ctx context.Context, o *options.Options) (controlle
|
|||
BaseContext: func() context.Context {
|
||||
return ctx
|
||||
},
|
||||
SyncPeriod: &o.ResyncPeriod.Duration,
|
||||
Cache: cache.Options{SyncPeriod: &o.ResyncPeriod.Duration},
|
||||
LeaderElection: o.LeaderElection.LeaderElect,
|
||||
LeaderElectionID: o.LeaderElection.ResourceName,
|
||||
LeaderElectionNamespace: o.LeaderElection.ResourceNamespace,
|
||||
|
@ -174,7 +176,7 @@ func createControllerManager(ctx context.Context, o *options.Options) (controlle
|
|||
LeaderElectionResourceLock: o.LeaderElection.ResourceLock,
|
||||
HealthProbeBindAddress: net.JoinHostPort(o.BindAddress, strconv.Itoa(o.SecurePort)),
|
||||
LivenessEndpointName: "/healthz",
|
||||
MetricsBindAddress: o.MetricsBindAddress,
|
||||
Metrics: metricsserver.Options{BindAddress: o.MetricsBindAddress},
|
||||
Controller: config.Controller{
|
||||
GroupKindConcurrency: map[string]int{
|
||||
operatorv1alpha1.SchemeGroupVersion.WithKind("Karmada").GroupKind().String(): o.ConcurrentKarmadaSyncs,
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
# Change history of go-restful
|
||||
|
||||
## [v3.10.2] - 2023-03-09
|
||||
## [v3.11.0] - 2023-08-19
|
||||
|
||||
- restored behavior as <= v3.9.0 with option to change path strategy using TrimRightSlashEnabled.
|
||||
|
||||
## [v3.10.2] - 2023-03-09 - DO NOT USE
|
||||
|
||||
- introduced MergePathStrategy to be able to revert behaviour of path concatenation to 3.9.0
|
||||
see comment in Readme how to customize this behaviour.
|
||||
|
||||
## [v3.10.1] - 2022-11-19
|
||||
## [v3.10.1] - 2022-11-19 - DO NOT USE
|
||||
|
||||
- fix broken 3.10.0 by using path package for joining paths
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ func (u UserResource) findUser(request *restful.Request, response *restful.Respo
|
|||
- Content encoding (gzip,deflate) of request and response payloads
|
||||
- Automatic responses on OPTIONS (using a filter)
|
||||
- Automatic CORS request handling (using a filter)
|
||||
- API declaration for Swagger UI ([go-restful-openapi](https://github.com/emicklei/go-restful-openapi), see [go-restful-swagger12](https://github.com/emicklei/go-restful-swagger12))
|
||||
- API declaration for Swagger UI ([go-restful-openapi](https://github.com/emicklei/go-restful-openapi))
|
||||
- Panic recovery to produce HTTP 500, customizable using RecoverHandler(...)
|
||||
- Route errors produce HTTP 404/405/406/415 errors, customizable using ServiceErrorHandler(...)
|
||||
- Configurable (trace) logging
|
||||
|
@ -96,10 +96,7 @@ There are several hooks to customize the behavior of the go-restful package.
|
|||
- Compression
|
||||
- Encoders for other serializers
|
||||
- Use [jsoniter](https://github.com/json-iterator/go) by building this package using a build tag, e.g. `go build -tags=jsoniter .`
|
||||
- Use the variable `MergePathStrategy` to change the behaviour of composing the Route path given a root path and a local route path
|
||||
- versions >= 3.10.1 has set the value to `PathJoinStrategy` that fixes a reported [security issue](https://github.com/advisories/GHSA-r48q-9g5r-8q2h) but may cause your services not to work correctly anymore.
|
||||
- versions <= 3.9 had the behaviour that can be restored in newer versions by setting the value to `TrimSlashStrategy`.
|
||||
- you can set value to a custom implementation (must implement MergePathStrategyFunc)
|
||||
- Use the package variable `TrimRightSlashEnabled` (default true) to control the behavior of matching routes that end with a slash `/`
|
||||
|
||||
## Resources
|
||||
|
||||
|
@ -112,4 +109,4 @@ There are several hooks to customize the behavior of the go-restful package.
|
|||
|
||||
Type ```git shortlog -s``` for a full list of contributors.
|
||||
|
||||
© 2012 - 2022, http://ernestmicklei.com. MIT License. Contributions are welcome.
|
||||
© 2012 - 2023, http://ernestmicklei.com. MIT License. Contributions are welcome.
|
||||
|
|
|
@ -41,6 +41,7 @@ type Route struct {
|
|||
ResponseErrors map[int]ResponseError
|
||||
DefaultResponse *ResponseError
|
||||
ReadSample, WriteSample interface{} // structs that model an example request or response payload
|
||||
WriteSamples []interface{} // if more than one return types is possible (oneof) then this will contain multiple values
|
||||
|
||||
// Extra information used to store custom information about the route.
|
||||
Metadata map[string]interface{}
|
||||
|
@ -164,7 +165,13 @@ func tokenizePath(path string) []string {
|
|||
if "/" == path {
|
||||
return nil
|
||||
}
|
||||
if TrimRightSlashEnabled {
|
||||
// 3.9.0
|
||||
return strings.Split(strings.Trim(path, "/"), "/")
|
||||
} else {
|
||||
// 3.10.2
|
||||
return strings.Split(strings.TrimLeft(path, "/"), "/")
|
||||
}
|
||||
}
|
||||
|
||||
// for debugging
|
||||
|
@ -177,4 +184,8 @@ func (r *Route) EnableContentEncoding(enabled bool) {
|
|||
r.contentEncodingEnabled = &enabled
|
||||
}
|
||||
|
||||
var TrimRightSlashEnabled = false
|
||||
// TrimRightSlashEnabled controls whether
|
||||
// - path on route building is using path.Join
|
||||
// - the path of the incoming request is trimmed of its slash suffux.
|
||||
// Value of true matches the behavior of <= 3.9.0
|
||||
var TrimRightSlashEnabled = true
|
||||
|
|
|
@ -34,7 +34,8 @@ type RouteBuilder struct {
|
|||
doc string
|
||||
notes string
|
||||
operation string
|
||||
readSample, writeSample interface{}
|
||||
readSample interface{}
|
||||
writeSamples []interface{}
|
||||
parameters []*Parameter
|
||||
errorMap map[int]ResponseError
|
||||
defaultResponse *ResponseError
|
||||
|
@ -135,9 +136,9 @@ func (b RouteBuilder) ParameterNamed(name string) (p *Parameter) {
|
|||
return p
|
||||
}
|
||||
|
||||
// Writes tells what resource type will be written as the response payload. Optional.
|
||||
func (b *RouteBuilder) Writes(sample interface{}) *RouteBuilder {
|
||||
b.writeSample = sample
|
||||
// Writes tells which one of the resource types will be written as the response payload. Optional.
|
||||
func (b *RouteBuilder) Writes(samples ...interface{}) *RouteBuilder {
|
||||
b.writeSamples = samples // oneof
|
||||
return b
|
||||
}
|
||||
|
||||
|
@ -342,39 +343,29 @@ func (b *RouteBuilder) Build() Route {
|
|||
ResponseErrors: b.errorMap,
|
||||
DefaultResponse: b.defaultResponse,
|
||||
ReadSample: b.readSample,
|
||||
WriteSample: b.writeSample,
|
||||
WriteSamples: b.writeSamples,
|
||||
Metadata: b.metadata,
|
||||
Deprecated: b.deprecated,
|
||||
contentEncodingEnabled: b.contentEncodingEnabled,
|
||||
allowedMethodsWithoutContentType: b.allowedMethodsWithoutContentType,
|
||||
}
|
||||
// set WriteSample if one specified
|
||||
if len(b.writeSamples) == 1 {
|
||||
route.WriteSample = b.writeSamples[0]
|
||||
}
|
||||
route.Extensions = b.extensions
|
||||
route.postBuild()
|
||||
return route
|
||||
}
|
||||
|
||||
type MergePathStrategyFunc func(rootPath, routePath string) string
|
||||
|
||||
var (
|
||||
// behavior >= 3.10
|
||||
PathJoinStrategy = func(rootPath, routePath string) string {
|
||||
return path.Join(rootPath, routePath)
|
||||
}
|
||||
|
||||
// behavior <= 3.9
|
||||
TrimSlashStrategy = func(rootPath, routePath string) string {
|
||||
return strings.TrimRight(rootPath, "/") + "/" + strings.TrimLeft(routePath, "/")
|
||||
}
|
||||
|
||||
// MergePathStrategy is the active strategy for merging a Route path when building the routing of all WebServices.
|
||||
// The value is set to PathJoinStrategy
|
||||
// PathJoinStrategy is a strategy that is more strict [Security - PRISMA-2022-0227]
|
||||
MergePathStrategy = PathJoinStrategy
|
||||
)
|
||||
|
||||
// merge two paths using the current (package global) merge path strategy.
|
||||
func concatPath(rootPath, routePath string) string {
|
||||
return MergePathStrategy(rootPath, routePath)
|
||||
|
||||
if TrimRightSlashEnabled {
|
||||
return strings.TrimRight(rootPath, "/") + "/" + strings.TrimLeft(routePath, "/")
|
||||
} else {
|
||||
return path.Join(rootPath, routePath)
|
||||
}
|
||||
}
|
||||
|
||||
var anonymousFuncCount int32
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
## 1.27.10
|
||||
|
||||
### Fixes
|
||||
- fix: go 1.21 adding goroutine ID to creator+location (#685) [bdc7803]
|
||||
|
||||
## 1.27.9
|
||||
|
||||
### Fixes
|
||||
- Prevent nil-dereference in format.Object for boxed nil error (#681) [3b31fc3]
|
||||
|
||||
### Maintenance
|
||||
- Bump golang.org/x/net from 0.11.0 to 0.12.0 (#679) [360849b]
|
||||
- chore: use String() instead of fmt.Sprintf (#678) [86f3659]
|
||||
- Bump golang.org/x/net from 0.10.0 to 0.11.0 (#674) [642ead0]
|
||||
- chore: unnecessary use of fmt.Sprintf (#677) [ceb9ca6]
|
||||
- Bump github.com/onsi/ginkgo/v2 from 2.10.0 to 2.11.0 (#675) [a2087d8]
|
||||
- docs: fix ContainSubstring references (#673) [fc9a89f]
|
||||
- Bump github.com/onsi/ginkgo/v2 from 2.9.7 to 2.10.0 (#671) [9076019]
|
||||
|
||||
## 1.27.8
|
||||
|
||||
### Fixes
|
||||
|
|
|
@ -259,7 +259,7 @@ func Object(object interface{}, indentation uint) string {
|
|||
indent := strings.Repeat(Indent, int(indentation))
|
||||
value := reflect.ValueOf(object)
|
||||
commonRepresentation := ""
|
||||
if err, ok := object.(error); ok {
|
||||
if err, ok := object.(error); ok && !isNilValue(value) { // isNilValue check needed here to avoid nil deref due to boxed nil
|
||||
commonRepresentation += "\n" + IndentString(err.Error(), indentation) + "\n" + indent
|
||||
}
|
||||
return fmt.Sprintf("%s<%s>: %s%s", indent, formatType(value), commonRepresentation, formatValue(value, indentation))
|
||||
|
@ -302,7 +302,7 @@ func formatType(v reflect.Value) string {
|
|||
case reflect.Map:
|
||||
return fmt.Sprintf("%s | len:%d", v.Type(), v.Len())
|
||||
default:
|
||||
return fmt.Sprintf("%s", v.Type())
|
||||
return v.Type().String()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ import (
|
|||
"github.com/onsi/gomega/types"
|
||||
)
|
||||
|
||||
const GOMEGA_VERSION = "1.27.8"
|
||||
const GOMEGA_VERSION = "1.27.10"
|
||||
|
||||
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().
|
||||
|
|
|
@ -94,7 +94,7 @@ func Succeed() types.GomegaMatcher {
|
|||
//
|
||||
// Expect(err).Should(MatchError("an error")) //asserts that err.Error() == "an error"
|
||||
// Expect(err).Should(MatchError(SomeError)) //asserts that err == SomeError (via reflect.DeepEqual)
|
||||
// Expect(err).Should(MatchError(ContainsSubstring("sprocket not found"))) // asserts that edrr.Error() contains substring "sprocket not found"
|
||||
// Expect(err).Should(MatchError(ContainSubstring("sprocket not found"))) // asserts that edrr.Error() contains substring "sprocket not found"
|
||||
//
|
||||
// It is an error for err to be nil or an object that does not implement the
|
||||
// Error interface
|
||||
|
|
|
@ -52,5 +52,5 @@ func (matcher *BeADirectoryMatcher) FailureMessage(actual interface{}) (message
|
|||
}
|
||||
|
||||
func (matcher *BeADirectoryMatcher) NegatedFailureMessage(actual interface{}) (message string) {
|
||||
return format.Message(actual, fmt.Sprintf("not be a directory"))
|
||||
return format.Message(actual, "not be a directory")
|
||||
}
|
||||
|
|
|
@ -52,5 +52,5 @@ func (matcher *BeARegularFileMatcher) FailureMessage(actual interface{}) (messag
|
|||
}
|
||||
|
||||
func (matcher *BeARegularFileMatcher) NegatedFailureMessage(actual interface{}) (message string) {
|
||||
return format.Message(actual, fmt.Sprintf("not be a regular file"))
|
||||
return format.Message(actual, "not be a regular file")
|
||||
}
|
||||
|
|
|
@ -32,9 +32,9 @@ func (matcher *BeAnExistingFileMatcher) Match(actual interface{}) (success bool,
|
|||
}
|
||||
|
||||
func (matcher *BeAnExistingFileMatcher) FailureMessage(actual interface{}) (message string) {
|
||||
return format.Message(actual, fmt.Sprintf("to exist"))
|
||||
return format.Message(actual, "to exist")
|
||||
}
|
||||
|
||||
func (matcher *BeAnExistingFileMatcher) NegatedFailureMessage(actual interface{}) (message string) {
|
||||
return format.Message(actual, fmt.Sprintf("not to exist"))
|
||||
return format.Message(actual, "not to exist")
|
||||
}
|
||||
|
|
|
@ -3,6 +3,27 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## 1.25.0 (1 Aug 2023)
|
||||
|
||||
This release contains several improvements including performance, API additions,
|
||||
and two new experimental packages whose APIs are unstable and may change in the
|
||||
future.
|
||||
|
||||
Enhancements:
|
||||
* [#1246][]: Add `zap/exp/zapslog` package for integration with slog.
|
||||
* [#1273][]: Add `Name` to `Logger` which returns the Logger's name if one is set.
|
||||
* [#1281][]: Add `zap/exp/expfield` package which contains helper methods
|
||||
`Str` and `Strs` for constructing String-like zap.Fields.
|
||||
* [#1310][]: Reduce stack size on `Any`.
|
||||
|
||||
Thanks to @knight42, @dzakaammar, @bcspragu, and @rexywork for their contributions
|
||||
to this release.
|
||||
|
||||
[#1246]: https://github.com/uber-go/zap/pull/1246
|
||||
[#1273]: https://github.com/uber-go/zap/pull/1273
|
||||
[#1281]: https://github.com/uber-go/zap/pull/1281
|
||||
[#1310]: https://github.com/uber-go/zap/pull/1310
|
||||
|
||||
## 1.24.0 (30 Nov 2022)
|
||||
|
||||
Enhancements:
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
export GOBIN ?= $(shell pwd)/bin
|
||||
|
||||
GOLINT = $(GOBIN)/golint
|
||||
REVIVE = $(GOBIN)/revive
|
||||
STATICCHECK = $(GOBIN)/staticcheck
|
||||
GOVULNCHECK = $(GOBIN)/govulncheck
|
||||
BENCH_FLAGS ?= -cpuprofile=cpu.pprof -memprofile=mem.pprof -benchmem
|
||||
|
||||
# Directories containing independent Go modules.
|
||||
#
|
||||
# We track coverage only for the main module.
|
||||
MODULE_DIRS = . ./benchmarks ./zapgrpc/internal/test
|
||||
MODULE_DIRS = . ./exp ./benchmarks ./zapgrpc/internal/test
|
||||
|
||||
# Many Go tools take file globs or directories as arguments instead of packages.
|
||||
GO_FILES := $(shell \
|
||||
|
@ -18,14 +19,15 @@ GO_FILES := $(shell \
|
|||
all: lint test
|
||||
|
||||
.PHONY: lint
|
||||
lint: $(GOLINT) $(STATICCHECK)
|
||||
lint: $(REVIVE) $(STATICCHECK)
|
||||
@rm -rf lint.log
|
||||
@echo "Checking formatting..."
|
||||
@gofmt -d -s $(GO_FILES) 2>&1 | tee lint.log
|
||||
@echo "Checking vet..."
|
||||
@$(foreach dir,$(MODULE_DIRS),(cd $(dir) && go vet ./... 2>&1) &&) true | tee -a lint.log
|
||||
@echo "Checking lint..."
|
||||
@$(foreach dir,$(MODULE_DIRS),(cd $(dir) && $(GOLINT) ./... 2>&1) &&) true | tee -a lint.log
|
||||
@$(foreach dir,$(MODULE_DIRS),(cd $(dir) && \
|
||||
$(REVIVE) -set_exit_status ./... 2>&1) &&) true | tee -a lint.log
|
||||
@echo "Checking staticcheck..."
|
||||
@$(foreach dir,$(MODULE_DIRS),(cd $(dir) && $(STATICCHECK) ./... 2>&1) &&) true | tee -a lint.log
|
||||
@echo "Checking for unresolved FIXMEs..."
|
||||
|
@ -40,8 +42,11 @@ lint: $(GOLINT) $(STATICCHECK)
|
|||
git --no-pager diff; \
|
||||
fi
|
||||
|
||||
$(GOLINT):
|
||||
cd tools && go install golang.org/x/lint/golint
|
||||
$(REVIVE):
|
||||
cd tools && go install github.com/mgechev/revive
|
||||
|
||||
$(GOVULNCHECK):
|
||||
cd tools && go install golang.org/x/vuln/cmd/govulncheck
|
||||
|
||||
$(STATICCHECK):
|
||||
cd tools && go install honnef.co/go/tools/cmd/staticcheck
|
||||
|
@ -71,3 +76,7 @@ updatereadme:
|
|||
.PHONY: tidy
|
||||
tidy:
|
||||
@$(foreach dir,$(MODULE_DIRS),(cd $(dir) && go mod tidy) &&) true
|
||||
|
||||
.PHONY: vulncheck
|
||||
vulncheck: $(GOVULNCHECK)
|
||||
$(GOVULNCHECK) ./...
|
|
@ -54,7 +54,7 @@ and make many small allocations. Put differently, using `encoding/json` and
|
|||
Zap takes a different approach. It includes a reflection-free, zero-allocation
|
||||
JSON encoder, and the base `Logger` strives to avoid serialization overhead
|
||||
and allocations wherever possible. By building the high-level `SugaredLogger`
|
||||
on that foundation, zap lets users _choose_ when they need to count every
|
||||
on that foundation, zap lets users *choose* when they need to count every
|
||||
allocation and when they'd prefer a more familiar, loosely typed API.
|
||||
|
||||
As measured by its own [benchmarking suite][], not only is zap more performant
|
||||
|
@ -65,39 +65,42 @@ id="anchor-versions">[1](#footnote-versions)</sup>
|
|||
Log a message and 10 fields:
|
||||
|
||||
| Package | Time | Time % to zap | Objects Allocated |
|
||||
| :------------------ | :---------: | :-----------: | :---------------: |
|
||||
| :zap: zap | 2900 ns/op | +0% | 5 allocs/op |
|
||||
| :zap: zap (sugared) | 3475 ns/op | +20% | 10 allocs/op |
|
||||
| zerolog | 10639 ns/op | +267% | 32 allocs/op |
|
||||
| go-kit | 14434 ns/op | +398% | 59 allocs/op |
|
||||
| logrus | 17104 ns/op | +490% | 81 allocs/op |
|
||||
| apex/log | 32424 ns/op | +1018% | 66 allocs/op |
|
||||
| log15 | 33579 ns/op | +1058% | 76 allocs/op |
|
||||
| :------ | :--: | :-----------: | :---------------: |
|
||||
| :zap: zap | 1744 ns/op | +0% | 5 allocs/op
|
||||
| :zap: zap (sugared) | 2483 ns/op | +42% | 10 allocs/op
|
||||
| zerolog | 918 ns/op | -47% | 1 allocs/op
|
||||
| go-kit | 5590 ns/op | +221% | 57 allocs/op
|
||||
| slog | 5640 ns/op | +223% | 40 allocs/op
|
||||
| apex/log | 21184 ns/op | +1115% | 63 allocs/op
|
||||
| logrus | 24338 ns/op | +1296% | 79 allocs/op
|
||||
| log15 | 26054 ns/op | +1394% | 74 allocs/op
|
||||
|
||||
Log a message with a logger that already has 10 fields of context:
|
||||
|
||||
| Package | Time | Time % to zap | Objects Allocated |
|
||||
| :------------------ | :---------: | :-----------: | :---------------: |
|
||||
| :zap: zap | 373 ns/op | +0% | 0 allocs/op |
|
||||
| :zap: zap (sugared) | 452 ns/op | +21% | 1 allocs/op |
|
||||
| zerolog | 288 ns/op | -23% | 0 allocs/op |
|
||||
| go-kit | 11785 ns/op | +3060% | 58 allocs/op |
|
||||
| logrus | 19629 ns/op | +5162% | 70 allocs/op |
|
||||
| log15 | 21866 ns/op | +5762% | 72 allocs/op |
|
||||
| apex/log | 30890 ns/op | +8182% | 55 allocs/op |
|
||||
| :------ | :--: | :-----------: | :---------------: |
|
||||
| :zap: zap | 193 ns/op | +0% | 0 allocs/op
|
||||
| :zap: zap (sugared) | 227 ns/op | +18% | 1 allocs/op
|
||||
| zerolog | 81 ns/op | -58% | 0 allocs/op
|
||||
| slog | 322 ns/op | +67% | 0 allocs/op
|
||||
| go-kit | 5377 ns/op | +2686% | 56 allocs/op
|
||||
| apex/log | 19518 ns/op | +10013% | 53 allocs/op
|
||||
| log15 | 19812 ns/op | +10165% | 70 allocs/op
|
||||
| logrus | 21997 ns/op | +11297% | 68 allocs/op
|
||||
|
||||
Log a static string, without any context or `printf`-style templating:
|
||||
|
||||
| Package | Time | Time % to zap | Objects Allocated |
|
||||
| :------------------ | :--------: | :-----------: | :---------------: |
|
||||
| :zap: zap | 381 ns/op | +0% | 0 allocs/op |
|
||||
| :zap: zap (sugared) | 410 ns/op | +8% | 1 allocs/op |
|
||||
| zerolog | 369 ns/op | -3% | 0 allocs/op |
|
||||
| standard library | 385 ns/op | +1% | 2 allocs/op |
|
||||
| go-kit | 606 ns/op | +59% | 11 allocs/op |
|
||||
| logrus | 1730 ns/op | +354% | 25 allocs/op |
|
||||
| apex/log | 1998 ns/op | +424% | 7 allocs/op |
|
||||
| log15 | 4546 ns/op | +1093% | 22 allocs/op |
|
||||
| :------ | :--: | :-----------: | :---------------: |
|
||||
| :zap: zap | 165 ns/op | +0% | 0 allocs/op
|
||||
| :zap: zap (sugared) | 212 ns/op | +28% | 1 allocs/op
|
||||
| zerolog | 95 ns/op | -42% | 0 allocs/op
|
||||
| slog | 296 ns/op | +79% | 0 allocs/op
|
||||
| go-kit | 415 ns/op | +152% | 9 allocs/op
|
||||
| standard library | 422 ns/op | +156% | 2 allocs/op
|
||||
| apex/log | 1601 ns/op | +870% | 5 allocs/op
|
||||
| logrus | 3017 ns/op | +1728% | 23 allocs/op
|
||||
| log15 | 3469 ns/op | +2002% | 20 allocs/op
|
||||
|
||||
## Development Status: Stable
|
||||
|
||||
|
@ -131,3 +134,4 @@ pinned in the [benchmarks/go.mod][] file. [↩](#anchor-versions)
|
|||
[cov]: https://codecov.io/gh/uber-go/zap
|
||||
[benchmarking suite]: https://github.com/uber-go/zap/tree/master/benchmarks
|
||||
[benchmarks/go.mod]: https://github.com/uber-go/zap/blob/master/benchmarks/go.mod
|
||||
|
||||
|
|
|
@ -20,25 +20,29 @@
|
|||
|
||||
package buffer
|
||||
|
||||
import "sync"
|
||||
import (
|
||||
"go.uber.org/zap/internal/pool"
|
||||
)
|
||||
|
||||
// A Pool is a type-safe wrapper around a sync.Pool.
|
||||
type Pool struct {
|
||||
p *sync.Pool
|
||||
p *pool.Pool[*Buffer]
|
||||
}
|
||||
|
||||
// NewPool constructs a new Pool.
|
||||
func NewPool() Pool {
|
||||
return Pool{p: &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &Buffer{bs: make([]byte, 0, _size)}
|
||||
},
|
||||
}}
|
||||
return Pool{
|
||||
p: pool.New(func() *Buffer {
|
||||
return &Buffer{
|
||||
bs: make([]byte, 0, _size),
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// Get retrieves a Buffer from the pool, creating one if necessary.
|
||||
func (p Pool) Get() *Buffer {
|
||||
buf := p.p.Get().(*Buffer)
|
||||
buf := p.p.Get()
|
||||
buf.Reset()
|
||||
buf.pool = p
|
||||
return buf
|
||||
|
|
|
@ -95,6 +95,32 @@ type Config struct {
|
|||
|
||||
// NewProductionEncoderConfig returns an opinionated EncoderConfig for
|
||||
// production environments.
|
||||
//
|
||||
// Messages encoded with this configuration will be JSON-formatted
|
||||
// and will have the following keys by default:
|
||||
//
|
||||
// - "level": The logging level (e.g. "info", "error").
|
||||
// - "ts": The current time in number of seconds since the Unix epoch.
|
||||
// - "msg": The message passed to the log statement.
|
||||
// - "caller": If available, a short path to the file and line number
|
||||
// where the log statement was issued.
|
||||
// The logger configuration determines whether this field is captured.
|
||||
// - "stacktrace": If available, a stack trace from the line
|
||||
// where the log statement was issued.
|
||||
// The logger configuration determines whether this field is captured.
|
||||
//
|
||||
// By default, the following formats are used for different types:
|
||||
//
|
||||
// - Time is formatted as floating-point number of seconds since the Unix
|
||||
// epoch.
|
||||
// - Duration is formatted as floating-point number of seconds.
|
||||
//
|
||||
// You may change these by setting the appropriate fields in the returned
|
||||
// object.
|
||||
// For example, use the following to change the time encoding format:
|
||||
//
|
||||
// cfg := zap.NewProductionEncoderConfig()
|
||||
// cfg.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||
func NewProductionEncoderConfig() zapcore.EncoderConfig {
|
||||
return zapcore.EncoderConfig{
|
||||
TimeKey: "ts",
|
||||
|
@ -112,11 +138,22 @@ func NewProductionEncoderConfig() zapcore.EncoderConfig {
|
|||
}
|
||||
}
|
||||
|
||||
// NewProductionConfig is a reasonable production logging configuration.
|
||||
// Logging is enabled at InfoLevel and above.
|
||||
// NewProductionConfig builds a reasonable default production logging
|
||||
// configuration.
|
||||
// Logging is enabled at InfoLevel and above, and uses a JSON encoder.
|
||||
// Logs are written to standard error.
|
||||
// Stacktraces are included on logs of ErrorLevel and above.
|
||||
// DPanicLevel logs will not panic, but will write a stacktrace.
|
||||
//
|
||||
// It uses a JSON encoder, writes to standard error, and enables sampling.
|
||||
// Stacktraces are automatically included on logs of ErrorLevel and above.
|
||||
// Sampling is enabled at 100:100 by default,
|
||||
// meaning that after the first 100 log entries
|
||||
// with the same level and message in the same second,
|
||||
// it will log every 100th entry
|
||||
// with the same level and message in the same second.
|
||||
// You may disable this behavior by setting Sampling to nil.
|
||||
//
|
||||
// See [NewProductionEncoderConfig] for information
|
||||
// on the default encoder configuration.
|
||||
func NewProductionConfig() Config {
|
||||
return Config{
|
||||
Level: NewAtomicLevelAt(InfoLevel),
|
||||
|
@ -134,6 +171,32 @@ func NewProductionConfig() Config {
|
|||
|
||||
// NewDevelopmentEncoderConfig returns an opinionated EncoderConfig for
|
||||
// development environments.
|
||||
//
|
||||
// Messages encoded with this configuration will use Zap's console encoder
|
||||
// intended to print human-readable output.
|
||||
// It will print log messages with the following information:
|
||||
//
|
||||
// - The log level (e.g. "INFO", "ERROR").
|
||||
// - The time in ISO8601 format (e.g. "2017-01-01T12:00:00Z").
|
||||
// - The message passed to the log statement.
|
||||
// - If available, a short path to the file and line number
|
||||
// where the log statement was issued.
|
||||
// The logger configuration determines whether this field is captured.
|
||||
// - If available, a stacktrace from the line
|
||||
// where the log statement was issued.
|
||||
// The logger configuration determines whether this field is captured.
|
||||
//
|
||||
// By default, the following formats are used for different types:
|
||||
//
|
||||
// - Time is formatted in ISO8601 format (e.g. "2017-01-01T12:00:00Z").
|
||||
// - Duration is formatted as a string (e.g. "1.234s").
|
||||
//
|
||||
// You may change these by setting the appropriate fields in the returned
|
||||
// object.
|
||||
// For example, use the following to change the time encoding format:
|
||||
//
|
||||
// cfg := zap.NewDevelopmentEncoderConfig()
|
||||
// cfg.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||
func NewDevelopmentEncoderConfig() zapcore.EncoderConfig {
|
||||
return zapcore.EncoderConfig{
|
||||
// Keys can be anything except the empty string.
|
||||
|
@ -152,12 +215,15 @@ func NewDevelopmentEncoderConfig() zapcore.EncoderConfig {
|
|||
}
|
||||
}
|
||||
|
||||
// NewDevelopmentConfig is a reasonable development logging configuration.
|
||||
// Logging is enabled at DebugLevel and above.
|
||||
// NewDevelopmentConfig builds a reasonable default development logging
|
||||
// configuration.
|
||||
// Logging is enabled at DebugLevel and above, and uses a console encoder.
|
||||
// Logs are written to standard error.
|
||||
// Stacktraces are included on logs of WarnLevel and above.
|
||||
// DPanicLevel logs will panic.
|
||||
//
|
||||
// It enables development mode (which makes DPanicLevel logs panic), uses a
|
||||
// console encoder, writes to standard error, and disables sampling.
|
||||
// Stacktraces are automatically included on logs of WarnLevel and above.
|
||||
// See [NewDevelopmentEncoderConfig] for information
|
||||
// on the default encoder configuration.
|
||||
func NewDevelopmentConfig() Config {
|
||||
return Config{
|
||||
Level: NewAtomicLevelAt(DebugLevel),
|
||||
|
|
|
@ -21,14 +21,13 @@
|
|||
package zap
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"go.uber.org/zap/internal/pool"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
var _errArrayElemPool = sync.Pool{New: func() interface{} {
|
||||
var _errArrayElemPool = pool.New(func() *errArrayElem {
|
||||
return &errArrayElem{}
|
||||
}}
|
||||
})
|
||||
|
||||
// Error is shorthand for the common idiom NamedError("error", err).
|
||||
func Error(err error) Field {
|
||||
|
@ -60,7 +59,7 @@ func (errs errArray) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
|||
// potentially an "errorVerbose" attribute, we need to wrap it in a
|
||||
// type that implements LogObjectMarshaler. To prevent this from
|
||||
// allocating, pool the wrapper type.
|
||||
elem := _errArrayElemPool.Get().(*errArrayElem)
|
||||
elem := _errArrayElemPool.Get()
|
||||
elem.error = errs[i]
|
||||
arr.AppendObject(elem)
|
||||
elem.error = nil
|
||||
|
|
|
@ -410,6 +410,43 @@ func Inline(val zapcore.ObjectMarshaler) Field {
|
|||
}
|
||||
}
|
||||
|
||||
// We discovered an issue where zap.Any can cause a performance degradation
|
||||
// when used in new goroutines.
|
||||
//
|
||||
// This happens because the compiler assigns 4.8kb (one zap.Field per arm of
|
||||
// switch statement) of stack space for zap.Any when it takes the form:
|
||||
//
|
||||
// switch v := v.(type) {
|
||||
// case string:
|
||||
// return String(key, v)
|
||||
// case int:
|
||||
// return Int(key, v)
|
||||
// // ...
|
||||
// default:
|
||||
// return Reflect(key, v)
|
||||
// }
|
||||
//
|
||||
// To avoid this, we use the type switch to assign a value to a single local variable
|
||||
// and then call a function on it.
|
||||
// The local variable is just a function reference so it doesn't allocate
|
||||
// when converted to an interface{}.
|
||||
//
|
||||
// A fair bit of experimentation went into this.
|
||||
// See also:
|
||||
//
|
||||
// - https://github.com/uber-go/zap/pull/1301
|
||||
// - https://github.com/uber-go/zap/pull/1303
|
||||
// - https://github.com/uber-go/zap/pull/1304
|
||||
// - https://github.com/uber-go/zap/pull/1305
|
||||
// - https://github.com/uber-go/zap/pull/1308
|
||||
type anyFieldC[T any] func(string, T) Field
|
||||
|
||||
func (f anyFieldC[T]) Any(key string, val any) Field {
|
||||
v, _ := val.(T)
|
||||
// val is guaranteed to be a T, except when it's nil.
|
||||
return f(key, v)
|
||||
}
|
||||
|
||||
// Any takes a key and an arbitrary value and chooses the best way to represent
|
||||
// them as a field, falling back to a reflection-based approach only if
|
||||
// necessary.
|
||||
|
@ -418,132 +455,136 @@ func Inline(val zapcore.ObjectMarshaler) Field {
|
|||
// them. To minimize surprises, []byte values are treated as binary blobs, byte
|
||||
// values are treated as uint8, and runes are always treated as integers.
|
||||
func Any(key string, value interface{}) Field {
|
||||
switch val := value.(type) {
|
||||
var c interface{ Any(string, any) Field }
|
||||
|
||||
switch value.(type) {
|
||||
case zapcore.ObjectMarshaler:
|
||||
return Object(key, val)
|
||||
c = anyFieldC[zapcore.ObjectMarshaler](Object)
|
||||
case zapcore.ArrayMarshaler:
|
||||
return Array(key, val)
|
||||
c = anyFieldC[zapcore.ArrayMarshaler](Array)
|
||||
case bool:
|
||||
return Bool(key, val)
|
||||
c = anyFieldC[bool](Bool)
|
||||
case *bool:
|
||||
return Boolp(key, val)
|
||||
c = anyFieldC[*bool](Boolp)
|
||||
case []bool:
|
||||
return Bools(key, val)
|
||||
c = anyFieldC[[]bool](Bools)
|
||||
case complex128:
|
||||
return Complex128(key, val)
|
||||
c = anyFieldC[complex128](Complex128)
|
||||
case *complex128:
|
||||
return Complex128p(key, val)
|
||||
c = anyFieldC[*complex128](Complex128p)
|
||||
case []complex128:
|
||||
return Complex128s(key, val)
|
||||
c = anyFieldC[[]complex128](Complex128s)
|
||||
case complex64:
|
||||
return Complex64(key, val)
|
||||
c = anyFieldC[complex64](Complex64)
|
||||
case *complex64:
|
||||
return Complex64p(key, val)
|
||||
c = anyFieldC[*complex64](Complex64p)
|
||||
case []complex64:
|
||||
return Complex64s(key, val)
|
||||
c = anyFieldC[[]complex64](Complex64s)
|
||||
case float64:
|
||||
return Float64(key, val)
|
||||
c = anyFieldC[float64](Float64)
|
||||
case *float64:
|
||||
return Float64p(key, val)
|
||||
c = anyFieldC[*float64](Float64p)
|
||||
case []float64:
|
||||
return Float64s(key, val)
|
||||
c = anyFieldC[[]float64](Float64s)
|
||||
case float32:
|
||||
return Float32(key, val)
|
||||
c = anyFieldC[float32](Float32)
|
||||
case *float32:
|
||||
return Float32p(key, val)
|
||||
c = anyFieldC[*float32](Float32p)
|
||||
case []float32:
|
||||
return Float32s(key, val)
|
||||
c = anyFieldC[[]float32](Float32s)
|
||||
case int:
|
||||
return Int(key, val)
|
||||
c = anyFieldC[int](Int)
|
||||
case *int:
|
||||
return Intp(key, val)
|
||||
c = anyFieldC[*int](Intp)
|
||||
case []int:
|
||||
return Ints(key, val)
|
||||
c = anyFieldC[[]int](Ints)
|
||||
case int64:
|
||||
return Int64(key, val)
|
||||
c = anyFieldC[int64](Int64)
|
||||
case *int64:
|
||||
return Int64p(key, val)
|
||||
c = anyFieldC[*int64](Int64p)
|
||||
case []int64:
|
||||
return Int64s(key, val)
|
||||
c = anyFieldC[[]int64](Int64s)
|
||||
case int32:
|
||||
return Int32(key, val)
|
||||
c = anyFieldC[int32](Int32)
|
||||
case *int32:
|
||||
return Int32p(key, val)
|
||||
c = anyFieldC[*int32](Int32p)
|
||||
case []int32:
|
||||
return Int32s(key, val)
|
||||
c = anyFieldC[[]int32](Int32s)
|
||||
case int16:
|
||||
return Int16(key, val)
|
||||
c = anyFieldC[int16](Int16)
|
||||
case *int16:
|
||||
return Int16p(key, val)
|
||||
c = anyFieldC[*int16](Int16p)
|
||||
case []int16:
|
||||
return Int16s(key, val)
|
||||
c = anyFieldC[[]int16](Int16s)
|
||||
case int8:
|
||||
return Int8(key, val)
|
||||
c = anyFieldC[int8](Int8)
|
||||
case *int8:
|
||||
return Int8p(key, val)
|
||||
c = anyFieldC[*int8](Int8p)
|
||||
case []int8:
|
||||
return Int8s(key, val)
|
||||
c = anyFieldC[[]int8](Int8s)
|
||||
case string:
|
||||
return String(key, val)
|
||||
c = anyFieldC[string](String)
|
||||
case *string:
|
||||
return Stringp(key, val)
|
||||
c = anyFieldC[*string](Stringp)
|
||||
case []string:
|
||||
return Strings(key, val)
|
||||
c = anyFieldC[[]string](Strings)
|
||||
case uint:
|
||||
return Uint(key, val)
|
||||
c = anyFieldC[uint](Uint)
|
||||
case *uint:
|
||||
return Uintp(key, val)
|
||||
c = anyFieldC[*uint](Uintp)
|
||||
case []uint:
|
||||
return Uints(key, val)
|
||||
c = anyFieldC[[]uint](Uints)
|
||||
case uint64:
|
||||
return Uint64(key, val)
|
||||
c = anyFieldC[uint64](Uint64)
|
||||
case *uint64:
|
||||
return Uint64p(key, val)
|
||||
c = anyFieldC[*uint64](Uint64p)
|
||||
case []uint64:
|
||||
return Uint64s(key, val)
|
||||
c = anyFieldC[[]uint64](Uint64s)
|
||||
case uint32:
|
||||
return Uint32(key, val)
|
||||
c = anyFieldC[uint32](Uint32)
|
||||
case *uint32:
|
||||
return Uint32p(key, val)
|
||||
c = anyFieldC[*uint32](Uint32p)
|
||||
case []uint32:
|
||||
return Uint32s(key, val)
|
||||
c = anyFieldC[[]uint32](Uint32s)
|
||||
case uint16:
|
||||
return Uint16(key, val)
|
||||
c = anyFieldC[uint16](Uint16)
|
||||
case *uint16:
|
||||
return Uint16p(key, val)
|
||||
c = anyFieldC[*uint16](Uint16p)
|
||||
case []uint16:
|
||||
return Uint16s(key, val)
|
||||
c = anyFieldC[[]uint16](Uint16s)
|
||||
case uint8:
|
||||
return Uint8(key, val)
|
||||
c = anyFieldC[uint8](Uint8)
|
||||
case *uint8:
|
||||
return Uint8p(key, val)
|
||||
c = anyFieldC[*uint8](Uint8p)
|
||||
case []byte:
|
||||
return Binary(key, val)
|
||||
c = anyFieldC[[]byte](Binary)
|
||||
case uintptr:
|
||||
return Uintptr(key, val)
|
||||
c = anyFieldC[uintptr](Uintptr)
|
||||
case *uintptr:
|
||||
return Uintptrp(key, val)
|
||||
c = anyFieldC[*uintptr](Uintptrp)
|
||||
case []uintptr:
|
||||
return Uintptrs(key, val)
|
||||
c = anyFieldC[[]uintptr](Uintptrs)
|
||||
case time.Time:
|
||||
return Time(key, val)
|
||||
c = anyFieldC[time.Time](Time)
|
||||
case *time.Time:
|
||||
return Timep(key, val)
|
||||
c = anyFieldC[*time.Time](Timep)
|
||||
case []time.Time:
|
||||
return Times(key, val)
|
||||
c = anyFieldC[[]time.Time](Times)
|
||||
case time.Duration:
|
||||
return Duration(key, val)
|
||||
c = anyFieldC[time.Duration](Duration)
|
||||
case *time.Duration:
|
||||
return Durationp(key, val)
|
||||
c = anyFieldC[*time.Duration](Durationp)
|
||||
case []time.Duration:
|
||||
return Durations(key, val)
|
||||
c = anyFieldC[[]time.Duration](Durations)
|
||||
case error:
|
||||
return NamedError(key, val)
|
||||
c = anyFieldC[error](NamedError)
|
||||
case []error:
|
||||
return Errors(key, val)
|
||||
c = anyFieldC[[]error](Errors)
|
||||
case fmt.Stringer:
|
||||
return Stringer(key, val)
|
||||
c = anyFieldC[fmt.Stringer](Stringer)
|
||||
default:
|
||||
return Reflect(key, val)
|
||||
c = anyFieldC[any](Reflect)
|
||||
}
|
||||
|
||||
return c.Any(key, value)
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
// Package internal and its subpackages hold types and functionality
|
||||
// that are not part of Zap's public API.
|
||||
package internal
|
||||
|
||||
import "go.uber.org/zap/zapcore"
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright (c) 2023 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
// Package pool provides internal pool utilities.
|
||||
package pool
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A Pool is a generic wrapper around [sync.Pool] to provide strongly-typed
|
||||
// object pooling.
|
||||
//
|
||||
// Note that SA6002 (ref: https://staticcheck.io/docs/checks/#SA6002) will
|
||||
// not be detected, so all internal pool use must take care to only store
|
||||
// pointer types.
|
||||
type Pool[T any] struct {
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
// New returns a new [Pool] for T, and will use fn to construct new Ts when
|
||||
// the pool is empty.
|
||||
func New[T any](fn func() T) *Pool[T] {
|
||||
return &Pool[T]{
|
||||
pool: sync.Pool{
|
||||
New: func() any {
|
||||
return fn()
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Get gets a T from the pool, or creates a new one if the pool is empty.
|
||||
func (p *Pool[T]) Get() T {
|
||||
return p.pool.Get().(T)
|
||||
}
|
||||
|
||||
// Put returns x into the pool.
|
||||
func (p *Pool[T]) Put(x T) {
|
||||
p.pool.Put(x)
|
||||
}
|
|
@ -21,7 +21,8 @@
|
|||
package zap
|
||||
|
||||
import (
|
||||
"go.uber.org/atomic"
|
||||
"sync/atomic"
|
||||
|
||||
"go.uber.org/zap/internal"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
@ -76,9 +77,9 @@ var _ internal.LeveledEnabler = AtomicLevel{}
|
|||
// NewAtomicLevel creates an AtomicLevel with InfoLevel and above logging
|
||||
// enabled.
|
||||
func NewAtomicLevel() AtomicLevel {
|
||||
return AtomicLevel{
|
||||
l: atomic.NewInt32(int32(InfoLevel)),
|
||||
}
|
||||
lvl := AtomicLevel{l: new(atomic.Int32)}
|
||||
lvl.l.Store(int32(InfoLevel))
|
||||
return lvl
|
||||
}
|
||||
|
||||
// NewAtomicLevelAt is a convenience function that creates an AtomicLevel
|
||||
|
|
|
@ -281,6 +281,12 @@ func (log *Logger) Core() zapcore.Core {
|
|||
return log.core
|
||||
}
|
||||
|
||||
// Name returns the Logger's underlying name,
|
||||
// or an empty string if the logger is unnamed.
|
||||
func (log *Logger) Name() string {
|
||||
return log.name
|
||||
}
|
||||
|
||||
func (log *Logger) clone() *Logger {
|
||||
copy := *log
|
||||
return ©
|
||||
|
|
|
@ -22,19 +22,17 @@ package zap
|
|||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"go.uber.org/zap/buffer"
|
||||
"go.uber.org/zap/internal/bufferpool"
|
||||
"go.uber.org/zap/internal/pool"
|
||||
)
|
||||
|
||||
var _stacktracePool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
var _stacktracePool = pool.New(func() *stacktrace {
|
||||
return &stacktrace{
|
||||
storage: make([]uintptr, 64),
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
type stacktrace struct {
|
||||
pcs []uintptr // program counters; always a subslice of storage
|
||||
|
@ -68,7 +66,7 @@ const (
|
|||
//
|
||||
// The caller must call Free on the returned stacktrace after using it.
|
||||
func captureStacktrace(skip int, depth stacktraceDepth) *stacktrace {
|
||||
stack := _stacktracePool.Get().(*stacktrace)
|
||||
stack := _stacktracePool.Get()
|
||||
|
||||
switch depth {
|
||||
case stacktraceFirst:
|
||||
|
|
|
@ -122,74 +122,88 @@ func (s *SugaredLogger) Level() zapcore.Level {
|
|||
return zapcore.LevelOf(s.base.core)
|
||||
}
|
||||
|
||||
// Debug uses fmt.Sprint to construct and log a message.
|
||||
// Debug logs the provided arguments at [DebugLevel].
|
||||
// Spaces are added between arguments when neither is a string.
|
||||
func (s *SugaredLogger) Debug(args ...interface{}) {
|
||||
s.log(DebugLevel, "", args, nil)
|
||||
}
|
||||
|
||||
// Info uses fmt.Sprint to construct and log a message.
|
||||
// Info logs the provided arguments at [InfoLevel].
|
||||
// Spaces are added between arguments when neither is a string.
|
||||
func (s *SugaredLogger) Info(args ...interface{}) {
|
||||
s.log(InfoLevel, "", args, nil)
|
||||
}
|
||||
|
||||
// Warn uses fmt.Sprint to construct and log a message.
|
||||
// Warn logs the provided arguments at [WarnLevel].
|
||||
// Spaces are added between arguments when neither is a string.
|
||||
func (s *SugaredLogger) Warn(args ...interface{}) {
|
||||
s.log(WarnLevel, "", args, nil)
|
||||
}
|
||||
|
||||
// Error uses fmt.Sprint to construct and log a message.
|
||||
// Error logs the provided arguments at [ErrorLevel].
|
||||
// Spaces are added between arguments when neither is a string.
|
||||
func (s *SugaredLogger) Error(args ...interface{}) {
|
||||
s.log(ErrorLevel, "", args, nil)
|
||||
}
|
||||
|
||||
// DPanic uses fmt.Sprint to construct and log a message. In development, the
|
||||
// logger then panics. (See DPanicLevel for details.)
|
||||
// DPanic logs the provided arguments at [DPanicLevel].
|
||||
// In development, the logger then panics. (See [DPanicLevel] for details.)
|
||||
// Spaces are added between arguments when neither is a string.
|
||||
func (s *SugaredLogger) DPanic(args ...interface{}) {
|
||||
s.log(DPanicLevel, "", args, nil)
|
||||
}
|
||||
|
||||
// Panic uses fmt.Sprint to construct and log a message, then panics.
|
||||
// Panic constructs a message with the provided arguments and panics.
|
||||
// Spaces are added between arguments when neither is a string.
|
||||
func (s *SugaredLogger) Panic(args ...interface{}) {
|
||||
s.log(PanicLevel, "", args, nil)
|
||||
}
|
||||
|
||||
// Fatal uses fmt.Sprint to construct and log a message, then calls os.Exit.
|
||||
// Fatal constructs a message with the provided arguments and calls os.Exit.
|
||||
// Spaces are added between arguments when neither is a string.
|
||||
func (s *SugaredLogger) Fatal(args ...interface{}) {
|
||||
s.log(FatalLevel, "", args, nil)
|
||||
}
|
||||
|
||||
// Debugf uses fmt.Sprintf to log a templated message.
|
||||
// Debugf formats the message according to the format specifier
|
||||
// and logs it at [DebugLevel].
|
||||
func (s *SugaredLogger) Debugf(template string, args ...interface{}) {
|
||||
s.log(DebugLevel, template, args, nil)
|
||||
}
|
||||
|
||||
// Infof uses fmt.Sprintf to log a templated message.
|
||||
// Infof formats the message according to the format specifier
|
||||
// and logs it at [InfoLevel].
|
||||
func (s *SugaredLogger) Infof(template string, args ...interface{}) {
|
||||
s.log(InfoLevel, template, args, nil)
|
||||
}
|
||||
|
||||
// Warnf uses fmt.Sprintf to log a templated message.
|
||||
// Warnf formats the message according to the format specifier
|
||||
// and logs it at [WarnLevel].
|
||||
func (s *SugaredLogger) Warnf(template string, args ...interface{}) {
|
||||
s.log(WarnLevel, template, args, nil)
|
||||
}
|
||||
|
||||
// Errorf uses fmt.Sprintf to log a templated message.
|
||||
// Errorf formats the message according to the format specifier
|
||||
// and logs it at [ErrorLevel].
|
||||
func (s *SugaredLogger) Errorf(template string, args ...interface{}) {
|
||||
s.log(ErrorLevel, template, args, nil)
|
||||
}
|
||||
|
||||
// DPanicf uses fmt.Sprintf to log a templated message. In development, the
|
||||
// logger then panics. (See DPanicLevel for details.)
|
||||
// DPanicf formats the message according to the format specifier
|
||||
// and logs it at [DPanicLevel].
|
||||
// In development, the logger then panics. (See [DPanicLevel] for details.)
|
||||
func (s *SugaredLogger) DPanicf(template string, args ...interface{}) {
|
||||
s.log(DPanicLevel, template, args, nil)
|
||||
}
|
||||
|
||||
// Panicf uses fmt.Sprintf to log a templated message, then panics.
|
||||
// Panicf formats the message according to the format specifier
|
||||
// and panics.
|
||||
func (s *SugaredLogger) Panicf(template string, args ...interface{}) {
|
||||
s.log(PanicLevel, template, args, nil)
|
||||
}
|
||||
|
||||
// Fatalf uses fmt.Sprintf to log a templated message, then calls os.Exit.
|
||||
// Fatalf formats the message according to the format specifier
|
||||
// and calls os.Exit.
|
||||
func (s *SugaredLogger) Fatalf(template string, args ...interface{}) {
|
||||
s.log(FatalLevel, template, args, nil)
|
||||
}
|
||||
|
@ -241,38 +255,45 @@ func (s *SugaredLogger) Fatalw(msg string, keysAndValues ...interface{}) {
|
|||
s.log(FatalLevel, msg, nil, keysAndValues)
|
||||
}
|
||||
|
||||
// Debugln uses fmt.Sprintln to construct and log a message.
|
||||
// Debugln logs a message at [DebugLevel].
|
||||
// Spaces are always added between arguments.
|
||||
func (s *SugaredLogger) Debugln(args ...interface{}) {
|
||||
s.logln(DebugLevel, args, nil)
|
||||
}
|
||||
|
||||
// Infoln uses fmt.Sprintln to construct and log a message.
|
||||
// Infoln logs a message at [InfoLevel].
|
||||
// Spaces are always added between arguments.
|
||||
func (s *SugaredLogger) Infoln(args ...interface{}) {
|
||||
s.logln(InfoLevel, args, nil)
|
||||
}
|
||||
|
||||
// Warnln uses fmt.Sprintln to construct and log a message.
|
||||
// Warnln logs a message at [WarnLevel].
|
||||
// Spaces are always added between arguments.
|
||||
func (s *SugaredLogger) Warnln(args ...interface{}) {
|
||||
s.logln(WarnLevel, args, nil)
|
||||
}
|
||||
|
||||
// Errorln uses fmt.Sprintln to construct and log a message.
|
||||
// Errorln logs a message at [ErrorLevel].
|
||||
// Spaces are always added between arguments.
|
||||
func (s *SugaredLogger) Errorln(args ...interface{}) {
|
||||
s.logln(ErrorLevel, args, nil)
|
||||
}
|
||||
|
||||
// DPanicln uses fmt.Sprintln to construct and log a message. In development, the
|
||||
// logger then panics. (See DPanicLevel for details.)
|
||||
// DPanicln logs a message at [DPanicLevel].
|
||||
// In development, the logger then panics. (See [DPanicLevel] for details.)
|
||||
// Spaces are always added between arguments.
|
||||
func (s *SugaredLogger) DPanicln(args ...interface{}) {
|
||||
s.logln(DPanicLevel, args, nil)
|
||||
}
|
||||
|
||||
// Panicln uses fmt.Sprintln to construct and log a message, then panics.
|
||||
// Panicln logs a message at [PanicLevel] and panics.
|
||||
// Spaces are always added between arguments.
|
||||
func (s *SugaredLogger) Panicln(args ...interface{}) {
|
||||
s.logln(PanicLevel, args, nil)
|
||||
}
|
||||
|
||||
// Fatalln uses fmt.Sprintln to construct and log a message, then calls os.Exit.
|
||||
// Fatalln logs a message at [FatalLevel] and calls os.Exit.
|
||||
// Spaces are always added between arguments.
|
||||
func (s *SugaredLogger) Fatalln(args ...interface{}) {
|
||||
s.logln(FatalLevel, args, nil)
|
||||
}
|
||||
|
|
|
@ -22,20 +22,20 @@ package zapcore
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"go.uber.org/zap/buffer"
|
||||
"go.uber.org/zap/internal/bufferpool"
|
||||
"go.uber.org/zap/internal/pool"
|
||||
)
|
||||
|
||||
var _sliceEncoderPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &sliceArrayEncoder{elems: make([]interface{}, 0, 2)}
|
||||
},
|
||||
}
|
||||
var _sliceEncoderPool = pool.New(func() *sliceArrayEncoder {
|
||||
return &sliceArrayEncoder{
|
||||
elems: make([]interface{}, 0, 2),
|
||||
}
|
||||
})
|
||||
|
||||
func getSliceEncoder() *sliceArrayEncoder {
|
||||
return _sliceEncoderPool.Get().(*sliceArrayEncoder)
|
||||
return _sliceEncoderPool.Get()
|
||||
}
|
||||
|
||||
func putSliceEncoder(e *sliceArrayEncoder) {
|
||||
|
|
|
@ -24,25 +24,23 @@ import (
|
|||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/multierr"
|
||||
"go.uber.org/zap/internal/bufferpool"
|
||||
"go.uber.org/zap/internal/exit"
|
||||
"go.uber.org/zap/internal/pool"
|
||||
)
|
||||
|
||||
var (
|
||||
_cePool = sync.Pool{New: func() interface{} {
|
||||
var _cePool = pool.New(func() *CheckedEntry {
|
||||
// Pre-allocate some space for cores.
|
||||
return &CheckedEntry{
|
||||
cores: make([]Core, 4),
|
||||
}
|
||||
}}
|
||||
)
|
||||
})
|
||||
|
||||
func getCheckedEntry() *CheckedEntry {
|
||||
ce := _cePool.Get().(*CheckedEntry)
|
||||
ce := _cePool.Get()
|
||||
ce.reset()
|
||||
return ce
|
||||
}
|
||||
|
|
|
@ -23,7 +23,8 @@ package zapcore
|
|||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"go.uber.org/zap/internal/pool"
|
||||
)
|
||||
|
||||
// Encodes the given error into fields of an object. A field with the given
|
||||
|
@ -103,9 +104,9 @@ func (errs errArray) MarshalLogArray(arr ArrayEncoder) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
var _errArrayElemPool = sync.Pool{New: func() interface{} {
|
||||
var _errArrayElemPool = pool.New(func() *errArrayElem {
|
||||
return &errArrayElem{}
|
||||
}}
|
||||
})
|
||||
|
||||
// Encodes any error into a {"error": ...} re-using the same errors logic.
|
||||
//
|
||||
|
@ -113,7 +114,7 @@ var _errArrayElemPool = sync.Pool{New: func() interface{} {
|
|||
type errArrayElem struct{ err error }
|
||||
|
||||
func newErrArrayElem(err error) *errArrayElem {
|
||||
e := _errArrayElemPool.Get().(*errArrayElem)
|
||||
e := _errArrayElemPool.Get()
|
||||
e.err = err
|
||||
return e
|
||||
}
|
||||
|
|
|
@ -23,24 +23,20 @@ package zapcore
|
|||
import (
|
||||
"encoding/base64"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"go.uber.org/zap/buffer"
|
||||
"go.uber.org/zap/internal/bufferpool"
|
||||
"go.uber.org/zap/internal/pool"
|
||||
)
|
||||
|
||||
// For JSON-escaping; see jsonEncoder.safeAddString below.
|
||||
const _hex = "0123456789abcdef"
|
||||
|
||||
var _jsonPool = sync.Pool{New: func() interface{} {
|
||||
var _jsonPool = pool.New(func() *jsonEncoder {
|
||||
return &jsonEncoder{}
|
||||
}}
|
||||
|
||||
func getJSONEncoder() *jsonEncoder {
|
||||
return _jsonPool.Get().(*jsonEncoder)
|
||||
}
|
||||
})
|
||||
|
||||
func putJSONEncoder(enc *jsonEncoder) {
|
||||
if enc.reflectBuf != nil {
|
||||
|
@ -354,7 +350,7 @@ func (enc *jsonEncoder) Clone() Encoder {
|
|||
}
|
||||
|
||||
func (enc *jsonEncoder) clone() *jsonEncoder {
|
||||
clone := getJSONEncoder()
|
||||
clone := _jsonPool.Get()
|
||||
clone.EncoderConfig = enc.EncoderConfig
|
||||
clone.spaced = enc.spaced
|
||||
clone.openNamespaces = enc.openNamespaces
|
||||
|
@ -527,7 +523,7 @@ func (enc *jsonEncoder) tryAddRuneSelf(b byte) bool {
|
|||
if b >= utf8.RuneSelf {
|
||||
return false
|
||||
}
|
||||
if 0x20 <= b && b != '\\' && b != '"' {
|
||||
if b >= 0x20 && b != '\\' && b != '"' {
|
||||
enc.buf.AppendByte(b)
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -21,9 +21,8 @@
|
|||
package zapcore
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -66,16 +65,16 @@ func (c *counter) IncCheckReset(t time.Time, tick time.Duration) uint64 {
|
|||
tn := t.UnixNano()
|
||||
resetAfter := c.resetAt.Load()
|
||||
if resetAfter > tn {
|
||||
return c.counter.Inc()
|
||||
return c.counter.Add(1)
|
||||
}
|
||||
|
||||
c.counter.Store(1)
|
||||
|
||||
newResetAfter := tn + tick.Nanoseconds()
|
||||
if !c.resetAt.CAS(resetAfter, newResetAfter) {
|
||||
if !c.resetAt.CompareAndSwap(resetAfter, newResetAfter) {
|
||||
// We raced with another goroutine trying to reset, and it also reset
|
||||
// the counter to 1, so we need to reincrement the counter.
|
||||
return c.counter.Inc()
|
||||
return c.counter.Add(1)
|
||||
}
|
||||
|
||||
return 1
|
||||
|
|
|
@ -30,10 +30,10 @@ import (
|
|||
|
||||
// See https://github.com/grpc/grpc-go/blob/v1.35.0/grpclog/loggerv2.go#L77-L86
|
||||
const (
|
||||
grpcLvlInfo = 0
|
||||
grpcLvlWarn = 1
|
||||
grpcLvlError = 2
|
||||
grpcLvlFatal = 3
|
||||
grpcLvlInfo int = iota
|
||||
grpcLvlWarn
|
||||
grpcLvlError
|
||||
grpcLvlFatal
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package maps defines various functions useful with maps of any type.
|
||||
package maps
|
||||
|
||||
// Keys returns the keys of the map m.
|
||||
// The keys will be in an indeterminate order.
|
||||
func Keys[M ~map[K]V, K comparable, V any](m M) []K {
|
||||
r := make([]K, 0, len(m))
|
||||
for k := range m {
|
||||
r = append(r, k)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Values returns the values of the map m.
|
||||
// The values will be in an indeterminate order.
|
||||
func Values[M ~map[K]V, K comparable, V any](m M) []V {
|
||||
r := make([]V, 0, len(m))
|
||||
for _, v := range m {
|
||||
r = append(r, v)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Equal reports whether two maps contain the same key/value pairs.
|
||||
// Values are compared using ==.
|
||||
func Equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool {
|
||||
if len(m1) != len(m2) {
|
||||
return false
|
||||
}
|
||||
for k, v1 := range m1 {
|
||||
if v2, ok := m2[k]; !ok || v1 != v2 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// EqualFunc is like Equal, but compares values using eq.
|
||||
// Keys are still compared with ==.
|
||||
func EqualFunc[M1 ~map[K]V1, M2 ~map[K]V2, K comparable, V1, V2 any](m1 M1, m2 M2, eq func(V1, V2) bool) bool {
|
||||
if len(m1) != len(m2) {
|
||||
return false
|
||||
}
|
||||
for k, v1 := range m1 {
|
||||
if v2, ok := m2[k]; !ok || !eq(v1, v2) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Clear removes all entries from m, leaving it empty.
|
||||
func Clear[M ~map[K]V, K comparable, V any](m M) {
|
||||
for k := range m {
|
||||
delete(m, k)
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a copy of m. This is a shallow clone:
|
||||
// the new keys and values are set using ordinary assignment.
|
||||
func Clone[M ~map[K]V, K comparable, V any](m M) M {
|
||||
// Preserve nil in case it matters.
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
r := make(M, len(m))
|
||||
for k, v := range m {
|
||||
r[k] = v
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Copy copies all key/value pairs in src adding them to dst.
|
||||
// When a key in src is already present in dst,
|
||||
// the value in dst will be overwritten by the value associated
|
||||
// with the key in src.
|
||||
func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2) {
|
||||
for k, v := range src {
|
||||
dst[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteFunc deletes any key/value pairs from m for which del returns true.
|
||||
func DeleteFunc[M ~map[K]V, K comparable, V any](m M, del func(K, V) bool) {
|
||||
for k, v := range m {
|
||||
if del(k, v) {
|
||||
delete(m, k)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package jsonpatch
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
@ -64,6 +65,9 @@ func NewOperation(op, path string, value interface{}) Operation {
|
|||
//
|
||||
// An error will be returned if any of the two documents are invalid.
|
||||
func CreatePatch(a, b []byte) ([]Operation, error) {
|
||||
if bytes.Equal(a, b) {
|
||||
return []Operation{}, nil
|
||||
}
|
||||
var aI interface{}
|
||||
var bI interface{}
|
||||
err := json.Unmarshal(a, &aI)
|
||||
|
|
|
@ -62,7 +62,7 @@ github.com/davecgh/go-spew/spew
|
|||
## explicit; go 1.15
|
||||
github.com/distribution/distribution/v3/digestset
|
||||
github.com/distribution/distribution/v3/reference
|
||||
# github.com/emicklei/go-restful/v3 v3.10.2
|
||||
# github.com/emicklei/go-restful/v3 v3.11.0
|
||||
## explicit; go 1.13
|
||||
github.com/emicklei/go-restful/v3
|
||||
github.com/emicklei/go-restful/v3/log
|
||||
|
@ -337,7 +337,7 @@ github.com/onsi/ginkgo/v2/internal/parallel_support
|
|||
github.com/onsi/ginkgo/v2/internal/testingtproxy
|
||||
github.com/onsi/ginkgo/v2/reporters
|
||||
github.com/onsi/ginkgo/v2/types
|
||||
# github.com/onsi/gomega v1.27.8
|
||||
# github.com/onsi/gomega v1.27.10
|
||||
## explicit; go 1.18
|
||||
github.com/onsi/gomega
|
||||
github.com/onsi/gomega/format
|
||||
|
@ -576,7 +576,7 @@ go.uber.org/mock/mockgen/model
|
|||
# go.uber.org/multierr v1.11.0
|
||||
## explicit; go 1.19
|
||||
go.uber.org/multierr
|
||||
# go.uber.org/zap v1.24.0
|
||||
# go.uber.org/zap v1.25.0
|
||||
## explicit; go 1.19
|
||||
go.uber.org/zap
|
||||
go.uber.org/zap/buffer
|
||||
|
@ -584,6 +584,7 @@ go.uber.org/zap/internal
|
|||
go.uber.org/zap/internal/bufferpool
|
||||
go.uber.org/zap/internal/color
|
||||
go.uber.org/zap/internal/exit
|
||||
go.uber.org/zap/internal/pool
|
||||
go.uber.org/zap/zapcore
|
||||
go.uber.org/zap/zapgrpc
|
||||
# golang.org/x/crypto v0.16.0
|
||||
|
@ -601,6 +602,7 @@ golang.org/x/crypto/ssh/terminal
|
|||
# golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e
|
||||
## explicit; go 1.18
|
||||
golang.org/x/exp/constraints
|
||||
golang.org/x/exp/maps
|
||||
golang.org/x/exp/slices
|
||||
# golang.org/x/mod v0.14.0
|
||||
## explicit; go 1.18
|
||||
|
@ -700,7 +702,7 @@ golang.org/x/tools/internal/tokeninternal
|
|||
golang.org/x/tools/internal/typeparams
|
||||
golang.org/x/tools/internal/typesinternal
|
||||
golang.org/x/tools/internal/versions
|
||||
# gomodules.xyz/jsonpatch/v2 v2.3.0
|
||||
# gomodules.xyz/jsonpatch/v2 v2.4.0
|
||||
## explicit; go 1.20
|
||||
gomodules.xyz/jsonpatch/v2
|
||||
# google.golang.org/appengine v1.6.7
|
||||
|
@ -1715,7 +1717,7 @@ sigs.k8s.io/cluster-api/util/certs
|
|||
sigs.k8s.io/cluster-api/util/labels/format
|
||||
sigs.k8s.io/cluster-api/util/secret
|
||||
sigs.k8s.io/cluster-api/util/version
|
||||
# sigs.k8s.io/controller-runtime v0.15.0
|
||||
# sigs.k8s.io/controller-runtime v0.16.3
|
||||
## explicit; go 1.20
|
||||
sigs.k8s.io/controller-runtime
|
||||
sigs.k8s.io/controller-runtime/pkg/builder
|
||||
|
@ -1750,6 +1752,7 @@ sigs.k8s.io/controller-runtime/pkg/log
|
|||
sigs.k8s.io/controller-runtime/pkg/manager
|
||||
sigs.k8s.io/controller-runtime/pkg/manager/signals
|
||||
sigs.k8s.io/controller-runtime/pkg/metrics
|
||||
sigs.k8s.io/controller-runtime/pkg/metrics/server
|
||||
sigs.k8s.io/controller-runtime/pkg/predicate
|
||||
sigs.k8s.io/controller-runtime/pkg/ratelimiter
|
||||
sigs.k8s.io/controller-runtime/pkg/reconcile
|
||||
|
|
|
@ -5,7 +5,6 @@ linters:
|
|||
- asciicheck
|
||||
- bidichk
|
||||
- bodyclose
|
||||
- depguard
|
||||
- dogsled
|
||||
- dupl
|
||||
- errcheck
|
||||
|
@ -13,6 +12,7 @@ linters:
|
|||
- errorlint
|
||||
- exhaustive
|
||||
- exportloopref
|
||||
- ginkgolinter
|
||||
- goconst
|
||||
- gocritic
|
||||
- gocyclo
|
||||
|
@ -62,10 +62,6 @@ linters-settings:
|
|||
go: "1.20"
|
||||
stylecheck:
|
||||
go: "1.20"
|
||||
depguard:
|
||||
include-go-root: true
|
||||
packages:
|
||||
- io/ioutil # https://go.dev/doc/go1.16#ioutil
|
||||
revive:
|
||||
rules:
|
||||
# The following rules are recommended https://github.com/mgechev/revive#recommended-configuration
|
||||
|
|
|
@ -41,6 +41,7 @@ GOLANGCI_LINT := $(abspath $(TOOLS_BIN_DIR)/golangci-lint)
|
|||
GO_APIDIFF := $(TOOLS_BIN_DIR)/go-apidiff
|
||||
CONTROLLER_GEN := $(TOOLS_BIN_DIR)/controller-gen
|
||||
ENVTEST_DIR := $(abspath tools/setup-envtest)
|
||||
SCRATCH_ENV_DIR := $(abspath examples/scratch-env)
|
||||
|
||||
# The help will print out all targets with their descriptions organized bellow their categories. The categories are represented by `##@` and the target descriptions by `##`.
|
||||
# The awk commands is responsible to read the entire set of makefiles included in this invocation, looking for lines of the file as xyz: ## something, and then pretty-format the target and help. Then, if there's a line with ##@ something, that gets pretty-printed as a category.
|
||||
|
@ -99,6 +100,7 @@ modules: ## Runs go mod to ensure modules are up to date.
|
|||
go mod tidy
|
||||
cd $(TOOLS_DIR); go mod tidy
|
||||
cd $(ENVTEST_DIR); go mod tidy
|
||||
cd $(SCRATCH_ENV_DIR); go mod tidy
|
||||
|
||||
.PHONY: generate
|
||||
generate: $(CONTROLLER_GEN) ## Runs controller-gen for internal types for config file
|
||||
|
@ -110,6 +112,7 @@ generate: $(CONTROLLER_GEN) ## Runs controller-gen for internal types for config
|
|||
|
||||
.PHONY: clean
|
||||
clean: ## Cleanup.
|
||||
$(GOLANGCI_LINT) cache clean
|
||||
$(MAKE) clean-bin
|
||||
|
||||
.PHONY: clean-bin
|
||||
|
@ -118,7 +121,7 @@ clean-bin: ## Remove all generated binaries.
|
|||
|
||||
.PHONY: verify-modules
|
||||
verify-modules: modules ## Verify go modules are up to date
|
||||
@if !(git diff --quiet HEAD -- go.sum go.mod $(TOOLS_DIR)/go.mod $(TOOLS_DIR)/go.sum $(ENVTEST_DIR)/go.mod $(ENVTEST_DIR)/go.sum); then \
|
||||
@if !(git diff --quiet HEAD -- go.sum go.mod $(TOOLS_DIR)/go.mod $(TOOLS_DIR)/go.sum $(ENVTEST_DIR)/go.mod $(ENVTEST_DIR)/go.sum $(SCRATCH_ENV_DIR)/go.sum); then \
|
||||
git diff; \
|
||||
echo "go module files are out of date, please run 'make modules'"; exit 1; \
|
||||
fi
|
||||
|
|
|
@ -9,7 +9,7 @@ exactly.
|
|||
|
||||
[guidelines]: https://sigs.k8s.io/kubebuilder-release-tools/VERSIONING.md
|
||||
|
||||
## Compatiblity and Release Support
|
||||
## Compatibility and Release Support
|
||||
|
||||
For release branches, we generally tend to support backporting one (1)
|
||||
major release (`release-{X-1}` or `release-0.{Y-1}`), but may go back
|
||||
|
@ -19,12 +19,12 @@ further if the need arises and is very pressing (e.g. security updates).
|
|||
|
||||
Note the [guidelines on dependency versions][dep-versions]. Particularly:
|
||||
|
||||
- We **DO** guarantee Kubernetes REST API compability -- if a given
|
||||
- We **DO** guarantee Kubernetes REST API compatibility -- if a given
|
||||
version of controller-runtime stops working with what should be
|
||||
a supported version of Kubernetes, this is almost certainly a bug.
|
||||
|
||||
- We **DO NOT** guarantee any particular compability matrix between
|
||||
- We **DO NOT** guarantee any particular compatibility matrix between
|
||||
kubernetes library dependencies (client-go, apimachinery, etc); Such
|
||||
compability is infeasible due to the way those libraries are versioned.
|
||||
compatibility is infeasible due to the way those libraries are versioned.
|
||||
|
||||
[dep-versions]: https://sigs.k8s.io/kubebuilder-release-tools/VERSIONING.md#kubernetes-version-compatibility
|
||||
|
|
|
@ -110,6 +110,9 @@ var (
|
|||
NewWebhookManagedBy = builder.WebhookManagedBy
|
||||
|
||||
// NewManager returns a new Manager for creating Controllers.
|
||||
// Note that if ContentType in the given config is not set, "application/vnd.kubernetes.protobuf"
|
||||
// will be used for all built-in resources of Kubernetes, and "application/json" is for other types
|
||||
// including all CRD resources.
|
||||
NewManager = manager.New
|
||||
|
||||
// CreateOrUpdate creates or updates the given object obj in the Kubernetes
|
||||
|
|
|
@ -41,14 +41,14 @@ import (
|
|||
var newController = controller.New
|
||||
var getGvk = apiutil.GVKForObject
|
||||
|
||||
// project represents other forms that the we can use to
|
||||
// project represents other forms that we can use to
|
||||
// send/receive a given resource (metadata-only, unstructured, etc).
|
||||
type objectProjection int
|
||||
|
||||
const (
|
||||
// projectAsNormal doesn't change the object from the form given.
|
||||
projectAsNormal objectProjection = iota
|
||||
// projectAsMetadata turns this into an metadata-only watch.
|
||||
// projectAsMetadata turns this into a metadata-only watch.
|
||||
projectAsMetadata
|
||||
)
|
||||
|
||||
|
@ -69,7 +69,7 @@ func ControllerManagedBy(m manager.Manager) *Builder {
|
|||
return &Builder{mgr: m}
|
||||
}
|
||||
|
||||
// ForInput represents the information set by For method.
|
||||
// ForInput represents the information set by the For method.
|
||||
type ForInput struct {
|
||||
object client.Object
|
||||
predicates []predicate.Predicate
|
||||
|
@ -124,7 +124,7 @@ func (blder *Builder) Owns(object client.Object, opts ...OwnsOption) *Builder {
|
|||
// WatchesInput represents the information set by Watches method.
|
||||
type WatchesInput struct {
|
||||
src source.Source
|
||||
eventhandler handler.EventHandler
|
||||
eventHandler handler.EventHandler
|
||||
predicates []predicate.Predicate
|
||||
objectProjection objectProjection
|
||||
}
|
||||
|
@ -133,10 +133,10 @@ type WatchesInput struct {
|
|||
// update events by *reconciling the object* with the given EventHandler.
|
||||
//
|
||||
// This is the equivalent of calling
|
||||
// WatchesRawSource(source.Kind(scheme, object), eventhandler, opts...).
|
||||
func (blder *Builder) Watches(object client.Object, eventhandler handler.EventHandler, opts ...WatchesOption) *Builder {
|
||||
// WatchesRawSource(source.Kind(cache, object), eventHandler, opts...).
|
||||
func (blder *Builder) Watches(object client.Object, eventHandler handler.EventHandler, opts ...WatchesOption) *Builder {
|
||||
src := source.Kind(blder.mgr.GetCache(), object)
|
||||
return blder.WatchesRawSource(src, eventhandler, opts...)
|
||||
return blder.WatchesRawSource(src, eventHandler, opts...)
|
||||
}
|
||||
|
||||
// WatchesMetadata is the same as Watches, but forces the internal cache to only watch PartialObjectMetadata.
|
||||
|
@ -166,18 +166,18 @@ func (blder *Builder) Watches(object client.Object, eventhandler handler.EventHa
|
|||
// In the first case, controller-runtime will create another cache for the
|
||||
// concrete type on top of the metadata cache; this increases memory
|
||||
// consumption and leads to race conditions as caches are not in sync.
|
||||
func (blder *Builder) WatchesMetadata(object client.Object, eventhandler handler.EventHandler, opts ...WatchesOption) *Builder {
|
||||
func (blder *Builder) WatchesMetadata(object client.Object, eventHandler handler.EventHandler, opts ...WatchesOption) *Builder {
|
||||
opts = append(opts, OnlyMetadata)
|
||||
return blder.Watches(object, eventhandler, opts...)
|
||||
return blder.Watches(object, eventHandler, opts...)
|
||||
}
|
||||
|
||||
// WatchesRawSource exposes the lower-level ControllerManagedBy Watches functions through the builder.
|
||||
// Specified predicates are registered only for given source.
|
||||
//
|
||||
// STOP! Consider using For(...), Owns(...), Watches(...), WatchesMetadata(...) instead.
|
||||
// This method is only exposed for more advanced use cases, most users should use higher level functions.
|
||||
func (blder *Builder) WatchesRawSource(src source.Source, eventhandler handler.EventHandler, opts ...WatchesOption) *Builder {
|
||||
input := WatchesInput{src: src, eventhandler: eventhandler}
|
||||
// This method is only exposed for more advanced use cases, most users should use one of the higher level functions.
|
||||
func (blder *Builder) WatchesRawSource(src source.Source, eventHandler handler.EventHandler, opts ...WatchesOption) *Builder {
|
||||
input := WatchesInput{src: src, eventHandler: eventHandler}
|
||||
for _, opt := range opts {
|
||||
opt.ApplyToWatches(&input)
|
||||
}
|
||||
|
@ -195,7 +195,7 @@ func (blder *Builder) WithEventFilter(p predicate.Predicate) *Builder {
|
|||
return blder
|
||||
}
|
||||
|
||||
// WithOptions overrides the controller options use in doController. Defaults to empty.
|
||||
// WithOptions overrides the controller options used in doController. Defaults to empty.
|
||||
func (blder *Builder) WithOptions(options controller.Options) *Builder {
|
||||
blder.ctrlOptions = options
|
||||
return blder
|
||||
|
@ -274,7 +274,8 @@ func (blder *Builder) doWatch() error {
|
|||
}
|
||||
src := source.Kind(blder.mgr.GetCache(), obj)
|
||||
hdler := &handler.EnqueueRequestForObject{}
|
||||
allPredicates := append(blder.globalPredicates, blder.forInput.predicates...)
|
||||
allPredicates := append([]predicate.Predicate(nil), blder.globalPredicates...)
|
||||
allPredicates = append(allPredicates, blder.forInput.predicates...)
|
||||
if err := blder.ctrl.Watch(src, hdler, allPredicates...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -311,19 +312,17 @@ func (blder *Builder) doWatch() error {
|
|||
return errors.New("there are no watches configured, controller will never get triggered. Use For(), Owns() or Watches() to set them up")
|
||||
}
|
||||
for _, w := range blder.watchesInput {
|
||||
allPredicates := append([]predicate.Predicate(nil), blder.globalPredicates...)
|
||||
allPredicates = append(allPredicates, w.predicates...)
|
||||
|
||||
// If the source of this watch is of type Kind, project it.
|
||||
if srckind, ok := w.src.(*internalsource.Kind); ok {
|
||||
typeForSrc, err := blder.project(srckind.Type, w.objectProjection)
|
||||
if srcKind, ok := w.src.(*internalsource.Kind); ok {
|
||||
typeForSrc, err := blder.project(srcKind.Type, w.objectProjection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
srckind.Type = typeForSrc
|
||||
srcKind.Type = typeForSrc
|
||||
}
|
||||
|
||||
if err := blder.ctrl.Watch(w.src, w.eventhandler, allPredicates...); err != nil {
|
||||
allPredicates := append([]predicate.Predicate(nil), blder.globalPredicates...)
|
||||
allPredicates = append(allPredicates, w.predicates...)
|
||||
if err := blder.ctrl.Watch(w.src, w.eventHandler, allPredicates...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -344,12 +343,15 @@ func (blder *Builder) doController(r reconcile.Reconciler) error {
|
|||
globalOpts := blder.mgr.GetControllerOptions()
|
||||
|
||||
ctrlOptions := blder.ctrlOptions
|
||||
if ctrlOptions.Reconciler != nil && r != nil {
|
||||
return errors.New("reconciler was set via WithOptions() and via Build() or Complete()")
|
||||
}
|
||||
if ctrlOptions.Reconciler == nil {
|
||||
ctrlOptions.Reconciler = r
|
||||
}
|
||||
|
||||
// Retrieve the GVK from the object we're reconciling
|
||||
// to prepopulate logger information, and to optionally generate a default name.
|
||||
// to pre-populate logger information, and to optionally generate a default name.
|
||||
var gvk schema.GroupVersionKind
|
||||
hasGVK := blder.forInput.object != nil
|
||||
if hasGVK {
|
||||
|
|
|
@ -28,7 +28,7 @@ type ForOption interface {
|
|||
ApplyToFor(*ForInput)
|
||||
}
|
||||
|
||||
// OwnsOption is some configuration that modifies options for a owns request.
|
||||
// OwnsOption is some configuration that modifies options for an owns request.
|
||||
type OwnsOption interface {
|
||||
// ApplyToOwns applies this configuration to the given owns input.
|
||||
ApplyToOwns(*OwnsInput)
|
||||
|
@ -79,8 +79,8 @@ var _ WatchesOption = &Predicates{}
|
|||
|
||||
// {{{ For & Owns Dual-Type options
|
||||
|
||||
// asProjection configures the projection (currently only metadata) on the input.
|
||||
// Currently only metadata is supported. We might want to expand
|
||||
// projectAs configures the projection on the input.
|
||||
// Currently only OnlyMetadata is supported. We might want to expand
|
||||
// this to arbitrary non-special local projections in the future.
|
||||
type projectAs objectProjection
|
||||
|
||||
|
|
|
@ -37,8 +37,8 @@ import (
|
|||
// WebhookBuilder builds a Webhook.
|
||||
type WebhookBuilder struct {
|
||||
apiType runtime.Object
|
||||
withDefaulter admission.CustomDefaulter
|
||||
withValidator admission.CustomValidator
|
||||
customDefaulter admission.CustomDefaulter
|
||||
customValidator admission.CustomValidator
|
||||
gvk schema.GroupVersionKind
|
||||
mgr manager.Manager
|
||||
config *rest.Config
|
||||
|
@ -46,7 +46,7 @@ type WebhookBuilder struct {
|
|||
logConstructor func(base logr.Logger, req *admission.Request) logr.Logger
|
||||
}
|
||||
|
||||
// WebhookManagedBy allows inform its manager.Manager.
|
||||
// WebhookManagedBy returns a new webhook builder.
|
||||
func WebhookManagedBy(m manager.Manager) *WebhookBuilder {
|
||||
return &WebhookBuilder{mgr: m}
|
||||
}
|
||||
|
@ -61,15 +61,15 @@ func (blder *WebhookBuilder) For(apiType runtime.Object) *WebhookBuilder {
|
|||
return blder
|
||||
}
|
||||
|
||||
// WithDefaulter takes a admission.WithDefaulter interface, a MutatingWebhook will be wired for this type.
|
||||
// WithDefaulter takes an admission.CustomDefaulter interface, a MutatingWebhook will be wired for this type.
|
||||
func (blder *WebhookBuilder) WithDefaulter(defaulter admission.CustomDefaulter) *WebhookBuilder {
|
||||
blder.withDefaulter = defaulter
|
||||
blder.customDefaulter = defaulter
|
||||
return blder
|
||||
}
|
||||
|
||||
// WithValidator takes a admission.WithValidator interface, a ValidatingWebhook will be wired for this type.
|
||||
// WithValidator takes a admission.CustomValidator interface, a ValidatingWebhook will be wired for this type.
|
||||
func (blder *WebhookBuilder) WithValidator(validator admission.CustomValidator) *WebhookBuilder {
|
||||
blder.withValidator = validator
|
||||
blder.customValidator = validator
|
||||
return blder
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ func (blder *WebhookBuilder) WithLogConstructor(logConstructor func(base logr.Lo
|
|||
return blder
|
||||
}
|
||||
|
||||
// RecoverPanic indicates whether the panic caused by webhook should be recovered.
|
||||
// RecoverPanic indicates whether panics caused by the webhook should be recovered.
|
||||
func (blder *WebhookBuilder) RecoverPanic() *WebhookBuilder {
|
||||
blder.recoverPanic = true
|
||||
return blder
|
||||
|
@ -129,12 +129,12 @@ func (blder *WebhookBuilder) registerWebhooks() error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Create webhook(s) for each type
|
||||
blder.gvk, err = apiutil.GVKForObject(typ, blder.mgr.GetScheme())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Register webhook(s) for type
|
||||
blder.registerDefaultingWebhook()
|
||||
blder.registerValidatingWebhook()
|
||||
|
||||
|
@ -145,7 +145,7 @@ func (blder *WebhookBuilder) registerWebhooks() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// registerDefaultingWebhook registers a defaulting webhook if th.
|
||||
// registerDefaultingWebhook registers a defaulting webhook if necessary.
|
||||
func (blder *WebhookBuilder) registerDefaultingWebhook() {
|
||||
mwh := blder.getDefaultingWebhook()
|
||||
if mwh != nil {
|
||||
|
@ -164,7 +164,7 @@ func (blder *WebhookBuilder) registerDefaultingWebhook() {
|
|||
}
|
||||
|
||||
func (blder *WebhookBuilder) getDefaultingWebhook() *admission.Webhook {
|
||||
if defaulter := blder.withDefaulter; defaulter != nil {
|
||||
if defaulter := blder.customDefaulter; defaulter != nil {
|
||||
return admission.WithCustomDefaulter(blder.mgr.GetScheme(), blder.apiType, defaulter).WithRecoverPanic(blder.recoverPanic)
|
||||
}
|
||||
if defaulter, ok := blder.apiType.(admission.Defaulter); ok {
|
||||
|
@ -176,6 +176,7 @@ func (blder *WebhookBuilder) getDefaultingWebhook() *admission.Webhook {
|
|||
return nil
|
||||
}
|
||||
|
||||
// registerValidatingWebhook registers a validating webhook if necessary.
|
||||
func (blder *WebhookBuilder) registerValidatingWebhook() {
|
||||
vwh := blder.getValidatingWebhook()
|
||||
if vwh != nil {
|
||||
|
@ -194,7 +195,7 @@ func (blder *WebhookBuilder) registerValidatingWebhook() {
|
|||
}
|
||||
|
||||
func (blder *WebhookBuilder) getValidatingWebhook() *admission.Webhook {
|
||||
if validator := blder.withValidator; validator != nil {
|
||||
if validator := blder.customValidator; validator != nil {
|
||||
return admission.WithCustomValidator(blder.mgr.GetScheme(), blder.apiType, validator).WithRecoverPanic(blder.recoverPanic)
|
||||
}
|
||||
if validator, ok := blder.apiType.(admission.Validator); ok {
|
||||
|
|
|
@ -22,6 +22,8 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
|
@ -31,6 +33,7 @@ import (
|
|||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
toolscache "k8s.io/client-go/tools/cache"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/cache/internal"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
@ -43,14 +46,28 @@ var (
|
|||
defaultSyncPeriod = 10 * time.Hour
|
||||
)
|
||||
|
||||
// InformerGetOptions defines the behavior of how informers are retrieved.
|
||||
type InformerGetOptions internal.GetOptions
|
||||
|
||||
// InformerGetOption defines an option that alters the behavior of how informers are retrieved.
|
||||
type InformerGetOption func(*InformerGetOptions)
|
||||
|
||||
// BlockUntilSynced determines whether a get request for an informer should block
|
||||
// until the informer's cache has synced.
|
||||
func BlockUntilSynced(shouldBlock bool) InformerGetOption {
|
||||
return func(opts *InformerGetOptions) {
|
||||
opts.BlockUntilSynced = &shouldBlock
|
||||
}
|
||||
}
|
||||
|
||||
// Cache knows how to load Kubernetes objects, fetch informers to request
|
||||
// to receive events for Kubernetes objects (at a low-level),
|
||||
// and add indices to fields on the objects stored in the cache.
|
||||
type Cache interface {
|
||||
// Cache acts as a client to objects stored in the cache.
|
||||
// Reader acts as a client to objects stored in the cache.
|
||||
client.Reader
|
||||
|
||||
// Cache loads informers and adds field indices.
|
||||
// Informers loads informers and adds field indices.
|
||||
Informers
|
||||
}
|
||||
|
||||
|
@ -60,11 +77,11 @@ type Cache interface {
|
|||
type Informers interface {
|
||||
// GetInformer fetches or constructs an informer for the given object that corresponds to a single
|
||||
// API kind and resource.
|
||||
GetInformer(ctx context.Context, obj client.Object) (Informer, error)
|
||||
GetInformer(ctx context.Context, obj client.Object, opts ...InformerGetOption) (Informer, error)
|
||||
|
||||
// GetInformerForKind is similar to GetInformer, except that it takes a group-version-kind, instead
|
||||
// of the underlying object.
|
||||
GetInformerForKind(ctx context.Context, gvk schema.GroupVersionKind) (Informer, error)
|
||||
GetInformerForKind(ctx context.Context, gvk schema.GroupVersionKind, opts ...InformerGetOption) (Informer, error)
|
||||
|
||||
// Start runs all the informers known to this cache until the context is closed.
|
||||
// It blocks.
|
||||
|
@ -73,36 +90,44 @@ type Informers interface {
|
|||
// WaitForCacheSync waits for all the caches to sync. Returns false if it could not sync a cache.
|
||||
WaitForCacheSync(ctx context.Context) bool
|
||||
|
||||
// Informers knows how to add indices to the caches (informers) that it manages.
|
||||
// FieldIndexer adds indices to the managed informers.
|
||||
client.FieldIndexer
|
||||
}
|
||||
|
||||
// Informer - informer allows you interact with the underlying informer.
|
||||
// Informer allows you to interact with the underlying informer.
|
||||
type Informer interface {
|
||||
// AddEventHandler adds an event handler to the shared informer using the shared informer's resync
|
||||
// period. Events to a single handler are delivered sequentially, but there is no coordination
|
||||
// between different handlers.
|
||||
// It returns a registration handle for the handler that can be used to remove
|
||||
// the handler again.
|
||||
// the handler again and an error if the handler cannot be added.
|
||||
AddEventHandler(handler toolscache.ResourceEventHandler) (toolscache.ResourceEventHandlerRegistration, error)
|
||||
|
||||
// AddEventHandlerWithResyncPeriod adds an event handler to the shared informer using the
|
||||
// specified resync period. Events to a single handler are delivered sequentially, but there is
|
||||
// no coordination between different handlers.
|
||||
// It returns a registration handle for the handler that can be used to remove
|
||||
// the handler again and an error if the handler cannot be added.
|
||||
AddEventHandlerWithResyncPeriod(handler toolscache.ResourceEventHandler, resyncPeriod time.Duration) (toolscache.ResourceEventHandlerRegistration, error)
|
||||
// RemoveEventHandler removes a formerly added event handler given by
|
||||
|
||||
// RemoveEventHandler removes a previously added event handler given by
|
||||
// its registration handle.
|
||||
// This function is guaranteed to be idempotent, and thread-safe.
|
||||
// This function is guaranteed to be idempotent and thread-safe.
|
||||
RemoveEventHandler(handle toolscache.ResourceEventHandlerRegistration) error
|
||||
// AddIndexers adds more indexers to this store. If you call this after you already have data
|
||||
|
||||
// AddIndexers adds indexers to this store. If this is called after there is already data
|
||||
// in the store, the results are undefined.
|
||||
AddIndexers(indexers toolscache.Indexers) error
|
||||
|
||||
// HasSynced return true if the informers underlying store has synced.
|
||||
HasSynced() bool
|
||||
}
|
||||
|
||||
// Options are the optional arguments for creating a new InformersMap object.
|
||||
// AllNamespaces should be used as the map key to deliminate namespace settings
|
||||
// that apply to all namespaces that themselves do not have explicit settings.
|
||||
const AllNamespaces = metav1.NamespaceAll
|
||||
|
||||
// Options are the optional arguments for creating a new Cache object.
|
||||
type Options struct {
|
||||
// HTTPClient is the http client to use for the REST client
|
||||
HTTPClient *http.Client
|
||||
|
@ -140,45 +165,90 @@ type Options struct {
|
|||
// instead of `reconcile.Result{}`.
|
||||
SyncPeriod *time.Duration
|
||||
|
||||
// Namespaces restricts the cache's ListWatch to the desired namespaces
|
||||
// Default watches all namespaces
|
||||
Namespaces []string
|
||||
// ReaderFailOnMissingInformer configures the cache to return a ErrResourceNotCached error when a user
|
||||
// requests, using Get() and List(), a resource the cache does not already have an informer for.
|
||||
//
|
||||
// This error is distinct from an errors.NotFound.
|
||||
//
|
||||
// Defaults to false, which means that the cache will start a new informer
|
||||
// for every new requested resource.
|
||||
ReaderFailOnMissingInformer bool
|
||||
|
||||
// DefaultLabelSelector will be used as a label selectors for all object types
|
||||
// unless they have a more specific selector set in ByObject.
|
||||
// DefaultNamespaces maps namespace names to cache configs. If set, only
|
||||
// the namespaces in here will be watched and it will by used to default
|
||||
// ByObject.Namespaces for all objects if that is nil.
|
||||
//
|
||||
// It is possible to have specific Config for just some namespaces
|
||||
// but cache all namespaces by using the AllNamespaces const as the map key.
|
||||
// This will then include all namespaces that do not have a more specific
|
||||
// setting.
|
||||
//
|
||||
// The options in the Config that are nil will be defaulted from
|
||||
// the respective Default* settings.
|
||||
DefaultNamespaces map[string]Config
|
||||
|
||||
// DefaultLabelSelector will be used as a label selector for all objects
|
||||
// unless there is already one set in ByObject or DefaultNamespaces.
|
||||
DefaultLabelSelector labels.Selector
|
||||
|
||||
// DefaultFieldSelector will be used as a field selectors for all object types
|
||||
// unless they have a more specific selector set in ByObject.
|
||||
// DefaultFieldSelector will be used as a field selector for all object types
|
||||
// unless there is already one set in ByObject or DefaultNamespaces.
|
||||
DefaultFieldSelector fields.Selector
|
||||
|
||||
// DefaultTransform will be used as transform for all object types
|
||||
// unless they have a more specific transform set in ByObject.
|
||||
// unless there is already one set in ByObject or DefaultNamespaces.
|
||||
DefaultTransform toolscache.TransformFunc
|
||||
|
||||
// ByObject restricts the cache's ListWatch to the desired fields per GVK at the specified object.
|
||||
ByObject map[client.Object]ByObject
|
||||
|
||||
// UnsafeDisableDeepCopy indicates not to deep copy objects during get or
|
||||
// list objects for EVERY object.
|
||||
// DefaultUnsafeDisableDeepCopy is the default for UnsafeDisableDeepCopy
|
||||
// for everything that doesn't specify this.
|
||||
//
|
||||
// Be very careful with this, when enabled you must DeepCopy any object before mutating it,
|
||||
// otherwise you will mutate the object in the cache.
|
||||
//
|
||||
// This is a global setting for all objects, and can be overridden by the ByObject setting.
|
||||
UnsafeDisableDeepCopy *bool
|
||||
// This will be used for all object types, unless it is set in ByObject or
|
||||
// DefaultNamespaces.
|
||||
DefaultUnsafeDisableDeepCopy *bool
|
||||
|
||||
// ByObject restricts the cache's ListWatch to the desired fields per GVK at the specified object.
|
||||
// object, this will fall through to Default* settings.
|
||||
ByObject map[client.Object]ByObject
|
||||
|
||||
// newInformer allows overriding of NewSharedIndexInformer for testing.
|
||||
newInformer *func(toolscache.ListerWatcher, runtime.Object, time.Duration, toolscache.Indexers) toolscache.SharedIndexInformer
|
||||
}
|
||||
|
||||
// ByObject offers more fine-grained control over the cache's ListWatch by object.
|
||||
type ByObject struct {
|
||||
// Namespaces maps a namespace name to cache configs. If set, only the
|
||||
// namespaces in this map will be cached.
|
||||
//
|
||||
// Settings in the map value that are unset will be defaulted.
|
||||
// Use an empty value for the specific setting to prevent that.
|
||||
//
|
||||
// It is possible to have specific Config for just some namespaces
|
||||
// but cache all namespaces by using the AllNamespaces const as the map key.
|
||||
// This will then include all namespaces that do not have a more specific
|
||||
// setting.
|
||||
//
|
||||
// A nil map allows to default this to the cache's DefaultNamespaces setting.
|
||||
// An empty map prevents this and means that all namespaces will be cached.
|
||||
//
|
||||
// The defaulting follows the following precedence order:
|
||||
// 1. ByObject
|
||||
// 2. DefaultNamespaces[namespace]
|
||||
// 3. Default*
|
||||
//
|
||||
// This must be unset for cluster-scoped objects.
|
||||
Namespaces map[string]Config
|
||||
|
||||
// Label represents a label selector for the object.
|
||||
Label labels.Selector
|
||||
|
||||
// Field represents a field selector for the object.
|
||||
Field fields.Selector
|
||||
|
||||
// Transform is a map from objects to transformer functions which
|
||||
// get applied when objects of the transformation are about to be committed
|
||||
// to cache.
|
||||
// Transform is a transformer function for the object which gets applied
|
||||
// when objects of the transformation are about to be committed to the cache.
|
||||
//
|
||||
// This function is called both for new objects to enter the cache,
|
||||
// and for updated objects.
|
||||
|
@ -191,60 +261,134 @@ type ByObject struct {
|
|||
UnsafeDisableDeepCopy *bool
|
||||
}
|
||||
|
||||
// Config describes all potential options for a given watch.
|
||||
type Config struct {
|
||||
// LabelSelector specifies a label selector. A nil value allows to
|
||||
// default this.
|
||||
//
|
||||
// Set to labels.Everything() if you don't want this defaulted.
|
||||
LabelSelector labels.Selector
|
||||
|
||||
// FieldSelector specifics a field selector. A nil value allows to
|
||||
// default this.
|
||||
//
|
||||
// Set to fields.Everything() if you don't want this defaulted.
|
||||
FieldSelector fields.Selector
|
||||
|
||||
// Transform specifies a transform func. A nil value allows to default
|
||||
// this.
|
||||
//
|
||||
// Set to an empty func to prevent this:
|
||||
// func(in interface{}) (interface{}, error) { return in, nil }
|
||||
Transform toolscache.TransformFunc
|
||||
|
||||
// UnsafeDisableDeepCopy specifies if List and Get requests against the
|
||||
// cache should not DeepCopy. A nil value allows to default this.
|
||||
UnsafeDisableDeepCopy *bool
|
||||
}
|
||||
|
||||
// NewCacheFunc - Function for creating a new cache from the options and a rest config.
|
||||
type NewCacheFunc func(config *rest.Config, opts Options) (Cache, error)
|
||||
|
||||
// New initializes and returns a new Cache.
|
||||
func New(config *rest.Config, opts Options) (Cache, error) {
|
||||
if len(opts.Namespaces) == 0 {
|
||||
opts.Namespaces = []string{metav1.NamespaceAll}
|
||||
}
|
||||
if len(opts.Namespaces) > 1 {
|
||||
return newMultiNamespaceCache(config, opts)
|
||||
}
|
||||
|
||||
opts, err := defaultOpts(config, opts)
|
||||
func New(cfg *rest.Config, opts Options) (Cache, error) {
|
||||
opts, err := defaultOpts(cfg, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
byGVK, err := convertToInformerOptsByGVK(opts.ByObject, opts.Scheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
newCacheFunc := newCache(cfg, opts)
|
||||
|
||||
var defaultCache Cache
|
||||
if len(opts.DefaultNamespaces) > 0 {
|
||||
defaultConfig := optionDefaultsToConfig(&opts)
|
||||
defaultCache = newMultiNamespaceCache(newCacheFunc, opts.Scheme, opts.Mapper, opts.DefaultNamespaces, &defaultConfig)
|
||||
} else {
|
||||
defaultCache = newCacheFunc(optionDefaultsToConfig(&opts), corev1.NamespaceAll)
|
||||
}
|
||||
// Set the default selector and transform.
|
||||
byGVK[schema.GroupVersionKind{}] = internal.InformersOptsByGVK{
|
||||
Selector: internal.Selector{
|
||||
Label: opts.DefaultLabelSelector,
|
||||
Field: opts.DefaultFieldSelector,
|
||||
},
|
||||
|
||||
if len(opts.ByObject) == 0 {
|
||||
return defaultCache, nil
|
||||
}
|
||||
|
||||
delegating := &delegatingByGVKCache{
|
||||
scheme: opts.Scheme,
|
||||
caches: make(map[schema.GroupVersionKind]Cache, len(opts.ByObject)),
|
||||
defaultCache: defaultCache,
|
||||
}
|
||||
|
||||
for obj, config := range opts.ByObject {
|
||||
gvk, err := apiutil.GVKForObject(obj, opts.Scheme)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get GVK for type %T: %w", obj, err)
|
||||
}
|
||||
var cache Cache
|
||||
if len(config.Namespaces) > 0 {
|
||||
cache = newMultiNamespaceCache(newCacheFunc, opts.Scheme, opts.Mapper, config.Namespaces, nil)
|
||||
} else {
|
||||
cache = newCacheFunc(byObjectToConfig(config), corev1.NamespaceAll)
|
||||
}
|
||||
delegating.caches[gvk] = cache
|
||||
}
|
||||
|
||||
return delegating, nil
|
||||
}
|
||||
|
||||
func optionDefaultsToConfig(opts *Options) Config {
|
||||
return Config{
|
||||
LabelSelector: opts.DefaultLabelSelector,
|
||||
FieldSelector: opts.DefaultFieldSelector,
|
||||
Transform: opts.DefaultTransform,
|
||||
UnsafeDisableDeepCopy: opts.UnsafeDisableDeepCopy,
|
||||
UnsafeDisableDeepCopy: opts.DefaultUnsafeDisableDeepCopy,
|
||||
}
|
||||
}
|
||||
|
||||
func byObjectToConfig(byObject ByObject) Config {
|
||||
return Config{
|
||||
LabelSelector: byObject.Label,
|
||||
FieldSelector: byObject.Field,
|
||||
Transform: byObject.Transform,
|
||||
UnsafeDisableDeepCopy: byObject.UnsafeDisableDeepCopy,
|
||||
}
|
||||
}
|
||||
|
||||
type newCacheFunc func(config Config, namespace string) Cache
|
||||
|
||||
func newCache(restConfig *rest.Config, opts Options) newCacheFunc {
|
||||
return func(config Config, namespace string) Cache {
|
||||
return &informerCache{
|
||||
scheme: opts.Scheme,
|
||||
Informers: internal.NewInformers(config, &internal.InformersOpts{
|
||||
Informers: internal.NewInformers(restConfig, &internal.InformersOpts{
|
||||
HTTPClient: opts.HTTPClient,
|
||||
Scheme: opts.Scheme,
|
||||
Mapper: opts.Mapper,
|
||||
ResyncPeriod: *opts.SyncPeriod,
|
||||
Namespace: opts.Namespaces[0],
|
||||
ByGVK: byGVK,
|
||||
Namespace: namespace,
|
||||
Selector: internal.Selector{
|
||||
Label: config.LabelSelector,
|
||||
Field: config.FieldSelector,
|
||||
},
|
||||
Transform: config.Transform,
|
||||
UnsafeDisableDeepCopy: pointer.BoolDeref(config.UnsafeDisableDeepCopy, false),
|
||||
NewInformer: opts.newInformer,
|
||||
}),
|
||||
}, nil
|
||||
readerFailOnMissingInformer: opts.ReaderFailOnMissingInformer,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func defaultOpts(config *rest.Config, opts Options) (Options, error) {
|
||||
logger := log.WithName("setup")
|
||||
config = rest.CopyConfig(config)
|
||||
if config.UserAgent == "" {
|
||||
config.UserAgent = rest.DefaultKubernetesUserAgent()
|
||||
}
|
||||
|
||||
// Use the rest HTTP client for the provided config if unset
|
||||
if opts.HTTPClient == nil {
|
||||
var err error
|
||||
opts.HTTPClient, err = rest.HTTPClientFor(config)
|
||||
if err != nil {
|
||||
logger.Error(err, "Failed to get HTTP client")
|
||||
return opts, fmt.Errorf("could not create HTTP client from config: %w", err)
|
||||
return Options{}, fmt.Errorf("could not create HTTP client from config: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -258,11 +402,66 @@ func defaultOpts(config *rest.Config, opts Options) (Options, error) {
|
|||
var err error
|
||||
opts.Mapper, err = apiutil.NewDiscoveryRESTMapper(config, opts.HTTPClient)
|
||||
if err != nil {
|
||||
logger.Error(err, "Failed to get API Group-Resources")
|
||||
return opts, fmt.Errorf("could not create RESTMapper from config: %w", err)
|
||||
return Options{}, fmt.Errorf("could not create RESTMapper from config: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
for namespace, cfg := range opts.DefaultNamespaces {
|
||||
cfg = defaultConfig(cfg, optionDefaultsToConfig(&opts))
|
||||
if namespace == metav1.NamespaceAll {
|
||||
cfg.FieldSelector = fields.AndSelectors(appendIfNotNil(namespaceAllSelector(maps.Keys(opts.DefaultNamespaces)), cfg.FieldSelector)...)
|
||||
}
|
||||
opts.DefaultNamespaces[namespace] = cfg
|
||||
}
|
||||
|
||||
for obj, byObject := range opts.ByObject {
|
||||
isNamespaced, err := apiutil.IsObjectNamespaced(obj, opts.Scheme, opts.Mapper)
|
||||
if err != nil {
|
||||
return opts, fmt.Errorf("failed to determine if %T is namespaced: %w", obj, err)
|
||||
}
|
||||
if !isNamespaced && byObject.Namespaces != nil {
|
||||
return opts, fmt.Errorf("type %T is not namespaced, but its ByObject.Namespaces setting is not nil", obj)
|
||||
}
|
||||
|
||||
// Default the namespace-level configs first, because they need to use the undefaulted type-level config.
|
||||
for namespace, config := range byObject.Namespaces {
|
||||
// 1. Default from the undefaulted type-level config
|
||||
config = defaultConfig(config, byObjectToConfig(byObject))
|
||||
|
||||
// 2. Default from the namespace-level config. This was defaulted from the global default config earlier, but
|
||||
// might not have an entry for the current namespace.
|
||||
if defaultNamespaceSettings, hasDefaultNamespace := opts.DefaultNamespaces[namespace]; hasDefaultNamespace {
|
||||
config = defaultConfig(config, defaultNamespaceSettings)
|
||||
}
|
||||
|
||||
// 3. Default from the global defaults
|
||||
config = defaultConfig(config, optionDefaultsToConfig(&opts))
|
||||
|
||||
if namespace == metav1.NamespaceAll {
|
||||
config.FieldSelector = fields.AndSelectors(
|
||||
appendIfNotNil(
|
||||
namespaceAllSelector(maps.Keys(byObject.Namespaces)),
|
||||
config.FieldSelector,
|
||||
)...,
|
||||
)
|
||||
}
|
||||
|
||||
byObject.Namespaces[namespace] = config
|
||||
}
|
||||
|
||||
defaultedConfig := defaultConfig(byObjectToConfig(byObject), optionDefaultsToConfig(&opts))
|
||||
byObject.Label = defaultedConfig.LabelSelector
|
||||
byObject.Field = defaultedConfig.FieldSelector
|
||||
byObject.Transform = defaultedConfig.Transform
|
||||
byObject.UnsafeDisableDeepCopy = defaultedConfig.UnsafeDisableDeepCopy
|
||||
|
||||
if isNamespaced && byObject.Namespaces == nil {
|
||||
byObject.Namespaces = opts.DefaultNamespaces
|
||||
}
|
||||
|
||||
opts.ByObject[obj] = byObject
|
||||
}
|
||||
|
||||
// Default the resync period to 10 hours if unset
|
||||
if opts.SyncPeriod == nil {
|
||||
opts.SyncPeriod = &defaultSyncPeriod
|
||||
|
@ -270,24 +469,37 @@ func defaultOpts(config *rest.Config, opts Options) (Options, error) {
|
|||
return opts, nil
|
||||
}
|
||||
|
||||
func convertToInformerOptsByGVK(in map[client.Object]ByObject, scheme *runtime.Scheme) (map[schema.GroupVersionKind]internal.InformersOptsByGVK, error) {
|
||||
out := map[schema.GroupVersionKind]internal.InformersOptsByGVK{}
|
||||
for object, byObject := range in {
|
||||
gvk, err := apiutil.GVKForObject(object, scheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func defaultConfig(toDefault, defaultFrom Config) Config {
|
||||
if toDefault.LabelSelector == nil {
|
||||
toDefault.LabelSelector = defaultFrom.LabelSelector
|
||||
}
|
||||
if _, ok := out[gvk]; ok {
|
||||
return nil, fmt.Errorf("duplicate cache options for GVK %v, cache.Options.ByObject has multiple types with the same GroupVersionKind", gvk)
|
||||
if toDefault.FieldSelector == nil {
|
||||
toDefault.FieldSelector = defaultFrom.FieldSelector
|
||||
}
|
||||
out[gvk] = internal.InformersOptsByGVK{
|
||||
Selector: internal.Selector{
|
||||
Field: byObject.Field,
|
||||
Label: byObject.Label,
|
||||
},
|
||||
Transform: byObject.Transform,
|
||||
UnsafeDisableDeepCopy: byObject.UnsafeDisableDeepCopy,
|
||||
if toDefault.Transform == nil {
|
||||
toDefault.Transform = defaultFrom.Transform
|
||||
}
|
||||
if toDefault.UnsafeDisableDeepCopy == nil {
|
||||
toDefault.UnsafeDisableDeepCopy = defaultFrom.UnsafeDisableDeepCopy
|
||||
}
|
||||
return out, nil
|
||||
|
||||
return toDefault
|
||||
}
|
||||
|
||||
func namespaceAllSelector(namespaces []string) fields.Selector {
|
||||
selectors := make([]fields.Selector, 0, len(namespaces)-1)
|
||||
for _, namespace := range namespaces {
|
||||
if namespace != metav1.NamespaceAll {
|
||||
selectors = append(selectors, fields.OneTermNotEqualSelector("metadata.namespace", namespace))
|
||||
}
|
||||
}
|
||||
|
||||
return fields.AndSelectors(selectors...)
|
||||
}
|
||||
|
||||
func appendIfNotNil[T comparable](a, b T) []T {
|
||||
if b != *new(T) {
|
||||
return []T{a, b}
|
||||
}
|
||||
return []T{a}
|
||||
}
|
||||
|
|
127
vendor/sigs.k8s.io/controller-runtime/pkg/cache/delegating_by_gvk_cache.go
generated
vendored
Normal file
127
vendor/sigs.k8s.io/controller-runtime/pkg/cache/delegating_by_gvk_cache.go
generated
vendored
Normal file
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
|
||||
)
|
||||
|
||||
// delegatingByGVKCache delegates to a type-specific cache if present
|
||||
// and uses the defaultCache otherwise.
|
||||
type delegatingByGVKCache struct {
|
||||
scheme *runtime.Scheme
|
||||
caches map[schema.GroupVersionKind]Cache
|
||||
defaultCache Cache
|
||||
}
|
||||
|
||||
func (dbt *delegatingByGVKCache) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
|
||||
cache, err := dbt.cacheForObject(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return cache.Get(ctx, key, obj, opts...)
|
||||
}
|
||||
|
||||
func (dbt *delegatingByGVKCache) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
|
||||
cache, err := dbt.cacheForObject(list)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return cache.List(ctx, list, opts...)
|
||||
}
|
||||
|
||||
func (dbt *delegatingByGVKCache) GetInformer(ctx context.Context, obj client.Object, opts ...InformerGetOption) (Informer, error) {
|
||||
cache, err := dbt.cacheForObject(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cache.GetInformer(ctx, obj, opts...)
|
||||
}
|
||||
|
||||
func (dbt *delegatingByGVKCache) GetInformerForKind(ctx context.Context, gvk schema.GroupVersionKind, opts ...InformerGetOption) (Informer, error) {
|
||||
return dbt.cacheForGVK(gvk).GetInformerForKind(ctx, gvk, opts...)
|
||||
}
|
||||
|
||||
func (dbt *delegatingByGVKCache) Start(ctx context.Context) error {
|
||||
allCaches := maps.Values(dbt.caches)
|
||||
allCaches = append(allCaches, dbt.defaultCache)
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
errs := make(chan error)
|
||||
for idx := range allCaches {
|
||||
cache := allCaches[idx]
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := cache.Start(ctx); err != nil {
|
||||
errs <- err
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
select {
|
||||
case err := <-errs:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (dbt *delegatingByGVKCache) WaitForCacheSync(ctx context.Context) bool {
|
||||
synced := true
|
||||
for _, cache := range append(maps.Values(dbt.caches), dbt.defaultCache) {
|
||||
if !cache.WaitForCacheSync(ctx) {
|
||||
synced = false
|
||||
}
|
||||
}
|
||||
|
||||
return synced
|
||||
}
|
||||
|
||||
func (dbt *delegatingByGVKCache) IndexField(ctx context.Context, obj client.Object, field string, extractValue client.IndexerFunc) error {
|
||||
cache, err := dbt.cacheForObject(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return cache.IndexField(ctx, obj, field, extractValue)
|
||||
}
|
||||
|
||||
func (dbt *delegatingByGVKCache) cacheForObject(o runtime.Object) (Cache, error) {
|
||||
gvk, err := apiutil.GVKForObject(o, dbt.scheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gvk.Kind = strings.TrimSuffix(gvk.Kind, "List")
|
||||
return dbt.cacheForGVK(gvk), nil
|
||||
}
|
||||
|
||||
func (dbt *delegatingByGVKCache) cacheForGVK(gvk schema.GroupVersionKind) Cache {
|
||||
if specific, hasSpecific := dbt.caches[gvk]; hasSpecific {
|
||||
return specific
|
||||
}
|
||||
|
||||
return dbt.defaultCache
|
||||
}
|
|
@ -27,6 +27,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/cache/internal"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
|
||||
|
@ -45,11 +46,28 @@ func (*ErrCacheNotStarted) Error() string {
|
|||
return "the cache is not started, can not read objects"
|
||||
}
|
||||
|
||||
var _ error = (*ErrCacheNotStarted)(nil)
|
||||
|
||||
// ErrResourceNotCached indicates that the resource type
|
||||
// the client asked the cache for is not cached, i.e. the
|
||||
// corresponding informer does not exist yet.
|
||||
type ErrResourceNotCached struct {
|
||||
GVK schema.GroupVersionKind
|
||||
}
|
||||
|
||||
// Error returns the error
|
||||
func (r ErrResourceNotCached) Error() string {
|
||||
return fmt.Sprintf("%s is not cached", r.GVK.String())
|
||||
}
|
||||
|
||||
var _ error = (*ErrResourceNotCached)(nil)
|
||||
|
||||
// informerCache is a Kubernetes Object cache populated from internal.Informers.
|
||||
// informerCache wraps internal.Informers.
|
||||
type informerCache struct {
|
||||
scheme *runtime.Scheme
|
||||
*internal.Informers
|
||||
readerFailOnMissingInformer bool
|
||||
}
|
||||
|
||||
// Get implements Reader.
|
||||
|
@ -59,7 +77,7 @@ func (ic *informerCache) Get(ctx context.Context, key client.ObjectKey, out clie
|
|||
return err
|
||||
}
|
||||
|
||||
started, cache, err := ic.Informers.Get(ctx, gvk, out)
|
||||
started, cache, err := ic.getInformerForKind(ctx, gvk, out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -67,7 +85,7 @@ func (ic *informerCache) Get(ctx context.Context, key client.ObjectKey, out clie
|
|||
if !started {
|
||||
return &ErrCacheNotStarted{}
|
||||
}
|
||||
return cache.Reader.Get(ctx, key, out)
|
||||
return cache.Reader.Get(ctx, key, out, opts...)
|
||||
}
|
||||
|
||||
// List implements Reader.
|
||||
|
@ -77,7 +95,7 @@ func (ic *informerCache) List(ctx context.Context, out client.ObjectList, opts .
|
|||
return err
|
||||
}
|
||||
|
||||
started, cache, err := ic.Informers.Get(ctx, *gvk, cacheTypeObj)
|
||||
started, cache, err := ic.getInformerForKind(ctx, *gvk, cacheTypeObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -123,33 +141,53 @@ func (ic *informerCache) objectTypeForListObject(list client.ObjectList) (*schem
|
|||
return &gvk, cacheTypeObj, nil
|
||||
}
|
||||
|
||||
// GetInformerForKind returns the informer for the GroupVersionKind.
|
||||
func (ic *informerCache) GetInformerForKind(ctx context.Context, gvk schema.GroupVersionKind) (Informer, error) {
|
||||
func applyGetOptions(opts ...InformerGetOption) *internal.GetOptions {
|
||||
cfg := &InformerGetOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(cfg)
|
||||
}
|
||||
return (*internal.GetOptions)(cfg)
|
||||
}
|
||||
|
||||
// GetInformerForKind returns the informer for the GroupVersionKind. If no informer exists, one will be started.
|
||||
func (ic *informerCache) GetInformerForKind(ctx context.Context, gvk schema.GroupVersionKind, opts ...InformerGetOption) (Informer, error) {
|
||||
// Map the gvk to an object
|
||||
obj, err := ic.scheme.New(gvk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, i, err := ic.Informers.Get(ctx, gvk, obj)
|
||||
_, i, err := ic.Informers.Get(ctx, gvk, obj, applyGetOptions(opts...))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return i.Informer, err
|
||||
return i.Informer, nil
|
||||
}
|
||||
|
||||
// GetInformer returns the informer for the obj.
|
||||
func (ic *informerCache) GetInformer(ctx context.Context, obj client.Object) (Informer, error) {
|
||||
// GetInformer returns the informer for the obj. If no informer exists, one will be started.
|
||||
func (ic *informerCache) GetInformer(ctx context.Context, obj client.Object, opts ...InformerGetOption) (Informer, error) {
|
||||
gvk, err := apiutil.GVKForObject(obj, ic.scheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, i, err := ic.Informers.Get(ctx, gvk, obj)
|
||||
_, i, err := ic.Informers.Get(ctx, gvk, obj, applyGetOptions(opts...))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return i.Informer, err
|
||||
return i.Informer, nil
|
||||
}
|
||||
|
||||
func (ic *informerCache) getInformerForKind(ctx context.Context, gvk schema.GroupVersionKind, obj runtime.Object) (bool, *internal.Cache, error) {
|
||||
if ic.readerFailOnMissingInformer {
|
||||
cache, started, ok := ic.Informers.Peek(gvk, obj)
|
||||
if !ok {
|
||||
return false, nil, &ErrResourceNotCached{GVK: gvk}
|
||||
}
|
||||
return started, cache, nil
|
||||
}
|
||||
|
||||
return ic.Informers.Get(ctx, gvk, obj, &internal.GetOptions{})
|
||||
}
|
||||
|
||||
// NeedLeaderElection implements the LeaderElectionRunnable interface
|
||||
|
@ -158,7 +196,7 @@ func (ic *informerCache) NeedLeaderElection() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// IndexField adds an indexer to the underlying cache, using extraction function to get
|
||||
// IndexField adds an indexer to the underlying informer, using extractValue function to get
|
||||
// value(s) from the given field. This index can then be used by passing a field selector
|
||||
// to List. For one-to-one compatibility with "normal" field selectors, only return one value.
|
||||
// The values may be anything. They will automatically be prefixed with the namespace of the
|
||||
|
@ -171,7 +209,7 @@ func (ic *informerCache) IndexField(ctx context.Context, obj client.Object, fiel
|
|||
return indexByField(informer, field, extractValue)
|
||||
}
|
||||
|
||||
func indexByField(indexer Informer, field string, extractor client.IndexerFunc) error {
|
||||
func indexByField(informer Informer, field string, extractValue client.IndexerFunc) error {
|
||||
indexFunc := func(objRaw interface{}) ([]string, error) {
|
||||
// TODO(directxman12): check if this is the correct type?
|
||||
obj, isObj := objRaw.(client.Object)
|
||||
|
@ -184,7 +222,7 @@ func indexByField(indexer Informer, field string, extractor client.IndexerFunc)
|
|||
}
|
||||
ns := meta.GetNamespace()
|
||||
|
||||
rawVals := extractor(obj)
|
||||
rawVals := extractValue(obj)
|
||||
var vals []string
|
||||
if ns == "" {
|
||||
// if we're not doubling the keys for the namespaced case, just create a new slice with same length
|
||||
|
@ -207,5 +245,5 @@ func indexByField(indexer Informer, field string, extractor client.IndexerFunc)
|
|||
return vals, nil
|
||||
}
|
||||
|
||||
return indexer.AddIndexers(cache.Indexers{internal.FieldIndexName(field): indexFunc})
|
||||
return informer.AddIndexers(cache.Indexers{internal.FieldIndexName(field): indexFunc})
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ type CacheReader struct {
|
|||
}
|
||||
|
||||
// Get checks the indexer for the object and writes a copy of it if found.
|
||||
func (c *CacheReader) Get(_ context.Context, key client.ObjectKey, out client.Object, opts ...client.GetOption) error {
|
||||
func (c *CacheReader) Get(_ context.Context, key client.ObjectKey, out client.Object, _ ...client.GetOption) error {
|
||||
if c.scopeName == apimeta.RESTScopeNameRoot {
|
||||
key.Namespace = ""
|
||||
}
|
||||
|
@ -67,9 +67,9 @@ func (c *CacheReader) Get(_ context.Context, key client.ObjectKey, out client.Ob
|
|||
|
||||
// Not found, return an error
|
||||
if !exists {
|
||||
// Resource gets transformed into Kind in the error anyway, so this is fine
|
||||
return apierrors.NewNotFound(schema.GroupResource{
|
||||
Group: c.groupVersionKind.Group,
|
||||
// Resource gets set as Kind in the error so this is fine
|
||||
Resource: c.groupVersionKind.Kind,
|
||||
}, key.Name)
|
||||
}
|
||||
|
@ -111,6 +111,10 @@ func (c *CacheReader) List(_ context.Context, out client.ObjectList, opts ...cli
|
|||
listOpts := client.ListOptions{}
|
||||
listOpts.ApplyOptions(opts)
|
||||
|
||||
if listOpts.Continue != "" {
|
||||
return fmt.Errorf("continue list option is not supported by the cache")
|
||||
}
|
||||
|
||||
switch {
|
||||
case listOpts.FieldSelector != nil:
|
||||
// TODO(directxman12): support more complicated field selectors by
|
||||
|
@ -191,7 +195,7 @@ func FieldIndexName(field string) string {
|
|||
return "field:" + field
|
||||
}
|
||||
|
||||
// noNamespaceNamespace is used as the "namespace" when we want to list across all namespaces.
|
||||
// allNamespacesNamespace is used as the "namespace" when we want to list across all namespaces.
|
||||
const allNamespacesNamespace = "__all_namespaces"
|
||||
|
||||
// KeyToNamespacedKey prefixes the given index key with a namespace
|
||||
|
|
|
@ -45,19 +45,18 @@ type InformersOpts struct {
|
|||
Mapper meta.RESTMapper
|
||||
ResyncPeriod time.Duration
|
||||
Namespace string
|
||||
ByGVK map[schema.GroupVersionKind]InformersOptsByGVK
|
||||
}
|
||||
|
||||
// InformersOptsByGVK configured additional by group version kind (or object)
|
||||
// in an InformerMap.
|
||||
type InformersOptsByGVK struct {
|
||||
NewInformer *func(cache.ListerWatcher, runtime.Object, time.Duration, cache.Indexers) cache.SharedIndexInformer
|
||||
Selector Selector
|
||||
Transform cache.TransformFunc
|
||||
UnsafeDisableDeepCopy *bool
|
||||
UnsafeDisableDeepCopy bool
|
||||
}
|
||||
|
||||
// NewInformers creates a new InformersMap that can create informers under the hood.
|
||||
func NewInformers(config *rest.Config, options *InformersOpts) *Informers {
|
||||
newInformer := cache.NewSharedIndexInformer
|
||||
if options.NewInformer != nil {
|
||||
newInformer = *options.NewInformer
|
||||
}
|
||||
return &Informers{
|
||||
config: config,
|
||||
httpClient: options.HTTPClient,
|
||||
|
@ -73,7 +72,10 @@ func NewInformers(config *rest.Config, options *InformersOpts) *Informers {
|
|||
resync: options.ResyncPeriod,
|
||||
startWait: make(chan struct{}),
|
||||
namespace: options.Namespace,
|
||||
byGVK: options.ByGVK,
|
||||
selector: options.Selector,
|
||||
transform: options.Transform,
|
||||
unsafeDisableDeepCopy: options.UnsafeDisableDeepCopy,
|
||||
newInformer: newInformer,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,6 +94,13 @@ type tracker struct {
|
|||
Metadata map[schema.GroupVersionKind]*Cache
|
||||
}
|
||||
|
||||
// GetOptions provides configuration to customize the behavior when
|
||||
// getting an informer.
|
||||
type GetOptions struct {
|
||||
// BlockUntilSynced controls if the informer retrieval will block until the informer is synced. Defaults to `true`.
|
||||
BlockUntilSynced *bool
|
||||
}
|
||||
|
||||
// Informers create and caches Informers for (runtime.Object, schema.GroupVersionKind) pairs.
|
||||
// It uses a standard parameter codec constructed based on the given generated Scheme.
|
||||
type Informers struct {
|
||||
|
@ -144,46 +153,12 @@ type Informers struct {
|
|||
// default or empty string means all namespaces
|
||||
namespace string
|
||||
|
||||
byGVK map[schema.GroupVersionKind]InformersOptsByGVK
|
||||
}
|
||||
selector Selector
|
||||
transform cache.TransformFunc
|
||||
unsafeDisableDeepCopy bool
|
||||
|
||||
func (ip *Informers) getSelector(gvk schema.GroupVersionKind) Selector {
|
||||
if ip.byGVK == nil {
|
||||
return Selector{}
|
||||
}
|
||||
if res, ok := ip.byGVK[gvk]; ok {
|
||||
return res.Selector
|
||||
}
|
||||
if res, ok := ip.byGVK[schema.GroupVersionKind{}]; ok {
|
||||
return res.Selector
|
||||
}
|
||||
return Selector{}
|
||||
}
|
||||
|
||||
func (ip *Informers) getTransform(gvk schema.GroupVersionKind) cache.TransformFunc {
|
||||
if ip.byGVK == nil {
|
||||
return nil
|
||||
}
|
||||
if res, ok := ip.byGVK[gvk]; ok {
|
||||
return res.Transform
|
||||
}
|
||||
if res, ok := ip.byGVK[schema.GroupVersionKind{}]; ok {
|
||||
return res.Transform
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ip *Informers) getDisableDeepCopy(gvk schema.GroupVersionKind) bool {
|
||||
if ip.byGVK == nil {
|
||||
return false
|
||||
}
|
||||
if res, ok := ip.byGVK[gvk]; ok && res.UnsafeDisableDeepCopy != nil {
|
||||
return *res.UnsafeDisableDeepCopy
|
||||
}
|
||||
if res, ok := ip.byGVK[schema.GroupVersionKind{}]; ok && res.UnsafeDisableDeepCopy != nil {
|
||||
return *res.UnsafeDisableDeepCopy
|
||||
}
|
||||
return false
|
||||
// NewInformer allows overriding of the shared index informer constructor for testing.
|
||||
newInformer func(cache.ListerWatcher, runtime.Object, time.Duration, cache.Indexers) cache.SharedIndexInformer
|
||||
}
|
||||
|
||||
// Start calls Run on each of the informers and sets started to true. Blocks on the context.
|
||||
|
@ -271,7 +246,8 @@ func (ip *Informers) WaitForCacheSync(ctx context.Context) bool {
|
|||
return cache.WaitForCacheSync(ctx.Done(), ip.getHasSyncedFuncs()...)
|
||||
}
|
||||
|
||||
func (ip *Informers) get(gvk schema.GroupVersionKind, obj runtime.Object) (res *Cache, started bool, ok bool) {
|
||||
// Peek attempts to get the informer for the GVK, but does not start one if one does not exist.
|
||||
func (ip *Informers) Peek(gvk schema.GroupVersionKind, obj runtime.Object) (res *Cache, started bool, ok bool) {
|
||||
ip.mu.RLock()
|
||||
defer ip.mu.RUnlock()
|
||||
i, ok := ip.informersByType(obj)[gvk]
|
||||
|
@ -280,9 +256,9 @@ func (ip *Informers) get(gvk schema.GroupVersionKind, obj runtime.Object) (res *
|
|||
|
||||
// Get will create a new Informer and add it to the map of specificInformersMap if none exists. Returns
|
||||
// the Informer from the map.
|
||||
func (ip *Informers) Get(ctx context.Context, gvk schema.GroupVersionKind, obj runtime.Object) (bool, *Cache, error) {
|
||||
func (ip *Informers) Get(ctx context.Context, gvk schema.GroupVersionKind, obj runtime.Object, opts *GetOptions) (bool, *Cache, error) {
|
||||
// Return the informer if it is found
|
||||
i, started, ok := ip.get(gvk, obj)
|
||||
i, started, ok := ip.Peek(gvk, obj)
|
||||
if !ok {
|
||||
var err error
|
||||
if i, started, err = ip.addInformerToMap(gvk, obj); err != nil {
|
||||
|
@ -290,7 +266,12 @@ func (ip *Informers) Get(ctx context.Context, gvk schema.GroupVersionKind, obj r
|
|||
}
|
||||
}
|
||||
|
||||
if started && !i.Informer.HasSynced() {
|
||||
shouldBlock := true
|
||||
if opts.BlockUntilSynced != nil {
|
||||
shouldBlock = *opts.BlockUntilSynced
|
||||
}
|
||||
|
||||
if shouldBlock && started && !i.Informer.HasSynced() {
|
||||
// Wait for it to sync before returning the Informer so that folks don't read from a stale cache.
|
||||
if !cache.WaitForCacheSync(ctx.Done(), i.Informer.HasSynced) {
|
||||
return started, nil, apierrors.NewTimeoutError(fmt.Sprintf("failed waiting for %T Informer to sync", obj), 0)
|
||||
|
@ -311,6 +292,7 @@ func (ip *Informers) informersByType(obj runtime.Object) map[schema.GroupVersion
|
|||
}
|
||||
}
|
||||
|
||||
// addInformerToMap either returns an existing informer or creates a new informer, adds it to the map and returns it.
|
||||
func (ip *Informers) addInformerToMap(gvk schema.GroupVersionKind, obj runtime.Object) (*Cache, bool, error) {
|
||||
ip.mu.Lock()
|
||||
defer ip.mu.Unlock()
|
||||
|
@ -327,13 +309,13 @@ func (ip *Informers) addInformerToMap(gvk schema.GroupVersionKind, obj runtime.O
|
|||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
sharedIndexInformer := cache.NewSharedIndexInformer(&cache.ListWatch{
|
||||
sharedIndexInformer := ip.newInformer(&cache.ListWatch{
|
||||
ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) {
|
||||
ip.getSelector(gvk).ApplyToList(&opts)
|
||||
ip.selector.ApplyToList(&opts)
|
||||
return listWatcher.ListFunc(opts)
|
||||
},
|
||||
WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) {
|
||||
ip.getSelector(gvk).ApplyToList(&opts)
|
||||
ip.selector.ApplyToList(&opts)
|
||||
opts.Watch = true // Watch needs to be set to true separately
|
||||
return listWatcher.WatchFunc(opts)
|
||||
},
|
||||
|
@ -342,7 +324,7 @@ func (ip *Informers) addInformerToMap(gvk schema.GroupVersionKind, obj runtime.O
|
|||
})
|
||||
|
||||
// Check to see if there is a transformer for this gvk
|
||||
if err := sharedIndexInformer.SetTransform(ip.getTransform(gvk)); err != nil {
|
||||
if err := sharedIndexInformer.SetTransform(ip.transform); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
|
@ -358,7 +340,7 @@ func (ip *Informers) addInformerToMap(gvk schema.GroupVersionKind, obj runtime.O
|
|||
indexer: sharedIndexInformer.GetIndexer(),
|
||||
groupVersionKind: gvk,
|
||||
scopeName: mapping.Scope.Name(),
|
||||
disableDeepCopy: ip.getDisableDeepCopy(gvk),
|
||||
disableDeepCopy: ip.unsafeDisableDeepCopy,
|
||||
},
|
||||
}
|
||||
ip.informersByType(obj)[gvk] = i
|
||||
|
@ -382,7 +364,7 @@ func (ip *Informers) makeListWatcher(gvk schema.GroupVersionKind, obj runtime.Ob
|
|||
// Figure out if the GVK we're dealing with is global, or namespace scoped.
|
||||
var namespace string
|
||||
if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
|
||||
namespace = restrictNamespaceBySelector(ip.namespace, ip.getSelector(gvk))
|
||||
namespace = restrictNamespaceBySelector(ip.namespace, ip.selector)
|
||||
}
|
||||
|
||||
switch obj.(type) {
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
|
||||
)
|
||||
|
||||
// TransformFuncByGVK provides access to the correct transform function for
|
||||
// any given GVK.
|
||||
type TransformFuncByGVK interface {
|
||||
Set(runtime.Object, *runtime.Scheme, cache.TransformFunc) error
|
||||
Get(schema.GroupVersionKind) cache.TransformFunc
|
||||
SetDefault(transformer cache.TransformFunc)
|
||||
}
|
||||
|
||||
type transformFuncByGVK struct {
|
||||
defaultTransform cache.TransformFunc
|
||||
transformers map[schema.GroupVersionKind]cache.TransformFunc
|
||||
}
|
||||
|
||||
// TransformFuncByGVKFromMap creates a TransformFuncByGVK from a map that
|
||||
// maps GVKs to TransformFuncs.
|
||||
func TransformFuncByGVKFromMap(in map[schema.GroupVersionKind]cache.TransformFunc) TransformFuncByGVK {
|
||||
byGVK := &transformFuncByGVK{}
|
||||
if defaultFunc, hasDefault := in[schema.GroupVersionKind{}]; hasDefault {
|
||||
byGVK.defaultTransform = defaultFunc
|
||||
}
|
||||
delete(in, schema.GroupVersionKind{})
|
||||
byGVK.transformers = in
|
||||
return byGVK
|
||||
}
|
||||
|
||||
func (t *transformFuncByGVK) SetDefault(transformer cache.TransformFunc) {
|
||||
t.defaultTransform = transformer
|
||||
}
|
||||
|
||||
func (t *transformFuncByGVK) Set(obj runtime.Object, scheme *runtime.Scheme, transformer cache.TransformFunc) error {
|
||||
gvk, err := apiutil.GVKForObject(obj, scheme)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.transformers[gvk] = transformer
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t transformFuncByGVK) Get(gvk schema.GroupVersionKind) cache.TransformFunc {
|
||||
if val, ok := t.transformers[gvk]; ok {
|
||||
return val
|
||||
}
|
||||
return t.defaultTransform
|
||||
}
|
|
@ -23,10 +23,11 @@ import (
|
|||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/rest"
|
||||
toolscache "k8s.io/client-go/tools/cache"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
|
||||
)
|
||||
|
@ -34,49 +35,31 @@ import (
|
|||
// a new global namespaced cache to handle cluster scoped resources.
|
||||
const globalCache = "_cluster-scope"
|
||||
|
||||
// MultiNamespacedCacheBuilder - Builder function to create a new multi-namespaced cache.
|
||||
// This will scope the cache to a list of namespaces. Listing for all namespaces
|
||||
// will list for all the namespaces that this knows about. By default this will create
|
||||
// a global cache for cluster scoped resource. Note that this is not intended
|
||||
// to be used for excluding namespaces, this is better done via a Predicate. Also note that
|
||||
// you may face performance issues when using this with a high number of namespaces.
|
||||
//
|
||||
// Deprecated: Use cache.Options.Namespaces instead.
|
||||
func MultiNamespacedCacheBuilder(namespaces []string) NewCacheFunc {
|
||||
return func(config *rest.Config, opts Options) (Cache, error) {
|
||||
opts.Namespaces = namespaces
|
||||
return newMultiNamespaceCache(config, opts)
|
||||
}
|
||||
}
|
||||
|
||||
func newMultiNamespaceCache(config *rest.Config, opts Options) (Cache, error) {
|
||||
if len(opts.Namespaces) < 2 {
|
||||
return nil, fmt.Errorf("must specify more than one namespace to use multi-namespace cache")
|
||||
}
|
||||
opts, err := defaultOpts(config, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func newMultiNamespaceCache(
|
||||
newCache newCacheFunc,
|
||||
scheme *runtime.Scheme,
|
||||
restMapper apimeta.RESTMapper,
|
||||
namespaces map[string]Config,
|
||||
globalConfig *Config, // may be nil in which case no cache for cluster-scoped objects will be created
|
||||
) Cache {
|
||||
// Create every namespace cache.
|
||||
caches := map[string]Cache{}
|
||||
for _, ns := range opts.Namespaces {
|
||||
opts.Namespaces = []string{ns}
|
||||
c, err := New(config, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caches[ns] = c
|
||||
for namespace, config := range namespaces {
|
||||
caches[namespace] = newCache(config, namespace)
|
||||
}
|
||||
|
||||
// Create a cache for cluster scoped resources.
|
||||
opts.Namespaces = []string{}
|
||||
gCache, err := New(config, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating global cache: %w", err)
|
||||
// Create a cache for cluster scoped resources if requested
|
||||
var clusterCache Cache
|
||||
if globalConfig != nil {
|
||||
clusterCache = newCache(*globalConfig, corev1.NamespaceAll)
|
||||
}
|
||||
|
||||
return &multiNamespaceCache{namespaceToCache: caches, Scheme: opts.Scheme, RESTMapper: opts.Mapper, clusterCache: gCache}, nil
|
||||
return &multiNamespaceCache{
|
||||
namespaceToCache: caches,
|
||||
Scheme: scheme,
|
||||
RESTMapper: restMapper,
|
||||
clusterCache: clusterCache,
|
||||
}
|
||||
}
|
||||
|
||||
// multiNamespaceCache knows how to handle multiple namespaced caches
|
||||
|
@ -84,90 +67,96 @@ func newMultiNamespaceCache(config *rest.Config, opts Options) (Cache, error) {
|
|||
// operator to a list of namespaces instead of watching every namespace
|
||||
// in the cluster.
|
||||
type multiNamespaceCache struct {
|
||||
namespaceToCache map[string]Cache
|
||||
Scheme *runtime.Scheme
|
||||
RESTMapper apimeta.RESTMapper
|
||||
namespaceToCache map[string]Cache
|
||||
clusterCache Cache
|
||||
}
|
||||
|
||||
var _ Cache = &multiNamespaceCache{}
|
||||
|
||||
// Methods for multiNamespaceCache to conform to the Informers interface.
|
||||
func (c *multiNamespaceCache) GetInformer(ctx context.Context, obj client.Object) (Informer, error) {
|
||||
informers := map[string]Informer{}
|
||||
|
||||
// If the object is clusterscoped, get the informer from clusterCache,
|
||||
func (c *multiNamespaceCache) GetInformer(ctx context.Context, obj client.Object, opts ...InformerGetOption) (Informer, error) {
|
||||
// If the object is cluster scoped, get the informer from clusterCache,
|
||||
// if not use the namespaced caches.
|
||||
isNamespaced, err := apiutil.IsObjectNamespaced(obj, c.Scheme, c.RESTMapper)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isNamespaced {
|
||||
clusterCacheInf, err := c.clusterCache.GetInformer(ctx, obj)
|
||||
clusterCacheInformer, err := c.clusterCache.GetInformer(ctx, obj, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
informers[globalCache] = clusterCacheInf
|
||||
|
||||
return &multiNamespaceInformer{namespaceToInformer: informers}, nil
|
||||
return &multiNamespaceInformer{
|
||||
namespaceToInformer: map[string]Informer{
|
||||
globalCache: clusterCacheInformer,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
namespaceToInformer := map[string]Informer{}
|
||||
for ns, cache := range c.namespaceToCache {
|
||||
informer, err := cache.GetInformer(ctx, obj)
|
||||
informer, err := cache.GetInformer(ctx, obj, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
informers[ns] = informer
|
||||
namespaceToInformer[ns] = informer
|
||||
}
|
||||
|
||||
return &multiNamespaceInformer{namespaceToInformer: informers}, nil
|
||||
return &multiNamespaceInformer{namespaceToInformer: namespaceToInformer}, nil
|
||||
}
|
||||
|
||||
func (c *multiNamespaceCache) GetInformerForKind(ctx context.Context, gvk schema.GroupVersionKind) (Informer, error) {
|
||||
informers := map[string]Informer{}
|
||||
|
||||
// If the object is clusterscoped, get the informer from clusterCache,
|
||||
func (c *multiNamespaceCache) GetInformerForKind(ctx context.Context, gvk schema.GroupVersionKind, opts ...InformerGetOption) (Informer, error) {
|
||||
// If the object is cluster scoped, get the informer from clusterCache,
|
||||
// if not use the namespaced caches.
|
||||
isNamespaced, err := apiutil.IsGVKNamespaced(gvk, c.RESTMapper)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isNamespaced {
|
||||
clusterCacheInf, err := c.clusterCache.GetInformerForKind(ctx, gvk)
|
||||
clusterCacheInformer, err := c.clusterCache.GetInformerForKind(ctx, gvk, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
informers[globalCache] = clusterCacheInf
|
||||
|
||||
return &multiNamespaceInformer{namespaceToInformer: informers}, nil
|
||||
return &multiNamespaceInformer{
|
||||
namespaceToInformer: map[string]Informer{
|
||||
globalCache: clusterCacheInformer,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
namespaceToInformer := map[string]Informer{}
|
||||
for ns, cache := range c.namespaceToCache {
|
||||
informer, err := cache.GetInformerForKind(ctx, gvk)
|
||||
informer, err := cache.GetInformerForKind(ctx, gvk, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
informers[ns] = informer
|
||||
namespaceToInformer[ns] = informer
|
||||
}
|
||||
|
||||
return &multiNamespaceInformer{namespaceToInformer: informers}, nil
|
||||
return &multiNamespaceInformer{namespaceToInformer: namespaceToInformer}, nil
|
||||
}
|
||||
|
||||
func (c *multiNamespaceCache) Start(ctx context.Context) error {
|
||||
// start global cache
|
||||
if c.clusterCache != nil {
|
||||
go func() {
|
||||
err := c.clusterCache.Start(ctx)
|
||||
if err != nil {
|
||||
log.Error(err, "cluster scoped cache failed to start")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// start namespaced caches
|
||||
for ns, cache := range c.namespaceToCache {
|
||||
go func(ns string, cache Cache) {
|
||||
err := cache.Start(ctx)
|
||||
if err != nil {
|
||||
log.Error(err, "multinamespace cache failed to start namespaced informer", "namespace", ns)
|
||||
if err := cache.Start(ctx); err != nil {
|
||||
log.Error(err, "multi-namespace cache failed to start namespaced informer", "namespace", ns)
|
||||
}
|
||||
}(ns, cache)
|
||||
}
|
||||
|
@ -179,13 +168,13 @@ func (c *multiNamespaceCache) Start(ctx context.Context) error {
|
|||
func (c *multiNamespaceCache) WaitForCacheSync(ctx context.Context) bool {
|
||||
synced := true
|
||||
for _, cache := range c.namespaceToCache {
|
||||
if s := cache.WaitForCacheSync(ctx); !s {
|
||||
synced = s
|
||||
if !cache.WaitForCacheSync(ctx) {
|
||||
synced = false
|
||||
}
|
||||
}
|
||||
|
||||
// check if cluster scoped cache has synced
|
||||
if !c.clusterCache.WaitForCacheSync(ctx) {
|
||||
if c.clusterCache != nil && !c.clusterCache.WaitForCacheSync(ctx) {
|
||||
synced = false
|
||||
}
|
||||
return synced
|
||||
|
@ -222,9 +211,12 @@ func (c *multiNamespaceCache) Get(ctx context.Context, key client.ObjectKey, obj
|
|||
|
||||
cache, ok := c.namespaceToCache[key.Namespace]
|
||||
if !ok {
|
||||
if global, hasGlobal := c.namespaceToCache[metav1.NamespaceAll]; hasGlobal {
|
||||
return global.Get(ctx, key, obj, opts...)
|
||||
}
|
||||
return fmt.Errorf("unable to get: %v because of unknown namespace for the cache", key)
|
||||
}
|
||||
return cache.Get(ctx, key, obj)
|
||||
return cache.Get(ctx, key, obj, opts...)
|
||||
}
|
||||
|
||||
// List multi namespace cache will get all the objects in the namespaces that the cache is watching if asked for all namespaces.
|
||||
|
@ -245,7 +237,7 @@ func (c *multiNamespaceCache) List(ctx context.Context, list client.ObjectList,
|
|||
if listOpts.Namespace != corev1.NamespaceAll {
|
||||
cache, ok := c.namespaceToCache[listOpts.Namespace]
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to get: %v because of unknown namespace for the cache", listOpts.Namespace)
|
||||
return fmt.Errorf("unable to list: %v because of unknown namespace for the cache", listOpts.Namespace)
|
||||
}
|
||||
return cache.List(ctx, list, opts...)
|
||||
}
|
||||
|
@ -278,12 +270,14 @@ func (c *multiNamespaceCache) List(ctx context.Context, list client.ObjectList,
|
|||
return fmt.Errorf("object: %T must be a list type", list)
|
||||
}
|
||||
allItems = append(allItems, items...)
|
||||
|
||||
// The last list call should have the most correct resource version.
|
||||
resourceVersion = accessor.GetResourceVersion()
|
||||
if limitSet {
|
||||
// decrement Limit by the number of items
|
||||
// fetched from the current namespace.
|
||||
listOpts.Limit -= int64(len(items))
|
||||
|
||||
// if a Limit was set and the number of
|
||||
// items read has reached this set limit,
|
||||
// then stop reading.
|
||||
|
@ -325,9 +319,12 @@ func (h handlerRegistration) HasSynced() bool {
|
|||
|
||||
var _ Informer = &multiNamespaceInformer{}
|
||||
|
||||
// AddEventHandler adds the handler to each namespaced informer.
|
||||
// AddEventHandler adds the handler to each informer.
|
||||
func (i *multiNamespaceInformer) AddEventHandler(handler toolscache.ResourceEventHandler) (toolscache.ResourceEventHandlerRegistration, error) {
|
||||
handles := handlerRegistration{handles: make(map[string]toolscache.ResourceEventHandlerRegistration, len(i.namespaceToInformer))}
|
||||
handles := handlerRegistration{
|
||||
handles: make(map[string]toolscache.ResourceEventHandlerRegistration, len(i.namespaceToInformer)),
|
||||
}
|
||||
|
||||
for ns, informer := range i.namespaceToInformer {
|
||||
registration, err := informer.AddEventHandler(handler)
|
||||
if err != nil {
|
||||
|
@ -335,12 +332,16 @@ func (i *multiNamespaceInformer) AddEventHandler(handler toolscache.ResourceEven
|
|||
}
|
||||
handles.handles[ns] = registration
|
||||
}
|
||||
|
||||
return handles, nil
|
||||
}
|
||||
|
||||
// AddEventHandlerWithResyncPeriod adds the handler with a resync period to each namespaced informer.
|
||||
func (i *multiNamespaceInformer) AddEventHandlerWithResyncPeriod(handler toolscache.ResourceEventHandler, resyncPeriod time.Duration) (toolscache.ResourceEventHandlerRegistration, error) {
|
||||
handles := handlerRegistration{handles: make(map[string]toolscache.ResourceEventHandlerRegistration, len(i.namespaceToInformer))}
|
||||
handles := handlerRegistration{
|
||||
handles: make(map[string]toolscache.ResourceEventHandlerRegistration, len(i.namespaceToInformer)),
|
||||
}
|
||||
|
||||
for ns, informer := range i.namespaceToInformer {
|
||||
registration, err := informer.AddEventHandlerWithResyncPeriod(handler, resyncPeriod)
|
||||
if err != nil {
|
||||
|
@ -348,14 +349,15 @@ func (i *multiNamespaceInformer) AddEventHandlerWithResyncPeriod(handler toolsca
|
|||
}
|
||||
handles.handles[ns] = registration
|
||||
}
|
||||
|
||||
return handles, nil
|
||||
}
|
||||
|
||||
// RemoveEventHandler removes a formerly added event handler given by its registration handle.
|
||||
// RemoveEventHandler removes a previously added event handler given by its registration handle.
|
||||
func (i *multiNamespaceInformer) RemoveEventHandler(h toolscache.ResourceEventHandlerRegistration) error {
|
||||
handles, ok := h.(handlerRegistration)
|
||||
if !ok {
|
||||
return fmt.Errorf("it is not the registration returned by multiNamespaceInformer")
|
||||
return fmt.Errorf("registration is not a registration returned by multiNamespaceInformer")
|
||||
}
|
||||
for ns, informer := range i.namespaceToInformer {
|
||||
registration, ok := handles.handles[ns]
|
||||
|
@ -369,7 +371,7 @@ func (i *multiNamespaceInformer) RemoveEventHandler(h toolscache.ResourceEventHa
|
|||
return nil
|
||||
}
|
||||
|
||||
// AddIndexers adds the indexer for each namespaced informer.
|
||||
// AddIndexers adds the indexers to each informer.
|
||||
func (i *multiNamespaceInformer) AddIndexers(indexers toolscache.Indexers) error {
|
||||
for _, informer := range i.namespaceToInformer {
|
||||
err := informer.AddIndexers(indexers)
|
||||
|
@ -380,11 +382,11 @@ func (i *multiNamespaceInformer) AddIndexers(indexers toolscache.Indexers) error
|
|||
return nil
|
||||
}
|
||||
|
||||
// HasSynced checks if each namespaced informer has synced.
|
||||
// HasSynced checks if each informer has synced.
|
||||
func (i *multiNamespaceInformer) HasSynced() bool {
|
||||
for _, informer := range i.namespaceToInformer {
|
||||
if ok := informer.HasSynced(); !ok {
|
||||
return ok
|
||||
if !informer.HasSynced() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
|
54
vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/errors.go
generated
vendored
Normal file
54
vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/errors.go
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package apiutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// ErrResourceDiscoveryFailed is returned if the RESTMapper cannot discover supported resources for some GroupVersions.
|
||||
// It wraps the errors encountered, except "NotFound" errors are replaced with meta.NoResourceMatchError, for
|
||||
// backwards compatibility with code that uses meta.IsNoMatchError() to check for unsupported APIs.
|
||||
type ErrResourceDiscoveryFailed map[schema.GroupVersion]error
|
||||
|
||||
// Error implements the error interface.
|
||||
func (e *ErrResourceDiscoveryFailed) Error() string {
|
||||
subErrors := []string{}
|
||||
for k, v := range *e {
|
||||
subErrors = append(subErrors, fmt.Sprintf("%s: %v", k, v))
|
||||
}
|
||||
sort.Strings(subErrors)
|
||||
return fmt.Sprintf("unable to retrieve the complete list of server APIs: %s", strings.Join(subErrors, ", "))
|
||||
}
|
||||
|
||||
func (e *ErrResourceDiscoveryFailed) Unwrap() []error {
|
||||
subErrors := []error{}
|
||||
for gv, err := range *e {
|
||||
if apierrors.IsNotFound(err) {
|
||||
err = &meta.NoResourceMatchError{PartialResource: gv.WithResource("")}
|
||||
}
|
||||
subErrors = append(subErrors, err)
|
||||
}
|
||||
return subErrors
|
||||
}
|
|
@ -152,6 +152,12 @@ func (m *mapper) getMapper() meta.RESTMapper {
|
|||
// addKnownGroupAndReload reloads the mapper with updated information about missing API group.
|
||||
// versions can be specified for partial updates, for instance for v1beta1 version only.
|
||||
func (m *mapper) addKnownGroupAndReload(groupName string, versions ...string) error {
|
||||
// versions will here be [""] if the forwarded Version value of
|
||||
// GroupVersionResource (in calling method) was not specified.
|
||||
if len(versions) == 1 && versions[0] == "" {
|
||||
versions = nil
|
||||
}
|
||||
|
||||
// If no specific versions are set by user, we will scan all available ones for the API group.
|
||||
// This operation requires 2 requests: /api and /apis, but only once. For all subsequent calls
|
||||
// this data will be taken from cache.
|
||||
|
@ -280,7 +286,8 @@ func (m *mapper) fetchGroupVersionResources(groupName string, versions ...string
|
|||
}
|
||||
|
||||
if len(failedGroups) > 0 {
|
||||
return nil, &discovery.ErrGroupDiscoveryFailed{Groups: failedGroups}
|
||||
err := ErrResourceDiscoveryFailed(failedGroups)
|
||||
return nil, &err
|
||||
}
|
||||
|
||||
return groupVersionResources, nil
|
||||
|
|
|
@ -77,10 +77,12 @@ type CacheOptions struct {
|
|||
// Reader is a cache-backed reader that will be used to read objects from the cache.
|
||||
// +required
|
||||
Reader Reader
|
||||
// DisableFor is a list of objects that should not be read from the cache.
|
||||
// DisableFor is a list of objects that should never be read from the cache.
|
||||
// Objects configured here always result in a live lookup.
|
||||
DisableFor []Object
|
||||
// Unstructured is a flag that indicates whether the cache-backed client should
|
||||
// read unstructured objects or lists from the cache.
|
||||
// If false, unstructured objects will always result in a live lookup.
|
||||
Unstructured bool
|
||||
}
|
||||
|
||||
|
@ -110,6 +112,11 @@ func newClient(config *rest.Config, options Options) (*client, error) {
|
|||
return nil, fmt.Errorf("must provide non-nil rest.Config to client.New")
|
||||
}
|
||||
|
||||
config = rest.CopyConfig(config)
|
||||
if config.UserAgent == "" {
|
||||
config.UserAgent = rest.DefaultKubernetesUserAgent()
|
||||
}
|
||||
|
||||
if !options.WarningHandler.SuppressWarnings {
|
||||
// surface warnings
|
||||
logger := log.Log.WithName("KubeAPIWarningLogger")
|
||||
|
@ -117,7 +124,6 @@ func newClient(config *rest.Config, options Options) (*client, error) {
|
|||
// is log.KubeAPIWarningLogger with deduplication enabled.
|
||||
// See log.KubeAPIWarningLoggerOptions for considerations
|
||||
// regarding deduplication.
|
||||
config = rest.CopyConfig(config)
|
||||
config.WarningHandler = log.NewKubeAPIWarningLogger(
|
||||
logger,
|
||||
log.KubeAPIWarningLoggerOptions{
|
||||
|
@ -160,7 +166,7 @@ func newClient(config *rest.Config, options Options) (*client, error) {
|
|||
unstructuredResourceByType: make(map[schema.GroupVersionKind]*resourceMeta),
|
||||
}
|
||||
|
||||
rawMetaClient, err := metadata.NewForConfigAndClient(config, options.HTTPClient)
|
||||
rawMetaClient, err := metadata.NewForConfigAndClient(metadata.ConfigFor(config), options.HTTPClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to construct metadata-only client for use as part of client: %w", err)
|
||||
}
|
||||
|
@ -338,9 +344,11 @@ func (c *client) Get(ctx context.Context, key ObjectKey, obj Object, opts ...Get
|
|||
if isUncached, err := c.shouldBypassCache(obj); err != nil {
|
||||
return err
|
||||
} else if !isUncached {
|
||||
// Attempt to get from the cache.
|
||||
return c.cache.Get(ctx, key, obj, opts...)
|
||||
}
|
||||
|
||||
// Perform a live lookup.
|
||||
switch obj.(type) {
|
||||
case runtime.Unstructured:
|
||||
return c.unstructuredClient.Get(ctx, key, obj, opts...)
|
||||
|
@ -358,9 +366,11 @@ func (c *client) List(ctx context.Context, obj ObjectList, opts ...ListOption) e
|
|||
if isUncached, err := c.shouldBypassCache(obj); err != nil {
|
||||
return err
|
||||
} else if !isUncached {
|
||||
// Attempt to get from the cache.
|
||||
return c.cache.List(ctx, obj, opts...)
|
||||
}
|
||||
|
||||
// Perform a live lookup.
|
||||
switch x := obj.(type) {
|
||||
case runtime.Unstructured:
|
||||
return c.unstructuredClient.List(ctx, obj, opts...)
|
||||
|
|
|
@ -31,8 +31,6 @@ import (
|
|||
|
||||
// Using v4 to match upstream
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/interceptor"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
policyv1 "k8s.io/api/policy/v1"
|
||||
policyv1beta1 "k8s.io/api/policy/v1beta1"
|
||||
|
@ -52,10 +50,11 @@ import (
|
|||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/testing"
|
||||
"sigs.k8s.io/controller-runtime/pkg/internal/field/selector"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/interceptor"
|
||||
"sigs.k8s.io/controller-runtime/pkg/internal/field/selector"
|
||||
"sigs.k8s.io/controller-runtime/pkg/internal/objectutil"
|
||||
)
|
||||
|
||||
|
@ -88,21 +87,10 @@ const (
|
|||
|
||||
// NewFakeClient creates a new fake client for testing.
|
||||
// You can choose to initialize it with a slice of runtime.Object.
|
||||
//
|
||||
// Deprecated: Please use NewClientBuilder instead.
|
||||
func NewFakeClient(initObjs ...runtime.Object) client.WithWatch {
|
||||
return NewClientBuilder().WithRuntimeObjects(initObjs...).Build()
|
||||
}
|
||||
|
||||
// NewFakeClientWithScheme creates a new fake client with the given scheme
|
||||
// for testing.
|
||||
// You can choose to initialize it with a slice of runtime.Object.
|
||||
//
|
||||
// Deprecated: Please use NewClientBuilder instead.
|
||||
func NewFakeClientWithScheme(clientScheme *runtime.Scheme, initObjs ...runtime.Object) client.WithWatch {
|
||||
return NewClientBuilder().WithScheme(clientScheme).WithRuntimeObjects(initObjs...).Build()
|
||||
}
|
||||
|
||||
// NewClientBuilder returns a new builder to create a fake client.
|
||||
func NewClientBuilder() *ClientBuilder {
|
||||
return &ClientBuilder{}
|
||||
|
@ -373,7 +361,7 @@ func (t versionedTracker) Update(gvr schema.GroupVersionResource, obj runtime.Ob
|
|||
isStatus := false
|
||||
// We apply patches using a client-go reaction that ends up calling the trackers Update. As we can't change
|
||||
// that reaction, we use the callstack to figure out if this originated from the status client.
|
||||
if bytes.Contains(debug.Stack(), []byte("sigs.k8s.io/controller-runtime/pkg/client/fake.(*fakeSubResourceClient).Patch")) {
|
||||
if bytes.Contains(debug.Stack(), []byte("sigs.k8s.io/controller-runtime/pkg/client/fake.(*fakeSubResourceClient).statusPatch")) {
|
||||
isStatus = true
|
||||
}
|
||||
return t.update(gvr, obj, ns, isStatus, false)
|
||||
|
@ -412,9 +400,14 @@ func (t versionedTracker) update(gvr schema.GroupVersionResource, obj runtime.Ob
|
|||
|
||||
if t.withStatusSubresource.Has(gvk) {
|
||||
if isStatus { // copy everything but status and metadata.ResourceVersion from original object
|
||||
if err := copyNonStatusFrom(oldObject, obj); err != nil {
|
||||
if err := copyStatusFrom(obj, oldObject); err != nil {
|
||||
return fmt.Errorf("failed to copy non-status field for object with status subresouce: %w", err)
|
||||
}
|
||||
passedRV := accessor.GetResourceVersion()
|
||||
if err := copyFrom(oldObject, obj); err != nil {
|
||||
return fmt.Errorf("failed to restore non-status fields: %w", err)
|
||||
}
|
||||
accessor.SetResourceVersion(passedRV)
|
||||
} else { // copy status from original object
|
||||
if err := copyStatusFrom(oldObject, obj); err != nil {
|
||||
return fmt.Errorf("failed to copy the status for object with status subresource: %w", err)
|
||||
|
@ -961,45 +954,6 @@ func dryPatch(action testing.PatchActionImpl, tracker testing.ObjectTracker) (ru
|
|||
return obj, nil
|
||||
}
|
||||
|
||||
func copyNonStatusFrom(old, new runtime.Object) error {
|
||||
newClientObject, ok := new.(client.Object)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T is not a client.Object", new)
|
||||
}
|
||||
// The only thing other than status we have to retain
|
||||
rv := newClientObject.GetResourceVersion()
|
||||
|
||||
oldMapStringAny, err := toMapStringAny(old)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert old to *unstructured.Unstructured: %w", err)
|
||||
}
|
||||
newMapStringAny, err := toMapStringAny(new)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert new to *unststructured.Unstructured: %w", err)
|
||||
}
|
||||
|
||||
// delete everything other than status in case it has fields that were not present in
|
||||
// the old object
|
||||
for k := range newMapStringAny {
|
||||
if k != "status" {
|
||||
delete(newMapStringAny, k)
|
||||
}
|
||||
}
|
||||
// copy everything other than status from the old object
|
||||
for k := range oldMapStringAny {
|
||||
if k != "status" {
|
||||
newMapStringAny[k] = oldMapStringAny[k]
|
||||
}
|
||||
}
|
||||
|
||||
newClientObject.SetResourceVersion(rv)
|
||||
|
||||
if err := fromMapStringAny(newMapStringAny, new); err != nil {
|
||||
return fmt.Errorf("failed to convert back from map[string]any: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// copyStatusFrom copies the status from old into new
|
||||
func copyStatusFrom(old, new runtime.Object) error {
|
||||
oldMapStringAny, err := toMapStringAny(old)
|
||||
|
@ -1020,6 +974,19 @@ func copyStatusFrom(old, new runtime.Object) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// copyFrom copies from old into new
|
||||
func copyFrom(old, new runtime.Object) error {
|
||||
oldMapStringAny, err := toMapStringAny(old)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert old to *unstructured.Unstructured: %w", err)
|
||||
}
|
||||
if err := fromMapStringAny(oldMapStringAny, new); err != nil {
|
||||
return fmt.Errorf("failed to convert back from map[string]any: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func toMapStringAny(obj runtime.Object) (map[string]any, error) {
|
||||
if unstructured, isUnstructured := obj.(*unstructured.Unstructured); isUnstructured {
|
||||
return unstructured.Object, nil
|
||||
|
@ -1045,6 +1012,7 @@ func fromMapStringAny(u map[string]any, target runtime.Object) error {
|
|||
return fmt.Errorf("failed to serialize: %w", err)
|
||||
}
|
||||
|
||||
zero(target)
|
||||
if err := json.Unmarshal(serialized, &target); err != nil {
|
||||
return fmt.Errorf("failed to deserialize: %w", err)
|
||||
}
|
||||
|
@ -1137,6 +1105,15 @@ func (sw *fakeSubResourceClient) Patch(ctx context.Context, obj client.Object, p
|
|||
body = patchOptions.SubResourceBody
|
||||
}
|
||||
|
||||
// this is necessary to identify that last call was made for status patch, through stack trace.
|
||||
if sw.subResource == "status" {
|
||||
return sw.statusPatch(body, patch, patchOptions)
|
||||
}
|
||||
|
||||
return sw.client.patch(body, patch, &patchOptions.PatchOptions)
|
||||
}
|
||||
|
||||
func (sw *fakeSubResourceClient) statusPatch(body client.Object, patch client.Patch, patchOptions client.SubResourcePatchOptions) error {
|
||||
return sw.client.patch(body, patch, &patchOptions.PatchOptions)
|
||||
}
|
||||
|
||||
|
@ -1177,7 +1154,7 @@ func allowsUnconditionalUpdate(gvk schema.GroupVersionKind) bool {
|
|||
case "PodSecurityPolicy":
|
||||
return true
|
||||
}
|
||||
case "rbac":
|
||||
case "rbac.authorization.k8s.io":
|
||||
switch gvk.Kind {
|
||||
case "ClusterRole", "ClusterRoleBinding", "Role", "RoleBinding":
|
||||
return true
|
||||
|
|
|
@ -20,7 +20,7 @@ Package fake provides a fake client for testing.
|
|||
A fake client is backed by its simple object store indexed by GroupVersionResource.
|
||||
You can create a fake client with optional objects.
|
||||
|
||||
client := NewFakeClientWithScheme(scheme, initObjs...) // initObjs is a slice of runtime.Object
|
||||
client := NewClientBuilder().WithScheme(scheme).WithObj(initObjs...).Build()
|
||||
|
||||
You can invoke the methods defined in the Client interface.
|
||||
|
||||
|
|
|
@ -142,6 +142,7 @@ type SubResourceWriter interface {
|
|||
// Create saves the subResource object in the Kubernetes cluster. obj must be a
|
||||
// struct pointer so that obj can be updated with the content returned by the Server.
|
||||
Create(ctx context.Context, obj Object, subResource Object, opts ...SubResourceCreateOption) error
|
||||
|
||||
// Update updates the fields corresponding to the status subresource for the
|
||||
// given obj. obj must be a struct pointer so that obj can be updated
|
||||
// with the content returned by the Server.
|
||||
|
|
|
@ -513,8 +513,15 @@ type MatchingLabels map[string]string
|
|||
// ApplyToList applies this configuration to the given list options.
|
||||
func (m MatchingLabels) ApplyToList(opts *ListOptions) {
|
||||
// TODO(directxman12): can we avoid reserializing this over and over?
|
||||
sel := labels.SelectorFromValidatedSet(map[string]string(m))
|
||||
opts.LabelSelector = sel
|
||||
if opts.LabelSelector == nil {
|
||||
opts.LabelSelector = labels.NewSelector()
|
||||
}
|
||||
// If there's already a selector, we need to AND the two together.
|
||||
noValidSel := labels.SelectorFromValidatedSet(map[string]string(m))
|
||||
reqs, _ := noValidSel.Requirements()
|
||||
for _, req := range reqs {
|
||||
opts.LabelSelector = opts.LabelSelector.Add(req)
|
||||
}
|
||||
}
|
||||
|
||||
// ApplyToDeleteAllOf applies this configuration to the given an List options.
|
||||
|
@ -528,14 +535,17 @@ type HasLabels []string
|
|||
|
||||
// ApplyToList applies this configuration to the given list options.
|
||||
func (m HasLabels) ApplyToList(opts *ListOptions) {
|
||||
sel := labels.NewSelector()
|
||||
if opts.LabelSelector == nil {
|
||||
opts.LabelSelector = labels.NewSelector()
|
||||
}
|
||||
// TODO: ignore invalid labels will result in an empty selector.
|
||||
// This is inconsistent to the that of MatchingLabels.
|
||||
for _, label := range m {
|
||||
r, err := labels.NewRequirement(label, selection.Exists, nil)
|
||||
if err == nil {
|
||||
sel = sel.Add(*r)
|
||||
opts.LabelSelector = opts.LabelSelector.Add(*r)
|
||||
}
|
||||
}
|
||||
opts.LabelSelector = sel
|
||||
}
|
||||
|
||||
// ApplyToDeleteAllOf applies this configuration to the given an List options.
|
||||
|
|
|
@ -224,11 +224,11 @@ func (uc *unstructuredClient) List(ctx context.Context, obj ObjectList, opts ...
|
|||
|
||||
func (uc *unstructuredClient) GetSubResource(ctx context.Context, obj, subResourceObj Object, subResource string, opts ...SubResourceGetOption) error {
|
||||
if _, ok := obj.(runtime.Unstructured); !ok {
|
||||
return fmt.Errorf("unstructured client did not understand object: %T", subResource)
|
||||
return fmt.Errorf("unstructured client did not understand object: %T", obj)
|
||||
}
|
||||
|
||||
if _, ok := subResourceObj.(runtime.Unstructured); !ok {
|
||||
return fmt.Errorf("unstructured client did not understand object: %T", obj)
|
||||
return fmt.Errorf("unstructured client did not understand object: %T", subResourceObj)
|
||||
}
|
||||
|
||||
if subResourceObj.GetName() == "" {
|
||||
|
@ -255,11 +255,11 @@ func (uc *unstructuredClient) GetSubResource(ctx context.Context, obj, subResour
|
|||
|
||||
func (uc *unstructuredClient) CreateSubResource(ctx context.Context, obj, subResourceObj Object, subResource string, opts ...SubResourceCreateOption) error {
|
||||
if _, ok := obj.(runtime.Unstructured); !ok {
|
||||
return fmt.Errorf("unstructured client did not understand object: %T", subResourceObj)
|
||||
return fmt.Errorf("unstructured client did not understand object: %T", obj)
|
||||
}
|
||||
|
||||
if _, ok := subResourceObj.(runtime.Unstructured); !ok {
|
||||
return fmt.Errorf("unstructured client did not understand object: %T", obj)
|
||||
return fmt.Errorf("unstructured client did not understand object: %T", subResourceObj)
|
||||
}
|
||||
|
||||
if subResourceObj.GetName() == "" {
|
||||
|
|
|
@ -28,12 +28,11 @@ import (
|
|||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/internal/log"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/cache"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/internal/log"
|
||||
intrec "sigs.k8s.io/controller-runtime/pkg/internal/recorder"
|
||||
)
|
||||
|
||||
|
@ -95,18 +94,10 @@ type Options struct {
|
|||
// value only if you know what you are doing. Defaults to 10 hours if unset.
|
||||
// there will a 10 percent jitter between the SyncPeriod of all controllers
|
||||
// so that all controllers will not send list requests simultaneously.
|
||||
//
|
||||
// Deprecated: Use Cache.SyncPeriod instead.
|
||||
SyncPeriod *time.Duration
|
||||
|
||||
// Namespace if specified restricts the manager's cache to watch objects in
|
||||
// the desired namespace Defaults to all namespaces
|
||||
//
|
||||
// Note: If a namespace is specified, controllers can still Watch for a
|
||||
// cluster-scoped resource (e.g Node). For namespaced resources the cache
|
||||
// will only hold objects from the desired namespace.
|
||||
//
|
||||
// Deprecated: Use Cache.Namespaces instead.
|
||||
Namespace string
|
||||
|
||||
// HTTPClient is the http client that will be used to create the default
|
||||
// Cache and Client. If not set the rest.HTTPClientFor function will be used
|
||||
// to create the http client.
|
||||
|
@ -141,18 +132,6 @@ type Options struct {
|
|||
// Only use a custom NewClient if you know what you are doing.
|
||||
NewClient client.NewClientFunc
|
||||
|
||||
// ClientDisableCacheFor tells the client that, if any cache is used, to bypass it
|
||||
// for the given objects.
|
||||
//
|
||||
// Deprecated: Use Client.Cache.DisableFor instead.
|
||||
ClientDisableCacheFor []client.Object
|
||||
|
||||
// DryRunClient specifies whether the client should be configured to enforce
|
||||
// dryRun mode.
|
||||
//
|
||||
// Deprecated: Use Client.DryRun instead.
|
||||
DryRunClient bool
|
||||
|
||||
// EventBroadcaster records Events emitted by the manager and sends them to the Kubernetes API
|
||||
// Use this to customize the event correlator and spam filter
|
||||
//
|
||||
|
@ -179,6 +158,13 @@ func New(config *rest.Config, opts ...Option) (Cluster, error) {
|
|||
return nil, errors.New("must specify Config")
|
||||
}
|
||||
|
||||
originalConfig := config
|
||||
|
||||
config = rest.CopyConfig(config)
|
||||
if config.UserAgent == "" {
|
||||
config.UserAgent = rest.DefaultKubernetesUserAgent()
|
||||
}
|
||||
|
||||
options := Options{}
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
|
@ -211,9 +197,6 @@ func New(config *rest.Config, opts ...Option) (Cluster, error) {
|
|||
if cacheOpts.SyncPeriod == nil {
|
||||
cacheOpts.SyncPeriod = options.SyncPeriod
|
||||
}
|
||||
if len(cacheOpts.Namespaces) == 0 && options.Namespace != "" {
|
||||
cacheOpts.Namespaces = []string{options.Namespace}
|
||||
}
|
||||
}
|
||||
cache, err := options.NewCache(config, cacheOpts)
|
||||
if err != nil {
|
||||
|
@ -240,16 +223,6 @@ func New(config *rest.Config, opts ...Option) (Cluster, error) {
|
|||
if clientOpts.Cache.Reader == nil {
|
||||
clientOpts.Cache.Reader = cache
|
||||
}
|
||||
|
||||
// For backward compatibility, the ClientDisableCacheFor option should
|
||||
// be appended to the DisableFor option in the client.
|
||||
clientOpts.Cache.DisableFor = append(clientOpts.Cache.DisableFor, options.ClientDisableCacheFor...)
|
||||
|
||||
if clientOpts.DryRun == nil && options.DryRunClient {
|
||||
// For backward compatibility, the DryRunClient (if set) option should override
|
||||
// the DryRun option in the client (if unset).
|
||||
clientOpts.DryRun = pointer.Bool(true)
|
||||
}
|
||||
}
|
||||
clientWriter, err := options.NewClient(config, clientOpts)
|
||||
if err != nil {
|
||||
|
@ -275,7 +248,7 @@ func New(config *rest.Config, opts ...Option) (Cluster, error) {
|
|||
}
|
||||
|
||||
return &cluster{
|
||||
config: config,
|
||||
config: originalConfig,
|
||||
httpClient: options.HTTPClient,
|
||||
scheme: options.Scheme,
|
||||
cache: cache,
|
||||
|
|
1
vendor/sigs.k8s.io/controller-runtime/pkg/config/v1alpha1/zz_generated.deepcopy.go
generated
vendored
1
vendor/sigs.k8s.io/controller-runtime/pkg/config/v1alpha1/zz_generated.deepcopy.go
generated
vendored
|
@ -1,5 +1,4 @@
|
|||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
|
|
|
@ -159,7 +159,9 @@ func NewUnmanaged(name string, mgr manager.Manager, options Options) (Controller
|
|||
return &controller.Controller{
|
||||
Do: options.Reconciler,
|
||||
MakeQueue: func() workqueue.RateLimitingInterface {
|
||||
return workqueue.NewNamedRateLimitingQueue(options.RateLimiter, name)
|
||||
return workqueue.NewRateLimitingQueueWithConfig(options.RateLimiter, workqueue.RateLimitingQueueConfig{
|
||||
Name: name,
|
||||
})
|
||||
},
|
||||
MaxConcurrentReconciles: options.MaxConcurrentReconciles,
|
||||
CacheSyncTimeout: options.CacheSyncTimeout,
|
||||
|
|
22
vendor/sigs.k8s.io/controller-runtime/pkg/controller/controllerutil/controllerutil.go
generated
vendored
22
vendor/sigs.k8s.io/controller-runtime/pkg/controller/controllerutil/controllerutil.go
generated
vendored
|
@ -28,6 +28,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
|
||||
)
|
||||
|
@ -365,15 +366,18 @@ func AddFinalizer(o client.Object, finalizer string) (finalizersUpdated bool) {
|
|||
// It returns an indication of whether it updated the object's list of finalizers.
|
||||
func RemoveFinalizer(o client.Object, finalizer string) (finalizersUpdated bool) {
|
||||
f := o.GetFinalizers()
|
||||
for i := 0; i < len(f); i++ {
|
||||
length := len(f)
|
||||
|
||||
index := 0
|
||||
for i := 0; i < length; i++ {
|
||||
if f[i] == finalizer {
|
||||
f = append(f[:i], f[i+1:]...)
|
||||
i--
|
||||
finalizersUpdated = true
|
||||
continue
|
||||
}
|
||||
f[index] = f[i]
|
||||
index++
|
||||
}
|
||||
o.SetFinalizers(f)
|
||||
return
|
||||
o.SetFinalizers(f[:index])
|
||||
return length != index
|
||||
}
|
||||
|
||||
// ContainsFinalizer checks an Object that the provided finalizer is present.
|
||||
|
@ -386,9 +390,3 @@ func ContainsFinalizer(o client.Object, finalizer string) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Object allows functions to work indistinctly with any resource that
|
||||
// implements both Object interfaces.
|
||||
//
|
||||
// Deprecated: Use client.Object instead.
|
||||
type Object = client.Object
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/internal/controller/metrics"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
@ -311,6 +312,7 @@ func (c *Controller) reconcileHandler(ctx context.Context, obj interface{}) {
|
|||
|
||||
// RunInformersAndControllers the syncHandler, passing it the Namespace/Name string of the
|
||||
// resource to be synced.
|
||||
log.V(5).Info("Reconciling")
|
||||
result, err := c.Reconcile(ctx, req)
|
||||
switch {
|
||||
case err != nil:
|
||||
|
@ -321,8 +323,12 @@ func (c *Controller) reconcileHandler(ctx context.Context, obj interface{}) {
|
|||
}
|
||||
ctrlmetrics.ReconcileErrors.WithLabelValues(c.Name).Inc()
|
||||
ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, labelError).Inc()
|
||||
if !result.IsZero() {
|
||||
log.Info("Warning: Reconciler returned both a non-zero result and a non-nil error. The result will always be ignored if the error is non-nil and the non-nil error causes reqeueuing with exponential backoff. For more details, see: https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/reconcile#Reconciler")
|
||||
}
|
||||
log.Error(err, "Reconciler error")
|
||||
case result.RequeueAfter > 0:
|
||||
log.V(5).Info(fmt.Sprintf("Reconcile done, requeueing after %s", result.RequeueAfter))
|
||||
// The result.RequeueAfter request will be lost, if it is returned
|
||||
// along with a non-nil error. But this is intended as
|
||||
// We need to drive to stable reconcile loops before queuing due
|
||||
|
@ -331,9 +337,11 @@ func (c *Controller) reconcileHandler(ctx context.Context, obj interface{}) {
|
|||
c.Queue.AddAfter(req, result.RequeueAfter)
|
||||
ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, labelRequeueAfter).Inc()
|
||||
case result.Requeue:
|
||||
log.V(5).Info("Reconcile done, requeueing")
|
||||
c.Queue.AddRateLimited(req)
|
||||
ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, labelRequeue).Inc()
|
||||
default:
|
||||
log.V(5).Info("Reconcile successful")
|
||||
// Finally, if no error occurs we Forget this item so it does not
|
||||
// get queued again until another change happens.
|
||||
c.Queue.Forget(obj)
|
||||
|
|
|
@ -188,6 +188,9 @@ func (l *delegatingLogSink) WithValues(tags ...interface{}) logr.LogSink {
|
|||
// provided, instead of the temporary initial one, if this method
|
||||
// has not been previously called.
|
||||
func (l *delegatingLogSink) Fulfill(actual logr.LogSink) {
|
||||
if actual == nil {
|
||||
actual = NullLogSink{}
|
||||
}
|
||||
if l.promise != nil {
|
||||
l.promise.Fulfill(actual)
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ limitations under the License.
|
|||
package log
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
@ -56,7 +57,15 @@ func eventuallyFulfillRoot() {
|
|||
}
|
||||
if time.Since(rootLogCreated).Seconds() >= 30 {
|
||||
if logFullfilled.CompareAndSwap(false, true) {
|
||||
fmt.Fprintf(os.Stderr, "[controller-runtime] log.SetLogger(...) was never called, logs will not be displayed:\n%s", debug.Stack())
|
||||
stack := debug.Stack()
|
||||
stackLines := bytes.Count(stack, []byte{'\n'})
|
||||
sep := []byte{'\n', '\t', '>', ' ', ' '}
|
||||
|
||||
fmt.Fprintf(os.Stderr,
|
||||
"[controller-runtime] log.SetLogger(...) was never called; logs will not be displayed.\nDetected at:%s%s", sep,
|
||||
// prefix every line, so it's clear this is a stack trace related to the above message
|
||||
bytes.Replace(stack, []byte{'\n'}, sep, stackLines-1),
|
||||
)
|
||||
SetLogger(logr.New(NullLogSink{}))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
kerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
|
@ -44,7 +43,7 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/healthz"
|
||||
"sigs.k8s.io/controller-runtime/pkg/internal/httpserver"
|
||||
intrec "sigs.k8s.io/controller-runtime/pkg/internal/recorder"
|
||||
"sigs.k8s.io/controller-runtime/pkg/metrics"
|
||||
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
||||
)
|
||||
|
||||
|
@ -57,7 +56,6 @@ const (
|
|||
|
||||
defaultReadinessEndpoint = "/readyz"
|
||||
defaultLivenessEndpoint = "/healthz"
|
||||
defaultMetricsEndpoint = "/metrics"
|
||||
)
|
||||
|
||||
var _ Runnable = &controllerManager{}
|
||||
|
@ -84,11 +82,8 @@ type controllerManager struct {
|
|||
// on shutdown
|
||||
leaderElectionReleaseOnCancel bool
|
||||
|
||||
// metricsListener is used to serve prometheus metrics
|
||||
metricsListener net.Listener
|
||||
|
||||
// metricsExtraHandlers contains extra handlers to register on http server that serves metrics.
|
||||
metricsExtraHandlers map[string]http.Handler
|
||||
// metricsServer is used to serve prometheus metrics
|
||||
metricsServer metricsserver.Server
|
||||
|
||||
// healthProbeListener is used to serve liveness probe
|
||||
healthProbeListener net.Listener
|
||||
|
@ -184,28 +179,6 @@ func (cm *controllerManager) add(r Runnable) error {
|
|||
return cm.runnables.Add(r)
|
||||
}
|
||||
|
||||
// AddMetricsExtraHandler adds extra handler served on path to the http server that serves metrics.
|
||||
func (cm *controllerManager) AddMetricsExtraHandler(path string, handler http.Handler) error {
|
||||
cm.Lock()
|
||||
defer cm.Unlock()
|
||||
|
||||
if cm.started {
|
||||
return fmt.Errorf("unable to add new metrics handler because metrics endpoint has already been created")
|
||||
}
|
||||
|
||||
if path == defaultMetricsEndpoint {
|
||||
return fmt.Errorf("overriding builtin %s endpoint is not allowed", defaultMetricsEndpoint)
|
||||
}
|
||||
|
||||
if _, found := cm.metricsExtraHandlers[path]; found {
|
||||
return fmt.Errorf("can't register extra handler by duplicate path %q on metrics http server", path)
|
||||
}
|
||||
|
||||
cm.metricsExtraHandlers[path] = handler
|
||||
cm.logger.V(2).Info("Registering metrics http server extra handler", "path", path)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddHealthzCheck allows you to add Healthz checker.
|
||||
func (cm *controllerManager) AddHealthzCheck(name string, check healthz.Checker) error {
|
||||
cm.Lock()
|
||||
|
@ -296,31 +269,10 @@ func (cm *controllerManager) GetControllerOptions() config.Controller {
|
|||
return cm.controllerConfig
|
||||
}
|
||||
|
||||
func (cm *controllerManager) addMetricsServer() error {
|
||||
func (cm *controllerManager) addHealthProbeServer() error {
|
||||
mux := http.NewServeMux()
|
||||
srv := httpserver.New(mux)
|
||||
|
||||
handler := promhttp.HandlerFor(metrics.Registry, promhttp.HandlerOpts{
|
||||
ErrorHandling: promhttp.HTTPErrorOnError,
|
||||
})
|
||||
// TODO(JoelSpeed): Use existing Kubernetes machinery for serving metrics
|
||||
mux.Handle(defaultMetricsEndpoint, handler)
|
||||
for path, extraHandler := range cm.metricsExtraHandlers {
|
||||
mux.Handle(path, extraHandler)
|
||||
}
|
||||
|
||||
return cm.add(&server{
|
||||
Kind: "metrics",
|
||||
Log: cm.logger.WithValues("path", defaultMetricsEndpoint),
|
||||
Server: srv,
|
||||
Listener: cm.metricsListener,
|
||||
})
|
||||
}
|
||||
|
||||
func (cm *controllerManager) serveHealthProbes() {
|
||||
mux := http.NewServeMux()
|
||||
server := httpserver.New(mux)
|
||||
|
||||
if cm.readyzHandler != nil {
|
||||
mux.Handle(cm.readinessEndpointName, http.StripPrefix(cm.readinessEndpointName, cm.readyzHandler))
|
||||
// Append '/' suffix to handle subpaths
|
||||
|
@ -332,7 +284,12 @@ func (cm *controllerManager) serveHealthProbes() {
|
|||
mux.Handle(cm.livenessEndpointName+"/", http.StripPrefix(cm.livenessEndpointName, cm.healthzHandler))
|
||||
}
|
||||
|
||||
go cm.httpServe("health probe", cm.logger, server, cm.healthProbeListener)
|
||||
return cm.add(&server{
|
||||
Kind: "health probe",
|
||||
Log: cm.logger,
|
||||
Server: srv,
|
||||
Listener: cm.healthProbeListener,
|
||||
})
|
||||
}
|
||||
|
||||
func (cm *controllerManager) addPprofServer() error {
|
||||
|
@ -353,42 +310,6 @@ func (cm *controllerManager) addPprofServer() error {
|
|||
})
|
||||
}
|
||||
|
||||
func (cm *controllerManager) httpServe(kind string, log logr.Logger, server *http.Server, ln net.Listener) {
|
||||
log = log.WithValues("kind", kind, "addr", ln.Addr())
|
||||
|
||||
go func() {
|
||||
log.Info("Starting server")
|
||||
if err := server.Serve(ln); err != nil {
|
||||
if errors.Is(err, http.ErrServerClosed) {
|
||||
return
|
||||
}
|
||||
if atomic.LoadInt64(cm.stopProcedureEngaged) > 0 {
|
||||
// There might be cases where connections are still open and we try to shutdown
|
||||
// but not having enough time to close the connection causes an error in Serve
|
||||
//
|
||||
// In that case we want to avoid returning an error to the main error channel.
|
||||
log.Error(err, "error on Serve after stop has been engaged")
|
||||
return
|
||||
}
|
||||
cm.errChan <- err
|
||||
}
|
||||
}()
|
||||
|
||||
// Shutdown the server when stop is closed.
|
||||
<-cm.internalProceduresStop
|
||||
if err := server.Shutdown(cm.shutdownCtx); err != nil {
|
||||
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
||||
// Avoid logging context related errors.
|
||||
return
|
||||
}
|
||||
if atomic.LoadInt64(cm.stopProcedureEngaged) > 0 {
|
||||
cm.logger.Error(err, "error on Shutdown after stop has been engaged")
|
||||
return
|
||||
}
|
||||
cm.errChan <- err
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts the manager and waits indefinitely.
|
||||
// There is only two ways to have start return:
|
||||
// An error has occurred during in one of the internal operations,
|
||||
|
@ -441,15 +362,19 @@ func (cm *controllerManager) Start(ctx context.Context) (err error) {
|
|||
// Metrics should be served whether the controller is leader or not.
|
||||
// (If we don't serve metrics for non-leaders, prometheus will still scrape
|
||||
// the pod but will get a connection refused).
|
||||
if cm.metricsListener != nil {
|
||||
if err := cm.addMetricsServer(); err != nil {
|
||||
if cm.metricsServer != nil {
|
||||
// Note: We are adding the metrics server directly to HTTPServers here as matching on the
|
||||
// metricsserver.Server interface in cm.runnables.Add would be very brittle.
|
||||
if err := cm.runnables.HTTPServers.Add(cm.metricsServer, nil); err != nil {
|
||||
return fmt.Errorf("failed to add metrics server: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Serve health probes.
|
||||
if cm.healthProbeListener != nil {
|
||||
cm.serveHealthProbes()
|
||||
if err := cm.addHealthProbeServer(); err != nil {
|
||||
return fmt.Errorf("failed to add health probe server: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Add pprof server
|
||||
|
@ -459,7 +384,17 @@ func (cm *controllerManager) Start(ctx context.Context) (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
// First start any webhook servers, which includes conversion, validation, and defaulting
|
||||
// First start any internal HTTP servers, which includes health probes, metrics and profiling if enabled.
|
||||
//
|
||||
// WARNING: Internal HTTP servers MUST start before any cache is populated, otherwise it would block
|
||||
// conversion webhooks to be ready for serving which make the cache never get ready.
|
||||
if err := cm.runnables.HTTPServers.Start(cm.internalCtx); err != nil {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start HTTP servers: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Start any webhook servers, which includes conversion, validation, and defaulting
|
||||
// webhooks that are registered.
|
||||
//
|
||||
// WARNING: Webhooks MUST start before any cache is populated, otherwise there is a race condition
|
||||
|
@ -591,10 +526,13 @@ func (cm *controllerManager) engageStopProcedure(stopComplete <-chan struct{}) e
|
|||
cm.logger.Info("Stopping and waiting for caches")
|
||||
cm.runnables.Caches.StopAndWait(cm.shutdownCtx)
|
||||
|
||||
// Webhooks should come last, as they might be still serving some requests.
|
||||
// Webhooks and internal HTTP servers should come last, as they might be still serving some requests.
|
||||
cm.logger.Info("Stopping and waiting for webhooks")
|
||||
cm.runnables.Webhooks.StopAndWait(cm.shutdownCtx)
|
||||
|
||||
cm.logger.Info("Stopping and waiting for HTTP servers")
|
||||
cm.runnables.HTTPServers.StopAndWait(cm.shutdownCtx)
|
||||
|
||||
// Proceed to close the manager and overall shutdown context.
|
||||
cm.logger.Info("Wait completed, proceeding to shutdown the manager")
|
||||
shutdownCancel()
|
||||
|
|
|
@ -18,7 +18,7 @@ package manager
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
@ -26,6 +26,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
coordinationv1 "k8s.io/api/coordination/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
@ -33,6 +35,7 @@ import (
|
|||
"k8s.io/client-go/tools/leaderelection/resourcelock"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/utils/pointer"
|
||||
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/cache"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
@ -43,7 +46,6 @@ import (
|
|||
intrec "sigs.k8s.io/controller-runtime/pkg/internal/recorder"
|
||||
"sigs.k8s.io/controller-runtime/pkg/leaderelection"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/metrics"
|
||||
"sigs.k8s.io/controller-runtime/pkg/recorder"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
||||
)
|
||||
|
@ -65,13 +67,6 @@ type Manager interface {
|
|||
// election was configured.
|
||||
Elected() <-chan struct{}
|
||||
|
||||
// AddMetricsExtraHandler adds an extra handler served on path to the http server that serves metrics.
|
||||
// Might be useful to register some diagnostic endpoints e.g. pprof. Note that these endpoints meant to be
|
||||
// sensitive and shouldn't be exposed publicly.
|
||||
// If the simple path -> handler mapping offered here is not enough, a new http server/listener should be added as
|
||||
// Runnable to the manager via Add method.
|
||||
AddMetricsExtraHandler(path string, handler http.Handler) error
|
||||
|
||||
// AddHealthzCheck allows you to add Healthz checker
|
||||
AddHealthzCheck(name string, check healthz.Checker) error
|
||||
|
||||
|
@ -140,35 +135,6 @@ type Options struct {
|
|||
// Only use a custom NewClient if you know what you are doing.
|
||||
NewClient client.NewClientFunc
|
||||
|
||||
// SyncPeriod determines the minimum frequency at which watched resources are
|
||||
// reconciled. A lower period will correct entropy more quickly, but reduce
|
||||
// responsiveness to change if there are many watched resources. Change this
|
||||
// value only if you know what you are doing. Defaults to 10 hours if unset.
|
||||
// there will a 10 percent jitter between the SyncPeriod of all controllers
|
||||
// so that all controllers will not send list requests simultaneously.
|
||||
//
|
||||
// This applies to all controllers.
|
||||
//
|
||||
// A period sync happens for two reasons:
|
||||
// 1. To insure against a bug in the controller that causes an object to not
|
||||
// be requeued, when it otherwise should be requeued.
|
||||
// 2. To insure against an unknown bug in controller-runtime, or its dependencies,
|
||||
// that causes an object to not be requeued, when it otherwise should be
|
||||
// requeued, or to be removed from the queue, when it otherwise should not
|
||||
// be removed.
|
||||
//
|
||||
// If you want
|
||||
// 1. to insure against missed watch events, or
|
||||
// 2. to poll services that cannot be watched,
|
||||
// then we recommend that, instead of changing the default period, the
|
||||
// controller requeue, with a constant duration `t`, whenever the controller
|
||||
// is "done" with an object, and would otherwise not requeue it, i.e., we
|
||||
// recommend the `Reconcile` function return `reconcile.Result{RequeueAfter: t}`,
|
||||
// instead of `reconcile.Result{}`.
|
||||
//
|
||||
// Deprecated: Use Cache.SyncPeriod instead.
|
||||
SyncPeriod *time.Duration
|
||||
|
||||
// Logger is the logger that should be used by this manager.
|
||||
// If none is set, it defaults to log.Log global logger.
|
||||
Logger logr.Logger
|
||||
|
@ -239,27 +205,17 @@ type Options struct {
|
|||
// wait to force acquire leadership. This is measured against time of
|
||||
// last observed ack. Default is 15 seconds.
|
||||
LeaseDuration *time.Duration
|
||||
|
||||
// RenewDeadline is the duration that the acting controlplane will retry
|
||||
// refreshing leadership before giving up. Default is 10 seconds.
|
||||
RenewDeadline *time.Duration
|
||||
|
||||
// RetryPeriod is the duration the LeaderElector clients should wait
|
||||
// between tries of actions. Default is 2 seconds.
|
||||
RetryPeriod *time.Duration
|
||||
|
||||
// Namespace, if specified, restricts the manager's cache to watch objects in
|
||||
// the desired namespace. Defaults to all namespaces.
|
||||
//
|
||||
// Note: If a namespace is specified, controllers can still Watch for a
|
||||
// cluster-scoped resource (e.g Node). For namespaced resources, the cache
|
||||
// will only hold objects from the desired namespace.
|
||||
//
|
||||
// Deprecated: Use Cache.Namespaces instead.
|
||||
Namespace string
|
||||
|
||||
// MetricsBindAddress is the TCP address that the controller should bind to
|
||||
// for serving prometheus metrics.
|
||||
// It can be set to "0" to disable the metrics serving.
|
||||
MetricsBindAddress string
|
||||
// Metrics are the metricsserver.Options that will be used to create the metricsserver.Server.
|
||||
Metrics metricsserver.Options
|
||||
|
||||
// HealthProbeBindAddress is the TCP address that the controller should bind to
|
||||
// for serving health probes
|
||||
|
@ -279,34 +235,9 @@ type Options struct {
|
|||
// before exposing it to public.
|
||||
PprofBindAddress string
|
||||
|
||||
// Port is the port that the webhook server serves at.
|
||||
// It is used to set webhook.Server.Port if WebhookServer is not set.
|
||||
//
|
||||
// Deprecated: Use WebhookServer instead. A WebhookServer can be created via webhook.NewServer.
|
||||
Port int
|
||||
// Host is the hostname that the webhook server binds to.
|
||||
// It is used to set webhook.Server.Host if WebhookServer is not set.
|
||||
//
|
||||
// Deprecated: Use WebhookServer instead. A WebhookServer can be created via webhook.NewServer.
|
||||
Host string
|
||||
|
||||
// CertDir is the directory that contains the server key and certificate.
|
||||
// If not set, webhook server would look up the server key and certificate in
|
||||
// {TempDir}/k8s-webhook-server/serving-certs. The server key and certificate
|
||||
// must be named tls.key and tls.crt, respectively.
|
||||
// It is used to set webhook.Server.CertDir if WebhookServer is not set.
|
||||
//
|
||||
// Deprecated: Use WebhookServer instead. A WebhookServer can be created via webhook.NewServer.
|
||||
CertDir string
|
||||
|
||||
// TLSOpts is used to allow configuring the TLS config used for the webhook server.
|
||||
//
|
||||
// Deprecated: Use WebhookServer instead. A WebhookServer can be created via webhook.NewServer.
|
||||
TLSOpts []func(*tls.Config)
|
||||
|
||||
// WebhookServer is an externally configured webhook.Server. By default,
|
||||
// a Manager will create a default server using Port, Host, and CertDir;
|
||||
// if this is set, the Manager will use this server instead.
|
||||
// a Manager will create a server via webhook.NewServer with default settings.
|
||||
// If this is set, the Manager will use this server instead.
|
||||
WebhookServer webhook.Server
|
||||
|
||||
// BaseContext is the function that provides Context values to Runnables
|
||||
|
@ -314,18 +245,6 @@ type Options struct {
|
|||
// will receive a new Background Context instead.
|
||||
BaseContext BaseContextFunc
|
||||
|
||||
// ClientDisableCacheFor tells the client that, if any cache is used, to bypass it
|
||||
// for the given objects.
|
||||
//
|
||||
// Deprecated: Use Client.Cache.DisableCacheFor instead.
|
||||
ClientDisableCacheFor []client.Object
|
||||
|
||||
// DryRunClient specifies whether the client should be configured to enforce
|
||||
// dryRun mode.
|
||||
//
|
||||
// Deprecated: Use Client.DryRun instead.
|
||||
DryRunClient bool
|
||||
|
||||
// EventBroadcaster records Events emitted by the manager and sends them to the Kubernetes API
|
||||
// Use this to customize the event correlator and spam filter
|
||||
//
|
||||
|
@ -353,7 +272,7 @@ type Options struct {
|
|||
// Dependency injection for testing
|
||||
newRecorderProvider func(config *rest.Config, httpClient *http.Client, scheme *runtime.Scheme, logger logr.Logger, makeBroadcaster intrec.EventBroadcasterProducer) (*intrec.Provider, error)
|
||||
newResourceLock func(config *rest.Config, recorderProvider recorder.Provider, options leaderelection.Options) (resourcelock.Interface, error)
|
||||
newMetricsListener func(addr string) (net.Listener, error)
|
||||
newMetricsServer func(options metricsserver.Options, config *rest.Config, httpClient *http.Client) (metricsserver.Server, error)
|
||||
newHealthProbeListener func(addr string) (net.Listener, error)
|
||||
newPprofListener func(addr string) (net.Listener, error)
|
||||
}
|
||||
|
@ -390,7 +309,13 @@ type LeaderElectionRunnable interface {
|
|||
}
|
||||
|
||||
// New returns a new Manager for creating Controllers.
|
||||
// Note that if ContentType in the given config is not set, "application/vnd.kubernetes.protobuf"
|
||||
// will be used for all built-in resources of Kubernetes, and "application/json" is for other types
|
||||
// including all CRD resources.
|
||||
func New(config *rest.Config, options Options) (Manager, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("must specify Config")
|
||||
}
|
||||
// Set default values for options fields
|
||||
options = setOptionsDefaults(options)
|
||||
|
||||
|
@ -398,20 +323,21 @@ func New(config *rest.Config, options Options) (Manager, error) {
|
|||
clusterOptions.Scheme = options.Scheme
|
||||
clusterOptions.MapperProvider = options.MapperProvider
|
||||
clusterOptions.Logger = options.Logger
|
||||
clusterOptions.SyncPeriod = options.SyncPeriod
|
||||
clusterOptions.NewCache = options.NewCache
|
||||
clusterOptions.NewClient = options.NewClient
|
||||
clusterOptions.Cache = options.Cache
|
||||
clusterOptions.Client = options.Client
|
||||
clusterOptions.Namespace = options.Namespace //nolint:staticcheck
|
||||
clusterOptions.ClientDisableCacheFor = options.ClientDisableCacheFor //nolint:staticcheck
|
||||
clusterOptions.DryRunClient = options.DryRunClient //nolint:staticcheck
|
||||
clusterOptions.EventBroadcaster = options.EventBroadcaster //nolint:staticcheck
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config = rest.CopyConfig(config)
|
||||
if config.UserAgent == "" {
|
||||
config.UserAgent = rest.DefaultKubernetesUserAgent()
|
||||
}
|
||||
|
||||
// Create the recorder provider to inject event recorders for the components.
|
||||
// TODO(directxman12): the log for the event provider should have a context (name, tags, etc) specific
|
||||
// to the particular controller that it's being injected into, rather than a generic one like is here.
|
||||
|
@ -429,7 +355,20 @@ func New(config *rest.Config, options Options) (Manager, error) {
|
|||
leaderRecorderProvider = recorderProvider
|
||||
} else {
|
||||
leaderConfig = rest.CopyConfig(options.LeaderElectionConfig)
|
||||
leaderRecorderProvider, err = options.newRecorderProvider(leaderConfig, cluster.GetHTTPClient(), cluster.GetScheme(), options.Logger.WithName("events"), options.makeBroadcaster)
|
||||
scheme := cluster.GetScheme()
|
||||
err := corev1.AddToScheme(scheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = coordinationv1.AddToScheme(scheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
httpClient, err := rest.HTTPClientFor(options.LeaderElectionConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
leaderRecorderProvider, err = options.newRecorderProvider(leaderConfig, httpClient, scheme, options.Logger.WithName("events"), options.makeBroadcaster)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -450,16 +389,12 @@ func New(config *rest.Config, options Options) (Manager, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Create the metrics listener. This will throw an error if the metrics bind
|
||||
// address is invalid or already in use.
|
||||
metricsListener, err := options.newMetricsListener(options.MetricsBindAddress)
|
||||
// Create the metrics server.
|
||||
metricsServer, err := options.newMetricsServer(options.Metrics, config, cluster.GetHTTPClient())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// By default we have no extra endpoints to expose on metrics http server.
|
||||
metricsExtraHandlers := make(map[string]http.Handler)
|
||||
|
||||
// Create health probes listener. This will throw an error if the bind
|
||||
// address is invalid or already in use.
|
||||
healthProbeListener, err := options.newHealthProbeListener(options.HealthProbeBindAddress)
|
||||
|
@ -476,7 +411,6 @@ func New(config *rest.Config, options Options) (Manager, error) {
|
|||
|
||||
errChan := make(chan error)
|
||||
runnables := newRunnables(options.BaseContext, errChan)
|
||||
|
||||
return &controllerManager{
|
||||
stopProcedureEngaged: pointer.Int64(0),
|
||||
cluster: cluster,
|
||||
|
@ -484,8 +418,7 @@ func New(config *rest.Config, options Options) (Manager, error) {
|
|||
errChan: errChan,
|
||||
recorderProvider: recorderProvider,
|
||||
resourceLock: resourceLock,
|
||||
metricsListener: metricsListener,
|
||||
metricsExtraHandlers: metricsExtraHandlers,
|
||||
metricsServer: metricsServer,
|
||||
controllerConfig: options.Controller,
|
||||
logger: options.Logger,
|
||||
elected: make(chan struct{}),
|
||||
|
@ -523,16 +456,16 @@ func (o Options) AndFrom(loader config.ControllerManagerConfiguration) (Options,
|
|||
|
||||
o = o.setLeaderElectionConfig(newObj)
|
||||
|
||||
if o.SyncPeriod == nil && newObj.SyncPeriod != nil {
|
||||
o.SyncPeriod = &newObj.SyncPeriod.Duration
|
||||
if o.Cache.SyncPeriod == nil && newObj.SyncPeriod != nil {
|
||||
o.Cache.SyncPeriod = &newObj.SyncPeriod.Duration
|
||||
}
|
||||
|
||||
if o.Namespace == "" && newObj.CacheNamespace != "" {
|
||||
o.Namespace = newObj.CacheNamespace
|
||||
if len(o.Cache.DefaultNamespaces) == 0 && newObj.CacheNamespace != "" {
|
||||
o.Cache.DefaultNamespaces = map[string]cache.Config{newObj.CacheNamespace: {}}
|
||||
}
|
||||
|
||||
if o.MetricsBindAddress == "" && newObj.Metrics.BindAddress != "" {
|
||||
o.MetricsBindAddress = newObj.Metrics.BindAddress
|
||||
if o.Metrics.BindAddress == "" && newObj.Metrics.BindAddress != "" {
|
||||
o.Metrics.BindAddress = newObj.Metrics.BindAddress
|
||||
}
|
||||
|
||||
if o.HealthProbeBindAddress == "" && newObj.Health.HealthProbeBindAddress != "" {
|
||||
|
@ -547,20 +480,15 @@ func (o Options) AndFrom(loader config.ControllerManagerConfiguration) (Options,
|
|||
o.LivenessEndpointName = newObj.Health.LivenessEndpointName
|
||||
}
|
||||
|
||||
if o.Port == 0 && newObj.Webhook.Port != nil {
|
||||
o.Port = *newObj.Webhook.Port
|
||||
}
|
||||
if o.Host == "" && newObj.Webhook.Host != "" {
|
||||
o.Host = newObj.Webhook.Host
|
||||
}
|
||||
if o.CertDir == "" && newObj.Webhook.CertDir != "" {
|
||||
o.CertDir = newObj.Webhook.CertDir
|
||||
}
|
||||
if o.WebhookServer == nil {
|
||||
port := 0
|
||||
if newObj.Webhook.Port != nil {
|
||||
port = *newObj.Webhook.Port
|
||||
}
|
||||
o.WebhookServer = webhook.NewServer(webhook.Options{
|
||||
Port: o.Port,
|
||||
Host: o.Host,
|
||||
CertDir: o.CertDir,
|
||||
Port: port,
|
||||
Host: newObj.Webhook.Host,
|
||||
CertDir: newObj.Webhook.CertDir,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -688,8 +616,8 @@ func setOptionsDefaults(options Options) Options {
|
|||
}
|
||||
}
|
||||
|
||||
if options.newMetricsListener == nil {
|
||||
options.newMetricsListener = metrics.NewListener
|
||||
if options.newMetricsServer == nil {
|
||||
options.newMetricsServer = metricsserver.NewServer
|
||||
}
|
||||
leaseDuration, renewDeadline, retryPeriod := defaultLeaseDuration, defaultRenewDeadline, defaultRetryPeriod
|
||||
if options.LeaseDuration == nil {
|
||||
|
@ -734,12 +662,7 @@ func setOptionsDefaults(options Options) Options {
|
|||
}
|
||||
|
||||
if options.WebhookServer == nil {
|
||||
options.WebhookServer = webhook.NewServer(webhook.Options{
|
||||
Host: options.Host,
|
||||
Port: options.Port,
|
||||
CertDir: options.CertDir,
|
||||
TLSOpts: options.TLSOpts,
|
||||
})
|
||||
options.WebhookServer = webhook.NewServer(webhook.Options{})
|
||||
}
|
||||
|
||||
return options
|
||||
|
|
|
@ -28,6 +28,7 @@ type runnableCheck func(ctx context.Context) bool
|
|||
// runnables handles all the runnables for a manager by grouping them accordingly to their
|
||||
// type (webhooks, caches etc.).
|
||||
type runnables struct {
|
||||
HTTPServers *runnableGroup
|
||||
Webhooks *runnableGroup
|
||||
Caches *runnableGroup
|
||||
LeaderElection *runnableGroup
|
||||
|
@ -37,6 +38,7 @@ type runnables struct {
|
|||
// newRunnables creates a new runnables object.
|
||||
func newRunnables(baseContext BaseContextFunc, errChan chan error) *runnables {
|
||||
return &runnables{
|
||||
HTTPServers: newRunnableGroup(baseContext, errChan),
|
||||
Webhooks: newRunnableGroup(baseContext, errChan),
|
||||
Caches: newRunnableGroup(baseContext, errChan),
|
||||
LeaderElection: newRunnableGroup(baseContext, errChan),
|
||||
|
@ -52,6 +54,8 @@ func newRunnables(baseContext BaseContextFunc, errChan chan error) *runnables {
|
|||
// The runnables added after Start are started directly.
|
||||
func (r *runnables) Add(fn Runnable) error {
|
||||
switch runnable := fn.(type) {
|
||||
case *server:
|
||||
return r.HTTPServers.Add(fn, nil)
|
||||
case hasCache:
|
||||
return r.Caches.Add(fn, func(ctx context.Context) bool {
|
||||
return runnable.GetCache().WaitForCacheSync(ctx)
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/internal/log"
|
||||
)
|
||||
|
||||
var log = logf.RuntimeLog.WithName("metrics")
|
||||
|
||||
// DefaultBindAddress sets the default bind address for the metrics listener
|
||||
// The metrics is on by default.
|
||||
var DefaultBindAddress = ":8080"
|
||||
|
||||
// NewListener creates a new TCP listener bound to the given address.
|
||||
func NewListener(addr string) (net.Listener, error) {
|
||||
if addr == "" {
|
||||
// If the metrics bind address is empty, default to ":8080"
|
||||
addr = DefaultBindAddress
|
||||
}
|
||||
|
||||
// Add a case to disable metrics altogether
|
||||
if addr == "0" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
log.Info("Metrics server is starting to listen", "addr", addr)
|
||||
ln, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
er := fmt.Errorf("error listening on %s: %w", addr, err)
|
||||
log.Error(er, "metrics server failed to listen. You may want to disable the metrics server or use another port if it is due to conflicts")
|
||||
return nil, er
|
||||
}
|
||||
return ln, nil
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
Package server provides the metrics server implementation.
|
||||
*/
|
||||
package server
|
||||
|
||||
import (
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/internal/log"
|
||||
)
|
||||
|
||||
var log = logf.RuntimeLog.WithName("metrics")
|
312
vendor/sigs.k8s.io/controller-runtime/pkg/metrics/server/server.go
generated
vendored
Normal file
312
vendor/sigs.k8s.io/controller-runtime/pkg/metrics/server/server.go
generated
vendored
Normal file
|
@ -0,0 +1,312 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"k8s.io/client-go/rest"
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/certwatcher"
|
||||
"sigs.k8s.io/controller-runtime/pkg/internal/httpserver"
|
||||
"sigs.k8s.io/controller-runtime/pkg/metrics"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultMetricsEndpoint = "/metrics"
|
||||
)
|
||||
|
||||
// DefaultBindAddress is the default bind address for the metrics server.
|
||||
var DefaultBindAddress = ":8080"
|
||||
|
||||
// Server is a server that serves metrics.
|
||||
type Server interface {
|
||||
// NeedLeaderElection implements the LeaderElectionRunnable interface, which indicates
|
||||
// the metrics server doesn't need leader election.
|
||||
NeedLeaderElection() bool
|
||||
|
||||
// Start runs the server.
|
||||
// It will install the metrics related resources depending on the server configuration.
|
||||
Start(ctx context.Context) error
|
||||
}
|
||||
|
||||
// Options are all available options for the metrics.Server
|
||||
type Options struct {
|
||||
// SecureServing enables serving metrics via https.
|
||||
// Per default metrics will be served via http.
|
||||
SecureServing bool
|
||||
|
||||
// BindAddress is the bind address for the metrics server.
|
||||
// It will be defaulted to ":8080" if unspecified.
|
||||
// Set this to "0" to disable the metrics server.
|
||||
BindAddress string
|
||||
|
||||
// ExtraHandlers contains a map of handlers (by path) which will be added to the metrics server.
|
||||
// This might be useful to register diagnostic endpoints e.g. pprof.
|
||||
// Note that pprof endpoints are meant to be sensitive and shouldn't be exposed publicly.
|
||||
// If the simple path -> handler mapping offered here is not enough, a new http
|
||||
// server/listener should be added as Runnable to the manager via the Add method.
|
||||
ExtraHandlers map[string]http.Handler
|
||||
|
||||
// FilterProvider provides a filter which is a func that is added around
|
||||
// the metrics and the extra handlers on the metrics server.
|
||||
// This can be e.g. used to enforce authentication and authorization on the handlers
|
||||
// endpoint by setting this field to filters.WithAuthenticationAndAuthorization.
|
||||
FilterProvider func(c *rest.Config, httpClient *http.Client) (Filter, error)
|
||||
|
||||
// CertDir is the directory that contains the server key and certificate. Defaults to
|
||||
// <temp-dir>/k8s-metrics-server/serving-certs.
|
||||
//
|
||||
// Note: This option is only used when TLSOpts does not set GetCertificate.
|
||||
// Note: If certificate or key doesn't exist a self-signed certificate will be used.
|
||||
CertDir string
|
||||
|
||||
// CertName is the server certificate name. Defaults to tls.crt.
|
||||
//
|
||||
// Note: This option is only used when TLSOpts does not set GetCertificate.
|
||||
// Note: If certificate or key doesn't exist a self-signed certificate will be used.
|
||||
CertName string
|
||||
|
||||
// KeyName is the server key name. Defaults to tls.key.
|
||||
//
|
||||
// Note: This option is only used when TLSOpts does not set GetCertificate.
|
||||
// Note: If certificate or key doesn't exist a self-signed certificate will be used.
|
||||
KeyName string
|
||||
|
||||
// TLSOpts is used to allow configuring the TLS config used for the server.
|
||||
// This also allows providing a certificate via GetCertificate.
|
||||
TLSOpts []func(*tls.Config)
|
||||
}
|
||||
|
||||
// Filter is a func that is added around metrics and extra handlers on the metrics server.
|
||||
type Filter func(log logr.Logger, handler http.Handler) (http.Handler, error)
|
||||
|
||||
// NewServer constructs a new metrics.Server from the provided options.
|
||||
func NewServer(o Options, config *rest.Config, httpClient *http.Client) (Server, error) {
|
||||
o.setDefaults()
|
||||
|
||||
// Skip server creation if metrics are disabled.
|
||||
if o.BindAddress == "0" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Validate that ExtraHandlers is not overwriting the default /metrics endpoint.
|
||||
if o.ExtraHandlers != nil {
|
||||
if _, ok := o.ExtraHandlers[defaultMetricsEndpoint]; ok {
|
||||
return nil, fmt.Errorf("overriding builtin %s endpoint is not allowed", defaultMetricsEndpoint)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the metrics filter if a FilterProvider is set.
|
||||
var metricsFilter Filter
|
||||
if o.FilterProvider != nil {
|
||||
var err error
|
||||
metricsFilter, err = o.FilterProvider(config, httpClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("filter provider failed to create filter for the metrics server: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &defaultServer{
|
||||
metricsFilter: metricsFilter,
|
||||
options: o,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// defaultServer is the default implementation used for Server.
|
||||
type defaultServer struct {
|
||||
options Options
|
||||
|
||||
// metricsFilter is a filter which is added around
|
||||
// the metrics and the extra handlers on the metrics server.
|
||||
metricsFilter Filter
|
||||
|
||||
// mu protects access to the bindAddr field.
|
||||
mu sync.RWMutex
|
||||
|
||||
// bindAddr is used to store the bindAddr after the listener has been created.
|
||||
// This is used during testing to figure out the port that has been chosen randomly.
|
||||
bindAddr string
|
||||
}
|
||||
|
||||
// setDefaults does defaulting for the Server.
|
||||
func (o *Options) setDefaults() {
|
||||
if o.BindAddress == "" {
|
||||
o.BindAddress = DefaultBindAddress
|
||||
}
|
||||
|
||||
if len(o.CertDir) == 0 {
|
||||
o.CertDir = filepath.Join(os.TempDir(), "k8s-metrics-server", "serving-certs")
|
||||
}
|
||||
|
||||
if len(o.CertName) == 0 {
|
||||
o.CertName = "tls.crt"
|
||||
}
|
||||
|
||||
if len(o.KeyName) == 0 {
|
||||
o.KeyName = "tls.key"
|
||||
}
|
||||
}
|
||||
|
||||
// NeedLeaderElection implements the LeaderElectionRunnable interface, which indicates
|
||||
// the metrics server doesn't need leader election.
|
||||
func (*defaultServer) NeedLeaderElection() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Start runs the server.
|
||||
// It will install the metrics related resources depend on the server configuration.
|
||||
func (s *defaultServer) Start(ctx context.Context) error {
|
||||
log.Info("Starting metrics server")
|
||||
|
||||
listener, err := s.createListener(ctx, log)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start metrics server: failed to create listener: %w", err)
|
||||
}
|
||||
// Storing bindAddr here so we can retrieve it during testing via GetBindAddr.
|
||||
s.mu.Lock()
|
||||
s.bindAddr = listener.Addr().String()
|
||||
s.mu.Unlock()
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
handler := promhttp.HandlerFor(metrics.Registry, promhttp.HandlerOpts{
|
||||
ErrorHandling: promhttp.HTTPErrorOnError,
|
||||
})
|
||||
if s.metricsFilter != nil {
|
||||
log := log.WithValues("path", defaultMetricsEndpoint)
|
||||
var err error
|
||||
handler, err = s.metricsFilter(log, handler)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start metrics server: failed to add metrics filter: %w", err)
|
||||
}
|
||||
}
|
||||
// TODO(JoelSpeed): Use existing Kubernetes machinery for serving metrics
|
||||
mux.Handle(defaultMetricsEndpoint, handler)
|
||||
|
||||
for path, extraHandler := range s.options.ExtraHandlers {
|
||||
if s.metricsFilter != nil {
|
||||
log := log.WithValues("path", path)
|
||||
var err error
|
||||
extraHandler, err = s.metricsFilter(log, extraHandler)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start metrics server: failed to add metrics filter to extra handler for path %s: %w", path, err)
|
||||
}
|
||||
}
|
||||
mux.Handle(path, extraHandler)
|
||||
}
|
||||
|
||||
log.Info("Serving metrics server", "bindAddress", s.options.BindAddress, "secure", s.options.SecureServing)
|
||||
|
||||
srv := httpserver.New(mux)
|
||||
|
||||
idleConnsClosed := make(chan struct{})
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
log.Info("Shutting down metrics server with timeout of 1 minute")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
|
||||
defer cancel()
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
// Error from closing listeners, or context timeout
|
||||
log.Error(err, "error shutting down the HTTP server")
|
||||
}
|
||||
close(idleConnsClosed)
|
||||
}()
|
||||
|
||||
if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||
return err
|
||||
}
|
||||
|
||||
<-idleConnsClosed
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *defaultServer) createListener(ctx context.Context, log logr.Logger) (net.Listener, error) {
|
||||
if !s.options.SecureServing {
|
||||
return net.Listen("tcp", s.options.BindAddress)
|
||||
}
|
||||
|
||||
cfg := &tls.Config{ //nolint:gosec
|
||||
NextProtos: []string{"h2"},
|
||||
}
|
||||
// fallback TLS config ready, will now mutate if passer wants full control over it
|
||||
for _, op := range s.options.TLSOpts {
|
||||
op(cfg)
|
||||
}
|
||||
|
||||
if cfg.GetCertificate == nil {
|
||||
certPath := filepath.Join(s.options.CertDir, s.options.CertName)
|
||||
keyPath := filepath.Join(s.options.CertDir, s.options.KeyName)
|
||||
|
||||
_, certErr := os.Stat(certPath)
|
||||
certExists := !os.IsNotExist(certErr)
|
||||
_, keyErr := os.Stat(keyPath)
|
||||
keyExists := !os.IsNotExist(keyErr)
|
||||
if certExists && keyExists {
|
||||
// Create the certificate watcher and
|
||||
// set the config's GetCertificate on the TLSConfig
|
||||
certWatcher, err := certwatcher.New(certPath, keyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.GetCertificate = certWatcher.GetCertificate
|
||||
|
||||
go func() {
|
||||
if err := certWatcher.Start(ctx); err != nil {
|
||||
log.Error(err, "certificate watcher error")
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// If cfg.GetCertificate is still nil, i.e. we didn't configure a cert watcher, fallback to a self-signed certificate.
|
||||
if cfg.GetCertificate == nil {
|
||||
// Note: Using self-signed certificates here should be good enough. It's just important that we
|
||||
// encrypt the communication. For example kube-controller-manager also uses a self-signed certificate
|
||||
// for the metrics endpoint per default.
|
||||
cert, key, err := certutil.GenerateSelfSignedCertKeyWithFixtures("localhost", []net.IP{{127, 0, 0, 1}}, nil, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate self-signed certificate for metrics server: %w", err)
|
||||
}
|
||||
|
||||
keyPair, err := tls.X509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create self-signed key pair for metrics server: %w", err)
|
||||
}
|
||||
cfg.Certificates = []tls.Certificate{keyPair}
|
||||
}
|
||||
|
||||
return tls.Listen("tcp", s.options.BindAddress, cfg)
|
||||
}
|
||||
|
||||
func (s *defaultServer) GetBindAddr() string {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.bindAddr
|
||||
}
|
|
@ -89,8 +89,16 @@ instead the reconcile function observes this when reading the cluster state and
|
|||
*/
|
||||
type Reconciler interface {
|
||||
// Reconcile performs a full reconciliation for the object referred to by the Request.
|
||||
// The Controller will requeue the Request to be processed again if an error is non-nil or
|
||||
// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
|
||||
//
|
||||
// If the returned error is non-nil, the Result is ignored and the request will be
|
||||
// requeued using exponential backoff. The only exception is if the error is a
|
||||
// TerminalError in which case no requeuing happens.
|
||||
//
|
||||
// If the error is nil and the returned Result has a non-zero result.RequeueAfter, the request
|
||||
// will be requeued after the specified duration.
|
||||
//
|
||||
// If the error is nil and result.RequeueAfter is zero and result.Reque is true, the request
|
||||
// will be requeued using exponential backoff.
|
||||
Reconcile(context.Context, Request) (Result, error)
|
||||
}
|
||||
|
||||
|
@ -112,11 +120,15 @@ type terminalError struct {
|
|||
err error
|
||||
}
|
||||
|
||||
// This function will return nil if te.err is nil.
|
||||
func (te *terminalError) Unwrap() error {
|
||||
return te.err
|
||||
}
|
||||
|
||||
func (te *terminalError) Error() string {
|
||||
if te.err == nil {
|
||||
return "nil terminal error"
|
||||
}
|
||||
return "terminal error: " + te.err.Error()
|
||||
}
|
||||
|
||||
|
|
|
@ -71,6 +71,7 @@ func (d *Decoder) DecodeRaw(rawObj runtime.RawExtension, into runtime.Object) er
|
|||
return err
|
||||
}
|
||||
unstructuredInto.SetUnstructuredContent(object)
|
||||
return nil
|
||||
}
|
||||
|
||||
deserializer := d.codecs.UniversalDeserializer()
|
||||
|
|
|
@ -93,7 +93,7 @@ func (wh *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
wh.writeResponse(w, reviewResponse)
|
||||
return
|
||||
}
|
||||
wh.getLogger(&req).V(4).Info("received request")
|
||||
wh.getLogger(&req).V(5).Info("received request")
|
||||
|
||||
reviewResponse = wh.Handle(ctx, req)
|
||||
wh.writeResponseTyped(w, reviewResponse, actualAdmRevGVK)
|
||||
|
@ -136,11 +136,11 @@ func (wh *Webhook) writeAdmissionResponse(w io.Writer, ar v1.AdmissionReview) {
|
|||
}
|
||||
} else {
|
||||
res := ar.Response
|
||||
if log := wh.getLogger(nil); log.V(4).Enabled() {
|
||||
if log := wh.getLogger(nil); log.V(5).Enabled() {
|
||||
if res.Result != nil {
|
||||
log = log.WithValues("code", res.Result.Code, "reason", res.Result.Reason, "message", res.Result.Message)
|
||||
}
|
||||
log.V(4).Info("wrote response", "requestID", res.UID, "allowed", res.Allowed)
|
||||
log.V(5).Info("wrote response", "requestID", res.UID, "allowed", res.Allowed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,37 +77,33 @@ type Options struct {
|
|||
// It will be defaulted to 9443 if unspecified.
|
||||
Port int
|
||||
|
||||
// CertDir is the directory that contains the server key and certificate. The
|
||||
// server key and certificate.
|
||||
// CertDir is the directory that contains the server key and certificate. Defaults to
|
||||
// <temp-dir>/k8s-webhook-server/serving-certs.
|
||||
CertDir string
|
||||
|
||||
// CertName is the server certificate name. Defaults to tls.crt.
|
||||
//
|
||||
// Note: This option should only be set when TLSOpts does not override GetCertificate.
|
||||
// Note: This option is only used when TLSOpts does not set GetCertificate.
|
||||
CertName string
|
||||
|
||||
// KeyName is the server key name. Defaults to tls.key.
|
||||
//
|
||||
// Note: This option should only be set when TLSOpts does not override GetCertificate.
|
||||
// Note: This option is only used when TLSOpts does not set GetCertificate.
|
||||
KeyName string
|
||||
|
||||
// ClientCAName is the CA certificate name which server used to verify remote(client)'s certificate.
|
||||
// Defaults to "", which means server does not verify client's certificate.
|
||||
ClientCAName string
|
||||
|
||||
// TLSVersion is the minimum version of TLS supported. Accepts
|
||||
// "", "1.0", "1.1", "1.2" and "1.3" only ("" is equivalent to "1.0" for backwards compatibility)
|
||||
// Deprecated: Use TLSOpts instead.
|
||||
TLSMinVersion string
|
||||
|
||||
// TLSOpts is used to allow configuring the TLS config used for the server
|
||||
// TLSOpts is used to allow configuring the TLS config used for the server.
|
||||
// This also allows providing a certificate via GetCertificate.
|
||||
TLSOpts []func(*tls.Config)
|
||||
|
||||
// WebhookMux is the multiplexer that handles different webhooks.
|
||||
WebhookMux *http.ServeMux
|
||||
}
|
||||
|
||||
// NewServer constructs a new Server from the provided options.
|
||||
// NewServer constructs a new webhook.Server from the provided options.
|
||||
func NewServer(o Options) Server {
|
||||
return &DefaultServer{
|
||||
Options: o,
|
||||
|
@ -187,42 +183,15 @@ func (s *DefaultServer) Register(path string, hook http.Handler) {
|
|||
regLog.Info("Registering webhook")
|
||||
}
|
||||
|
||||
// tlsVersion converts from human-readable TLS version (for example "1.1")
|
||||
// to the values accepted by tls.Config (for example 0x301).
|
||||
func tlsVersion(version string) (uint16, error) {
|
||||
switch version {
|
||||
// default is previous behaviour
|
||||
case "":
|
||||
return tls.VersionTLS10, nil
|
||||
case "1.0":
|
||||
return tls.VersionTLS10, nil
|
||||
case "1.1":
|
||||
return tls.VersionTLS11, nil
|
||||
case "1.2":
|
||||
return tls.VersionTLS12, nil
|
||||
case "1.3":
|
||||
return tls.VersionTLS13, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("invalid TLSMinVersion %v: expects 1.0, 1.1, 1.2, 1.3 or empty", version)
|
||||
}
|
||||
}
|
||||
|
||||
// Start runs the server.
|
||||
// It will install the webhook related resources depend on the server configuration.
|
||||
func (s *DefaultServer) Start(ctx context.Context) error {
|
||||
s.defaultingOnce.Do(s.setDefaults)
|
||||
|
||||
baseHookLog := log.WithName("webhooks")
|
||||
baseHookLog.Info("Starting webhook server")
|
||||
|
||||
tlsMinVersion, err := tlsVersion(s.Options.TLSMinVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("Starting webhook server")
|
||||
|
||||
cfg := &tls.Config{ //nolint:gosec
|
||||
NextProtos: []string{"h2"},
|
||||
MinVersion: tlsMinVersion,
|
||||
}
|
||||
// fallback TLS config ready, will now mutate if passer wants full control over it
|
||||
for _, op := range s.Options.TLSOpts {
|
||||
|
|
Loading…
Reference in New Issue