diff --git a/.gitignore b/.gitignore index ada68ff..3e46fec 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ *.so *.dylib bin/* +vendor/* Dockerfile.cross # Test binary, built with `go test -c` diff --git a/Makefile b/Makefile index 9197b6c..e5053cf 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # Image URL to use all building/pushing image targets IMG ?= controller:latest # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. -ENVTEST_K8S_VERSION = 1.31.0 +ENVTEST_K8S_VERSION = 1.30.0 # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) @@ -162,7 +162,7 @@ GOLANGCI_LINT = $(LOCALBIN)/golangci-lint-$(GOLANGCI_LINT_VERSION) ## Tool Versions KUSTOMIZE_VERSION ?= v5.3.0 -CONTROLLER_TOOLS_VERSION ?= v0.14.0 +CONTROLLER_TOOLS_VERSION ?= v0.17.3 ENVTEST_VERSION ?= release-0.17 GOLANGCI_LINT_VERSION ?= v1.54.2 KB_TOOLS_ARCHIVE_NAME :=kubebuilder-tools-$(ENVTEST_K8S_VERSION)-$(GOHOSTOS)-$(GOHOSTARCH).tar.gz diff --git a/apis/v1alpha1/clusterprofile_types.go b/apis/v1alpha1/clusterprofile_types.go index 576c49b..1cd4a2f 100644 --- a/apis/v1alpha1/clusterprofile_types.go +++ b/apis/v1alpha1/clusterprofile_types.go @@ -18,6 +18,7 @@ package v1alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + clientcmdv1 "k8s.io/client-go/tools/clientcmd/api/v1" ) // ClusterProfileSpec defines the desired state of ClusterProfile. @@ -69,6 +70,15 @@ type ClusterProfileStatus struct { // - Custom names defined by cluster managers // +optional Properties []Property `json:"properties,omitempty"` + + // CredentialProviders is a list credential providers that can provide kubeconfig + // credential to connect to the cluster. + CredentialProviders []CredentialProvider `json:"credentialProviders,omitempty"` +} + +type CredentialProvider struct { + Name string `json:"name"` + Cluster clientcmdv1.Cluster `json:"cluster,omitempty"` } // ClusterVersion represents version information about the cluster. diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go index 5100f05..6f4bc56 100644 --- a/apis/v1alpha1/zz_generated.deepcopy.go +++ b/apis/v1alpha1/zz_generated.deepcopy.go @@ -133,6 +133,13 @@ func (in *ClusterProfileStatus) DeepCopyInto(out *ClusterProfileStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.CredentialProviders != nil { + in, out := &in.CredentialProviders, &out.CredentialProviders + *out = make([]CredentialProvider, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterProfileStatus. @@ -160,6 +167,22 @@ func (in *ClusterVersion) DeepCopy() *ClusterVersion { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CredentialProvider) DeepCopyInto(out *CredentialProvider) { + *out = *in + in.Cluster.DeepCopyInto(&out.Cluster) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialProvider. +func (in *CredentialProvider) DeepCopy() *CredentialProvider { + if in == nil { + return nil + } + out := new(CredentialProvider) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Property) DeepCopyInto(out *Property) { *out = *in diff --git a/client/clientset/versioned/clientset.go b/client/clientset/versioned/clientset.go index 03be6f2..94928f1 100644 --- a/client/clientset/versioned/clientset.go +++ b/client/clientset/versioned/clientset.go @@ -18,8 +18,8 @@ limitations under the License. package versioned import ( - "fmt" - "net/http" + fmt "fmt" + http "net/http" discovery "k8s.io/client-go/discovery" rest "k8s.io/client-go/rest" diff --git a/client/clientset/versioned/fake/clientset_generated.go b/client/clientset/versioned/fake/clientset_generated.go index b431dc1..99f305a 100644 --- a/client/clientset/versioned/fake/clientset_generated.go +++ b/client/clientset/versioned/fake/clientset_generated.go @@ -30,8 +30,12 @@ import ( // NewSimpleClientset returns a clientset that will respond with the provided objects. // It's backed by a very simple object tracker that processes creates, updates and deletions as-is, -// without applying any validations and/or defaults. It shouldn't be considered a replacement +// without applying any field management, validations and/or defaults. It shouldn't be considered a replacement // for a real clientset and is mostly useful in simple unit tests. +// +// DEPRECATED: NewClientset replaces this with support for field management, which significantly improves +// server side apply testing. NewClientset is only available when apply configurations are generated (e.g. +// via --with-applyconfig). func NewSimpleClientset(objects ...runtime.Object) *Clientset { o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) for _, obj := range objects { diff --git a/client/clientset/versioned/typed/apis/v1alpha1/apis_client.go b/client/clientset/versioned/typed/apis/v1alpha1/apis_client.go index e09a553..3c758ab 100644 --- a/client/clientset/versioned/typed/apis/v1alpha1/apis_client.go +++ b/client/clientset/versioned/typed/apis/v1alpha1/apis_client.go @@ -18,11 +18,11 @@ limitations under the License. package v1alpha1 import ( - "net/http" + http "net/http" rest "k8s.io/client-go/rest" - v1alpha1 "sigs.k8s.io/cluster-inventory-api/apis/v1alpha1" - "sigs.k8s.io/cluster-inventory-api/client/clientset/versioned/scheme" + apisv1alpha1 "sigs.k8s.io/cluster-inventory-api/apis/v1alpha1" + scheme "sigs.k8s.io/cluster-inventory-api/client/clientset/versioned/scheme" ) type ApisV1alpha1Interface interface { @@ -84,10 +84,10 @@ func New(c rest.Interface) *ApisV1alpha1Client { } func setConfigDefaults(config *rest.Config) error { - gv := v1alpha1.SchemeGroupVersion + gv := apisv1alpha1.SchemeGroupVersion config.GroupVersion = &gv config.APIPath = "/apis" - config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() + config.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion() if config.UserAgent == "" { config.UserAgent = rest.DefaultKubernetesUserAgent() diff --git a/client/clientset/versioned/typed/apis/v1alpha1/clusterprofile.go b/client/clientset/versioned/typed/apis/v1alpha1/clusterprofile.go index 1ed1105..3ad86c7 100644 --- a/client/clientset/versioned/typed/apis/v1alpha1/clusterprofile.go +++ b/client/clientset/versioned/typed/apis/v1alpha1/clusterprofile.go @@ -18,14 +18,13 @@ limitations under the License. package v1alpha1 import ( - "context" - "time" + context "context" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" - v1alpha1 "sigs.k8s.io/cluster-inventory-api/apis/v1alpha1" + gentype "k8s.io/client-go/gentype" + apisv1alpha1 "sigs.k8s.io/cluster-inventory-api/apis/v1alpha1" scheme "sigs.k8s.io/cluster-inventory-api/client/clientset/versioned/scheme" ) @@ -37,158 +36,34 @@ type ClusterProfilesGetter interface { // ClusterProfileInterface has methods to work with ClusterProfile resources. type ClusterProfileInterface interface { - Create(ctx context.Context, clusterProfile *v1alpha1.ClusterProfile, opts v1.CreateOptions) (*v1alpha1.ClusterProfile, error) - Update(ctx context.Context, clusterProfile *v1alpha1.ClusterProfile, opts v1.UpdateOptions) (*v1alpha1.ClusterProfile, error) - UpdateStatus(ctx context.Context, clusterProfile *v1alpha1.ClusterProfile, opts v1.UpdateOptions) (*v1alpha1.ClusterProfile, error) + Create(ctx context.Context, clusterProfile *apisv1alpha1.ClusterProfile, opts v1.CreateOptions) (*apisv1alpha1.ClusterProfile, error) + Update(ctx context.Context, clusterProfile *apisv1alpha1.ClusterProfile, opts v1.UpdateOptions) (*apisv1alpha1.ClusterProfile, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, clusterProfile *apisv1alpha1.ClusterProfile, opts v1.UpdateOptions) (*apisv1alpha1.ClusterProfile, 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) (*v1alpha1.ClusterProfile, error) - List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.ClusterProfileList, error) + Get(ctx context.Context, name string, opts v1.GetOptions) (*apisv1alpha1.ClusterProfile, error) + List(ctx context.Context, opts v1.ListOptions) (*apisv1alpha1.ClusterProfileList, 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 *v1alpha1.ClusterProfile, err error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *apisv1alpha1.ClusterProfile, err error) ClusterProfileExpansion } // clusterProfiles implements ClusterProfileInterface type clusterProfiles struct { - client rest.Interface - ns string + *gentype.ClientWithList[*apisv1alpha1.ClusterProfile, *apisv1alpha1.ClusterProfileList] } // newClusterProfiles returns a ClusterProfiles func newClusterProfiles(c *ApisV1alpha1Client, namespace string) *clusterProfiles { return &clusterProfiles{ - client: c.RESTClient(), - ns: namespace, + gentype.NewClientWithList[*apisv1alpha1.ClusterProfile, *apisv1alpha1.ClusterProfileList]( + "clusterprofiles", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *apisv1alpha1.ClusterProfile { return &apisv1alpha1.ClusterProfile{} }, + func() *apisv1alpha1.ClusterProfileList { return &apisv1alpha1.ClusterProfileList{} }, + ), } } - -// Get takes name of the clusterProfile, and returns the corresponding clusterProfile object, and an error if there is any. -func (c *clusterProfiles) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.ClusterProfile, err error) { - result = &v1alpha1.ClusterProfile{} - err = c.client.Get(). - Namespace(c.ns). - Resource("clusterprofiles"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(ctx). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of ClusterProfiles that match those selectors. -func (c *clusterProfiles) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.ClusterProfileList, err error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - result = &v1alpha1.ClusterProfileList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("clusterprofiles"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Do(ctx). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested clusterProfiles. -func (c *clusterProfiles) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("clusterprofiles"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Watch(ctx) -} - -// Create takes the representation of a clusterProfile and creates it. Returns the server's representation of the clusterProfile, and an error, if there is any. -func (c *clusterProfiles) Create(ctx context.Context, clusterProfile *v1alpha1.ClusterProfile, opts v1.CreateOptions) (result *v1alpha1.ClusterProfile, err error) { - result = &v1alpha1.ClusterProfile{} - err = c.client.Post(). - Namespace(c.ns). - Resource("clusterprofiles"). - VersionedParams(&opts, scheme.ParameterCodec). - Body(clusterProfile). - Do(ctx). - Into(result) - return -} - -// Update takes the representation of a clusterProfile and updates it. Returns the server's representation of the clusterProfile, and an error, if there is any. -func (c *clusterProfiles) Update(ctx context.Context, clusterProfile *v1alpha1.ClusterProfile, opts v1.UpdateOptions) (result *v1alpha1.ClusterProfile, err error) { - result = &v1alpha1.ClusterProfile{} - err = c.client.Put(). - Namespace(c.ns). - Resource("clusterprofiles"). - Name(clusterProfile.Name). - VersionedParams(&opts, scheme.ParameterCodec). - Body(clusterProfile). - Do(ctx). - Into(result) - return -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *clusterProfiles) UpdateStatus(ctx context.Context, clusterProfile *v1alpha1.ClusterProfile, opts v1.UpdateOptions) (result *v1alpha1.ClusterProfile, err error) { - result = &v1alpha1.ClusterProfile{} - err = c.client.Put(). - Namespace(c.ns). - Resource("clusterprofiles"). - Name(clusterProfile.Name). - SubResource("status"). - VersionedParams(&opts, scheme.ParameterCodec). - Body(clusterProfile). - Do(ctx). - Into(result) - return -} - -// Delete takes name of the clusterProfile and deletes it. Returns an error if one occurs. -func (c *clusterProfiles) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("clusterprofiles"). - Name(name). - Body(&opts). - Do(ctx). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *clusterProfiles) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { - var timeout time.Duration - if listOpts.TimeoutSeconds != nil { - timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second - } - return c.client.Delete(). - Namespace(c.ns). - Resource("clusterprofiles"). - VersionedParams(&listOpts, scheme.ParameterCodec). - Timeout(timeout). - Body(&opts). - Do(ctx). - Error() -} - -// Patch applies the patch and returns the patched clusterProfile. -func (c *clusterProfiles) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ClusterProfile, err error) { - result = &v1alpha1.ClusterProfile{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("clusterprofiles"). - Name(name). - SubResource(subresources...). - VersionedParams(&opts, scheme.ParameterCodec). - Body(data). - Do(ctx). - Into(result) - return -} diff --git a/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_apis_client.go b/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_apis_client.go index 4a41183..1fb9721 100644 --- a/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_apis_client.go +++ b/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_apis_client.go @@ -28,7 +28,7 @@ type FakeApisV1alpha1 struct { } func (c *FakeApisV1alpha1) ClusterProfiles(namespace string) v1alpha1.ClusterProfileInterface { - return &FakeClusterProfiles{c, namespace} + return newFakeClusterProfiles(c, namespace) } // RESTClient returns a RESTClient that is used to communicate diff --git a/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_clusterprofile.go b/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_clusterprofile.go index 091b488..2feb69e 100644 --- a/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_clusterprofile.go +++ b/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_clusterprofile.go @@ -18,123 +18,34 @@ limitations under the License. package fake import ( - "context" - - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" + gentype "k8s.io/client-go/gentype" v1alpha1 "sigs.k8s.io/cluster-inventory-api/apis/v1alpha1" + apisv1alpha1 "sigs.k8s.io/cluster-inventory-api/client/clientset/versioned/typed/apis/v1alpha1" ) -// FakeClusterProfiles implements ClusterProfileInterface -type FakeClusterProfiles struct { +// fakeClusterProfiles implements ClusterProfileInterface +type fakeClusterProfiles struct { + *gentype.FakeClientWithList[*v1alpha1.ClusterProfile, *v1alpha1.ClusterProfileList] Fake *FakeApisV1alpha1 - ns string -} - -var clusterprofilesResource = v1alpha1.SchemeGroupVersion.WithResource("clusterprofiles") - -var clusterprofilesKind = v1alpha1.SchemeGroupVersion.WithKind("ClusterProfile") - -// Get takes name of the clusterProfile, and returns the corresponding clusterProfile object, and an error if there is any. -func (c *FakeClusterProfiles) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.ClusterProfile, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetAction(clusterprofilesResource, c.ns, name), &v1alpha1.ClusterProfile{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha1.ClusterProfile), err -} - -// List takes label and field selectors, and returns the list of ClusterProfiles that match those selectors. -func (c *FakeClusterProfiles) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.ClusterProfileList, err error) { - obj, err := c.Fake. - Invokes(testing.NewListAction(clusterprofilesResource, clusterprofilesKind, c.ns, opts), &v1alpha1.ClusterProfileList{}) - - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &v1alpha1.ClusterProfileList{ListMeta: obj.(*v1alpha1.ClusterProfileList).ListMeta} - for _, item := range obj.(*v1alpha1.ClusterProfileList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested clusterProfiles. -func (c *FakeClusterProfiles) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchAction(clusterprofilesResource, c.ns, opts)) - -} - -// Create takes the representation of a clusterProfile and creates it. Returns the server's representation of the clusterProfile, and an error, if there is any. -func (c *FakeClusterProfiles) Create(ctx context.Context, clusterProfile *v1alpha1.ClusterProfile, opts v1.CreateOptions) (result *v1alpha1.ClusterProfile, err error) { - obj, err := c.Fake. - Invokes(testing.NewCreateAction(clusterprofilesResource, c.ns, clusterProfile), &v1alpha1.ClusterProfile{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha1.ClusterProfile), err -} - -// Update takes the representation of a clusterProfile and updates it. Returns the server's representation of the clusterProfile, and an error, if there is any. -func (c *FakeClusterProfiles) Update(ctx context.Context, clusterProfile *v1alpha1.ClusterProfile, opts v1.UpdateOptions) (result *v1alpha1.ClusterProfile, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateAction(clusterprofilesResource, c.ns, clusterProfile), &v1alpha1.ClusterProfile{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha1.ClusterProfile), err -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeClusterProfiles) UpdateStatus(ctx context.Context, clusterProfile *v1alpha1.ClusterProfile, opts v1.UpdateOptions) (*v1alpha1.ClusterProfile, error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(clusterprofilesResource, "status", c.ns, clusterProfile), &v1alpha1.ClusterProfile{}) - - if obj == nil { - return nil, err - } - return obj.(*v1alpha1.ClusterProfile), err -} - -// Delete takes name of the clusterProfile and deletes it. Returns an error if one occurs. -func (c *FakeClusterProfiles) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteActionWithOptions(clusterprofilesResource, c.ns, name, opts), &v1alpha1.ClusterProfile{}) - - return err } -// DeleteCollection deletes a collection of objects. -func (c *FakeClusterProfiles) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(clusterprofilesResource, c.ns, listOpts) - - _, err := c.Fake.Invokes(action, &v1alpha1.ClusterProfileList{}) - return err -} - -// Patch applies the patch and returns the patched clusterProfile. -func (c *FakeClusterProfiles) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ClusterProfile, err error) { - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(clusterprofilesResource, c.ns, name, pt, data, subresources...), &v1alpha1.ClusterProfile{}) - - if obj == nil { - return nil, err +func newFakeClusterProfiles(fake *FakeApisV1alpha1, namespace string) apisv1alpha1.ClusterProfileInterface { + return &fakeClusterProfiles{ + gentype.NewFakeClientWithList[*v1alpha1.ClusterProfile, *v1alpha1.ClusterProfileList]( + fake.Fake, + namespace, + v1alpha1.SchemeGroupVersion.WithResource("clusterprofiles"), + v1alpha1.SchemeGroupVersion.WithKind("ClusterProfile"), + func() *v1alpha1.ClusterProfile { return &v1alpha1.ClusterProfile{} }, + func() *v1alpha1.ClusterProfileList { return &v1alpha1.ClusterProfileList{} }, + func(dst, src *v1alpha1.ClusterProfileList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.ClusterProfileList) []*v1alpha1.ClusterProfile { + return gentype.ToPointerSlice(list.Items) + }, + func(list *v1alpha1.ClusterProfileList, items []*v1alpha1.ClusterProfile) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, } - return obj.(*v1alpha1.ClusterProfile), err } diff --git a/client/informers/externalversions/apis/v1alpha1/clusterprofile.go b/client/informers/externalversions/apis/v1alpha1/clusterprofile.go index 9c581a0..94ca193 100644 --- a/client/informers/externalversions/apis/v1alpha1/clusterprofile.go +++ b/client/informers/externalversions/apis/v1alpha1/clusterprofile.go @@ -18,24 +18,24 @@ limitations under the License. package v1alpha1 import ( - "context" + 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" - apisv1alpha1 "sigs.k8s.io/cluster-inventory-api/apis/v1alpha1" + clusterinventoryapiapisv1alpha1 "sigs.k8s.io/cluster-inventory-api/apis/v1alpha1" versioned "sigs.k8s.io/cluster-inventory-api/client/clientset/versioned" internalinterfaces "sigs.k8s.io/cluster-inventory-api/client/informers/externalversions/internalinterfaces" - v1alpha1 "sigs.k8s.io/cluster-inventory-api/client/listers/apis/v1alpha1" + apisv1alpha1 "sigs.k8s.io/cluster-inventory-api/client/listers/apis/v1alpha1" ) // ClusterProfileInformer provides access to a shared informer and lister for // ClusterProfiles. type ClusterProfileInformer interface { Informer() cache.SharedIndexInformer - Lister() v1alpha1.ClusterProfileLister + Lister() apisv1alpha1.ClusterProfileLister } type clusterProfileInformer struct { @@ -70,7 +70,7 @@ func NewFilteredClusterProfileInformer(client versioned.Interface, namespace str return client.ApisV1alpha1().ClusterProfiles(namespace).Watch(context.TODO(), options) }, }, - &apisv1alpha1.ClusterProfile{}, + &clusterinventoryapiapisv1alpha1.ClusterProfile{}, resyncPeriod, indexers, ) @@ -81,9 +81,9 @@ func (f *clusterProfileInformer) defaultInformer(client versioned.Interface, res } func (f *clusterProfileInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&apisv1alpha1.ClusterProfile{}, f.defaultInformer) + return f.factory.InformerFor(&clusterinventoryapiapisv1alpha1.ClusterProfile{}, f.defaultInformer) } -func (f *clusterProfileInformer) Lister() v1alpha1.ClusterProfileLister { - return v1alpha1.NewClusterProfileLister(f.Informer().GetIndexer()) +func (f *clusterProfileInformer) Lister() apisv1alpha1.ClusterProfileLister { + return apisv1alpha1.NewClusterProfileLister(f.Informer().GetIndexer()) } diff --git a/client/informers/externalversions/factory.go b/client/informers/externalversions/factory.go index 542f1cf..6e7d5fb 100644 --- a/client/informers/externalversions/factory.go +++ b/client/informers/externalversions/factory.go @@ -227,6 +227,7 @@ type SharedInformerFactory interface { // Start initializes all requested informers. They are handled in goroutines // which run until the stop channel gets closed. + // Warning: Start does not block. When run in a go-routine, it will race with a later WaitForCacheSync. Start(stopCh <-chan struct{}) // Shutdown marks a factory as shutting down. At that point no new diff --git a/client/informers/externalversions/generic.go b/client/informers/externalversions/generic.go index 9f7cb58..55c1bb3 100644 --- a/client/informers/externalversions/generic.go +++ b/client/informers/externalversions/generic.go @@ -18,7 +18,7 @@ limitations under the License. package externalversions import ( - "fmt" + fmt "fmt" schema "k8s.io/apimachinery/pkg/runtime/schema" cache "k8s.io/client-go/tools/cache" diff --git a/client/listers/apis/v1alpha1/clusterprofile.go b/client/listers/apis/v1alpha1/clusterprofile.go index 997429d..7a722c9 100644 --- a/client/listers/apis/v1alpha1/clusterprofile.go +++ b/client/listers/apis/v1alpha1/clusterprofile.go @@ -18,10 +18,10 @@ limitations under the License. package v1alpha1 import ( - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/tools/cache" - v1alpha1 "sigs.k8s.io/cluster-inventory-api/apis/v1alpha1" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" + apisv1alpha1 "sigs.k8s.io/cluster-inventory-api/apis/v1alpha1" ) // ClusterProfileLister helps list ClusterProfiles. @@ -29,7 +29,7 @@ import ( type ClusterProfileLister interface { // List lists all ClusterProfiles in the indexer. // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1alpha1.ClusterProfile, err error) + List(selector labels.Selector) (ret []*apisv1alpha1.ClusterProfile, err error) // ClusterProfiles returns an object that can list and get ClusterProfiles. ClusterProfiles(namespace string) ClusterProfileNamespaceLister ClusterProfileListerExpansion @@ -37,25 +37,17 @@ type ClusterProfileLister interface { // clusterProfileLister implements the ClusterProfileLister interface. type clusterProfileLister struct { - indexer cache.Indexer + listers.ResourceIndexer[*apisv1alpha1.ClusterProfile] } // NewClusterProfileLister returns a new ClusterProfileLister. func NewClusterProfileLister(indexer cache.Indexer) ClusterProfileLister { - return &clusterProfileLister{indexer: indexer} -} - -// List lists all ClusterProfiles in the indexer. -func (s *clusterProfileLister) List(selector labels.Selector) (ret []*v1alpha1.ClusterProfile, err error) { - err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*v1alpha1.ClusterProfile)) - }) - return ret, err + return &clusterProfileLister{listers.New[*apisv1alpha1.ClusterProfile](indexer, apisv1alpha1.Resource("clusterprofile"))} } // ClusterProfiles returns an object that can list and get ClusterProfiles. func (s *clusterProfileLister) ClusterProfiles(namespace string) ClusterProfileNamespaceLister { - return clusterProfileNamespaceLister{indexer: s.indexer, namespace: namespace} + return clusterProfileNamespaceLister{listers.NewNamespaced[*apisv1alpha1.ClusterProfile](s.ResourceIndexer, namespace)} } // ClusterProfileNamespaceLister helps list and get ClusterProfiles. @@ -63,36 +55,15 @@ func (s *clusterProfileLister) ClusterProfiles(namespace string) ClusterProfileN type ClusterProfileNamespaceLister interface { // List lists all ClusterProfiles in the indexer for a given namespace. // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1alpha1.ClusterProfile, err error) + List(selector labels.Selector) (ret []*apisv1alpha1.ClusterProfile, err error) // Get retrieves the ClusterProfile from the indexer for a given namespace and name. // Objects returned here must be treated as read-only. - Get(name string) (*v1alpha1.ClusterProfile, error) + Get(name string) (*apisv1alpha1.ClusterProfile, error) ClusterProfileNamespaceListerExpansion } // clusterProfileNamespaceLister implements the ClusterProfileNamespaceLister // interface. type clusterProfileNamespaceLister struct { - indexer cache.Indexer - namespace string -} - -// List lists all ClusterProfiles in the indexer for a given namespace. -func (s clusterProfileNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.ClusterProfile, err error) { - err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*v1alpha1.ClusterProfile)) - }) - return ret, err -} - -// Get retrieves the ClusterProfile from the indexer for a given namespace and name. -func (s clusterProfileNamespaceLister) Get(name string) (*v1alpha1.ClusterProfile, error) { - obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) - if err != nil { - return nil, err - } - if !exists { - return nil, errors.NewNotFound(v1alpha1.Resource("clusterprofile"), name) - } - return obj.(*v1alpha1.ClusterProfile), nil + listers.ResourceIndexer[*apisv1alpha1.ClusterProfile] } diff --git a/config/crd/bases/_.yaml b/config/crd/bases/_.yaml new file mode 100644 index 0000000..087f8a2 --- /dev/null +++ b/config/crd/bases/_.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 +spec: + group: "" + names: + kind: "" + plural: "" + scope: "" + versions: null diff --git a/config/crd/bases/multicluster.x-k8s.io_clusterprofiles.yaml b/config/crd/bases/multicluster.x-k8s.io_clusterprofiles.yaml index 0996e21..7164025 100644 --- a/config/crd/bases/multicluster.x-k8s.io_clusterprofiles.yaml +++ b/config/crd/bases/multicluster.x-k8s.io_clusterprofiles.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.17.3 name: clusterprofiles.multicluster.x-k8s.io spec: group: multicluster.x-k8s.io @@ -66,16 +66,8 @@ spec: description: Conditions contains the different condition statuses for this cluster. items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -116,12 +108,7 @@ spec: - 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. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string @@ -133,6 +120,86 @@ spec: - type type: object type: array + credentialProviders: + description: |- + CredentialProviders is a list credential providers that can provide kubeconfig + credential to connect to the cluster. + items: + properties: + cluster: + description: Cluster contains information about how to communicate + with a kubernetes cluster + properties: + certificate-authority: + description: CertificateAuthority is the path to a cert + file for the certificate authority. + type: string + certificate-authority-data: + description: CertificateAuthorityData contains PEM-encoded + certificate authority certificates. Overrides CertificateAuthority + format: byte + type: string + disable-compression: + description: |- + DisableCompression allows client to opt-out of response compression for all requests to the server. This is useful + to speed up requests (specifically lists) when client-server network bandwidth is ample, by saving time on + compression (server-side) and decompression (client-side): https://github.com/kubernetes/kubernetes/issues/112296. + type: boolean + extensions: + description: Extensions holds additional information. This + is useful for extenders so that reads and writes don't + clobber unknown fields + items: + description: NamedExtension relates nicknames to extension + information + properties: + extension: + description: Extension holds the extension information + type: object + x-kubernetes-preserve-unknown-fields: true + name: + description: Name is the nickname for this Extension + type: string + required: + - extension + - name + type: object + type: array + insecure-skip-tls-verify: + description: InsecureSkipTLSVerify skips the validity check + for the server's certificate. This will make your HTTPS + connections insecure. + type: boolean + proxy-url: + description: |- + ProxyURL is the URL to the proxy to be used for all requests made by this + client. URLs with "http", "https", and "socks5" schemes are supported. If + this configuration is not provided or the empty string, the client + attempts to construct a proxy configuration from http_proxy and + https_proxy environment variables. If these environment variables are not + set, the client does not attempt to proxy requests. + + socks5 proxying does not currently support spdy streaming endpoints (exec, + attach, port forward). + type: string + server: + description: Server is the address of the kubernetes cluster + (https://hostname:port). + type: string + tls-server-name: + description: TLSServerName is used to check server certificate. + If TLSServerName is empty, the hostname used to contact + the server is used. + type: string + required: + - server + type: object + name: + type: string + required: + - name + type: object + type: array properties: description: |- Properties defines cluster characteristics through a list of Property objects. diff --git a/go.mod b/go.mod index 16f53c2..24e5597 100644 --- a/go.mod +++ b/go.mod @@ -1,53 +1,53 @@ module sigs.k8s.io/cluster-inventory-api -go 1.23.0 +go 1.24.0 require ( github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.36.1 - k8s.io/api v0.32.3 - k8s.io/apimachinery v0.32.3 - k8s.io/client-go v0.32.3 + k8s.io/api v0.33.0 + k8s.io/apimachinery v0.33.0 + k8s.io/client-go v0.33.0 k8s.io/code-generator v0.32.1 - sigs.k8s.io/controller-runtime v0.20.3 + sigs.k8s.io/controller-runtime v0.20.4 ) require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-openapi/jsonpointer v0.21.1 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.9 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/prometheus/client_golang v1.20.5 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/x448/float16 v0.8.4 // indirect - golang.org/x/mod v0.21.0 // indirect - golang.org/x/net v0.37.0 // indirect - golang.org/x/oauth2 v0.28.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/term v0.30.0 // indirect - golang.org/x/text v0.23.0 // indirect + golang.org/x/mod v0.24.0 // indirect + golang.org/x/net v0.39.0 // indirect + golang.org/x/oauth2 v0.29.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/term v0.31.0 // indirect + golang.org/x/text v0.24.0 // indirect golang.org/x/time v0.11.0 // indirect - golang.org/x/tools v0.26.0 // indirect - google.golang.org/protobuf v1.36.5 // indirect + golang.org/x/tools v0.30.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect @@ -55,7 +55,7 @@ require ( k8s.io/apiextensions-apiserver v0.32.1 // indirect k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20250304201544-e5f78fe3ede9 // indirect + k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect diff --git a/go.sum b/go.sum index 5d640aa..31eb1bc 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjT github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= @@ -39,8 +39,6 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -50,8 +48,6 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -63,6 +59,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -94,19 +92,21 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= @@ -115,6 +115,8 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= @@ -124,24 +126,24 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= -golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= +golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -151,14 +153,14 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -166,8 +168,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -180,8 +182,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -197,26 +199,26 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= -k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= +k8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU= +k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM= k8s.io/apiextensions-apiserver v0.32.1 h1:hjkALhRUeCariC8DiVmb5jj0VjIc1N0DREP32+6UXZw= k8s.io/apiextensions-apiserver v0.32.1/go.mod h1:sxWIGuGiYov7Io1fAS2X06NjMIk5CbRHc2StSmbaQto= -k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= -k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= -k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= +k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ= +k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/client-go v0.33.0 h1:UASR0sAYVUzs2kYuKn/ZakZlcs2bEHaizrrHUZg0G98= +k8s.io/client-go v0.33.0/go.mod h1:kGkd+l/gNGg8GYWAPr0xF1rRKvVWvzh9vmZAMXtaKOg= k8s.io/code-generator v0.32.1 h1:4lw1kFNDuFYXquTkB7Sl5EwPMUP2yyW9hh6BnFfRZFY= k8s.io/code-generator v0.32.1/go.mod h1:zaILfm00CVyP/6/pJMJ3zxRepXkxyDfUV5SNG4CjZI4= k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 h1:si3PfKm8dDYxgfbeA6orqrtLkvvIeH8UqffFJDl0bz4= k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250304201544-e5f78fe3ede9 h1:t0huyHnz6HsokckRxAF1bY0cqPFwzINKCL7yltEjZQc= -k8s.io/kube-openapi v0.0.0-20250304201544-e5f78fe3ede9/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.20.3 h1:I6Ln8JfQjHH7JbtCD2HCYHoIzajoRxPNuvhvcDbZgkI= -sigs.k8s.io/controller-runtime v0.20.3/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= +sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU= +sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= diff --git a/pkg/README.md b/pkg/README.md new file mode 100644 index 0000000..8ff7319 --- /dev/null +++ b/pkg/README.md @@ -0,0 +1,4 @@ +# Build and run the controler + +go build -out controller.bin controller_example.go +./controller.bin -clusterprofile-provider-file ./cp-creds.json diff --git a/pkg/controller_example.go b/pkg/controller_example.go new file mode 100644 index 0000000..347e321 --- /dev/null +++ b/pkg/controller_example.go @@ -0,0 +1,45 @@ +package main + +import ( + "flag" + "log" + + clientcmdv1 "k8s.io/client-go/tools/clientcmd/api/v1" + "sigs.k8s.io/cluster-inventory-api/apis/v1alpha1" + "sigs.k8s.io/cluster-inventory-api/pkg/credentials" +) + +func main() { + credentialsProviders := credentials.SetupProviderFileFlag() + flag.Parse() + + cpCreds, err := credentials.NewFromFile(*credentialsProviders) + if err != nil { + log.Fatalf("Got error reading credentials providers: %v", err) + } + + // normally we would get this clusterprofile from the local cluster (maybe a watch?) + // and we would maintain the restconfigs for clusters we're interested in. + exampleClusterProfile := v1alpha1.ClusterProfile{ + Spec: v1alpha1.ClusterProfileSpec{ + DisplayName: "My Cluster", + }, + Status: v1alpha1.ClusterProfileStatus{ + CredentialProviders: []v1alpha1.CredentialProvider{ + { + Name: "gkeFleet", + Cluster: clientcmdv1.Cluster{ + Server: "https://myserver.tld:443", + }, + }, + }, + }, + } + + restConfigForMyCluster, err := cpCreds.BuildConfigFromCP(&exampleClusterProfile) + if err != nil { + log.Fatalf("Got error generating restConfig: %v", err) + } + log.Printf("Got credentials: %v", restConfigForMyCluster) + // I can then use this rest.Config to build a k8s client. +} diff --git a/pkg/cp-creds.json b/pkg/cp-creds.json new file mode 100644 index 0000000..98fc8c0 --- /dev/null +++ b/pkg/cp-creds.json @@ -0,0 +1,14 @@ +{ + "providers": [ + { + "name": "gkeFleet", + "execConfig": { + "apiVersion": "client.authentication.k8s.io/v1beta1", + "args": null, + "command": "gke-gcloud-auth-plugin", + "env": null, + "provideClusterInfo": true + } + } + ] +} diff --git a/pkg/credentials/config.go b/pkg/credentials/config.go new file mode 100644 index 0000000..9897a30 --- /dev/null +++ b/pkg/credentials/config.go @@ -0,0 +1,134 @@ +package credentials + +import ( + "encoding/json" + "flag" + "fmt" + "net/http" + "net/url" + "os" + + "k8s.io/client-go/pkg/apis/clientauthentication" + "k8s.io/client-go/rest" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + clientcmdv1 "k8s.io/client-go/tools/clientcmd/api/v1" + "sigs.k8s.io/cluster-inventory-api/apis/v1alpha1" +) + +type Provider struct { + Name string `json:"name"` + ExecConfig *clientcmdapi.ExecConfig `json:"execConfig"` +} + +type CredentialsProvider struct { + Providers []Provider `json:"providers"` +} + +func New(providers []Provider) *CredentialsProvider { + return &CredentialsProvider{ + Providers: providers, + } +} + +// SetupProviderFileFlag defines the -clusterprofile-provider-file command-line flag and returns a pointer +// to the string that will hold the path. flag.Parse() must still be called manually by the caller +func SetupProviderFileFlag() *string { + return flag.String("clusterprofile-provider-file", "clusterprofile-provider-file.json", "Path to the JSON configuration file") +} + +func NewFromFile(path string) (*CredentialsProvider, error) { + // 1. Read the file's content + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("failed to read credentials file: %w", err) + } + + // 2. Create a new Providers instance and unmarshal the data into it + var providers CredentialsProvider + if err := json.Unmarshal(data, &providers); err != nil { + return nil, fmt.Errorf("failed to unmarshal credential proviers: %w", err) + } + + // 3. Return the populated credentials + return &providers, nil +} + +func (cp *CredentialsProvider) BuildConfigFromCP(clusterprofile *v1alpha1.ClusterProfile) (*rest.Config, error) { + // 1. obtain the correct provider from the CP + provider := cp.getProviderFromClusterProfile(clusterprofile) + if provider == nil { + return nil, fmt.Errorf("no matching provider found for cluster profile %q", clusterprofile.Name) + } + cluster := convertCluster(provider.Cluster) + + // 2. Get Exec Config + execConfig := cp.getExecConfigFromConfig(provider.Name) + if execConfig == nil { + return nil, fmt.Errorf("no exec credentials found for provider %q", provider.Name) + } + + // 3. build resulting rest.Config + config := &rest.Config{ + Host: cluster.Server, + TLSClientConfig: rest.TLSClientConfig{ + CAData: cluster.CertificateAuthorityData, + }, + Proxy: func(request *http.Request) (*url.URL, error) { + if cluster.ProxyURL == "" { + return nil, nil + } + return url.Parse(cluster.ProxyURL) + }, + } + + config.ExecProvider = &clientcmdapi.ExecConfig{ + APIVersion: execConfig.APIVersion, + Command: execConfig.Command, + Args: execConfig.Args, + Env: execConfig.Env, + InteractiveMode: "Never", + ProvideClusterInfo: execConfig.ProvideClusterInfo, + } + + return config, nil +} + +func (cp *CredentialsProvider) getExecConfigFromConfig(providerName string) *clientcmdapi.ExecConfig { + for _, provider := range cp.Providers { + if provider.Name == providerName { + return provider.ExecConfig + } + } + return nil +} + +func (cp *CredentialsProvider) getProviderFromClusterProfile(cluster *v1alpha1.ClusterProfile) *v1alpha1.CredentialProvider { + cpProviderTypes := map[string]*v1alpha1.CredentialProvider{} + + for _, provider := range cluster.Status.CredentialProviders { + newProvider := provider.DeepCopy() + cpProviderTypes[provider.Name] = newProvider + } + + // we return the first provider that the CP supports. + for _, providerType := range cp.Providers { + if provider, found := cpProviderTypes[providerType.Name]; found { + return provider + } + } + return nil +} + +func convertCluster(cluster clientcmdv1.Cluster) *clientauthentication.Cluster { + return &clientauthentication.Cluster{ + Server: cluster.Server, + TLSServerName: cluster.TLSServerName, + InsecureSkipTLSVerify: cluster.InsecureSkipTLSVerify, + //CertificateAuthority: cluster.CertificateAuthority, + CertificateAuthorityData: cluster.CertificateAuthorityData, + ProxyURL: cluster.ProxyURL, + DisableCompression: cluster.DisableCompression, + } + // Certificate Authority is a file path, so it doesn't apply to us. + // Extensions is unclear on how we could use it and is not relevant at the moment. +} diff --git a/pkg/credentials/config_test.go b/pkg/credentials/config_test.go new file mode 100644 index 0000000..f9b0961 --- /dev/null +++ b/pkg/credentials/config_test.go @@ -0,0 +1,394 @@ +/* +Copyright 2024 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 credentials + +import ( + "encoding/json" + "flag" + "os" + "path/filepath" + "testing" + + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + clientauthenticationv1 "k8s.io/client-go/pkg/apis/clientauthentication/v1" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + clientcmdv1 "k8s.io/client-go/tools/clientcmd/api/v1" + "sigs.k8s.io/cluster-inventory-api/apis/v1alpha1" +) + +func TestCredentials(t *testing.T) { + gomega.RegisterFailHandler(ginkgo.Fail) + ginkgo.RunSpecs(t, "Credentials Package Suite") +} + +var _ = ginkgo.Describe("CredentialsProvider", func() { + var ( + tempDir string + testProviders []Provider + credentialsProvider *CredentialsProvider + ) + + ginkgo.BeforeEach(func() { + var err error + tempDir, err = os.MkdirTemp("", "credentials-test") + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + testProviders = []Provider{ + { + Name: "test-provider-1", + ExecConfig: &clientcmdapi.ExecConfig{ + Command: "test-command-1", + Args: []string{"arg1", "arg2"}, + APIVersion: "client.authentication.k8s.io/v1beta1", + }, + }, + { + Name: "test-provider-2", + ExecConfig: &clientcmdapi.ExecConfig{ + Command: "test-command-2", + Args: []string{"arg3"}, + APIVersion: "client.authentication.k8s.io/v1beta1", + }, + }, + } + credentialsProvider = New(testProviders) + }) + + ginkgo.AfterEach(func() { + if tempDir != "" { + err := os.RemoveAll(tempDir) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + } + }) + + ginkgo.Describe("New", func() { + ginkgo.It("should create a new CredentialsProvider with provided providers", func() { + providers := []Provider{ + {Name: "provider1", ExecConfig: &clientcmdapi.ExecConfig{Command: "cmd1"}}, + {Name: "provider2", ExecConfig: &clientcmdapi.ExecConfig{Command: "cmd2"}}, + } + cp := New(providers) + gomega.Expect(cp).NotTo(gomega.BeNil()) + gomega.Expect(cp.Providers).To(gomega.HaveLen(2)) + gomega.Expect(cp.Providers[0].Name).To(gomega.Equal("provider1")) + gomega.Expect(cp.Providers[1].Name).To(gomega.Equal("provider2")) + }) + + ginkgo.It("should create a CredentialsProvider with empty providers", func() { + cp := New([]Provider{}) + gomega.Expect(cp).NotTo(gomega.BeNil()) + gomega.Expect(cp.Providers).To(gomega.HaveLen(0)) + }) + + ginkgo.It("should create a CredentialsProvider with nil providers", func() { + cp := New(nil) + gomega.Expect(cp).NotTo(gomega.BeNil()) + gomega.Expect(cp.Providers).To(gomega.BeNil()) + }) + }) + + ginkgo.Describe("SetupProviderFileFlag", func() { + ginkgo.It("should define a command-line flag and return a pointer to the string", func() { + // Reset flag package for clean test + flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) + + filePath := SetupProviderFileFlag() + gomega.Expect(filePath).NotTo(gomega.BeNil()) + gomega.Expect(*filePath).To(gomega.Equal("clusterprofile-provider-file.json")) + + // Verify the flag was registered + flagSet := flag.CommandLine + flagValue := flagSet.Lookup("clusterprofile-provider-file") + gomega.Expect(flagValue).NotTo(gomega.BeNil()) + gomega.Expect(flagValue.DefValue).To(gomega.Equal("clusterprofile-provider-file.json")) + }) + }) + + ginkgo.Describe("NewFromFile", func() { + ginkgo.It("should successfully read and unmarshal a valid JSON file", func() { + // Create a test JSON file + testData := CredentialsProvider{ + Providers: []Provider{ + { + Name: "gkeFleet", + ExecConfig: &clientcmdapi.ExecConfig{ + APIVersion: "client.authentication.k8s.io/v1beta1", + Command: "gke-gcloud-auth-plugin", + ProvideClusterInfo: true, + }, + }, + }, + } + + jsonData, err := json.Marshal(testData) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + testFile := filepath.Join(tempDir, "test-config.json") + err = os.WriteFile(testFile, jsonData, 0644) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + // Test reading the file + cp, err := NewFromFile(testFile) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(cp).NotTo(gomega.BeNil()) + gomega.Expect(cp.Providers).To(gomega.HaveLen(1)) + gomega.Expect(cp.Providers[0].Name).To(gomega.Equal("gkeFleet")) + gomega.Expect(cp.Providers[0].ExecConfig.Command).To(gomega.Equal("gke-gcloud-auth-plugin")) + }) + + ginkgo.It("should return an error when file does not exist", func() { + nonExistentFile := filepath.Join(tempDir, "non-existent.json") + cp, err := NewFromFile(nonExistentFile) + gomega.Expect(err).To(gomega.HaveOccurred()) + gomega.Expect(cp).To(gomega.BeNil()) + gomega.Expect(err.Error()).To(gomega.ContainSubstring("failed to read credentials file")) + }) + + ginkgo.It("should return an error when file contains invalid JSON", func() { + invalidJSONFile := filepath.Join(tempDir, "invalid.json") + err := os.WriteFile(invalidJSONFile, []byte("invalid json content"), 0644) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + cp, err := NewFromFile(invalidJSONFile) + gomega.Expect(err).To(gomega.HaveOccurred()) + gomega.Expect(cp).To(gomega.BeNil()) + gomega.Expect(err.Error()).To(gomega.ContainSubstring("failed to unmarshal credential proviers")) + }) + + ginkgo.It("should handle empty JSON file", func() { + emptyJSONFile := filepath.Join(tempDir, "empty.json") + err := os.WriteFile(emptyJSONFile, []byte("{}"), 0644) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + cp, err := NewFromFile(emptyJSONFile) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(cp).NotTo(gomega.BeNil()) + gomega.Expect(cp.Providers).To(gomega.BeEmpty()) + }) + }) + + ginkgo.Describe("getExecConfigFromConfig", func() { + ginkgo.It("should return the correct ExecConfig for existing provider", func() { + execConfig := credentialsProvider.getExecConfigFromConfig("test-provider-1") + gomega.Expect(execConfig).NotTo(gomega.BeNil()) + gomega.Expect(execConfig.Command).To(gomega.Equal("test-command-1")) + gomega.Expect(execConfig.Args).To(gomega.Equal([]string{"arg1", "arg2"})) + }) + + ginkgo.It("should return nil for non-existing provider", func() { + execConfig := credentialsProvider.getExecConfigFromConfig("non-existent-provider") + gomega.Expect(execConfig).To(gomega.BeNil()) + }) + + ginkgo.It("should return nil for empty provider name", func() { + execConfig := credentialsProvider.getExecConfigFromConfig("") + gomega.Expect(execConfig).To(gomega.BeNil()) + }) + + ginkgo.It("should handle CredentialsProvider with no providers", func() { + emptyCP := New([]Provider{}) + execConfig := emptyCP.getExecConfigFromConfig("any-provider") + gomega.Expect(execConfig).To(gomega.BeNil()) + }) + }) + + ginkgo.Describe("getProviderFromClusterProfile", func() { + var clusterProfile *v1alpha1.ClusterProfile + + ginkgo.BeforeEach(func() { + clusterProfile = &v1alpha1.ClusterProfile{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + }, + Status: v1alpha1.ClusterProfileStatus{ + CredentialProviders: []v1alpha1.CredentialProvider{ + { + Name: "test-provider-1", + Cluster: clientcmdv1.Cluster{ + Server: "https://test-server-1.com", + CertificateAuthorityData: []byte("test-ca-data-1"), + }, + }, + { + Name: "test-provider-2", + Cluster: clientcmdv1.Cluster{ + Server: "https://test-server-2.com", + CertificateAuthorityData: []byte("test-ca-data-2"), + }, + }, + { + Name: "unsupported-provider", + Cluster: clientcmdv1.Cluster{ + Server: "https://unsupported-server.com", + }, + }, + }, + }, + } + }) + + ginkgo.It("should return the first matching provider", func() { + provider := credentialsProvider.getProviderFromClusterProfile(clusterProfile) + gomega.Expect(provider).NotTo(gomega.BeNil()) + gomega.Expect(provider.Name).To(gomega.Equal("test-provider-1")) + gomega.Expect(provider.Cluster.Server).To(gomega.Equal("https://test-server-1.com")) + }) + + ginkgo.It("should return nil when no matching provider is found", func() { + // Create a CredentialsProvider with providers that don't match the ClusterProfile + mismatchedCP := New([]Provider{ + {Name: "different-provider", ExecConfig: &clientcmdapi.ExecConfig{Command: "cmd"}}, + }) + provider := mismatchedCP.getProviderFromClusterProfile(clusterProfile) + gomega.Expect(provider).To(gomega.BeNil()) + }) + + ginkgo.It("should handle ClusterProfile with no credential providers", func() { + emptyClusterProfile := &v1alpha1.ClusterProfile{ + ObjectMeta: metav1.ObjectMeta{Name: "empty-cluster"}, + Status: v1alpha1.ClusterProfileStatus{}, + } + provider := credentialsProvider.getProviderFromClusterProfile(emptyClusterProfile) + gomega.Expect(provider).To(gomega.BeNil()) + }) + + ginkgo.It("should return a deep copy of the provider", func() { + provider := credentialsProvider.getProviderFromClusterProfile(clusterProfile) + gomega.Expect(provider).NotTo(gomega.BeNil()) + + // Modify the original cluster profile + originalServer := clusterProfile.Status.CredentialProviders[0].Cluster.Server + clusterProfile.Status.CredentialProviders[0].Cluster.Server = "modified-server" + + // The returned provider should not be affected + gomega.Expect(provider.Cluster.Server).To(gomega.Equal(originalServer)) + gomega.Expect(provider.Cluster.Server).NotTo(gomega.Equal("modified-server")) + }) + }) + + ginkgo.Describe("convertCluster", func() { + ginkgo.It("should convert clientcmdv1.Cluster to clientauthentication.Cluster", func() { + inputCluster := clientcmdv1.Cluster{ + Server: "https://test-server.com", + TLSServerName: "test-tls-server", + InsecureSkipTLSVerify: true, + CertificateAuthorityData: []byte("test-ca-data"), + ProxyURL: "http://proxy.example.com", + DisableCompression: true, + } + + result := convertCluster(inputCluster) + gomega.Expect(result).NotTo(gomega.BeNil()) + gomega.Expect(result.Server).To(gomega.Equal("https://test-server.com")) + gomega.Expect(result.TLSServerName).To(gomega.Equal("test-tls-server")) + gomega.Expect(result.InsecureSkipTLSVerify).To(gomega.BeTrue()) + gomega.Expect(result.CertificateAuthorityData).To(gomega.Equal([]byte("test-ca-data"))) + gomega.Expect(result.ProxyURL).To(gomega.Equal("http://proxy.example.com")) + gomega.Expect(result.DisableCompression).To(gomega.BeTrue()) + }) + + ginkgo.It("should handle empty cluster", func() { + inputCluster := clientcmdv1.Cluster{} + result := convertCluster(inputCluster) + gomega.Expect(result).NotTo(gomega.BeNil()) + gomega.Expect(result.Server).To(gomega.BeEmpty()) + gomega.Expect(result.TLSServerName).To(gomega.BeEmpty()) + gomega.Expect(result.InsecureSkipTLSVerify).To(gomega.BeFalse()) + gomega.Expect(result.CertificateAuthorityData).To(gomega.BeNil()) + gomega.Expect(result.ProxyURL).To(gomega.BeEmpty()) + gomega.Expect(result.DisableCompression).To(gomega.BeFalse()) + }) + }) + + ginkgo.Describe("BuildConfigFromCP", func() { + var clusterProfile *v1alpha1.ClusterProfile + + ginkgo.BeforeEach(func() { + clusterProfile = &v1alpha1.ClusterProfile{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + }, + Status: v1alpha1.ClusterProfileStatus{ + CredentialProviders: []v1alpha1.CredentialProvider{ + { + Name: "test-provider-1", + Cluster: clientcmdv1.Cluster{ + Server: "https://test-server.com", + CertificateAuthorityData: []byte("test-ca-data"), + ProxyURL: "http://proxy.example.com", + }, + }, + }, + }, + } + }) + + ginkgo.It("should return an error when no matching provider is found", func() { + // Create a CredentialsProvider with providers that don't match the ClusterProfile + mismatchedCP := New([]Provider{ + {Name: "different-provider", ExecConfig: &clientcmdapi.ExecConfig{Command: "cmd"}}, + }) + config, err := mismatchedCP.BuildConfigFromCP(clusterProfile) + gomega.Expect(err).To(gomega.HaveOccurred()) + gomega.Expect(config).To(gomega.BeNil()) + gomega.Expect(err.Error()).To(gomega.ContainSubstring("no matching provider found for cluster profile")) + }) + + ginkgo.It("should return an error when no exec config is found", func() { + // Create a CredentialsProvider with matching provider name but no ExecConfig + noExecCP := New([]Provider{ + {Name: "test-provider-1", ExecConfig: nil}, + }) + config, err := noExecCP.BuildConfigFromCP(clusterProfile) + gomega.Expect(err).To(gomega.HaveOccurred()) + gomega.Expect(config).To(gomega.BeNil()) + gomega.Expect(err.Error()).To(gomega.ContainSubstring("no exec credentials found for provider")) + }) + + ginkgo.It("should build config successfully", func() { + cred := clientauthenticationv1.ExecCredential{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "client.authentication.k8s.io/v1", + Kind: "ExecCredential", + }, + Status: &clientauthenticationv1.ExecCredentialStatus{ + Token: "test-token", + }, + } + jsonData, err := json.Marshal(cred) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + testFile := filepath.Join(tempDir, "test-config.json") + err = os.WriteFile(testFile, jsonData, 0644) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + execCP := New([]Provider{ + {Name: "test-provider-1", ExecConfig: &clientcmdapi.ExecConfig{ + APIVersion: "client.authentication.k8s.io/v1", + Command: "cat", + Args: []string{testFile}, + }}, + }) + + config, err := execCP.BuildConfigFromCP(clusterProfile) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(config).NotTo(gomega.BeNil()) + gomega.Expect(config.Host).To(gomega.Equal("https://test-server.com")) + gomega.Expect(config.TLSClientConfig.CAData).To(gomega.Equal([]byte("test-ca-data"))) + }) + }) +}) diff --git a/test/integration/credentials_test.go b/test/integration/credentials_test.go new file mode 100644 index 0000000..68975a7 --- /dev/null +++ b/test/integration/credentials_test.go @@ -0,0 +1,142 @@ +package integration + +import ( + "context" + "encoding/json" + "fmt" + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + "k8s.io/client-go/kubernetes" + clientauthenticationv1 "k8s.io/client-go/pkg/apis/clientauthentication/v1" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + clientcmdv1 "k8s.io/client-go/tools/clientcmd/api/v1" + "os" + "path/filepath" + "sigs.k8s.io/cluster-inventory-api/apis/v1alpha1" + "sigs.k8s.io/cluster-inventory-api/pkg/credentials" + "sigs.k8s.io/yaml" +) + +var _ = ginkgo.Describe("Credentials test", func() { + var clusterName string + var clusterManagerName string + var tempDir string + + ginkgo.BeforeEach(func() { + clusterName = fmt.Sprintf("cluster-%s", rand.String(5)) + clusterManagerName = fmt.Sprintf("cluster-manager-%s", rand.String(5)) + + clusterProfile := &v1alpha1.ClusterProfile{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterName, + Labels: map[string]string{v1alpha1.LabelClusterManagerKey: clusterManagerName}, + Namespace: testNamespace, + }, + Spec: v1alpha1.ClusterProfileSpec{ + DisplayName: clusterName, + ClusterManager: v1alpha1.ClusterManager{ + Name: clusterManagerName, + }, + }, + } + + _, err := clusterProfileClient.ApisV1alpha1().ClusterProfiles(testNamespace).Create( + context.TODO(), + clusterProfile, + metav1.CreateOptions{}, + ) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + + tempDir, err = os.MkdirTemp("", "integration-credentials-test") + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + }) + + ginkgo.AfterEach(func() { + if tempDir != "" { + err := os.RemoveAll(tempDir) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + } + }) + + ginkgo.It("Should get credntial by cluster profile", func() { + cp, err := clusterProfileClient.ApisV1alpha1().ClusterProfiles(testNamespace).Get( + context.TODO(), clusterName, metav1.GetOptions{}) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + + cp.Status = v1alpha1.ClusterProfileStatus{ + CredentialProviders: []v1alpha1.CredentialProvider{ + { + Name: "provider1", + Cluster: clientcmdv1.Cluster{ + Server: cfg.Host, + CertificateAuthorityData: cfg.CAData, + }, + }, + }, + } + + cp, err = clusterProfileClient.ApisV1alpha1().ClusterProfiles(testNamespace).UpdateStatus( + context.TODO(), + cp, + metav1.UpdateOptions{}, + ) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + + // read creds in existing rest.Config and put into execCredential. + credConfig := clientauthenticationv1.ExecCredential{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "client.authentication.k8s.io/v1", + Kind: "ExecCredential", + }, + Status: &clientauthenticationv1.ExecCredentialStatus{ + ClientCertificateData: string(cfg.CertData), + ClientKeyData: string(cfg.KeyData), + }, + } + + credData, err := yaml.Marshal(credConfig) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + credFile := filepath.Join(tempDir, "test-cred.yaml") + err = os.WriteFile(credFile, credData, 0644) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + // Create provider configuration file + credProviderConfig := credentials.CredentialsProvider{ + Providers: []credentials.Provider{ + { + Name: "provider1", + ExecConfig: &clientcmdapi.ExecConfig{ + APIVersion: "client.authentication.k8s.io/v1", + Command: "cat", + Args: []string{credFile}, + ProvideClusterInfo: true, + }, + }, + }, + } + + credProviderData, err := json.Marshal(credProviderConfig) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + configFile := filepath.Join(tempDir, "test-config.json") + err = os.WriteFile(configFile, credProviderData, 0644) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + credentialConfig, err := credentials.NewFromFile(configFile) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + config, err := credentialConfig.BuildConfigFromCP(cp) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(config).NotTo(gomega.BeNil()) + + kubeClientFromCP, err := kubernetes.NewForConfig(config) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + // test if the client can connect to the cluster + _, err = kubeClientFromCP.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + }) +}) diff --git a/test/integration/suite_test.go b/test/integration/suite_test.go index 70b5def..dbe8095 100644 --- a/test/integration/suite_test.go +++ b/test/integration/suite_test.go @@ -18,16 +18,15 @@ package integration import ( "context" - "path/filepath" - "testing" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/rand" + "k8s.io/client-go/rest" + "path/filepath" + "testing" "github.com/onsi/ginkgo" "github.com/onsi/gomega" - "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/envtest" @@ -36,6 +35,7 @@ import ( var testEnv *envtest.Environment var testNamespace string +var cfg *rest.Config var kubernetesClient kubernetes.Interface var clusterProfileClient cpclientset.Interface @@ -55,7 +55,8 @@ var _ = ginkgo.BeforeSuite(func(done ginkgo.Done) { }, } - cfg, err := testEnv.Start() + var err error + cfg, err = testEnv.Start() gomega.Expect(err).ToNot(gomega.HaveOccurred()) gomega.Expect(cfg).ToNot(gomega.BeNil())