Skip to content

Commit f8da588

Browse files
committed
add approval plugin
Signed-off-by: Wantong Jiang <[email protected]>
1 parent baaeed5 commit f8da588

File tree

6 files changed

+611
-38
lines changed

6 files changed

+611
-38
lines changed

tools/fleet/README.md

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,3 @@ After running the uncordon command:
165165

166166
1. Check that the cordon taint has been removed from the MemberCluster resource
167167
2. Monitor that new workloads can be scheduled to the cluster according to placement policies
168-
169-
## Migration from Separate Tools
170-
171-
If you were previously using the separate `kubectl-draincluster` and `kubectl-uncordoncluster` plugins, you can replace them with this unified `kubectl-fleet` plugin:
172-
173-
- `kubectl draincluster``kubectl fleet drain`
174-
- `kubectl uncordoncluster``kubectl fleet uncordon`
175-
176-
The command-line arguments remain the same, only the command structure has changed to use subcommands.

tools/fleet/cmd/approve/approve.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
Copyright 2025 The KubeFleet Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package approve
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"log"
23+
24+
"github.com/spf13/cobra"
25+
"k8s.io/apimachinery/pkg/api/meta"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/apimachinery/pkg/runtime"
28+
"k8s.io/apimachinery/pkg/types"
29+
"k8s.io/client-go/util/retry"
30+
"sigs.k8s.io/controller-runtime/pkg/client"
31+
32+
clusterv1beta1 "github.com/kubefleet-dev/kubefleet/apis/cluster/v1beta1"
33+
placementv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1"
34+
toolsutils "github.com/kubefleet-dev/kubefleet/tools/utils"
35+
)
36+
37+
type approveOptions struct {
38+
hubClusterContext string
39+
kind string
40+
name string
41+
}
42+
43+
func NewCmdApprove() *cobra.Command {
44+
o := &approveOptions{}
45+
46+
cmd := &cobra.Command{
47+
Use: "approve <kind>",
48+
Short: "Approve a resource",
49+
Long: `Approve a resource by patching an "Approved" condition to its status.
50+
51+
This command patches the resource with an "Approved" condition,
52+
allowing staged update runs to proceed to the next stage.
53+
54+
Currently supported kinds:
55+
clusterapprovalrequest - Approve a ClusterApprovalRequest`,
56+
Args: cobra.ExactArgs(1),
57+
RunE: func(cmd *cobra.Command, args []string) error {
58+
o.kind = args[0]
59+
return o.run(cmd.Context())
60+
},
61+
}
62+
63+
cmd.Flags().StringVar(&o.hubClusterContext, "hubClusterContext", "", "The name of the kubeconfig context to use for the hub cluster")
64+
cmd.Flags().StringVar(&o.name, "name", "", "The name of the resource to approve")
65+
66+
// Mark required flags
67+
_ = cmd.MarkFlagRequired("hubClusterContext")
68+
_ = cmd.MarkFlagRequired("name")
69+
70+
return cmd
71+
}
72+
73+
func (o *approveOptions) run(ctx context.Context) error {
74+
if o.kind == "" {
75+
return fmt.Errorf("resource kind is required")
76+
}
77+
if o.name == "" {
78+
return fmt.Errorf("resource name is required")
79+
}
80+
81+
// Validate that we only support clusterapprovalrequest for now
82+
if o.kind != "clusterapprovalrequest" {
83+
return fmt.Errorf("unsupported resource kind %q, only 'clusterapprovalrequest' is supported", o.kind)
84+
}
85+
86+
_, hubClient, err := o.setupClient()
87+
if err != nil {
88+
return err
89+
}
90+
91+
// Patch the ClusterApprovalRequest status with approved condition
92+
err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
93+
var car placementv1beta1.ClusterApprovalRequest
94+
if err := hubClient.Get(ctx, types.NamespacedName{Name: o.name}, &car); err != nil {
95+
return err
96+
}
97+
98+
// Add the Approved condition
99+
approvedCondition := metav1.Condition{
100+
Type: string(placementv1beta1.ApprovalRequestConditionApproved),
101+
Status: metav1.ConditionTrue,
102+
Reason: "ClusterApprovalRequestApproved",
103+
Message: "ClusterApprovalRequest has been approved",
104+
ObservedGeneration: car.Generation,
105+
}
106+
107+
// Update or add the condition
108+
meta.SetStatusCondition(&car.Status.Conditions, approvedCondition)
109+
110+
return hubClient.Status().Update(ctx, &car)
111+
})
112+
113+
if err != nil {
114+
return fmt.Errorf("failed to approve ClusterApprovalRequest %q: %w", o.name, err)
115+
}
116+
117+
fmt.Printf("ClusterApprovalRequest %q approved successfully\n", o.name)
118+
return nil
119+
}
120+
121+
// setupClient creates and configures the Kubernetes client
122+
func (o *approveOptions) setupClient() (*runtime.Scheme, client.Client, error) {
123+
scheme := runtime.NewScheme()
124+
125+
if err := clusterv1beta1.AddToScheme(scheme); err != nil {
126+
log.Fatalf("failed to add custom APIs (cluster) to the runtime scheme: %v", err)
127+
}
128+
if err := placementv1beta1.AddToScheme(scheme); err != nil {
129+
log.Fatalf("failed to add custom APIs (placement) to the runtime scheme: %v", err)
130+
}
131+
132+
hubClient, err := toolsutils.GetClusterClientFromClusterContext(o.hubClusterContext, scheme)
133+
if err != nil {
134+
log.Fatalf("failed to create hub cluster client: %v", err)
135+
}
136+
137+
return scheme, hubClient, nil
138+
}

0 commit comments

Comments
 (0)