Skip to content

Commit abfa3ef

Browse files
authored
fix: member agent validate opts, fix klog.ErrorS, and kubectl-fleet CLI improvements (#575)
Signed-off-by: Yetkin Timocin <ytimocin@microsoft.com>
1 parent 81ecb5b commit abfa3ef

10 files changed

Lines changed: 102 additions & 63 deletions

File tree

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ generate: $(CONTROLLER_GEN) ## Generate deep copy methods
224224
build: generate fmt vet ## Build agent binaries
225225
go build -o bin/hubagent cmd/hubagent/main.go
226226
go build -o bin/memberagent cmd/memberagent/main.go
227+
go build -o bin/kubectl-fleet ./tools/fleet/
227228

228229
.PHONY: run-hubagent
229230
run-hubagent: manifests generate fmt vet ## Run hub-agent from your host

cmd/memberagent/main.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ func main() {
9191
klog.InfoS("flag:", "name", f.Name, "value", f.Value)
9292
})
9393

94+
if errs := opts.Validate(); len(errs) != 0 {
95+
klog.ErrorS(errs.ToAggregate(), "invalid parameter")
96+
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
97+
}
98+
9499
// Set up controller-runtime logger
95100
ctrl.SetLogger(zap.New(zap.UseDevMode(true)))
96101

@@ -213,13 +218,16 @@ func buildHubConfig(hubURL string, useCertificateAuth bool, tlsClientInsecure bo
213218
return err
214219
})
215220
if err != nil {
216-
klog.ErrorS(err, "Failed to retrieve token file from the path %s", tokenFilePath)
221+
klog.ErrorS(err, "Failed to retrieve token file", "path", tokenFilePath)
217222
return nil, err
218223
}
219224
hubConfig.BearerTokenFile = tokenFilePath
220225
}
221226

222227
hubConfig.TLSClientConfig.Insecure = tlsClientInsecure
228+
if tlsClientInsecure {
229+
klog.Warning("TLS verification is disabled for hub cluster connection. This is insecure and should not be used in production.")
230+
}
223231
if !tlsClientInsecure {
224232
caBundle, ok := os.LookupEnv("CA_BUNDLE")
225233
if ok && caBundle == "" {
@@ -257,7 +265,7 @@ func buildHubConfig(hubURL string, useCertificateAuth bool, tlsClientInsecure bo
257265
r := textproto.NewReader(bufio.NewReader(strings.NewReader(header)))
258266
h, err := r.ReadMIMEHeader()
259267
if err != nil && !errors.Is(err, io.EOF) {
260-
klog.ErrorS(err, "Failed to parse HUB_KUBE_HEADER %q", header)
268+
klog.ErrorS(err, "Failed to parse HUB_KUBE_HEADER", "header", header)
261269
return nil, err
262270
}
263271
hubConfig.WrapTransport = func(rt http.RoundTripper) http.RoundTripper {

test/e2e/cluster_staged_updaterun_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2203,7 +2203,7 @@ func approveClusterApprovalRequest(stageName, updateRunName, stageTask string) {
22032203

22042204
// Use kubectl-fleet approve plugin to approve the request
22052205
cmd := exec.Command(fleetBinaryPath, "approve", "clusterapprovalrequest",
2206-
"--hubClusterContext", "kind-hub",
2206+
"--hub-cluster-context", "kind-hub",
22072207
"--name", approvalRequestName)
22082208
output, err := cmd.CombinedOutput()
22092209
Expect(err).ToNot(HaveOccurred(), "kubectl-fleet approve failed: %s", string(output))

test/e2e/drain_tool_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -360,17 +360,17 @@ var _ = Describe("Drain is allowed on one cluster, blocked on others - ClusterRe
360360
func runDrainClusterBinary(hubClusterName, memberClusterName string) {
361361
By(fmt.Sprintf("draining cluster %s", memberClusterName))
362362
cmd := exec.Command(fleetBinaryPath, "draincluster",
363-
"--hubClusterContext", hubClusterName,
364-
"--clusterName", memberClusterName)
363+
"--hub-cluster-context", hubClusterName,
364+
"--cluster-name", memberClusterName)
365365
_, err := cmd.CombinedOutput()
366366
Expect(err).ToNot(HaveOccurred(), "Drain command failed with error: %v", err)
367367
}
368368

369369
func runUncordonClusterBinary(hubClusterName, memberClusterName string) {
370370
By(fmt.Sprintf("uncordoning cluster %s", memberClusterName))
371371
cmd := exec.Command(fleetBinaryPath, "uncordoncluster",
372-
"--hubClusterContext", hubClusterName,
373-
"--clusterName", memberClusterName)
372+
"--hub-cluster-context", hubClusterName,
373+
"--cluster-name", memberClusterName)
374374
_, err := cmd.CombinedOutput()
375375
Expect(err).ToNot(HaveOccurred(), "Uncordon command failed with error: %v", err)
376376
}

tools/fleet/README.md

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -72,51 +72,51 @@ Use the `approve` subcommand to approve approval request resources for staged up
7272

7373
**Cluster-scoped (ClusterApprovalRequest):**
7474
```bash
75-
kubectl fleet approve clusterapprovalrequest --hubClusterContext <hub-cluster-context> --name <approval-request-name>
75+
kubectl fleet approve clusterapprovalrequest --hub-cluster-context <hub-cluster-context> --name <approval-request-name>
7676
# Or using alias:
77-
kubectl fleet approve careq --hubClusterContext <hub-cluster-context> --name <approval-request-name>
77+
kubectl fleet approve careq --hub-cluster-context <hub-cluster-context> --name <approval-request-name>
7878
```
7979

8080
**Namespace-scoped (ApprovalRequest):**
8181
```bash
82-
kubectl fleet approve approvalrequest --hubClusterContext <hub-cluster-context> --name <approval-request-name> --namespace <namespace>
82+
kubectl fleet approve approvalrequest --hub-cluster-context <hub-cluster-context> --name <approval-request-name> --namespace <namespace>
8383
# Or using alias:
84-
kubectl fleet approve areq --hubClusterContext <hub-cluster-context> --name <approval-request-name> -n <namespace>
84+
kubectl fleet approve areq --hub-cluster-context <hub-cluster-context> --name <approval-request-name> -n <namespace>
8585
```
8686

8787
Example:
8888
```bash
8989
# Approve a ClusterApprovalRequest
90-
kubectl fleet approve clusterapprovalrequest --hubClusterContext hub --name my-approval-request
90+
kubectl fleet approve clusterapprovalrequest --hub-cluster-context hub --name my-approval-request
9191

9292
# Approve an ApprovalRequest in a specific namespace
93-
kubectl fleet approve approvalrequest --hubClusterContext hub --name my-approval-request --namespace my-namespace
93+
kubectl fleet approve approvalrequest --hub-cluster-context hub --name my-approval-request --namespace my-namespace
9494
```
9595

9696
### Drain a Member Cluster
9797

9898
Use the `draincluster` subcommand to remove all resources propagated to a member cluster from the hub cluster by any `Placement` resource. This is useful when you want to temporarily move all workloads off a member cluster in preparation for an event like upgrade or reconfiguration.
9999

100100
```bash
101-
kubectl fleet draincluster --hubClusterContext <hub-cluster-context> --clusterName <memberClusterName>
101+
kubectl fleet draincluster --hub-cluster-context <hub-cluster-context> --cluster-name <memberClusterName>
102102
```
103103

104104
Example:
105105
```bash
106-
kubectl fleet draincluster --hubClusterContext hub --clusterName member-cluster-1
106+
kubectl fleet draincluster --hub-cluster-context hub --cluster-name member-cluster-1
107107
```
108108

109109
### Uncordon a Member Cluster
110110

111111
Use the `uncordoncluster` subcommand to uncordon a member cluster that has been previously drained, allowing resources to be propagated to the cluster again.
112112

113113
```bash
114-
kubectl fleet uncordoncluster --hubClusterContext <hub-cluster-context> --clusterName <memberClusterName>
114+
kubectl fleet uncordoncluster --hub-cluster-context <hub-cluster-context> --cluster-name <memberClusterName>
115115
```
116116

117117
Example:
118118
```bash
119-
kubectl fleet uncordoncluster --hubClusterContext hub --clusterName member-cluster-1
119+
kubectl fleet uncordoncluster --hub-cluster-context hub --cluster-name member-cluster-1
120120
```
121121

122122
## Subcommands
@@ -156,13 +156,13 @@ If the `cordon` taint is not present on the member cluster, the command will hav
156156
## Flags
157157

158158
The `approve` subcommand uses the following flags:
159-
- `--hubClusterContext`: kubectl context for the hub cluster (required)
159+
- `--hub-cluster-context`: kubectl context for the hub cluster (required)
160160
- `--name`: name of the resource to approve (required)
161161
- `--namespace`, `-n`: namespace of the resource to approve (required for `approvalrequest`, not allowed for `clusterapprovalrequest`)
162162

163163
Both `draincluster` and `uncordoncluster` subcommands use the following flags:
164-
- `--hubClusterContext`: kubectl context for the hub cluster (required)
165-
- `--clusterName`: name of the member cluster to operate on (required)
164+
- `--hub-cluster-context`: kubectl context for the hub cluster (required)
165+
- `--cluster-name`: name of the member cluster to operate on (required)
166166

167167
## Examples
168168

@@ -173,31 +173,31 @@ Both `draincluster` and `uncordoncluster` subcommands use the following flags:
173173
kubectl config get-contexts
174174

175175
# 2. Drain a cluster for maintenance
176-
kubectl fleet draincluster --hubClusterContext production-hub --clusterName worker-node-1
176+
kubectl fleet draincluster --hub-cluster-context production-hub --cluster-name worker-node-1
177177

178178
# 3. Perform maintenance on the worker-node-1 cluster
179179
# ... maintenance operations ...
180180

181181
# 4. After maintenance, uncordon the cluster to allow workloads back
182-
kubectl fleet uncordoncluster --hubClusterContext production-hub --clusterName worker-node-1
182+
kubectl fleet uncordoncluster --hub-cluster-context production-hub --cluster-name worker-node-1
183183
```
184184

185185
### Additional Examples
186186

187187
```bash
188188
# Approve a ClusterApprovalRequest for staged updates
189-
kubectl fleet approve clusterapprovalrequest --hubClusterContext hub --name update-approval-stage-1
189+
kubectl fleet approve clusterapprovalrequest --hub-cluster-context hub --name update-approval-stage-1
190190

191191
# Approve a ApprovalRequest for staged updates
192-
kubectl fleet approve approvalrequest --hubClusterContext hub --name update-approval-stage-1 --namespace test-namespace
192+
kubectl fleet approve approvalrequest --hub-cluster-context hub --name update-approval-stage-1 --namespace test-namespace
193193

194194
# Drain multiple clusters (run separately for each cluster)
195-
kubectl fleet draincluster --hubClusterContext hub --clusterName east-cluster
196-
kubectl fleet draincluster --hubClusterContext hub --clusterName west-cluster
195+
kubectl fleet draincluster --hub-cluster-context hub --cluster-name east-cluster
196+
kubectl fleet draincluster --hub-cluster-context hub --cluster-name west-cluster
197197

198198
# Uncordon clusters after maintenance
199-
kubectl fleet uncordoncluster --hubClusterContext hub --clusterName east-cluster
200-
kubectl fleet uncordoncluster --hubClusterContext hub --clusterName west-cluster
199+
kubectl fleet uncordoncluster --hub-cluster-context hub --cluster-name east-cluster
200+
kubectl fleet uncordoncluster --hub-cluster-context hub --cluster-name west-cluster
201201
```
202202

203203
## Troubleshooting

tools/fleet/cmd/approve/approve.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ import (
2020
"context"
2121
"fmt"
2222
"log"
23+
"time"
2324

2425
"github.com/spf13/cobra"
2526
"k8s.io/apimachinery/pkg/api/meta"
2627
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27-
"k8s.io/apimachinery/pkg/runtime"
2828
"k8s.io/apimachinery/pkg/types"
2929
"k8s.io/client-go/util/retry"
3030
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -87,6 +87,7 @@ type approveOptions struct {
8787
hubClusterContext string
8888
name string
8989
namespace string
90+
timeout time.Duration
9091

9192
hubClient client.Client
9293
}
@@ -119,16 +120,19 @@ For namespace-scoped resources (approvalrequest), you must also specify the --na
119120
if err := o.setupClient(); err != nil {
120121
return err
121122
}
122-
return o.run(cmd.Context(), cfg)
123+
ctx, cancel := context.WithTimeout(cmd.Context(), o.timeout)
124+
defer cancel()
125+
return o.run(ctx, cfg)
123126
},
124127
}
125128

126-
cmd.Flags().StringVar(&o.hubClusterContext, "hubClusterContext", "", "The name of the kubeconfig context to use for the hub cluster")
129+
cmd.Flags().StringVar(&o.hubClusterContext, "hub-cluster-context", "", "The name of the kubeconfig context to use for the hub cluster")
127130
cmd.Flags().StringVar(&o.name, "name", "", "The name of the resource to approve")
128131
cmd.Flags().StringVarP(&o.namespace, "namespace", "n", "", "The namespace of the resource to approve (required for namespace-scoped resources)")
132+
cmd.Flags().DurationVar(&o.timeout, "timeout", 5*time.Minute, "Maximum time to wait for the operation to complete")
129133

130134
// Mark required flags.
131-
_ = cmd.MarkFlagRequired("hubClusterContext")
135+
_ = cmd.MarkFlagRequired("hub-cluster-context")
132136
_ = cmd.MarkFlagRequired("name")
133137

134138
return cmd
@@ -208,10 +212,9 @@ func (o *approveOptions) approveApprovalRequest(ctx context.Context) error {
208212

209213
// setupClient creates and configures the Kubernetes client
210214
func (o *approveOptions) setupClient() error {
211-
scheme := runtime.NewScheme()
212-
213-
if err := placementv1beta1.AddToScheme(scheme); err != nil {
214-
return fmt.Errorf("failed to add custom APIs (placement) to the runtime scheme: %w", err)
215+
scheme, err := toolsutils.NewFleetScheme()
216+
if err != nil {
217+
return fmt.Errorf("failed to create runtime scheme: %w", err)
215218
}
216219

217220
hubClient, err := toolsutils.GetClusterClientFromClusterContext(o.hubClusterContext, scheme)

tools/fleet/cmd/draincluster/draincluster.go

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ import (
2020
"context"
2121
"fmt"
2222
"log"
23+
"time"
2324

2425
"github.com/spf13/cobra"
2526
k8errors "k8s.io/apimachinery/pkg/api/errors"
2627
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27-
"k8s.io/apimachinery/pkg/runtime"
2828
"k8s.io/apimachinery/pkg/types"
2929
"k8s.io/apimachinery/pkg/util/uuid"
3030
"k8s.io/apimachinery/pkg/util/validation"
@@ -49,6 +49,7 @@ const (
4949
type drainOptions struct {
5050
hubClusterContext string
5151
clusterName string
52+
timeout time.Duration
5253

5354
hubClient client.Client
5455
}
@@ -70,18 +71,20 @@ func NewCmdDrainCluster() *cobra.Command {
7071
}
7172

7273
// Add flags specific to drain command
73-
cmd.Flags().StringVar(&o.hubClusterContext, "hubClusterContext", "", "kubectl context for the hub cluster (required)")
74-
cmd.Flags().StringVar(&o.clusterName, "clusterName", "", "name of the member cluster (required)")
74+
cmd.Flags().StringVar(&o.hubClusterContext, "hub-cluster-context", "", "kubectl context for the hub cluster (required)")
75+
cmd.Flags().StringVar(&o.clusterName, "cluster-name", "", "name of the member cluster (required)")
76+
cmd.Flags().DurationVar(&o.timeout, "timeout", 5*time.Minute, "Maximum time to wait for the operation to complete")
7577

7678
// Mark required flags
77-
_ = cmd.MarkFlagRequired("hubClusterContext")
78-
_ = cmd.MarkFlagRequired("clusterName")
79+
_ = cmd.MarkFlagRequired("hub-cluster-context")
80+
_ = cmd.MarkFlagRequired("cluster-name")
7981

8082
return cmd
8183
}
8284

8385
func (o *drainOptions) runDrain() error {
84-
ctx := context.Background()
86+
ctx, cancel := context.WithTimeout(context.Background(), o.timeout)
87+
defer cancel()
8588

8689
isDrainSuccessful, err := o.drain(ctx)
8790
if err != nil {
@@ -111,13 +114,9 @@ func (o *drainOptions) runDrain() error {
111114

112115
// setupClient creates and configures the Kubernetes client
113116
func (o *drainOptions) setupClient() error {
114-
scheme := runtime.NewScheme()
115-
116-
if err := clusterv1beta1.AddToScheme(scheme); err != nil {
117-
return fmt.Errorf("failed to add custom APIs (cluster) to the runtime scheme: %w", err)
118-
}
119-
if err := placementv1beta1.AddToScheme(scheme); err != nil {
120-
return fmt.Errorf("failed to add custom APIs (placement) to the runtime scheme: %w", err)
117+
scheme, err := toolsutils.NewFleetScheme()
118+
if err != nil {
119+
return fmt.Errorf("failed to create runtime scheme: %w", err)
121120
}
122121

123122
hubClient, err := toolsutils.GetClusterClientFromClusterContext(o.hubClusterContext, scheme)

tools/fleet/cmd/uncordoncluster/uncordoncluster.go

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,22 @@ import (
2020
"context"
2121
"fmt"
2222
"log"
23+
"time"
2324

2425
"github.com/spf13/cobra"
25-
"k8s.io/apimachinery/pkg/runtime"
2626
"k8s.io/apimachinery/pkg/types"
2727
"k8s.io/client-go/util/retry"
2828
"sigs.k8s.io/controller-runtime/pkg/client"
2929

3030
clusterv1beta1 "github.com/kubefleet-dev/kubefleet/apis/cluster/v1beta1"
31-
placementv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1"
3231
toolsutils "github.com/kubefleet-dev/kubefleet/tools/utils"
3332
)
3433

3534
// uncordonOptions wraps common cluster connection parameters
3635
type uncordonOptions struct {
3736
hubClusterContext string
3837
clusterName string
38+
timeout time.Duration
3939

4040
hubClient client.Client
4141
}
@@ -57,18 +57,21 @@ func NewCmdUncordonCluster() *cobra.Command {
5757
}
5858

5959
// Add flags specific to uncordon command
60-
cmd.Flags().StringVar(&o.hubClusterContext, "hubClusterContext", "", "kubectl context for the hub cluster (required)")
61-
cmd.Flags().StringVar(&o.clusterName, "clusterName", "", "name of the member cluster (required)")
60+
cmd.Flags().StringVar(&o.hubClusterContext, "hub-cluster-context", "", "kubectl context for the hub cluster (required)")
61+
cmd.Flags().StringVar(&o.clusterName, "cluster-name", "", "name of the member cluster (required)")
62+
cmd.Flags().DurationVar(&o.timeout, "timeout", 5*time.Minute, "Maximum time to wait for the operation to complete")
6263

6364
// Mark required flags
64-
_ = cmd.MarkFlagRequired("hubClusterContext")
65-
_ = cmd.MarkFlagRequired("clusterName")
65+
_ = cmd.MarkFlagRequired("hub-cluster-context")
66+
_ = cmd.MarkFlagRequired("cluster-name")
6667

6768
return cmd
6869
}
6970

7071
func (o *uncordonOptions) runUncordon() error {
71-
ctx := context.Background()
72+
ctx, cancel := context.WithTimeout(context.Background(), o.timeout)
73+
defer cancel()
74+
7275
if err := o.uncordon(ctx); err != nil {
7376
return fmt.Errorf("failed to uncordon cluster %s: %w", o.clusterName, err)
7477
}
@@ -79,13 +82,9 @@ func (o *uncordonOptions) runUncordon() error {
7982

8083
// setupClient creates and configures the Kubernetes client
8184
func (o *uncordonOptions) setupClient() error {
82-
scheme := runtime.NewScheme()
83-
84-
if err := clusterv1beta1.AddToScheme(scheme); err != nil {
85-
return fmt.Errorf("failed to add custom APIs (cluster) to the runtime scheme: %w", err)
86-
}
87-
if err := placementv1beta1.AddToScheme(scheme); err != nil {
88-
return fmt.Errorf("failed to add custom APIs (placement) to the runtime scheme: %w", err)
85+
scheme, err := toolsutils.NewFleetScheme()
86+
if err != nil {
87+
return fmt.Errorf("failed to create runtime scheme: %w", err)
8988
}
9089

9190
hubClient, err := toolsutils.GetClusterClientFromClusterContext(o.hubClusterContext, scheme)

0 commit comments

Comments
 (0)