Skip to content

Commit

Permalink
Add discoverVariables call to the ClusterClass reconciler
Browse files Browse the repository at this point in the history
  • Loading branch information
killianmuldoon committed Jan 30, 2023
1 parent 75bcb29 commit f8f6213
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 16 deletions.
4 changes: 4 additions & 0 deletions api/v1beta1/clusterclass_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,10 @@ type ExternalPatchDefinition struct {
// +optional
ValidateExtension *string `json:"validateExtension,omitempty"`

// DiscoverVariablesExtension references an extension which is called to discover variables.
// +optional
DiscoverVariablesExtension *string `json:"discoverVariablesExtension,omitempty"`

// Settings defines key value pairs to be passed to the extensions.
// Values defined here take precedence over the values defined in the
// corresponding ExtensionConfig.
Expand Down
5 changes: 5 additions & 0 deletions api/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions api/v1beta1/zz_generated.openapi.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cmd/clusterctl/client/alpha/rollout_pauser.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func (r *rollout) ObjectPauser(proxy cluster.Proxy, ref corev1.ObjectReference)
return errors.Wrapf(err, "failed to fetch %v/%v", ref.Kind, ref.Name)
}
if deployment.Spec.Paused {
return errors.Errorf("MachineDeploymet is already paused: %v/%v\n", ref.Kind, ref.Name) //nolint:revive // MachineDeployment is intentionally capitalized.
return errors.Errorf("MachineDeployment is already paused: %v/%v\n", ref.Kind, ref.Name) //nolint:revive // MachineDeployment is intentionally capitalized.
}
if err := pauseMachineDeployment(proxy, ref.Name, ref.Namespace); err != nil {
return err
Expand Down
4 changes: 4 additions & 0 deletions config/crd/bases/cluster.x-k8s.io_clusterclasses.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

60 changes: 49 additions & 11 deletions internal/controllers/clusterclass/clusterclass_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ import (

clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
"sigs.k8s.io/cluster-api/controllers/external"
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
"sigs.k8s.io/cluster-api/feature"
tlog "sigs.k8s.io/cluster-api/internal/log"
runtimeclient "sigs.k8s.io/cluster-api/internal/runtime/client"
"sigs.k8s.io/cluster-api/util/annotations"
"sigs.k8s.io/cluster-api/util/conditions"
"sigs.k8s.io/cluster-api/util/conversion"
Expand All @@ -53,6 +56,9 @@ type Reconciler struct {
// WatchFilterValue is the label value used to filter events prior to reconciliation.
WatchFilterValue string

// runtimeClient is a client for calling runtime extensions.
runtimeClient runtimeclient.Client

// UnstructuredCachingClient provides a client that forces caching of unstructured objects,
// thus allowing to optimize reads for templates or provider specific objects.
UnstructuredCachingClient client.Client
Expand All @@ -65,6 +71,7 @@ func (r *Reconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, opt
WithOptions(options).
WithEventFilter(predicates.ResourceNotPausedAndHasFilterLabel(ctrl.LoggerFrom(ctx), r.WatchFilterValue)).
Complete(r)

if err != nil {
return errors.Wrap(err, "failed setting up with a controller manager")
}
Expand Down Expand Up @@ -177,25 +184,56 @@ func (r *Reconciler) reconcile(ctx context.Context, clusterClass *clusterv1.Clus
return ctrl.Result{}, kerrors.NewAggregate(errs)
}

// Ensure the variables are added to the ClusterClass status.
// Add inline variable definitions to the ClusterClass status.
clusterClass.Status.Variables = []clusterv1.ClusterClassStatusVariable{}
for _, variable := range clusterClass.Spec.Variables {
clusterClass.Status.Variables = append(clusterClass.Status.Variables,
clusterv1.ClusterClassStatusVariable{
Name: variable.Name,
Definitions: []clusterv1.ClusterClassStatusVariableDefinition{
{
From: clusterv1.VariableDefinitionFromInline,
Required: variable.Required,
Schema: variable.Schema,
},
}})
clusterClass.Status.Variables = append(clusterClass.Status.Variables, statusVariableFromClusterClassVariable(variable, clusterv1.VariableDefinitionFromInline))
}

// If RuntimeSDK is enabled call the DiscoverVariables hook for all associated Runtime Extensions and add the variables
// to the ClusterClass status.
if feature.Gates.Enabled(feature.RuntimeSDK) {
for _, patch := range clusterClass.Spec.Patches {
if patch.External == nil || patch.External.DiscoverVariablesExtension == nil {
continue
}
req := &runtimehooksv1.DiscoverVariablesRequest{}
req.Settings = patch.External.Settings

resp := &runtimehooksv1.DiscoverVariablesResponse{}
err := r.runtimeClient.CallExtension(ctx, runtimehooksv1.GeneratePatches, clusterClass, *patch.External.ValidateExtension, req, resp)
if err != nil {
return ctrl.Result{}, err
}
if resp.Status != runtimehooksv1.ResponseStatusSuccess {
return ctrl.Result{}, errors.Errorf("failed to discover variables for ClusterClass %s: %s", clusterClass.Name, resp.Message)
}
if resp.Variables != nil {
for _, variable := range resp.Variables {
clusterClass.Status.Variables = append(clusterClass.Status.Variables, statusVariableFromClusterClassVariable(variable, patch.Name))
}
}
}
}
reconcileConditions(clusterClass, outdatedRefs)

return ctrl.Result{}, nil
}

func statusVariableFromClusterClassVariable(variable clusterv1.ClusterClassVariable, from string) clusterv1.ClusterClassStatusVariable {
return clusterv1.ClusterClassStatusVariable{
Name: variable.Name,
// TODO: In a future iteration this should be false where definitions are equal.
DefintionsConflict: true,
Definitions: []clusterv1.ClusterClassStatusVariableDefinition{
{
From: from,
Required: variable.Required,
Schema: variable.Schema,
},
}}
}

func reconcileConditions(clusterClass *clusterv1.ClusterClass, outdatedRefs map[*corev1.ObjectReference]*corev1.ObjectReference) {
if len(outdatedRefs) > 0 {
var msg []string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,16 +140,16 @@ func assertStatusVariables(actualClusterClass *clusterv1.ClusterClass) error {
continue
}
found = true
if statusVar.DefintionsConflict {
return errors.Errorf("ClusterClass status %s variable RequiresNamespace does not match. Expected %v , got %v", specVar.Name, false, statusVar.DefintionsConflict)
if !statusVar.DefintionsConflict {
return errors.Errorf("ClusterClass status %s variable DefintionsConflict does not match. Expected %v , got %v", specVar.Name, true, statusVar.DefintionsConflict)
}
if len(statusVar.Definitions) != 1 {
return errors.Errorf("ClusterClass status has multiple definitions for variable %s. Expected a single definition", specVar.Name)
}
// For this test assume there is only one status variable definition, and that it should match the spec.
statusVarDefinition := statusVar.Definitions[0]
if statusVarDefinition.From != clusterv1.VariableDefinitionFromInline {
return errors.Errorf("ClusterClass status variable %s namespace field does not match. Expected %s. Got %s", statusVar.Name, clusterv1.VariableDefinitionFromInline, statusVarDefinition.From)
return errors.Errorf("ClusterClass status variable %s from field does not match. Expected %s. Got %s", statusVar.Name, clusterv1.VariableDefinitionFromInline, statusVarDefinition.From)
}
if specVar.Required != statusVarDefinition.Required {
return errors.Errorf("ClusterClass status variable %s required field does not match. Expecte %v. Got %v", specVar.Name, statusVarDefinition.Required, statusVarDefinition.Required)
Expand All @@ -159,7 +159,7 @@ func assertStatusVariables(actualClusterClass *clusterv1.ClusterClass) error {
}
}
if !found {
return errors.Errorf("ClusterClass does not define variable %s", specVar.Name)
return errors.Errorf("ClusterClass does not have status for variable %s", specVar.Name)
}
}
return nil
Expand Down

0 comments on commit f8f6213

Please sign in to comment.