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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions cmd/sharder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,9 @@ import (
"sigs.k8s.io/controller-runtime/pkg/manager/signals"

"github.com/timebertt/kubernetes-controller-sharding/cmd/sharder/app"
"github.com/timebertt/kubernetes-controller-sharding/pkg/utils/client"
)

func main() {
client.DeduplicateWarnings()

if err := app.NewCommand().ExecuteContext(signals.SetupSignalHandler()); err != nil {
fmt.Println(err)
os.Exit(1)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ toolchain go1.24.0
require (
github.com/cespare/xxhash/v2 v2.3.0
github.com/go-logr/logr v1.4.2
github.com/google/go-cmp v0.6.0
github.com/google/uuid v1.6.0
github.com/hashicorp/go-multierror v1.1.1
github.com/onsi/ginkgo/v2 v2.22.2
Expand Down Expand Up @@ -54,6 +53,7 @@ require (
github.com/google/btree v1.1.3 // indirect
github.com/google/cel-go v0.22.0 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/sharder/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ func (r *Reconciler) resyncResource(

list := &metav1.PartialObjectMetadataList{}
list.SetGroupVersionKind(gvks[0])
err = pager.New(r.Reader).EachListItem(ctx, list,
err = pager.New(r.Reader).EachListItemWithAlloc(ctx, list,
func(obj client.Object) error {
if !namespaces.Has(obj.GetNamespace()) {
return nil
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2023 Tim Ebert.
Copyright 2025 Tim Ebert.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -14,20 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

package client
package client_test

import (
"os"
"testing"

"k8s.io/client-go/rest"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

// DeduplicateWarnings configures a client-go warning handler that deduplicates API warnings in order to not spam logs.
func DeduplicateWarnings() {
rest.SetDefaultWarningHandler(
rest.NewWarningWriter(os.Stderr, rest.WarningWriterOptions{
// only print a given warning the first time we receive it
Deduplicate: true,
}),
)
func TestClient(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Client Utils Suite")
}
41 changes: 41 additions & 0 deletions pkg/utils/client/options_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
Copyright 2025 Tim Ebert.

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 client_test

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"sigs.k8s.io/controller-runtime/pkg/client"

. "github.com/timebertt/kubernetes-controller-sharding/pkg/utils/client"
)

var _ = Describe("ResourceVersion", func() {
It("should set the resourceVersion on GetOptions", func() {
opts := &client.GetOptions{}
opts.ApplyOptions([]client.GetOption{ResourceVersion("1")})

Expect(opts.Raw.ResourceVersion).To(Equal("1"))
})

It("should set the resourceVersion on ListOptions", func() {
opts := &client.ListOptions{}
opts.ApplyOptions([]client.ListOption{ResourceVersion("1")})

Expect(opts.Raw.ResourceVersion).To(Equal("1"))
})
})
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2024 Tim Ebert.
Copyright 2025 Tim Ebert.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -14,12 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

// Package komega is a modified version of sigs.k8s.io/controller-runtime/pkg/envtest/komega.
// Instead of requiring users to set a global context, the returned functions accept a context to integrate nicely with
// ginkgo's/gomega's timeout/interrupt handling and polling.
// For this, users must pass a spec-specific context, e.g.:
//
// It("...", func(ctx SpecContext) {
// Eventually(ctx, Get(...)).Should(Succeed())
// })
package komega
package errors_test

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestErrors(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Errors Utils Suite")
}
2 changes: 1 addition & 1 deletion pkg/utils/errors/multi.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
)

// FormatErrors is like multierror.ListFormatFunc without the noisy newlines and tabs.
// It also simplies the format for a single error.
// It also simplifies the format for a single error.
func FormatErrors(es []error) string {
if len(es) == 1 {
return es[0].Error()
Expand Down
40 changes: 40 additions & 0 deletions pkg/utils/errors/multi_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
Copyright 2025 Tim Ebert.

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 errors_test

import (
"fmt"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

. "github.com/timebertt/kubernetes-controller-sharding/pkg/utils/errors"
)

var _ = Describe("FormatErrors", func() {
It("should return the single error", func() {
Expect(FormatErrors([]error{fmt.Errorf("foo")})).To(Equal("foo"))
})

It("should return the error count and comma separated error list", func() {
Expect(
FormatErrors([]error{fmt.Errorf("foo"), fmt.Errorf("bar")}),
).To(
Equal("2 errors occurred: foo, bar"),
)
})
})
43 changes: 43 additions & 0 deletions pkg/utils/healthz/cache_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
Copyright 2025 Tim Ebert.

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 healthz_test

import (
"context"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

. "github.com/timebertt/kubernetes-controller-sharding/pkg/utils/healthz"
)

var _ = Describe("CacheSync", func() {
It("should succeed if all informers sync", func() {
checker := CacheSync(fakeSyncWaiter(true))
Expect(checker(nil)).NotTo(HaveOccurred())
})
It("should fail if informers don't sync", func() {
checker := CacheSync(fakeSyncWaiter(false))
Expect(checker(nil)).To(MatchError(ContainSubstring("not synced")))
})
})

type fakeSyncWaiter bool

func (f fakeSyncWaiter) WaitForCacheSync(_ context.Context) bool {
return bool(f)
}
29 changes: 29 additions & 0 deletions pkg/utils/healthz/healthz_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
Copyright 2025 Tim Ebert.

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 healthz_test

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestHealthz(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Healthz Suite")
}
17 changes: 11 additions & 6 deletions pkg/utils/pager/pager.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,13 @@ const (
defaultPageBufferSize = 10
)

// lister is the subset of client.Reader that ListPager uses.
type lister interface {
List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error
}

// New creates a new pager from the provided reader using the default options.
func New(reader client.Reader) *ListPager {
func New(reader lister) *ListPager {
return &ListPager{
Reader: reader,
PageSize: defaultPageSize,
Expand All @@ -48,7 +53,7 @@ func New(reader client.Reader) *ListPager {
// Exception: this ListPager also fixes the `specifying resource version is not allowed when using continue` error
// in EachListItem and EachListItemWithAlloc.
type ListPager struct {
Reader client.Reader
Reader lister

// PageSize is the maximum number of objects to retrieve in individual list calls.
// If a client.Limit option is passed, the pager uses the option's value instead.
Expand All @@ -68,7 +73,7 @@ type ListPager struct {
// ListPager.PageBufferSize chunks buffered concurrently in the background.
//
// If items passed to fn are retained for different durations, and you want to avoid
// retaining the whole slice returned by p.PageFn as long as any item is referenced,
// retaining the whole slice returned by p.Reader.List as long as any item is referenced,
// use EachListItemWithAlloc instead.
func (p *ListPager) EachListItem(ctx context.Context, list client.ObjectList, fn func(obj client.Object) error, opts ...client.ListOption) error {
return p.eachListChunkBuffered(ctx, list, func(list client.ObjectList) error {
Expand All @@ -78,13 +83,13 @@ func (p *ListPager) EachListItem(ctx context.Context, list client.ObjectList, fn
}, opts...)
}

// EachListItemWithAlloc works like EachListItem, but avoids retaining references to the items slice returned by p.PageFn.
// It does this by making a shallow copy of non-pointer items in the slice returned by p.PageFn.
// EachListItemWithAlloc works like EachListItem, but avoids retaining references to the items slice returned by p.Reader.List.
// It does this by making a shallow copy of non-pointer items in the slice returned by p.Reader.List.
//
// If the items passed to fn are not retained, or are retained for the same duration, use EachListItem instead for memory efficiency.
func (p *ListPager) EachListItemWithAlloc(ctx context.Context, list client.ObjectList, fn func(obj client.Object) error, opts ...client.ListOption) error {
return p.eachListChunkBuffered(ctx, list, func(list client.ObjectList) error {
return meta.EachListItem(list, func(obj runtime.Object) error {
return meta.EachListItemWithAlloc(list, func(obj runtime.Object) error {
return fn(obj.(client.Object))
})
}, opts...)
Expand Down
29 changes: 29 additions & 0 deletions pkg/utils/pager/pager_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
Copyright 2025 Tim Ebert.
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 pager_test

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestPager(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Pager Suite")
}
Loading
Loading