Skip to content

Commit acf021b

Browse files
Support multiline conditions
1 parent 99f611a commit acf021b

File tree

2 files changed

+106
-23
lines changed

2 files changed

+106
-23
lines changed

cmd/clusterctl/cmd/describe_cluster.go

+100-23
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"fmt"
2222
"os"
23+
"regexp"
2324
"sort"
2425
"strings"
2526
"time"
@@ -284,6 +285,11 @@ func addObjectRowV1Beta2(prefix string, tbl *tablewriter.Table, objectTree *tree
284285
// - The object's ready condition + counters
285286
// - The object's up to date condition + counters
286287
// - Condition "REASON", "SINCE", "MESSAGE"
288+
msg := strings.Split(rowDescriptor.message, "\n")
289+
msg0 := ""
290+
if len(msg) > 1 {
291+
msg0 = msg[0]
292+
}
287293
tbl.Append([]string{
288294
fmt.Sprintf("%s%s", gray.Sprint(prefix), name),
289295
rowDescriptor.replicas,
@@ -293,7 +299,20 @@ func addObjectRowV1Beta2(prefix string, tbl *tablewriter.Table, objectTree *tree
293299
rowDescriptor.status,
294300
rowDescriptor.reason,
295301
rowDescriptor.age,
296-
rowDescriptor.message})
302+
msg0})
303+
304+
for _, m := range msg[1:] {
305+
tbl.Append([]string{
306+
fmt.Sprintf("%s", getMultilinePrefix(gray.Sprint(prefix))),
307+
"",
308+
"",
309+
"",
310+
"",
311+
"",
312+
"",
313+
"",
314+
m})
315+
}
297316

298317
// If it is required to show all the conditions for the object, add a row for each object's conditions.
299318
if tree.IsShowConditionsObject(obj) {
@@ -407,17 +426,36 @@ func addOtherConditionsV1Beta2(prefix string, tbl *tablewriter.Table, objectTree
407426
}
408427

409428
childPrefix := getChildPrefix(prefix+childrenPipe+filler, i, len(conditions))
410-
color, status, age, reason, message := v1Beta2ConditionInfo(condition, positivePolarity)
429+
_, status, age, reason, message := v1Beta2ConditionInfo(condition, positivePolarity)
430+
431+
msg := strings.Split(message, "\n")
432+
msg0 := ""
433+
if len(msg) > 1 {
434+
msg0 = msg[0]
435+
}
411436
tbl.Append([]string{
412437
fmt.Sprintf("%s%s", gray.Sprint(childPrefix), cyan.Sprint(condition.Type)),
413438
"",
414439
"",
415440
"",
416441
"",
417-
color.Sprint(status),
418-
color.Sprint(reason),
419-
color.Sprint(age),
420-
color.Sprint(message)})
442+
status,
443+
reason,
444+
age,
445+
msg0})
446+
447+
for _, m := range msg[1:] {
448+
tbl.Append([]string{
449+
fmt.Sprintf("%s", gray.Sprint(getMultilinePrefix(childPrefix))),
450+
"",
451+
"",
452+
"",
453+
"",
454+
"",
455+
"",
456+
"",
457+
m})
458+
}
421459
}
422460
}
423461

@@ -466,6 +504,20 @@ func getChildPrefix(currentPrefix string, childIndex, childCount int) string {
466504
return nextPrefix + lastElemPrefix
467505
}
468506

507+
// getMultilinePrefix return the tree view prefix for a multiline condition.
508+
func getMultilinePrefix(currentPrefix string) string {
509+
// All ├─ should be replaced by |, so all the existing hierarchic dependencies are carried on
510+
if strings.HasSuffix(currentPrefix, firstElemPrefix) {
511+
return strings.TrimSuffix(currentPrefix, firstElemPrefix) + pipe
512+
}
513+
// All └─ should be replaced by " " because we are under the last element of the tree (nothing to carry on)
514+
if strings.HasSuffix(currentPrefix, lastElemPrefix) {
515+
return strings.TrimSuffix(currentPrefix, lastElemPrefix)
516+
}
517+
518+
return "?"
519+
}
520+
469521
// getRowName returns the object name in the tree view according to following rules:
470522
// - group objects are represented as #of objects kind, e.g. 3 Machines...
471523
// - other virtual objects are represented using the object name, e.g. Workers, or meta name if provided.
@@ -544,11 +596,11 @@ func newV1beta2RowDescriptor(obj ctrlclient.Object) v1beta2RowDescriptor {
544596
}
545597

546598
if available := tree.GetAvailableV1Beta2Condition(obj); available != nil {
547-
availableColor, availableStatus, availableAge, availableReason, availableMessage := v1Beta2ConditionInfo(*available, true)
548-
v.status = availableColor.Sprintf("Available: %s", availableStatus)
549-
v.reason = availableColor.Sprint(availableReason)
550-
v.age = availableColor.Sprint(availableAge)
551-
v.message = availableColor.Sprint(availableMessage)
599+
_, availableStatus, availableAge, availableReason, availableMessage := v1Beta2ConditionInfo(*available, true)
600+
v.status = fmt.Sprintf("Available: %s", availableStatus)
601+
v.reason = availableReason
602+
v.age = availableAge
603+
v.message = availableMessage
552604
}
553605
case *clusterv1.MachineDeployment:
554606
md := obj.(*clusterv1.MachineDeployment)
@@ -604,11 +656,11 @@ func newV1beta2RowDescriptor(obj ctrlclient.Object) v1beta2RowDescriptor {
604656

605657
v.readyCounters = "0"
606658
if ready := tree.GetReadyV1Beta2Condition(obj); ready != nil {
607-
readyColor, readyStatus, readyAge, readyReason, readyMessage := v1Beta2ConditionInfo(*ready, true)
608-
v.status = readyColor.Sprintf("Ready: %s", readyStatus)
609-
v.reason = readyColor.Sprint(readyReason)
610-
v.age = readyColor.Sprint(readyAge)
611-
v.message = readyColor.Sprint(readyMessage)
659+
_, readyStatus, readyAge, readyReason, readyMessage := v1Beta2ConditionInfo(*ready, true)
660+
v.status = fmt.Sprintf("Ready: %s", readyStatus)
661+
v.reason = readyReason
662+
v.age = readyAge
663+
v.message = readyMessage
612664
if ready.Status == metav1.ConditionTrue {
613665
v.readyCounters = "1"
614666
}
@@ -623,8 +675,8 @@ func newV1beta2RowDescriptor(obj ctrlclient.Object) v1beta2RowDescriptor {
623675

624676
case *unstructured.Unstructured:
625677
if ready := tree.GetReadyV1Beta2Condition(obj); ready != nil {
626-
readyColor, readyStatus, readyAge, readyReason, readyMessage := v1Beta2ConditionInfo(*ready, true)
627-
v.status = readyColor.Sprintf("Ready: %s", readyStatus)
678+
_, readyStatus, readyAge, readyReason, readyMessage := v1Beta2ConditionInfo(*ready, true)
679+
v.status = fmt.Sprintf("Ready: %s", readyStatus)
628680
v.reason = readyReason
629681
v.age = readyAge
630682
v.message = readyMessage
@@ -686,15 +738,40 @@ func v1Beta2ConditionInfo(c metav1.Condition, positivePolarity bool) (color *col
686738
status = string(c.Status)
687739
reason = c.Reason
688740
age = duration.HumanDuration(time.Since(c.LastTransitionTime.Time))
689-
message = c.Message
741+
message = formatParagraph(c.Message, 100)
690742

691-
// Eventually cut the message to keep the table dimension under control.
692-
if len(message) > 100 {
693-
message = fmt.Sprintf("%s ...", message[:100])
694-
}
695743
return
696744
}
697745

746+
var re = regexp.MustCompile("[\\s]+")
747+
748+
func formatParagraph(text string, maxWidth int) string {
749+
lines := []string{}
750+
for _, l := range strings.Split(text, "\n") {
751+
tmp := ""
752+
for _, c := range []rune(l) {
753+
if c == ' ' {
754+
tmp += " "
755+
continue
756+
}
757+
break
758+
}
759+
for _, w := range re.Split(l, -1) {
760+
if len(tmp)+len(w) < maxWidth {
761+
if strings.TrimSpace(tmp) != "" {
762+
tmp += " "
763+
}
764+
tmp += w
765+
continue
766+
}
767+
lines = append(lines, tmp)
768+
tmp = w
769+
}
770+
lines = append(lines, tmp)
771+
}
772+
return strings.Join(lines, "\n")
773+
}
774+
698775
// conditionDescriptor contains all the info for representing a condition.
699776
type conditionDescriptor struct {
700777
readyColor *color.Color

cmd/clusterctl/cmd/describe_cluster_test.go

+6
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ import (
3535
"sigs.k8s.io/cluster-api/util/conditions"
3636
)
3737

38+
func Test_foo(t *testing.T) {
39+
txt := "Hello, world!\n * This is another line with a long text that I would like to get splitted into two lines."
40+
p := formatParagraph(txt, 50)
41+
fmt.Println(p)
42+
}
43+
3844
func Test_getRowName(t *testing.T) {
3945
tests := []struct {
4046
name string

0 commit comments

Comments
 (0)