Skip to content

Commit

Permalink
Merge pull request #4303 from JoelSpeed/external-cluster-infra
Browse files Browse the repository at this point in the history
✨  Add externally managed annotation and predicate
  • Loading branch information
k8s-ci-robot authored May 4, 2021
2 parents 6424396 + 44a4d42 commit 3065a92
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 1 deletion.
8 changes: 8 additions & 0 deletions api/v1alpha4/common_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ const (

// InterruptibleLabel is the label used to mark the nodes that run on interruptible instances.
InterruptibleLabel = "cluster.x-k8s.io/interruptible"

// ManagedByAnnotation is an annotation that can be applied to InfraCluster resources to signify that
// some external system is managing the cluster infrastructure.
//
// Provider InfraCluster controllers will ignore resources with this annotation.
// An external controller must fulfill the contract of the InfraCluster resource.
// External infrastructure providers should ensure that the annotation, once set, cannot be removed.
ManagedByAnnotation = "cluster.x-k8s.io/managed-by"
)

// MachineAddressType describes a valid MachineAddress type.
Expand Down
2 changes: 2 additions & 0 deletions docs/book/src/developer/providers/cluster-infrastructure.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ The following diagram shows the typical logic for a cluster infrastructure provi

### Normal resource

1. If the resource is externally managed, exit the reconciliation
1. The `ResourceIsNotExternallyManaged` predicate can be used to prevent reconciling externally managed resources
1. If the resource does not have a `Cluster` owner, exit the reconciliation
1. The Cluster API `Cluster` reconciler populates this based on the value in the `Cluster`'s `spec.infrastructureRef`
field.
Expand Down
19 changes: 18 additions & 1 deletion docs/book/src/developer/providers/v1alpha3-to-v1alpha4.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Minimum Go version

- The Go version used by Cluster API is now Go 1.16+
- In case cloudbuild is used to push images, please upgrade to `gcr.io/k8s-testimages/gcb-docker-gcloud:v20210331-c732583`
- In case cloudbuild is used to push images, please upgrade to `gcr.io/k8s-testimages/gcb-docker-gcloud:v20210331-c732583`
in the cloudbuild YAML files.

## Controller Runtime version
Expand Down Expand Up @@ -251,4 +251,21 @@ with `cert-manager.io/v1`
`MachineDeployment.Spec.Strategy.RollingUpdate.MaxSurge`, `MachineDeployment.Spec.Strategy.RollingUpdate.MaxUnavailable` and `MachineHealthCheck.Spec.MaxUnhealthy` would have previously taken a String value with an integer character in it e.g "3" as a valid input and process it as a percentage value.
Only String values like "3%" or Int values e.g 3 are valid input values now. A string not matching the percentage format will fail, e.g "3".

## Required change to support externally managed infrastructure.

- A new annotation `cluster.x-k8s.io/managed-by` has been introduced that allows cluster infrastructure to be managed
externally.
- When this annotation is added to an `InfraCluster` resource, the controller for these resources should not reconcile
the resource.
- The `ResourceIsNotExternallyManaged` predicate is a useful helper to check for the annotation and the filter the resource easily:
```go
c, err := ctrl.NewControllerManagedBy(mgr).
For(&providerv1.InfraCluster{}).
Watches(...).
WithOptions(options).
WithEventFilter(predicates.ResourceIsNotExternallyManaged(ctrl.LoggerFrom(ctx))).
Build(r)
if err != nil {
return errors.Wrap(err, "failed setting up with a controller manager")
}
```
4 changes: 4 additions & 0 deletions docs/book/src/images/cluster-infra-provider.plantuml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ start

:New/Updated/Deleted resource;

if (Is Externally Managed?) then (yes)
stop
else (no)
endif
if (Deleted?) then (yes)
if (Has cluster owner?) then (yes)
:Reconcile deletion;
Expand Down
Binary file modified docs/book/src/images/cluster-infra-provider.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions util/annotations/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ func IsPaused(cluster *clusterv1.Cluster, o metav1.Object) bool {
return HasPausedAnnotation(o)
}

// IsExternallyManaged returns true if the object has the `managed-by` annotation.
func IsExternallyManaged(o metav1.Object) bool {
return hasAnnotation(o, clusterv1.ManagedByAnnotation)
}

// HasPausedAnnotation returns true if the object has the `paused` annotation.
func HasPausedAnnotation(o metav1.Object) bool {
return hasAnnotation(o, clusterv1.PausedAnnotation)
Expand Down
32 changes: 32 additions & 0 deletions util/predicates/generic_predicates.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,35 @@ func processIfLabelMatch(logger logr.Logger, obj client.Object, labelValue strin
log.V(4).Info("Resource does not match label, will not attempt to map resource")
return false
}

// ResourceIsNotExternallyManaged returns a predicate that returns true only if the resource does not contain
// the externally managed annotation.
// This implements a requirement for InfraCluster providers to be able to ignore externally managed
// cluster infrastructure.
func ResourceIsNotExternallyManaged(logger logr.Logger) predicate.Funcs {
return predicate.Funcs{
UpdateFunc: func(e event.UpdateEvent) bool {
return processIfNotExternallyManaged(logger.WithValues("predicate", "updateEvent"), e.ObjectNew)
},
CreateFunc: func(e event.CreateEvent) bool {
return processIfNotExternallyManaged(logger.WithValues("predicate", "createEvent"), e.Object)
},
DeleteFunc: func(e event.DeleteEvent) bool {
return processIfNotExternallyManaged(logger.WithValues("predicate", "deleteEvent"), e.Object)
},
GenericFunc: func(e event.GenericEvent) bool {
return processIfNotExternallyManaged(logger.WithValues("predicate", "genericEvent"), e.Object)
},
}
}

func processIfNotExternallyManaged(logger logr.Logger, obj client.Object) bool {
kind := strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind)
log := logger.WithValues("namespace", obj.GetNamespace(), kind, obj.GetName())
if annotations.IsExternallyManaged(obj) {
log.V(4).Info("Resource is externally managed, will not attempt to map resource")
return false
}
log.V(4).Info("Resource is managed, will attempt to map resource")
return true
}

0 comments on commit 3065a92

Please sign in to comment.