sigs.k8s.io/cluster-api@v1.7.1/cmd/clusterctl/client/tree/tree_test.go (about) 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 "strings" 21 "testing" 22 "time" 23 24 . "github.com/onsi/gomega" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 27 "k8s.io/apimachinery/pkg/types" 28 "sigs.k8s.io/controller-runtime/pkg/client" 29 30 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 31 "sigs.k8s.io/cluster-api/util/conditions" 32 ) 33 34 func Test_hasSameReadyStatusSeverityAndReason(t *testing.T) { 35 readyTrue := conditions.TrueCondition(clusterv1.ReadyCondition) 36 readyFalseReasonInfo := conditions.FalseCondition(clusterv1.ReadyCondition, "Reason", clusterv1.ConditionSeverityInfo, "message falseInfo1") 37 readyFalseAnotherReasonInfo := conditions.FalseCondition(clusterv1.ReadyCondition, "AnotherReason", clusterv1.ConditionSeverityInfo, "message falseInfo1") 38 readyFalseReasonWarning := conditions.FalseCondition(clusterv1.ReadyCondition, "Reason", clusterv1.ConditionSeverityWarning, "message falseInfo1") 39 40 type args struct { 41 a *clusterv1.Condition 42 b *clusterv1.Condition 43 } 44 tests := []struct { 45 name string 46 args args 47 want bool 48 }{ 49 { 50 name: "Objects without conditions are the same", 51 args: args{ 52 a: nil, 53 b: nil, 54 }, 55 want: true, 56 }, 57 { 58 name: "Objects with same Ready condition are the same", 59 args: args{ 60 a: readyTrue, 61 b: readyTrue, 62 }, 63 want: true, 64 }, 65 { 66 name: "Objects with different Ready.Status are not the same", 67 args: args{ 68 a: readyTrue, 69 b: readyFalseReasonInfo, 70 }, 71 want: false, 72 }, 73 { 74 name: "Objects with different Ready.Reason are not the same", 75 args: args{ 76 a: readyFalseReasonInfo, 77 b: readyFalseAnotherReasonInfo, 78 }, 79 want: false, 80 }, 81 { 82 name: "Objects with different Ready.Severity are not the same", 83 args: args{ 84 a: readyFalseReasonInfo, 85 b: readyFalseReasonWarning, 86 }, 87 want: false, 88 }, 89 } 90 for _, tt := range tests { 91 t.Run(tt.name, func(t *testing.T) { 92 g := NewWithT(t) 93 94 got := hasSameReadyStatusSeverityAndReason(tt.args.a, tt.args.b) 95 g.Expect(got).To(Equal(tt.want)) 96 }) 97 } 98 } 99 100 func Test_minLastTransitionTime(t *testing.T) { 101 now := &clusterv1.Condition{Type: "now", LastTransitionTime: metav1.Now()} 102 beforeNow := &clusterv1.Condition{Type: "beforeNow", LastTransitionTime: metav1.Time{Time: now.LastTransitionTime.Time.Add(-1 * time.Hour)}} 103 type args struct { 104 a *clusterv1.Condition 105 b *clusterv1.Condition 106 } 107 tests := []struct { 108 name string 109 args args 110 want metav1.Time 111 }{ 112 { 113 name: "nil, nil should return empty time", 114 args: args{ 115 a: nil, 116 b: nil, 117 }, 118 want: metav1.Time{}, 119 }, 120 { 121 name: "nil, now should return now", 122 args: args{ 123 a: nil, 124 b: now, 125 }, 126 want: now.LastTransitionTime, 127 }, 128 { 129 name: "now, nil should return now", 130 args: args{ 131 a: now, 132 b: nil, 133 }, 134 want: now.LastTransitionTime, 135 }, 136 { 137 name: "now, beforeNow should return beforeNow", 138 args: args{ 139 a: now, 140 b: beforeNow, 141 }, 142 want: beforeNow.LastTransitionTime, 143 }, 144 { 145 name: "beforeNow, now should return beforeNow", 146 args: args{ 147 a: now, 148 b: beforeNow, 149 }, 150 want: beforeNow.LastTransitionTime, 151 }, 152 } 153 for _, tt := range tests { 154 t.Run(tt.name, func(t *testing.T) { 155 g := NewWithT(t) 156 157 got := minLastTransitionTime(tt.args.a, tt.args.b) 158 g.Expect(got.Time).To(BeTemporally("~", tt.want.Time)) 159 }) 160 } 161 } 162 163 func Test_isObjDebug(t *testing.T) { 164 obj := fakeMachine("my-machine") 165 type args struct { 166 filter string 167 } 168 tests := []struct { 169 name string 170 args args 171 want bool 172 }{ 173 { 174 name: "empty filter should return false", 175 args: args{ 176 filter: "", 177 }, 178 want: false, 179 }, 180 { 181 name: "all filter should return true", 182 args: args{ 183 filter: "all", 184 }, 185 want: true, 186 }, 187 { 188 name: "kind filter should return true", 189 args: args{ 190 filter: "Machine", 191 }, 192 want: true, 193 }, 194 { 195 name: "another kind filter should return false", 196 args: args{ 197 filter: "AnotherKind", 198 }, 199 want: false, 200 }, 201 { 202 name: "kind/name filter should return true", 203 args: args{ 204 filter: "Machine/my-machine", 205 }, 206 want: true, 207 }, 208 { 209 name: "kind/wrong name filter should return false", 210 args: args{ 211 filter: "Cluster/another-cluster", 212 }, 213 want: false, 214 }, 215 } 216 for _, tt := range tests { 217 t.Run(tt.name, func(t *testing.T) { 218 g := NewWithT(t) 219 220 got := isObjDebug(obj, tt.args.filter) 221 g.Expect(got).To(Equal(tt.want)) 222 }) 223 } 224 } 225 226 func Test_createGroupNode(t *testing.T) { 227 now := metav1.Now() 228 beforeNow := metav1.Time{Time: now.Time.Add(-1 * time.Hour)} 229 230 obj := &clusterv1.Machine{ 231 TypeMeta: metav1.TypeMeta{ 232 Kind: "Machine", 233 }, 234 ObjectMeta: metav1.ObjectMeta{ 235 Namespace: "ns", 236 Name: "my-machine", 237 }, 238 Status: clusterv1.MachineStatus{ 239 Conditions: clusterv1.Conditions{ 240 clusterv1.Condition{Type: clusterv1.ReadyCondition, LastTransitionTime: now}, 241 }, 242 }, 243 } 244 245 sibling := &clusterv1.Machine{ 246 TypeMeta: metav1.TypeMeta{ 247 Kind: "Machine", 248 }, 249 ObjectMeta: metav1.ObjectMeta{ 250 Namespace: "ns", 251 Name: "sibling-machine", 252 }, 253 Status: clusterv1.MachineStatus{ 254 Conditions: clusterv1.Conditions{ 255 clusterv1.Condition{Type: clusterv1.ReadyCondition, LastTransitionTime: beforeNow}, 256 }, 257 }, 258 } 259 260 want := &unstructured.Unstructured{ 261 Object: map[string]interface{}{ 262 "apiVersion": "virtual.cluster.x-k8s.io/v1beta1", 263 "kind": "MachineGroup", 264 "metadata": map[string]interface{}{ 265 "namespace": "ns", 266 "name": "", // random string 267 "annotations": map[string]interface{}{ 268 VirtualObjectAnnotation: "True", 269 GroupObjectAnnotation: "True", 270 GroupItemsAnnotation: "my-machine, sibling-machine", 271 }, 272 "uid": "", // random string 273 }, 274 "status": map[string]interface{}{ 275 "conditions": []interface{}{ 276 map[string]interface{}{ 277 "status": "", 278 "lastTransitionTime": beforeNow.Time.UTC().Format(time.RFC3339), 279 "type": "Ready", 280 }, 281 }, 282 }, 283 }, 284 } 285 286 g := NewWithT(t) 287 got := createGroupNode(sibling, GetReadyCondition(sibling), obj, GetReadyCondition(obj)) 288 289 // Some values are generated randomly, so pick up them. 290 want.SetName(got.GetName()) 291 want.SetUID(got.GetUID()) 292 293 g.Expect(got).To(BeComparableTo(want)) 294 } 295 296 func Test_updateGroupNode(t *testing.T) { 297 now := metav1.Now() 298 beforeNow := metav1.Time{Time: now.Time.Add(-1 * time.Hour)} 299 300 group := &unstructured.Unstructured{ 301 Object: map[string]interface{}{ 302 "apiVersion": "virtual.cluster.x-k8s.io/v1beta1", 303 "kind": "MachineGroup", 304 "metadata": map[string]interface{}{ 305 "namespace": "ns", 306 "name": "random-name", 307 "annotations": map[string]interface{}{ 308 VirtualObjectAnnotation: "True", 309 GroupObjectAnnotation: "True", 310 GroupItemsAnnotation: "my-machine, sibling-machine", 311 }, 312 "uid": "random-uid", 313 }, 314 "status": map[string]interface{}{ 315 "conditions": []interface{}{ 316 map[string]interface{}{ 317 "status": "", 318 "lastTransitionTime": beforeNow.Time.UTC().Format(time.RFC3339), 319 "type": "Ready", 320 }, 321 }, 322 }, 323 }, 324 } 325 326 obj := &clusterv1.Machine{ 327 TypeMeta: metav1.TypeMeta{ 328 Kind: "Machine", 329 }, 330 ObjectMeta: metav1.ObjectMeta{ 331 Namespace: "ns", 332 Name: "another-machine", 333 }, 334 Status: clusterv1.MachineStatus{ 335 Conditions: clusterv1.Conditions{ 336 clusterv1.Condition{Type: clusterv1.ReadyCondition, LastTransitionTime: now}, 337 }, 338 }, 339 } 340 341 want := &unstructured.Unstructured{ 342 Object: map[string]interface{}{ 343 "apiVersion": "virtual.cluster.x-k8s.io/v1beta1", 344 "kind": "MachineGroup", 345 "metadata": map[string]interface{}{ 346 "namespace": "ns", 347 "name": "random-name", 348 "annotations": map[string]interface{}{ 349 VirtualObjectAnnotation: "True", 350 GroupObjectAnnotation: "True", 351 GroupItemsAnnotation: "another-machine, my-machine, sibling-machine", 352 }, 353 "uid": "random-uid", 354 }, 355 "status": map[string]interface{}{ 356 "conditions": []interface{}{ 357 map[string]interface{}{ 358 "status": "", 359 "lastTransitionTime": beforeNow.Time.UTC().Format(time.RFC3339), 360 "type": "Ready", 361 }, 362 }, 363 }, 364 }, 365 } 366 367 g := NewWithT(t) 368 updateGroupNode(group, GetReadyCondition(group), obj, GetReadyCondition(obj)) 369 370 g.Expect(group).To(BeComparableTo(want)) 371 } 372 373 func Test_Add_setsShowObjectConditionsAnnotation(t *testing.T) { 374 parent := fakeCluster("parent") 375 obj := fakeMachine("my-machine") 376 377 type args struct { 378 treeOptions ObjectTreeOptions 379 } 380 tests := []struct { 381 name string 382 args args 383 want bool 384 }{ 385 { 386 name: "filter selecting my machine should not add the annotation", 387 args: args{ 388 treeOptions: ObjectTreeOptions{ShowOtherConditions: "all"}, 389 }, 390 want: true, 391 }, 392 { 393 name: "filter not selecting my machine should not add the annotation", 394 args: args{ 395 treeOptions: ObjectTreeOptions{ShowOtherConditions: ""}, 396 }, 397 want: false, 398 }, 399 } 400 for _, tt := range tests { 401 t.Run(tt.name, func(t *testing.T) { 402 root := parent.DeepCopy() 403 tree := NewObjectTree(root, tt.args.treeOptions) 404 405 g := NewWithT(t) 406 getAdded, gotVisible := tree.Add(root, obj.DeepCopy()) 407 g.Expect(getAdded).To(BeTrue()) 408 g.Expect(gotVisible).To(BeTrue()) 409 410 gotObj := tree.GetObject("my-machine") 411 g.Expect(gotObj).ToNot(BeNil()) 412 switch tt.want { 413 case true: 414 g.Expect(gotObj.GetAnnotations()).To(HaveKey(ShowObjectConditionsAnnotation)) 415 g.Expect(gotObj.GetAnnotations()[ShowObjectConditionsAnnotation]).To(Equal("True")) 416 case false: 417 g.Expect(gotObj.GetAnnotations()).ToNot(HaveKey(ShowObjectConditionsAnnotation)) 418 } 419 }) 420 } 421 } 422 423 func Test_Add_setsGroupingObjectAnnotation(t *testing.T) { 424 parent := fakeCluster("parent") 425 obj := fakeMachine("my-machine") 426 427 type args struct { 428 treeOptions ObjectTreeOptions 429 addOptions []AddObjectOption 430 } 431 tests := []struct { 432 name string 433 args args 434 want bool 435 }{ 436 { 437 name: "should not add the annotation if not requested to", 438 args: args{ 439 treeOptions: ObjectTreeOptions{}, 440 addOptions: nil, // without GroupingObject option 441 }, 442 want: false, 443 }, 444 { 445 name: "should add the annotation if requested to and grouping is enabled", 446 args: args{ 447 treeOptions: ObjectTreeOptions{Grouping: true}, 448 addOptions: []AddObjectOption{GroupingObject(true)}, 449 }, 450 want: true, 451 }, 452 { 453 name: "should not add the annotation if requested to, but grouping is disabled", 454 args: args{ 455 treeOptions: ObjectTreeOptions{Grouping: false}, 456 addOptions: []AddObjectOption{GroupingObject(true)}, 457 }, 458 want: false, 459 }, 460 } 461 for _, tt := range tests { 462 t.Run(tt.name, func(t *testing.T) { 463 root := parent.DeepCopy() 464 tree := NewObjectTree(root, tt.args.treeOptions) 465 466 g := NewWithT(t) 467 getAdded, gotVisible := tree.Add(root, obj.DeepCopy(), tt.args.addOptions...) 468 g.Expect(getAdded).To(BeTrue()) 469 g.Expect(gotVisible).To(BeTrue()) 470 471 gotObj := tree.GetObject("my-machine") 472 g.Expect(gotObj).ToNot(BeNil()) 473 switch tt.want { 474 case true: 475 g.Expect(gotObj.GetAnnotations()).To(HaveKey(GroupingObjectAnnotation)) 476 g.Expect(gotObj.GetAnnotations()[GroupingObjectAnnotation]).To(Equal("True")) 477 case false: 478 g.Expect(gotObj.GetAnnotations()).ToNot(HaveKey(GroupingObjectAnnotation)) 479 } 480 }) 481 } 482 } 483 484 func Test_Add_setsObjectMetaNameAnnotation(t *testing.T) { 485 parent := fakeCluster("parent") 486 obj := fakeMachine("my-machine") 487 488 type args struct { 489 addOptions []AddObjectOption 490 } 491 tests := []struct { 492 name string 493 args args 494 want bool 495 }{ 496 { 497 name: "should not add the annotation if not requested to", 498 args: args{ 499 addOptions: nil, // without ObjectMetaName option 500 }, 501 want: false, 502 }, 503 { 504 name: "should add the annotation if requested to", 505 args: args{ 506 addOptions: []AddObjectOption{ObjectMetaName("MetaName")}, 507 }, 508 want: true, 509 }, 510 } 511 for _, tt := range tests { 512 t.Run(tt.name, func(t *testing.T) { 513 root := parent.DeepCopy() 514 tree := NewObjectTree(root, ObjectTreeOptions{}) 515 516 g := NewWithT(t) 517 getAdded, gotVisible := tree.Add(root, obj.DeepCopy(), tt.args.addOptions...) 518 g.Expect(getAdded).To(BeTrue()) 519 g.Expect(gotVisible).To(BeTrue()) 520 521 gotObj := tree.GetObject("my-machine") 522 g.Expect(gotObj).ToNot(BeNil()) 523 switch tt.want { 524 case true: 525 g.Expect(gotObj.GetAnnotations()).To(HaveKey(ObjectMetaNameAnnotation)) 526 g.Expect(gotObj.GetAnnotations()[ObjectMetaNameAnnotation]).To(Equal("MetaName")) 527 case false: 528 g.Expect(gotObj.GetAnnotations()).ToNot(HaveKey(ObjectMetaNameAnnotation)) 529 } 530 }) 531 } 532 } 533 534 func Test_Add_NoEcho(t *testing.T) { 535 parent := fakeCluster("parent", 536 withClusterCondition(conditions.TrueCondition(clusterv1.ReadyCondition)), 537 ) 538 539 type args struct { 540 treeOptions ObjectTreeOptions 541 addOptions []AddObjectOption 542 obj *clusterv1.Machine 543 } 544 tests := []struct { 545 name string 546 args args 547 wantNode bool 548 }{ 549 { 550 name: "should always add if NoEcho option is not present", 551 args: args{ 552 treeOptions: ObjectTreeOptions{}, 553 addOptions: nil, 554 obj: fakeMachine("my-machine", 555 withMachineCondition(conditions.TrueCondition(clusterv1.ReadyCondition)), 556 ), 557 }, 558 wantNode: true, 559 }, 560 { 561 name: "should not add if NoEcho option is present and objects have same ReadyCondition", 562 args: args{ 563 treeOptions: ObjectTreeOptions{}, 564 addOptions: []AddObjectOption{NoEcho(true)}, 565 obj: fakeMachine("my-machine", 566 withMachineCondition(conditions.TrueCondition(clusterv1.ReadyCondition)), 567 ), 568 }, 569 wantNode: false, 570 }, 571 { 572 name: "should add if NoEcho option is present but objects have not same ReadyCondition", 573 args: args{ 574 treeOptions: ObjectTreeOptions{}, 575 addOptions: []AddObjectOption{NoEcho(true)}, 576 obj: fakeMachine("my-machine", 577 withMachineCondition(conditions.FalseCondition(clusterv1.ReadyCondition, "", clusterv1.ConditionSeverityInfo, "")), 578 ), 579 }, 580 wantNode: true, 581 }, 582 { 583 name: "should add if NoEcho option is present, objects have same ReadyCondition, but NoEcho is disabled", 584 args: args{ 585 treeOptions: ObjectTreeOptions{Echo: true}, 586 addOptions: []AddObjectOption{NoEcho(true)}, 587 obj: fakeMachine("my-machine", 588 withMachineCondition(conditions.TrueCondition(clusterv1.ReadyCondition)), 589 ), 590 }, 591 wantNode: true, 592 }, 593 } 594 for _, tt := range tests { 595 t.Run(tt.name, func(t *testing.T) { 596 root := parent.DeepCopy() 597 tree := NewObjectTree(root, tt.args.treeOptions) 598 599 g := NewWithT(t) 600 getAdded, gotVisible := tree.Add(root, tt.args.obj, tt.args.addOptions...) 601 g.Expect(getAdded).To(Equal(tt.wantNode)) 602 g.Expect(gotVisible).To(Equal(tt.wantNode)) 603 604 gotObj := tree.GetObject("my-machine") 605 switch tt.wantNode { 606 case true: 607 g.Expect(gotObj).ToNot(BeNil()) 608 case false: 609 g.Expect(gotObj).To(BeNil()) 610 } 611 }) 612 } 613 } 614 615 func Test_Add_Grouping(t *testing.T) { 616 parent := fakeCluster("parent", 617 withClusterAnnotation(GroupingObjectAnnotation, "True"), 618 ) 619 620 type args struct { 621 addOptions []AddObjectOption 622 siblings []client.Object 623 obj client.Object 624 } 625 tests := []struct { 626 name string 627 args args 628 wantNodesPrefix []string 629 wantVisible bool 630 wantItems string 631 }{ 632 { 633 name: "should never group the first child object", 634 args: args{ 635 obj: fakeMachine("my-machine"), 636 }, 637 wantNodesPrefix: []string{"my-machine"}, 638 wantVisible: true, 639 }, 640 { 641 name: "should group child node if it has same kind and conditions of an existing one", 642 args: args{ 643 siblings: []client.Object{ 644 fakeMachine("first-machine", 645 withMachineCondition(conditions.TrueCondition(clusterv1.ReadyCondition)), 646 ), 647 }, 648 obj: fakeMachine("second-machine", 649 withMachineCondition(conditions.TrueCondition(clusterv1.ReadyCondition)), 650 ), 651 }, 652 wantNodesPrefix: []string{"zz_True"}, 653 wantVisible: false, 654 wantItems: "first-machine, second-machine", 655 }, 656 { 657 name: "should group child node if it has same kind and conditions of an existing group", 658 args: args{ 659 siblings: []client.Object{ 660 fakeMachine("first-machine", 661 withMachineCondition(conditions.TrueCondition(clusterv1.ReadyCondition)), 662 ), 663 fakeMachine("second-machine", 664 withMachineCondition(conditions.TrueCondition(clusterv1.ReadyCondition)), 665 ), 666 }, 667 obj: fakeMachine("third-machine", 668 withMachineCondition(conditions.TrueCondition(clusterv1.ReadyCondition)), 669 ), 670 }, 671 wantNodesPrefix: []string{"zz_True"}, 672 wantVisible: false, 673 wantItems: "first-machine, second-machine, third-machine", 674 }, 675 { 676 name: "should not group child node if it has different kind", 677 args: args{ 678 siblings: []client.Object{ 679 fakeMachine("first-machine", 680 withMachineCondition(conditions.TrueCondition(clusterv1.ReadyCondition)), 681 ), 682 fakeMachine("second-machine", 683 withMachineCondition(conditions.TrueCondition(clusterv1.ReadyCondition)), 684 ), 685 }, 686 obj: VirtualObject("ns", "NotAMachine", "other-object"), 687 }, 688 wantNodesPrefix: []string{"zz_True", "other-object"}, 689 wantVisible: true, 690 wantItems: "first-machine, second-machine", 691 }, 692 } 693 for _, tt := range tests { 694 t.Run(tt.name, func(t *testing.T) { 695 root := parent.DeepCopy() 696 tree := NewObjectTree(root, ObjectTreeOptions{}) 697 698 for i := range tt.args.siblings { 699 tree.Add(parent, tt.args.siblings[i], tt.args.addOptions...) 700 } 701 702 g := NewWithT(t) 703 getAdded, gotVisible := tree.Add(root, tt.args.obj, tt.args.addOptions...) 704 g.Expect(getAdded).To(BeTrue()) 705 g.Expect(gotVisible).To(Equal(tt.wantVisible)) 706 707 gotObjs := tree.GetObjectsByParent("parent") 708 g.Expect(gotObjs).To(HaveLen(len(tt.wantNodesPrefix))) 709 for _, obj := range gotObjs { 710 found := false 711 for _, prefix := range tt.wantNodesPrefix { 712 if strings.HasPrefix(obj.GetName(), prefix) { 713 found = true 714 break 715 } 716 } 717 g.Expect(found).To(BeTrue(), "Found object with name %q, waiting for one of %s", obj.GetName(), tt.wantNodesPrefix) 718 719 if strings.HasPrefix(obj.GetName(), "zz_") { 720 g.Expect(GetGroupItems(obj)).To(Equal(tt.wantItems)) 721 } 722 } 723 }) 724 } 725 } 726 727 type clusterOption func(*clusterv1.Cluster) 728 729 func fakeCluster(name string, options ...clusterOption) *clusterv1.Cluster { 730 c := &clusterv1.Cluster{ 731 TypeMeta: metav1.TypeMeta{ 732 Kind: "Cluster", 733 }, 734 ObjectMeta: metav1.ObjectMeta{ 735 Namespace: "ns", 736 Name: name, 737 UID: types.UID(name), 738 }, 739 } 740 for _, opt := range options { 741 opt(c) 742 } 743 return c 744 } 745 746 func withClusterAnnotation(name, value string) func(*clusterv1.Cluster) { 747 return func(c *clusterv1.Cluster) { 748 if c.Annotations == nil { 749 c.Annotations = map[string]string{} 750 } 751 c.Annotations[name] = value 752 } 753 } 754 755 func withClusterCondition(c *clusterv1.Condition) func(*clusterv1.Cluster) { 756 return func(m *clusterv1.Cluster) { 757 conditions.Set(m, c) 758 } 759 } 760 761 type machineOption func(*clusterv1.Machine) 762 763 func fakeMachine(name string, options ...machineOption) *clusterv1.Machine { 764 m := &clusterv1.Machine{ 765 TypeMeta: metav1.TypeMeta{ 766 Kind: "Machine", 767 }, 768 ObjectMeta: metav1.ObjectMeta{ 769 Namespace: "ns", 770 Name: name, 771 UID: types.UID(name), 772 }, 773 } 774 for _, opt := range options { 775 opt(m) 776 } 777 return m 778 } 779 780 func withMachineCondition(c *clusterv1.Condition) func(*clusterv1.Machine) { 781 return func(m *clusterv1.Machine) { 782 conditions.Set(m, c) 783 } 784 }