@@ -12,69 +12,94 @@ import (
1212 "maps"
1313 "strings"
1414
15- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16- "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
17-
1815 "github.com/cloudoperators/greenhouse/pkg/apis/greenhouse/v1alpha1"
1916 "github.com/spf13/cobra"
2017 "k8s.io/apimachinery/pkg/runtime"
21- "k8s.io/apimachinery/pkg/runtime/schema"
22- "k8s.io/client-go/dynamic"
18+ "k8s.io/client-go/rest"
2319 clientcmd "k8s.io/client-go/tools/clientcmd"
2420 clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
21+ "sigs.k8s.io/controller-runtime/pkg/client"
2522)
2623
2724var (
28- greenhouseCentralClusterKubeconfig string
29- greenhouseRemoteClusterKubeconfig string
30- prefix string
31- mergeIdenticalUsers bool
25+ greenhouseClusterKubeconfig string
26+ greenhouseClusterContext string
27+ greenhouseClusterNamespace string
28+ remoteClusterKubeconfig string
29+ remoteClusterName string
30+ prefix string
31+ mergeIdenticalUsers bool
3232)
3333
3434func init () {
35- syncCmd .Flags ().StringVar (& greenhouseCentralClusterKubeconfig , "central-cluster-kubeconfig" , clientcmd .RecommendedHomeFile , "kubeconfig for central Greenhouse cluster" )
36- syncCmd .Flags ().StringVar (& greenhouseRemoteClusterKubeconfig , "remote-cluster-kubeconfig" , clientcmd .RecommendedHomeFile , "kubeconfig for remote Greenhouse clusters" )
37- syncCmd .Flags ().StringVar (& prefix , "prefix" , "cloudctl" , "prefix for kubeconfig entries. It is used to separate and manage the entries of this tool only" )
35+ syncCmd .Flags ().StringVarP (& greenhouseClusterKubeconfig , "greenhouse-cluster-kubeconfig" , "k" , clientcmd .RecommendedHomeFile , "kubeconfig file path for Greenhouse cluster" )
36+ syncCmd .Flags ().StringVarP (& greenhouseClusterContext , "greenhouse-cluster-context" , "c" , "" , "context in greenhouse-cluster-kubeconfig, the context in the file is used if this flag is not set" )
37+ syncCmd .Flags ().StringVarP (& greenhouseClusterNamespace , "greenhouse-cluster-namespace" , "n" , "" , "namespace for greenhouse-cluster-kubeconfig, it is the same value as Greenhouse organization" )
38+ syncCmd .MarkFlagRequired ("greenhouse-cluster-namespace" )
39+ syncCmd .Flags ().StringVarP (& remoteClusterKubeconfig , "remote-cluster-kubeconfig" , "r" , clientcmd .RecommendedHomeFile , "kubeconfig file path for remote clusters" )
40+ syncCmd .Flags ().StringVar (& remoteClusterName , "remote-cluster-name" , "" , "name of the remote cluster, if not set all clusters are retrieved" )
41+ syncCmd .Flags ().StringVar (& prefix , "prefix" , "cloudctl" , "prefix for kubeconfig entries. it is used to separate and manage the entries of this tool only" )
3842 syncCmd .Flags ().BoolVar (& mergeIdenticalUsers , "merge-identical-users" , true , "merge identical user information in kubeconfig file so that you only login once for the clusters that share the same auth info" )
3943}
4044
4145var (
4246 syncCmd = & cobra.Command {
4347 Use : "sync" ,
44- Short : "Fetches remote kubeconfigs from Greenhouse cluster and merges them into your local config" ,
48+ Short : "Fetches kubeconfigs of remote clusters from Greenhouse cluster and merges them into your local config" ,
4549 RunE : runSync ,
4650 }
4751)
4852
4953func runSync (cmd * cobra.Command , args []string ) error {
5054
51- centralConfig , err := clientcmd .BuildConfigFromFlags ("" , greenhouseCentralClusterKubeconfig )
55+ centralConfig , err := clientcmd .BuildConfigFromFlags ("" , greenhouseClusterKubeconfig )
5256 if err != nil {
53- return fmt .Errorf ("failed to build central kubeconfig: %w" , err )
57+ return fmt .Errorf ("failed to build greenhouse kubeconfig: %w" , err )
5458 }
5559
56- dynamicClient , err := dynamic .NewForConfig (centralConfig )
57- if err != nil {
58- return fmt .Errorf ("failed to create dynamic client: %w" , err )
60+ if greenhouseClusterContext != "" {
61+ centralConfig , err = configWithContext (greenhouseClusterContext , greenhouseClusterKubeconfig )
62+ if err != nil {
63+ return fmt .Errorf ("failed to build greenhouse kubeconfig with context %s: %w" , greenhouseClusterContext , err )
64+ }
5965 }
6066
61- gvr := schema. GroupVersionResource {
62- Group : "greenhouse.sap" ,
63- Version : " v1alpha1" ,
64- Resource : "clusterkubeconfigs" ,
67+ // Create a scheme and register Greenhouse types.
68+ scheme := runtime . NewScheme ()
69+ if err := v1alpha1 . AddToScheme ( scheme ); err != nil {
70+ return fmt . Errorf ( "failed to add greenhouse scheme: %w" , err )
6571 }
6672
67- unstructuredList , err := dynamicClient .Resource (gvr ).Namespace ("ccloud" ).List (cmd .Context (), metav1.ListOptions {})
73+ // Create a typed client.
74+ c , err := client .New (centralConfig , client.Options {Scheme : scheme })
6875 if err != nil {
69- return fmt .Errorf ("failed to list ClusterKubeconfigs : %w" , err )
76+ return fmt .Errorf ("failed to create client : %w" , err )
7077 }
7178
72- if len (unstructuredList .Items ) == 0 {
79+ ctx := cmd .Context ()
80+ var clusterKubeconfigs []v1alpha1.ClusterKubeconfig
81+
82+ // If a specific remote cluster name is provided, fetch that single resource;
83+ // otherwise, list all ClusterKubeconfigs in the given namespace.
84+ if remoteClusterName != "" {
85+ var ckc v1alpha1.ClusterKubeconfig
86+ if err := c .Get (ctx , client.ObjectKey {Namespace : greenhouseClusterNamespace , Name : remoteClusterName }, & ckc ); err != nil {
87+ return fmt .Errorf ("failed to get ClusterKubeconfig %q: %w" , remoteClusterName , err )
88+ }
89+ clusterKubeconfigs = append (clusterKubeconfigs , ckc )
90+ } else {
91+ var list v1alpha1.ClusterKubeconfigList
92+ if err := c .List (ctx , & list , client .InNamespace (greenhouseClusterNamespace )); err != nil {
93+ return fmt .Errorf ("failed to list ClusterKubeconfigs: %w" , err )
94+ }
95+ clusterKubeconfigs = list .Items
96+ }
97+ if len (clusterKubeconfigs ) == 0 {
7398 log .Println ("No ClusterKubeconfigs found to sync." )
7499 return nil
75100 }
76101
77- localConfig , err := clientcmd .LoadFromFile (greenhouseRemoteClusterKubeconfig )
102+ localConfig , err := clientcmd .LoadFromFile (remoteClusterKubeconfig )
78103 if err != nil {
79104 return fmt .Errorf ("failed to load local kubeconfig: %w" , err )
80105 }
@@ -83,7 +108,7 @@ func runSync(cmd *cobra.Command, args []string) error {
83108 localConfig = clientcmdapi .NewConfig ()
84109 }
85110
86- serverConfig , err := buildIncomingKubeconfig (unstructuredList . Items )
111+ serverConfig , err := buildIncomingKubeconfig (clusterKubeconfigs )
87112 if err != nil {
88113 return fmt .Errorf ("failed to create server config: %w" , err )
89114 }
@@ -93,52 +118,47 @@ func runSync(cmd *cobra.Command, args []string) error {
93118 return fmt .Errorf ("failed to merge ClusterKubeconfig: %w" , err )
94119 }
95120
96- err = writeConfig (localConfig , greenhouseRemoteClusterKubeconfig )
121+ err = writeConfig (localConfig , remoteClusterKubeconfig )
97122 if err != nil {
98123 return fmt .Errorf ("failed to write merged kubeconfig: %w" , err )
99124 }
100125
101- log .Println ("Successfully synced and merged the new cluster kubeconfig with your local config." )
126+ log .Println ("Successfully synced and merged into your local config." )
102127 return nil
103128}
104129
105- func buildIncomingKubeconfig (items []unstructured.Unstructured ) (* clientcmdapi.Config , error ) {
130+ // buildIncomingKubeconfig converts the list of typed ClusterKubeconfig objects
131+ // into a clientcmdapi.Config.
132+ func buildIncomingKubeconfig (items []v1alpha1.ClusterKubeconfig ) (* clientcmdapi.Config , error ) {
106133 kubeconfig := clientcmdapi .NewConfig ()
107134
108- for _ , unstructuredItem := range items {
109- var ckc v1alpha1.ClusterKubeconfig
110- err := runtime .DefaultUnstructuredConverter .FromUnstructured (unstructuredItem .Object , & ckc )
111- if err != nil {
112- return nil , fmt .Errorf ("failed to convert unstructured to ClusterKubeconfig: %w" , err )
113- }
114-
115- // Assuming each ClusterKubeconfig has exactly one context, authInfo, and cluster
135+ for _ , ckc := range items {
136+ // Assuming each ClusterKubeconfig has exactly one context, authInfo, and cluster.
116137 if len (ckc .Spec .Kubeconfig .Contexts ) > 0 {
117- ctx := ckc .Spec .Kubeconfig .Contexts [0 ]
118- kubeconfig .Contexts [ctx .Name ] = & clientcmdapi.Context {
119- Cluster : ctx .Context .Cluster ,
120- AuthInfo : ctx .Context .AuthInfo ,
121- Namespace : ctx .Context .Namespace ,
138+ ctxItem := ckc .Spec .Kubeconfig .Contexts [0 ]
139+ kubeconfig .Contexts [ctxItem .Name ] = & clientcmdapi.Context {
140+ Cluster : ctxItem .Context .Cluster ,
141+ AuthInfo : ctxItem .Context .AuthInfo ,
142+ Namespace : ctxItem .Context .Namespace ,
122143 }
123144 }
124145
125146 if len (ckc .Spec .Kubeconfig .AuthInfo ) > 0 {
126- auth := ckc .Spec .Kubeconfig .AuthInfo [0 ]. AuthInfo
127- kubeconfig .AuthInfos [ckc . Spec . Kubeconfig . AuthInfo [ 0 ] .Name ] = & clientcmdapi.AuthInfo {
128- ClientCertificateData : auth .ClientCertificateData ,
129- ClientKeyData : auth .ClientKeyData ,
130- AuthProvider : & auth .AuthProvider ,
147+ authItem := ckc .Spec .Kubeconfig .AuthInfo [0 ]
148+ kubeconfig .AuthInfos [authItem .Name ] = & clientcmdapi.AuthInfo {
149+ ClientCertificateData : authItem . AuthInfo .ClientCertificateData ,
150+ ClientKeyData : authItem . AuthInfo .ClientKeyData ,
151+ AuthProvider : & authItem . AuthInfo .AuthProvider ,
131152 }
132153 }
133154
134155 if len (ckc .Spec .Kubeconfig .Clusters ) > 0 {
135- cluster := ckc .Spec .Kubeconfig .Clusters [0 ]. Cluster
136- kubeconfig .Clusters [ckc . Spec . Kubeconfig . Clusters [ 0 ] .Name ] = & clientcmdapi.Cluster {
137- Server : cluster .Server ,
138- CertificateAuthorityData : cluster .CertificateAuthorityData ,
156+ clusterItem := ckc .Spec .Kubeconfig .Clusters [0 ]
157+ kubeconfig .Clusters [clusterItem .Name ] = & clientcmdapi.Cluster {
158+ Server : clusterItem . Cluster .Server ,
159+ CertificateAuthorityData : clusterItem . Cluster .CertificateAuthorityData ,
139160 }
140161 }
141-
142162 }
143163
144164 return kubeconfig , nil
@@ -447,3 +467,11 @@ func mergeAuthInfo(serverAuth, localAuth *clientcmdapi.AuthInfo) *clientcmdapi.A
447467
448468 return mergedAuth
449469}
470+
471+ func configWithContext (context , kubeconfigPath string ) (* rest.Config , error ) {
472+ return clientcmd .NewNonInteractiveDeferredLoadingClientConfig (
473+ & clientcmd.ClientConfigLoadingRules {ExplicitPath : kubeconfigPath },
474+ & clientcmd.ConfigOverrides {
475+ CurrentContext : context ,
476+ }).ClientConfig ()
477+ }
0 commit comments