Skip to content

Commit 6be9dba

Browse files
Add clusterctl describe command
1 parent 7d01abe commit 6be9dba

17 files changed

+2712
-0
lines changed

cmd/clusterctl/client/client.go

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster"
2323
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
2424
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository"
25+
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/tree"
2526
)
2627

2728
// Client is exposes the clusterctl high-level client library.
@@ -67,6 +68,9 @@ type Client interface {
6768
// variables.
6869
ProcessYAML(options ProcessYAMLOptions) (YamlPrinter, error)
6970

71+
// DescribeCluster returns the object tree representing the status of a Cluster API cluster.
72+
DescribeCluster(options DescribeClusterOptions) (*tree.ObjectTree, error)
73+
7074
// Interface for alpha features in clusterctl
7175
AlphaClient
7276
}

cmd/clusterctl/client/client_test.go

+5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster"
3030
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
3131
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository"
32+
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/tree"
3233
yaml "sigs.k8s.io/cluster-api/cmd/clusterctl/client/yamlprocessor"
3334
"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/scheme"
3435
"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test"
@@ -122,6 +123,10 @@ func (f fakeClient) RolloutRestart(options RolloutRestartOptions) error {
122123
return f.internalClient.RolloutRestart(options)
123124
}
124125

126+
func (f fakeClient) DescribeCluster(options DescribeClusterOptions) (*tree.ObjectTree, error) {
127+
return f.internalClient.DescribeCluster(options)
128+
}
129+
125130
// newFakeClient returns a clusterctl client that allows to execute tests on a set of fake config, fake repositories and fake clusters.
126131
// you can use WithCluster and WithRepository to prepare for the test case.
127132
func newFakeClient(configClient config.Client) *fakeClient {

cmd/clusterctl/client/describe.go

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
Copyright 2020 The Kubernetes 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 client
18+
19+
import (
20+
"context"
21+
22+
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/tree"
23+
)
24+
25+
// DescribeClusterOptions carries the options supported by DescribeCluster.
26+
type DescribeClusterOptions struct {
27+
// Kubeconfig defines the kubeconfig to use for accessing the management cluster. If empty,
28+
// default rules for kubeconfig discovery will be used.
29+
Kubeconfig Kubeconfig
30+
31+
// Namespace where the workload cluster is located. If unspecified, the current namespace will be used.
32+
Namespace string
33+
34+
// ClusterName to be used for the workload cluster.
35+
ClusterName string
36+
37+
// ShowOtherConditions is a list of comma separated kind or kind/name for which we should add the ShowObjectConditionsAnnotation
38+
// to signal to the presentation layer to show all the conditions for the objects.
39+
ShowOtherConditions string
40+
41+
// DisableNoEcho disable hiding MachineInfrastructure or BootstrapConfig objects if the object's ready condition is true
42+
// or it has the same Status, Severity and Reason of the parent's object ready condition (it is an echo)
43+
DisableNoEcho bool
44+
45+
// DisableGrouping disable grouping machines objects in case the ready condition
46+
// has the same Status, Severity and Reason
47+
DisableGrouping bool
48+
}
49+
50+
// DescribeCluster returns the object tree representing the status of a Cluster API cluster.
51+
func (c *clusterctlClient) DescribeCluster(options DescribeClusterOptions) (*tree.ObjectTree, error) {
52+
// gets access to the management cluster
53+
cluster, err := c.clusterClientFactory(ClusterClientFactoryInput{Kubeconfig: options.Kubeconfig})
54+
if err != nil {
55+
return nil, err
56+
}
57+
58+
// If the option specifying the Namespace is empty, try to detect it.
59+
if options.Namespace == "" {
60+
currentNamespace, err := cluster.Proxy().CurrentNamespace()
61+
if err != nil {
62+
return nil, err
63+
}
64+
options.Namespace = currentNamespace
65+
}
66+
67+
// Fetch the Cluster client.
68+
client, err := cluster.Proxy().NewClient()
69+
if err != nil {
70+
return nil, err
71+
}
72+
73+
// Gets the object tree representing the status of a Cluster API cluster.
74+
return tree.Discovery(context.TODO(), client, options.Namespace, options.ClusterName, tree.DiscoverOptions{
75+
ShowOtherConditions: options.ShowOtherConditions,
76+
DisableNoEcho: options.DisableNoEcho,
77+
DisableGrouping: options.DisableGrouping,
78+
})
79+
}
+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
Copyright 2020 The Kubernetes 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 tree
18+
19+
import (
20+
"strconv"
21+
22+
"sigs.k8s.io/controller-runtime/pkg/client"
23+
)
24+
25+
const (
26+
// ShowObjectConditionsAnnotation documents that the presentation layer should show all the conditions for the object.
27+
ShowObjectConditionsAnnotation = "tree.cluster.x-k8s.io.io/show-conditions"
28+
29+
// ObjectMetaNameAnnotation contains the meta name that should be used for the object in the presentation layer,
30+
// e.g. control plane for KCP.
31+
ObjectMetaNameAnnotation = "tree.cluster.x-k8s.io.io/meta-name"
32+
33+
// VirtualObjectAnnotation documents that the object does not correspond to any real object, but instead is
34+
// a virtual object introduced to provide a better representation of the cluster status, e.g. workers.
35+
VirtualObjectAnnotation = "tree.cluster.x-k8s.io.io/virtual-object"
36+
37+
// GroupingObjectAnnotation is an annotation that should be applied to a node in order to trigger the grouping action
38+
// when adding the node's children. e.g. if you have a control-plane node, and you apply this annotation, then
39+
// the control-plane machines added as a children of this node will be grouped in case the ready condition
40+
// has the same Status, Severity and Reason.
41+
GroupingObjectAnnotation = "tree.cluster.x-k8s.io.io/grouping-object"
42+
43+
// GroupObjectAnnotation is an annotation that documents that a node is the result of a grouping operation, and
44+
// thus the node is representing group of sibling nodes, e.g. a group of machines.
45+
GroupObjectAnnotation = "tree.cluster.x-k8s.io.io/group-object"
46+
47+
// GroupItemsAnnotation contains the list of names for the objects included in a group object.
48+
GroupItemsAnnotation = "tree.cluster.x-k8s.io.io/group-items"
49+
50+
// GroupItemsSeparator is the separator used in the GroupItemsAnnotation
51+
GroupItemsSeparator = ", "
52+
)
53+
54+
// GetMetaName returns the object meta name that should be used for the object in the presentation layer, if defined.
55+
func GetMetaName(obj client.Object) string {
56+
if val, ok := getAnnotation(obj, ObjectMetaNameAnnotation); ok {
57+
return val
58+
}
59+
return ""
60+
}
61+
62+
// IsGroupingObject returns true in case the object is responsible to trigger the grouping action
63+
// when adding the object's children. e.g. A control-plane object, could be responsible of grouping
64+
// the control-plane machines while added as a children objects.
65+
func IsGroupingObject(obj client.Object) bool {
66+
if val, ok := getBoolAnnotation(obj, GroupingObjectAnnotation); ok {
67+
return val
68+
}
69+
return false
70+
}
71+
72+
// IsGroupObject return true if the object is the result of a grouping operation, and
73+
// thus the object is representing group of sibling object, e.g. a group of machines.
74+
func IsGroupObject(obj client.Object) bool {
75+
if val, ok := getBoolAnnotation(obj, GroupObjectAnnotation); ok {
76+
return val
77+
}
78+
return false
79+
}
80+
81+
// GetGroupItems return the list of names for the objects included in a group object.
82+
func GetGroupItems(obj client.Object) string {
83+
if val, ok := getAnnotation(obj, GroupItemsAnnotation); ok {
84+
return val
85+
}
86+
return ""
87+
}
88+
89+
// IsVirtualObject return true if the object does not correspond to any real object, but instead it is
90+
// a virtual object introduced to provide a better representation of the cluster status.
91+
func IsVirtualObject(obj client.Object) bool {
92+
if val, ok := getBoolAnnotation(obj, VirtualObjectAnnotation); ok {
93+
return val
94+
}
95+
return false
96+
}
97+
98+
// IsShowConditionsObject returns true if the presentation layer should show all the conditions for the object.
99+
func IsShowConditionsObject(obj client.Object) bool {
100+
if val, ok := getBoolAnnotation(obj, ShowObjectConditionsAnnotation); ok {
101+
return val
102+
}
103+
return false
104+
}
105+
106+
func getAnnotation(obj client.Object, annotation string) (string, bool) {
107+
if obj == nil {
108+
return "", false
109+
}
110+
val, ok := obj.GetAnnotations()[annotation]
111+
return val, ok
112+
}
113+
114+
func getBoolAnnotation(obj client.Object, annotation string) (bool, bool) {
115+
val, ok := getAnnotation(obj, annotation)
116+
if ok {
117+
if boolVal, err := strconv.ParseBool(val); err == nil {
118+
return boolVal, true
119+
}
120+
}
121+
return false, false
122+
}
123+
124+
func addAnnotation(obj client.Object, annotation, value string) {
125+
annotations := obj.GetAnnotations()
126+
if annotations == nil {
127+
annotations = map[string]string{}
128+
}
129+
annotations[annotation] = value
130+
obj.SetAnnotations(annotations)
131+
}

0 commit comments

Comments
 (0)