Skip to content

Commit

Permalink
awsmanagedcontrolplane: add status.version, implement version populat…
Browse files Browse the repository at this point in the history
…ion for CAPI's MachineSetPreflightCheck

In the latest version, CAPI has turned on by default the MachineSetPreflightCheck feature,
which now enforces the managed ControPlane object spec and status to have a new Version field (more details on the API change https://cluster-api.sigs.k8s.io/developer/providers/contracts/control-plane#controlplane-version).

See: kubernetes-sigs#5225

Also more conversation details with the core CAPI maintainers: https://kubernetes.slack.com/archives/CD6U2V71N/p1739783013734149
  • Loading branch information
damdo committed Feb 28, 2025
1 parent e84e075 commit 47c0abb
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4115,6 +4115,11 @@ spec:
Ready denotes that the AWSManagedControlPlane API Server is ready to
receive requests and that the VPC infra is ready.
type: boolean
version:
description: |-
Version represents the minimum Kubernetes version for the control plane machines
in the cluster.
type: string
required:
- ready
type: object
Expand Down
7 changes: 7 additions & 0 deletions controlplane/eks/api/v1beta1/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
infrav1beta1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta1"
infrav1beta2 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
ekscontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/eks/api/v1beta2"
v1beta2 "sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/eks/api/v1beta2"
utilconversion "sigs.k8s.io/cluster-api/util/conversion"
"sigs.k8s.io/controller-runtime/pkg/conversion"
)
Expand All @@ -41,6 +42,7 @@ func (r *AWSManagedControlPlane) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.VpcCni.Disable = r.Spec.DisableVPCCNI
dst.Spec.Partition = restored.Spec.Partition
dst.Spec.RestrictPrivateSubnets = restored.Spec.RestrictPrivateSubnets
dst.Status.Version = restored.Status.Version

return nil
}
Expand Down Expand Up @@ -117,3 +119,8 @@ func Convert_v1beta2_VpcCni_To_v1beta1_VpcCni(in *ekscontrolplanev1.VpcCni, out
func Convert_v1beta2_AWSManagedControlPlaneSpec_To_v1beta1_AWSManagedControlPlaneSpec(in *ekscontrolplanev1.AWSManagedControlPlaneSpec, out *AWSManagedControlPlaneSpec, scope apiconversion.Scope) error {
return autoConvert_v1beta2_AWSManagedControlPlaneSpec_To_v1beta1_AWSManagedControlPlaneSpec(in, out, scope)
}

// Convert_v1beta2_AWSManagedControlPlaneStatus_To_v1beta1_AWSManagedControlPlaneStatus is an autogenerated conversion function.
func Convert_v1beta2_AWSManagedControlPlaneStatus_To_v1beta1_AWSManagedControlPlaneStatus(in *v1beta2.AWSManagedControlPlaneStatus, out *AWSManagedControlPlaneStatus, s apiconversion.Scope) error {
return autoConvert_v1beta2_AWSManagedControlPlaneStatus_To_v1beta1_AWSManagedControlPlaneStatus(in, out, s)
}
16 changes: 6 additions & 10 deletions controlplane/eks/api/v1beta1/zz_generated.conversion.go

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

4 changes: 4 additions & 0 deletions controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,10 @@ type AWSManagedControlPlaneStatus struct {
// associated identity provider
// +optional
IdentityProviderStatus IdentityProviderStatus `json:"identityProviderStatus,omitempty"`
// Version represents the minimum Kubernetes version for the control plane machines
// in the cluster.
// +optional
Version *string `json:"version,omitempty"`
}

// +kubebuilder:object:root=true
Expand Down
5 changes: 5 additions & 0 deletions controlplane/eks/api/v1beta2/zz_generated.deepcopy.go

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

51 changes: 51 additions & 0 deletions pkg/cloud/services/eks/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ import (
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/eks"
"github.com/blang/semver"
"github.com/go-logr/logr"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/version"
"k8s.io/klog/v2"
"k8s.io/utils/ptr"

infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
ekscontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/eks/api/v1beta2"
Expand Down Expand Up @@ -140,7 +142,54 @@ func (s *Service) reconcileCluster(ctx context.Context) error {
return nil
}

// computeCurrentStatusVersion returns the computed current EKS cluster kubernetes version.
// The computation has awareness of the fact that EKS clusters only return a major.minor kubernetes version,
// and returns a compatible version for te status according to the one the user specified in the spec.
func computeCurrentStatusVersion(specV *string, clusterV *string) *string {
specVersion := ""
if specV != nil {
specVersion = *specV
}

clusterVersion := ""
if clusterV != nil {
clusterVersion = *clusterV
}

// Ignore parsing errors as these are already validated by the kubebuilder validation and the AWS API.
// Also specVersion might not be specified in the spec.Version for AWSManagedControlPlane, this results in a "0.0.0" version.
// Also clusterVersion might not yet be returned by the AWS EKS API, as the cluster might still be initializing, this results in a "0.0.0" version.
specSemverVersion, _ := semver.ParseTolerant(specVersion)
currentSemverVersion, _ := semver.ParseTolerant(clusterVersion)

// If AWS EKS API is not returning a version, set the status.Version to empty string.
if currentSemverVersion.String() == "0.0.0" {
return ptr.To("")
}

if currentSemverVersion.Major == specSemverVersion.Major &&
currentSemverVersion.Minor == specSemverVersion.Minor &&
specSemverVersion.Patch != 0 {
// Treat this case differently as we want it to exactly match the spec.Version,
// including its Patch, in the status.Version.
currentSemverVersion.Patch = specSemverVersion.Patch

return ptr.To(currentSemverVersion.String())
}

// For all the other cases it doesn't matter to have a patch version, as EKS ignores it internally.
// So set the current cluster.Version (this always is a major.minor version format (e.g. "1.31")) in the status.Version.
// Even in the event where in the spec.Version a zero patch version is specified (e.g. "1.31.0"),
// the call to semver.ParseTolerant on the consumer side
// will make sure the version with and without the trailing zero actually result in a match.
return clusterV
}

func (s *Service) setStatus(cluster *eks.Cluster) error {
// Set the current Kubernetes control plane version in the status.
s.scope.ControlPlane.Status.Version = computeCurrentStatusVersion(s.scope.ControlPlane.Spec.Version, cluster.Version)

// Set the current cluster status in the control plane status.
switch *cluster.Status {
case eks.ClusterStatusDeleting:
s.scope.ControlPlane.Status.Ready = false
Expand Down Expand Up @@ -168,6 +217,8 @@ func (s *Service) setStatus(cluster *eks.Cluster) error {
default:
return errors.Errorf("unexpected EKS cluster status %s", *cluster.Status)
}

// Persists the control plane configuration and status.
if err := s.scope.PatchObject(); err != nil {
return errors.Wrap(err, "failed to update control plane")
}
Expand Down

0 comments on commit 47c0abb

Please sign in to comment.