Skip to content

✨Add clusterctl options to show templates and cluster resource sets #5762

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions cmd/clusterctl/client/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ type DescribeClusterOptions struct {
// ShowMachineSets instructs the discovery process to include machine sets in the ObjectTree.
ShowMachineSets bool

// ShowClusterResourceSets instructs the discovery process to include cluster resource sets in the ObjectTree.
ShowClusterResourceSets bool

// ShowTemplates instructs the discovery process to include infrastructure and bootstrap config templates in the ObjectTree.
ShowTemplates bool

// AddTemplateVirtualNode instructs the discovery process to group template under a virtual node.
AddTemplateVirtualNode bool

// Echo displays MachineInfrastructure or BootstrapConfig objects if the object's ready condition is true
// or it has the same Status, Severity and Reason of the parent's object ready condition (it is an echo)
Echo bool
Expand Down Expand Up @@ -80,9 +89,12 @@ func (c *clusterctlClient) DescribeCluster(options DescribeClusterOptions) (*tre

// Gets the object tree representing the status of a Cluster API cluster.
return tree.Discovery(context.TODO(), client, options.Namespace, options.ClusterName, tree.DiscoverOptions{
ShowOtherConditions: options.ShowOtherConditions,
ShowMachineSets: options.ShowMachineSets,
Echo: options.Echo,
Grouping: options.Grouping,
ShowOtherConditions: options.ShowOtherConditions,
ShowMachineSets: options.ShowMachineSets,
ShowClusterResourceSets: options.ShowClusterResourceSets,
ShowTemplates: options.ShowTemplates,
AddTemplateVirtualNode: options.AddTemplateVirtualNode,
Echo: options.Echo,
Grouping: options.Grouping,
})
}
21 changes: 18 additions & 3 deletions cmd/clusterctl/client/tree/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ const (

// GroupItemsSeparator is the separator used in the GroupItemsAnnotation.
GroupItemsSeparator = ", "

// ObjectZOrderAnnotation contains an integer that defines the sorting of child objects when the object tree is printed.
// Objects are sorted by their z-order from highest to lowest, and then by their name in alphaebetical order if the
// z-order is the same. Objects with no z-order set are assumed to have a default z-order of 0.
ObjectZOrderAnnotation = "tree.cluster.x-k8s.io.io/z-order"
)

// GetMetaName returns the object meta name that should be used for the object in the presentation layer, if defined.
Expand All @@ -69,7 +74,7 @@ func IsGroupingObject(obj client.Object) bool {
return false
}

// IsGroupObject return true if the object is the result of a grouping operation, and
// IsGroupObject returns true if the object is the result of a grouping operation, and
// thus the object is representing group of sibling object, e.g. a group of machines.
func IsGroupObject(obj client.Object) bool {
if val, ok := getBoolAnnotation(obj, GroupObjectAnnotation); ok {
Expand All @@ -78,15 +83,25 @@ func IsGroupObject(obj client.Object) bool {
return false
}

// GetGroupItems return the list of names for the objects included in a group object.
// GetGroupItems returns the list of names for the objects included in a group object.
func GetGroupItems(obj client.Object) string {
if val, ok := getAnnotation(obj, GroupItemsAnnotation); ok {
return val
}
return ""
}

// IsVirtualObject return true if the object does not correspond to any real object, but instead it is
// GetZOrder return the zOrder of the object. Objects with no zOrder have a default zOrder of 0.
func GetZOrder(obj client.Object) int {
if val, ok := getAnnotation(obj, ObjectZOrderAnnotation); ok {
if zOrder, err := strconv.ParseInt(val, 10, 0); err == nil {
return int(zOrder)
}
}
return 0
}

// IsVirtualObject returns true if the object does not correspond to any real object, but instead it is
// a virtual object introduced to provide a better representation of the cluster status.
func IsVirtualObject(obj client.Object) bool {
if val, ok := getBoolAnnotation(obj, VirtualObjectAnnotation); ok {
Expand Down
194 changes: 175 additions & 19 deletions cmd/clusterctl/client/tree/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@ package tree
import (
"context"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/controller-runtime/pkg/client"

clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
"sigs.k8s.io/cluster-api/controllers/external"
addonsv1 "sigs.k8s.io/cluster-api/exp/addons/api/v1beta1"
expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
"sigs.k8s.io/cluster-api/util"
)

Expand All @@ -36,8 +40,16 @@ type DiscoverOptions struct {
// ShowMachineSets instructs the discovery process to include machine sets in the ObjectTree.
ShowMachineSets bool

// ShowClusterResourceSets instructs the discovery process to include cluster resource sets in the ObjectTree.
ShowClusterResourceSets bool

// ShowTemplates instructs the discovery process to include infrastructure and bootstrap config templates in the ObjectTree.
ShowTemplates bool

// AddTemplateVirtualNode instructs the discovery process to group template under a virtual node.
AddTemplateVirtualNode bool

// Echo displays MachineInfrastructure or BootstrapConfig objects if the object's ready condition is true
// or it has the same Status, Severity and Reason of the parent's object ready condition (it is an echo)
Echo bool

// Grouping groups machine objects in case the ready conditions
Expand Down Expand Up @@ -75,10 +87,14 @@ func Discovery(ctx context.Context, c client.Client, namespace, name string, opt
tree.Add(cluster, clusterInfra, ObjectMetaName("ClusterInfrastructure"))
}

if options.ShowClusterResourceSets {
addClusterResourceSetsToObjectTree(ctx, c, cluster, tree)
}

// Adds control plane
controlPLane, err := external.Get(ctx, c, cluster.Spec.ControlPlaneRef, cluster.Namespace)
controlPlane, err := external.Get(ctx, c, cluster.Spec.ControlPlaneRef, cluster.Namespace)
if err == nil {
tree.Add(cluster, controlPLane, ObjectMetaName("ControlPlane"), GroupingObject(true))
addControlPlane(cluster, controlPlane, tree, options)
}

// Adds control plane machines.
Expand All @@ -105,25 +121,103 @@ func Discovery(ctx context.Context, c client.Client, namespace, name string, opt
controlPlaneMachines := selectControlPlaneMachines(machinesList)
for i := range controlPlaneMachines {
cp := controlPlaneMachines[i]
addMachineFunc(controlPLane, cp)
addMachineFunc(controlPlane, cp)
}

if len(machinesList.Items) == len(controlPlaneMachines) {
return tree, nil
machinePoolList, err := getMachinePoolsInCluster(ctx, c, cluster.Namespace, cluster.Name)
if err != nil {
return nil, err
}

workers := VirtualObject(cluster.Namespace, "WorkerGroup", "Workers")
tree.Add(cluster, workers)
// Add WorkerGroup if there are MachineDeployments or MachinePools
if len(machinesList.Items) != len(controlPlaneMachines) || len(machinePoolList.Items) > 0 {
tree.Add(cluster, workers)
}

if len(machinesList.Items) != len(controlPlaneMachines) { // Add MachineDeployment objects
tree.Add(cluster, workers)
err = addMachineDeploymentToObjectTree(ctx, c, cluster, workers, machinesList, tree, options, addMachineFunc)
if err != nil {
return nil, err
}

// Handles orphan machines.
if len(machineMap) < len(machinesList.Items) {
other := VirtualObject(cluster.Namespace, "OtherGroup", "Other")
tree.Add(workers, other)

for i := range machinesList.Items {
m := &machinesList.Items[i]
if _, ok := machineMap[m.Name]; ok {
continue
}
addMachineFunc(other, m)
}
}
}

if len(machinePoolList.Items) > 0 { // Add MachinePool objects
tree.Add(cluster, workers)
addMachinePoolsToObjectTree(ctx, c, cluster.Namespace, workers, machinePoolList, tree)
}

return tree, nil
}

func addClusterResourceSetsToObjectTree(ctx context.Context, c client.Client, cluster *clusterv1.Cluster, tree *ObjectTree) {
if resourceSetBinding, err := getResourceSetBindingInCluster(ctx, c, cluster.Namespace, cluster.Name); err == nil {
resourceSetGroup := VirtualObject(cluster.Namespace, "ClusterResourceSetGroup", "ClusterResourceSets")
tree.Add(cluster, resourceSetGroup)

for _, binding := range resourceSetBinding.Spec.Bindings {
resourceSetRefObject := ObjectReferenceObject(&corev1.ObjectReference{
Kind: "ClusterResourceSet",
Namespace: cluster.Namespace,
Name: binding.ClusterResourceSetName,
APIVersion: addonsv1.GroupVersion.String(),
})
tree.Add(resourceSetGroup, resourceSetRefObject)
}
}
}

func addControlPlane(cluster *clusterv1.Cluster, controlPlane *unstructured.Unstructured, tree *ObjectTree, options DiscoverOptions) {
tree.Add(cluster, controlPlane, ObjectMetaName("ControlPlane"), GroupingObject(true))

if options.ShowTemplates {
// Add control plane infrastructure ref using spec fields guaranteed in contract
infrastructureRef, found, err := unstructured.NestedMap(controlPlane.UnstructuredContent(), "spec", "machineTemplate", "infrastructureRef")
if err == nil && found {
infrastructureObjectRef := &corev1.ObjectReference{
Kind: infrastructureRef["kind"].(string),
Namespace: infrastructureRef["namespace"].(string),
Name: infrastructureRef["name"].(string),
APIVersion: infrastructureRef["apiVersion"].(string),
}

machineTemplateRefObject := ObjectReferenceObject(infrastructureObjectRef)
var templateParent client.Object
if options.AddTemplateVirtualNode {
templateParent = addTemplateVirtualNode(tree, controlPlane, cluster.Namespace)
} else {
templateParent = controlPlane
}
tree.Add(templateParent, machineTemplateRefObject, ObjectMetaName("MachineInfrastructureTemplate"))
}
}
}

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 {
// Adds worker machines.
machinesDeploymentList, err := getMachineDeploymentsInCluster(ctx, c, cluster.Namespace, cluster.Name)
if err != nil {
return nil, err
return err
}

machineSetList, err := getMachineSetsInCluster(ctx, c, cluster.Namespace, cluster.Name)
if err != nil {
return nil, err
return err
}

for i := range machinesDeploymentList.Items {
Expand All @@ -134,6 +228,21 @@ func Discovery(ctx context.Context, c client.Client, namespace, name string, opt
}
tree.Add(workers, md, addOpts...)

if options.ShowTemplates {
var templateParent client.Object
if options.AddTemplateVirtualNode {
templateParent = addTemplateVirtualNode(tree, md, cluster.Namespace)
} else {
templateParent = md
}

bootstrapTemplateRefObject := ObjectReferenceObject(md.Spec.Template.Spec.Bootstrap.ConfigRef)
tree.Add(templateParent, bootstrapTemplateRefObject, ObjectMetaName("BootstrapConfigTemplate"))

machineTemplateRefObject := ObjectReferenceObject(&md.Spec.Template.Spec.InfrastructureRef)
tree.Add(templateParent, machineTemplateRefObject, ObjectMetaName("MachineInfrastructureTemplate"))
}

machineSets := selectMachinesSetsControlledBy(machineSetList, md)
for i := range machineSets {
ms := machineSets[i]
Expand All @@ -151,21 +260,42 @@ func Discovery(ctx context.Context, c client.Client, namespace, name string, opt
}
}

// Handles orphan machines.
if len(machineMap) < len(machinesList.Items) {
other := VirtualObject(cluster.Namespace, "OtherGroup", "Other")
tree.Add(workers, other)
return nil
}

func addMachinePoolsToObjectTree(ctx context.Context, c client.Client, namespace string, workers *unstructured.Unstructured, machinePoolList *expv1.MachinePoolList, tree *ObjectTree) {
for i := range machinePoolList.Items {
mp := &machinePoolList.Items[i]
_, visible := tree.Add(workers, mp)

for i := range machinesList.Items {
m := &machinesList.Items[i]
if _, ok := machineMap[m.Name]; ok {
continue
if visible {
if machinePoolBootstrap, err := external.Get(ctx, c, mp.Spec.Template.Spec.Bootstrap.ConfigRef, namespace); err == nil {
tree.Add(mp, machinePoolBootstrap, ObjectMetaName("BootstrapConfig"), NoEcho(true))
}

if machinePoolInfra, err := external.Get(ctx, c, &mp.Spec.Template.Spec.InfrastructureRef, namespace); err == nil {
tree.Add(mp, machinePoolInfra, ObjectMetaName("MachineInfrastructure"), NoEcho(true))
}
addMachineFunc(other, m)
}
}
}

return tree, nil
func getResourceSetBindingInCluster(ctx context.Context, c client.Client, namespace string, name string) (*addonsv1.ClusterResourceSetBinding, error) {
if name == "" {
return nil, nil
}

resourceSetBinding := &addonsv1.ClusterResourceSetBinding{}
resourceSetBindingKey := client.ObjectKey{Namespace: namespace, Name: name}
if err := c.Get(ctx, resourceSetBindingKey, resourceSetBinding); err != nil {
return nil, err
}
resourceSetBinding.TypeMeta = metav1.TypeMeta{
Kind: "ClusterResourceSetBinding",
APIVersion: addonsv1.GroupVersion.String(),
}

return resourceSetBinding, nil
}

func getMachinesInCluster(ctx context.Context, c client.Client, namespace, name string) (*clusterv1.MachineList, error) {
Expand Down Expand Up @@ -213,6 +343,21 @@ func getMachineSetsInCluster(ctx context.Context, c client.Client, namespace, na
return machineSetList, nil
}

func getMachinePoolsInCluster(ctx context.Context, c client.Client, namespace, name string) (*expv1.MachinePoolList, error) {
if name == "" {
return nil, nil
}

machinePoolList := &expv1.MachinePoolList{}
labels := map[string]string{clusterv1.ClusterLabelName: name}

if err := c.List(ctx, machinePoolList, client.InNamespace(namespace), client.MatchingLabels(labels)); err != nil {
return nil, err
}

return machinePoolList, nil
}

func selectControlPlaneMachines(machineList *clusterv1.MachineList) []*clusterv1.Machine {
machines := []*clusterv1.Machine{}
for i := range machineList.Items {
Expand Down Expand Up @@ -245,3 +390,14 @@ func selectMachinesControlledBy(machineList *clusterv1.MachineList, controller c
}
return machines
}

func addTemplateVirtualNode(tree *ObjectTree, parent client.Object, namespace string) client.Object {
templateNode := VirtualObject(namespace, "TemplateGroup", parent.GetName())
addOpts := []AddObjectOption{
ZOrder(1),
ObjectMetaName("Templates"),
}
tree.Add(parent, templateNode, addOpts...)

return templateNode
}
Loading