diff --git a/config/crds/admin.kcp.io_apiidentities.yaml b/config/crds/admin.kcp.io_apiidentities.yaml new file mode 100644 index 00000000000..681b9a3eae3 --- /dev/null +++ b/config/crds/admin.kcp.io_apiidentities.yaml @@ -0,0 +1,187 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 + name: apiidentities.admin.kcp.io +spec: + group: admin.kcp.io + names: + categories: + - kcp + kind: APIIdentity + listKind: APIIdentityList + plural: apiidentities + singular: apiidentity + scope: Cluster + versions: + - additionalPrinterColumns: + - description: APIExport owning the identity + jsonPath: .status.owner.name + name: Owner + type: string + - description: Logical cluster of the owner + jsonPath: .status.owner.cluster + name: Cluster + type: string + - description: Identity of a core APIExport in root + jsonPath: .status.core + name: Core + type: boolean + - description: True while other APIExports hold the identity + jsonPath: .status.conditions[?(@.type=="Shared")].status + name: Shared + priority: 1 + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + APIIdentity is the view of one API identity: which APIExport owns it and + which other APIExports hold it. It is computed from the APIExports of the + installation and not stored anywhere; there is one per identity, named + after the identity value, served read-only through the Admin workspace. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + status: + description: |- + APIIdentityStatus is the derived state of an identity. The object has no + spec: nothing about it is user intent. + properties: + conditions: + description: conditions is a list of conditions that apply to the + identity. + items: + description: Condition defines an observation of a object operational + state. + properties: + lastTransitionTime: + description: |- + Last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when + the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + A human readable message indicating details about the transition. + This field may be empty. + type: string + reason: + description: |- + The reason for the condition's last transition in CamelCase. + The specific API may choose whether or not this field is considered a guaranteed API. + This field may not be empty. + type: string + severity: + description: |- + Severity provides an explicit classification of Reason code, so the users or machines can immediately + understand the current situation and act accordingly. + The Severity field MUST be set only when Status=False. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: |- + Type of condition in CamelCase or in foo.example.com/CamelCase. + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions + can be useful (see .node.status.conditions), the ability to deconflict is important. + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + core: + description: |- + core is true for the identities of the core APIExports in the root + workspace, which every shard needs before it can serve. + type: boolean + holders: + description: |- + holders lists the APIExports, other than the owner, that serve the + identity: those whose identity secret carries the same key, oldest + first. + items: + description: Holder is an APIExport, other than the owner, that + serves the identity. + properties: + cluster: + description: cluster is the logical cluster name hosting the + APIExport. + minLength: 1 + type: string + name: + description: name is the name of the APIExport. + minLength: 1 + type: string + since: + description: since is when the holder was created. + format: date-time + type: string + uid: + description: |- + uid is the UID of the APIExport, so that an export deleted and + recreated under the same name can be told apart from the original. + type: string + required: + - cluster + - name + type: object + type: array + x-kubernetes-list-map-keys: + - cluster + - name + x-kubernetes-list-type: map + owner: + description: |- + owner is the APIExport that holds the identity authoritatively: the + oldest one holding it. When it is deleted the oldest holder succeeds + it; an identity no APIExport holds any more has no entry. + properties: + cluster: + description: cluster is the logical cluster name hosting the APIExport. + minLength: 1 + type: string + name: + description: name is the name of the APIExport. + minLength: 1 + type: string + uid: + description: |- + uid is the UID of the APIExport, so that an export deleted and + recreated under the same name can be told apart from the original. + type: string + required: + - cluster + - name + type: object + required: + - owner + type: object + type: object + served: true + storage: true + subresources: {} diff --git a/docs/content/concepts/apis/exporting-apis.md b/docs/content/concepts/apis/exporting-apis.md index 762b781bf91..82eae7f7a33 100644 --- a/docs/content/concepts/apis/exporting-apis.md +++ b/docs/content/concepts/apis/exporting-apis.md @@ -163,15 +163,32 @@ above), kcp automatically generates one for you. The identity is always stored i the `APIExport.` By default, it is created in the `kcp-system` namespace in a secret whose name is the name of the `APIExport`. -An `APIExport`'s identity is similar to a private key; you should never share it with anyone. +An `APIExport`'s identity key is similar to a private key; you should never share it with anyone. -The identity's **hash** is similar to a public key; it is not private and there are times when other consumers of your -exported APIs need to know and reference it. +The identity's **hash** (`status.identityHash`) is similar to a public key; it is not private and there are times +when other consumers of your exported APIs need to know and reference it. Given 2 Workspaces, each with its own `APIExport` that exports `widgets.example.kcp.io`, kcp uses the identity hash (in a mostly transparent manner) to ensure the correct instances associated with the appropriate `APIResourceSchema` are served to clients. See [Build Your Controller](#build-your-controller) for more information. +#### Sharing an identity with another APIExport + +Two `APIExports` whose secrets carry the same `key` have the same identity: they serve the same resources out of the +same storage, which is how an `APIExport` is split into two or moved to another workspace without touching the +consumers' objects. Today that works by copying the secret, and it keeps working. + +kcp shows every identity as an `APIIdentity` (`admin.kcp.io`), listed read-only in the +[Admin workspace](../sharding/shards.md#the-admin-workspace-admin) with `kubectl ws use :admin`. The view is computed +from the `APIExports` of all shards, as replicated to the cache server; nothing is stored. The oldest `APIExport` holding +an identity is its **owner**, every other `APIExport` whose secret carries the same `key` is a **holder**, and the entry +names both. An identity with holders carries the `Shared` condition. The view does not change who may serve an identity, +which is still whoever has the secret; it makes sharing visible. + +When the owner's `APIExport` is deleted, the oldest holder becomes the owner. An identity no `APIExport` holds any more +has no entry; the next `APIExport` whose secret carries the original `key` brings it back. +`status.identityHash` is immutable once set. + ### Permission Claims When a consumer creates an `APIBinding` that binds to an `APIExport`, the API provider who owns the `APIExport` diff --git a/docs/content/concepts/sharding/shards.md b/docs/content/concepts/sharding/shards.md index 0e42b367969..d1c77bd5217 100644 --- a/docs/content/concepts/sharding/shards.md +++ b/docs/content/concepts/sharding/shards.md @@ -108,6 +108,23 @@ root us-east-2 https://127.0.0.1:6444 ... shard-1 us-east-1 https://127.0.0.1:6445 ... ``` +The Admin workspace also serves the API identity view read-only: +`apiidentities.admin.kcp.io` lists every APIExport identity in the +installation, computed from the replicated `APIExports`, named by the +identity hash, with the owning `APIExport`, the other exports holding it and +whether it is a core identity. See +[Exporting APIs](../apis/exporting-apis.md#sharing-an-identity-with-another-apiexport). + +```sh +$ kubectl get apiidentities +NAME OWNER CLUSTER CORE AGE +0e2fc4327ee7af2b2b9a0f2b4b1a8c4c7a0d6c2f0f1b6e5a9d8c7b6a5f4e3d2c tenancy.kcp.io root true 3d +5fdf7c7aaf407fd1594566869803f565bb84d22156cef5c445d2ee13ac2cfca6 widgets 2cynbfy2m0wtjqcs false 2h +$ kubectl get apiidentities -l apiidentities.admin.kcp.io/core=true # the core identities +$ kubectl get apiidentities -l apiidentities.admin.kcp.io/owner-cluster=root # owned by exports in root +$ kubectl get apiidentities -l apiidentities.admin.kcp.io/owner=widgets # owned by an export named widgets +``` + Access requires membership in the `system:kcp:admin` group; the serving path does not depend on the root shard. To reach the view through a front-proxy, add a `/services/` path mapping; any shard can be the backend. diff --git a/hack/update-codegen-clients.sh b/hack/update-codegen-clients.sh index 1b46bca3310..f23d03fa77c 100755 --- a/hack/update-codegen-clients.sh +++ b/hack/update-codegen-clients.sh @@ -51,6 +51,7 @@ mkdir -p ${SDK_PKG}/client/{clientset,applyconfiguration,listers,informers} github.com/kcp-dev/sdk/apis/apis/v1alpha1 \ github.com/kcp-dev/sdk/apis/apis/v1alpha2 \ github.com/kcp-dev/sdk/apis/cache/v1alpha1 \ + github.com/kcp-dev/sdk/apis/admin/v1alpha1 \ github.com/kcp-dev/sdk/apis/migration/v1alpha1 \ github.com/kcp-dev/sdk/apis/topology/v1alpha1 \ github.com/kcp-dev/sdk/apis/third_party/conditions/apis/conditions/v1alpha1 \ @@ -68,6 +69,7 @@ mkdir -p ${SDK_PKG}/client/{clientset,applyconfiguration,listers,informers} --input github.com/kcp-dev/sdk/apis/apis/v1alpha2 \ --input github.com/kcp-dev/sdk/apis/topology/v1alpha1 \ --input github.com/kcp-dev/sdk/apis/cache/v1alpha1 \ + --input github.com/kcp-dev/sdk/apis/admin/v1alpha1 \ --input github.com/kcp-dev/sdk/apis/migration/v1alpha1 \ --input-base="" \ --apply-configuration-package=github.com/kcp-dev/sdk/client/applyconfiguration \ @@ -151,6 +153,7 @@ go install "${OPENAPI_PKG}"/cmd/openapi-gen github.com/kcp-dev/sdk/apis/apis/v1alpha2 \ github.com/kcp-dev/sdk/apis/topology/v1alpha1 \ github.com/kcp-dev/sdk/apis/cache/v1alpha1 \ + github.com/kcp-dev/sdk/apis/admin/v1alpha1 \ github.com/kcp-dev/sdk/apis/migration/v1alpha1 \ github.com/kcp-dev/sdk/apis/third_party/conditions/apis/conditions/v1alpha1 @@ -165,6 +168,7 @@ go install "${OPENAPI_PKG}"/cmd/openapi-gen github.com/kcp-dev/sdk/apis/apis/v1alpha2 \ github.com/kcp-dev/sdk/apis/topology/v1alpha1 \ github.com/kcp-dev/sdk/apis/cache/v1alpha1 \ + github.com/kcp-dev/sdk/apis/admin/v1alpha1 \ github.com/kcp-dev/sdk/apis/migration/v1alpha1 \ github.com/kcp-dev/sdk/apis/third_party/conditions/apis/conditions/v1alpha1 \ k8s.io/apimachinery/pkg/apis/meta/v1 \ diff --git a/hack/update-codegen-crds.sh b/hack/update-codegen-crds.sh index 8ceaecf07af..7a6456bf4d3 100755 --- a/hack/update-codegen-crds.sh +++ b/hack/update-codegen-crds.sh @@ -47,7 +47,8 @@ done ( ${KCP_APIGEN_GEN} --input-dir "${REPO_ROOT}"/config/crds --output-dir "${REPO_ROOT}"/config/root-phase0 \ - --ignore-export-schemas cachedobjects.cache.kcp.io + --ignore-export-schemas cachedobjects.cache.kcp.io \ + --ignore-schemas apiidentities.admin.kcp.io ) diff --git a/pkg/admission/apiexport/admission.go b/pkg/admission/apiexport/admission.go index f8c66fcd249..68a1a42eca4 100644 --- a/pkg/admission/apiexport/admission.go +++ b/pkg/admission/apiexport/admission.go @@ -27,6 +27,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/authentication/user" "github.com/kcp-dev/sdk/apis/apis" apisv1alpha1 "github.com/kcp-dev/sdk/apis/apis/v1alpha1" @@ -75,6 +76,13 @@ func (e *APIExportAdmission) Validate(ctx context.Context, a admission.Attribute return fmt.Errorf("unexpected type %T", a.GetObject()) } + if a.GetSubresource() == "status" { + if a.GetOperation() == admission.Update { + return validateStatusUpdate(a, u) + } + return nil + } + switch a.GetKind().GroupVersion().Version { case apisv1alpha1.SchemeGroupVersion.Version: // v1alpha1 is deprecated, but we still need to support it for a while @@ -124,6 +132,46 @@ func (e *APIExportAdmission) Validate(ctx context.Context, a admission.Attribute return nil } +// validateStatusUpdate keeps status.identityHash immutable for everyone but +// kcp itself. The identity keys the storage of every consumer's bound +// instances and is what permission claims and wildcard clients address the +// export by; a status write that changed it would make the writer's export +// serve another export's consumers' data. kcp changes it only to withdraw an +// authorization. The field sits at the same path in every served version. +func validateStatusUpdate(a admission.Attributes, u *unstructured.Unstructured) error { + old, ok := a.GetOldObject().(*unstructured.Unstructured) + if !ok { + return fmt.Errorf("unexpected type %T", a.GetOldObject()) + } + oldHash, _, err := unstructured.NestedString(old.Object, "status", "identityHash") + if err != nil { + return fmt.Errorf("failed to read status.identityHash: %w", err) + } + newHash, _, err := unstructured.NestedString(u.Object, "status", "identityHash") + if err != nil { + return fmt.Errorf("failed to read status.identityHash: %w", err) + } + if oldHash == "" || newHash == oldHash || isSystemPrivileged(a) { + return nil + } + return admission.NewForbidden(a, + field.Forbidden( + field.NewPath("status").Child("identityHash"), + "identityHash is immutable once set")) +} + +func isSystemPrivileged(a admission.Attributes) bool { + if a.GetUserInfo() == nil { + return false + } + for _, g := range a.GetUserInfo().GetGroups() { + if g == user.SystemPrivilegedGroup { + return true + } + } + return false +} + func (e *APIExportAdmission) validatev1alpha2(_ context.Context, a admission.Attributes, ae *apisv1alpha2.APIExport) (err error) { for i, pc := range ae.Spec.PermissionClaims { if pc.IdentityHash == "" && !e.isBuiltIn(pc.GroupResource) && pc.Group != apis.GroupName { diff --git a/pkg/admission/apiexport/admission_status_test.go b/pkg/admission/apiexport/admission_status_test.go new file mode 100644 index 00000000000..21fb0407a5b --- /dev/null +++ b/pkg/admission/apiexport/admission_status_test.go @@ -0,0 +1,87 @@ +/* +Copyright 2026 The kcp 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 apiexport + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/authentication/user" + + "github.com/kcp-dev/sdk/apis/apis" + apisv1alpha2 "github.com/kcp-dev/sdk/apis/apis/v1alpha2" + + "github.com/kcp-dev/kcp/pkg/admission/helpers" +) + +func statusAttr(oldHash, newHash string, groups ...string) admission.Attributes { + export := func(hash string) *apisv1alpha2.APIExport { + return &apisv1alpha2.APIExport{ + ObjectMeta: metav1.ObjectMeta{Name: "cool-something"}, + Status: apisv1alpha2.APIExportStatus{IdentityHash: hash}, + } + } + return admission.NewAttributesRecord( + helpers.ToUnstructuredOrDie(export(newHash)), + helpers.ToUnstructuredOrDie(export(oldHash)), + apisv1alpha2.Kind("APIExport").WithVersion("v1alpha2"), + "", + "cool-something", + apisv1alpha2.Resource("apiexports").WithVersion("v1alpha2"), + "status", + admission.Update, + &metav1.UpdateOptions{}, + false, + &user.DefaultInfo{Name: "someone", Groups: groups}, + ) +} + +func TestStatusIdentityHashImmutable(t *testing.T) { + t.Parallel() + + cases := map[string]struct { + oldHash, newHash string + groups []string + wantErr string + }{ + "first assignment is allowed": {oldHash: "", newHash: "abc"}, + "unchanged is allowed": {oldHash: "abc", newHash: "abc"}, + "other status fields may change": {oldHash: "", newHash: ""}, + "changing it is forbidden": {oldHash: "abc", newHash: "def", wantErr: "identityHash is immutable once set"}, + "clearing it is forbidden": {oldHash: "abc", newHash: "", wantErr: "identityHash is immutable once set"}, + "kcp itself may withdraw it": {oldHash: "abc", newHash: "", groups: []string{user.SystemPrivilegedGroup}}, + "kcp itself may change it": {oldHash: "abc", newHash: "def", groups: []string{user.SystemPrivilegedGroup}}, + "a workspace admin group does not help": {oldHash: "abc", newHash: "def", groups: []string{"system:kcp:workspace:access", "system:authenticated"}, wantErr: "identityHash is immutable once set"}, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + t.Parallel() + plugin := NewAPIExportAdmission(func(apis.GroupResource) bool { return false }) + err := plugin.Validate(context.Background(), statusAttr(tc.oldHash, tc.newHash, tc.groups...), nil) + if tc.wantErr == "" { + require.NoError(t, err) + return + } + require.Error(t, err) + require.Contains(t, err.Error(), tc.wantErr) + }) + } +} diff --git a/pkg/apiidentity/projector.go b/pkg/apiidentity/projector.go new file mode 100644 index 00000000000..b94e5fe1e78 --- /dev/null +++ b/pkg/apiidentity/projector.go @@ -0,0 +1,433 @@ +/* +Copyright 2026 The kcp 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 apiidentity derives the APIIdentity view from APIExports. An +// identity is held by every APIExport whose status carries its hash; the +// view names the owner and the other holders. Nothing is stored: the view +// is computed from the APIExports replicated to the cache server, which are +// ordinary per-shard data, so it is the same on every shard and needs no +// coordination between them. +package apiidentity + +import ( + "sort" + "strconv" + "sync" + + "k8s.io/apimachinery/pkg/api/equality" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/tools/cache" + + "github.com/kcp-dev/logicalcluster/v3" + adminv1alpha1 "github.com/kcp-dev/sdk/apis/admin/v1alpha1" + apisv1alpha2 "github.com/kcp-dev/sdk/apis/apis/v1alpha2" + "github.com/kcp-dev/sdk/apis/core" + "github.com/kcp-dev/sdk/apis/third_party/conditions/util/conditions" + + "github.com/kcp-dev/kcp/pkg/server/bootstrap" + "github.com/kcp-dev/kcp/pkg/tombstone" +) + +// IsCoreExport reports whether the export is one of the core APIExports in +// the root workspace, whose identities every shard needs before it can serve. +func IsCoreExport(export *apisv1alpha2.APIExport) bool { + return logicalcluster.From(export) == core.RootCluster && coreExportNames.Has(export.Name) +} + +var coreExportNames = func() sets.Set[string] { + names := sets.New[string]() + for _, name := range bootstrap.KcpRootGroupExportNames { + names.Insert(name) + } + for _, name := range bootstrap.KcpRootGroupResourceExportNames { + names.Insert(name) + } + return names +}() + +// Project computes the view of one identity from the exports holding it. +// The owner is the oldest export, by creation time, then cluster, then name, +// so every reader of the same exports agrees on it; the others are holders. +// It returns nil when no export holds the identity. +func Project(hash string, exports []*apisv1alpha2.APIExport, isCore func(*apisv1alpha2.APIExport) bool) *adminv1alpha1.APIIdentity { + if len(exports) == 0 { + return nil + } + sorted := append([]*apisv1alpha2.APIExport(nil), exports...) + sort.SliceStable(sorted, func(i, j int) bool { + if !sorted[i].CreationTimestamp.Equal(&sorted[j].CreationTimestamp) { + return sorted[i].CreationTimestamp.Before(&sorted[j].CreationTimestamp) + } + if ci, cj := logicalcluster.From(sorted[i]), logicalcluster.From(sorted[j]); ci != cj { + return ci < cj + } + return sorted[i].Name < sorted[j].Name + }) + + owner := sorted[0] + ownerRef := reference(owner) + core := isCore != nil && isCore(owner) + entry := &adminv1alpha1.APIIdentity{ + TypeMeta: metav1.TypeMeta{APIVersion: adminv1alpha1.SchemeGroupVersion.String(), Kind: "APIIdentity"}, + ObjectMeta: metav1.ObjectMeta{ + Name: hash, + CreationTimestamp: owner.CreationTimestamp, + Labels: adminv1alpha1.Labels(ownerRef, core), + }, + Status: adminv1alpha1.APIIdentityStatus{Owner: ownerRef, Core: core}, + } + for _, holder := range sorted[1:] { + entry.Status.Holders = append(entry.Status.Holders, adminv1alpha1.Holder{ExportReference: reference(holder), Since: holder.CreationTimestamp}) + } + if len(entry.Status.Holders) > 0 { + conditions.MarkTrue(entry, adminv1alpha1.APIIdentityShared) + } + return entry +} + +func reference(export *apisv1alpha2.APIExport) adminv1alpha1.ExportReference { + return adminv1alpha1.ExportReference{Cluster: logicalcluster.From(export).String(), Name: export.Name, UID: string(export.UID)} +} + +// Projector keeps the view of every identity current from APIExport events +// and serves it: by name, as a list, as a watch, and to event handlers. It +// is an event handler for an APIExport informer, normally the one against +// the cache server. Resource versions are its own, counting the changes of +// the view; a watch resuming from one it no longer remembers is told to +// relist, like a watch on an apiserver. +type Projector struct { + isCore func(*apisv1alpha2.APIExport) bool + + lock sync.RWMutex + exports map[string]map[string]*apisv1alpha2.APIExport // identity hash -> cluster|name -> export + entries map[string]*adminv1alpha1.APIIdentity // identity hash -> current view + rv uint64 + history []event + watchers map[*watcher]struct{} + handlers []cache.ResourceEventHandler +} + +// historyLength bounds the events kept for watches resuming from a +// resource version. +const historyLength = 1024 + +type event struct { + rv uint64 + typ watch.EventType + obj *adminv1alpha1.APIIdentity +} + +// NewProjector creates a projector. isCore tells which exports are the core +// ones of the installation; IsCoreExport is the usual choice. +func NewProjector(isCore func(*apisv1alpha2.APIExport) bool) *Projector { + return &Projector{ + isCore: isCore, + exports: map[string]map[string]*apisv1alpha2.APIExport{}, + entries: map[string]*adminv1alpha1.APIIdentity{}, + watchers: map[*watcher]struct{}{}, + } +} + +var _ cache.ResourceEventHandler = &Projector{} + +func (p *Projector) OnAdd(obj any, _ bool) { + if export, ok := obj.(*apisv1alpha2.APIExport); ok { + p.apply(nil, export) + } +} + +func (p *Projector) OnUpdate(oldObj, newObj any) { + oldExport, _ := oldObj.(*apisv1alpha2.APIExport) + if newExport, ok := newObj.(*apisv1alpha2.APIExport); ok { + p.apply(oldExport, newExport) + } +} + +func (p *Projector) OnDelete(obj any) { + if export := tombstone.Obj[*apisv1alpha2.APIExport](obj); export != nil { + p.apply(export, nil) + } +} + +func exportKey(export *apisv1alpha2.APIExport) string { + return logicalcluster.From(export).String() + "|" + export.Name +} + +// apply records that old became new (either may be nil) and re-projects the +// identities affected. +func (p *Projector) apply(oldExport, newExport *apisv1alpha2.APIExport) { + p.lock.Lock() + defer p.lock.Unlock() + + affected := sets.New[string]() + if oldExport != nil && oldExport.Status.IdentityHash != "" { + hash := oldExport.Status.IdentityHash + delete(p.exports[hash], exportKey(oldExport)) + if len(p.exports[hash]) == 0 { + delete(p.exports, hash) + } + affected.Insert(hash) + } + if newExport != nil && newExport.Status.IdentityHash != "" { + hash := newExport.Status.IdentityHash + if p.exports[hash] == nil { + p.exports[hash] = map[string]*apisv1alpha2.APIExport{} + } + p.exports[hash][exportKey(newExport)] = newExport + affected.Insert(hash) + } + for _, hash := range sets.List(affected) { + p.reproject(hash) + } +} + +// reproject recomputes one identity's view and emits the change, if any. +// Called with the lock held. +func (p *Projector) reproject(hash string) { + exports := make([]*apisv1alpha2.APIExport, 0, len(p.exports[hash])) + for _, export := range p.exports[hash] { + exports = append(exports, export) + } + next := Project(hash, exports, p.isCore) + prev := p.entries[hash] + + switch { + case prev == nil && next == nil: + return + case next == nil: + delete(p.entries, hash) + p.emit(watch.Deleted, prev) + case prev == nil: + p.entries[hash] = next + p.emit(watch.Added, next) + default: + carryOverConditionTimes(prev, next) + next.ResourceVersion = prev.ResourceVersion + if equality.Semantic.DeepEqual(prev, next) { + return + } + p.entries[hash] = next + p.emit(watch.Modified, next) + } +} + +// carryOverConditionTimes keeps the transition time of a condition that did +// not change status, so that a recomputation is not a change. +func carryOverConditionTimes(prev, next *adminv1alpha1.APIIdentity) { + for i := range next.Status.Conditions { + if old := conditions.Get(prev, next.Status.Conditions[i].Type); old != nil && old.Status == next.Status.Conditions[i].Status { + next.Status.Conditions[i].LastTransitionTime = old.LastTransitionTime + } + } +} + +// emit stamps the next resource version on the object and delivers the +// event to the watchers and handlers. Called with the lock held. +func (p *Projector) emit(typ watch.EventType, obj *adminv1alpha1.APIIdentity) { + p.rv++ + obj.ResourceVersion = strconv.FormatUint(p.rv, 10) + ev := event{rv: p.rv, typ: typ, obj: obj} + + p.history = append(p.history, ev) + if len(p.history) > historyLength { + p.history = p.history[len(p.history)-historyLength:] + } + for w := range p.watchers { + if !w.deliver(ev) { + delete(p.watchers, w) + } + } + for _, handler := range p.handlers { + switch typ { + case watch.Added: + handler.OnAdd(obj.DeepCopy(), false) + case watch.Modified: + handler.OnUpdate(nil, obj.DeepCopy()) + case watch.Deleted: + handler.OnDelete(obj.DeepCopy()) + } + } +} + +// ResourceVersion is the version of the view: the number of changes so far. +func (p *Projector) ResourceVersion() string { + p.lock.RLock() + defer p.lock.RUnlock() + return strconv.FormatUint(p.rv, 10) +} + +// Get returns the view of the named identity. +func (p *Projector) Get(name string) (*adminv1alpha1.APIIdentity, bool) { + p.lock.RLock() + defer p.lock.RUnlock() + entry, ok := p.entries[name] + if !ok { + return nil, false + } + return entry.DeepCopy(), true +} + +// List returns the views matching the selector, by name. +func (p *Projector) List(selector labels.Selector) []*adminv1alpha1.APIIdentity { + if selector == nil { + selector = labels.Everything() + } + p.lock.RLock() + defer p.lock.RUnlock() + var result []*adminv1alpha1.APIIdentity + for _, entry := range p.entries { + if selector.Matches(labels.Set(entry.Labels)) { + result = append(result, entry.DeepCopy()) + } + } + sort.Slice(result, func(i, j int) bool { return result[i].Name < result[j].Name }) + return result +} + +// AddEventHandler registers a handler for the changes of the view. It gets +// the current entries first, as initial adds. Handlers run with the +// projector's lock held and must not call back into it. +func (p *Projector) AddEventHandler(handler cache.ResourceEventHandler) { + p.lock.Lock() + defer p.lock.Unlock() + p.handlers = append(p.handlers, handler) + for _, entry := range p.entries { + handler.OnAdd(entry.DeepCopy(), true) + } +} + +// WatchOptions are the watch semantics a client asks for. +type WatchOptions struct { + // ResourceVersion is where to start: "" for now, "0" for the current + // entries followed by changes, otherwise the changes after that version. + ResourceVersion string + // SendInitialEvents asks for the current entries first, closed by a + // bookmark when AllowWatchBookmarks is set, as a watch-list client does. + SendInitialEvents bool + AllowWatchBookmarks bool +} + +// Watch streams the changes of the entries matching the selector. +func (p *Projector) Watch(selector labels.Selector, opts WatchOptions) (watch.Interface, error) { + if selector == nil { + selector = labels.Everything() + } + w := &watcher{projector: p, selector: selector, ch: make(chan watch.Event, watchBuffer)} + + p.lock.Lock() + defer p.lock.Unlock() + + switch { + case opts.SendInitialEvents || opts.ResourceVersion == "0": + for _, entry := range p.sortedEntries() { + w.deliver(event{rv: p.rv, typ: watch.Added, obj: entry}) + } + if opts.SendInitialEvents && opts.AllowWatchBookmarks { + w.deliver(event{rv: p.rv, typ: watch.Bookmark, obj: &adminv1alpha1.APIIdentity{ + TypeMeta: metav1.TypeMeta{APIVersion: adminv1alpha1.SchemeGroupVersion.String(), Kind: "APIIdentity"}, + ObjectMeta: metav1.ObjectMeta{ResourceVersion: strconv.FormatUint(p.rv, 10), Annotations: map[string]string{metav1.InitialEventsAnnotationKey: "true"}}, + }}) + } + case opts.ResourceVersion == "": + // from now on + default: + from, err := strconv.ParseUint(opts.ResourceVersion, 10, 64) + if err != nil { + return nil, apierrors.NewBadRequest("invalid resourceVersion " + opts.ResourceVersion) + } + if from > p.rv || (from < p.rv && (len(p.history) == 0 || p.history[0].rv > from+1)) { + // too old, or unknown: the client relists + w.ch <- watch.Event{Type: watch.Error, Object: &apierrors.NewResourceExpired("too old resource version: " + opts.ResourceVersion).ErrStatus} + close(w.ch) + w.closed = true + return w, nil + } + for _, ev := range p.history { + if ev.rv > from { + w.deliver(ev) + } + } + } + p.watchers[w] = struct{}{} + return w, nil +} + +func (p *Projector) sortedEntries() []*adminv1alpha1.APIIdentity { + entries := make([]*adminv1alpha1.APIIdentity, 0, len(p.entries)) + for _, entry := range p.entries { + entries = append(entries, entry) + } + sort.Slice(entries, func(i, j int) bool { return entries[i].Name < entries[j].Name }) + return entries +} + +func (p *Projector) removeWatcher(w *watcher) { + p.lock.Lock() + defer p.lock.Unlock() + if _, ok := p.watchers[w]; ok { + delete(p.watchers, w) + w.close() + } +} + +// watchBuffer is how many events a watcher may fall behind before it is +// closed, which makes the client relist. +const watchBuffer = 1000 + +type watcher struct { + projector *Projector + selector labels.Selector + ch chan watch.Event + closed bool // guarded by the projector's lock +} + +// deliver queues the event for the watcher. It reports false when the +// watcher could not keep up and was closed. Called with the lock held. +func (w *watcher) deliver(ev event) bool { + if w.closed { + return false + } + if ev.typ != watch.Bookmark && !w.selector.Matches(labels.Set(ev.obj.Labels)) { + return true + } + select { + case w.ch <- watch.Event{Type: ev.typ, Object: ev.obj.DeepCopy()}: + return true + default: + w.close() + return false + } +} + +// close ends the stream. Called with the lock held. +func (w *watcher) close() { + if !w.closed { + w.closed = true + close(w.ch) + } +} + +func (w *watcher) Stop() { + w.projector.removeWatcher(w) +} + +func (w *watcher) ResultChan() <-chan watch.Event { + return w.ch +} diff --git a/pkg/apiidentity/projector_test.go b/pkg/apiidentity/projector_test.go new file mode 100644 index 00000000000..81a5ad31797 --- /dev/null +++ b/pkg/apiidentity/projector_test.go @@ -0,0 +1,305 @@ +/* +Copyright 2026 The kcp 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 apiidentity + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/tools/cache" + + "github.com/kcp-dev/logicalcluster/v3" + adminv1alpha1 "github.com/kcp-dev/sdk/apis/admin/v1alpha1" + apisv1alpha2 "github.com/kcp-dev/sdk/apis/apis/v1alpha2" + "github.com/kcp-dev/sdk/apis/third_party/conditions/util/conditions" +) + +const ( + hashA = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + hashB = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" +) + +var epoch = time.Date(2026, 10, 1, 0, 0, 0, 0, time.UTC) + +// export is an APIExport holding the identity, created at epoch plus the +// given number of minutes. +func export(cluster, name, hash string, createdMinutes int) *apisv1alpha2.APIExport { + return &apisv1alpha2.APIExport{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + UID: types.UID("uid-" + cluster + "-" + name), + Annotations: map[string]string{logicalcluster.AnnotationKey: cluster}, + CreationTimestamp: metav1.NewTime(epoch.Add(time.Duration(createdMinutes) * time.Minute)), + }, + Status: apisv1alpha2.APIExportStatus{IdentityHash: hash}, + } +} + +func ref(cluster, name string) adminv1alpha1.ExportReference { + return adminv1alpha1.ExportReference{Cluster: cluster, Name: name, UID: "uid-" + cluster + "-" + name} +} + +func TestProject(t *testing.T) { + t.Parallel() + + t.Run("no exports, no entry", func(t *testing.T) { + t.Parallel() + require.Nil(t, Project(hashA, nil, IsCoreExport)) + }) + + t.Run("the oldest export owns, the others hold, in age order", func(t *testing.T) { + t.Parallel() + entry := Project(hashA, []*apisv1alpha2.APIExport{ + export("ws-c", "wildwest", hashA, 5), + export("ws-a", "wildwest", hashA, 1), + export("ws-b", "wildwest", hashA, 3), + }, IsCoreExport) + require.Equal(t, hashA, entry.Name) + require.Equal(t, ref("ws-a", "wildwest"), entry.Status.Owner) + require.Equal(t, []adminv1alpha1.Holder{ + {ExportReference: ref("ws-b", "wildwest"), Since: metav1.NewTime(epoch.Add(3 * time.Minute))}, + {ExportReference: ref("ws-c", "wildwest"), Since: metav1.NewTime(epoch.Add(5 * time.Minute))}, + }, entry.Status.Holders) + require.True(t, conditions.IsTrue(entry, adminv1alpha1.APIIdentityShared)) + require.False(t, entry.Status.Core) + require.Equal(t, adminv1alpha1.Labels(ref("ws-a", "wildwest"), false), entry.Labels) + require.Equal(t, epoch.Add(time.Minute), entry.CreationTimestamp.Time) + }) + + t.Run("same age: cluster, then name, decides", func(t *testing.T) { + t.Parallel() + entry := Project(hashA, []*apisv1alpha2.APIExport{ + export("ws-b", "a", hashA, 1), + export("ws-a", "z", hashA, 1), + export("ws-a", "b", hashA, 1), + }, IsCoreExport) + require.Equal(t, ref("ws-a", "b"), entry.Status.Owner) + }) + + t.Run("a sole holder is not shared", func(t *testing.T) { + t.Parallel() + entry := Project(hashA, []*apisv1alpha2.APIExport{export("ws-a", "wildwest", hashA, 1)}, IsCoreExport) + require.Empty(t, entry.Status.Holders) + require.Nil(t, conditions.Get(entry, adminv1alpha1.APIIdentityShared)) + }) + + t.Run("the core exports of root are core", func(t *testing.T) { + t.Parallel() + entry := Project(hashA, []*apisv1alpha2.APIExport{export("root", "tenancy.kcp.io", hashA, 1)}, IsCoreExport) + require.True(t, entry.Status.Core) + require.Equal(t, "true", entry.Labels[adminv1alpha1.APIIdentityCoreLabel]) + require.False(t, Project(hashA, []*apisv1alpha2.APIExport{export("org", "tenancy.kcp.io", hashA, 1)}, IsCoreExport).Status.Core) + }) +} + +// recorder collects the events a handler gets. +type recorder struct { + events []string +} + +func (r *recorder) OnAdd(obj interface{}, _ bool) { r.record("add", obj) } +func (r *recorder) OnUpdate(_, obj interface{}) { r.record("update", obj) } +func (r *recorder) OnDelete(obj interface{}) { r.record("delete", obj) } +func (r *recorder) record(typ string, obj interface{}) { + r.events = append(r.events, typ+" "+obj.(*adminv1alpha1.APIIdentity).Name[:1]+" owner="+obj.(*adminv1alpha1.APIIdentity).Status.Owner.Cluster) +} + +func TestProjectorFollowsExports(t *testing.T) { + t.Parallel() + p := NewProjector(IsCoreExport) + rec := &recorder{} + p.AddEventHandler(rec) + + t.Log("an export without identity is not an entry") + p.OnAdd(export("ws-a", "wildwest", "", 1), true) + require.Empty(t, p.List(nil)) + + t.Log("the identity appears with the first export holding it") + p.OnUpdate(export("ws-a", "wildwest", "", 1), export("ws-a", "wildwest", hashA, 1)) + entry, ok := p.Get(hashA) + require.True(t, ok) + require.Equal(t, ref("ws-a", "wildwest"), entry.Status.Owner) + require.Equal(t, "1", entry.ResourceVersion) + + t.Log("a second export with the same hash becomes a holder") + p.OnAdd(export("ws-b", "wildwest", hashA, 2), false) + entry, _ = p.Get(hashA) + require.Len(t, entry.Status.Holders, 1) + require.Equal(t, "2", entry.ResourceVersion) + + t.Log("re-delivering the same export changes nothing") + p.OnUpdate(export("ws-b", "wildwest", hashA, 2), export("ws-b", "wildwest", hashA, 2)) + entry, _ = p.Get(hashA) + require.Equal(t, "2", entry.ResourceVersion) + require.Equal(t, "2", p.ResourceVersion()) + + t.Log("deleting the owner passes the identity to the holder") + p.OnDelete(cache.DeletedFinalStateUnknown{Key: "ws-a|wildwest", Obj: export("ws-a", "wildwest", hashA, 1)}) + entry, _ = p.Get(hashA) + require.Equal(t, ref("ws-b", "wildwest"), entry.Status.Owner) + require.Empty(t, entry.Status.Holders) + + t.Log("deleting the last export removes the entry") + p.OnDelete(export("ws-b", "wildwest", hashA, 2)) + _, ok = p.Get(hashA) + require.False(t, ok) + + require.Equal(t, []string{"add a owner=ws-a", "update a owner=ws-a", "update a owner=ws-b", "delete a owner=ws-b"}, rec.events) +} + +func TestProjectorListAndSelectors(t *testing.T) { + t.Parallel() + p := NewProjector(IsCoreExport) + p.OnAdd(export("root", "tenancy.kcp.io", hashA, 1), true) + p.OnAdd(export("org", "widgets", hashB, 1), true) + + names := func(entries []*adminv1alpha1.APIIdentity) []string { + out := make([]string, 0, len(entries)) + for _, e := range entries { + out = append(out, e.Name) + } + return out + } + require.Equal(t, []string{hashA, hashB}, names(p.List(nil))) + require.Equal(t, []string{hashA}, names(p.List(labels.SelectorFromSet(labels.Set{adminv1alpha1.APIIdentityCoreLabel: "true"})))) + require.Equal(t, []string{hashB}, names(p.List(labels.SelectorFromSet(labels.Set{adminv1alpha1.APIIdentityOwnerLabel: "widgets"})))) + require.Equal(t, []string{hashA}, names(p.List(labels.SelectorFromSet(labels.Set{adminv1alpha1.APIIdentityOwnerClusterLabel: "root"})))) + + t.Log("a late handler gets the current entries as initial adds") + rec := &recorder{} + p.AddEventHandler(rec) + require.ElementsMatch(t, []string{"add a owner=root", "add b owner=org"}, rec.events) +} + +func next(t *testing.T, w watch.Interface) watch.Event { + t.Helper() + select { + case ev, ok := <-w.ResultChan(): + require.True(t, ok, "the watch closed") + return ev + case <-time.After(5 * time.Second): + t.Fatal("timed out waiting for a watch event") + return watch.Event{} + } +} + +func TestProjectorWatch(t *testing.T) { + t.Parallel() + + t.Run("from now delivers only changes", func(t *testing.T) { + t.Parallel() + p := NewProjector(IsCoreExport) + p.OnAdd(export("ws-a", "wildwest", hashA, 1), true) + w, err := p.Watch(nil, WatchOptions{}) + require.NoError(t, err) + t.Cleanup(w.Stop) + + p.OnAdd(export("ws-b", "wildwest", hashA, 2), false) + ev := next(t, w) + require.Equal(t, watch.Modified, ev.Type) + require.Len(t, ev.Object.(*adminv1alpha1.APIIdentity).Status.Holders, 1) + }) + + t.Run("watch-list sends the entries, a bookmark, then changes", func(t *testing.T) { + t.Parallel() + p := NewProjector(IsCoreExport) + p.OnAdd(export("ws-a", "wildwest", hashA, 1), true) + w, err := p.Watch(nil, WatchOptions{SendInitialEvents: true, AllowWatchBookmarks: true}) + require.NoError(t, err) + t.Cleanup(w.Stop) + + ev := next(t, w) + require.Equal(t, watch.Added, ev.Type) + ev = next(t, w) + require.Equal(t, watch.Bookmark, ev.Type) + require.Equal(t, "true", ev.Object.(*adminv1alpha1.APIIdentity).Annotations[metav1.InitialEventsAnnotationKey]) + require.Equal(t, "1", ev.Object.(*adminv1alpha1.APIIdentity).ResourceVersion) + + p.OnDelete(export("ws-a", "wildwest", hashA, 1)) + require.Equal(t, watch.Deleted, next(t, w).Type) + }) + + t.Run("resuming from a version replays what followed", func(t *testing.T) { + t.Parallel() + p := NewProjector(IsCoreExport) + p.OnAdd(export("ws-a", "wildwest", hashA, 1), true) + rv := p.ResourceVersion() + p.OnAdd(export("org", "widgets", hashB, 1), true) + + w, err := p.Watch(nil, WatchOptions{ResourceVersion: rv}) + require.NoError(t, err) + t.Cleanup(w.Stop) + ev := next(t, w) + require.Equal(t, watch.Added, ev.Type) + require.Equal(t, hashB, ev.Object.(*adminv1alpha1.APIIdentity).Name) + }) + + t.Run("a version from the future is expired", func(t *testing.T) { + t.Parallel() + p := NewProjector(IsCoreExport) + w, err := p.Watch(nil, WatchOptions{ResourceVersion: "42"}) + require.NoError(t, err) + ev := next(t, w) + require.Equal(t, watch.Error, ev.Type) + require.True(t, apierrors.IsResourceExpired(apierrors.FromObject(ev.Object))) + _, ok := <-w.ResultChan() + require.False(t, ok, "the stream ends after the error") + }) + + t.Run("a version older than the history is expired", func(t *testing.T) { + t.Parallel() + p := NewProjector(IsCoreExport) + for range historyLength + 5 { + p.OnAdd(export("ws-a", "wildwest", hashA, 1), true) + p.OnDelete(export("ws-a", "wildwest", hashA, 1)) + } + w, err := p.Watch(nil, WatchOptions{ResourceVersion: "1"}) + require.NoError(t, err) + require.Equal(t, watch.Error, next(t, w).Type) + }) + + t.Run("a selector filters the stream", func(t *testing.T) { + t.Parallel() + p := NewProjector(IsCoreExport) + w, err := p.Watch(labels.SelectorFromSet(labels.Set{adminv1alpha1.APIIdentityCoreLabel: "true"}), WatchOptions{}) + require.NoError(t, err) + t.Cleanup(w.Stop) + + p.OnAdd(export("org", "widgets", hashB, 1), false) + p.OnAdd(export("root", "tenancy.kcp.io", hashA, 1), false) + ev := next(t, w) + require.Equal(t, hashA, ev.Object.(*adminv1alpha1.APIIdentity).Name) + }) + + t.Run("stop ends the stream", func(t *testing.T) { + t.Parallel() + p := NewProjector(IsCoreExport) + w, err := p.Watch(nil, WatchOptions{}) + require.NoError(t, err) + w.Stop() + w.Stop() + _, ok := <-w.ResultChan() + require.False(t, ok) + }) +} diff --git a/pkg/reconciler/apis/apiexport/apiexport_controller_test.go b/pkg/reconciler/apis/apiexport/apiexport_controller_test.go index 5edc8083ff2..1e3d829b548 100644 --- a/pkg/reconciler/apis/apiexport/apiexport_controller_test.go +++ b/pkg/reconciler/apis/apiexport/apiexport_controller_test.go @@ -28,6 +28,7 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/util/workqueue" "github.com/kcp-dev/logicalcluster/v3" apisv1alpha1 "github.com/kcp-dev/sdk/apis/apis/v1alpha1" @@ -185,6 +186,7 @@ func TestReconcile(t *testing.T) { someOtherKey := "def" c := &controller{ + queue: workqueue.NewTypedRateLimitingQueue(workqueue.DefaultTypedControllerRateLimiter[string]()), getNamespace: func(clusterName logicalcluster.Name, name string) (*corev1.Namespace, error) { return &corev1.Namespace{}, nil }, diff --git a/pkg/reconciler/apis/apiexport/apiexport_reconcile.go b/pkg/reconciler/apis/apiexport/apiexport_reconcile.go index 43f37993cf8..ce837f620d4 100644 --- a/pkg/reconciler/apis/apiexport/apiexport_reconcile.go +++ b/pkg/reconciler/apis/apiexport/apiexport_reconcile.go @@ -89,17 +89,10 @@ func (c *controller) reconcile(ctx context.Context, apiExport *apisv1alpha2.APIE return nil } - // Ref exists - make sure it's valid - if err := c.updateOrVerifyIdentitySecretHash(ctx, clusterName, apiExport); err != nil { - conditions.MarkFalse( - apiExport, - apisv1alpha2.APIExportIdentityValid, - apisv1alpha2.IdentityVerificationFailedReason, - conditionsv1alpha1.ConditionSeverityError, - "%v", - err, - ) - } + // Ref exists - make sure it's valid. A failure is reported on the + // IdentityValid condition and is not retried: it is a wrong or missing + // secret, and the export is reconciled again when that secret changes. + c.reconcileIdentity(ctx, apiExport) // Ensure the APIExportEndpointSlice exists if _, ok := apiExport.Annotations[apisv1alpha2.APIExportEndpointSliceSkipAnnotation]; !ok { @@ -189,30 +182,6 @@ func (c *controller) createIdentitySecret(ctx context.Context, clusterName logic return c.createSecret(ctx, clusterName, secret) } -func (c *controller) updateOrVerifyIdentitySecretHash(ctx context.Context, clusterName logicalcluster.Name, apiExport *apisv1alpha2.APIExport) error { - secret, err := c.getSecret(ctx, clusterName, apiExport.Spec.Identity.SecretRef.Namespace, apiExport.Spec.Identity.SecretRef.Name) - if err != nil { - return err - } - - hash, err := identity.IdentityHash(secret) - if err != nil { - return err - } - - if apiExport.Status.IdentityHash == "" { - apiExport.Status.IdentityHash = hash - } - - if apiExport.Status.IdentityHash != hash { - return fmt.Errorf("hash mismatch: identity secret hash %q must match status.identityHash %q", hash, apiExport.Status.IdentityHash) - } - - conditions.MarkTrue(apiExport, apisv1alpha2.APIExportIdentityValid) - - return nil -} - func (c *controller) updateVirtualWorkspaceURLs(ctx context.Context, apiExport *apisv1alpha2.APIExport) error { logger := klog.FromContext(ctx) shards, err := c.listShards() diff --git a/pkg/reconciler/apis/apiexport/apiexport_reconcile_identity.go b/pkg/reconciler/apis/apiexport/apiexport_reconcile_identity.go new file mode 100644 index 00000000000..c1ca1d58ef5 --- /dev/null +++ b/pkg/reconciler/apis/apiexport/apiexport_reconcile_identity.go @@ -0,0 +1,64 @@ +/* +Copyright 2026 The kcp 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 apiexport + +import ( + "context" + "fmt" + + "github.com/kcp-dev/logicalcluster/v3" + apisv1alpha2 "github.com/kcp-dev/sdk/apis/apis/v1alpha2" + conditionsv1alpha1 "github.com/kcp-dev/sdk/apis/third_party/conditions/apis/conditions/v1alpha1" + "github.com/kcp-dev/sdk/apis/third_party/conditions/util/conditions" + + "github.com/kcp-dev/kcp/pkg/identity" +) + +// reconcileIdentity assigns the export's identity: the hash of the secret's +// identity key. Two exports whose secrets carry the same key have the same +// identity, which is how an identity is shared; who owns it and who holds it +// is derived from the exports themselves (see pkg/apiidentity), not +// recorded anywhere. +// +// Once set, status.identityHash is immutable: a secret edited to another +// key fails verification instead of changing the identity. Failures are +// reported on the IdentityValid condition. +func (c *controller) reconcileIdentity(ctx context.Context, apiExport *apisv1alpha2.APIExport) { + clusterName := logicalcluster.From(apiExport) + + secret, err := c.getSecret(ctx, clusterName, apiExport.Spec.Identity.SecretRef.Namespace, apiExport.Spec.Identity.SecretRef.Name) + if err != nil { + c.identityVerificationFailed(apiExport, err) + return + } + hash, err := identity.IdentityHash(secret) + if err != nil { + c.identityVerificationFailed(apiExport, err) + return + } + if apiExport.Status.IdentityHash != "" && apiExport.Status.IdentityHash != hash { + c.identityVerificationFailed(apiExport, fmt.Errorf("hash mismatch: identity secret hash %q must match status.identityHash %q", hash, apiExport.Status.IdentityHash)) + return + } + + apiExport.Status.IdentityHash = hash + conditions.MarkTrue(apiExport, apisv1alpha2.APIExportIdentityValid) +} + +func (c *controller) identityVerificationFailed(apiExport *apisv1alpha2.APIExport, err error) { + conditions.MarkFalse(apiExport, apisv1alpha2.APIExportIdentityValid, apisv1alpha2.IdentityVerificationFailedReason, conditionsv1alpha1.ConditionSeverityError, "%v", err) +} diff --git a/pkg/virtual/admin/authorizer/authorizer.go b/pkg/virtual/admin/authorizer/authorizer.go index a791ad6f2ff..4859cd673de 100644 --- a/pkg/virtual/admin/authorizer/authorizer.go +++ b/pkg/virtual/admin/authorizer/authorizer.go @@ -22,6 +22,8 @@ import ( "k8s.io/apiserver/pkg/authorization/authorizer" + corev1alpha1 "github.com/kcp-dev/sdk/apis/core/v1alpha1" + "github.com/kcp-dev/kcp/pkg/authorization/bootstrap" ) @@ -40,7 +42,13 @@ func NewAdminAuthorizer() authorizer.Authorizer { func (a *adminAuthorizer) Authorize(_ context.Context, attr authorizer.Attributes) (authorizer.Decision, string, error) { switch attr.GetVerb() { - case "get", "list", "watch", "update", "patch": + case "get", "list", "watch": + case "update", "patch": + // only Shard objects accept (allow-listed) writes; every other view + // is derived state and read-only. + if attr.GetAPIGroup() != corev1alpha1.SchemeGroupVersion.Group || attr.GetResource() != "shards" { + return authorizer.DecisionDeny, "resource is read-only in the admin workspace", nil + } default: return authorizer.DecisionDeny, "verb not supported by the admin workspace", nil } diff --git a/pkg/virtual/admin/authorizer/authorizer_test.go b/pkg/virtual/admin/authorizer/authorizer_test.go index 886312ae73a..00ac95f562b 100644 --- a/pkg/virtual/admin/authorizer/authorizer_test.go +++ b/pkg/virtual/admin/authorizer/authorizer_test.go @@ -32,10 +32,12 @@ func TestAdminAuthorizer(t *testing.T) { t.Parallel() for _, tc := range []struct { - name string - groups []string - verb string - want authorizer.Decision + name string + groups []string + verb string + group string // defaults to core.kcp.io + resource string // defaults to shards + want authorizer.Decision }{ { // The external administrator identity, passed through by the @@ -96,16 +98,49 @@ func TestAdminAuthorizer(t *testing.T) { verb: "delete", want: authorizer.DecisionDeny, }, + { + name: "system:kcp:admin may read the identity registry", + groups: []string{bootstrap.SystemKcpAdminGroup}, + verb: "list", + group: "admin.kcp.io", + resource: "apiidentities", + want: authorizer.DecisionAllow, + }, + { + // The registry is derived state written by kcp only; the + // allow-listed writes exist for Shards alone. + name: "system:masters may not update the identity registry", + groups: []string{bootstrap.SystemMastersGroup}, + verb: "update", + group: "admin.kcp.io", + resource: "apiidentities", + want: authorizer.DecisionDeny, + }, + { + name: "system:kcp:admin may not patch the identity registry", + groups: []string{bootstrap.SystemKcpAdminGroup}, + verb: "patch", + group: "admin.kcp.io", + resource: "apiidentities", + want: authorizer.DecisionDeny, + }, } { t.Run(tc.name, func(t *testing.T) { t.Parallel() + group, resource := tc.group, tc.resource + if group == "" { + group = "core.kcp.io" + } + if resource == "" { + resource = "shards" + } got, reason, err := NewAdminAuthorizer().Authorize(context.Background(), authorizer.AttributesRecord{ User: &user.DefaultInfo{Name: "tester", Groups: tc.groups}, Verb: tc.verb, - APIGroup: "core.kcp.io", + APIGroup: group, APIVersion: "v1alpha1", - Resource: "shards", + Resource: resource, ResourceRequest: true, }) require.NoError(t, err) diff --git a/pkg/virtual/admin/builder/apiidentities.go b/pkg/virtual/admin/builder/apiidentities.go new file mode 100644 index 00000000000..f02c82ab96d --- /dev/null +++ b/pkg/virtual/admin/builder/apiidentities.go @@ -0,0 +1,191 @@ +/* +Copyright 2026 The kcp 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 builder + +import ( + "context" + "errors" + "sync" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/watch" + genericapiserver "k8s.io/apiserver/pkg/server" + "k8s.io/utils/ptr" + + kcpdynamic "github.com/kcp-dev/client-go/dynamic" + adminv1alpha1 "github.com/kcp-dev/sdk/apis/admin/v1alpha1" + "github.com/kcp-dev/virtual-workspace-framework/pkg/dynamic/apidefinition" + "github.com/kcp-dev/virtual-workspace-framework/pkg/dynamic/apiserver" + "github.com/kcp-dev/virtual-workspace-framework/pkg/forwardingregistry" + + "github.com/kcp-dev/kcp/pkg/apiidentity" +) + +// provideAPIIdentitiesRestStorage builds the REST storage for the identity +// view: GET/LIST/WATCH computed by the projector from the APIExports +// replicated to the cache server. Nothing is stored and nothing is written: +// the view is read-only. +func provideAPIIdentitiesRestStorage( + mainConfig genericapiserver.CompletedConfig, + projector *apiidentity.Projector, +) (apidefinition.APIDefinition, error) { + ctx, cancelFn := context.WithCancel(context.Background()) + + // the view is computed; the storage never reaches for a client. + clientFunc := forwardingregistry.DynamicClusterClientFunc(func(_ context.Context) (kcpdynamic.ClusterInterface, error) { + return nil, errors.New("the identity view is computed, it has no backing client") + }) + + restProvider, err := forwardingregistry.ProvideReadOnlyRestStorage(ctx, clientFunc, projectedView(projector), nil) + if err != nil { + cancelFn() + return nil, err + } + + def, err := apiserver.CreateServingInfoFor(mainConfig, APIIdentitiesSchema, adminv1alpha1.SchemeGroupVersion.Version, restProvider) + if err != nil { + cancelFn() + return nil, err + } + + return &apiDefinitionWithCancel{ + APIDefinition: def, + cancelFn: cancelFn, + }, nil +} + +var apiIdentitiesResource = schema.GroupResource{Group: adminv1alpha1.SchemeGroupVersion.Group, Resource: "apiidentities"} + +// projectedView replaces the reads of the storage with the projector's view. +func projectedView(projector *apiidentity.Projector) forwardingregistry.StorageWrapper { + return forwardingregistry.StorageWrapperFunc(func(_ schema.GroupResource, storage *forwardingregistry.StoreFuncs) { + storage.GetterFunc = func(_ context.Context, name string, _ *metav1.GetOptions) (runtime.Object, error) { + entry, ok := projector.Get(name) + if !ok { + return nil, apierrors.NewNotFound(apiIdentitiesResource, name) + } + return toUnstructured(entry) + } + + storage.ListerFunc = func(_ context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) { + list := &unstructured.UnstructuredList{} + list.SetAPIVersion(adminv1alpha1.SchemeGroupVersion.String()) + list.SetKind("APIIdentityList") + list.SetResourceVersion(projector.ResourceVersion()) + for _, entry := range projector.List(options.LabelSelector) { + if !matchesFields(options.FieldSelector, entry) { + continue + } + obj, err := toUnstructured(entry) + if err != nil { + return nil, err + } + list.Items = append(list.Items, *obj) + } + return list, nil + } + + storage.WatcherFunc = func(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error) { + w, err := projector.Watch(options.LabelSelector, apiidentity.WatchOptions{ + ResourceVersion: options.ResourceVersion, + SendInitialEvents: ptr.Deref(options.SendInitialEvents, false), + AllowWatchBookmarks: options.AllowWatchBookmarks, + }) + if err != nil { + return nil, err + } + return newConvertingWatch(ctx, w, options.FieldSelector), nil + } + }) +} + +// matchesFields applies the one field selector the view supports, +// metadata.name. +func matchesFields(selector fields.Selector, entry *adminv1alpha1.APIIdentity) bool { + return selector == nil || selector.Empty() || selector.Matches(fields.Set{"metadata.name": entry.Name}) +} + +func toUnstructured(entry *adminv1alpha1.APIIdentity) (*unstructured.Unstructured, error) { + content, err := runtime.DefaultUnstructuredConverter.ToUnstructured(entry) + if err != nil { + return nil, err + } + return &unstructured.Unstructured{Object: content}, nil +} + +// convertingWatch forwards the projector's typed events as unstructured +// objects, which is what the storage serves, dropping those a field +// selector excludes. Bookmarks and errors pass through. +type convertingWatch struct { + delegate watch.Interface + resultChan chan watch.Event + stopOnce sync.Once +} + +func newConvertingWatch(ctx context.Context, delegate watch.Interface, selector fields.Selector) *convertingWatch { + w := &convertingWatch{ + delegate: delegate, + resultChan: make(chan watch.Event, 100), // Matches outgoingBufSize=100 in k8s.io/apiserver/pkg/storage/etcd3/watcher.go. + } + + go func() { + defer close(w.resultChan) + for { + select { + case event, ok := <-delegate.ResultChan(): + if !ok { + return + } + if entry, ok := event.Object.(*adminv1alpha1.APIIdentity); ok { + if event.Type != watch.Bookmark && !matchesFields(selector, entry) { + continue + } + obj, err := toUnstructured(entry) + if err != nil { + event = watch.Event{Type: watch.Error, Object: &apierrors.NewInternalError(err).ErrStatus} + } else { + event.Object = obj + } + } + select { + case w.resultChan <- event: + case <-ctx.Done(): + delegate.Stop() + return + } + case <-ctx.Done(): + delegate.Stop() + return + } + } + }() + return w +} + +func (w *convertingWatch) Stop() { + w.stopOnce.Do(func() { w.delegate.Stop() }) +} + +func (w *convertingWatch) ResultChan() <-chan watch.Event { + return w.resultChan +} diff --git a/pkg/virtual/admin/builder/build.go b/pkg/virtual/admin/builder/build.go index 130290a88d6..e61428b2681 100644 --- a/pkg/virtual/admin/builder/build.go +++ b/pkg/virtual/admin/builder/build.go @@ -22,12 +22,12 @@ import ( "strings" "sync" - "k8s.io/apimachinery/pkg/runtime/schema" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" genericapiserver "k8s.io/apiserver/pkg/server" kcpdynamic "github.com/kcp-dev/client-go/dynamic" "github.com/kcp-dev/logicalcluster/v3" + adminv1alpha1 "github.com/kcp-dev/sdk/apis/admin/v1alpha1" "github.com/kcp-dev/sdk/apis/core" corev1alpha1 "github.com/kcp-dev/sdk/apis/core/v1alpha1" "github.com/kcp-dev/virtual-workspace-framework/framework" @@ -36,26 +36,34 @@ import ( dynamiccontext "github.com/kcp-dev/virtual-workspace-framework/pkg/dynamic/context" "github.com/kcp-dev/virtual-workspace-framework/pkg/rootapiserver" + "github.com/kcp-dev/kcp/pkg/apiidentity" "github.com/kcp-dev/kcp/pkg/authorization" "github.com/kcp-dev/kcp/pkg/virtual/admin" adminauthorizer "github.com/kcp-dev/kcp/pkg/virtual/admin/authorizer" ) -// apiDomainKey is the single, static API domain of the shards view: the VW -// serves one fixed API set regardless of the caller. +// apiDomainKey is the single, static API domain of the Admin workspace: the +// VW serves one fixed API set regardless of the caller. const apiDomainKey = dynamiccontext.APIDomainKey(admin.VirtualWorkspaceName) -// BuildVirtualWorkspace builds the Admin workspace virtual workspace. Its -// first resource is a read-only, cache-backed aggregate view of every -// shard's Shard object. +// BuildVirtualWorkspace builds the Admin workspace virtual workspace. It +// serves two cache-backed, installation-wide views: every shard's Shard +// object, writable for the allow-listed operational fields, and the +// read-only APIIdentity registry. // // URL patterns: // // /services/admin/apis/core.kcp.io/v1alpha1/shards (plain clients, e.g. kubectl via `ws use :admin`) -// /services/admin/clusters//apis/core.kcp.io/... (cluster-aware clients; the segment is accepted and ignored) +// /services/admin/apis/admin.kcp.io/v1alpha1/apiidentities +// /services/admin/clusters//apis/... (cluster-aware clients; the segment is accepted and ignored) +// +// identitiesSynced reports whether the identity view has seen every +// APIExport the cache server holds; the workspace is not ready before. func BuildVirtualWorkspace( rootPathPrefix string, cacheDynamicClusterClient kcpdynamic.ClusterInterface, + identities *apiidentity.Projector, + identitiesSynced func() bool, ) ([]rootapiserver.NamedVirtualWorkspace, error) { if !strings.HasSuffix(rootPathPrefix, "/") { rootPathPrefix += "/" @@ -76,15 +84,19 @@ func BuildVirtualWorkspace( ReadyChecker: framework.ReadyFunc(func() error { select { case <-readyCh: - return nil default: return errors.New("admin virtual workspace is not started") } + if !identitiesSynced() { + return errors.New("the identity view has not synced yet") + } + return nil }), BootstrapAPISetManagement: func(mainConfig genericapiserver.CompletedConfig) (apidefinition.APIDefinitionSetGetter, error) { - provider := &singleResourceAPIDefinitionSetProvider{ + provider := &adminAPIDefinitionSetProvider{ config: mainConfig, cacheDynamicClusterClient: cacheDynamicClusterClient, + identities: identities, } if err := mainConfig.AddPostStartHook(admin.VirtualWorkspaceName, func(hookContext genericapiserver.PostStartHookContext) error { close(readyCh) @@ -103,7 +115,7 @@ func BuildVirtualWorkspace( // digestURL accepts /services/admin and everything below it. An optional // /clusters/ segment (added by cluster-aware clients) is parsed -// and stripped; the shards view is identical for every value. +// and stripped; the views are identical for every value. func digestURL(urlPath, rootPathPrefix string) (cluster genericapirequest.Cluster, logicalPath string, accepted bool) { if !strings.HasPrefix(urlPath, rootPathPrefix) { return genericapirequest.Cluster{}, "", false @@ -139,17 +151,18 @@ func digestURL(urlPath, rootPathPrefix string) (cluster genericapirequest.Cluste return cluster, prefix, true } -// singleResourceAPIDefinitionSetProvider serves the one fixed API of this -// virtual workspace, lazily constructing the serving info once. -type singleResourceAPIDefinitionSetProvider struct { +// adminAPIDefinitionSetProvider serves the fixed API set of this virtual +// workspace, lazily constructing the serving info once. +type adminAPIDefinitionSetProvider struct { config genericapiserver.CompletedConfig cacheDynamicClusterClient kcpdynamic.ClusterInterface + identities *apiidentity.Projector lock sync.Mutex cached apidefinition.APIDefinitionSet } -func (p *singleResourceAPIDefinitionSetProvider) GetAPIDefinitionSet(_ context.Context, key dynamiccontext.APIDomainKey) (apidefinition.APIDefinitionSet, bool, error) { +func (p *adminAPIDefinitionSetProvider) GetAPIDefinitionSet(_ context.Context, key dynamiccontext.APIDomainKey) (apidefinition.APIDefinitionSet, bool, error) { if key != apiDomainKey { return nil, false, nil } @@ -160,22 +173,24 @@ func (p *singleResourceAPIDefinitionSetProvider) GetAPIDefinitionSet(_ context.C return p.cached, true, nil } - apiDef, err := provideShardsRestStorage(p.config, p.cacheDynamicClusterClient) + shardsDef, err := provideShardsRestStorage(p.config, p.cacheDynamicClusterClient) + if err != nil { + return nil, false, err + } + apiIdentitiesDef, err := provideAPIIdentitiesRestStorage(p.config, p.identities) if err != nil { + shardsDef.TearDown() return nil, false, err } p.cached = apidefinition.APIDefinitionSet{ - schema.GroupVersionResource{ - Group: corev1alpha1.SchemeGroupVersion.Group, - Version: corev1alpha1.SchemeGroupVersion.Version, - Resource: "shards", - }: apiDef, + corev1alpha1.SchemeGroupVersion.WithResource("shards"): shardsDef, + adminv1alpha1.SchemeGroupVersion.WithResource("apiidentities"): apiIdentitiesDef, } return p.cached, true, nil } -var _ apidefinition.APIDefinitionSetGetter = &singleResourceAPIDefinitionSetProvider{} +var _ apidefinition.APIDefinitionSetGetter = &adminAPIDefinitionSetProvider{} func newAuthorizer() *authorization.Decorator { auth := adminauthorizer.NewAdminAuthorizer() diff --git a/pkg/virtual/admin/builder/build_test.go b/pkg/virtual/admin/builder/build_test.go index 816fe59c7b3..88296298268 100644 --- a/pkg/virtual/admin/builder/build_test.go +++ b/pkg/virtual/admin/builder/build_test.go @@ -24,11 +24,20 @@ import ( "github.com/stretchr/testify/require" apierrors "k8s.io/apimachinery/pkg/api/errors" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/watch" "github.com/kcp-dev/logicalcluster/v3" + adminv1alpha1 "github.com/kcp-dev/sdk/apis/admin/v1alpha1" + apisv1alpha2 "github.com/kcp-dev/sdk/apis/apis/v1alpha2" corev1alpha1 "github.com/kcp-dev/sdk/apis/core/v1alpha1" + "github.com/kcp-dev/virtual-workspace-framework/pkg/forwardingregistry" + + "github.com/kcp-dev/kcp/pkg/apiidentity" ) func TestDigestURL(t *testing.T) { @@ -44,6 +53,7 @@ func TestDigestURL(t *testing.T) { }{ {urlPath: "/services/admin", accepted: true, clusterName: "root", prefixToTrim: "/services/admin"}, {urlPath: "/services/admin/apis/core.kcp.io/v1alpha1/shards", accepted: true, clusterName: "root", prefixToTrim: "/services/admin"}, + {urlPath: "/services/admin/apis/admin.kcp.io/v1alpha1/apiidentities", accepted: true, clusterName: "root", prefixToTrim: "/services/admin"}, {urlPath: "/services/admin/clusters/*/apis/core.kcp.io/v1alpha1/shards", accepted: true, wildcard: true, prefixToTrim: "/services/admin/clusters/*"}, {urlPath: "/services/admin/clusters/myws/apis/core.kcp.io/v1alpha1/shards", accepted: true, clusterName: "myws", prefixToTrim: "/services/admin/clusters/myws"}, {urlPath: "/services/adminfoo/apis", accepted: false}, @@ -389,3 +399,48 @@ func TestMutableFieldUpdate(t *testing.T) { }) } } + +func TestProjectedViewServesTheProjector(t *testing.T) { + t.Parallel() + const hash = "abc" + projector := apiidentity.NewProjector(apiidentity.IsCoreExport) + projector.OnAdd(&apisv1alpha2.APIExport{ + ObjectMeta: metav1.ObjectMeta{Name: "widgets", Annotations: map[string]string{logicalcluster.AnnotationKey: "org"}}, + Status: apisv1alpha2.APIExportStatus{IdentityHash: hash}, + }, true) + storage := &forwardingregistry.StoreFuncs{} + projectedView(projector).Decorate(apiIdentitiesResource, storage) + ctx := context.Background() + + t.Log("get serves the entry as unstructured, unknown names are not found") + obj, err := storage.GetterFunc(ctx, hash, nil) + require.NoError(t, err) + entry := obj.(*unstructured.Unstructured) + require.Equal(t, "APIIdentity", entry.GetKind()) + require.Equal(t, "widgets", entry.GetLabels()[adminv1alpha1.APIIdentityOwnerLabel]) + _, err = storage.GetterFunc(ctx, "nope", nil) + require.True(t, apierrors.IsNotFound(err)) + + t.Log("list honours label and name selectors") + obj, err = storage.ListerFunc(ctx, &metainternalversion.ListOptions{LabelSelector: labels.SelectorFromSet(labels.Set{adminv1alpha1.APIIdentityCoreLabel: "false"})}) + require.NoError(t, err) + list := obj.(*unstructured.UnstructuredList) + require.Equal(t, "APIIdentityList", list.GetKind()) + require.Len(t, list.Items, 1) + require.Equal(t, projector.ResourceVersion(), list.GetResourceVersion()) + obj, err = storage.ListerFunc(ctx, &metainternalversion.ListOptions{FieldSelector: fields.OneTermEqualSelector("metadata.name", "other")}) + require.NoError(t, err) + require.Empty(t, obj.(*unstructured.UnstructuredList).Items) + + t.Log("watch delivers changes as unstructured") + w, err := storage.WatcherFunc(ctx, &metainternalversion.ListOptions{}) + require.NoError(t, err) + t.Cleanup(w.Stop) + projector.OnDelete(&apisv1alpha2.APIExport{ + ObjectMeta: metav1.ObjectMeta{Name: "widgets", Annotations: map[string]string{logicalcluster.AnnotationKey: "org"}}, + Status: apisv1alpha2.APIExportStatus{IdentityHash: hash}, + }) + event := <-w.ResultChan() + require.Equal(t, watch.Deleted, event.Type) + require.Equal(t, hash, event.Object.(*unstructured.Unstructured).GetName()) +} diff --git a/pkg/virtual/admin/builder/forwarding.go b/pkg/virtual/admin/builder/forwarding.go index 7c195902733..f81042346ad 100644 --- a/pkg/virtual/admin/builder/forwarding.go +++ b/pkg/virtual/admin/builder/forwarding.go @@ -169,14 +169,7 @@ func sourceContext(ctx context.Context) context.Context { // Shard object. The kcp.io/cluster annotation is kept: it tells the consumer // in which logical cluster the authoritative object lives. func fixupShard(obj *unstructured.Unstructured) { - annotations := obj.GetAnnotations() - if annotations == nil { - return - } - delete(annotations, shard.AnnotationKey) - delete(annotations, replication.AnnotationKeyOriginalResourceUID) - delete(annotations, replication.AnnotationKeyOriginalResourceVersion) - obj.SetAnnotations(annotations) + stripCacheBookkeeping(obj) } // stripField removes the field at the given path from the object, pruning @@ -646,3 +639,17 @@ func (w *fixupWatch) Stop() { func (w *fixupWatch) ResultChan() <-chan watch.Event { return w.resultChan } + +// stripCacheBookkeeping removes the cache-server bookkeeping annotations from +// an object read from the cache. The kcp.io/cluster annotation is kept: it +// tells the consumer which logical cluster the object belongs to. +func stripCacheBookkeeping(obj *unstructured.Unstructured) { + annotations := obj.GetAnnotations() + if annotations == nil { + return + } + delete(annotations, shard.AnnotationKey) + delete(annotations, replication.AnnotationKeyOriginalResourceUID) + delete(annotations, replication.AnnotationKeyOriginalResourceVersion) + obj.SetAnnotations(annotations) +} diff --git a/pkg/virtual/admin/builder/schema.go b/pkg/virtual/admin/builder/schema.go index 0f0e2ba75ec..81c5d45a098 100644 --- a/pkg/virtual/admin/builder/schema.go +++ b/pkg/virtual/admin/builder/schema.go @@ -30,14 +30,25 @@ import ( // derived from the embedded CRD manifest. var ShardsSchema *apisv1alpha1.APIResourceSchema +// APIIdentitiesSchema is the APIResourceSchema for admin.kcp.io/v1alpha1 +// APIIdentities, derived from the embedded CRD manifest. +var APIIdentitiesSchema *apisv1alpha1.APIResourceSchema + func init() { + ShardsSchema = mustSchema("core.kcp.io_shards.yaml") + APIIdentitiesSchema = mustSchema("admin.kcp.io_apiidentities.yaml") +} + +// mustSchema converts the named embedded CRD manifest into an +// APIResourceSchema. +func mustSchema(fileName string) *apisv1alpha1.APIResourceSchema { crd := apiextensionsv1.CustomResourceDefinition{} - if err := configcrds.Unmarshal("core.kcp.io_shards.yaml", &crd); err != nil { - panic(fmt.Sprintf("failed to unmarshal shards CRD: %v", err)) + if err := configcrds.Unmarshal(fileName, &crd); err != nil { + panic(fmt.Sprintf("failed to unmarshal CRD %s: %v", fileName, err)) } schema, err := apisv1alpha1.CRDToAPIResourceSchema(&crd, "crd") if err != nil { panic(fmt.Sprintf("failed to convert CRD %s.%s to APIResourceSchema: %v", crd.Spec.Names.Plural, crd.Spec.Group, err)) } - ShardsSchema = schema + return schema } diff --git a/pkg/virtual/admin/options/options.go b/pkg/virtual/admin/options/options.go index b0c68df3325..612de41a17c 100644 --- a/pkg/virtual/admin/options/options.go +++ b/pkg/virtual/admin/options/options.go @@ -24,8 +24,10 @@ import ( "k8s.io/client-go/rest" kcpdynamic "github.com/kcp-dev/client-go/dynamic" + kcpinformers "github.com/kcp-dev/sdk/client/informers/externalversions" "github.com/kcp-dev/virtual-workspace-framework/pkg/rootapiserver" + "github.com/kcp-dev/kcp/pkg/apiidentity" "github.com/kcp-dev/kcp/pkg/virtual/admin" "github.com/kcp-dev/kcp/pkg/virtual/admin/builder" ) @@ -47,6 +49,7 @@ func (o *Admin) NewAdmin( rootPathPrefix string, _ *rest.Config, cacheConfig *rest.Config, + cachedKcpInformers kcpinformers.SharedInformerFactory, ) ([]rootapiserver.NamedVirtualWorkspace, error) { // We assume the cacheConfig already has the cache-related roundtrippers applied. cacheDynamicClusterClient, err := kcpdynamic.NewForConfig(cacheConfig) @@ -54,8 +57,18 @@ func (o *Admin) NewAdmin( return nil, err } + // the identity view follows the APIExports of every shard, replicated + // to the cache server. + identities := apiidentity.NewProjector(apiidentity.IsCoreExport) + registration, err := cachedKcpInformers.Apis().V1alpha2().APIExports().Informer().AddEventHandler(identities) + if err != nil { + return nil, err + } + return builder.BuildVirtualWorkspace( path.Join(rootPathPrefix, admin.VirtualWorkspaceName), cacheDynamicClusterClient, + identities, + registration.HasSynced, ) } diff --git a/pkg/virtual/options/options.go b/pkg/virtual/options/options.go index 7d1a74c8e23..ac6212ccefe 100644 --- a/pkg/virtual/options/options.go +++ b/pkg/virtual/options/options.go @@ -121,6 +121,7 @@ func (o *Options) NewVirtualWorkspaces( rootPathPrefix, config, cacheConfig, + cachedKcpInformers, ) if err != nil { return nil, err diff --git a/staging/src/github.com/kcp-dev/sdk/apis/admin/register.go b/staging/src/github.com/kcp-dev/sdk/apis/admin/register.go new file mode 100644 index 00000000000..92b28ad3259 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/apis/admin/register.go @@ -0,0 +1,25 @@ +/* +Copyright 2026 The kcp 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 admin holds the installation-wide administrative APIs of kcp: +// objects that are owned by kcp itself, live on the cache server and are +// served read-only through the Admin workspace. +package admin + +const ( + // GroupName is the API group of the administrative APIs. + GroupName = "admin.kcp.io" +) diff --git a/staging/src/github.com/kcp-dev/sdk/apis/admin/v1alpha1/doc.go b/staging/src/github.com/kcp-dev/sdk/apis/admin/v1alpha1/doc.go new file mode 100644 index 00000000000..aad70b185bb --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/apis/admin/v1alpha1/doc.go @@ -0,0 +1,23 @@ +/* +Copyright 2026 The kcp 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. +*/ + +// +k8s:deepcopy-gen=package,register +// +groupName=admin.kcp.io +// +k8s:openapi-gen=true +// +k8s:openapi-model-package=com.github.kcp-dev.sdk.apis.admin.v1alpha1 + +// Package v1alpha1 contains the v1alpha1 version of the administrative APIs. +package v1alpha1 diff --git a/staging/src/github.com/kcp-dev/sdk/apis/admin/v1alpha1/register.go b/staging/src/github.com/kcp-dev/sdk/apis/admin/v1alpha1/register.go new file mode 100644 index 00000000000..6a3ae3137ee --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/apis/admin/v1alpha1/register.go @@ -0,0 +1,52 @@ +/* +Copyright 2026 The kcp 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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/kcp-dev/sdk/apis/admin" +) + +// SchemeGroupVersion is group version used to register these objects. +var SchemeGroupVersion = schema.GroupVersion{Group: admin.GroupName, Version: "v1alpha1"} + +// Kind takes an unqualified kind and returns back a Group qualified GroupKind. +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource. +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &APIIdentity{}, + &APIIdentityList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/staging/src/github.com/kcp-dev/sdk/apis/admin/v1alpha1/types_apiidentity.go b/staging/src/github.com/kcp-dev/sdk/apis/admin/v1alpha1/types_apiidentity.go new file mode 100644 index 00000000000..301e59ced11 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/apis/admin/v1alpha1/types_apiidentity.go @@ -0,0 +1,168 @@ +/* +Copyright 2026 The kcp 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 v1alpha1 + +import ( + "strconv" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + conditionsv1alpha1 "github.com/kcp-dev/sdk/apis/third_party/conditions/apis/conditions/v1alpha1" + "github.com/kcp-dev/sdk/apis/third_party/conditions/util/conditions" +) + +// APIIdentity is the view of one API identity: which APIExport owns it and +// which other APIExports hold it. It is computed from the APIExports of the +// installation and not stored anywhere; there is one per identity, named +// after the identity value, served read-only through the Admin workspace. +// +// +crd +// +genclient +// +genclient:nonNamespaced +// +genclient:noStatus +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Cluster,categories=kcp +// +kubebuilder:printcolumn:name="Owner",type=string,JSONPath=`.status.owner.name`,description="APIExport owning the identity" +// +kubebuilder:printcolumn:name="Cluster",type=string,JSONPath=`.status.owner.cluster`,description="Logical cluster of the owner" +// +kubebuilder:printcolumn:name="Core",type=boolean,JSONPath=`.status.core`,description="Identity of a core APIExport in root" +// +kubebuilder:printcolumn:name="Shared",type=string,JSONPath=`.status.conditions[?(@.type=="Shared")].status`,priority=1,description="True while other APIExports hold the identity" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +type APIIdentity struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + // +optional + Status APIIdentityStatus `json:"status,omitempty"` +} + +// ExportReference identifies an APIExport across the installation. +type ExportReference struct { + // cluster is the logical cluster name hosting the APIExport. + // + // +required + // +kubebuilder:validation:MinLength=1 + Cluster string `json:"cluster"` + + // name is the name of the APIExport. + // + // +required + // +kubebuilder:validation:MinLength=1 + Name string `json:"name"` + + // uid is the UID of the APIExport, so that an export deleted and + // recreated under the same name can be told apart from the original. + // + // +optional + UID string `json:"uid,omitempty"` +} + +// Equal reports whether two references name the same APIExport, ignoring +// the UID: a recreated export under the same name in the same cluster is +// the same export for the purpose of holding an identity. +func (r ExportReference) Equal(o ExportReference) bool { + return r.Cluster == o.Cluster && r.Name == o.Name +} + +// Holder is an APIExport, other than the owner, that serves the identity. +type Holder struct { + ExportReference `json:",inline"` + + // since is when the holder was created. + // + // +optional + Since metav1.Time `json:"since,omitempty"` +} + +// APIIdentityStatus is the derived state of an identity. The object has no +// spec: nothing about it is user intent. +type APIIdentityStatus struct { + // owner is the APIExport that holds the identity authoritatively: the + // oldest one holding it. When it is deleted the oldest holder succeeds + // it; an identity no APIExport holds any more has no entry. + // + // +required + Owner ExportReference `json:"owner"` + + // holders lists the APIExports, other than the owner, that serve the + // identity: those whose identity secret carries the same key, oldest + // first. + // + // +optional + // +listType=map + // +listMapKey=cluster + // +listMapKey=name + Holders []Holder `json:"holders,omitempty"` + + // core is true for the identities of the core APIExports in the root + // workspace, which every shard needs before it can serve. + // + // +optional + Core bool `json:"core,omitempty"` + + // conditions is a list of conditions that apply to the identity. + // + // +optional + Conditions conditionsv1alpha1.Conditions `json:"conditions,omitempty"` +} + +const ( + // APIIdentityOwnerLabel is the name of the APIExport that owns the + // identity, so that entries can be selected by export name. + APIIdentityOwnerLabel = "apiidentities.admin.kcp.io/owner" + // APIIdentityOwnerClusterLabel is the logical cluster of the APIExport + // that owns the identity, so that entries can be selected by workspace. + APIIdentityOwnerClusterLabel = "apiidentities.admin.kcp.io/owner-cluster" + // APIIdentityCoreLabel is "true" on the identities of the core + // APIExports in root and "false" on every other one. + APIIdentityCoreLabel = "apiidentities.admin.kcp.io/core" +) + +// Labels returns the labels an entry carries for its owner and core flag. +func Labels(owner ExportReference, core bool) map[string]string { + return map[string]string{ + APIIdentityOwnerLabel: owner.Name, + APIIdentityOwnerClusterLabel: owner.Cluster, + APIIdentityCoreLabel: strconv.FormatBool(core), + } +} + +const ( + // APIIdentityShared is True while holders is non-empty. + APIIdentityShared conditionsv1alpha1.ConditionType = "Shared" +) + +func (in *APIIdentity) SetConditions(c conditionsv1alpha1.Conditions) { + in.Status.Conditions = c +} + +func (in *APIIdentity) GetConditions() conditionsv1alpha1.Conditions { + return in.Status.Conditions +} + +var _ conditions.Getter = &APIIdentity{} +var _ conditions.Setter = &APIIdentity{} + +// APIIdentityList is a list of APIIdentity resources. +// +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type APIIdentityList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []APIIdentity `json:"items"` +} diff --git a/staging/src/github.com/kcp-dev/sdk/apis/admin/v1alpha1/zz_generated.deepcopy.go b/staging/src/github.com/kcp-dev/sdk/apis/admin/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..727494ddeb3 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/apis/admin/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,153 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright The kcp 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" + + conditionsv1alpha1 "github.com/kcp-dev/sdk/apis/third_party/conditions/apis/conditions/v1alpha1" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *APIIdentity) DeepCopyInto(out *APIIdentity) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIIdentity. +func (in *APIIdentity) DeepCopy() *APIIdentity { + if in == nil { + return nil + } + out := new(APIIdentity) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *APIIdentity) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *APIIdentityList) DeepCopyInto(out *APIIdentityList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]APIIdentity, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIIdentityList. +func (in *APIIdentityList) DeepCopy() *APIIdentityList { + if in == nil { + return nil + } + out := new(APIIdentityList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *APIIdentityList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *APIIdentityStatus) DeepCopyInto(out *APIIdentityStatus) { + *out = *in + out.Owner = in.Owner + if in.Holders != nil { + in, out := &in.Holders, &out.Holders + *out = make([]Holder, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make(conditionsv1alpha1.Conditions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIIdentityStatus. +func (in *APIIdentityStatus) DeepCopy() *APIIdentityStatus { + if in == nil { + return nil + } + out := new(APIIdentityStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExportReference) DeepCopyInto(out *ExportReference) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExportReference. +func (in *ExportReference) DeepCopy() *ExportReference { + if in == nil { + return nil + } + out := new(ExportReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Holder) DeepCopyInto(out *Holder) { + *out = *in + out.ExportReference = in.ExportReference + in.Since.DeepCopyInto(&out.Since) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Holder. +func (in *Holder) DeepCopy() *Holder { + if in == nil { + return nil + } + out := new(Holder) + in.DeepCopyInto(out) + return out +} diff --git a/staging/src/github.com/kcp-dev/sdk/apis/admin/v1alpha1/zz_generated.model_name.go b/staging/src/github.com/kcp-dev/sdk/apis/admin/v1alpha1/zz_generated.model_name.go new file mode 100644 index 00000000000..8b958ee5f41 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/apis/admin/v1alpha1/zz_generated.model_name.go @@ -0,0 +1,47 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright The kcp 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. +*/ + +// Code generated by openapi-gen. DO NOT EDIT. + +package v1alpha1 + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in APIIdentity) OpenAPIModelName() string { + return "com.github.kcp-dev.sdk.apis.admin.v1alpha1.APIIdentity" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in APIIdentityList) OpenAPIModelName() string { + return "com.github.kcp-dev.sdk.apis.admin.v1alpha1.APIIdentityList" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in APIIdentityStatus) OpenAPIModelName() string { + return "com.github.kcp-dev.sdk.apis.admin.v1alpha1.APIIdentityStatus" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ExportReference) OpenAPIModelName() string { + return "com.github.kcp-dev.sdk.apis.admin.v1alpha1.ExportReference" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Holder) OpenAPIModelName() string { + return "com.github.kcp-dev.sdk.apis.admin.v1alpha1.Holder" +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/admin/v1alpha1/apiidentity.go b/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/admin/v1alpha1/apiidentity.go new file mode 100644 index 00000000000..33fa8909294 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/admin/v1alpha1/apiidentity.go @@ -0,0 +1,239 @@ +/* +Copyright The kcp 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + + v1 "github.com/kcp-dev/sdk/client/applyconfiguration/meta/v1" +) + +// APIIdentityApplyConfiguration represents a declarative configuration of the APIIdentity type for use +// with apply. +// +// APIIdentity is the view of one API identity: which APIExport owns it and +// which other APIExports hold it. It is computed from the APIExports of the +// installation and not stored anywhere; there is one per identity, named +// after the identity value, served read-only through the Admin workspace. +type APIIdentityApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Status *APIIdentityStatusApplyConfiguration `json:"status,omitempty"` +} + +// APIIdentity constructs a declarative configuration of the APIIdentity type for use with +// apply. +func APIIdentity(name string) *APIIdentityApplyConfiguration { + b := &APIIdentityApplyConfiguration{} + b.WithName(name) + b.WithKind("APIIdentity") + b.WithAPIVersion("admin.kcp.io/v1alpha1") + return b +} + +func (b APIIdentityApplyConfiguration) IsApplyConfiguration() {} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *APIIdentityApplyConfiguration) WithKind(value string) *APIIdentityApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *APIIdentityApplyConfiguration) WithAPIVersion(value string) *APIIdentityApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *APIIdentityApplyConfiguration) WithName(value string) *APIIdentityApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *APIIdentityApplyConfiguration) WithGenerateName(value string) *APIIdentityApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *APIIdentityApplyConfiguration) WithNamespace(value string) *APIIdentityApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *APIIdentityApplyConfiguration) WithUID(value types.UID) *APIIdentityApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *APIIdentityApplyConfiguration) WithResourceVersion(value string) *APIIdentityApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *APIIdentityApplyConfiguration) WithGeneration(value int64) *APIIdentityApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *APIIdentityApplyConfiguration) WithCreationTimestamp(value metav1.Time) *APIIdentityApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *APIIdentityApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *APIIdentityApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *APIIdentityApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *APIIdentityApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *APIIdentityApplyConfiguration) WithLabels(entries map[string]string) *APIIdentityApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *APIIdentityApplyConfiguration) WithAnnotations(entries map[string]string) *APIIdentityApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *APIIdentityApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *APIIdentityApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *APIIdentityApplyConfiguration) WithFinalizers(values ...string) *APIIdentityApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *APIIdentityApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *APIIdentityApplyConfiguration) WithStatus(value *APIIdentityStatusApplyConfiguration) *APIIdentityApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *APIIdentityApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *APIIdentityApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *APIIdentityApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *APIIdentityApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/admin/v1alpha1/apiidentitystatus.go b/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/admin/v1alpha1/apiidentitystatus.go new file mode 100644 index 00000000000..a60faf3bb58 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/admin/v1alpha1/apiidentitystatus.go @@ -0,0 +1,87 @@ +/* +Copyright The kcp 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + conditionsv1alpha1 "github.com/kcp-dev/sdk/apis/third_party/conditions/apis/conditions/v1alpha1" +) + +// APIIdentityStatusApplyConfiguration represents a declarative configuration of the APIIdentityStatus type for use +// with apply. +// +// APIIdentityStatus is the derived state of an identity. The object has no +// spec: nothing about it is user intent. +type APIIdentityStatusApplyConfiguration struct { + // owner is the APIExport that holds the identity authoritatively: the + // oldest one holding it. When it is deleted the oldest holder succeeds + // it; an identity no APIExport holds any more has no entry. + Owner *ExportReferenceApplyConfiguration `json:"owner,omitempty"` + // holders lists the APIExports, other than the owner, that serve the + // identity: those whose identity secret carries the same key, oldest + // first. + Holders []HolderApplyConfiguration `json:"holders,omitempty"` + // core is true for the identities of the core APIExports in the root + // workspace, which every shard needs before it can serve. + Core *bool `json:"core,omitempty"` + // conditions is a list of conditions that apply to the identity. + Conditions *conditionsv1alpha1.Conditions `json:"conditions,omitempty"` +} + +// APIIdentityStatusApplyConfiguration constructs a declarative configuration of the APIIdentityStatus type for use with +// apply. +func APIIdentityStatus() *APIIdentityStatusApplyConfiguration { + return &APIIdentityStatusApplyConfiguration{} +} + +// WithOwner sets the Owner field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Owner field is set to the value of the last call. +func (b *APIIdentityStatusApplyConfiguration) WithOwner(value *ExportReferenceApplyConfiguration) *APIIdentityStatusApplyConfiguration { + b.Owner = value + return b +} + +// WithHolders adds the given value to the Holders field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Holders field. +func (b *APIIdentityStatusApplyConfiguration) WithHolders(values ...*HolderApplyConfiguration) *APIIdentityStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithHolders") + } + b.Holders = append(b.Holders, *values[i]) + } + return b +} + +// WithCore sets the Core field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Core field is set to the value of the last call. +func (b *APIIdentityStatusApplyConfiguration) WithCore(value bool) *APIIdentityStatusApplyConfiguration { + b.Core = &value + return b +} + +// WithConditions sets the Conditions field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Conditions field is set to the value of the last call. +func (b *APIIdentityStatusApplyConfiguration) WithConditions(value conditionsv1alpha1.Conditions) *APIIdentityStatusApplyConfiguration { + b.Conditions = &value + return b +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/admin/v1alpha1/exportreference.go b/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/admin/v1alpha1/exportreference.go new file mode 100644 index 00000000000..3df81c5b9e0 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/admin/v1alpha1/exportreference.go @@ -0,0 +1,63 @@ +/* +Copyright The kcp 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// ExportReferenceApplyConfiguration represents a declarative configuration of the ExportReference type for use +// with apply. +// +// ExportReference identifies an APIExport across the installation. +type ExportReferenceApplyConfiguration struct { + // cluster is the logical cluster name hosting the APIExport. + Cluster *string `json:"cluster,omitempty"` + // name is the name of the APIExport. + Name *string `json:"name,omitempty"` + // uid is the UID of the APIExport, so that an export deleted and + // recreated under the same name can be told apart from the original. + UID *string `json:"uid,omitempty"` +} + +// ExportReferenceApplyConfiguration constructs a declarative configuration of the ExportReference type for use with +// apply. +func ExportReference() *ExportReferenceApplyConfiguration { + return &ExportReferenceApplyConfiguration{} +} + +// WithCluster sets the Cluster field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Cluster field is set to the value of the last call. +func (b *ExportReferenceApplyConfiguration) WithCluster(value string) *ExportReferenceApplyConfiguration { + b.Cluster = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *ExportReferenceApplyConfiguration) WithName(value string) *ExportReferenceApplyConfiguration { + b.Name = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *ExportReferenceApplyConfiguration) WithUID(value string) *ExportReferenceApplyConfiguration { + b.UID = &value + return b +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/admin/v1alpha1/holder.go b/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/admin/v1alpha1/holder.go new file mode 100644 index 00000000000..6b7017405a6 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/admin/v1alpha1/holder.go @@ -0,0 +1,71 @@ +/* +Copyright The kcp 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// HolderApplyConfiguration represents a declarative configuration of the Holder type for use +// with apply. +// +// Holder is an APIExport, other than the owner, that serves the identity. +type HolderApplyConfiguration struct { + ExportReferenceApplyConfiguration `json:",inline"` + // since is when the holder was created. + Since *v1.Time `json:"since,omitempty"` +} + +// HolderApplyConfiguration constructs a declarative configuration of the Holder type for use with +// apply. +func Holder() *HolderApplyConfiguration { + return &HolderApplyConfiguration{} +} + +// WithCluster sets the Cluster field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Cluster field is set to the value of the last call. +func (b *HolderApplyConfiguration) WithCluster(value string) *HolderApplyConfiguration { + b.ExportReferenceApplyConfiguration.Cluster = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *HolderApplyConfiguration) WithName(value string) *HolderApplyConfiguration { + b.ExportReferenceApplyConfiguration.Name = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *HolderApplyConfiguration) WithUID(value string) *HolderApplyConfiguration { + b.ExportReferenceApplyConfiguration.UID = &value + return b +} + +// WithSince sets the Since field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Since field is set to the value of the last call. +func (b *HolderApplyConfiguration) WithSince(value v1.Time) *HolderApplyConfiguration { + b.Since = &value + return b +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/utils.go b/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/utils.go index 4a0ea5ba332..da7b18fbaa1 100644 --- a/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/utils.go +++ b/staging/src/github.com/kcp-dev/sdk/client/applyconfiguration/utils.go @@ -25,7 +25,8 @@ import ( managedfields "k8s.io/apimachinery/pkg/util/managedfields" metav1 "k8s.io/client-go/applyconfigurations/meta/v1" - v1alpha1 "github.com/kcp-dev/sdk/apis/apis/v1alpha1" + v1alpha1 "github.com/kcp-dev/sdk/apis/admin/v1alpha1" + apisv1alpha1 "github.com/kcp-dev/sdk/apis/apis/v1alpha1" v1alpha2 "github.com/kcp-dev/sdk/apis/apis/v1alpha2" cachev1alpha1 "github.com/kcp-dev/sdk/apis/cache/v1alpha1" corev1alpha1 "github.com/kcp-dev/sdk/apis/core/v1alpha1" @@ -33,7 +34,8 @@ import ( tenancyv1alpha1 "github.com/kcp-dev/sdk/apis/tenancy/v1alpha1" conditionsv1alpha1 "github.com/kcp-dev/sdk/apis/third_party/conditions/apis/conditions/v1alpha1" topologyv1alpha1 "github.com/kcp-dev/sdk/apis/topology/v1alpha1" - apisv1alpha1 "github.com/kcp-dev/sdk/client/applyconfiguration/apis/v1alpha1" + adminv1alpha1 "github.com/kcp-dev/sdk/client/applyconfiguration/admin/v1alpha1" + applyconfigurationapisv1alpha1 "github.com/kcp-dev/sdk/client/applyconfiguration/apis/v1alpha1" apisv1alpha2 "github.com/kcp-dev/sdk/client/applyconfiguration/apis/v1alpha2" applyconfigurationcachev1alpha1 "github.com/kcp-dev/sdk/client/applyconfiguration/cache/v1alpha1" applyconfigurationconditionsv1alpha1 "github.com/kcp-dev/sdk/client/applyconfiguration/conditions/v1alpha1" @@ -49,67 +51,77 @@ import ( // apply configuration type exists for the given GroupVersionKind. func ForKind(kind schema.GroupVersionKind) interface{} { switch kind { - // Group=apis.kcp.io, Version=v1alpha1 - case v1alpha1.SchemeGroupVersion.WithKind("AcceptablePermissionClaim"): - return &apisv1alpha1.AcceptablePermissionClaimApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("APIBinding"): - return &apisv1alpha1.APIBindingApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("APIBindingSpec"): - return &apisv1alpha1.APIBindingSpecApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("APIBindingStatus"): - return &apisv1alpha1.APIBindingStatusApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("APIConversion"): - return &apisv1alpha1.APIConversionApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("APIConversionRule"): - return &apisv1alpha1.APIConversionRuleApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("APIConversionSpec"): - return &apisv1alpha1.APIConversionSpecApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("APIExport"): - return &apisv1alpha1.APIExportApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("APIExportEndpointSlice"): - return &apisv1alpha1.APIExportEndpointSliceApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("APIExportEndpointSliceSpec"): - return &apisv1alpha1.APIExportEndpointSliceSpecApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("APIExportEndpointSliceStatus"): - return &apisv1alpha1.APIExportEndpointSliceStatusApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("APIExportSpec"): - return &apisv1alpha1.APIExportSpecApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("APIExportStatus"): - return &apisv1alpha1.APIExportStatusApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("APIResourceSchema"): - return &apisv1alpha1.APIResourceSchemaApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("APIResourceSchemaSpec"): - return &apisv1alpha1.APIResourceSchemaSpecApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("APIResourceVersion"): - return &apisv1alpha1.APIResourceVersionApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("APIVersionConversion"): - return &apisv1alpha1.APIVersionConversionApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("BindingReference"): - return &apisv1alpha1.BindingReferenceApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("BoundAPIResource"): - return &apisv1alpha1.BoundAPIResourceApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("BoundAPIResourceSchema"): - return &apisv1alpha1.BoundAPIResourceSchemaApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("CustomResourceConversion"): - return &apisv1alpha1.CustomResourceConversionApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("ExportBindingReference"): - return &apisv1alpha1.ExportBindingReferenceApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("GroupResource"): - return &apisv1alpha1.GroupResourceApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("Identity"): - return &apisv1alpha1.IdentityApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("MaximalPermissionPolicy"): - return &apisv1alpha1.MaximalPermissionPolicyApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("PermissionClaim"): - return &apisv1alpha1.PermissionClaimApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("ResourceSelector"): - return &apisv1alpha1.ResourceSelectorApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("VirtualWorkspace"): - return &apisv1alpha1.VirtualWorkspaceApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("WebhookClientConfig"): - return &apisv1alpha1.WebhookClientConfigApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("WebhookConversion"): - return &apisv1alpha1.WebhookConversionApplyConfiguration{} + // Group=admin.kcp.io, Version=v1alpha1 + case v1alpha1.SchemeGroupVersion.WithKind("APIIdentity"): + return &adminv1alpha1.APIIdentityApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("APIIdentityStatus"): + return &adminv1alpha1.APIIdentityStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ExportReference"): + return &adminv1alpha1.ExportReferenceApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("Holder"): + return &adminv1alpha1.HolderApplyConfiguration{} + + // Group=apis.kcp.io, Version=v1alpha1 + case apisv1alpha1.SchemeGroupVersion.WithKind("AcceptablePermissionClaim"): + return &applyconfigurationapisv1alpha1.AcceptablePermissionClaimApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("APIBinding"): + return &applyconfigurationapisv1alpha1.APIBindingApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("APIBindingSpec"): + return &applyconfigurationapisv1alpha1.APIBindingSpecApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("APIBindingStatus"): + return &applyconfigurationapisv1alpha1.APIBindingStatusApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("APIConversion"): + return &applyconfigurationapisv1alpha1.APIConversionApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("APIConversionRule"): + return &applyconfigurationapisv1alpha1.APIConversionRuleApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("APIConversionSpec"): + return &applyconfigurationapisv1alpha1.APIConversionSpecApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("APIExport"): + return &applyconfigurationapisv1alpha1.APIExportApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("APIExportEndpointSlice"): + return &applyconfigurationapisv1alpha1.APIExportEndpointSliceApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("APIExportEndpointSliceSpec"): + return &applyconfigurationapisv1alpha1.APIExportEndpointSliceSpecApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("APIExportEndpointSliceStatus"): + return &applyconfigurationapisv1alpha1.APIExportEndpointSliceStatusApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("APIExportSpec"): + return &applyconfigurationapisv1alpha1.APIExportSpecApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("APIExportStatus"): + return &applyconfigurationapisv1alpha1.APIExportStatusApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("APIResourceSchema"): + return &applyconfigurationapisv1alpha1.APIResourceSchemaApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("APIResourceSchemaSpec"): + return &applyconfigurationapisv1alpha1.APIResourceSchemaSpecApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("APIResourceVersion"): + return &applyconfigurationapisv1alpha1.APIResourceVersionApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("APIVersionConversion"): + return &applyconfigurationapisv1alpha1.APIVersionConversionApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("BindingReference"): + return &applyconfigurationapisv1alpha1.BindingReferenceApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("BoundAPIResource"): + return &applyconfigurationapisv1alpha1.BoundAPIResourceApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("BoundAPIResourceSchema"): + return &applyconfigurationapisv1alpha1.BoundAPIResourceSchemaApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("CustomResourceConversion"): + return &applyconfigurationapisv1alpha1.CustomResourceConversionApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("ExportBindingReference"): + return &applyconfigurationapisv1alpha1.ExportBindingReferenceApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("GroupResource"): + return &applyconfigurationapisv1alpha1.GroupResourceApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("Identity"): + return &applyconfigurationapisv1alpha1.IdentityApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("MaximalPermissionPolicy"): + return &applyconfigurationapisv1alpha1.MaximalPermissionPolicyApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("PermissionClaim"): + return &applyconfigurationapisv1alpha1.PermissionClaimApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("ResourceSelector"): + return &applyconfigurationapisv1alpha1.ResourceSelectorApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("VirtualWorkspace"): + return &applyconfigurationapisv1alpha1.VirtualWorkspaceApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("WebhookClientConfig"): + return &applyconfigurationapisv1alpha1.WebhookClientConfigApplyConfiguration{} + case apisv1alpha1.SchemeGroupVersion.WithKind("WebhookConversion"): + return &applyconfigurationapisv1alpha1.WebhookConversionApplyConfiguration{} // Group=apis.kcp.io, Version=v1alpha2 case v1alpha2.SchemeGroupVersion.WithKind("AcceptablePermissionClaim"): diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/clientset.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/clientset.go index dd061114ab8..5820e7e93a6 100644 --- a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/clientset.go +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/clientset.go @@ -26,6 +26,7 @@ import ( rest "k8s.io/client-go/rest" flowcontrol "k8s.io/client-go/util/flowcontrol" + adminv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1" apisv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/apis/v1alpha1" apisv1alpha2 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/apis/v1alpha2" cachev1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/cache/v1alpha1" @@ -37,6 +38,7 @@ import ( type Interface interface { Discovery() discovery.DiscoveryInterface + AdminV1alpha1() adminv1alpha1.AdminV1alpha1Interface ApisV1alpha1() apisv1alpha1.ApisV1alpha1Interface ApisV1alpha2() apisv1alpha2.ApisV1alpha2Interface CacheV1alpha1() cachev1alpha1.CacheV1alpha1Interface @@ -49,6 +51,7 @@ type Interface interface { // Clientset contains the clients for groups. type Clientset struct { *discovery.DiscoveryClient + adminV1alpha1 *adminv1alpha1.AdminV1alpha1Client apisV1alpha1 *apisv1alpha1.ApisV1alpha1Client apisV1alpha2 *apisv1alpha2.ApisV1alpha2Client cacheV1alpha1 *cachev1alpha1.CacheV1alpha1Client @@ -58,6 +61,11 @@ type Clientset struct { topologyV1alpha1 *topologyv1alpha1.TopologyV1alpha1Client } +// AdminV1alpha1 retrieves the AdminV1alpha1Client +func (c *Clientset) AdminV1alpha1() adminv1alpha1.AdminV1alpha1Interface { + return c.adminV1alpha1 +} + // ApisV1alpha1 retrieves the ApisV1alpha1Client func (c *Clientset) ApisV1alpha1() apisv1alpha1.ApisV1alpha1Interface { return c.apisV1alpha1 @@ -137,6 +145,10 @@ func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, var cs Clientset var err error + cs.adminV1alpha1, err = adminv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) + if err != nil { + return nil, err + } cs.apisV1alpha1, err = apisv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) if err != nil { return nil, err @@ -186,6 +198,7 @@ func NewForConfigOrDie(c *rest.Config) *Clientset { // New creates a new Clientset for the given RESTClient. func New(c rest.Interface) *Clientset { var cs Clientset + cs.adminV1alpha1 = adminv1alpha1.New(c) cs.apisV1alpha1 = apisv1alpha1.New(c) cs.apisV1alpha2 = apisv1alpha2.New(c) cs.cacheV1alpha1 = cachev1alpha1.New(c) diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/clientset.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/clientset.go index f79a81cf9b6..06ff600e51d 100644 --- a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/clientset.go +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/clientset.go @@ -29,6 +29,7 @@ import ( kcpclient "github.com/kcp-dev/apimachinery/v2/pkg/client" "github.com/kcp-dev/logicalcluster/v3" client "github.com/kcp-dev/sdk/client/clientset/versioned" + adminv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1" apisv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/apis/v1alpha1" apisv1alpha2 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/apis/v1alpha2" cachev1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/cache/v1alpha1" @@ -41,6 +42,7 @@ import ( type ClusterInterface interface { Cluster(logicalcluster.Path) client.Interface Discovery() discovery.DiscoveryInterface + AdminV1alpha1() adminv1alpha1.AdminV1alpha1ClusterInterface ApisV1alpha1() apisv1alpha1.ApisV1alpha1ClusterInterface ApisV1alpha2() apisv1alpha2.ApisV1alpha2ClusterInterface CacheV1alpha1() cachev1alpha1.CacheV1alpha1ClusterInterface @@ -54,6 +56,7 @@ type ClusterInterface interface { type ClusterClientset struct { *discovery.DiscoveryClient clientCache kcpclient.Cache[*client.Clientset] + adminV1alpha1 *adminv1alpha1.AdminV1alpha1ClusterClient apisV1alpha1 *apisv1alpha1.ApisV1alpha1ClusterClient apisV1alpha2 *apisv1alpha2.ApisV1alpha2ClusterClient cacheV1alpha1 *cachev1alpha1.CacheV1alpha1ClusterClient @@ -71,6 +74,11 @@ func (c *ClusterClientset) Discovery() discovery.DiscoveryInterface { return c.DiscoveryClient } +// AdminV1alpha1 retrieves the AdminV1alpha1ClusterClient. +func (c *ClusterClientset) AdminV1alpha1() adminv1alpha1.AdminV1alpha1ClusterInterface { + return c.adminV1alpha1 +} + // ApisV1alpha1 retrieves the ApisV1alpha1ClusterClient. func (c *ClusterClientset) ApisV1alpha1() apisv1alpha1.ApisV1alpha1ClusterInterface { return c.apisV1alpha1 @@ -158,6 +166,10 @@ func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*ClusterCli var cs ClusterClientset cs.clientCache = cache var err error + cs.adminV1alpha1, err = adminv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) + if err != nil { + return nil, err + } cs.apisV1alpha1, err = apisv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) if err != nil { return nil, err @@ -207,6 +219,7 @@ func NewForConfigOrDie(c *rest.Config) *ClusterClientset { // New creates a new ClusterClientset for the given RESTClient. func New(c *rest.Config) *ClusterClientset { var cs ClusterClientset + cs.adminV1alpha1 = adminv1alpha1.NewForConfigOrDie(c) cs.apisV1alpha1 = apisv1alpha1.NewForConfigOrDie(c) cs.apisV1alpha2 = apisv1alpha2.NewForConfigOrDie(c) cs.cacheV1alpha1 = cachev1alpha1.NewForConfigOrDie(c) diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/fake/clientset.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/fake/clientset.go index 9b1532c35e9..319a4e7f732 100644 --- a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/fake/clientset.go +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/fake/clientset.go @@ -29,6 +29,8 @@ import ( clientset "github.com/kcp-dev/sdk/client/clientset/versioned" kcpclientset "github.com/kcp-dev/sdk/client/clientset/versioned/cluster" kcpclientscheme "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/scheme" + kcpadminv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1" + kcpfakeadminv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1/fake" kcpapisv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/apis/v1alpha1" kcpfakeapisv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/apis/v1alpha1/fake" kcpapisv1alpha2 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/apis/v1alpha2" @@ -43,6 +45,7 @@ import ( kcpfaketenancyv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/tenancy/v1alpha1/fake" kcptopologyv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/topology/v1alpha1" kcpfaketopologyv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/topology/v1alpha1/fake" + adminv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1" apisv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/apis/v1alpha1" apisv1alpha2 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/apis/v1alpha2" cachev1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/cache/v1alpha1" @@ -125,6 +128,11 @@ func (c *ClusterClientset) Cluster(clusterPath logicalcluster.Path) clientset.In } } +// AdminV1alpha1 retrieves the AdminV1alpha1ClusterClient +func (c *ClusterClientset) AdminV1alpha1() kcpadminv1alpha1.AdminV1alpha1ClusterInterface { + return &kcpfakeadminv1alpha1.AdminV1alpha1ClusterClient{Fake: &c.Fake} +} + // ApisV1alpha1 retrieves the ApisV1alpha1ClusterClient func (c *ClusterClientset) ApisV1alpha1() kcpapisv1alpha1.ApisV1alpha1ClusterInterface { return &kcpfakeapisv1alpha1.ApisV1alpha1ClusterClient{Fake: &c.Fake} @@ -203,6 +211,11 @@ func NewClientset(objects ...runtime.Object) *ClusterClientset { return cs } +// AdminV1alpha1 retrieves the AdminV1alpha1Client +func (c *Clientset) AdminV1alpha1() adminv1alpha1.AdminV1alpha1Interface { + return &kcpfakeadminv1alpha1.AdminV1alpha1Client{Fake: c.Fake, ClusterPath: c.clusterPath} +} + // ApisV1alpha1 retrieves the ApisV1alpha1Client func (c *Clientset) ApisV1alpha1() apisv1alpha1.ApisV1alpha1Interface { return &kcpfakeapisv1alpha1.ApisV1alpha1Client{Fake: c.Fake, ClusterPath: c.clusterPath} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/fake/register.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/fake/register.go index 9701248653e..522d106f7f2 100644 --- a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/fake/register.go +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/fake/register.go @@ -25,6 +25,7 @@ import ( serializer "k8s.io/apimachinery/pkg/runtime/serializer" utilruntime "k8s.io/apimachinery/pkg/util/runtime" + adminv1alpha1 "github.com/kcp-dev/sdk/apis/admin/v1alpha1" apisv1alpha1 "github.com/kcp-dev/sdk/apis/apis/v1alpha1" apisv1alpha2 "github.com/kcp-dev/sdk/apis/apis/v1alpha2" cachev1alpha1 "github.com/kcp-dev/sdk/apis/cache/v1alpha1" @@ -38,6 +39,7 @@ var scheme = runtime.NewScheme() var codecs = serializer.NewCodecFactory(scheme) var localSchemeBuilder = runtime.SchemeBuilder{ + adminv1alpha1.AddToScheme, apisv1alpha1.AddToScheme, apisv1alpha2.AddToScheme, cachev1alpha1.AddToScheme, diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/scheme/register.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/scheme/register.go index f2981eeee15..6aab37902f8 100644 --- a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/scheme/register.go +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/scheme/register.go @@ -25,6 +25,7 @@ import ( serializer "k8s.io/apimachinery/pkg/runtime/serializer" utilruntime "k8s.io/apimachinery/pkg/util/runtime" + adminv1alpha1 "github.com/kcp-dev/sdk/apis/admin/v1alpha1" apisv1alpha1 "github.com/kcp-dev/sdk/apis/apis/v1alpha1" apisv1alpha2 "github.com/kcp-dev/sdk/apis/apis/v1alpha2" cachev1alpha1 "github.com/kcp-dev/sdk/apis/cache/v1alpha1" @@ -38,6 +39,7 @@ var Scheme = runtime.NewScheme() var Codecs = serializer.NewCodecFactory(Scheme) var ParameterCodec = runtime.NewParameterCodec(Scheme) var localSchemeBuilder = runtime.SchemeBuilder{ + adminv1alpha1.AddToScheme, apisv1alpha1.AddToScheme, apisv1alpha2.AddToScheme, cachev1alpha1.AddToScheme, diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1/admin_client.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1/admin_client.go new file mode 100644 index 00000000000..29ce8f8bf9d --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1/admin_client.go @@ -0,0 +1,103 @@ +/* +Copyright The kcp 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. +*/ + +// Code generated by cluster-client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + http "net/http" + + rest "k8s.io/client-go/rest" + + kcpclient "github.com/kcp-dev/apimachinery/v2/pkg/client" + "github.com/kcp-dev/logicalcluster/v3" + kcpadminv1alpha1 "github.com/kcp-dev/sdk/apis/admin/v1alpha1" + kcpscheme "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/scheme" + kcpv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1" +) + +type AdminV1alpha1ClusterInterface interface { + AdminV1alpha1ClusterScoper + APIIdentitiesClusterGetter +} + +type AdminV1alpha1ClusterScoper interface { + Cluster(logicalcluster.Path) kcpv1alpha1.AdminV1alpha1Interface +} + +// AdminV1alpha1ClusterClient is used to interact with features provided by the admin.kcp.io group. +type AdminV1alpha1ClusterClient struct { + clientCache kcpclient.Cache[*kcpv1alpha1.AdminV1alpha1Client] +} + +func (c *AdminV1alpha1ClusterClient) Cluster(clusterPath logicalcluster.Path) kcpv1alpha1.AdminV1alpha1Interface { + if clusterPath == logicalcluster.Wildcard { + panic("A specific cluster must be provided when scoping, not the wildcard.") + } + return c.clientCache.ClusterOrDie(clusterPath) +} + +func (c *AdminV1alpha1ClusterClient) APIIdentities() APIIdentityClusterInterface { + return &aPIIdentitiesClusterInterface{clientCache: c.clientCache} +} + +// NewForConfig creates a new AdminV1alpha1ClusterClient for the given config. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *rest.Config) (*AdminV1alpha1ClusterClient, error) { + config := *c + setConfigDefaults(&config) + httpClient, err := rest.HTTPClientFor(&config) + if err != nil { + return nil, err + } + return NewForConfigAndClient(&config, httpClient) +} + +// NewForConfigAndClient creates a new AdminV1alpha1ClusterClient for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +func NewForConfigAndClient(c *rest.Config, h *http.Client) (*AdminV1alpha1ClusterClient, error) { + cache := kcpclient.NewCache(c, h, &kcpclient.Constructor[*kcpv1alpha1.AdminV1alpha1Client]{ + NewForConfigAndClient: kcpv1alpha1.NewForConfigAndClient, + }) + if _, err := cache.Cluster(logicalcluster.Name("root").Path()); err != nil { + return nil, err + } + + return &AdminV1alpha1ClusterClient{clientCache: cache}, nil +} + +// NewForConfigOrDie creates a new AdminV1alpha1ClusterClient for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *AdminV1alpha1ClusterClient { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +func setConfigDefaults(config *rest.Config) { + gv := kcpadminv1alpha1.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(kcpscheme.Scheme, kcpscheme.Codecs).WithoutConversion() + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1/apiidentity.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1/apiidentity.go new file mode 100644 index 00000000000..acf4436530d --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1/apiidentity.go @@ -0,0 +1,69 @@ +/* +Copyright The kcp 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. +*/ + +// Code generated by cluster-client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + watch "k8s.io/apimachinery/pkg/watch" + + kcpclient "github.com/kcp-dev/apimachinery/v2/pkg/client" + "github.com/kcp-dev/logicalcluster/v3" + kcpadminv1alpha1 "github.com/kcp-dev/sdk/apis/admin/v1alpha1" + kcpv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1" +) + +// APIIdentitiesClusterGetter has a method to return a APIIdentityClusterInterface. +// A group's cluster client should implement this interface. +type APIIdentitiesClusterGetter interface { + APIIdentities() APIIdentityClusterInterface +} + +// APIIdentityClusterInterface can operate on APIIdentities across all clusters, +// or scope down to one cluster and return a kcpv1alpha1.APIIdentityInterface. +type APIIdentityClusterInterface interface { + Cluster(logicalcluster.Path) kcpv1alpha1.APIIdentityInterface + List(ctx context.Context, opts v1.ListOptions) (*kcpadminv1alpha1.APIIdentityList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + APIIdentityClusterExpansion +} + +type aPIIdentitiesClusterInterface struct { + clientCache kcpclient.Cache[*kcpv1alpha1.AdminV1alpha1Client] +} + +// Cluster scopes the client down to a particular cluster. +func (c *aPIIdentitiesClusterInterface) Cluster(clusterPath logicalcluster.Path) kcpv1alpha1.APIIdentityInterface { + if clusterPath == logicalcluster.Wildcard { + panic("A specific cluster must be provided when scoping, not the wildcard.") + } + + return c.clientCache.ClusterOrDie(clusterPath).APIIdentities() +} + +// List returns the entire collection of all APIIdentities across all clusters. +func (c *aPIIdentitiesClusterInterface) List(ctx context.Context, opts v1.ListOptions) (*kcpadminv1alpha1.APIIdentityList, error) { + return c.clientCache.ClusterOrDie(logicalcluster.Wildcard).APIIdentities().List(ctx, opts) +} + +// Watch begins to watch all APIIdentities across all clusters. +func (c *aPIIdentitiesClusterInterface) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.clientCache.ClusterOrDie(logicalcluster.Wildcard).APIIdentities().Watch(ctx, opts) +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1/doc.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1/doc.go new file mode 100644 index 00000000000..3e873237924 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1/doc.go @@ -0,0 +1,20 @@ +/* +Copyright The kcp 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. +*/ + +// Code generated by cluster-client-gen. DO NOT EDIT. + +// This package has the automatically generated typed clients. +package v1alpha1 diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1/fake/admin_client.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1/fake/admin_client.go new file mode 100644 index 00000000000..7099f5e3f27 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1/fake/admin_client.go @@ -0,0 +1,61 @@ +/* +Copyright The kcp 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. +*/ + +// Code generated by cluster-client-gen. DO NOT EDIT. + +package fake + +import ( + rest "k8s.io/client-go/rest" + + kcptesting "github.com/kcp-dev/client-go/third_party/k8s.io/client-go/testing" + "github.com/kcp-dev/logicalcluster/v3" + kcpadminv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1" + adminv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1" +) + +var _ kcpadminv1alpha1.AdminV1alpha1ClusterInterface = (*AdminV1alpha1ClusterClient)(nil) + +type AdminV1alpha1ClusterClient struct { + *kcptesting.Fake +} + +func (c *AdminV1alpha1ClusterClient) Cluster(clusterPath logicalcluster.Path) adminv1alpha1.AdminV1alpha1Interface { + if clusterPath == logicalcluster.Wildcard { + panic("A specific cluster must be provided when scoping, not the wildcard.") + } + return &AdminV1alpha1Client{Fake: c.Fake, ClusterPath: clusterPath} +} + +func (c *AdminV1alpha1ClusterClient) APIIdentities() kcpadminv1alpha1.APIIdentityClusterInterface { + return newFakeAPIIdentityClusterClient(c) +} + +type AdminV1alpha1Client struct { + *kcptesting.Fake + ClusterPath logicalcluster.Path +} + +func (c *AdminV1alpha1Client) APIIdentities() adminv1alpha1.APIIdentityInterface { + return newFakeAPIIdentityClient(c.Fake, c.ClusterPath) +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *AdminV1alpha1Client) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1/fake/apiidentity.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1/fake/apiidentity.go new file mode 100644 index 00000000000..938faacb49f --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1/fake/apiidentity.go @@ -0,0 +1,89 @@ +/* +Copyright The kcp 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. +*/ + +// Code generated by cluster-client-gen. DO NOT EDIT. + +package fake + +import ( + kcpgentype "github.com/kcp-dev/client-go/third_party/k8s.io/client-go/gentype" + kcptesting "github.com/kcp-dev/client-go/third_party/k8s.io/client-go/testing" + "github.com/kcp-dev/logicalcluster/v3" + adminv1alpha1 "github.com/kcp-dev/sdk/apis/admin/v1alpha1" + kcpv1alpha1 "github.com/kcp-dev/sdk/client/applyconfiguration/admin/v1alpha1" + typedkcpadminv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1" + typedadminv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1" +) + +// aPIIdentityClusterClient implements APIIdentityClusterInterface +type aPIIdentityClusterClient struct { + *kcpgentype.FakeClusterClientWithList[*adminv1alpha1.APIIdentity, *adminv1alpha1.APIIdentityList] + Fake *kcptesting.Fake +} + +func newFakeAPIIdentityClusterClient(fake *AdminV1alpha1ClusterClient) typedkcpadminv1alpha1.APIIdentityClusterInterface { + return &aPIIdentityClusterClient{ + kcpgentype.NewFakeClusterClientWithList[*adminv1alpha1.APIIdentity, *adminv1alpha1.APIIdentityList]( + fake.Fake, + adminv1alpha1.SchemeGroupVersion.WithResource("apiidentities"), + adminv1alpha1.SchemeGroupVersion.WithKind("APIIdentity"), + func() *adminv1alpha1.APIIdentity { return &adminv1alpha1.APIIdentity{} }, + func() *adminv1alpha1.APIIdentityList { return &adminv1alpha1.APIIdentityList{} }, + func(dst, src *adminv1alpha1.APIIdentityList) { dst.ListMeta = src.ListMeta }, + func(list *adminv1alpha1.APIIdentityList) []*adminv1alpha1.APIIdentity { + return kcpgentype.ToPointerSlice(list.Items) + }, + func(list *adminv1alpha1.APIIdentityList, items []*adminv1alpha1.APIIdentity) { + list.Items = kcpgentype.FromPointerSlice(items) + }, + ), + fake.Fake, + } +} + +func (c *aPIIdentityClusterClient) Cluster(cluster logicalcluster.Path) typedadminv1alpha1.APIIdentityInterface { + return newFakeAPIIdentityClient(c.Fake, cluster) +} + +// aPIIdentityScopedClient implements APIIdentityInterface +type aPIIdentityScopedClient struct { + *kcpgentype.FakeClientWithListAndApply[*adminv1alpha1.APIIdentity, *adminv1alpha1.APIIdentityList, *kcpv1alpha1.APIIdentityApplyConfiguration] + Fake *kcptesting.Fake + ClusterPath logicalcluster.Path +} + +func newFakeAPIIdentityClient(fake *kcptesting.Fake, clusterPath logicalcluster.Path) typedadminv1alpha1.APIIdentityInterface { + return &aPIIdentityScopedClient{ + kcpgentype.NewFakeClientWithListAndApply[*adminv1alpha1.APIIdentity, *adminv1alpha1.APIIdentityList, *kcpv1alpha1.APIIdentityApplyConfiguration]( + fake, + clusterPath, + "", + adminv1alpha1.SchemeGroupVersion.WithResource("apiidentities"), + adminv1alpha1.SchemeGroupVersion.WithKind("APIIdentity"), + func() *adminv1alpha1.APIIdentity { return &adminv1alpha1.APIIdentity{} }, + func() *adminv1alpha1.APIIdentityList { return &adminv1alpha1.APIIdentityList{} }, + func(dst, src *adminv1alpha1.APIIdentityList) { dst.ListMeta = src.ListMeta }, + func(list *adminv1alpha1.APIIdentityList) []*adminv1alpha1.APIIdentity { + return kcpgentype.ToPointerSlice(list.Items) + }, + func(list *adminv1alpha1.APIIdentityList, items []*adminv1alpha1.APIIdentity) { + list.Items = kcpgentype.FromPointerSlice(items) + }, + ), + fake, + clusterPath, + } +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1/fake/doc.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1/fake/doc.go new file mode 100644 index 00000000000..100cd63c05b --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1/fake/doc.go @@ -0,0 +1,20 @@ +/* +Copyright The kcp 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. +*/ + +// Code generated by cluster-client-gen. DO NOT EDIT. + +// Package fake has the automatically generated cluster clients. +package fake diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1/generated_expansion.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1/generated_expansion.go new file mode 100644 index 00000000000..28f295ee6c9 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/cluster/typed/admin/v1alpha1/generated_expansion.go @@ -0,0 +1,21 @@ +/* +Copyright The kcp 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. +*/ + +// Code generated by cluster-client-gen. DO NOT EDIT. + +package v1alpha1 + +type APIIdentityClusterExpansion interface{} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/fake/clientset_generated.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/fake/clientset_generated.go index f5e771300df..dd5b93c8692 100644 --- a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/fake/clientset_generated.go +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/fake/clientset_generated.go @@ -28,6 +28,8 @@ import ( applyconfiguration "github.com/kcp-dev/sdk/client/applyconfiguration" clientset "github.com/kcp-dev/sdk/client/clientset/versioned" + adminv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1" + fakeadminv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1/fake" apisv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/apis/v1alpha1" fakeapisv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/apis/v1alpha1/fake" apisv1alpha2 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/apis/v1alpha2" @@ -149,6 +151,11 @@ var ( _ testing.FakeClient = &Clientset{} ) +// AdminV1alpha1 retrieves the AdminV1alpha1Client +func (c *Clientset) AdminV1alpha1() adminv1alpha1.AdminV1alpha1Interface { + return &fakeadminv1alpha1.FakeAdminV1alpha1{Fake: &c.Fake} +} + // ApisV1alpha1 retrieves the ApisV1alpha1Client func (c *Clientset) ApisV1alpha1() apisv1alpha1.ApisV1alpha1Interface { return &fakeapisv1alpha1.FakeApisV1alpha1{Fake: &c.Fake} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/fake/register.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/fake/register.go index 4656aa8f03a..5de56d6c012 100644 --- a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/fake/register.go +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/fake/register.go @@ -25,6 +25,7 @@ import ( serializer "k8s.io/apimachinery/pkg/runtime/serializer" utilruntime "k8s.io/apimachinery/pkg/util/runtime" + adminv1alpha1 "github.com/kcp-dev/sdk/apis/admin/v1alpha1" apisv1alpha1 "github.com/kcp-dev/sdk/apis/apis/v1alpha1" apisv1alpha2 "github.com/kcp-dev/sdk/apis/apis/v1alpha2" cachev1alpha1 "github.com/kcp-dev/sdk/apis/cache/v1alpha1" @@ -38,6 +39,7 @@ var scheme = runtime.NewScheme() var codecs = serializer.NewCodecFactory(scheme) var localSchemeBuilder = runtime.SchemeBuilder{ + adminv1alpha1.AddToScheme, apisv1alpha1.AddToScheme, apisv1alpha2.AddToScheme, cachev1alpha1.AddToScheme, diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/scheme/register.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/scheme/register.go index f86bd546c4f..29cd396f5b0 100644 --- a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/scheme/register.go +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/scheme/register.go @@ -25,6 +25,7 @@ import ( serializer "k8s.io/apimachinery/pkg/runtime/serializer" utilruntime "k8s.io/apimachinery/pkg/util/runtime" + adminv1alpha1 "github.com/kcp-dev/sdk/apis/admin/v1alpha1" apisv1alpha1 "github.com/kcp-dev/sdk/apis/apis/v1alpha1" apisv1alpha2 "github.com/kcp-dev/sdk/apis/apis/v1alpha2" cachev1alpha1 "github.com/kcp-dev/sdk/apis/cache/v1alpha1" @@ -38,6 +39,7 @@ var Scheme = runtime.NewScheme() var Codecs = serializer.NewCodecFactory(Scheme) var ParameterCodec = runtime.NewParameterCodec(Scheme) var localSchemeBuilder = runtime.SchemeBuilder{ + adminv1alpha1.AddToScheme, apisv1alpha1.AddToScheme, apisv1alpha2.AddToScheme, cachev1alpha1.AddToScheme, diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1/admin_client.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1/admin_client.go new file mode 100644 index 00000000000..81b4e59acf3 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1/admin_client.go @@ -0,0 +1,102 @@ +/* +Copyright The kcp 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + http "net/http" + + rest "k8s.io/client-go/rest" + + adminv1alpha1 "github.com/kcp-dev/sdk/apis/admin/v1alpha1" + scheme "github.com/kcp-dev/sdk/client/clientset/versioned/scheme" +) + +type AdminV1alpha1Interface interface { + RESTClient() rest.Interface + APIIdentitiesGetter +} + +// AdminV1alpha1Client is used to interact with features provided by the admin.kcp.io group. +type AdminV1alpha1Client struct { + restClient rest.Interface +} + +func (c *AdminV1alpha1Client) APIIdentities() APIIdentityInterface { + return newAPIIdentities(c) +} + +// NewForConfig creates a new AdminV1alpha1Client for the given config. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *rest.Config) (*AdminV1alpha1Client, error) { + config := *c + setConfigDefaults(&config) + httpClient, err := rest.HTTPClientFor(&config) + if err != nil { + return nil, err + } + return NewForConfigAndClient(&config, httpClient) +} + +// NewForConfigAndClient creates a new AdminV1alpha1Client for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +func NewForConfigAndClient(c *rest.Config, h *http.Client) (*AdminV1alpha1Client, error) { + config := *c + setConfigDefaults(&config) + client, err := rest.RESTClientForConfigAndClient(&config, h) + if err != nil { + return nil, err + } + return &AdminV1alpha1Client{client}, nil +} + +// NewForConfigOrDie creates a new AdminV1alpha1Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *AdminV1alpha1Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new AdminV1alpha1Client for the given RESTClient. +func New(c rest.Interface) *AdminV1alpha1Client { + return &AdminV1alpha1Client{c} +} + +func setConfigDefaults(config *rest.Config) { + gv := adminv1alpha1.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion() + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *AdminV1alpha1Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1/apiidentity.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1/apiidentity.go new file mode 100644 index 00000000000..94f8de2db16 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1/apiidentity.go @@ -0,0 +1,71 @@ +/* +Copyright The kcp 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + gentype "k8s.io/client-go/gentype" + + adminv1alpha1 "github.com/kcp-dev/sdk/apis/admin/v1alpha1" + applyconfigurationadminv1alpha1 "github.com/kcp-dev/sdk/client/applyconfiguration/admin/v1alpha1" + scheme "github.com/kcp-dev/sdk/client/clientset/versioned/scheme" +) + +// APIIdentitiesGetter has a method to return a APIIdentityInterface. +// A group's client should implement this interface. +type APIIdentitiesGetter interface { + APIIdentities() APIIdentityInterface +} + +// APIIdentityInterface has methods to work with APIIdentity resources. +type APIIdentityInterface interface { + Create(ctx context.Context, aPIIdentity *adminv1alpha1.APIIdentity, opts v1.CreateOptions) (*adminv1alpha1.APIIdentity, error) + Update(ctx context.Context, aPIIdentity *adminv1alpha1.APIIdentity, opts v1.UpdateOptions) (*adminv1alpha1.APIIdentity, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*adminv1alpha1.APIIdentity, error) + List(ctx context.Context, opts v1.ListOptions) (*adminv1alpha1.APIIdentityList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *adminv1alpha1.APIIdentity, err error) + Apply(ctx context.Context, aPIIdentity *applyconfigurationadminv1alpha1.APIIdentityApplyConfiguration, opts v1.ApplyOptions) (result *adminv1alpha1.APIIdentity, err error) + APIIdentityExpansion +} + +// aPIIdentities implements APIIdentityInterface +type aPIIdentities struct { + *gentype.ClientWithListAndApply[*adminv1alpha1.APIIdentity, *adminv1alpha1.APIIdentityList, *applyconfigurationadminv1alpha1.APIIdentityApplyConfiguration] +} + +// newAPIIdentities returns a APIIdentities +func newAPIIdentities(c *AdminV1alpha1Client) *aPIIdentities { + return &aPIIdentities{ + gentype.NewClientWithListAndApply[*adminv1alpha1.APIIdentity, *adminv1alpha1.APIIdentityList, *applyconfigurationadminv1alpha1.APIIdentityApplyConfiguration]( + "apiidentities", + c.RESTClient(), + scheme.ParameterCodec, + "", + func() *adminv1alpha1.APIIdentity { return &adminv1alpha1.APIIdentity{} }, + func() *adminv1alpha1.APIIdentityList { return &adminv1alpha1.APIIdentityList{} }, + ), + } +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1/doc.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1/doc.go new file mode 100644 index 00000000000..2be316615e0 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1/doc.go @@ -0,0 +1,20 @@ +/* +Copyright The kcp 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated typed clients. +package v1alpha1 diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1/fake/doc.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1/fake/doc.go new file mode 100644 index 00000000000..59e4de8efe6 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1/fake/doc.go @@ -0,0 +1,20 @@ +/* +Copyright The kcp 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// Package fake has the automatically generated clients. +package fake diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1/fake/fake_admin_client.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1/fake/fake_admin_client.go new file mode 100644 index 00000000000..a455f34dbf4 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1/fake/fake_admin_client.go @@ -0,0 +1,41 @@ +/* +Copyright The kcp 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" + + v1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1" +) + +type FakeAdminV1alpha1 struct { + *testing.Fake +} + +func (c *FakeAdminV1alpha1) APIIdentities() v1alpha1.APIIdentityInterface { + return newFakeAPIIdentities(c) +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeAdminV1alpha1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1/fake/fake_apiidentity.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1/fake/fake_apiidentity.go new file mode 100644 index 00000000000..33f59b4539d --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1/fake/fake_apiidentity.go @@ -0,0 +1,54 @@ +/* +Copyright The kcp 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + gentype "k8s.io/client-go/gentype" + + v1alpha1 "github.com/kcp-dev/sdk/apis/admin/v1alpha1" + adminv1alpha1 "github.com/kcp-dev/sdk/client/applyconfiguration/admin/v1alpha1" + typedadminv1alpha1 "github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1" +) + +// fakeAPIIdentities implements APIIdentityInterface +type fakeAPIIdentities struct { + *gentype.FakeClientWithListAndApply[*v1alpha1.APIIdentity, *v1alpha1.APIIdentityList, *adminv1alpha1.APIIdentityApplyConfiguration] + Fake *FakeAdminV1alpha1 +} + +func newFakeAPIIdentities(fake *FakeAdminV1alpha1) typedadminv1alpha1.APIIdentityInterface { + return &fakeAPIIdentities{ + gentype.NewFakeClientWithListAndApply[*v1alpha1.APIIdentity, *v1alpha1.APIIdentityList, *adminv1alpha1.APIIdentityApplyConfiguration]( + fake.Fake, + "", + v1alpha1.SchemeGroupVersion.WithResource("apiidentities"), + v1alpha1.SchemeGroupVersion.WithKind("APIIdentity"), + func() *v1alpha1.APIIdentity { return &v1alpha1.APIIdentity{} }, + func() *v1alpha1.APIIdentityList { return &v1alpha1.APIIdentityList{} }, + func(dst, src *v1alpha1.APIIdentityList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.APIIdentityList) []*v1alpha1.APIIdentity { + return gentype.ToPointerSlice(list.Items) + }, + func(list *v1alpha1.APIIdentityList, items []*v1alpha1.APIIdentity) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1/generated_expansion.go b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1/generated_expansion.go new file mode 100644 index 00000000000..eca9ba68999 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/clientset/versioned/typed/admin/v1alpha1/generated_expansion.go @@ -0,0 +1,21 @@ +/* +Copyright The kcp 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +type APIIdentityExpansion interface{} diff --git a/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/admin/interface.go b/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/admin/interface.go new file mode 100644 index 00000000000..256c725c2cb --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/admin/interface.go @@ -0,0 +1,67 @@ +/* +Copyright The kcp 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. +*/ + +// Code generated by cluster-informer-gen. DO NOT EDIT. + +package admin + +import ( + kcpv1alpha1 "github.com/kcp-dev/sdk/client/informers/externalversions/admin/v1alpha1" + kcpinternalinterfaces "github.com/kcp-dev/sdk/client/informers/externalversions/internalinterfaces" +) + +// ClusterInterface provides access to each of this group's versions. +type ClusterInterface interface { + // V1alpha1 provides access to shared informers for resources in V1alpha1. + V1alpha1() kcpv1alpha1.ClusterInterface +} + +type group struct { + factory kcpinternalinterfaces.SharedInformerFactory + tweakListOptions kcpinternalinterfaces.TweakListOptionsFunc +} + +// New returns a new ClusterInterface. +func New(f kcpinternalinterfaces.SharedInformerFactory, tweakListOptions kcpinternalinterfaces.TweakListOptionsFunc) ClusterInterface { + return &group{factory: f, tweakListOptions: tweakListOptions} +} + +// V1alpha1 returns a new kcpv1alpha1.ClusterInterface. +func (g *group) V1alpha1() kcpv1alpha1.ClusterInterface { + return kcpv1alpha1.New(g.factory, g.tweakListOptions) +} + +// Interface provides access to each of this group's versions. +type Interface interface { + // V1alpha1 provides access to shared informers for resources in V1alpha1. + V1alpha1() kcpv1alpha1.Interface +} + +type scopedGroup struct { + factory kcpinternalinterfaces.SharedScopedInformerFactory + tweakListOptions kcpinternalinterfaces.TweakListOptionsFunc + namespace string +} + +// New returns a new Interface. +func NewScoped(f kcpinternalinterfaces.SharedScopedInformerFactory, namespace string, tweakListOptions kcpinternalinterfaces.TweakListOptionsFunc) Interface { + return &scopedGroup{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// V1alpha1 returns a new kcpv1alpha1.Interface. +func (g *scopedGroup) V1alpha1() kcpv1alpha1.Interface { + return kcpv1alpha1.NewScoped(g.factory, g.namespace, g.tweakListOptions) +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/admin/v1alpha1/apiidentity.go b/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/admin/v1alpha1/apiidentity.go new file mode 100644 index 00000000000..685af6ab226 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/admin/v1alpha1/apiidentity.go @@ -0,0 +1,182 @@ +/* +Copyright The kcp 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. +*/ + +// Code generated by cluster-informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + time "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" + + kcpcache "github.com/kcp-dev/apimachinery/v2/pkg/cache" + kcpinformers "github.com/kcp-dev/apimachinery/v2/third_party/informers" + logicalcluster "github.com/kcp-dev/logicalcluster/v3" + kcpadminv1alpha1 "github.com/kcp-dev/sdk/apis/admin/v1alpha1" + kcpversioned "github.com/kcp-dev/sdk/client/clientset/versioned" + kcpcluster "github.com/kcp-dev/sdk/client/clientset/versioned/cluster" + kcpinternalinterfaces "github.com/kcp-dev/sdk/client/informers/externalversions/internalinterfaces" + kcpv1alpha1 "github.com/kcp-dev/sdk/client/listers/admin/v1alpha1" +) + +// APIIdentityClusterInformer provides access to a shared informer and lister for +// APIIdentities. +type APIIdentityClusterInformer interface { + Cluster(logicalcluster.Name) APIIdentityInformer + ClusterWithContext(context.Context, logicalcluster.Name) APIIdentityInformer + Informer() kcpcache.ScopeableSharedIndexInformer + Lister() kcpv1alpha1.APIIdentityClusterLister +} + +type aPIIdentityClusterInformer struct { + factory kcpinternalinterfaces.SharedInformerFactory + tweakListOptions kcpinternalinterfaces.TweakListOptionsFunc +} + +// NewAPIIdentityClusterInformer constructs a new informer for APIIdentity type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewAPIIdentityClusterInformer(client kcpcluster.ClusterInterface, resyncPeriod time.Duration, indexers cache.Indexers) kcpcache.ScopeableSharedIndexInformer { + return NewFilteredAPIIdentityClusterInformer(client, resyncPeriod, indexers, nil) +} + +// NewFilteredAPIIdentityClusterInformer constructs a new informer for APIIdentity type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredAPIIdentityClusterInformer(client kcpcluster.ClusterInterface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions kcpinternalinterfaces.TweakListOptionsFunc) kcpcache.ScopeableSharedIndexInformer { + return kcpinformers.NewSharedIndexInformer( + cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.AdminV1alpha1().APIIdentities().List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.AdminV1alpha1().APIIdentities().Watch(context.Background(), options) + }, + }, client), + &kcpadminv1alpha1.APIIdentity{}, + resyncPeriod, + indexers, + ) +} + +func (i *aPIIdentityClusterInformer) defaultInformer(client kcpcluster.ClusterInterface, resyncPeriod time.Duration) kcpcache.ScopeableSharedIndexInformer { + return NewFilteredAPIIdentityClusterInformer(client, resyncPeriod, cache.Indexers{ + kcpcache.ClusterIndexName: kcpcache.ClusterIndexFunc, + kcpcache.ClusterAndNamespaceIndexName: kcpcache.ClusterAndNamespaceIndexFunc, + }, i.tweakListOptions) +} + +func (i *aPIIdentityClusterInformer) Informer() kcpcache.ScopeableSharedIndexInformer { + return i.factory.InformerFor(&kcpadminv1alpha1.APIIdentity{}, i.defaultInformer) +} + +func (i *aPIIdentityClusterInformer) Lister() kcpv1alpha1.APIIdentityClusterLister { + return kcpv1alpha1.NewAPIIdentityClusterLister(i.Informer().GetIndexer()) +} + +func (i *aPIIdentityClusterInformer) Cluster(clusterName logicalcluster.Name) APIIdentityInformer { + return &aPIIdentityInformer{ + informer: i.Informer().Cluster(clusterName), + lister: i.Lister().Cluster(clusterName), + } +} + +func (i *aPIIdentityClusterInformer) ClusterWithContext(ctx context.Context, clusterName logicalcluster.Name) APIIdentityInformer { + return &aPIIdentityInformer{ + informer: i.Informer().ClusterWithContext(ctx, clusterName), + lister: i.Lister().Cluster(clusterName), + } +} + +type aPIIdentityInformer struct { + informer cache.SharedIndexInformer + lister kcpv1alpha1.APIIdentityLister +} + +func (i *aPIIdentityInformer) Informer() cache.SharedIndexInformer { + return i.informer +} + +func (i *aPIIdentityInformer) Lister() kcpv1alpha1.APIIdentityLister { + return i.lister +} + +// APIIdentityInformer provides access to a shared informer and lister for +// APIIdentities. +type APIIdentityInformer interface { + Informer() cache.SharedIndexInformer + Lister() kcpv1alpha1.APIIdentityLister +} + +type aPIIdentityScopedInformer struct { + factory kcpinternalinterfaces.SharedScopedInformerFactory + tweakListOptions kcpinternalinterfaces.TweakListOptionsFunc +} + +// NewAPIIdentityInformer constructs a new informer for APIIdentity type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewAPIIdentityInformer(client kcpversioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredAPIIdentityInformer(client, resyncPeriod, indexers, nil) +} + +// NewFilteredAPIIdentityInformer constructs a new informer for APIIdentity type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredAPIIdentityInformer(client kcpversioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions kcpinternalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.AdminV1alpha1().APIIdentities().List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.AdminV1alpha1().APIIdentities().Watch(context.Background(), options) + }, + }, client), + &kcpadminv1alpha1.APIIdentity{}, + resyncPeriod, + indexers, + ) +} + +func (i *aPIIdentityScopedInformer) Informer() cache.SharedIndexInformer { + return i.factory.InformerFor(&kcpadminv1alpha1.APIIdentity{}, i.defaultInformer) +} + +func (i *aPIIdentityScopedInformer) Lister() kcpv1alpha1.APIIdentityLister { + return kcpv1alpha1.NewAPIIdentityLister(i.Informer().GetIndexer()) +} + +func (i *aPIIdentityScopedInformer) defaultInformer(client kcpversioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredAPIIdentityInformer(client, resyncPeriod, cache.Indexers{}, i.tweakListOptions) +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/admin/v1alpha1/interface.go b/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/admin/v1alpha1/interface.go new file mode 100644 index 00000000000..a2752ad9b83 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/admin/v1alpha1/interface.go @@ -0,0 +1,64 @@ +/* +Copyright The kcp 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. +*/ + +// Code generated by cluster-informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + kcpinternalinterfaces "github.com/kcp-dev/sdk/client/informers/externalversions/internalinterfaces" +) + +type ClusterInterface interface { + // APIIdentities returns a APIIdentityClusterInformer. + APIIdentities() APIIdentityClusterInformer +} + +type version struct { + factory kcpinternalinterfaces.SharedInformerFactory + tweakListOptions kcpinternalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f kcpinternalinterfaces.SharedInformerFactory, tweakListOptions kcpinternalinterfaces.TweakListOptionsFunc) ClusterInterface { + return &version{factory: f, tweakListOptions: tweakListOptions} +} + +// APIIdentities returns a APIIdentityClusterInformer. +func (v *version) APIIdentities() APIIdentityClusterInformer { + return &aPIIdentityClusterInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} + +type Interface interface { + // APIIdentities returns a APIIdentityInformer. + APIIdentities() APIIdentityInformer +} + +type scopedVersion struct { + factory kcpinternalinterfaces.SharedScopedInformerFactory + tweakListOptions kcpinternalinterfaces.TweakListOptionsFunc + namespace string +} + +// New returns a new Interface. +func NewScoped(f kcpinternalinterfaces.SharedScopedInformerFactory, namespace string, tweakListOptions kcpinternalinterfaces.TweakListOptionsFunc) Interface { + return &scopedVersion{factory: f, tweakListOptions: tweakListOptions} +} + +// APIIdentities returns a APIIdentityInformer. +func (v *scopedVersion) APIIdentities() APIIdentityInformer { + return &aPIIdentityScopedInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/factory.go b/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/factory.go index d12e41c288a..1b348925cdd 100644 --- a/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/factory.go +++ b/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/factory.go @@ -32,6 +32,7 @@ import ( logicalcluster "github.com/kcp-dev/logicalcluster/v3" kcpversioned "github.com/kcp-dev/sdk/client/clientset/versioned" kcpcluster "github.com/kcp-dev/sdk/client/clientset/versioned/cluster" + kcpadmin "github.com/kcp-dev/sdk/client/informers/externalversions/admin" kcpapis "github.com/kcp-dev/sdk/client/informers/externalversions/apis" kcpexternalversionscache "github.com/kcp-dev/sdk/client/informers/externalversions/cache" kcpcore "github.com/kcp-dev/sdk/client/informers/externalversions/core" @@ -281,6 +282,7 @@ type SharedInformerFactory interface { // client. InformerFor(obj runtime.Object, newFunc kcpinternalinterfaces.NewInformerFunc) kcpcache.ScopeableSharedIndexInformer + Admin() kcpadmin.ClusterInterface Apis() kcpapis.ClusterInterface Cache() kcpexternalversionscache.ClusterInterface Core() kcpcore.ClusterInterface @@ -289,6 +291,10 @@ type SharedInformerFactory interface { Topology() kcptopology.ClusterInterface } +func (f *sharedInformerFactory) Admin() kcpadmin.ClusterInterface { + return kcpadmin.New(f, f.tweakListOptions) +} + func (f *sharedInformerFactory) Apis() kcpapis.ClusterInterface { return kcpapis.New(f, f.tweakListOptions) } @@ -457,6 +463,7 @@ type SharedScopedInformerFactory interface { ForResource(resource schema.GroupVersionResource) (GenericInformer, error) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool + Admin() kcpadmin.Interface Apis() kcpapis.Interface Cache() kcpexternalversionscache.Interface Core() kcpcore.Interface @@ -465,6 +472,10 @@ type SharedScopedInformerFactory interface { Topology() kcptopology.Interface } +func (f *sharedScopedInformerFactory) Admin() kcpadmin.Interface { + return kcpadmin.NewScoped(f, f.namespace, f.tweakListOptions) +} + func (f *sharedScopedInformerFactory) Apis() kcpapis.Interface { return kcpapis.NewScoped(f, f.namespace, f.tweakListOptions) } diff --git a/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/generic.go b/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/generic.go index a66540870e1..95560c568ee 100644 --- a/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/generic.go +++ b/staging/src/github.com/kcp-dev/sdk/client/informers/externalversions/generic.go @@ -27,7 +27,8 @@ import ( kcpcache "github.com/kcp-dev/apimachinery/v2/pkg/cache" logicalcluster "github.com/kcp-dev/logicalcluster/v3" - kcpv1alpha1 "github.com/kcp-dev/sdk/apis/apis/v1alpha1" + kcpv1alpha1 "github.com/kcp-dev/sdk/apis/admin/v1alpha1" + kcpapisv1alpha1 "github.com/kcp-dev/sdk/apis/apis/v1alpha1" kcpv1alpha2 "github.com/kcp-dev/sdk/apis/apis/v1alpha2" kcpcachev1alpha1 "github.com/kcp-dev/sdk/apis/cache/v1alpha1" kcpcorev1alpha1 "github.com/kcp-dev/sdk/apis/core/v1alpha1" @@ -99,16 +100,20 @@ func (i *genericInformer) Lister() cache.GenericLister { // TODO extend this to unknown resources with a client pool func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericClusterInformer, error) { switch resource { - // Group=apis.kcp.io, Version=v1alpha1 - case kcpv1alpha1.SchemeGroupVersion.WithResource("apibindings"): + // Group=admin.kcp.io, Version=v1alpha1 + case kcpv1alpha1.SchemeGroupVersion.WithResource("apiidentities"): + return &genericClusterInformer{resource: resource.GroupResource(), informer: f.Admin().V1alpha1().APIIdentities().Informer()}, nil + + // Group=apis.kcp.io, Version=v1alpha1 + case kcpapisv1alpha1.SchemeGroupVersion.WithResource("apibindings"): return &genericClusterInformer{resource: resource.GroupResource(), informer: f.Apis().V1alpha1().APIBindings().Informer()}, nil - case kcpv1alpha1.SchemeGroupVersion.WithResource("apiconversions"): + case kcpapisv1alpha1.SchemeGroupVersion.WithResource("apiconversions"): return &genericClusterInformer{resource: resource.GroupResource(), informer: f.Apis().V1alpha1().APIConversions().Informer()}, nil - case kcpv1alpha1.SchemeGroupVersion.WithResource("apiexports"): + case kcpapisv1alpha1.SchemeGroupVersion.WithResource("apiexports"): return &genericClusterInformer{resource: resource.GroupResource(), informer: f.Apis().V1alpha1().APIExports().Informer()}, nil - case kcpv1alpha1.SchemeGroupVersion.WithResource("apiexportendpointslices"): + case kcpapisv1alpha1.SchemeGroupVersion.WithResource("apiexportendpointslices"): return &genericClusterInformer{resource: resource.GroupResource(), informer: f.Apis().V1alpha1().APIExportEndpointSlices().Informer()}, nil - case kcpv1alpha1.SchemeGroupVersion.WithResource("apiresourceschemas"): + case kcpapisv1alpha1.SchemeGroupVersion.WithResource("apiresourceschemas"): return &genericClusterInformer{resource: resource.GroupResource(), informer: f.Apis().V1alpha1().APIResourceSchemas().Informer()}, nil // Group=apis.kcp.io, Version=v1alpha2 @@ -156,20 +161,25 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource // TODO extend this to unknown resources with a client pool func (f *sharedScopedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { switch resource { - // Group=apis.kcp.io, Version=v1alpha1 - case kcpv1alpha1.SchemeGroupVersion.WithResource("apibindings"): + // Group=admin.kcp.io, Version=v1alpha1 + case kcpv1alpha1.SchemeGroupVersion.WithResource("apiidentities"): + informer := f.Admin().V1alpha1().APIIdentities().Informer() + return &genericInformer{lister: cache.NewGenericLister(informer.GetIndexer(), resource.GroupResource()), informer: informer}, nil + + // Group=apis.kcp.io, Version=v1alpha1 + case kcpapisv1alpha1.SchemeGroupVersion.WithResource("apibindings"): informer := f.Apis().V1alpha1().APIBindings().Informer() return &genericInformer{lister: cache.NewGenericLister(informer.GetIndexer(), resource.GroupResource()), informer: informer}, nil - case kcpv1alpha1.SchemeGroupVersion.WithResource("apiconversions"): + case kcpapisv1alpha1.SchemeGroupVersion.WithResource("apiconversions"): informer := f.Apis().V1alpha1().APIConversions().Informer() return &genericInformer{lister: cache.NewGenericLister(informer.GetIndexer(), resource.GroupResource()), informer: informer}, nil - case kcpv1alpha1.SchemeGroupVersion.WithResource("apiexports"): + case kcpapisv1alpha1.SchemeGroupVersion.WithResource("apiexports"): informer := f.Apis().V1alpha1().APIExports().Informer() return &genericInformer{lister: cache.NewGenericLister(informer.GetIndexer(), resource.GroupResource()), informer: informer}, nil - case kcpv1alpha1.SchemeGroupVersion.WithResource("apiexportendpointslices"): + case kcpapisv1alpha1.SchemeGroupVersion.WithResource("apiexportendpointslices"): informer := f.Apis().V1alpha1().APIExportEndpointSlices().Informer() return &genericInformer{lister: cache.NewGenericLister(informer.GetIndexer(), resource.GroupResource()), informer: informer}, nil - case kcpv1alpha1.SchemeGroupVersion.WithResource("apiresourceschemas"): + case kcpapisv1alpha1.SchemeGroupVersion.WithResource("apiresourceschemas"): informer := f.Apis().V1alpha1().APIResourceSchemas().Informer() return &genericInformer{lister: cache.NewGenericLister(informer.GetIndexer(), resource.GroupResource()), informer: informer}, nil diff --git a/staging/src/github.com/kcp-dev/sdk/client/listers/admin/v1alpha1/apiidentity.go b/staging/src/github.com/kcp-dev/sdk/client/listers/admin/v1alpha1/apiidentity.go new file mode 100644 index 00000000000..51a37cf7ed4 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/listers/admin/v1alpha1/apiidentity.go @@ -0,0 +1,102 @@ +/* +Copyright The kcp 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. +*/ + +// Code generated by cluster-lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + + kcplisters "github.com/kcp-dev/client-go/third_party/k8s.io/client-go/listers" + "github.com/kcp-dev/logicalcluster/v3" + kcpv1alpha1 "github.com/kcp-dev/sdk/apis/admin/v1alpha1" +) + +// APIIdentityClusterLister helps list APIIdentities across all workspaces, +// or scope down to a APIIdentityLister for one workspace. +// All objects returned here must be treated as read-only. +type APIIdentityClusterLister interface { + // List lists all APIIdentities in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*kcpv1alpha1.APIIdentity, err error) + // Cluster returns a lister that can list and get APIIdentities in one workspace. + Cluster(clusterName logicalcluster.Name) APIIdentityLister + APIIdentityClusterListerExpansion +} + +// aPIIdentityClusterLister implements the APIIdentityClusterLister interface. +type aPIIdentityClusterLister struct { + kcplisters.ResourceClusterIndexer[*kcpv1alpha1.APIIdentity] +} + +var _ APIIdentityClusterLister = new(aPIIdentityClusterLister) + +// NewAPIIdentityClusterLister returns a new APIIdentityClusterLister. +// We assume that the indexer: +// - is fed by a cross-workspace LIST+WATCH +// - uses kcpcache.MetaClusterNamespaceKeyFunc as the key function +// - has the kcpcache.ClusterIndex as an index +func NewAPIIdentityClusterLister(indexer cache.Indexer) APIIdentityClusterLister { + return &aPIIdentityClusterLister{ + kcplisters.NewCluster[*kcpv1alpha1.APIIdentity](indexer, kcpv1alpha1.Resource("apiidentity")), + } +} + +// Cluster scopes the lister to one workspace, allowing users to list and get APIIdentities. +func (l *aPIIdentityClusterLister) Cluster(clusterName logicalcluster.Name) APIIdentityLister { + return &aPIIdentityLister{ + l.ResourceClusterIndexer.WithCluster(clusterName), + } +} + +// aPIIdentityLister can list all APIIdentities inside a workspace +// or scope down to a APIIdentityNamespaceLister for one namespace. +type aPIIdentityLister struct { + kcplisters.ResourceIndexer[*kcpv1alpha1.APIIdentity] +} + +var _ APIIdentityLister = new(aPIIdentityLister) + +// APIIdentityLister can list all APIIdentities, or get one in particular. +// All objects returned here must be treated as read-only. +type APIIdentityLister interface { + // List lists all APIIdentities in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*kcpv1alpha1.APIIdentity, err error) + // Get retrieves the APIIdentity from the indexer for a given workspace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*kcpv1alpha1.APIIdentity, error) + APIIdentityListerExpansion +} + +// NewAPIIdentityLister returns a new APIIdentityLister. +// We assume that the indexer: +// - is fed by a cross-workspace LIST+WATCH +// - uses kcpcache.MetaClusterNamespaceKeyFunc as the key function +// - has the kcpcache.ClusterIndex as an index +func NewAPIIdentityLister(indexer cache.Indexer) APIIdentityLister { + return &aPIIdentityLister{ + kcplisters.New[*kcpv1alpha1.APIIdentity](indexer, kcpv1alpha1.Resource("apiidentity")), + } +} + +// aPIIdentityScopedLister can list all APIIdentities inside a workspace +// or scope down to a APIIdentityNamespaceLister. +type aPIIdentityScopedLister struct { + kcplisters.ResourceIndexer[*kcpv1alpha1.APIIdentity] +} diff --git a/staging/src/github.com/kcp-dev/sdk/client/listers/admin/v1alpha1/expansion_generated.go b/staging/src/github.com/kcp-dev/sdk/client/listers/admin/v1alpha1/expansion_generated.go new file mode 100644 index 00000000000..3ce24577057 --- /dev/null +++ b/staging/src/github.com/kcp-dev/sdk/client/listers/admin/v1alpha1/expansion_generated.go @@ -0,0 +1,27 @@ +/* +Copyright The kcp 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. +*/ + +// Code generated by cluster-lister-gen. DO NOT EDIT. + +package v1alpha1 + +// APIIdentityClusterListerExpansion allows custom methods to be added to +// APIIdentityClusterLister. +type APIIdentityClusterListerExpansion interface{} + +// APIIdentityListerExpansion allows custom methods to be added to +// APIIdentityLister. +type APIIdentityListerExpansion interface{} diff --git a/staging/src/github.com/kcp-dev/sdk/cmd/apigen/main.go b/staging/src/github.com/kcp-dev/sdk/cmd/apigen/main.go index 2875f8769db..c00d66e3661 100644 --- a/staging/src/github.com/kcp-dev/sdk/cmd/apigen/main.go +++ b/staging/src/github.com/kcp-dev/sdk/cmd/apigen/main.go @@ -61,18 +61,41 @@ type options struct { inputDir string outputDir string ignoreExportSchemas []string + ignoreSchemas []string preserveResources bool headerFile string ignoreExportSchemasParsed []metav1.GroupResource + ignoreSchemasParsed []metav1.GroupResource headerText string } +// parseGroupResources parses strings like "resource.group.org" into GroupResources. +func parseGroupResources(in []string) ([]metav1.GroupResource, error) { + var out []metav1.GroupResource + for _, s := range in { + parts := strings.Split(s, ".") + if len(parts) < 2 { + return nil, fmt.Errorf("invalid schema %q: must have at least 2 dot-separated segments", s) + } + + resource := parts[0] + group := strings.Join(parts[1:], ".") + if group == "core" { + group = "" + } + group = strings.TrimSuffix(group, "/") + out = append(out, metav1.GroupResource{Group: group, Resource: resource}) + } + return out, nil +} + func bindOptions(fs *pflag.FlagSet) *options { o := options{} fs.StringVar(&o.inputDir, "input-dir", "", "Directory containing CustomResourceDefinition YAML files.") fs.StringVar(&o.outputDir, "output-dir", "", "Directory where APIResourceSchemas and APIExports will be written.") fs.StringSliceVar(&o.ignoreExportSchemas, "ignore-export-schemas", []string{}, "Comma-separated list of 'Resource.Group' to be ignored for APIExports generation.") + fs.StringSliceVar(&o.ignoreSchemas, "ignore-schemas", []string{}, "Comma-separated list of 'Resource.Group' for which no APIResourceSchema is generated at all.") fs.BoolVar(&o.preserveResources, "preserve-resources", false, "Preserve existing resources in APIExport manifests instead of overriding them.") fs.StringVar(&o.headerFile, "header-file", "", "Path to a file containing boilerplate header text; the contents will be prepended to all generated files.") return &o @@ -85,22 +108,12 @@ func (o *options) Validate() error { if o.outputDir == "" { return fmt.Errorf("--output-dir is required") } - if len(o.ignoreExportSchemas) > 0 { - for _, s := range o.ignoreExportSchemas { - // parse strings like "resource.group.org" - parts := strings.Split(s, ".") - if len(parts) < 2 { - return fmt.Errorf("invalid schema %q: must have at least 2 dot-separated segments", s) - } - - resource := parts[0] - group := strings.Join(parts[1:], ".") - if group == "core" { - group = "" - } - group = strings.TrimSuffix(group, "/") - o.ignoreExportSchemasParsed = append(o.ignoreExportSchemasParsed, metav1.GroupResource{Group: group, Resource: resource}) - } + var err error + if o.ignoreExportSchemasParsed, err = parseGroupResources(o.ignoreExportSchemas); err != nil { + return err + } + if o.ignoreSchemasParsed, err = parseGroupResources(o.ignoreSchemas); err != nil { + return err } if o.headerFile != "" { headerText, err := os.ReadFile(o.headerFile) @@ -155,6 +168,12 @@ func main() { logger.Error(err, "Could not load CustomResourceDefinitions.") os.Exit(1) } + for _, gr := range opts.ignoreSchemasParsed { + if _, ok := crds[gr]; ok { + logger.Info(fmt.Sprintf("Skipping APIResourceSchema generation for %s", gr)) + delete(crds, gr) + } + } if _, err := os.Stat(opts.outputDir); os.IsNotExist(err) { if err := os.MkdirAll(opts.outputDir, os.FileMode(0755)); err != nil { diff --git a/staging/src/github.com/kcp-dev/sdk/openapi/zz_generated.openapi.go b/staging/src/github.com/kcp-dev/sdk/openapi/zz_generated.openapi.go index e8c36fcb221..e99007fef91 100644 --- a/staging/src/github.com/kcp-dev/sdk/openapi/zz_generated.openapi.go +++ b/staging/src/github.com/kcp-dev/sdk/openapi/zz_generated.openapi.go @@ -28,7 +28,8 @@ import ( common "k8s.io/kube-openapi/pkg/common" spec "k8s.io/kube-openapi/pkg/validation/spec" - v1alpha1 "github.com/kcp-dev/sdk/apis/apis/v1alpha1" + v1alpha1 "github.com/kcp-dev/sdk/apis/admin/v1alpha1" + apisv1alpha1 "github.com/kcp-dev/sdk/apis/apis/v1alpha1" v1alpha2 "github.com/kcp-dev/sdk/apis/apis/v1alpha2" cachev1alpha1 "github.com/kcp-dev/sdk/apis/cache/v1alpha1" corev1alpha1 "github.com/kcp-dev/sdk/apis/core/v1alpha1" @@ -40,42 +41,47 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ - v1alpha1.APIBinding{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIBinding(ref), - v1alpha1.APIBindingList{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIBindingList(ref), - v1alpha1.APIBindingSpec{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIBindingSpec(ref), - v1alpha1.APIBindingStatus{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIBindingStatus(ref), - v1alpha1.APIConversion{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIConversion(ref), - v1alpha1.APIConversionList{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIConversionList(ref), - v1alpha1.APIConversionRule{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIConversionRule(ref), - v1alpha1.APIConversionSpec{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIConversionSpec(ref), - v1alpha1.APIExport{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIExport(ref), - v1alpha1.APIExportEndpointSlice{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIExportEndpointSlice(ref), - v1alpha1.APIExportEndpointSliceList{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIExportEndpointSliceList(ref), - v1alpha1.APIExportEndpointSliceSpec{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIExportEndpointSliceSpec(ref), - v1alpha1.APIExportEndpointSliceStatus{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIExportEndpointSliceStatus(ref), - v1alpha1.APIExportList{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIExportList(ref), - v1alpha1.APIExportSpec{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIExportSpec(ref), - v1alpha1.APIExportStatus{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIExportStatus(ref), - v1alpha1.APIResourceSchema{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIResourceSchema(ref), - v1alpha1.APIResourceSchemaList{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIResourceSchemaList(ref), - v1alpha1.APIResourceSchemaSpec{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIResourceSchemaSpec(ref), - v1alpha1.APIResourceVersion{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIResourceVersion(ref), - v1alpha1.APIVersionConversion{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIVersionConversion(ref), - v1alpha1.AcceptablePermissionClaim{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_AcceptablePermissionClaim(ref), - v1alpha1.BindingReference{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_BindingReference(ref), - v1alpha1.BoundAPIResource{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_BoundAPIResource(ref), - v1alpha1.BoundAPIResourceSchema{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_BoundAPIResourceSchema(ref), - v1alpha1.CustomResourceConversion{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_CustomResourceConversion(ref), - v1alpha1.ExportBindingReference{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_ExportBindingReference(ref), - v1alpha1.GroupResource{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_GroupResource(ref), - v1alpha1.Identity{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_Identity(ref), - v1alpha1.LocalAPIExportPolicy{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_LocalAPIExportPolicy(ref), - v1alpha1.MaximalPermissionPolicy{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_MaximalPermissionPolicy(ref), - v1alpha1.PermissionClaim{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_PermissionClaim(ref), - v1alpha1.ResourceSelector{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_ResourceSelector(ref), - v1alpha1.VirtualWorkspace{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_VirtualWorkspace(ref), - v1alpha1.WebhookClientConfig{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_WebhookClientConfig(ref), - v1alpha1.WebhookConversion{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_WebhookConversion(ref), + v1alpha1.APIIdentity{}.OpenAPIModelName(): schema_sdk_apis_admin_v1alpha1_APIIdentity(ref), + v1alpha1.APIIdentityList{}.OpenAPIModelName(): schema_sdk_apis_admin_v1alpha1_APIIdentityList(ref), + v1alpha1.APIIdentityStatus{}.OpenAPIModelName(): schema_sdk_apis_admin_v1alpha1_APIIdentityStatus(ref), + v1alpha1.ExportReference{}.OpenAPIModelName(): schema_sdk_apis_admin_v1alpha1_ExportReference(ref), + v1alpha1.Holder{}.OpenAPIModelName(): schema_sdk_apis_admin_v1alpha1_Holder(ref), + apisv1alpha1.APIBinding{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIBinding(ref), + apisv1alpha1.APIBindingList{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIBindingList(ref), + apisv1alpha1.APIBindingSpec{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIBindingSpec(ref), + apisv1alpha1.APIBindingStatus{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIBindingStatus(ref), + apisv1alpha1.APIConversion{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIConversion(ref), + apisv1alpha1.APIConversionList{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIConversionList(ref), + apisv1alpha1.APIConversionRule{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIConversionRule(ref), + apisv1alpha1.APIConversionSpec{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIConversionSpec(ref), + apisv1alpha1.APIExport{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIExport(ref), + apisv1alpha1.APIExportEndpointSlice{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIExportEndpointSlice(ref), + apisv1alpha1.APIExportEndpointSliceList{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIExportEndpointSliceList(ref), + apisv1alpha1.APIExportEndpointSliceSpec{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIExportEndpointSliceSpec(ref), + apisv1alpha1.APIExportEndpointSliceStatus{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIExportEndpointSliceStatus(ref), + apisv1alpha1.APIExportList{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIExportList(ref), + apisv1alpha1.APIExportSpec{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIExportSpec(ref), + apisv1alpha1.APIExportStatus{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIExportStatus(ref), + apisv1alpha1.APIResourceSchema{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIResourceSchema(ref), + apisv1alpha1.APIResourceSchemaList{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIResourceSchemaList(ref), + apisv1alpha1.APIResourceSchemaSpec{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIResourceSchemaSpec(ref), + apisv1alpha1.APIResourceVersion{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIResourceVersion(ref), + apisv1alpha1.APIVersionConversion{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_APIVersionConversion(ref), + apisv1alpha1.AcceptablePermissionClaim{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_AcceptablePermissionClaim(ref), + apisv1alpha1.BindingReference{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_BindingReference(ref), + apisv1alpha1.BoundAPIResource{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_BoundAPIResource(ref), + apisv1alpha1.BoundAPIResourceSchema{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_BoundAPIResourceSchema(ref), + apisv1alpha1.CustomResourceConversion{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_CustomResourceConversion(ref), + apisv1alpha1.ExportBindingReference{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_ExportBindingReference(ref), + apisv1alpha1.GroupResource{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_GroupResource(ref), + apisv1alpha1.Identity{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_Identity(ref), + apisv1alpha1.LocalAPIExportPolicy{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_LocalAPIExportPolicy(ref), + apisv1alpha1.MaximalPermissionPolicy{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_MaximalPermissionPolicy(ref), + apisv1alpha1.PermissionClaim{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_PermissionClaim(ref), + apisv1alpha1.ResourceSelector{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_ResourceSelector(ref), + apisv1alpha1.VirtualWorkspace{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_VirtualWorkspace(ref), + apisv1alpha1.WebhookClientConfig{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_WebhookClientConfig(ref), + apisv1alpha1.WebhookConversion{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha1_WebhookConversion(ref), v1alpha2.APIBinding{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha2_APIBinding(ref), v1alpha2.APIBindingList{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha2_APIBindingList(ref), v1alpha2.APIBindingSpec{}.OpenAPIModelName(): schema_sdk_apis_apis_v1alpha2_APIBindingSpec(ref), @@ -229,6 +235,245 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA } } +func schema_sdk_apis_admin_v1alpha1_APIIdentity(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "APIIdentity is the view of one API identity: which APIExport owns it and which other APIExports hold it. It is computed from the APIExports of the installation and not stored anywhere; there is one per identity, named after the identity value, served read-only through the Admin workspace.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(v1.ObjectMeta{}.OpenAPIModelName()), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(v1alpha1.APIIdentityStatus{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + Dependencies: []string{ + v1alpha1.APIIdentityStatus{}.OpenAPIModelName(), v1.ObjectMeta{}.OpenAPIModelName()}, + } +} + +func schema_sdk_apis_admin_v1alpha1_APIIdentityList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "APIIdentityList is a list of APIIdentity resources.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(v1.ListMeta{}.OpenAPIModelName()), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(v1alpha1.APIIdentity{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + }, + Required: []string{"metadata", "items"}, + }, + }, + Dependencies: []string{ + v1alpha1.APIIdentity{}.OpenAPIModelName(), v1.ListMeta{}.OpenAPIModelName()}, + } +} + +func schema_sdk_apis_admin_v1alpha1_APIIdentityStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "APIIdentityStatus is the derived state of an identity. The object has no spec: nothing about it is user intent.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "owner": { + SchemaProps: spec.SchemaProps{ + Description: "owner is the APIExport that holds the identity authoritatively: the oldest one holding it. When it is deleted the oldest holder succeeds it; an identity no APIExport holds any more has no entry.", + Default: map[string]interface{}{}, + Ref: ref(v1alpha1.ExportReference{}.OpenAPIModelName()), + }, + }, + "holders": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "cluster", + "name", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "holders lists the APIExports, other than the owner, that serve the identity: those whose identity secret carries the same key, oldest first.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(v1alpha1.Holder{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + "core": { + SchemaProps: spec.SchemaProps{ + Description: "core is true for the identities of the core APIExports in the root workspace, which every shard needs before it can serve.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "conditions": { + SchemaProps: spec.SchemaProps{ + Description: "conditions is a list of conditions that apply to the identity.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref(conditionsv1alpha1.Condition{}.OpenAPIModelName()), + }, + }, + }, + }, + }, + }, + Required: []string{"owner"}, + }, + }, + Dependencies: []string{ + v1alpha1.ExportReference{}.OpenAPIModelName(), v1alpha1.Holder{}.OpenAPIModelName(), conditionsv1alpha1.Condition{}.OpenAPIModelName()}, + } +} + +func schema_sdk_apis_admin_v1alpha1_ExportReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ExportReference identifies an APIExport across the installation.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "cluster": { + SchemaProps: spec.SchemaProps{ + Description: "cluster is the logical cluster name hosting the APIExport.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is the name of the APIExport.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "uid": { + SchemaProps: spec.SchemaProps{ + Description: "uid is the UID of the APIExport, so that an export deleted and recreated under the same name can be told apart from the original.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"cluster", "name"}, + }, + }, + } +} + +func schema_sdk_apis_admin_v1alpha1_Holder(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Holder is an APIExport, other than the owner, that serves the identity.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "cluster": { + SchemaProps: spec.SchemaProps{ + Description: "cluster is the logical cluster name hosting the APIExport.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is the name of the APIExport.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "uid": { + SchemaProps: spec.SchemaProps{ + Description: "uid is the UID of the APIExport, so that an export deleted and recreated under the same name can be told apart from the original.", + Type: []string{"string"}, + Format: "", + }, + }, + "since": { + SchemaProps: spec.SchemaProps{ + Description: "since is when the holder was created.", + Ref: ref(v1.Time{}.OpenAPIModelName()), + }, + }, + }, + Required: []string{"cluster", "name"}, + }, + }, + Dependencies: []string{ + v1.Time{}.OpenAPIModelName()}, + } +} + func schema_sdk_apis_apis_v1alpha1_APIBinding(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -260,14 +505,14 @@ func schema_sdk_apis_apis_v1alpha1_APIBinding(ref common.ReferenceCallback) comm SchemaProps: spec.SchemaProps{ Description: "Spec holds the desired state.", Default: map[string]interface{}{}, - Ref: ref(v1alpha1.APIBindingSpec{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.APIBindingSpec{}.OpenAPIModelName()), }, }, "status": { SchemaProps: spec.SchemaProps{ Description: "Status communicates the observed state.", Default: map[string]interface{}{}, - Ref: ref(v1alpha1.APIBindingStatus{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.APIBindingStatus{}.OpenAPIModelName()), }, }, }, @@ -275,7 +520,7 @@ func schema_sdk_apis_apis_v1alpha1_APIBinding(ref common.ReferenceCallback) comm }, }, Dependencies: []string{ - v1alpha1.APIBindingSpec{}.OpenAPIModelName(), v1alpha1.APIBindingStatus{}.OpenAPIModelName(), v1.ObjectMeta{}.OpenAPIModelName()}, + apisv1alpha1.APIBindingSpec{}.OpenAPIModelName(), apisv1alpha1.APIBindingStatus{}.OpenAPIModelName(), v1.ObjectMeta{}.OpenAPIModelName()}, } } @@ -313,7 +558,7 @@ func schema_sdk_apis_apis_v1alpha1_APIBindingList(ref common.ReferenceCallback) Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(v1alpha1.APIBinding{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.APIBinding{}.OpenAPIModelName()), }, }, }, @@ -324,7 +569,7 @@ func schema_sdk_apis_apis_v1alpha1_APIBindingList(ref common.ReferenceCallback) }, }, Dependencies: []string{ - v1alpha1.APIBinding{}.OpenAPIModelName(), v1.ListMeta{}.OpenAPIModelName()}, + apisv1alpha1.APIBinding{}.OpenAPIModelName(), v1.ListMeta{}.OpenAPIModelName()}, } } @@ -339,7 +584,7 @@ func schema_sdk_apis_apis_v1alpha1_APIBindingSpec(ref common.ReferenceCallback) SchemaProps: spec.SchemaProps{ Description: "reference uniquely identifies an API to bind to.", Default: map[string]interface{}{}, - Ref: ref(v1alpha1.BindingReference{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.BindingReference{}.OpenAPIModelName()), }, }, "permissionClaims": { @@ -350,7 +595,7 @@ func schema_sdk_apis_apis_v1alpha1_APIBindingSpec(ref common.ReferenceCallback) Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(v1alpha1.AcceptablePermissionClaim{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.AcceptablePermissionClaim{}.OpenAPIModelName()), }, }, }, @@ -361,7 +606,7 @@ func schema_sdk_apis_apis_v1alpha1_APIBindingSpec(ref common.ReferenceCallback) }, }, Dependencies: []string{ - v1alpha1.AcceptablePermissionClaim{}.OpenAPIModelName(), v1alpha1.BindingReference{}.OpenAPIModelName()}, + apisv1alpha1.AcceptablePermissionClaim{}.OpenAPIModelName(), apisv1alpha1.BindingReference{}.OpenAPIModelName()}, } } @@ -396,7 +641,7 @@ func schema_sdk_apis_apis_v1alpha1_APIBindingStatus(ref common.ReferenceCallback Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(v1alpha1.BoundAPIResource{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.BoundAPIResource{}.OpenAPIModelName()), }, }, }, @@ -431,7 +676,7 @@ func schema_sdk_apis_apis_v1alpha1_APIBindingStatus(ref common.ReferenceCallback Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(v1alpha1.PermissionClaim{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.PermissionClaim{}.OpenAPIModelName()), }, }, }, @@ -445,7 +690,7 @@ func schema_sdk_apis_apis_v1alpha1_APIBindingStatus(ref common.ReferenceCallback Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(v1alpha1.PermissionClaim{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.PermissionClaim{}.OpenAPIModelName()), }, }, }, @@ -455,7 +700,7 @@ func schema_sdk_apis_apis_v1alpha1_APIBindingStatus(ref common.ReferenceCallback }, }, Dependencies: []string{ - v1alpha1.BoundAPIResource{}.OpenAPIModelName(), v1alpha1.PermissionClaim{}.OpenAPIModelName(), conditionsv1alpha1.Condition{}.OpenAPIModelName()}, + apisv1alpha1.BoundAPIResource{}.OpenAPIModelName(), apisv1alpha1.PermissionClaim{}.OpenAPIModelName(), conditionsv1alpha1.Condition{}.OpenAPIModelName()}, } } @@ -490,7 +735,7 @@ func schema_sdk_apis_apis_v1alpha1_APIConversion(ref common.ReferenceCallback) c SchemaProps: spec.SchemaProps{ Description: "Spec holds the desired state.", Default: map[string]interface{}{}, - Ref: ref(v1alpha1.APIConversionSpec{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.APIConversionSpec{}.OpenAPIModelName()), }, }, }, @@ -498,7 +743,7 @@ func schema_sdk_apis_apis_v1alpha1_APIConversion(ref common.ReferenceCallback) c }, }, Dependencies: []string{ - v1alpha1.APIConversionSpec{}.OpenAPIModelName(), v1.ObjectMeta{}.OpenAPIModelName()}, + apisv1alpha1.APIConversionSpec{}.OpenAPIModelName(), v1.ObjectMeta{}.OpenAPIModelName()}, } } @@ -536,7 +781,7 @@ func schema_sdk_apis_apis_v1alpha1_APIConversionList(ref common.ReferenceCallbac Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(v1alpha1.APIConversion{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.APIConversion{}.OpenAPIModelName()), }, }, }, @@ -547,7 +792,7 @@ func schema_sdk_apis_apis_v1alpha1_APIConversionList(ref common.ReferenceCallbac }, }, Dependencies: []string{ - v1alpha1.APIConversion{}.OpenAPIModelName(), v1.ListMeta{}.OpenAPIModelName()}, + apisv1alpha1.APIConversion{}.OpenAPIModelName(), v1.ListMeta{}.OpenAPIModelName()}, } } @@ -612,7 +857,7 @@ func schema_sdk_apis_apis_v1alpha1_APIConversionSpec(ref common.ReferenceCallbac Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(v1alpha1.APIVersionConversion{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.APIVersionConversion{}.OpenAPIModelName()), }, }, }, @@ -623,7 +868,7 @@ func schema_sdk_apis_apis_v1alpha1_APIConversionSpec(ref common.ReferenceCallbac }, }, Dependencies: []string{ - v1alpha1.APIVersionConversion{}.OpenAPIModelName()}, + apisv1alpha1.APIVersionConversion{}.OpenAPIModelName()}, } } @@ -658,21 +903,21 @@ func schema_sdk_apis_apis_v1alpha1_APIExport(ref common.ReferenceCallback) commo SchemaProps: spec.SchemaProps{ Description: "Spec holds the desired state.", Default: map[string]interface{}{}, - Ref: ref(v1alpha1.APIExportSpec{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.APIExportSpec{}.OpenAPIModelName()), }, }, "status": { SchemaProps: spec.SchemaProps{ Description: "Status communicates the observed state.", Default: map[string]interface{}{}, - Ref: ref(v1alpha1.APIExportStatus{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.APIExportStatus{}.OpenAPIModelName()), }, }, }, }, }, Dependencies: []string{ - v1alpha1.APIExportSpec{}.OpenAPIModelName(), v1alpha1.APIExportStatus{}.OpenAPIModelName(), v1.ObjectMeta{}.OpenAPIModelName()}, + apisv1alpha1.APIExportSpec{}.OpenAPIModelName(), apisv1alpha1.APIExportStatus{}.OpenAPIModelName(), v1.ObjectMeta{}.OpenAPIModelName()}, } } @@ -707,21 +952,21 @@ func schema_sdk_apis_apis_v1alpha1_APIExportEndpointSlice(ref common.ReferenceCa SchemaProps: spec.SchemaProps{ Description: "spec holds the desired state: - the targeted APIExport - an optional partition for filtering", Default: map[string]interface{}{}, - Ref: ref(v1alpha1.APIExportEndpointSliceSpec{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.APIExportEndpointSliceSpec{}.OpenAPIModelName()), }, }, "status": { SchemaProps: spec.SchemaProps{ Description: "status communicates the observed state: the filtered list of endpoints for the APIExport service.", Default: map[string]interface{}{}, - Ref: ref(v1alpha1.APIExportEndpointSliceStatus{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.APIExportEndpointSliceStatus{}.OpenAPIModelName()), }, }, }, }, }, Dependencies: []string{ - v1alpha1.APIExportEndpointSliceSpec{}.OpenAPIModelName(), v1alpha1.APIExportEndpointSliceStatus{}.OpenAPIModelName(), v1.ObjectMeta{}.OpenAPIModelName()}, + apisv1alpha1.APIExportEndpointSliceSpec{}.OpenAPIModelName(), apisv1alpha1.APIExportEndpointSliceStatus{}.OpenAPIModelName(), v1.ObjectMeta{}.OpenAPIModelName()}, } } @@ -759,7 +1004,7 @@ func schema_sdk_apis_apis_v1alpha1_APIExportEndpointSliceList(ref common.Referen Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(v1alpha1.APIExportEndpointSlice{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.APIExportEndpointSlice{}.OpenAPIModelName()), }, }, }, @@ -770,7 +1015,7 @@ func schema_sdk_apis_apis_v1alpha1_APIExportEndpointSliceList(ref common.Referen }, }, Dependencies: []string{ - v1alpha1.APIExportEndpointSlice{}.OpenAPIModelName(), v1.ListMeta{}.OpenAPIModelName()}, + apisv1alpha1.APIExportEndpointSlice{}.OpenAPIModelName(), v1.ListMeta{}.OpenAPIModelName()}, } } @@ -785,7 +1030,7 @@ func schema_sdk_apis_apis_v1alpha1_APIExportEndpointSliceSpec(ref common.Referen SchemaProps: spec.SchemaProps{ Description: "export points to the API export.", Default: map[string]interface{}{}, - Ref: ref(v1alpha1.ExportBindingReference{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.ExportBindingReference{}.OpenAPIModelName()), }, }, "partition": { @@ -800,7 +1045,7 @@ func schema_sdk_apis_apis_v1alpha1_APIExportEndpointSliceSpec(ref common.Referen }, }, Dependencies: []string{ - v1alpha1.ExportBindingReference{}.OpenAPIModelName()}, + apisv1alpha1.ExportBindingReference{}.OpenAPIModelName()}, } } @@ -896,7 +1141,7 @@ func schema_sdk_apis_apis_v1alpha1_APIExportList(ref common.ReferenceCallback) c Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(v1alpha1.APIExport{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.APIExport{}.OpenAPIModelName()), }, }, }, @@ -907,7 +1152,7 @@ func schema_sdk_apis_apis_v1alpha1_APIExportList(ref common.ReferenceCallback) c }, }, Dependencies: []string{ - v1alpha1.APIExport{}.OpenAPIModelName(), v1.ListMeta{}.OpenAPIModelName()}, + apisv1alpha1.APIExport{}.OpenAPIModelName(), v1.ListMeta{}.OpenAPIModelName()}, } } @@ -941,13 +1186,13 @@ func schema_sdk_apis_apis_v1alpha1_APIExportSpec(ref common.ReferenceCallback) c "identity": { SchemaProps: spec.SchemaProps{ Description: "identity points to a secret that contains the API identity in the 'key' file. The API identity determines an unique etcd prefix for objects stored via this APIExport.\n\nDifferent APIExport in a workspace can share a common identity, or have different ones. The identity (the secret) can also be transferred to another workspace when the APIExport is moved.\n\nThe identity is a secret of the API provider. The APIBindings referencing this APIExport will store a derived, non-sensitive value of this identity.\n\nThe identity of an APIExport cannot be changed. A derived, non-sensitive value of the identity key is stored in the APIExport status and this value is immutable.\n\nThe identity is defaulted. A secret with the name of the APIExport is automatically created.", - Ref: ref(v1alpha1.Identity{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.Identity{}.OpenAPIModelName()), }, }, "maximalPermissionPolicy": { SchemaProps: spec.SchemaProps{ Description: "maximalPermissionPolicy will allow for a service provider to set an upper bound on what is allowed for a consumer of this API. If the policy is not set, no upper bound is applied, i.e the consuming users can do whatever the user workspace allows the user to do.\n\nThe policy consists of RBAC (Cluster)Roles and (Cluster)Bindings. A request of a user in a workspace that binds to this APIExport via an APIBinding is additionally checked against these rules, with the user name and the groups prefixed with `apis.kcp.io:binding:`.\n\nFor example: assume a user `adam` with groups `system:authenticated` and `a-team` binds to this APIExport in another workspace root:org:ws. Then a request in that workspace against a resource of this APIExport is authorized as every other request in that workspace, but in addition the RBAC policy here in the APIExport workspace has to grant access to the user `apis.kcp.io:binding:adam` with the groups `apis.kcp.io:binding:system:authenticated` and `apis.kcp.io:binding:a-team`.\n\nIf an APIExport with a maximalPermissionPolicy is deleted, these additional checks will no longer be applied to resources of this APIExport.\n\nFor example: Assume an APIExport with a maximalPermissionPolicy that only allows creation and deletion of resources, but not updates - if the APIExport is deleted users will be able to update the resources of the APIExport again (given the constraints of their workspace's RBAC policies).", - Ref: ref(v1alpha1.MaximalPermissionPolicy{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.MaximalPermissionPolicy{}.OpenAPIModelName()), }, }, "permissionClaims": { @@ -967,7 +1212,7 @@ func schema_sdk_apis_apis_v1alpha1_APIExportSpec(ref common.ReferenceCallback) c Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(v1alpha1.PermissionClaim{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.PermissionClaim{}.OpenAPIModelName()), }, }, }, @@ -977,7 +1222,7 @@ func schema_sdk_apis_apis_v1alpha1_APIExportSpec(ref common.ReferenceCallback) c }, }, Dependencies: []string{ - v1alpha1.Identity{}.OpenAPIModelName(), v1alpha1.MaximalPermissionPolicy{}.OpenAPIModelName(), v1alpha1.PermissionClaim{}.OpenAPIModelName()}, + apisv1alpha1.Identity{}.OpenAPIModelName(), apisv1alpha1.MaximalPermissionPolicy{}.OpenAPIModelName(), apisv1alpha1.PermissionClaim{}.OpenAPIModelName()}, } } @@ -1017,7 +1262,7 @@ func schema_sdk_apis_apis_v1alpha1_APIExportStatus(ref common.ReferenceCallback) Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(v1alpha1.VirtualWorkspace{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.VirtualWorkspace{}.OpenAPIModelName()), }, }, }, @@ -1027,7 +1272,7 @@ func schema_sdk_apis_apis_v1alpha1_APIExportStatus(ref common.ReferenceCallback) }, }, Dependencies: []string{ - v1alpha1.VirtualWorkspace{}.OpenAPIModelName(), conditionsv1alpha1.Condition{}.OpenAPIModelName()}, + apisv1alpha1.VirtualWorkspace{}.OpenAPIModelName(), conditionsv1alpha1.Condition{}.OpenAPIModelName()}, } } @@ -1062,14 +1307,14 @@ func schema_sdk_apis_apis_v1alpha1_APIResourceSchema(ref common.ReferenceCallbac SchemaProps: spec.SchemaProps{ Description: "Spec holds the desired state.", Default: map[string]interface{}{}, - Ref: ref(v1alpha1.APIResourceSchemaSpec{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.APIResourceSchemaSpec{}.OpenAPIModelName()), }, }, }, }, }, Dependencies: []string{ - v1alpha1.APIResourceSchemaSpec{}.OpenAPIModelName(), v1.ObjectMeta{}.OpenAPIModelName()}, + apisv1alpha1.APIResourceSchemaSpec{}.OpenAPIModelName(), v1.ObjectMeta{}.OpenAPIModelName()}, } } @@ -1107,7 +1352,7 @@ func schema_sdk_apis_apis_v1alpha1_APIResourceSchemaList(ref common.ReferenceCal Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(v1alpha1.APIResourceSchema{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.APIResourceSchema{}.OpenAPIModelName()), }, }, }, @@ -1118,7 +1363,7 @@ func schema_sdk_apis_apis_v1alpha1_APIResourceSchemaList(ref common.ReferenceCal }, }, Dependencies: []string{ - v1alpha1.APIResourceSchema{}.OpenAPIModelName(), v1.ListMeta{}.OpenAPIModelName()}, + apisv1alpha1.APIResourceSchema{}.OpenAPIModelName(), v1.ListMeta{}.OpenAPIModelName()}, } } @@ -1168,7 +1413,7 @@ func schema_sdk_apis_apis_v1alpha1_APIResourceSchemaSpec(ref common.ReferenceCal Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(v1alpha1.APIResourceVersion{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.APIResourceVersion{}.OpenAPIModelName()), }, }, }, @@ -1184,7 +1429,7 @@ func schema_sdk_apis_apis_v1alpha1_APIResourceSchemaSpec(ref common.ReferenceCal "conversion": { SchemaProps: spec.SchemaProps{ Description: "conversion defines conversion settings for the defined custom resource.", - Ref: ref(v1alpha1.CustomResourceConversion{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.CustomResourceConversion{}.OpenAPIModelName()), }, }, }, @@ -1192,7 +1437,7 @@ func schema_sdk_apis_apis_v1alpha1_APIResourceSchemaSpec(ref common.ReferenceCal }, }, Dependencies: []string{ - v1alpha1.APIResourceVersion{}.OpenAPIModelName(), v1alpha1.CustomResourceConversion{}.OpenAPIModelName(), "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionNames"}, + apisv1alpha1.APIResourceVersion{}.OpenAPIModelName(), apisv1alpha1.CustomResourceConversion{}.OpenAPIModelName(), "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionNames"}, } } @@ -1348,7 +1593,7 @@ func schema_sdk_apis_apis_v1alpha1_APIVersionConversion(ref common.ReferenceCall Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(v1alpha1.APIConversionRule{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.APIConversionRule{}.OpenAPIModelName()), }, }, }, @@ -1374,7 +1619,7 @@ func schema_sdk_apis_apis_v1alpha1_APIVersionConversion(ref common.ReferenceCall }, }, Dependencies: []string{ - v1alpha1.APIConversionRule{}.OpenAPIModelName()}, + apisv1alpha1.APIConversionRule{}.OpenAPIModelName()}, } } @@ -1415,7 +1660,7 @@ func schema_sdk_apis_apis_v1alpha1_AcceptablePermissionClaim(ref common.Referenc Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(v1alpha1.ResourceSelector{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.ResourceSelector{}.OpenAPIModelName()), }, }, }, @@ -1440,7 +1685,7 @@ func schema_sdk_apis_apis_v1alpha1_AcceptablePermissionClaim(ref common.Referenc }, }, Dependencies: []string{ - v1alpha1.ResourceSelector{}.OpenAPIModelName()}, + apisv1alpha1.ResourceSelector{}.OpenAPIModelName()}, } } @@ -1454,14 +1699,14 @@ func schema_sdk_apis_apis_v1alpha1_BindingReference(ref common.ReferenceCallback "export": { SchemaProps: spec.SchemaProps{ Description: "export is a reference to an APIExport by cluster name and export name. The creator of the APIBinding needs to have access to the APIExport with the verb `bind` in order to bind to it.", - Ref: ref(v1alpha1.ExportBindingReference{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.ExportBindingReference{}.OpenAPIModelName()), }, }, }, }, }, Dependencies: []string{ - v1alpha1.ExportBindingReference{}.OpenAPIModelName()}, + apisv1alpha1.ExportBindingReference{}.OpenAPIModelName()}, } } @@ -1492,7 +1737,7 @@ func schema_sdk_apis_apis_v1alpha1_BoundAPIResource(ref common.ReferenceCallback SchemaProps: spec.SchemaProps{ Description: "Schema references the APIResourceSchema that is bound to this API.", Default: map[string]interface{}{}, - Ref: ref(v1alpha1.BoundAPIResourceSchema{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.BoundAPIResourceSchema{}.OpenAPIModelName()), }, }, "storageVersions": { @@ -1520,7 +1765,7 @@ func schema_sdk_apis_apis_v1alpha1_BoundAPIResource(ref common.ReferenceCallback }, }, Dependencies: []string{ - v1alpha1.BoundAPIResourceSchema{}.OpenAPIModelName()}, + apisv1alpha1.BoundAPIResourceSchema{}.OpenAPIModelName()}, } } @@ -1580,7 +1825,7 @@ func schema_sdk_apis_apis_v1alpha1_CustomResourceConversion(ref common.Reference "webhook": { SchemaProps: spec.SchemaProps{ Description: "webhook describes how to call the conversion webhook. Required when `strategy` is set to `\"Webhook\"`.", - Ref: ref(v1alpha1.WebhookConversion{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.WebhookConversion{}.OpenAPIModelName()), }, }, }, @@ -1588,7 +1833,7 @@ func schema_sdk_apis_apis_v1alpha1_CustomResourceConversion(ref common.Reference }, }, Dependencies: []string{ - v1alpha1.WebhookConversion{}.OpenAPIModelName()}, + apisv1alpha1.WebhookConversion{}.OpenAPIModelName()}, } } @@ -1692,14 +1937,14 @@ func schema_sdk_apis_apis_v1alpha1_MaximalPermissionPolicy(ref common.ReferenceC "local": { SchemaProps: spec.SchemaProps{ Description: "local is the policy that is defined in same workspace as the API Export.", - Ref: ref(v1alpha1.LocalAPIExportPolicy{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.LocalAPIExportPolicy{}.OpenAPIModelName()), }, }, }, }, }, Dependencies: []string{ - v1alpha1.LocalAPIExportPolicy{}.OpenAPIModelName()}, + apisv1alpha1.LocalAPIExportPolicy{}.OpenAPIModelName()}, } } @@ -1740,7 +1985,7 @@ func schema_sdk_apis_apis_v1alpha1_PermissionClaim(ref common.ReferenceCallback) Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref(v1alpha1.ResourceSelector{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.ResourceSelector{}.OpenAPIModelName()), }, }, }, @@ -1758,7 +2003,7 @@ func schema_sdk_apis_apis_v1alpha1_PermissionClaim(ref common.ReferenceCallback) }, }, Dependencies: []string{ - v1alpha1.ResourceSelector{}.OpenAPIModelName()}, + apisv1alpha1.ResourceSelector{}.OpenAPIModelName()}, } } @@ -1846,7 +2091,7 @@ func schema_sdk_apis_apis_v1alpha1_WebhookConversion(ref common.ReferenceCallbac "clientConfig": { SchemaProps: spec.SchemaProps{ Description: "clientConfig is the instructions for how to call the webhook if strategy is `Webhook`.", - Ref: ref(v1alpha1.WebhookClientConfig{}.OpenAPIModelName()), + Ref: ref(apisv1alpha1.WebhookClientConfig{}.OpenAPIModelName()), }, }, "conversionReviewVersions": { @@ -1874,7 +2119,7 @@ func schema_sdk_apis_apis_v1alpha1_WebhookConversion(ref common.ReferenceCallbac }, }, Dependencies: []string{ - v1alpha1.WebhookClientConfig{}.OpenAPIModelName()}, + apisv1alpha1.WebhookClientConfig{}.OpenAPIModelName()}, } } diff --git a/test/e2e/apibinding/apiexport_identity_registry_test.go b/test/e2e/apibinding/apiexport_identity_registry_test.go new file mode 100644 index 00000000000..eae7ec050ea --- /dev/null +++ b/test/e2e/apibinding/apiexport_identity_registry_test.go @@ -0,0 +1,214 @@ +/* +Copyright 2026 The kcp 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 apibinding + +import ( + "fmt" + "net/url" + "testing" + "time" + + "github.com/stretchr/testify/require" + + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/discovery/cached/memory" + "k8s.io/client-go/rest" + "k8s.io/client-go/restmapper" + + kcpdynamic "github.com/kcp-dev/client-go/dynamic" + kcpkubernetesclientset "github.com/kcp-dev/client-go/kubernetes" + "github.com/kcp-dev/logicalcluster/v3" + adminv1alpha1 "github.com/kcp-dev/sdk/apis/admin/v1alpha1" + apisv1alpha1 "github.com/kcp-dev/sdk/apis/apis/v1alpha1" + apisv1alpha2 "github.com/kcp-dev/sdk/apis/apis/v1alpha2" + "github.com/kcp-dev/sdk/apis/core" + tenancyv1alpha1 "github.com/kcp-dev/sdk/apis/tenancy/v1alpha1" + "github.com/kcp-dev/sdk/apis/third_party/conditions/util/conditions" + kcpclientset "github.com/kcp-dev/sdk/client/clientset/versioned" + kcpclusterclientset "github.com/kcp-dev/sdk/client/clientset/versioned/cluster" + kcptesting "github.com/kcp-dev/sdk/testing" + kcptestinghelpers "github.com/kcp-dev/sdk/testing/helpers" + + "github.com/kcp-dev/kcp/config/helpers" + "github.com/kcp-dev/kcp/test/e2e/framework" +) + +// TestAPIExportIdentityRegistry exercises the identity view: the first +// APIExport to use an identity owns it, another APIExport with a copy of the +// key shares the identity and shows as a holder, the oldest holder +// inherits the identity when the owner is deleted, the entry is gone once +// the last export is deleted, and the next export with the key brings +// the identity anew. +func TestAPIExportIdentityRegistry(t *testing.T) { + t.Parallel() + framework.Suite(t, "control-plane") + + server := kcptesting.SharedKcpServer(t) + + orgPath, _ := kcptesting.NewWorkspaceFixture(t, server, core.RootCluster.Path(), kcptesting.WithType(core.RootCluster.Path(), "organization")) + ownerPath, ownerWS := kcptesting.NewWorkspaceFixture(t, server, orgPath) + holderPath, holderWS := kcptesting.NewWorkspaceFixture(t, server, orgPath) + newcomerPath, newcomerWS := kcptesting.NewWorkspaceFixture(t, server, orgPath) + + cfg := server.BaseConfig(t) + kcpClusterClient, err := kcpclusterclientset.NewForConfig(cfg) + require.NoError(t, err) + kubeClusterClient, err := kcpkubernetesclientset.NewForConfig(cfg) + require.NoError(t, err) + dynamicClusterClient, err := kcpdynamic.NewForConfig(cfg) + require.NoError(t, err) + + // the registry is read through the Admin workspace + vwCfg := rest.CopyConfig(cfg) + vwURL, err := url.Parse(cfg.Host) + require.NoError(t, err) + vwURL.Path = "/services/admin" + vwCfg.Host = vwURL.String() + adminClient, err := kcpclientset.NewForConfig(vwCfg) + require.NoError(t, err) + + for _, path := range []logicalcluster.Path{ownerPath, holderPath, newcomerPath} { + t.Logf("Install cowboys APIResourceSchema into %q", path) + mapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(kcpClusterClient.Cluster(path).Discovery())) + require.NoError(t, helpers.CreateResourceFromFS(t.Context(), dynamicClusterClient.Cluster(path), mapper, nil, "apiresourceschema_cowboys.yaml", testFiles)) + } + + newExport := func() *apisv1alpha2.APIExport { + return &apisv1alpha2.APIExport{ + ObjectMeta: metav1.ObjectMeta{Name: "wildwest"}, + Spec: apisv1alpha2.APIExportSpec{ + Resources: []apisv1alpha2.ResourceSchema{{ + Name: "cowboys", Group: "wildwest.dev", Schema: "today.cowboys.wildwest.dev", + Storage: apisv1alpha2.ResourceSchemaStorage{CRD: &apisv1alpha2.ResourceSchemaStorageCRD{}}, + }}, + }, + } + } + exportRef := func(ws *tenancyv1alpha1.Workspace, export *apisv1alpha2.APIExport) adminv1alpha1.ExportReference { + return adminv1alpha1.ExportReference{Cluster: ws.Spec.Cluster, Name: export.Name, UID: string(export.UID)} + } + getExport := func(path logicalcluster.Path) (*apisv1alpha2.APIExport, error) { + return kcpClusterClient.Cluster(path).ApisV1alpha2().APIExports().Get(t.Context(), "wildwest", metav1.GetOptions{}) + } + waitForIdentity := func(path logicalcluster.Path) *apisv1alpha2.APIExport { + t.Helper() + var export *apisv1alpha2.APIExport + kcptestinghelpers.Eventually(t, func() (bool, string) { + var err error + export, err = getExport(path) + if err != nil { + return false, err.Error() + } + cond := conditions.Get(export, apisv1alpha2.APIExportIdentityValid) + if cond == nil || cond.Status != corev1.ConditionTrue || export.Status.IdentityHash == "" { + return false, fmt.Sprintf("IdentityValid=%v identityHash=%q", cond, export.Status.IdentityHash) + } + return true, "" + }, wait.ForeverTestTimeout, 100*time.Millisecond) + return export + } + waitForEntry := func(identity string, timeout time.Duration, check func(*adminv1alpha1.APIIdentity) (bool, string)) { + t.Helper() + kcptestinghelpers.Eventually(t, func() (bool, string) { + entry, err := adminClient.AdminV1alpha1().APIIdentities().Get(t.Context(), identity, metav1.GetOptions{}) + if err != nil { + return false, err.Error() + } + return check(entry) + }, timeout, 250*time.Millisecond) + } + copyKey := func(from, to logicalcluster.Path) { + t.Helper() + secret, err := kubeClusterClient.Cluster(from).CoreV1().Secrets("kcp-system").Get(t.Context(), "wildwest", metav1.GetOptions{}) + require.NoError(t, err) + _, err = kubeClusterClient.Cluster(to).CoreV1().Namespaces().Create(t.Context(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "kcp-system"}}, metav1.CreateOptions{}) + require.True(t, err == nil || apierrors.IsAlreadyExists(err), "creating kcp-system: %v", err) + _, err = kubeClusterClient.Cluster(to).CoreV1().Secrets("kcp-system").Create(t.Context(), &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "wildwest"}, + Data: map[string][]byte{apisv1alpha1.SecretKeyAPIExportIdentity: secret.Data[apisv1alpha1.SecretKeyAPIExportIdentity]}, + }, metav1.CreateOptions{}) + require.NoError(t, err) + } + createWithKey := func(path logicalcluster.Path) { + t.Helper() + export := newExport() + export.Spec.Identity = &apisv1alpha2.Identity{SecretRef: &corev1.SecretReference{Namespace: "kcp-system", Name: "wildwest"}} + _, err := kcpClusterClient.Cluster(path).ApisV1alpha2().APIExports().Create(t.Context(), export, metav1.CreateOptions{}) + require.NoError(t, err) + } + + t.Logf("Create the owner APIExport in %q: it claims the identity", ownerPath) + _, err = kcpClusterClient.Cluster(ownerPath).ApisV1alpha2().APIExports().Create(t.Context(), newExport(), metav1.CreateOptions{}) + require.NoError(t, err) + owner := waitForIdentity(ownerPath) + identity := owner.Status.IdentityHash + waitForEntry(identity, wait.ForeverTestTimeout, func(entry *adminv1alpha1.APIIdentity) (bool, string) { + return entry.Status.Owner == exportRef(ownerWS, owner) && len(entry.Status.Holders) == 0 && !entry.Status.Core, + fmt.Sprintf("owner=%v holders=%v core=%v", entry.Status.Owner, entry.Status.Holders, entry.Status.Core) + }) + + t.Logf("Copy the owner's key into %q: the holder shares the identity and is recorded", holderPath) + copyKey(ownerPath, holderPath) + // The owner is the oldest export by creationTimestamp, which has second + // precision; an export created in the same second as the owner ties and + // the tie is broken by cluster name. Let the second pass so that the + // holder is strictly younger. + time.Sleep(time.Until(owner.CreationTimestamp.Add(time.Second))) + createWithKey(holderPath) + holder := waitForIdentity(holderPath) + require.Equal(t, identity, holder.Status.IdentityHash, "same key, same identity") + waitForEntry(identity, wait.ForeverTestTimeout, func(entry *adminv1alpha1.APIIdentity) (bool, string) { + if len(entry.Status.Holders) != 1 || !entry.Status.Holders[0].Equal(exportRef(holderWS, holder)) { + return false, fmt.Sprintf("holders=%v", entry.Status.Holders) + } + return conditions.IsTrue(entry, adminv1alpha1.APIIdentityShared), "not marked Shared yet" + }) + + t.Logf("Delete the owner: the holder becomes the owner and keeps serving") + require.NoError(t, kcpClusterClient.Cluster(ownerPath).ApisV1alpha2().APIExports().Delete(t.Context(), "wildwest", metav1.DeleteOptions{})) + waitForEntry(identity, wait.ForeverTestTimeout, func(entry *adminv1alpha1.APIIdentity) (bool, string) { + if !entry.Status.Owner.Equal(exportRef(holderWS, holder)) { + return false, fmt.Sprintf("owner=%v", entry.Status.Owner) + } + return len(entry.Status.Holders) == 0 && conditions.Get(entry, adminv1alpha1.APIIdentityShared) == nil, + fmt.Sprintf("holders=%v shared=%v", entry.Status.Holders, conditions.Get(entry, adminv1alpha1.APIIdentityShared)) + }) + holder, err = getExport(holderPath) + require.NoError(t, err) + require.Equal(t, identity, holder.Status.IdentityHash, "the identity does not change hands' value") + require.True(t, conditions.IsTrue(holder, apisv1alpha2.APIExportIdentityValid)) + + t.Logf("Delete the last export: the identity is gone from the registry") + copyKey(holderPath, newcomerPath) // keep the key around for the newcomer + require.NoError(t, kcpClusterClient.Cluster(holderPath).ApisV1alpha2().APIExports().Delete(t.Context(), "wildwest", metav1.DeleteOptions{})) + kcptestinghelpers.Eventually(t, func() (bool, string) { + _, err := adminClient.AdminV1alpha1().APIIdentities().Get(t.Context(), identity, metav1.GetOptions{}) + return apierrors.IsNotFound(err), fmt.Sprintf("get: %v", err) + }, wait.ForeverTestTimeout, 250*time.Millisecond) + + t.Logf("A newcomer with the key registers the identity anew") + createWithKey(newcomerPath) + newcomer := waitForIdentity(newcomerPath) + require.Equal(t, identity, newcomer.Status.IdentityHash, "same key, same identity") + waitForEntry(identity, wait.ForeverTestTimeout, func(entry *adminv1alpha1.APIIdentity) (bool, string) { + return entry.Status.Owner == exportRef(newcomerWS, newcomer) && len(entry.Status.Holders) == 0, + fmt.Sprintf("owner=%v holders=%v", entry.Status.Owner, entry.Status.Holders) + }) +} diff --git a/test/e2e/virtual/admin/apiidentities_test.go b/test/e2e/virtual/admin/apiidentities_test.go new file mode 100644 index 00000000000..d9488a5f1be --- /dev/null +++ b/test/e2e/virtual/admin/apiidentities_test.go @@ -0,0 +1,171 @@ +/* +Copyright 2026 The kcp 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 admin + +import ( + "context" + "net/url" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/rest" + + "github.com/kcp-dev/logicalcluster/v3" + adminv1alpha1 "github.com/kcp-dev/sdk/apis/admin/v1alpha1" + apisv1alpha2 "github.com/kcp-dev/sdk/apis/apis/v1alpha2" + "github.com/kcp-dev/sdk/apis/core" + kcpclientset "github.com/kcp-dev/sdk/client/clientset/versioned" + kcpclusterclientset "github.com/kcp-dev/sdk/client/clientset/versioned/cluster" + kcptesting "github.com/kcp-dev/sdk/testing" + + "github.com/kcp-dev/kcp/test/e2e/framework" +) + +// TestAdminWorkspaceAPIIdentities exercises the identity registry view of the +// Admin workspace (/services/admin): the core identities are listed with +// their root exports as owners, a new export's identity shows up with the +// export as owner and is delivered on a watch, bookkeeping annotations are +// stripped, and the view is read-only. +func TestAdminWorkspaceAPIIdentities(t *testing.T) { + t.Parallel() + framework.Suite(t, "control-plane") + + server := kcptesting.SharedKcpServer(t) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + cfg := server.BaseConfig(t) + kcpClusterClient, err := kcpclusterclientset.NewForConfig(cfg) + require.NoError(t, err) + + vwCfg := rest.CopyConfig(cfg) + vwURL, err := url.Parse(cfg.Host) + require.NoError(t, err) + vwURL.Path = "/services/admin" + vwCfg.Host = vwURL.String() + adminClient, err := kcpclientset.NewForConfig(vwCfg) + require.NoError(t, err) + + t.Logf("The core identities are registered with their root exports as owners") + tenancyExport, err := kcpClusterClient.Cluster(core.RootCluster.Path()).ApisV1alpha2().APIExports().Get(ctx, "tenancy.kcp.io", metav1.GetOptions{}) + require.NoError(t, err) + require.NotEmpty(t, tenancyExport.Status.IdentityHash) + + var registry *adminv1alpha1.APIIdentityList + require.EventuallyWithT(t, func(c *assert.CollectT) { + registry, err = adminClient.AdminV1alpha1().APIIdentities().List(ctx, metav1.ListOptions{}) + require.NoError(c, err) + var tenancy *adminv1alpha1.APIIdentity + for i := range registry.Items { + require.NotContains(c, registry.Items[i].Annotations, "kcp.io/shard", "cache bookkeeping annotations must be stripped") + if registry.Items[i].Name == tenancyExport.Status.IdentityHash { + tenancy = ®istry.Items[i] + } + } + require.NotNil(c, tenancy, "the tenancy.kcp.io identity must be registered") + require.True(c, tenancy.Status.Core, "tenancy.kcp.io is a core identity") + require.Equal(c, adminv1alpha1.ExportReference{Cluster: core.RootCluster.String(), Name: "tenancy.kcp.io", UID: string(tenancyExport.UID)}, tenancy.Status.Owner) + require.Equal(c, adminv1alpha1.Labels(tenancy.Status.Owner, true), tenancy.Labels) + }, wait.ForeverTestTimeout, 100*time.Millisecond) + + t.Logf("Select identities by label: core, by owner cluster, by owner name") + for selector, want := range map[string]func(*adminv1alpha1.APIIdentity) bool{ + adminv1alpha1.APIIdentityCoreLabel + "=true": func(e *adminv1alpha1.APIIdentity) bool { return e.Status.Core }, + adminv1alpha1.APIIdentityOwnerClusterLabel + "=" + core.RootCluster.String(): func(e *adminv1alpha1.APIIdentity) bool { return e.Status.Owner.Cluster == core.RootCluster.String() }, + adminv1alpha1.APIIdentityOwnerLabel + "=tenancy.kcp.io": func(e *adminv1alpha1.APIIdentity) bool { return e.Status.Owner.Name == "tenancy.kcp.io" }, + } { + selected, err := adminClient.AdminV1alpha1().APIIdentities().List(ctx, metav1.ListOptions{LabelSelector: selector}) + require.NoError(t, err, "selector %q", selector) + require.NotEmpty(t, selected.Items, "selector %q selected nothing", selector) + for i := range selected.Items { + require.True(t, want(&selected.Items[i]), "selector %q selected %s, owner %v core %v", selector, selected.Items[i].Name, selected.Items[i].Status.Owner, selected.Items[i].Status.Core) + } + } + + t.Logf("Get the core identity by name through the Admin workspace") + for _, rv := range []string{"", "0", registry.ResourceVersion} { + entry, err := adminClient.AdminV1alpha1().APIIdentities().Get(ctx, tenancyExport.Status.IdentityHash, metav1.GetOptions{ResourceVersion: rv}) + require.NoError(t, err, "failed to get identity with resourceVersion %q", rv) + require.Equal(t, tenancyExport.Status.IdentityHash, entry.Name) + require.NotContains(t, entry.Annotations, "kcp.io/shard", "cache bookkeeping annotations must be stripped") + } + _, err = adminClient.AdminV1alpha1().APIIdentities().Get(ctx, "does-not-exist", metav1.GetOptions{}) + require.True(t, apierrors.IsNotFound(err), "expected NotFound, got %v", err) + + t.Logf("A new APIExport's identity is registered with the export as owner, and delivered on a watch") + w, err := adminClient.AdminV1alpha1().APIIdentities().Watch(ctx, metav1.ListOptions{ResourceVersion: registry.ResourceVersion}) + require.NoError(t, err) + t.Cleanup(w.Stop) + + orgPath, _ := kcptesting.NewWorkspaceFixture(t, server, core.RootCluster.Path(), kcptesting.WithType(core.RootCluster.Path(), "organization")) + providerPath, providerWS := kcptesting.NewWorkspaceFixture(t, server, orgPath) + export, err := kcpClusterClient.Cluster(providerPath).ApisV1alpha2().APIExports().Create(ctx, &apisv1alpha2.APIExport{ + ObjectMeta: metav1.ObjectMeta{Name: "widgets"}, + }, metav1.CreateOptions{}) + require.NoError(t, err) + + require.EventuallyWithT(t, func(c *assert.CollectT) { + export, err = kcpClusterClient.Cluster(providerPath).ApisV1alpha2().APIExports().Get(ctx, export.Name, metav1.GetOptions{}) + require.NoError(c, err) + require.NotEmpty(c, export.Status.IdentityHash, "the export must get an identity") + }, wait.ForeverTestTimeout, 100*time.Millisecond) + + want := adminv1alpha1.ExportReference{Cluster: logicalcluster.Name(providerWS.Spec.Cluster).String(), Name: export.Name, UID: string(export.UID)} + require.EventuallyWithT(t, func(c *assert.CollectT) { + entry, err := adminClient.AdminV1alpha1().APIIdentities().Get(ctx, export.Status.IdentityHash, metav1.GetOptions{}) + require.NoError(c, err) + require.Equal(c, want, entry.Status.Owner) + require.False(c, entry.Status.Core) + require.NotContains(c, entry.Annotations, "kcp.io/shard", "cache bookkeeping annotations must be stripped") + }, wait.ForeverTestTimeout, 100*time.Millisecond) + + deadline := time.After(wait.ForeverTestTimeout) + for seen := false; !seen; { + select { + case event, ok := <-w.ResultChan(): + require.True(t, ok, "the watch closed before the new identity was delivered") + if event.Type == watch.Bookmark || event.Type == watch.Error { + continue + } + entry, ok := event.Object.(*adminv1alpha1.APIIdentity) + require.True(t, ok, "unexpected watch object %T", event.Object) + require.NotContains(t, entry.Annotations, "kcp.io/shard", "cache bookkeeping annotations must be stripped") + seen = entry.Name == export.Status.IdentityHash && entry.Status.Owner.Equal(want) + case <-deadline: + t.Fatalf("timed out waiting for the watch to deliver identity %s", export.Status.IdentityHash) + } + } + + t.Logf("The registry is read-only through the Admin workspace") + entry, err := adminClient.AdminV1alpha1().APIIdentities().Get(ctx, export.Status.IdentityHash, metav1.GetOptions{}) + require.NoError(t, err) + entry.Status.Core = true + _, err = adminClient.AdminV1alpha1().APIIdentities().Update(ctx, entry, metav1.UpdateOptions{}) + require.Error(t, err) + require.True(t, apierrors.IsMethodNotSupported(err) || apierrors.IsForbidden(err), "expected the update to be rejected, got %v", err) + err = adminClient.AdminV1alpha1().APIIdentities().Delete(ctx, entry.Name, metav1.DeleteOptions{}) + require.Error(t, err) + require.True(t, apierrors.IsMethodNotSupported(err) || apierrors.IsForbidden(err), "expected the delete to be rejected, got %v", err) +}