@@ -14,15 +14,17 @@ See the License for the specific language governing permissions and
1414limitations under the License.
1515*/
1616
17- package main
17+ package drain
1818
1919import (
2020 "context"
2121 "fmt"
2222 "log"
2323
24+ "github.com/spf13/cobra"
2425 k8errors "k8s.io/apimachinery/pkg/api/errors"
2526 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+ "k8s.io/apimachinery/pkg/runtime"
2628 "k8s.io/apimachinery/pkg/types"
2729 "k8s.io/apimachinery/pkg/util/uuid"
2830 "k8s.io/apimachinery/pkg/util/validation"
@@ -43,30 +45,54 @@ const (
4345 resourceIdentifierKeyFormat = "%s/%s/%s/%s/%s"
4446)
4547
48+ var (
49+ hubClusterContext string
50+ clusterName string
51+ )
52+
53+ // setupClient creates and configures the Kubernetes client
54+ func setupClient () (* runtime.Scheme , client.Client , error ) {
55+ scheme := runtime .NewScheme ()
56+
57+ if err := clusterv1beta1 .AddToScheme (scheme ); err != nil {
58+ log .Fatalf ("failed to add custom APIs (cluster) to the runtime scheme: %v" , err )
59+ }
60+ if err := placementv1beta1 .AddToScheme (scheme ); err != nil {
61+ log .Fatalf ("failed to add custom APIs (placement) to the runtime scheme: %v" , err )
62+ }
63+
64+ hubClient , err := toolsutils .GetClusterClientFromClusterContext (hubClusterContext , scheme )
65+ if err != nil {
66+ log .Fatalf ("failed to create hub cluster client: %v" , err )
67+ }
68+
69+ return scheme , hubClient , nil
70+ }
71+
4672type drainHelper struct {
47- hubClient client.Client
48- clusterName string
73+ HubClient client.Client
74+ ClusterName string
4975}
5076
5177func (h * drainHelper ) Drain (ctx context.Context ) (bool , error ) {
5278 if err := h .cordon (ctx ); err != nil {
53- return false , fmt .Errorf ("failed to cordon member cluster %s: %w" , h .clusterName , err )
79+ return false , fmt .Errorf ("failed to cordon member cluster %s: %w" , h .ClusterName , err )
5480 }
55- log .Printf ("Successfully cordoned member cluster %s by adding cordon taint" , h .clusterName )
81+ log .Printf ("Successfully cordoned member cluster %s by adding cordon taint" , h .ClusterName )
5682
5783 crpNameMap , err := h .fetchClusterResourcePlacementNamesToEvict (ctx )
5884 if err != nil {
5985 return false , err
6086 }
6187
6288 if len (crpNameMap ) == 0 {
63- log .Printf ("There are currently no resources propagated to %s from fleet using ClusterResourcePlacement resources" , h .clusterName )
89+ log .Printf ("There are currently no resources propagated to %s from fleet using ClusterResourcePlacement resources" , h .ClusterName )
6490 return true , nil
6591 }
6692
6793 isDrainSuccessful := true
6894 for crpName := range crpNameMap {
69- evictionName , err := generateDrainEvictionName (crpName , h .clusterName )
95+ evictionName , err := generateDrainEvictionName (crpName , h .ClusterName )
7096 if err != nil {
7197 return false , err
7298 }
@@ -80,54 +106,54 @@ func (h *drainHelper) Drain(ctx context.Context) (bool, error) {
80106 },
81107 Spec : placementv1beta1.PlacementEvictionSpec {
82108 PlacementName : crpName ,
83- ClusterName : h .clusterName ,
109+ ClusterName : h .ClusterName ,
84110 },
85111 }
86- return h .hubClient .Create (ctx , & eviction )
112+ return h .HubClient .Create (ctx , & eviction )
87113 })
88114
89115 if err != nil {
90- return false , fmt .Errorf ("failed to create eviction %s for CRP %s targeting member cluster %s: %w" , evictionName , crpName , h .clusterName , err )
116+ return false , fmt .Errorf ("failed to create eviction %s for CRP %s targeting member cluster %s: %w" , evictionName , crpName , h .ClusterName , err )
91117 }
92118
93- log .Printf ("Created eviction %s for CRP %s targeting member cluster %s" , evictionName , crpName , h .clusterName )
119+ log .Printf ("Created eviction %s for CRP %s targeting member cluster %s" , evictionName , crpName , h .ClusterName )
94120
95121 var eviction placementv1beta1.ClusterResourcePlacementEviction
96122 err = wait .ExponentialBackoffWithContext (ctx , retry .DefaultBackoff , func (ctx context.Context ) (bool , error ) {
97- if err := h .hubClient .Get (ctx , types.NamespacedName {Name : evictionName }, & eviction ); err != nil {
98- return false , fmt .Errorf ("failed to get eviction %s for CRP %s targeting member cluster %s: %w" , evictionName , crpName , h .clusterName , err )
123+ if err := h .HubClient .Get (ctx , types.NamespacedName {Name : evictionName }, & eviction ); err != nil {
124+ return false , fmt .Errorf ("failed to get eviction %s for CRP %s targeting member cluster %s: %w" , evictionName , crpName , h .ClusterName , err )
99125 }
100126 return evictionutils .IsEvictionInTerminalState (& eviction ), nil
101127 })
102128
103129 if err != nil {
104- return false , fmt .Errorf ("failed to wait for eviction %s for CRP %s targeting member cluster %s to reach terminal state: %w" , evictionName , crpName , h .clusterName , err )
130+ return false , fmt .Errorf ("failed to wait for eviction %s for CRP %s targeting member cluster %s to reach terminal state: %w" , evictionName , crpName , h .ClusterName , err )
105131 }
106132
107133 validCondition := eviction .GetCondition (string (placementv1beta1 .PlacementEvictionConditionTypeValid ))
108134 if validCondition != nil && validCondition .Status == metav1 .ConditionFalse {
109135 if validCondition .Reason == condition .EvictionInvalidMissingCRPMessage ||
110136 validCondition .Reason == condition .EvictionInvalidDeletingCRPMessage ||
111137 validCondition .Reason == condition .EvictionInvalidMissingCRBMessage {
112- log .Printf ("eviction %s is invalid with reason %s for CRP %s targeting member cluster %s, but drain will succeed" , evictionName , validCondition .Reason , crpName , h .clusterName )
138+ log .Printf ("eviction %s is invalid with reason %s for CRP %s targeting member cluster %s, but drain will succeed" , evictionName , validCondition .Reason , crpName , h .ClusterName )
113139 continue
114140 }
115141 }
116142 executedCondition := eviction .GetCondition (string (placementv1beta1 .PlacementEvictionConditionTypeExecuted ))
117143 if executedCondition == nil || executedCondition .Status == metav1 .ConditionFalse {
118144 isDrainSuccessful = false
119- log .Printf ("eviction %s was not executed successfully for CRP %s targeting member cluster %s" , evictionName , crpName , h .clusterName )
145+ log .Printf ("eviction %s was not executed successfully for CRP %s targeting member cluster %s" , evictionName , crpName , h .ClusterName )
120146 continue
121147 }
122- log .Printf ("eviction %s was executed successfully for CRP %s targeting member cluster %s" , evictionName , crpName , h .clusterName )
123-
148+ log .Printf ("eviction %s was executed successfully for CRP %s targeting member cluster %s" , evictionName , crpName , h .ClusterName )
149+
124150 clusterScopedResourceIdentifiers , err := h .collectClusterScopedResourcesSelectedByCRP (ctx , crpName )
125151 if err != nil {
126152 log .Printf ("failed to collect cluster scoped resources selected by CRP %s: %v" , crpName , err )
127153 continue
128154 }
129155 for _ , resourceIdentifier := range clusterScopedResourceIdentifiers {
130- log .Printf ("evicted resource %s propagated by CRP %s targeting member cluster %s" , generateResourceIdentifierKey (resourceIdentifier ), crpName , h .clusterName )
156+ log .Printf ("evicted resource %s propagated by CRP %s targeting member cluster %s" , generateResourceIdentifierKey (resourceIdentifier ), crpName , h .ClusterName )
131157 }
132158 }
133159
@@ -137,7 +163,7 @@ func (h *drainHelper) Drain(ctx context.Context) (bool, error) {
137163func (h * drainHelper ) cordon (ctx context.Context ) error {
138164 return retry .RetryOnConflict (retry .DefaultRetry , func () error {
139165 var mc clusterv1beta1.MemberCluster
140- if err := h .hubClient .Get (ctx , types.NamespacedName {Name : h .clusterName }, & mc ); err != nil {
166+ if err := h .HubClient .Get (ctx , types.NamespacedName {Name : h .ClusterName }, & mc ); err != nil {
141167 return err
142168 }
143169
@@ -149,20 +175,20 @@ func (h *drainHelper) cordon(ctx context.Context) error {
149175
150176 mc .Spec .Taints = append (mc .Spec .Taints , toolsutils .CordonTaint )
151177
152- return h .hubClient .Update (ctx , & mc )
178+ return h .HubClient .Update (ctx , & mc )
153179 })
154180}
155181
156182func (h * drainHelper ) fetchClusterResourcePlacementNamesToEvict (ctx context.Context ) (map [string ]bool , error ) {
157183 var crbList placementv1beta1.ClusterResourceBindingList
158- if err := h .hubClient .List (ctx , & crbList ); err != nil {
184+ if err := h .HubClient .List (ctx , & crbList ); err != nil {
159185 return map [string ]bool {}, fmt .Errorf ("failed to list cluster resource bindings: %w" , err )
160186 }
161187
162188 crpNameMap := make (map [string ]bool )
163189 for i := range crbList .Items {
164190 crb := crbList .Items [i ]
165- if crb .Spec .TargetCluster == h .clusterName && crb .DeletionTimestamp == nil {
191+ if crb .Spec .TargetCluster == h .ClusterName && crb .DeletionTimestamp == nil {
166192 crpName , ok := crb .GetLabels ()[placementv1beta1 .PlacementTrackingLabel ]
167193 if ! ok {
168194 return map [string ]bool {}, fmt .Errorf ("failed to get CRP name from binding %s" , crb .Name )
@@ -176,7 +202,7 @@ func (h *drainHelper) fetchClusterResourcePlacementNamesToEvict(ctx context.Cont
176202
177203func (h * drainHelper ) collectClusterScopedResourcesSelectedByCRP (ctx context.Context , crpName string ) ([]placementv1beta1.ResourceIdentifier , error ) {
178204 var crp placementv1beta1.ClusterResourcePlacement
179- if err := h .hubClient .Get (ctx , types.NamespacedName {Name : crpName }, & crp ); err != nil {
205+ if err := h .HubClient .Get (ctx , types.NamespacedName {Name : crpName }, & crp ); err != nil {
180206 return nil , fmt .Errorf ("failed to get ClusterResourcePlacement %s: %w" , crpName , err )
181207 }
182208
@@ -209,4 +235,64 @@ func generateResourceIdentifierKey(r placementv1beta1.ResourceIdentifier) string
209235 return fmt .Sprintf (resourceIdentifierKeyFormat , r .Group , r .Version , r .Kind , "''" , r .Name )
210236 }
211237 return fmt .Sprintf (resourceIdentifierKeyFormat , r .Group , r .Version , r .Kind , r .Namespace , r .Name )
212- }
238+ }
239+
240+ // NewCmdDrain creates a new drain command
241+ func NewCmdDrain () * cobra.Command {
242+ cmd := & cobra.Command {
243+ Use : "drain" ,
244+ Short : "Drain a member cluster" ,
245+ Long : "Drain a member cluster by cordoning it and removing propagated resources" ,
246+ RunE : func (command * cobra.Command , args []string ) error {
247+ return runDrain ()
248+ },
249+ }
250+
251+ // Add flags specific to drain command
252+ cmd .Flags ().StringVar (& hubClusterContext , "hubClusterContext" , "" , "kubectl context for the hub cluster (required)" )
253+ cmd .Flags ().StringVar (& clusterName , "clusterName" , "" , "name of the member cluster (required)" )
254+
255+ // Mark required flags
256+ _ = cmd .MarkFlagRequired ("hubClusterContext" )
257+ _ = cmd .MarkFlagRequired ("clusterName" )
258+
259+ return cmd
260+ }
261+
262+ func runDrain () error {
263+ _ , hubClient , err := setupClient ()
264+ if err != nil {
265+ return err
266+ }
267+
268+ ctx := context .Background ()
269+ drainHelper := & drainHelper {
270+ HubClient : hubClient ,
271+ ClusterName : clusterName ,
272+ }
273+
274+ isDrainSuccessful , err := drainHelper .Drain (ctx )
275+ if err != nil {
276+ log .Fatalf ("failed to drain member cluster %s: %v" , clusterName , err )
277+ }
278+
279+ if isDrainSuccessful {
280+ log .Printf ("drain was successful for cluster %s" , clusterName )
281+ } else {
282+ log .Printf ("drain was not successful for cluster %s" , clusterName )
283+ }
284+
285+ log .Printf ("retrying drain to ensure all resources propagated from hub cluster are evicted" )
286+ isDrainRetrySuccessful , err := drainHelper .Drain (ctx )
287+ if err != nil {
288+ log .Fatalf ("failed to drain cluster on retry %s: %v" , clusterName , err )
289+ }
290+ if isDrainRetrySuccessful {
291+ log .Printf ("drain retry was successful for cluster %s" , clusterName )
292+ } else {
293+ log .Printf ("drain retry was not successful for cluster %s" , clusterName )
294+ }
295+
296+ log .Printf ("reminder: uncordon the cluster %s to remove cordon taint if needed" , clusterName )
297+ return nil
298+ }
0 commit comments