working commit

This commit is contained in:
2026-03-13 19:02:42 +02:00
parent bebbf79c7a
commit 5c1da77f4c
1329 changed files with 314708 additions and 39 deletions
+63
View File
@@ -0,0 +1,63 @@
/*
Copyright 2016 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 dynamic
import (
"context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
)
type Interface interface {
Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface
}
type ResourceInterface interface {
Create(ctx context.Context, obj *unstructured.Unstructured, options metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error)
Update(ctx context.Context, obj *unstructured.Unstructured, options metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error)
UpdateStatus(ctx context.Context, obj *unstructured.Unstructured, options metav1.UpdateOptions) (*unstructured.Unstructured, error)
Delete(ctx context.Context, name string, options metav1.DeleteOptions, subresources ...string) error
DeleteCollection(ctx context.Context, options metav1.DeleteOptions, listOptions metav1.ListOptions) error
Get(ctx context.Context, name string, options metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error)
List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error)
Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, options metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error)
Apply(ctx context.Context, name string, obj *unstructured.Unstructured, options metav1.ApplyOptions, subresources ...string) (*unstructured.Unstructured, error)
ApplyStatus(ctx context.Context, name string, obj *unstructured.Unstructured, options metav1.ApplyOptions) (*unstructured.Unstructured, error)
}
type NamespaceableResourceInterface interface {
Namespace(string) ResourceInterface
ResourceInterface
}
// APIPathResolverFunc knows how to convert a groupVersion to its API path. The Kind field is optional.
// TODO find a better place to move this for existing callers
type APIPathResolverFunc func(kind schema.GroupVersionKind) string
// LegacyAPIPathResolverFunc can resolve paths properly with the legacy API.
// TODO find a better place to move this for existing callers
func LegacyAPIPathResolverFunc(kind schema.GroupVersionKind) string {
if len(kind.Group) == 0 {
return "/api"
}
return "/apis"
}
+144
View File
@@ -0,0 +1,144 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dynamic
import (
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"
"k8s.io/apimachinery/pkg/runtime/serializer/cbor"
"k8s.io/apimachinery/pkg/runtime/serializer/json"
"k8s.io/client-go/features"
)
var basicScheme = runtime.NewScheme()
var parameterScheme = runtime.NewScheme()
var dynamicParameterCodec = runtime.NewParameterCodec(parameterScheme)
var versionV1 = schema.GroupVersion{Version: "v1"}
func init() {
metav1.AddToGroupVersion(basicScheme, versionV1)
metav1.AddToGroupVersion(parameterScheme, versionV1)
}
func newBasicNegotiatedSerializer() basicNegotiatedSerializer {
supportedMediaTypes := []runtime.SerializerInfo{
{
MediaType: "application/json",
MediaTypeType: "application",
MediaTypeSubType: "json",
EncodesAsText: true,
Serializer: json.NewSerializerWithOptions(json.DefaultMetaFactory, unstructuredCreater{basicScheme}, unstructuredTyper{basicScheme}, json.SerializerOptions{}),
PrettySerializer: json.NewSerializerWithOptions(json.DefaultMetaFactory, unstructuredCreater{basicScheme}, unstructuredTyper{basicScheme}, json.SerializerOptions{Pretty: true}),
StreamSerializer: &runtime.StreamSerializerInfo{
EncodesAsText: true,
Serializer: json.NewSerializerWithOptions(json.DefaultMetaFactory, basicScheme, basicScheme, json.SerializerOptions{}),
Framer: json.Framer,
},
},
}
if features.FeatureGates().Enabled(features.ClientsAllowCBOR) {
supportedMediaTypes = append(supportedMediaTypes, runtime.SerializerInfo{
MediaType: "application/cbor",
MediaTypeType: "application",
MediaTypeSubType: "cbor",
Serializer: cbor.NewSerializer(unstructuredCreater{basicScheme}, unstructuredTyper{basicScheme}),
StreamSerializer: &runtime.StreamSerializerInfo{
Serializer: cbor.NewSerializer(basicScheme, basicScheme, cbor.Transcode(false)),
Framer: cbor.NewFramer(),
},
})
}
return basicNegotiatedSerializer{supportedMediaTypes: supportedMediaTypes}
}
type basicNegotiatedSerializer struct {
supportedMediaTypes []runtime.SerializerInfo
}
func (s basicNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo {
return s.supportedMediaTypes
}
func (s basicNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
return runtime.WithVersionEncoder{
Version: gv,
Encoder: encoder,
ObjectTyper: permissiveTyper{basicScheme},
}
}
func (s basicNegotiatedSerializer) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
return decoder
}
type unstructuredCreater struct {
nested runtime.ObjectCreater
}
func (c unstructuredCreater) New(kind schema.GroupVersionKind) (runtime.Object, error) {
out, err := c.nested.New(kind)
if err == nil {
return out, nil
}
out = &unstructured.Unstructured{}
out.GetObjectKind().SetGroupVersionKind(kind)
return out, nil
}
type unstructuredTyper struct {
nested runtime.ObjectTyper
}
func (t unstructuredTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) {
kinds, unversioned, err := t.nested.ObjectKinds(obj)
if err == nil {
return kinds, unversioned, nil
}
if _, ok := obj.(runtime.Unstructured); ok && !obj.GetObjectKind().GroupVersionKind().Empty() {
return []schema.GroupVersionKind{obj.GetObjectKind().GroupVersionKind()}, false, nil
}
return nil, false, err
}
func (t unstructuredTyper) Recognizes(gvk schema.GroupVersionKind) bool {
return true
}
// The dynamic client has historically accepted Unstructured objects with missing or empty
// apiVersion and/or kind as arguments to its write request methods. This typer will return the type
// of a runtime.Unstructured with no error, even if the type is missing or empty.
type permissiveTyper struct {
nested runtime.ObjectTyper
}
func (t permissiveTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) {
kinds, unversioned, err := t.nested.ObjectKinds(obj)
if err == nil {
return kinds, unversioned, nil
}
if _, ok := obj.(runtime.Unstructured); ok {
return []schema.GroupVersionKind{obj.GetObjectKind().GroupVersionKind()}, false, nil
}
return nil, false, err
}
func (t permissiveTyper) Recognizes(gvk schema.GroupVersionKind) bool {
return true
}
+362
View File
@@ -0,0 +1,362 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dynamic
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/features"
"k8s.io/client-go/rest"
"k8s.io/client-go/util/apply"
"net/http"
)
type DynamicClient struct {
client rest.Interface
}
var _ Interface = &DynamicClient{}
// ConfigFor returns a copy of the provided config with the
// appropriate dynamic client defaults set.
func ConfigFor(inConfig *rest.Config) *rest.Config {
config := rest.CopyConfig(inConfig)
config.ContentType = "application/json"
config.AcceptContentTypes = "application/json"
if features.FeatureGates().Enabled(features.ClientsAllowCBOR) {
config.AcceptContentTypes = "application/json;q=0.9,application/cbor;q=1"
if features.FeatureGates().Enabled(features.ClientsPreferCBOR) {
config.ContentType = "application/cbor"
}
}
config.NegotiatedSerializer = newBasicNegotiatedSerializer()
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
return config
}
// New creates a new DynamicClient for the given RESTClient.
func New(c rest.Interface) *DynamicClient {
return &DynamicClient{client: c}
}
// NewForConfigOrDie creates a new DynamicClient for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) *DynamicClient {
ret, err := NewForConfig(c)
if err != nil {
panic(err)
}
return ret
}
// NewForConfig creates a new dynamic client or returns an error.
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
// where httpClient was generated with rest.HTTPClientFor(c).
func NewForConfig(inConfig *rest.Config) (*DynamicClient, error) {
config := ConfigFor(inConfig)
httpClient, err := rest.HTTPClientFor(config)
if err != nil {
return nil, err
}
return NewForConfigAndClient(config, httpClient)
}
// NewForConfigAndClient creates a new dynamic client for the given config and http client.
// Note the http client provided takes precedence over the configured transport values.
func NewForConfigAndClient(inConfig *rest.Config, h *http.Client) (*DynamicClient, error) {
config := ConfigFor(inConfig)
config.GroupVersion = nil
config.APIPath = "/if-you-see-this-search-for-the-break"
restClient, err := rest.UnversionedRESTClientForConfigAndClient(config, h)
if err != nil {
return nil, err
}
return &DynamicClient{client: restClient}, nil
}
type dynamicResourceClient struct {
client *DynamicClient
namespace string
resource schema.GroupVersionResource
}
func (c *DynamicClient) Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface {
return &dynamicResourceClient{client: c, resource: resource}
}
func (c *dynamicResourceClient) Namespace(ns string) ResourceInterface {
ret := *c
ret.namespace = ns
return &ret
}
func (c *dynamicResourceClient) Create(ctx context.Context, obj *unstructured.Unstructured, opts metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error) {
name := ""
if len(subresources) > 0 {
accessor, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
name = accessor.GetName()
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
}
if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
return nil, err
}
var out unstructured.Unstructured
if err := c.client.client.
Post().
AbsPath(append(c.makeURLSegments(name), subresources...)...).
Body(obj).
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
Do(ctx).Into(&out); err != nil {
return nil, err
}
return &out, nil
}
func (c *dynamicResourceClient) Update(ctx context.Context, obj *unstructured.Unstructured, opts metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error) {
accessor, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
name := accessor.GetName()
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
return nil, err
}
var out unstructured.Unstructured
if err := c.client.client.
Put().
AbsPath(append(c.makeURLSegments(name), subresources...)...).
Body(obj).
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
Do(ctx).Into(&out); err != nil {
return nil, err
}
return &out, nil
}
func (c *dynamicResourceClient) UpdateStatus(ctx context.Context, obj *unstructured.Unstructured, opts metav1.UpdateOptions) (*unstructured.Unstructured, error) {
accessor, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
name := accessor.GetName()
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
return nil, err
}
var out unstructured.Unstructured
if err := c.client.client.
Put().
AbsPath(append(c.makeURLSegments(name), "status")...).
Body(obj).
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
Do(ctx).Into(&out); err != nil {
return nil, err
}
return &out, nil
}
func (c *dynamicResourceClient) Delete(ctx context.Context, name string, opts metav1.DeleteOptions, subresources ...string) error {
if len(name) == 0 {
return fmt.Errorf("name is required")
}
if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
return err
}
result := c.client.client.
Delete().
AbsPath(append(c.makeURLSegments(name), subresources...)...).
Body(&opts).
Do(ctx)
return result.Error()
}
func (c *dynamicResourceClient) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOptions metav1.ListOptions) error {
if err := validateNamespaceWithOptionalName(c.namespace); err != nil {
return err
}
result := c.client.client.
Delete().
AbsPath(c.makeURLSegments("")...).
Body(&opts).
SpecificallyVersionedParams(&listOptions, dynamicParameterCodec, versionV1).
Do(ctx)
return result.Error()
}
func (c *dynamicResourceClient) Get(ctx context.Context, name string, opts metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) {
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
return nil, err
}
var out unstructured.Unstructured
if err := c.client.client.
Get().
AbsPath(append(c.makeURLSegments(name), subresources...)...).
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
Do(ctx).Into(&out); err != nil {
return nil, err
}
return &out, nil
}
func (c *dynamicResourceClient) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) {
if err := validateNamespaceWithOptionalName(c.namespace); err != nil {
return nil, err
}
var out unstructured.UnstructuredList
if err := c.client.client.
Get().
AbsPath(c.makeURLSegments("")...).
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
Do(ctx).Into(&out); err != nil {
return nil, err
}
return &out, nil
}
func (c *dynamicResourceClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
opts.Watch = true
if err := validateNamespaceWithOptionalName(c.namespace); err != nil {
return nil, err
}
return c.client.client.Get().AbsPath(c.makeURLSegments("")...).
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
Watch(ctx)
}
func (c *dynamicResourceClient) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error) {
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
return nil, err
}
var out unstructured.Unstructured
if err := c.client.client.
Patch(pt).
AbsPath(append(c.makeURLSegments(name), subresources...)...).
Body(data).
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
Do(ctx).Into(&out); err != nil {
return nil, err
}
return &out, nil
}
func (c *dynamicResourceClient) Apply(ctx context.Context, name string, obj *unstructured.Unstructured, opts metav1.ApplyOptions, subresources ...string) (*unstructured.Unstructured, error) {
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
return nil, err
}
accessor, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
managedFields := accessor.GetManagedFields()
if len(managedFields) > 0 {
return nil, fmt.Errorf(`cannot apply an object with managed fields already set.
Use the client-go/applyconfigurations "UnstructructuredExtractor" to obtain the unstructured ApplyConfiguration for the given field manager that you can use/modify here to apply`)
}
patchOpts := opts.ToPatchOptions()
request, err := apply.NewRequest(c.client.client, obj.Object)
if err != nil {
return nil, err
}
var out unstructured.Unstructured
if err := request.
AbsPath(append(c.makeURLSegments(name), subresources...)...).
SpecificallyVersionedParams(&patchOpts, dynamicParameterCodec, versionV1).
Do(ctx).Into(&out); err != nil {
return nil, err
}
return &out, nil
}
func (c *dynamicResourceClient) ApplyStatus(ctx context.Context, name string, obj *unstructured.Unstructured, opts metav1.ApplyOptions) (*unstructured.Unstructured, error) {
return c.Apply(ctx, name, obj, opts, "status")
}
func validateNamespaceWithOptionalName(namespace string, name ...string) error {
if msgs := rest.IsValidPathSegmentName(namespace); len(msgs) != 0 {
return fmt.Errorf("invalid namespace %q: %v", namespace, msgs)
}
if len(name) > 1 {
panic("Invalid number of names")
} else if len(name) == 1 {
if msgs := rest.IsValidPathSegmentName(name[0]); len(msgs) != 0 {
return fmt.Errorf("invalid resource name %q: %v", name[0], msgs)
}
}
return nil
}
func (c *dynamicResourceClient) makeURLSegments(name string) []string {
url := []string{}
if len(c.resource.Group) == 0 {
url = append(url, "api")
} else {
url = append(url, "apis", c.resource.Group)
}
url = append(url, c.resource.Version)
if len(c.namespace) > 0 {
url = append(url, "namespaces", c.namespace)
}
url = append(url, c.resource.Resource)
if len(name) > 0 {
url = append(url, name)
}
return url
}