sigs.k8s.io/cluster-api@v1.7.1/cmd/clusterctl/client/tree/discovery.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 "context" 21 22 "github.com/pkg/errors" 23 corev1 "k8s.io/api/core/v1" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 26 "sigs.k8s.io/controller-runtime/pkg/client" 27 28 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 29 "sigs.k8s.io/cluster-api/controllers/external" 30 addonsv1 "sigs.k8s.io/cluster-api/exp/addons/api/v1beta1" 31 expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1" 32 "sigs.k8s.io/cluster-api/util" 33 ) 34 35 // DiscoverOptions define options for the discovery process. 36 type DiscoverOptions struct { 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 // ShowMachineSets instructs the discovery process to include machine sets in the ObjectTree. 42 ShowMachineSets bool 43 44 // ShowClusterResourceSets instructs the discovery process to include cluster resource sets in the ObjectTree. 45 ShowClusterResourceSets bool 46 47 // ShowTemplates instructs the discovery process to include infrastructure and bootstrap config templates in the ObjectTree. 48 ShowTemplates bool 49 50 // AddTemplateVirtualNode instructs the discovery process to group template under a virtual node. 51 AddTemplateVirtualNode bool 52 53 // Echo displays MachineInfrastructure or BootstrapConfig objects if the object's ready condition is true 54 Echo bool 55 56 // Grouping groups machine objects in case the ready conditions 57 // have the same Status, Severity and Reason. 58 Grouping bool 59 } 60 61 func (d DiscoverOptions) toObjectTreeOptions() ObjectTreeOptions { 62 return ObjectTreeOptions(d) 63 } 64 65 // Discovery returns an object tree representing the status of a Cluster API cluster. 66 func Discovery(ctx context.Context, c client.Client, namespace, name string, options DiscoverOptions) (*ObjectTree, error) { 67 // Fetch the Cluster instance. 68 cluster := &clusterv1.Cluster{} 69 clusterKey := client.ObjectKey{ 70 Namespace: namespace, 71 Name: name, 72 } 73 if err := c.Get(ctx, clusterKey, cluster); err != nil { 74 return nil, err 75 } 76 77 // Enforce TypeMeta to make sure checks on GVK works properly. 78 cluster.TypeMeta = metav1.TypeMeta{ 79 Kind: "Cluster", 80 APIVersion: clusterv1.GroupVersion.String(), 81 } 82 83 // Create an object tree with the cluster as root 84 tree := NewObjectTree(cluster, options.toObjectTreeOptions()) 85 86 // Adds cluster infra 87 clusterInfra, err := external.Get(ctx, c, cluster.Spec.InfrastructureRef, cluster.Namespace) 88 if err != nil { 89 return nil, errors.Wrap(err, "get InfraCluster reference from Cluster") 90 } 91 tree.Add(cluster, clusterInfra, ObjectMetaName("ClusterInfrastructure")) 92 93 if options.ShowClusterResourceSets { 94 addClusterResourceSetsToObjectTree(ctx, c, cluster, tree) 95 } 96 97 // Adds control plane 98 controlPlane, err := external.Get(ctx, c, cluster.Spec.ControlPlaneRef, cluster.Namespace) 99 if err == nil { 100 addControlPlane(cluster, controlPlane, tree, options) 101 } 102 103 // Adds control plane machines. 104 machinesList, err := getMachinesInCluster(ctx, c, cluster.Namespace, cluster.Name) 105 if err != nil { 106 return nil, err 107 } 108 machineMap := map[string]bool{} 109 addMachineFunc := func(parent client.Object, m *clusterv1.Machine) { 110 _, visible := tree.Add(parent, m) 111 machineMap[m.Name] = true 112 113 if visible { 114 if (m.Spec.InfrastructureRef != corev1.ObjectReference{}) { 115 if machineInfra, err := external.Get(ctx, c, &m.Spec.InfrastructureRef, cluster.Namespace); err == nil { 116 tree.Add(m, machineInfra, ObjectMetaName("MachineInfrastructure"), NoEcho(true)) 117 } 118 } 119 120 if m.Spec.Bootstrap.ConfigRef != nil { 121 if machineBootstrap, err := external.Get(ctx, c, m.Spec.Bootstrap.ConfigRef, cluster.Namespace); err == nil { 122 tree.Add(m, machineBootstrap, ObjectMetaName("BootstrapConfig"), NoEcho(true)) 123 } 124 } 125 } 126 } 127 128 controlPlaneMachines := selectControlPlaneMachines(machinesList) 129 if controlPlane != nil { 130 for i := range controlPlaneMachines { 131 cp := controlPlaneMachines[i] 132 addMachineFunc(controlPlane, cp) 133 } 134 } 135 136 machinePoolList, err := getMachinePoolsInCluster(ctx, c, cluster.Namespace, cluster.Name) 137 if err != nil { 138 return nil, err 139 } 140 141 workers := VirtualObject(cluster.Namespace, "WorkerGroup", "Workers") 142 // Add WorkerGroup if there are MachineDeployments or MachinePools 143 if len(machinesList.Items) != len(controlPlaneMachines) || len(machinePoolList.Items) > 0 { 144 tree.Add(cluster, workers) 145 } 146 147 if len(machinesList.Items) != len(controlPlaneMachines) { // Add MachineDeployment objects 148 tree.Add(cluster, workers) 149 err = addMachineDeploymentToObjectTree(ctx, c, cluster, workers, machinesList, tree, options, addMachineFunc) 150 if err != nil { 151 return nil, err 152 } 153 } 154 155 if len(machinePoolList.Items) > 0 { // Add MachinePool objects 156 tree.Add(cluster, workers) 157 addMachinePoolsToObjectTree(ctx, c, cluster.Namespace, workers, machinePoolList, machinesList, tree, addMachineFunc) 158 } 159 160 // Handles orphan machines. 161 if len(machineMap) < len(machinesList.Items) { 162 other := VirtualObject(cluster.Namespace, "OtherGroup", "Other") 163 tree.Add(workers, other) 164 165 for i := range machinesList.Items { 166 m := &machinesList.Items[i] 167 if _, ok := machineMap[m.Name]; ok { 168 continue 169 } 170 addMachineFunc(other, m) 171 } 172 } 173 174 return tree, nil 175 } 176 177 func addClusterResourceSetsToObjectTree(ctx context.Context, c client.Client, cluster *clusterv1.Cluster, tree *ObjectTree) { 178 if resourceSetBinding, err := getResourceSetBindingInCluster(ctx, c, cluster.Namespace, cluster.Name); err == nil { 179 resourceSetGroup := VirtualObject(cluster.Namespace, "ClusterResourceSetGroup", "ClusterResourceSets") 180 tree.Add(cluster, resourceSetGroup) 181 182 for _, binding := range resourceSetBinding.Spec.Bindings { 183 resourceSetRefObject := ObjectReferenceObject(&corev1.ObjectReference{ 184 Kind: "ClusterResourceSet", 185 Namespace: cluster.Namespace, 186 Name: binding.ClusterResourceSetName, 187 APIVersion: addonsv1.GroupVersion.String(), 188 }) 189 tree.Add(resourceSetGroup, resourceSetRefObject) 190 } 191 } 192 } 193 194 func addControlPlane(cluster *clusterv1.Cluster, controlPlane *unstructured.Unstructured, tree *ObjectTree, options DiscoverOptions) { 195 tree.Add(cluster, controlPlane, ObjectMetaName("ControlPlane"), GroupingObject(true)) 196 197 if options.ShowTemplates { 198 // Add control plane infrastructure ref using spec fields guaranteed in contract 199 infrastructureRef, found, err := unstructured.NestedMap(controlPlane.UnstructuredContent(), "spec", "machineTemplate", "infrastructureRef") 200 if err == nil && found { 201 infrastructureObjectRef := &corev1.ObjectReference{ 202 Kind: infrastructureRef["kind"].(string), 203 Namespace: infrastructureRef["namespace"].(string), 204 Name: infrastructureRef["name"].(string), 205 APIVersion: infrastructureRef["apiVersion"].(string), 206 } 207 208 machineTemplateRefObject := ObjectReferenceObject(infrastructureObjectRef) 209 var templateParent client.Object 210 if options.AddTemplateVirtualNode { 211 templateParent = addTemplateVirtualNode(tree, controlPlane, cluster.Namespace) 212 } else { 213 templateParent = controlPlane 214 } 215 tree.Add(templateParent, machineTemplateRefObject, ObjectMetaName("MachineInfrastructureTemplate")) 216 } 217 } 218 } 219 220 func addMachineDeploymentToObjectTree(ctx context.Context, c client.Client, cluster *clusterv1.Cluster, workers *unstructured.Unstructured, machinesList *clusterv1.MachineList, tree *ObjectTree, options DiscoverOptions, addMachineFunc func(parent client.Object, m *clusterv1.Machine)) error { 221 // Adds worker machines. 222 machinesDeploymentList, err := getMachineDeploymentsInCluster(ctx, c, cluster.Namespace, cluster.Name) 223 if err != nil { 224 return err 225 } 226 227 machineSetList, err := getMachineSetsInCluster(ctx, c, cluster.Namespace, cluster.Name) 228 if err != nil { 229 return err 230 } 231 232 for i := range machinesDeploymentList.Items { 233 md := &machinesDeploymentList.Items[i] 234 addOpts := make([]AddObjectOption, 0) 235 if !options.ShowMachineSets { 236 addOpts = append(addOpts, GroupingObject(true)) 237 } 238 tree.Add(workers, md, addOpts...) 239 240 if options.ShowTemplates { 241 var templateParent client.Object 242 if options.AddTemplateVirtualNode { 243 templateParent = addTemplateVirtualNode(tree, md, cluster.Namespace) 244 } else { 245 templateParent = md 246 } 247 248 // md.Spec.Template.Spec.Bootstrap.ConfigRef is optional 249 if md.Spec.Template.Spec.Bootstrap.ConfigRef != nil { 250 bootstrapTemplateRefObject := ObjectReferenceObject(md.Spec.Template.Spec.Bootstrap.ConfigRef) 251 tree.Add(templateParent, bootstrapTemplateRefObject, ObjectMetaName("BootstrapConfigTemplate")) 252 } 253 254 machineTemplateRefObject := ObjectReferenceObject(&md.Spec.Template.Spec.InfrastructureRef) 255 tree.Add(templateParent, machineTemplateRefObject, ObjectMetaName("MachineInfrastructureTemplate")) 256 } 257 258 machineSets := selectMachinesSetsControlledBy(machineSetList, md) 259 for i := range machineSets { 260 ms := machineSets[i] 261 262 var parent client.Object = md 263 if options.ShowMachineSets { 264 tree.Add(md, ms, GroupingObject(true)) 265 parent = ms 266 } 267 268 machines := selectMachinesControlledBy(machinesList, ms) 269 for _, w := range machines { 270 addMachineFunc(parent, w) 271 } 272 } 273 } 274 275 return nil 276 } 277 278 func addMachinePoolsToObjectTree(ctx context.Context, c client.Client, namespace string, workers *unstructured.Unstructured, machinePoolList *expv1.MachinePoolList, machinesList *clusterv1.MachineList, tree *ObjectTree, addMachineFunc func(parent client.Object, m *clusterv1.Machine)) { 279 for i := range machinePoolList.Items { 280 mp := &machinePoolList.Items[i] 281 _, visible := tree.Add(workers, mp, GroupingObject(true)) 282 283 if visible { 284 if machinePoolBootstrap, err := external.Get(ctx, c, mp.Spec.Template.Spec.Bootstrap.ConfigRef, namespace); err == nil { 285 tree.Add(mp, machinePoolBootstrap, ObjectMetaName("BootstrapConfig"), NoEcho(true)) 286 } 287 288 if machinePoolInfra, err := external.Get(ctx, c, &mp.Spec.Template.Spec.InfrastructureRef, namespace); err == nil { 289 tree.Add(mp, machinePoolInfra, ObjectMetaName("MachinePoolInfrastructure"), NoEcho(true)) 290 } 291 } 292 293 machines := selectMachinesControlledBy(machinesList, mp) 294 for _, m := range machines { 295 addMachineFunc(mp, m) 296 } 297 } 298 } 299 300 func getResourceSetBindingInCluster(ctx context.Context, c client.Client, namespace string, name string) (*addonsv1.ClusterResourceSetBinding, error) { 301 if name == "" { 302 return nil, nil 303 } 304 305 resourceSetBinding := &addonsv1.ClusterResourceSetBinding{} 306 resourceSetBindingKey := client.ObjectKey{Namespace: namespace, Name: name} 307 if err := c.Get(ctx, resourceSetBindingKey, resourceSetBinding); err != nil { 308 return nil, err 309 } 310 resourceSetBinding.TypeMeta = metav1.TypeMeta{ 311 Kind: "ClusterResourceSetBinding", 312 APIVersion: addonsv1.GroupVersion.String(), 313 } 314 315 return resourceSetBinding, nil 316 } 317 318 func getMachinesInCluster(ctx context.Context, c client.Client, namespace, name string) (*clusterv1.MachineList, error) { 319 if name == "" { 320 return nil, nil 321 } 322 323 machineList := &clusterv1.MachineList{} 324 labels := map[string]string{clusterv1.ClusterNameLabel: name} 325 326 if err := c.List(ctx, machineList, client.InNamespace(namespace), client.MatchingLabels(labels)); err != nil { 327 return nil, err 328 } 329 330 return machineList, nil 331 } 332 333 func getMachineDeploymentsInCluster(ctx context.Context, c client.Client, namespace, name string) (*clusterv1.MachineDeploymentList, error) { 334 if name == "" { 335 return nil, nil 336 } 337 338 machineDeploymentList := &clusterv1.MachineDeploymentList{} 339 labels := map[string]string{clusterv1.ClusterNameLabel: name} 340 341 if err := c.List(ctx, machineDeploymentList, client.InNamespace(namespace), client.MatchingLabels(labels)); err != nil { 342 return nil, err 343 } 344 345 return machineDeploymentList, nil 346 } 347 348 func getMachineSetsInCluster(ctx context.Context, c client.Client, namespace, name string) (*clusterv1.MachineSetList, error) { 349 if name == "" { 350 return nil, nil 351 } 352 353 machineSetList := &clusterv1.MachineSetList{} 354 labels := map[string]string{clusterv1.ClusterNameLabel: name} 355 356 if err := c.List(ctx, machineSetList, client.InNamespace(namespace), client.MatchingLabels(labels)); err != nil { 357 return nil, err 358 } 359 360 return machineSetList, nil 361 } 362 363 func getMachinePoolsInCluster(ctx context.Context, c client.Client, namespace, name string) (*expv1.MachinePoolList, error) { 364 if name == "" { 365 return nil, nil 366 } 367 368 machinePoolList := &expv1.MachinePoolList{} 369 labels := map[string]string{clusterv1.ClusterNameLabel: name} 370 371 if err := c.List(ctx, machinePoolList, client.InNamespace(namespace), client.MatchingLabels(labels)); err != nil { 372 return nil, err 373 } 374 375 return machinePoolList, nil 376 } 377 378 func selectControlPlaneMachines(machineList *clusterv1.MachineList) []*clusterv1.Machine { 379 machines := []*clusterv1.Machine{} 380 for i := range machineList.Items { 381 m := &machineList.Items[i] 382 if util.IsControlPlaneMachine(m) { 383 machines = append(machines, m) 384 } 385 } 386 return machines 387 } 388 389 func selectMachinesSetsControlledBy(machineSetList *clusterv1.MachineSetList, controller client.Object) []*clusterv1.MachineSet { 390 machineSets := []*clusterv1.MachineSet{} 391 for i := range machineSetList.Items { 392 m := &machineSetList.Items[i] 393 if util.IsControlledBy(m, controller) { 394 machineSets = append(machineSets, m) 395 } 396 } 397 return machineSets 398 } 399 400 func selectMachinesControlledBy(machineList *clusterv1.MachineList, controller client.Object) []*clusterv1.Machine { 401 machines := []*clusterv1.Machine{} 402 for i := range machineList.Items { 403 m := &machineList.Items[i] 404 if util.IsControlledBy(m, controller) { 405 machines = append(machines, m) 406 } 407 } 408 return machines 409 } 410 411 func addTemplateVirtualNode(tree *ObjectTree, parent client.Object, namespace string) client.Object { 412 templateNode := VirtualObject(namespace, "TemplateGroup", parent.GetName()) 413 addOpts := []AddObjectOption{ 414 ZOrder(1), 415 ObjectMetaName("Templates"), 416 } 417 tree.Add(parent, templateNode, addOpts...) 418 419 return templateNode 420 }