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  }