From 42b179e5fb87adcc8bfaba5f8576fb797a548b34 Mon Sep 17 00:00:00 2001 From: Arnob kumar saha Date: Thu, 9 Apr 2026 18:16:59 +0600 Subject: [PATCH] Implement database expose util Signed-off-by: Arnob kumar saha --- go.mod | 1 + go.sum | 2 + pkg/cmds/expose/expose.go | 41 ++ pkg/cmds/expose/gateway/gatway.go | 180 +++++++ pkg/cmds/root.go | 2 + vendor/k8s.io/apiserver/LICENSE | 202 ++++++++ .../apiserver/pkg/authentication/user/doc.go | 19 + .../apiserver/pkg/authentication/user/user.go | 88 ++++ .../kmodules.xyz/client-go/client/client.go | 270 ++++++++++ .../client-go/client/delegated.go | 235 +++++++++ .../client-go/client/retryclient.go | 244 +++++++++ .../kmodules.xyz/client-go/client/sa-token.go | 115 +++++ .../client-go/client/typeclient.go | 465 ++++++++++++++++++ vendor/modules.txt | 4 + 14 files changed, 1868 insertions(+) create mode 100644 pkg/cmds/expose/expose.go create mode 100644 pkg/cmds/expose/gateway/gatway.go create mode 100644 vendor/k8s.io/apiserver/LICENSE create mode 100644 vendor/k8s.io/apiserver/pkg/authentication/user/doc.go create mode 100644 vendor/k8s.io/apiserver/pkg/authentication/user/user.go create mode 100644 vendor/kmodules.xyz/client-go/client/client.go create mode 100644 vendor/kmodules.xyz/client-go/client/delegated.go create mode 100644 vendor/kmodules.xyz/client-go/client/retryclient.go create mode 100644 vendor/kmodules.xyz/client-go/client/sa-token.go create mode 100644 vendor/kmodules.xyz/client-go/client/typeclient.go diff --git a/go.mod b/go.mod index 41dd733f..e1a6c6ae 100644 --- a/go.mod +++ b/go.mod @@ -245,6 +245,7 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.34.3 // indirect + k8s.io/apiserver v0.34.3 // indirect k8s.io/component-base v0.34.3 // indirect k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect diff --git a/go.sum b/go.sum index a123139d..64f80d5b 100644 --- a/go.sum +++ b/go.sum @@ -408,6 +408,8 @@ github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4O github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM= github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw= +github.com/kmodules/apiserver v0.34.4-0.20251227112449-07fa35efc6fc h1:R5bKc1c8Qu7z+7+O0xNWxIPjCYuaHUVZ+dSfeCZEd+c= +github.com/kmodules/apiserver v0.34.4-0.20251227112449-07fa35efc6fc/go.mod h1:QPnnahMO5C2m3lm6fPW3+JmyQbvHZQ8uudAu/493P2w= github.com/kmodules/controller-runtime v0.22.5-0.20251227114913-f011264689cd h1:cpLV7Pr+pSo3kDYY4HsLZfbdF1WPQuPTP+Jo3hyoWzw= github.com/kmodules/controller-runtime v0.22.5-0.20251227114913-f011264689cd/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= diff --git a/pkg/cmds/expose/expose.go b/pkg/cmds/expose/expose.go new file mode 100644 index 00000000..369144d2 --- /dev/null +++ b/pkg/cmds/expose/expose.go @@ -0,0 +1,41 @@ +/* +Copyright AppsCode Inc. and Contributors + +Licensed under the AppsCode Community License 1.0.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://github.com/appscode/licenses/raw/1.0.0/AppsCode-Community-1.0.0.md + +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 expose + +import ( + "go.bytebuilders.dev/cli/pkg/cmds/expose/gateway" + + "github.com/spf13/cobra" + "k8s.io/cli-runtime/pkg/genericclioptions" + cmdutil "k8s.io/kubectl/pkg/cmd/util" +) + +func NewCmdExpose() *cobra.Command { + cmd := &cobra.Command{ + Use: "expose", + Short: "Expose databases", + DisableAutoGenTag: true, + } + + kubeConfigFlags := genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag() + matchVersionKubeConfigFlags := cmdutil.NewMatchVersionFlags(kubeConfigFlags) + f := cmdutil.NewFactory(matchVersionKubeConfigFlags) + + cmd.AddCommand(gateway.NewCmdGateway(f)) + + return cmd +} diff --git a/pkg/cmds/expose/gateway/gatway.go b/pkg/cmds/expose/gateway/gatway.go new file mode 100644 index 00000000..c1dcf0c0 --- /dev/null +++ b/pkg/cmds/expose/gateway/gatway.go @@ -0,0 +1,180 @@ +/* +Copyright AppsCode Inc. and Contributors + +Licensed under the AppsCode Community License 1.0.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://github.com/appscode/licenses/raw/1.0.0/AppsCode-Community-1.0.0.md + +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 gateway + +import ( + "context" + "log" + + catalogapi "go.bytebuilders.dev/catalog/api/catalog/v1alpha1" + catgwapi "go.bytebuilders.dev/catalog/api/gateway/v1alpha1" + + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/discovery" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/klog/v2" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + kutil "kmodules.xyz/client-go" + cu "kmodules.xyz/client-go/client" + dbapi "kubedb.dev/apimachinery/apis/kubedb/v1" + kubedbscheme "kubedb.dev/apimachinery/client/clientset/versioned/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var scheme = runtime.NewScheme() + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + utilruntime.Must(catalogapi.AddToScheme(scheme)) + utilruntime.Must(catgwapi.AddToScheme(scheme)) + utilruntime.Must(kubedbscheme.AddToScheme(scheme)) +} + +func NewCmdGateway(f cmdutil.Factory) *cobra.Command { + opt := newGatewayOpts(f) + cmd := &cobra.Command{ + Use: "gateway", + Short: "Gateway related info", + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + return opt.run() + }, + } + + cmd.Flags().StringVarP(&opt.db.resource, "db-type", "t", "mongodb", "Database type") + cmd.Flags().StringVarP(&opt.db.name, "name", "m", "mg-test", "Database name") + cmd.Flags().StringVarP(&opt.db.namespace, "namespace", "n", "demo", "Database namespace") + return cmd +} + +type gatewayOpts struct { + kc client.Client + disc *discovery.DiscoveryClient + config *rest.Config + db dbInfo + + mapResourceToKind map[string]string + mapSingularToKind map[string]string +} + +type dbInfo struct { + resource string + name string + namespace string +} + +func newGatewayOpts(f cmdutil.Factory) *gatewayOpts { + config, err := f.ToRESTConfig() + if err != nil { + log.Fatal(err) + } + kc, err := client.New(config, client.Options{Scheme: scheme}) + if err != nil { + log.Fatalf("failed to create client: %v", err) + } + + disc, err := discovery.NewDiscoveryClientForConfig(config) + if err != nil { + log.Fatal("creating discovery client: %w", err) + } + + return &gatewayOpts{kc: kc, config: config, disc: disc} +} + +func (g *gatewayOpts) run() error { + err := g.initMap() + if err != nil { + return err + } + kind := g.resolveKind(g.db.resource) + "Binding" + var binding unstructured.Unstructured + binding.SetGroupVersionKind(schema.GroupVersionKind{ + Group: catalogapi.GroupVersion.Group, + Version: catalogapi.GroupVersion.Version, + Kind: kind, + }) + binding.SetNamespace(g.db.namespace) + binding.SetName(g.db.name) + + // Set spec.sourceRef + sourceRef := map[string]any{ + "name": g.db.name, + } + + if g.db.namespace != "" { + sourceRef["namespace"] = g.db.namespace + } + + if err := unstructured.SetNestedField(binding.Object, sourceRef, "spec", "sourceRef"); err != nil { + return err + } + + vt, err := cu.CreateOrPatch(context.TODO(), g.kc, &binding, func(obj client.Object, createOp bool) client.Object { + return obj + // in := obj.(*catalogapi.BindingInterface) + // return in + }) + if vt != kutil.VerbUnchanged { + klog.Infof("%s/%s of kind %s has been %s", g.db.namespace, g.db.name, kind, vt) + } + return err +} + +func (g *gatewayOpts) initMap() error { + preferredResources, err := g.disc.ServerPreferredResources() + if err != nil && !discovery.IsGroupDiscoveryFailedError(err) { + return err + } + + g.mapSingularToKind = make(map[string]string) + g.mapResourceToKind = make(map[string]string) + + for _, p := range preferredResources { + // if p.GroupVersionKind().Group != dbapi.SchemeGroupVersion.Group {continue } + // This can't be done. Cause p.GroupVersionKind() is empty somehow + for _, res := range p.APIResources { + if res.Group != dbapi.SchemeGroupVersion.Group { + continue + } + g.entry(res) + } + } + return nil +} + +func (g *gatewayOpts) entry(res metav1.APIResource) { + g.mapResourceToKind[res.Name] = res.Kind + g.mapSingularToKind[res.SingularName] = res.Kind +} + +func (g *gatewayOpts) resolveKind(s string) string { + val, exists := g.mapSingularToKind[s] + if exists { + return val + } + val, exists = g.mapResourceToKind[s] + if exists { + return val + } + return s +} diff --git a/pkg/cmds/root.go b/pkg/cmds/root.go index 8b012fc4..540c5ebb 100644 --- a/pkg/cmds/root.go +++ b/pkg/cmds/root.go @@ -26,6 +26,7 @@ import ( "go.bytebuilders.dev/cli/pkg/cmds/cluster" cmdconfig "go.bytebuilders.dev/cli/pkg/cmds/config" "go.bytebuilders.dev/cli/pkg/cmds/debug" + "go.bytebuilders.dev/cli/pkg/cmds/expose" "go.bytebuilders.dev/cli/pkg/cmds/installer" "go.bytebuilders.dev/cli/pkg/config" ace "go.bytebuilders.dev/client" @@ -56,6 +57,7 @@ func NewRootCmd() *cobra.Command { rootCmd.AddCommand(installer.NewCmdInstaller()) rootCmd.AddCommand(debug.NewCmdDebug()) + rootCmd.AddCommand(expose.NewCmdExpose()) rootCmd.AddCommand(v.NewCmdVersion()) rootCmd.AddCommand(NewCmdCompletion()) diff --git a/vendor/k8s.io/apiserver/LICENSE b/vendor/k8s.io/apiserver/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/vendor/k8s.io/apiserver/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/vendor/k8s.io/apiserver/pkg/authentication/user/doc.go b/vendor/k8s.io/apiserver/pkg/authentication/user/doc.go new file mode 100644 index 00000000..570c51ae --- /dev/null +++ b/vendor/k8s.io/apiserver/pkg/authentication/user/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package user contains utilities for dealing with simple user exchange in the auth +// packages. The user.Info interface defines an interface for exchanging that info. +package user diff --git a/vendor/k8s.io/apiserver/pkg/authentication/user/user.go b/vendor/k8s.io/apiserver/pkg/authentication/user/user.go new file mode 100644 index 00000000..1af6f2b2 --- /dev/null +++ b/vendor/k8s.io/apiserver/pkg/authentication/user/user.go @@ -0,0 +1,88 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package user + +// Info describes a user that has been authenticated to the system. +type Info interface { + // GetName returns the name that uniquely identifies this user among all + // other active users. + GetName() string + // GetUID returns a unique value for a particular user that will change + // if the user is removed from the system and another user is added with + // the same name. + GetUID() string + // GetGroups returns the names of the groups the user is a member of + GetGroups() []string + + // GetExtra can contain any additional information that the authenticator + // thought was interesting. One example would be scopes on a token. + // Keys in this map should be namespaced to the authenticator or + // authenticator/authorizer pair making use of them. + // For instance: "example.org/foo" instead of "foo" + // This is a map[string][]string because it needs to be serializeable into + // a SubjectAccessReviewSpec.authorization.k8s.io for proper authorization + // delegation flows + // In order to faithfully round-trip through an impersonation flow, these keys + // MUST be lowercase. + GetExtra() map[string][]string +} + +// DefaultInfo provides a simple user information exchange object +// for components that implement the UserInfo interface. +type DefaultInfo struct { + Name string + UID string + Groups []string + Extra map[string][]string +} + +func (i *DefaultInfo) GetName() string { + return i.Name +} + +func (i *DefaultInfo) GetUID() string { + return i.UID +} + +func (i *DefaultInfo) GetGroups() []string { + return i.Groups +} + +func (i *DefaultInfo) GetExtra() map[string][]string { + return i.Extra +} + +const ( + // well-known user and group names + SystemPrivilegedGroup = "system:masters" + NodesGroup = "system:nodes" + MonitoringGroup = "system:monitoring" + AllUnauthenticated = "system:unauthenticated" + AllAuthenticated = "system:authenticated" + + Anonymous = "system:anonymous" + APIServerUser = "system:apiserver" + + // core kubernetes process identities + KubeProxy = "system:kube-proxy" + KubeControllerManager = "system:kube-controller-manager" + KubeScheduler = "system:kube-scheduler" + + // CredentialIDKey is the key used in a user's "extra" to specify the unique + // identifier for this identity document). + CredentialIDKey = "authentication.kubernetes.io/credential-id" +) diff --git a/vendor/kmodules.xyz/client-go/client/client.go b/vendor/kmodules.xyz/client-go/client/client.go new file mode 100644 index 00000000..e71042c2 --- /dev/null +++ b/vendor/kmodules.xyz/client-go/client/client.go @@ -0,0 +1,270 @@ +/* +Copyright AppsCode Inc. and Contributors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client + +import ( + "context" + "reflect" + "strings" + + "kmodules.xyz/client-go/meta" + + "github.com/pkg/errors" + kerr "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/klog/v2" + kutil "kmodules.xyz/client-go" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" +) + +func NewUncachedClient(cfg *rest.Config, funcs ...func(*runtime.Scheme) error) (client.Client, error) { + hc, err := rest.HTTPClientFor(cfg) + if err != nil { + return nil, err + } + mapper, err := apiutil.NewDynamicRESTMapper(cfg, hc) + if err != nil { + return nil, err + } + + builder := runtime.NewSchemeBuilder(funcs...) + builder.Register(clientgoscheme.AddToScheme) + scheme := runtime.NewScheme() + err = builder.AddToScheme(scheme) + if err != nil { + return nil, err + } + + return client.New(cfg, client.Options{ + Scheme: scheme, + Mapper: mapper, + //Opts: client.WarningHandlerOptions{ + // SuppressWarnings: false, + // AllowDuplicateLogs: false, + //}, + }) +} + +type ( + TransformFunc func(obj client.Object, createOp bool) client.Object + TransformFuncE func(obj client.Object, createOp bool) (client.Object, error) + PatchFunc func(obj client.Object) client.Object + PatchFuncE func(obj client.Object) (client.Object, error) +) + +func CreateOrPatchE(ctx context.Context, c client.Client, obj client.Object, transform TransformFuncE, opts ...client.PatchOption) (kutil.VerbType, error) { + gvk, err := apiutil.GVKForObject(obj, c.Scheme()) + if err != nil { + return kutil.VerbUnchanged, errors.Wrapf(err, "failed to get GVK for object %T", obj) + } + + cur := obj.DeepCopyObject().(client.Object) + key := types.NamespacedName{ + Namespace: cur.GetNamespace(), + Name: cur.GetName(), + } + err = c.Get(ctx, key, cur) + if kerr.IsNotFound(err) { + klog.V(3).Infof("Creating %+v %s/%s.", gvk, key.Namespace, key.Name) + + createOpts := make([]client.CreateOption, 0, len(opts)) + for i := range opts { + if opt, ok := opts[i].(client.CreateOption); ok { + createOpts = append(createOpts, opt) + } + } + mod, err := transform(obj.DeepCopyObject().(client.Object), true) + if err != nil { + return kutil.VerbUnchanged, err + } + mod.SetResourceVersion("") + err = c.Create(ctx, mod, createOpts...) + if err != nil { + return kutil.VerbUnchanged, err + } + + assign(obj, mod) + return kutil.VerbCreated, err + } else if err != nil { + return kutil.VerbUnchanged, err + } + + _, unstructuredObj := obj.(*unstructured.Unstructured) + + var patch client.Patch + if isOfficialTypes(gvk.Group) && !unstructuredObj { + patch = client.StrategicMergeFrom(cur) + } else { + patch = client.MergeFrom(cur) + } + mod, err := transform(cur.DeepCopyObject().(client.Object), false) + if err != nil { + return kutil.VerbUnchanged, err + } + err = c.Patch(ctx, mod, patch, opts...) + if err != nil { + return kutil.VerbUnchanged, err + } + + vt := kutil.VerbUnchanged + if mod.GetGeneration() > 0 { + if cur.GetGeneration() != mod.GetGeneration() { + vt = kutil.VerbPatched + } + } else { + // Secret, ServiceAccount etc resources do not use metadata.generation + if meta.ObjectHash(cur) != meta.ObjectHash(mod) { + vt = kutil.VerbPatched + } + } + assign(obj, mod) + return vt, nil +} + +func CreateOrPatch(ctx context.Context, c client.Client, obj client.Object, transform TransformFunc, opts ...client.PatchOption) (kutil.VerbType, error) { + return CreateOrPatchE(ctx, c, obj, func(obj client.Object, createOp bool) (client.Object, error) { + return transform(obj, createOp), nil + }, opts...) +} + +func PatchE(ctx context.Context, c client.Client, obj client.Object, transform PatchFuncE, opts ...client.PatchOption) (kutil.VerbType, error) { + gvk, err := apiutil.GVKForObject(obj, c.Scheme()) + if err != nil { + return kutil.VerbUnchanged, errors.Wrapf(err, "failed to get GVK for object %T", obj) + } + + _, unstructuredObj := obj.(*unstructured.Unstructured) + + var patch client.Patch + if isOfficialTypes(gvk.Group) && !unstructuredObj { + patch = client.StrategicMergeFrom(obj) + } else { + patch = client.MergeFrom(obj) + } + mod, err := transform(obj.DeepCopyObject().(client.Object)) + if err != nil { + return kutil.VerbUnchanged, err + } + err = c.Patch(ctx, mod, patch, opts...) + if err != nil { + return kutil.VerbUnchanged, err + } + + vt := kutil.VerbUnchanged + if mod.GetGeneration() > 0 { + if obj.GetGeneration() != mod.GetGeneration() { + vt = kutil.VerbPatched + } + } else { + // Secret, ServiceAccount etc resources do not use metadata.generation + if meta.ObjectHash(obj) != meta.ObjectHash(mod) { + vt = kutil.VerbPatched + } + } + assign(obj, mod) + return vt, nil +} + +func Patch(ctx context.Context, c client.Client, obj client.Object, transform PatchFunc, opts ...client.PatchOption) (kutil.VerbType, error) { + return PatchE(ctx, c, obj, func(obj client.Object) (client.Object, error) { + return transform(obj), nil + }, opts...) +} + +func assign(target, src any) { + srcValue := reflect.ValueOf(src) + if srcValue.Kind() == reflect.Pointer { + srcValue = srcValue.Elem() + } + reflect.ValueOf(target).Elem().Set(srcValue) +} + +func PatchStatusE(ctx context.Context, c client.Client, obj client.Object, transform PatchFuncE, opts ...client.SubResourcePatchOption) (kutil.VerbType, error) { + cur := obj.DeepCopyObject().(client.Object) + key := types.NamespacedName{ + Namespace: cur.GetNamespace(), + Name: cur.GetName(), + } + err := c.Get(ctx, key, cur) + if err != nil { + return kutil.VerbUnchanged, err + } + + // The body of the request was in an unknown format - + // accepted media types include: + // - application/json-patch+json, + // - application/merge-patch+json, + // - application/apply-patch+yaml + patch := client.MergeFrom(cur) + mod, err := transform(cur.DeepCopyObject().(client.Object)) + if err != nil { + return kutil.VerbUnchanged, err + } + err = c.Status().Patch(ctx, mod, patch, opts...) + if err != nil { + return kutil.VerbUnchanged, err + } + assign(obj, mod) + return kutil.VerbPatched, nil +} + +func PatchStatus(ctx context.Context, c client.Client, obj client.Object, transform PatchFunc, opts ...client.SubResourcePatchOption) (kutil.VerbType, error) { + return PatchStatusE(ctx, c, obj, func(obj client.Object) (client.Object, error) { + return transform(obj), nil + }, opts...) +} + +func isOfficialTypes(group string) bool { + return !strings.ContainsRune(group, '.') +} + +func GetForGVR(ctx context.Context, c client.Client, gvr schema.GroupVersionResource, ref types.NamespacedName) (client.Object, error) { + gvk, err := c.RESTMapper().KindFor(gvr) + if err != nil { + return nil, err + } + o, err := c.Scheme().New(gvk) + if err != nil { + return nil, err + } + obj := o.(client.Object) + err = c.Get(ctx, ref, obj) + return obj, err +} + +func GetForGVK(ctx context.Context, c client.Client, gvk schema.GroupVersionKind, ref types.NamespacedName) (client.Object, error) { + if gvk.Version == "" { + mapping, err := c.RESTMapper().RESTMapping(gvk.GroupKind()) + if err != nil { + return nil, err + } + gvk = mapping.GroupVersionKind + } + o, err := c.Scheme().New(gvk) + if err != nil { + return nil, err + } + obj := o.(client.Object) + err = c.Get(ctx, ref, obj) + return obj, err +} diff --git a/vendor/kmodules.xyz/client-go/client/delegated.go b/vendor/kmodules.xyz/client-go/client/delegated.go new file mode 100644 index 00000000..6a4c4eca --- /dev/null +++ b/vendor/kmodules.xyz/client-go/client/delegated.go @@ -0,0 +1,235 @@ +/* +Copyright AppsCode Inc. and Contributors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client + +import ( + "context" + "net/http" + "strings" + + apiutil2 "kmodules.xyz/client-go/client/apiutil" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/authentication/user" + restclient "k8s.io/client-go/rest" + "k8s.io/client-go/transport" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" +) + +// Adapted from https://github.com/kubernetes-sigs/controller-runtime/blob/master/pkg/client/split.go +// Uses discovery apis to correctly detect if an api type can be cached. + +// NewDelegatingClientInput encapsulates the input parameters to create a new delegating client. +type NewDelegatingClientInput struct { + config *restclient.Config + options client.Options + + CacheReader client.Reader + Client client.Client + UncachedObjects []client.Object + CacheUnstructured bool + Cachable apiutil2.Cachable +} + +// NewDelegatingClient creates a new delegating client. +// +// A delegating client forms a Client by composing separate reader, writer and +// statusclient interfaces. This way, you can have an Client that reads from a +// cache and writes to the API server. +func NewDelegatingClient(in NewDelegatingClientInput) (client.Client, error) { + uncachedGVKs := map[schema.GroupVersionKind]struct{}{} + for _, obj := range in.UncachedObjects { + gvk, err := apiutil.GVKForObject(obj, in.Client.Scheme()) + if err != nil { + return nil, err + } + uncachedGVKs[gvk] = struct{}{} + } + + return &DelegatingClient{ + config: in.config, + options: in.options, + scheme: in.Client.Scheme(), + mapper: in.Client.RESTMapper(), + Reader: &delegatingReader{ + CacheReader: in.CacheReader, + ClientReader: in.Client, + scheme: in.Client.Scheme(), + uncachedGVKs: uncachedGVKs, + cacheUnstructured: in.CacheUnstructured, + cachable: in.Cachable, + }, + Writer: in.Client, + StatusClient: in.Client, + SubResourceClientConstructor: in.Client, + }, nil +} + +type DelegatingClient struct { + client.Reader + client.Writer + client.StatusClient + client.SubResourceClientConstructor + + scheme *runtime.Scheme + mapper meta.RESTMapper + + config *restclient.Config + options client.Options +} + +func (d *DelegatingClient) RestConfig() *restclient.Config { + return d.config +} + +func (d *DelegatingClient) Impersonate(u user.Info) (*restclient.Config, client.Client, error) { + config := restclient.CopyConfig(d.config) + config.Impersonate = restclient.ImpersonationConfig{ + UserName: u.GetName(), + UID: u.GetUID(), + Groups: u.GetGroups(), + Extra: u.GetExtra(), + } + + // share the transport between all clients + optionsShallowCopy := d.options + if d.options.HTTPClient != nil { + optionsShallowCopy.HTTPClient = &http.Client{ + Transport: transport.NewImpersonatingRoundTripper(transport.ImpersonationConfig{ + UserName: u.GetName(), + UID: u.GetUID(), + Groups: u.GetGroups(), + Extra: u.GetExtra(), + }, d.options.HTTPClient.Transport), + } + } + cc, err := NewClient(config, optionsShallowCopy) + return config, cc, err +} + +// GroupVersionKindFor returns the GroupVersionKind for the given object. +func (d *DelegatingClient) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) { + return apiutil.GVKForObject(obj, d.scheme) +} + +// IsObjectNamespaced returns true if the GroupVersionKind of the object is namespaced. +func (d *DelegatingClient) IsObjectNamespaced(obj runtime.Object) (bool, error) { + return apiutil.IsObjectNamespaced(obj, d.scheme, d.mapper) +} + +// Scheme returns the scheme this client is using. +func (d *DelegatingClient) Scheme() *runtime.Scheme { + return d.scheme +} + +// RESTMapper returns the rest mapper this client is using. +func (d *DelegatingClient) RESTMapper() meta.RESTMapper { + return d.mapper +} + +// delegatingReader forms a Reader that will cause Get and List requests for +// unstructured types to use the ClientReader while requests for any other type +// of object with use the CacheReader. This avoids accidentally caching the +// entire cluster in the common case of loading arbitrary unstructured objects +// (e.g. from OwnerReferences). +type delegatingReader struct { + CacheReader client.Reader + ClientReader client.Reader + + uncachedGVKs map[schema.GroupVersionKind]struct{} + scheme *runtime.Scheme + cacheUnstructured bool + cachable apiutil2.Cachable +} + +func (d *delegatingReader) shouldBypassCache(obj runtime.Object) (bool, error) { + gvk, err := apiutil.GVKForObject(obj, d.scheme) + if err != nil { + return false, err + } + // TODO: this is producing unsafe guesses that don't actually work, + // but it matches ~99% of the cases out there. + if meta.IsListType(obj) { + gvk.Kind = strings.TrimSuffix(gvk.Kind, "List") + } + if d.cachable != nil { + canCache, err := d.cachable.GVK(gvk) + if err != nil || !canCache { + return true, err + } + } + if _, isUncached := d.uncachedGVKs[gvk]; isUncached { + return true, nil + } + if !d.cacheUnstructured { + _, isUnstructured := obj.(*unstructured.Unstructured) + _, isUnstructuredList := obj.(*unstructured.UnstructuredList) + return isUnstructured || isUnstructuredList, nil + } + return false, nil +} + +// Get retrieves an obj for a given object key from the Kubernetes Cluster. +func (d *delegatingReader) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + if isUncached, err := d.shouldBypassCache(obj); err != nil { + return err + } else if isUncached { + return d.ClientReader.Get(ctx, key, obj, opts...) + } + return d.CacheReader.Get(ctx, key, obj, opts...) +} + +// List retrieves list of objects for a given namespace and list options. +func (d *delegatingReader) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { + if isUncached, err := d.shouldBypassCache(list); err != nil { + return err + } else if isUncached { + return d.ClientReader.List(ctx, list, opts...) + } + return d.CacheReader.List(ctx, list, opts...) +} + +func (d *DelegatingClient) SubResource(subResource string) client.SubResourceClient { + return d.SubResourceClientConstructor.SubResource(subResource) +} + +func NewClient(config *restclient.Config, options client.Options) (client.Client, error) { + c, err := client.New(config, options) + if err != nil { + return nil, err + } + cachable, err := apiutil2.NewDynamicCachable(config) + if err != nil { + return nil, err + } + co := NewDelegatingClientInput{ + config: config, + options: options, + Client: c, + Cachable: cachable, + } + if options.Cache != nil { + co.CacheReader = options.Cache.Reader + co.UncachedObjects = options.Cache.DisableFor + co.CacheUnstructured = options.Cache.Unstructured // cache unstructured objects + } + return NewDelegatingClient(co) +} diff --git a/vendor/kmodules.xyz/client-go/client/retryclient.go b/vendor/kmodules.xyz/client-go/client/retryclient.go new file mode 100644 index 00000000..58fc5080 --- /dev/null +++ b/vendor/kmodules.xyz/client-go/client/retryclient.go @@ -0,0 +1,244 @@ +/* +Copyright AppsCode Inc. and Contributors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client + +import ( + "context" + "errors" + "io" + "time" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/wait" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type retryClient struct { + d client.Client + interval time.Duration + timeout time.Duration +} + +var _ client.Client = &retryClient{} + +func NewRetryClient(d client.Client) client.Client { + return &retryClient{d: d, interval: 500 * time.Millisecond, timeout: 5 * time.Minute} +} + +func NewRetryClientWithOptions(d client.Client, interval time.Duration, timeout time.Duration) client.Client { + return &retryClient{d: d, interval: interval, timeout: timeout} +} + +func (r *retryClient) Scheme() *runtime.Scheme { + return r.d.Scheme() +} + +func (r *retryClient) RESTMapper() meta.RESTMapper { + return r.d.RESTMapper() +} + +func (r *retryClient) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) { + return r.d.GroupVersionKindFor(obj) +} + +func (r *retryClient) IsObjectNamespaced(obj runtime.Object) (bool, error) { + return r.d.IsObjectNamespaced(obj) +} + +func (r *retryClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) (apierror error) { + _ = wait.PollUntilContextTimeout(ctx, r.interval, r.timeout, true, func(ctx context.Context) (done bool, err error) { + apierror = r.d.Get(ctx, key, obj, opts...) + err = apierror + done = err == nil || !errors.Is(err, io.EOF) + return + }) + return +} + +func (r *retryClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) (apierror error) { + _ = wait.PollUntilContextTimeout(ctx, r.interval, r.timeout, true, func(ctx context.Context) (done bool, err error) { + apierror = r.d.List(ctx, list, opts...) + err = apierror + done = err == nil || !errors.Is(err, io.EOF) + return + }) + return +} + +func (r *retryClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) (apierror error) { + _ = wait.PollUntilContextTimeout(ctx, r.interval, r.timeout, true, func(ctx context.Context) (done bool, err error) { + apierror = r.d.Create(ctx, obj, opts...) + err = apierror + done = err == nil || !errors.Is(err, io.EOF) + return + }) + return +} + +func (r *retryClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) (apierror error) { + _ = wait.PollUntilContextTimeout(ctx, r.interval, r.timeout, true, func(ctx context.Context) (done bool, err error) { + apierror = r.d.Delete(ctx, obj, opts...) + err = apierror + done = err == nil || !errors.Is(err, io.EOF) + return + }) + return +} + +func (r *retryClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) (apierror error) { + _ = wait.PollUntilContextTimeout(ctx, r.interval, r.timeout, true, func(ctx context.Context) (done bool, err error) { + apierror = r.d.Update(ctx, obj, opts...) + err = apierror + done = err == nil || !errors.Is(err, io.EOF) + return + }) + return +} + +func (r *retryClient) Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...client.ApplyOption) (apierror error) { + _ = wait.PollUntilContextTimeout(ctx, r.interval, r.timeout, true, func(ctx context.Context) (done bool, err error) { + apierror = r.d.Apply(ctx, obj, opts...) + err = apierror + done = err == nil || !errors.Is(err, io.EOF) + return + }) + return +} + +func (r *retryClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) (apierror error) { + _ = wait.PollUntilContextTimeout(ctx, r.interval, r.timeout, true, func(ctx context.Context) (done bool, err error) { + apierror = r.d.Patch(ctx, obj, patch, opts...) + err = apierror + done = err == nil || !errors.Is(err, io.EOF) + return + }) + return +} + +func (r *retryClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) (apierror error) { + _ = wait.PollUntilContextTimeout(ctx, r.interval, r.timeout, true, func(ctx context.Context) (done bool, err error) { + apierror = r.d.DeleteAllOf(ctx, obj, opts...) + err = apierror + done = err == nil || !errors.Is(err, io.EOF) + return + }) + return +} + +func (r *retryClient) Status() client.SubResourceWriter { + return &retrySubResourceWriter{ + d: r.d.Status(), + interval: r.interval, + timeout: r.timeout, + } +} + +func (r *retryClient) SubResource(subResource string) client.SubResourceClient { + return &retrySubResourceClient{ + d: r.d.SubResource(subResource), + interval: r.interval, + timeout: r.timeout, + } +} + +type retrySubResourceWriter struct { + d client.SubResourceWriter + interval time.Duration + timeout time.Duration +} + +var _ client.SubResourceWriter = &retrySubResourceWriter{} + +func (r *retrySubResourceWriter) Create(ctx context.Context, obj client.Object, subResource client.Object, opts ...client.SubResourceCreateOption) (apierror error) { + _ = wait.PollUntilContextTimeout(ctx, r.interval, r.timeout, true, func(ctx context.Context) (done bool, err error) { + apierror = r.d.Create(ctx, obj, subResource, opts...) + err = apierror + done = err == nil || !errors.Is(err, io.EOF) + return + }) + return +} + +func (r *retrySubResourceWriter) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) (apierror error) { + _ = wait.PollUntilContextTimeout(ctx, r.interval, r.timeout, true, func(ctx context.Context) (done bool, err error) { + apierror = r.d.Update(ctx, obj, opts...) + err = apierror + done = err == nil || !errors.Is(err, io.EOF) + return + }) + return +} + +func (r *retrySubResourceWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) (apierror error) { + _ = wait.PollUntilContextTimeout(ctx, r.interval, r.timeout, true, func(ctx context.Context) (done bool, err error) { + apierror = r.d.Patch(ctx, obj, patch, opts...) + err = apierror + done = err == nil || !errors.Is(err, io.EOF) + return + }) + return +} + +type retrySubResourceClient struct { + d client.SubResourceClient + interval time.Duration + timeout time.Duration +} + +var _ client.SubResourceClient = &retrySubResourceClient{} + +func (r *retrySubResourceClient) Get(ctx context.Context, obj client.Object, subResource client.Object, opts ...client.SubResourceGetOption) (apierror error) { + _ = wait.PollUntilContextTimeout(ctx, r.interval, r.timeout, true, func(ctx context.Context) (done bool, err error) { + apierror = r.d.Get(ctx, obj, subResource, opts...) + err = apierror + done = err == nil || !errors.Is(err, io.EOF) + return + }) + return +} + +func (r *retrySubResourceClient) Create(ctx context.Context, obj client.Object, subResource client.Object, opts ...client.SubResourceCreateOption) (apierror error) { + _ = wait.PollUntilContextTimeout(ctx, r.interval, r.timeout, true, func(ctx context.Context) (done bool, err error) { + apierror = r.d.Create(ctx, obj, subResource, opts...) + err = apierror + done = err == nil || !errors.Is(err, io.EOF) + return + }) + return +} + +func (r *retrySubResourceClient) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) (apierror error) { + _ = wait.PollUntilContextTimeout(ctx, r.interval, r.timeout, true, func(ctx context.Context) (done bool, err error) { + apierror = r.d.Update(ctx, obj, opts...) + err = apierror + done = err == nil || !errors.Is(err, io.EOF) + return + }) + return +} + +func (r *retrySubResourceClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) (apierror error) { + _ = wait.PollUntilContextTimeout(ctx, r.interval, r.timeout, true, func(ctx context.Context) (done bool, err error) { + apierror = r.d.Patch(ctx, obj, patch, opts...) + err = apierror + done = err == nil || !errors.Is(err, io.EOF) + return + }) + return +} diff --git a/vendor/kmodules.xyz/client-go/client/sa-token.go b/vendor/kmodules.xyz/client-go/client/sa-token.go new file mode 100644 index 00000000..866a86f3 --- /dev/null +++ b/vendor/kmodules.xyz/client-go/client/sa-token.go @@ -0,0 +1,115 @@ +/* +Copyright AppsCode Inc. and Contributors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client + +import ( + "context" + "time" + + core_util "kmodules.xyz/client-go/core/v1" + meta_util "kmodules.xyz/client-go/meta" + + "github.com/pkg/errors" + core "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilrand "k8s.io/apimachinery/pkg/util/rand" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/klog/v2" + kutil "kmodules.xyz/client-go" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/#token-controller +func getServiceAccountTokenSecret(kc client.Client, sa client.ObjectKey) (*core.Secret, error) { + var list core.SecretList + err := kc.List(context.TODO(), &list, client.InNamespace(sa.Namespace)) + if err != nil { + return nil, err + } + if len(list.Items) == 0 { + return nil, errors.New("token secret still haven't created yet") + } + for _, s := range list.Items { + if s.Type == core.SecretTypeServiceAccountToken && + s.Annotations[core.ServiceAccountNameKey] == sa.Name { + + _, caFound := s.Data["ca.crt"] + _, tokenFound := s.Data["token"] + if caFound && tokenFound { + return &s, nil + } + } + } + return nil, errors.New("token secret is not ready yet") +} + +const ( + RetryTimeout = 10 * time.Second +) + +func tryGetServiceAccountTokenSecret(kc client.Client, sa client.ObjectKey) (secret *core.Secret, err error) { + err = wait.PollUntilContextTimeout(context.Background(), kutil.RetryInterval, RetryTimeout, true, func(ctx context.Context) (bool, error) { + var e2 error + secret, e2 = getServiceAccountTokenSecret(kc, sa) + if e2 == nil { + return true, nil + } + klog.V(5).Infof("trying to get token secret for service account %s", sa) + return false, nil + }) + return +} + +func GetServiceAccountTokenSecret(kc client.Client, sa client.ObjectKey) (*core.Secret, error) { + secret, err := tryGetServiceAccountTokenSecret(kc, sa) + if err == nil { + klog.V(5).Infof("secret found for ServiceAccount %s", sa) + return secret, nil + } + + var saObj core.ServiceAccount + err = kc.Get(context.TODO(), sa, &saObj) + if err != nil { + return nil, errors.Wrapf(err, "failed to get ServiceAccount %s", sa) + } + + secretName := sa.Name + "-token-" + utilrand.String(6) + secret = &core.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: sa.Namespace, + }, + } + vt, err := CreateOrPatch(context.TODO(), kc, secret, func(obj client.Object, createOp bool) client.Object { + in := obj.(*core.Secret) + + in.Type = core.SecretTypeServiceAccountToken + ref := metav1.NewControllerRef(&saObj, core.SchemeGroupVersion.WithKind("ServiceAccount")) + core_util.EnsureOwnerReference(in, ref) + in.Annotations = meta_util.OverwriteKeys(in.Annotations, map[string]string{ + core.ServiceAccountNameKey: sa.Name, + }) + + return in + }) + if err != nil { + return nil, err + } + klog.Infof("%s Secret %s/%s", vt, secret.Namespace, secret.Name) + + return tryGetServiceAccountTokenSecret(kc, sa) +} diff --git a/vendor/kmodules.xyz/client-go/client/typeclient.go b/vendor/kmodules.xyz/client-go/client/typeclient.go new file mode 100644 index 00000000..d135ef44 --- /dev/null +++ b/vendor/kmodules.xyz/client-go/client/typeclient.go @@ -0,0 +1,465 @@ +/* +Copyright AppsCode Inc. and Contributors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client + +import ( + "context" + "fmt" + "strings" + "sync" + + apiutil2 "kmodules.xyz/client-go/client/apiutil" + + apimeta "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + restclient "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" +) + +const listType = "List" + +type typedClient struct { + c client.Client + cachable apiutil2.Cachable + *readerWrapper +} + +var ( + _ client.Reader = &typedClient{} + _ client.Writer = &typedClient{} + _ client.StatusClient = &typedClient{} + _ apiutil2.Cachable = &typedClient{} +) + +// GroupVersionKindFor returns the GroupVersionKind for the given object. +func (d *typedClient) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) { + return d.c.GroupVersionKindFor(obj) +} + +// IsObjectNamespaced returns true if the GroupVersionKind of the object is namespaced. +func (d *typedClient) IsObjectNamespaced(obj runtime.Object) (bool, error) { + return d.c.IsObjectNamespaced(obj) +} + +// Scheme returns the scheme this client is using. +func (d *typedClient) Scheme() *runtime.Scheme { + return d.c.Scheme() +} + +// RESTMapper returns the rest this client is using. +func (d *typedClient) RESTMapper() apimeta.RESTMapper { + return d.c.RESTMapper() +} + +type readerWrapper struct { + c client.Reader + scheme *runtime.Scheme + typeMap map[schema.GroupVersionKind]schema.GroupVersionKind + mu sync.Mutex +} + +var _ client.Reader = &readerWrapper{} + +func (d *readerWrapper) getMappedType(gvk schema.GroupVersionKind) (schema.GroupVersionKind, bool) { + d.mu.Lock() + rawGVK, found := d.typeMap[gvk] + d.mu.Unlock() + return rawGVK, found +} + +func (d *readerWrapper) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + gvk, err := apiutil.GVKForObject(obj, d.scheme) + if err != nil { + return err + } + + rawGVK, found := d.getMappedType(gvk) + if !found { + return d.c.Get(ctx, key, obj, opts...) + } + + ll, err := d.scheme.New(rawGVK) + if err != nil { + return err + } + llo := ll.(client.Object) + err = d.c.Get(ctx, key, llo, opts...) + if err != nil { + return err + } + + return d.scheme.Convert(llo, obj, nil) +} + +func (d *readerWrapper) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { + gvk, err := apiutil.GVKForObject(list, d.scheme) + if err != nil { + return err + } + if strings.HasSuffix(gvk.Kind, listType) && apimeta.IsListType(list) { + gvk.Kind = gvk.Kind[:len(gvk.Kind)-4] + } + + rawGVK, found := d.getMappedType(gvk) + if !found { + return d.c.List(ctx, list, opts...) + } + + listGVK := rawGVK + listGVK.Kind += listType + + ll, err := d.scheme.New(listGVK) + if err != nil { + return err + } + llo := ll.(client.ObjectList) + err = d.c.List(ctx, llo, opts...) + if err != nil { + return err + } + + list.SetResourceVersion(llo.GetResourceVersion()) + list.SetContinue(llo.GetContinue()) + list.SetSelfLink(llo.GetSelfLink()) + list.SetRemainingItemCount(llo.GetRemainingItemCount()) + + items := make([]runtime.Object, 0, apimeta.LenList(llo)) + err = apimeta.EachListItem(llo, func(object runtime.Object) error { + d2, err := d.scheme.New(gvk) + if err != nil { + return err + } + err = d.scheme.Convert(object, d2, nil) + if err != nil { + return err + } + items = append(items, d2) + return nil + }) + if err != nil { + return err + } + return apimeta.SetList(list, items) +} + +func (d *typedClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { + gvk, err := apiutil.GVKForObject(obj, d.c.Scheme()) + if err != nil { + return err + } + + rawGVK, found := d.getMappedType(gvk) + if !found { + return d.c.Create(ctx, obj, opts...) + } + + ll, err := d.c.Scheme().New(rawGVK) + if err != nil { + return err + } + llo := ll.(client.Object) + err = d.Scheme().Convert(obj, llo, nil) + if err != nil { + return err + } + return d.c.Create(ctx, llo, opts...) +} + +func (d *typedClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { + gvk, err := apiutil.GVKForObject(obj, d.c.Scheme()) + if err != nil { + return err + } + + rawGVK, found := d.getMappedType(gvk) + if !found { + return d.c.Delete(ctx, obj, opts...) + } + + ll, err := d.c.Scheme().New(rawGVK) + if err != nil { + return err + } + llo := ll.(client.Object) + llo.SetNamespace(obj.GetNamespace()) + llo.SetName(obj.GetName()) + llo.SetLabels(obj.GetLabels()) + return d.c.Delete(ctx, llo, opts...) +} + +func (d *typedClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + gvk, err := apiutil.GVKForObject(obj, d.c.Scheme()) + if err != nil { + return err + } + + rawGVK, found := d.getMappedType(gvk) + if !found { + return d.c.Update(ctx, obj, opts...) + } + + ll, err := d.c.Scheme().New(rawGVK) + if err != nil { + return err + } + llo := ll.(client.Object) + err = d.Scheme().Convert(obj, llo, nil) + if err != nil { + return err + } + return d.c.Update(ctx, llo, opts...) +} + +func (d *typedClient) Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...client.ApplyOption) error { + panic("not implemented") +} + +func (d *typedClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { + gvk, err := apiutil.GVKForObject(obj, d.c.Scheme()) + if err != nil { + return err + } + + rawGVK, found := d.getMappedType(gvk) + if !found { + return d.c.Patch(ctx, obj, patch, opts...) + } + + ll, err := d.c.Scheme().New(rawGVK) + if err != nil { + return err + } + llo := ll.(client.Object) + llo.SetNamespace(obj.GetNamespace()) + llo.SetName(obj.GetName()) + llo.SetLabels(obj.GetLabels()) + return d.c.Patch(ctx, llo, patch, opts...) +} + +func (d *typedClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error { + gvk, err := apiutil.GVKForObject(obj, d.c.Scheme()) + if err != nil { + return err + } + + rawGVK, found := d.getMappedType(gvk) + if !found { + return d.c.DeleteAllOf(ctx, obj, opts...) + } + + ll, err := d.c.Scheme().New(rawGVK) + if err != nil { + return err + } + llo := ll.(client.Object) + llo.SetNamespace(obj.GetNamespace()) + llo.SetName(obj.GetName()) + llo.SetLabels(obj.GetLabels()) + return d.c.DeleteAllOf(ctx, llo, opts...) +} + +func (d *typedClient) Status() client.StatusWriter { + return &typedStatusWriter{client: d} +} + +// typedStatusWriter is client.StatusWriter that writes status subresource. +type typedStatusWriter struct { + client *typedClient +} + +// ensure typedStatusWriter implements client.StatusWriter. +var _ client.StatusWriter = &typedStatusWriter{} + +func (sw *typedStatusWriter) Create(ctx context.Context, obj client.Object, subResource client.Object, opts ...client.SubResourceCreateOption) error { + gvk, err := apiutil.GVKForObject(obj, sw.client.c.Scheme()) + if err != nil { + return err + } + + rawGVK, found := sw.client.getMappedType(gvk) + if !found { + return sw.client.c.Status().Create(ctx, obj, subResource, opts...) + } + + ll, err := sw.client.Scheme().New(rawGVK) + if err != nil { + return err + } + llo := ll.(client.Object) + err = sw.client.Scheme().Convert(obj, llo, nil) + if err != nil { + return err + } + return sw.client.c.Status().Create(ctx, llo, subResource, opts...) +} + +func (sw *typedStatusWriter) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { + gvk, err := apiutil.GVKForObject(obj, sw.client.c.Scheme()) + if err != nil { + return err + } + + rawGVK, found := sw.client.getMappedType(gvk) + if !found { + return sw.client.c.Status().Update(ctx, obj, opts...) + } + + ll, err := sw.client.Scheme().New(rawGVK) + if err != nil { + return err + } + llo := ll.(client.Object) + err = sw.client.Scheme().Convert(obj, llo, nil) + if err != nil { + return err + } + return sw.client.c.Status().Update(ctx, llo, opts...) +} + +func (sw *typedStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error { + gvk, err := apiutil.GVKForObject(obj, sw.client.c.Scheme()) + if err != nil { + return err + } + + rawGVK, found := sw.client.getMappedType(gvk) + if !found { + return sw.client.c.Status().Patch(ctx, obj, patch, opts...) + } + + ll, err := sw.client.c.Scheme().New(rawGVK) + if err != nil { + return err + } + llo := ll.(client.Object) + llo.SetNamespace(obj.GetNamespace()) + llo.SetName(obj.GetName()) + llo.SetLabels(obj.GetLabels()) + return sw.client.c.Status().Patch(ctx, llo, patch, opts...) +} + +func (d *typedClient) SubResource(subResource string) client.SubResourceClient { + return d.c.SubResource(subResource) +} + +func (d *typedClient) GVK(gvk schema.GroupVersionKind) (bool, error) { + d.mu.Lock() + rawGVK, found := d.typeMap[gvk] + d.mu.Unlock() + + if !found { + return d.cachable.GVK(gvk) + } + return d.cachable.GVK(rawGVK) +} + +func (d *typedClient) GVR(gvr schema.GroupVersionResource) (bool, error) { + gvk, err := d.c.RESTMapper().KindFor(schema.GroupVersionResource{ + Group: gvr.Group, + Version: "", + Resource: gvr.Resource, + }) + if err != nil { + return false, err + } + return d.GVK(gvk) +} + +func BuildTypeMap(kc client.Client, gvks ...schema.GroupVersionKind) (map[schema.GroupVersionKind]schema.GroupVersionKind, error) { + tm := map[schema.GroupVersionKind]schema.GroupVersionKind{} + + for _, gvk := range gvks { + mappings, err := kc.RESTMapper().RESTMappings(gvk.GroupKind()) + if err != nil { + return nil, err + } + + var found bool + for _, mapping := range mappings { + if mapping.GroupVersionKind == gvk { + found = true + break + } + } + if !found { + for _, mapping := range mappings { + + in, err := kc.Scheme().New(gvk) + if err != nil { + return nil, err + } + out, err := kc.Scheme().New(mapping.GroupVersionKind) + if err != nil { + return nil, err + } + if err := kc.Scheme().Convert(in, out, nil); err == nil { + tm[gvk] = mapping.GroupVersionKind + found = true + break + } + } + } + if !found { + return nil, fmt.Errorf("type mapping not found for %+v", gvk) + } + } + + return tm, nil +} + +func NewAutoConvertClient(gvks ...schema.GroupVersionKind) client.NewClientFunc { + return func(config *restclient.Config, options client.Options) (client.Client, error) { + c, err := client.New(config, options) + if err != nil { + return nil, err + } + cachable, err := apiutil2.NewDynamicCachable(config) + if err != nil { + return nil, err + } + tm, err := BuildTypeMap(c, gvks...) + if err != nil { + return nil, err + } + tc := &typedClient{ + c: c, + cachable: cachable, + readerWrapper: &readerWrapper{ + c: c, + scheme: c.Scheme(), + typeMap: tm, + }, + } + + co := NewDelegatingClientInput{ + Client: tc, + Cachable: tc, + } + if options.Cache != nil { + co.CacheReader = &readerWrapper{ + c: options.Cache.Reader, + scheme: c.Scheme(), + typeMap: tm, + } + co.UncachedObjects = options.Cache.DisableFor + co.CacheUnstructured = options.Cache.Unstructured // cache unstructured objects + } + return NewDelegatingClient(co) + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 3da17b0b..6d816e78 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1579,6 +1579,9 @@ k8s.io/apimachinery/pkg/watch k8s.io/apimachinery/third_party/forked/golang/json k8s.io/apimachinery/third_party/forked/golang/netutil k8s.io/apimachinery/third_party/forked/golang/reflect +# k8s.io/apiserver v0.34.3 => github.com/kmodules/apiserver v0.34.4-0.20251227112449-07fa35efc6fc +## explicit; go 1.24.0 +k8s.io/apiserver/pkg/authentication/user # k8s.io/cli-runtime v0.34.3 ## explicit; go 1.24.0 k8s.io/cli-runtime/pkg/genericclioptions @@ -1933,6 +1936,7 @@ kmodules.xyz/client-go/api/v1 kmodules.xyz/client-go/apiextensions kmodules.xyz/client-go/apiextensions/v1 kmodules.xyz/client-go/apps/v1 +kmodules.xyz/client-go/client kmodules.xyz/client-go/client/apiutil kmodules.xyz/client-go/conditions kmodules.xyz/client-go/core/v1