Skip to content
Open
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
1 change: 1 addition & 0 deletions .github/workflows/kind-action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
uses: helm/kind-action@v1
with:
cluster_name: kinder
node_image: ghcr.io/carvel-dev/kindest/node:v1.36.0
- uses: actions/checkout@v4
with:
fetch-depth: 0
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-gh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
fail-fast: false
max-parallel: 2
matrix:
k8s-version: [v1.28.0, latest]
k8s-version: [v1.32.13, v1.33.12, v1.34.8, v1.35.5, v1.36.1]
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v4
Expand Down
29 changes: 29 additions & 0 deletions pkg/apiserver/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ import (
mutatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating"
validatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/validating"
genericopenapi "k8s.io/apiserver/pkg/endpoints/openapi"
genericfeatures "k8s.io/apiserver/pkg/features"
apirest "k8s.io/apiserver/pkg/registry/rest"
genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/dynamiccertificates"
genericoptions "k8s.io/apiserver/pkg/server/options"
apiservercompatibility "k8s.io/apiserver/pkg/util/compatibility"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/discovery"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
Expand Down Expand Up @@ -146,6 +148,10 @@ type NewAPIServerOpts struct {
}

func NewAPIServer(clientConfig *rest.Config, coreClient kubernetes.Interface, kcClient kcclient.Interface, opts NewAPIServerOpts) (*APIServer, error) { //nolint
if err := disableWatchListFeatureGate(); err != nil {
return nil, err
}

aggClient, err := aggregatorclient.NewForConfig(clientConfig)
if err != nil {
return nil, fmt.Errorf("building aggregation client: %v", err)
Expand Down Expand Up @@ -201,6 +207,29 @@ func NewAPIServer(clientConfig *rest.Config, coreClient kubernetes.Interface, kc
return &APIServer{server, make(chan struct{}), aggClient, opts.Logger}, nil
}

// disableWatchListFeatureGate turns off the WatchList feature for the packaging
// aggregated API server. This server is a thin proxy whose storage is the host
// cluster's InternalPackage(Metadata) CRDs, and its custom REST watch does not
// synthesize the WatchList "initial-events-end" bookmark.
//
// With WatchList enabled, the embedded apiserver's SetListOptionsDefaults injects
// sendInitialEvents + resourceVersionMatch=NotOlderThan into watch requests with
// resourceVersion=0. Those defaulted options are then rejected with 422 by host
// clusters that ship the WatchList feature gate disabled (e.g. Kubernetes 1.33,
// which turned it off by default), breaking a plain "watch&resourceVersion=0".
//
// Disabling it here means such requests are served as a plain watch, and
// WatchList-capable clients (e.g. client-go informers) receive a 422 for the
// streaming request and transparently fall back to list+watch.
func disableWatchListFeatureGate() error {
if err := utilfeature.DefaultMutableFeatureGate.SetFromMap(map[string]bool{
string(genericfeatures.WatchList): false,
}); err != nil {
return fmt.Errorf("disabling WatchList feature gate: %v", err)
}
return nil
}

// Run spawns a go routine that exits when apiserver is stopped.
func (as *APIServer) Run() error {
go func() {
Expand Down
13 changes: 13 additions & 0 deletions pkg/apiserver/apiserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,25 @@ import (
"github.com/go-logr/logr"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
genericfeatures "k8s.io/apiserver/pkg/features"
"k8s.io/apiserver/pkg/server/dynamiccertificates"
utilfeature "k8s.io/apiserver/pkg/util/feature"
clienttesting "k8s.io/client-go/testing"
apiregv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
fakeaggregator "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake"
)

// The packaging apiserver must run with the WatchList feature disabled so that
// watch&resourceVersion=0 requests are not defaulted with sendInitialEvents,
// which host clusters that ship WatchList off (e.g. K8s 1.33) reject with 422.
func Test_disableWatchListFeatureGate(t *testing.T) {
err := disableWatchListFeatureGate()
require.NoError(t, err)

require.False(t, utilfeature.DefaultFeatureGate.Enabled(genericfeatures.WatchList),
"WatchList feature gate must be disabled for the packaging apiserver")
}

// fakeCAProvider implements dynamiccertificates.CAContentProvider
type fakeCAProvider struct {
bundle []byte
Expand Down
Loading