Skip to content

Commit d9c2199

Browse files
authored
Merge pull request #11781 from tobiasgiese/fix-multiline-parent-conditions-tests
🐛 Fix multiline Ready condition in clusterctl describe for v1beta2
2 parents e70f946 + 4470114 commit d9c2199

File tree

3 files changed

+389
-16
lines changed

3 files changed

+389
-16
lines changed

cmd/clusterctl/client/tree/tree.go

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,11 @@ type ObjectTreeOptions struct {
6363

6464
// ObjectTree defines an object tree representing the status of a Cluster API cluster.
6565
type ObjectTree struct {
66-
root client.Object
67-
options ObjectTreeOptions
68-
items map[types.UID]client.Object
69-
ownership map[types.UID]map[types.UID]bool
66+
root client.Object
67+
options ObjectTreeOptions
68+
items map[types.UID]client.Object
69+
ownership map[types.UID]map[types.UID]bool
70+
parentship map[types.UID]types.UID
7071
}
7172

7273
// NewObjectTree creates a new object tree with the given root and options.
@@ -78,10 +79,11 @@ func NewObjectTree(root client.Object, options ObjectTreeOptions) *ObjectTree {
7879
}
7980

8081
return &ObjectTree{
81-
root: root,
82-
options: options,
83-
items: make(map[types.UID]client.Object),
84-
ownership: make(map[types.UID]map[types.UID]bool),
82+
root: root,
83+
options: options,
84+
items: make(map[types.UID]client.Object),
85+
ownership: make(map[types.UID]map[types.UID]bool),
86+
parentship: make(map[types.UID]types.UID),
8587
}
8688
}
8789

@@ -234,6 +236,7 @@ func (od ObjectTree) remove(parent client.Object, s client.Object) {
234236
}
235237
delete(od.items, s.GetUID())
236238
delete(od.ownership[parent.GetUID()], s.GetUID())
239+
delete(od.parentship, s.GetUID())
237240
}
238241

239242
func (od ObjectTree) addInner(parent client.Object, obj client.Object) {
@@ -242,6 +245,19 @@ func (od ObjectTree) addInner(parent client.Object, obj client.Object) {
242245
od.ownership[parent.GetUID()] = make(map[types.UID]bool)
243246
}
244247
od.ownership[parent.GetUID()][obj.GetUID()] = true
248+
od.parentship[obj.GetUID()] = parent.GetUID()
249+
}
250+
251+
// GetParent returns parent of an object.
252+
func (od ObjectTree) GetParent(id types.UID) client.Object {
253+
parentID, ok := od.parentship[id]
254+
if !ok {
255+
return nil
256+
}
257+
if parentID == od.root.GetUID() {
258+
return od.root
259+
}
260+
return od.items[parentID]
245261
}
246262

247263
// GetRoot returns the root of the tree.

cmd/clusterctl/cmd/describe_cluster.go

Lines changed: 92 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ const (
5050
lastElemPrefix = `└─`
5151
indent = " "
5252
pipe = `│ `
53+
54+
// lastObjectAnnotation defines the last object in the ObjectTree.
55+
// This is necessary to built the prefix for multiline condition messages.
56+
lastObjectAnnotation = "tree.cluster.x-k8s.io.io/last-object"
5357
)
5458

5559
var (
@@ -304,9 +308,10 @@ func addObjectRowV1Beta2(prefix string, tbl *tablewriter.Table, objectTree *tree
304308
rowDescriptor.age,
305309
msg0})
306310

311+
multilinePrefix := getRootMultiLineObjectPrefix(obj, objectTree)
307312
for _, m := range msg[1:] {
308313
tbl.Append([]string{
309-
getMultilinePrefix(gray.Sprint(prefix)),
314+
gray.Sprint(multilinePrefix),
310315
"",
311316
"",
312317
"",
@@ -324,7 +329,14 @@ func addObjectRowV1Beta2(prefix string, tbl *tablewriter.Table, objectTree *tree
324329

325330
// Add a row for each object's children, taking care of updating the tree view prefix.
326331
childrenObj := objectTree.GetObjectsByParent(obj.GetUID())
332+
childrenObj = orderChildrenObjects(childrenObj)
327333

334+
for i, child := range childrenObj {
335+
addObjectRowV1Beta2(getChildPrefix(prefix, i, len(childrenObj)), tbl, objectTree, child)
336+
}
337+
}
338+
339+
func orderChildrenObjects(childrenObj []ctrlclient.Object) []ctrlclient.Object {
328340
// printBefore returns true if children[i] should be printed before children[j]. Objects are sorted by z-order and
329341
// row name such that objects with higher z-order are printed first, and objects with the same z-order are
330342
// printed in alphabetical order.
@@ -336,10 +348,7 @@ func addObjectRowV1Beta2(prefix string, tbl *tablewriter.Table, objectTree *tree
336348
return tree.GetZOrder(childrenObj[i]) > tree.GetZOrder(childrenObj[j])
337349
}
338350
sort.Slice(childrenObj, printBefore)
339-
340-
for i, child := range childrenObj {
341-
addObjectRowV1Beta2(getChildPrefix(prefix, i, len(childrenObj)), tbl, objectTree, child)
342-
}
351+
return childrenObj
343352
}
344353

345354
// addObjectRow add a row for a given object, and recursively for all the object's children.
@@ -452,7 +461,7 @@ func addOtherConditionsV1Beta2(prefix string, tbl *tablewriter.Table, objectTree
452461

453462
for _, m := range msg[1:] {
454463
tbl.Append([]string{
455-
gray.Sprint(getMultilinePrefix(childPrefix)),
464+
gray.Sprint(getMultilineConditionPrefix(childPrefix)),
456465
"",
457466
"",
458467
"",
@@ -510,8 +519,8 @@ func getChildPrefix(currentPrefix string, childIndex, childCount int) string {
510519
return nextPrefix + lastElemPrefix
511520
}
512521

513-
// getMultilinePrefix return the tree view prefix for a multiline condition.
514-
func getMultilinePrefix(currentPrefix string) string {
522+
// getMultilineConditionPrefix return the tree view prefix for a multiline condition.
523+
func getMultilineConditionPrefix(currentPrefix string) string {
515524
// All ├─ should be replaced by |, so all the existing hierarchic dependencies are carried on
516525
if strings.HasSuffix(currentPrefix, firstElemPrefix) {
517526
return strings.TrimSuffix(currentPrefix, firstElemPrefix) + pipe
@@ -524,6 +533,81 @@ func getMultilinePrefix(currentPrefix string) string {
524533
return "?"
525534
}
526535

536+
// getRootMultiLineObjectPrefix generates the multiline prefix for an object.
537+
func getRootMultiLineObjectPrefix(obj ctrlclient.Object, objectTree *tree.ObjectTree) string {
538+
// If the object is the last one in the tree, no prefix is needed.
539+
if ensureLastObjectInTree(objectTree) == string(obj.GetUID()) {
540+
return ""
541+
}
542+
543+
// Determine the prefix for the current object.
544+
// If it is a leaf we don't have to add any prefix.
545+
var prefix string
546+
if len(objectTree.GetObjectsByParent(obj.GetUID())) > 0 {
547+
prefix = pipe
548+
}
549+
550+
// Traverse upward through the tree, calculating each parent's prefix.
551+
// The parent of the root object is nil, so we stop when we reach that point.
552+
previousUID := obj.GetUID()
553+
parent := objectTree.GetParent(obj.GetUID())
554+
for parent != nil {
555+
// We have to break the loop if the previous ID is the same as the current ID.
556+
// This should never happen as the root object doesn't have set the parentship.
557+
if previousUID == parent.GetUID() {
558+
break
559+
}
560+
561+
// Use pipe if the parent has children and the current node is not the last child.
562+
parentChildren := orderChildrenObjects(objectTree.GetObjectsByParent(parent.GetUID()))
563+
isLastChild := len(parentChildren) > 0 && parentChildren[len(parentChildren)-1].GetUID() == previousUID
564+
if objectTree.IsObjectWithChild(parent.GetUID()) && !isLastChild {
565+
prefix = pipe + prefix
566+
} else {
567+
prefix = indent + prefix
568+
}
569+
570+
// Set prefix and move up the tree.
571+
previousUID = parent.GetUID()
572+
parent = objectTree.GetParent(parent.GetUID())
573+
}
574+
return prefix
575+
}
576+
577+
func ensureLastObjectInTree(objectTree *tree.ObjectTree) string {
578+
// Compute last object in the tree and set it in the annotations.
579+
annotations := objectTree.GetRoot().GetAnnotations()
580+
if annotations == nil {
581+
annotations = map[string]string{}
582+
}
583+
584+
// Return if last object is already set.
585+
if val, ok := annotations[lastObjectAnnotation]; ok {
586+
return val
587+
}
588+
589+
lastObjectInTree := string(getLastObjectInTree(objectTree).GetUID())
590+
annotations[lastObjectAnnotation] = lastObjectInTree
591+
objectTree.GetRoot().SetAnnotations(annotations)
592+
return lastObjectInTree
593+
}
594+
595+
func getLastObjectInTree(objectTree *tree.ObjectTree) ctrlclient.Object {
596+
var objs []ctrlclient.Object
597+
598+
var traverse func(obj ctrlclient.Object)
599+
traverse = func(obj ctrlclient.Object) {
600+
objs = append(objs, obj)
601+
children := orderChildrenObjects(objectTree.GetObjectsByParent(obj.GetUID()))
602+
for _, child := range children {
603+
traverse(child)
604+
}
605+
}
606+
607+
traverse(objectTree.GetRoot())
608+
return objs[len(objs)-1]
609+
}
610+
527611
// getRowName returns the object name in the tree view according to following rules:
528612
// - group objects are represented as #of objects kind, e.g. 3 Machines...
529613
// - other virtual objects are represented using the object name, e.g. Workers, or meta name if provided.

0 commit comments

Comments
 (0)