Skip to content

Commit

Permalink
chore: refactor to use ClusterClassClusterUpgrader type
Browse files Browse the repository at this point in the history
  • Loading branch information
dkoshkin committed Sep 17, 2023
1 parent 4205b1c commit 58883d4
Show file tree
Hide file tree
Showing 26 changed files with 1,277 additions and 280 deletions.
13 changes: 13 additions & 0 deletions PROJECT
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,17 @@ resources:
kind: MachineImageTemplate
path: github.com/dkoshkin/kubernetes-upgrader/api/v1alpha1
version: v1alpha1
- api:
crdVersion: v1
namespaced: true
controller: true
domain: dimitrikoshkin.com
group: kubernetesupgraded
kind: ClusterClassClusterUpgrader
path: github.com/dkoshkin/kubernetes-upgrader/api/v1alpha1
version: v1alpha1
webhooks:
defaulting: true
validation: true
webhookVersion: v1
version: "3"
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,47 @@ You’ll need a Kubernetes cluster to run against.
Follow [CAPI's Quickstart documentation](https://cluster-api.sigs.k8s.io/user/quick-start.html) to create a cluster using [KIND](https://sigs.k8s.io/kind) and the Docker provider.
Use Kubernetes version `v1.26.3` if you are planning on using the sample config.
### Creating workload cluster
1. Create a KIND bootstrap cluster:
```sh
kind create cluster --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
ipFamily: dual
nodes:
- role: control-plane
extraMounts:
- hostPath: /var/run/docker.sock
containerPath: /var/run/docker.sock
EOF
```
1. Deploy CAPI providers:
```sh
export CLUSTER_TOPOLOGY=true
clusterctl init --infrastructure docker
```
1. Create a workload cluster:
```sh
# The list of service CIDR, default ["10.128.0.0/12"]
export SERVICE_CIDR=["10.96.0.0/12"]
# The list of pod CIDR, default ["192.168.0.0/16"]
export POD_CIDR=["192.168.0.0/16"]
# The service domain, default "cluster.local"
export SERVICE_DOMAIN="k8s.test"
# Create the cluster
clusterctl generate cluster capi-quickstart --flavor development \
--kubernetes-version v1.26.3 \
--control-plane-machine-count=1 \
--worker-machine-count=1 | kubectl apply -f -
```
### Running on the cluster
1. Generated the components manifests and build the image:
Expand Down
125 changes: 125 additions & 0 deletions api/v1alpha1/clusterclassclusterupgrader_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright 2023 Dimitri Koshkin. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

/*
Copyright 2023.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
"context"
"fmt"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.

// ClusterClassClusterUpgraderSpec defines the desired state of ClusterClassClusterUpgrader.
type ClusterClassClusterUpgraderSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file

// ClusterName is the name of the cluster to upgrade.
// +required
// +kubebuilder:validation:MinLength=1
ClusterName string `json:"clusterName"`

// TopologyVariable is the name of the topology variable to set with the MachineImage's ID.
// +optional
TopologyVariable *string `json:"topologyVariable,omitempty"`

// PlanRef is a reference to a Plan object.
// +required
PlanRef corev1.ObjectReference `json:"planRef"`
}

func (s *ClusterClassClusterUpgraderSpec) GetPlan(
ctx context.Context,
reader client.Reader,
) (*Plan, error) {
plan := &Plan{}
key := client.ObjectKey{
Name: s.PlanRef.Name,
Namespace: s.PlanRef.Namespace,
}
err := reader.Get(ctx, key, plan)
if err != nil {
return nil, fmt.Errorf("error getting Plan for ClusterClassClusterUpgrader: %w", err)
}

return plan, nil
}

// ClusterClassClusterUpgraderStatus defines the observed state of ClusterClassClusterUpgrader.
type ClusterClassClusterUpgraderStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file

// LatestFoundVersion is the highest version within the range that was set on the Cluster.
// +optional
LatestSetVersion string `json:"latestSetVersion,omitempty"`
}

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
//+kubebuilder:resource:categories=cluster-api
//+kubebuilder:printcolumn:name="Cluster Name",type="string",JSONPath=`.spec.clusterName`
//+kubebuilder:printcolumn:name="Latest Set Version",type="string",JSONPath=`.status.latestSetVersion`

// ClusterClassClusterUpgrader is the Schema for the clusterclassclusterupgraders API.
type ClusterClassClusterUpgrader struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec ClusterClassClusterUpgraderSpec `json:"spec,omitempty"`
Status ClusterClassClusterUpgraderStatus `json:"status,omitempty"`
}

func (r *ClusterClassClusterUpgrader) GetCluster(
ctx context.Context,
reader client.Reader,
) (*clusterv1.Cluster, error) {
cluster := &clusterv1.Cluster{}
key := client.ObjectKey{
Name: r.Spec.ClusterName,
Namespace: r.GetNamespace(),
}
err := reader.Get(ctx, key, cluster)
if err != nil {
return nil, fmt.Errorf("error getting Cluster for ClusterClassClusterUpgrader: %w", err)
}

return cluster, nil
}

//+kubebuilder:object:root=true

// ClusterClassClusterUpgraderList contains a list of ClusterClassClusterUpgrader.
type ClusterClassClusterUpgraderList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []ClusterClassClusterUpgrader `json:"items"`
}

//nolint:gochecknoinits // This is required for the Kubebuilder tooling.
func init() {
SchemeBuilder.Register(&ClusterClassClusterUpgrader{}, &ClusterClassClusterUpgraderList{})
}
101 changes: 101 additions & 0 deletions api/v1alpha1/clusterclassclusterupgrader_webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright 2023 Dimitri Koshkin. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

/*
Copyright 2023.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
"fmt"

"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

// log is for logging in this package.
//
//nolint:gochecknoglobals // This came from the Kubebuilder project.
var clusterclassclusterupgraderlog = logf.Log.WithName("clusterclassclusterupgrader-resource")

func (r *ClusterClassClusterUpgrader) SetupWebhookWithManager(mgr ctrl.Manager) error {
//nolint:wrapcheck // This came from the Kubebuilder project.
return ctrl.NewWebhookManagedBy(mgr).
For(r).
Complete()
}

// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
//nolint:lll // This is generated code.
//+kubebuilder:webhook:path=/mutate-kubernetesupgraded-dimitrikoshkin-com-v1alpha1-clusterclassclusterupgrader,mutating=true,failurePolicy=fail,sideEffects=None,groups=kubernetesupgraded.dimitrikoshkin.com,resources=clusterclassclusterupgraders,verbs=create;update,versions=v1alpha1,name=mclusterclassclusterupgrader.kb.io,admissionReviewVersions=v1

var _ webhook.Defaulter = &ClusterClassClusterUpgrader{}

// Default implements webhook.Defaulter so a webhook will be registered for the type.
func (r *ClusterClassClusterUpgrader) Default() {
clusterclassclusterupgraderlog.Info("default", "name", r.Name)

machineimagesyncerlog.Info("default", "name", r.Name)

if r.Spec.PlanRef.Namespace == "" {
r.Spec.PlanRef.Namespace = r.Namespace
}
}

// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
//nolint:lll // This is generated code.
//+kubebuilder:webhook:path=/validate-kubernetesupgraded-dimitrikoshkin-com-v1alpha1-clusterclassclusterupgrader,mutating=false,failurePolicy=fail,sideEffects=None,groups=kubernetesupgraded.dimitrikoshkin.com,resources=clusterclassclusterupgraders,verbs=create;update,versions=v1alpha1,name=vclusterclassclusterupgrader.kb.io,admissionReviewVersions=v1

var _ webhook.Validator = &ClusterClassClusterUpgrader{}

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type.
func (r *ClusterClassClusterUpgrader) ValidateCreate() (admission.Warnings, error) {
clusterclassclusterupgraderlog.Info("validate create", "name", r.Name)

if r.Spec.PlanRef.Name == "" {
//nolint:goerr113 // This is a user facing error.
return nil,
fmt.Errorf("spec.planRef.name must be set")
}

if r.Namespace != r.Spec.PlanRef.Namespace {
//nolint:goerr113 // This is a user facing error.
return nil,
fmt.Errorf("spec.planRef.namespace must be in the same namespace")
}
return nil, nil
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type.
func (r *ClusterClassClusterUpgrader) ValidateUpdate(
old runtime.Object,
) (admission.Warnings, error) {
clusterclassclusterupgraderlog.Info("validate update", "name", r.Name)

// TODO(user): fill in your validation logic upon object update.
return nil, nil
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type.
func (r *ClusterClassClusterUpgrader) ValidateDelete() (admission.Warnings, error) {
clusterclassclusterupgraderlog.Info("validate delete", "name", r.Name)

// TODO(user): fill in your validation logic upon object deletion.
return nil, nil
}
4 changes: 2 additions & 2 deletions api/v1alpha1/machineimagesyncer_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,14 @@ type MachineImageSyncerSpec struct {

func (s *MachineImageSyncerSpec) GetMachineImageTemplate(
ctx context.Context,
r client.Reader,
reader client.Reader,
) (*MachineImageTemplate, error) {
machineImageTemplate := &MachineImageTemplate{}
key := client.ObjectKey{
Name: s.MachineImageTemplateRef.Name,
Namespace: s.MachineImageTemplateRef.Namespace,
}
err := r.Get(ctx, key, machineImageTemplate)
err := reader.Get(ctx, key, machineImageTemplate)
if err != nil {
return nil, fmt.Errorf("error getting MachineImageTemplate for MachineImageSyncer: %w", err)
}
Expand Down
34 changes: 13 additions & 21 deletions api/v1alpha1/plan_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,6 @@ type PlanSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file

// ClusterName is the name of the cluster to upgrade.
// +required
// +kubebuilder:validation:MinLength=1
ClusterName string `json:"clusterName"`

// VersionRange gives a semver range of the Kubernetes version.
// The cluster will be upgraded to the highest version within the range.
// +required
Expand All @@ -47,37 +42,34 @@ type PlanSpec struct {
// Defaults to the empty LabelSelector, which matches all objects.
// +optional
MachineImageSelector *metav1.LabelSelector `json:"machineImageSelector,omitempty"`

// TopologyVariable is the name of the topology variable to set with the MachineImage's ID.
// +optional
TopologyVariable *string `json:"topologyVariable,omitempty"`
}

// PlanStatus defines the observed state of Plan.
type PlanStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file

// LatestFoundVersion is the highest version within the range that was found.
// +optional
LatestFoundVersion string `json:"latestFoundVersion,omitempty"`

// LatestFoundVersion is the highest version within the range that was set on the Cluster.
// +optional
LatestSetVersion string `json:"latestSetVersion,omitempty"`
// MachineImageDetails holds the details for a MachineImage with the highest version within the range.
MachineImageDetails *MachineImageDetails `json:"machineImageDetails"`

// MachineImageRef is a reference to the MachineImage that was applied to the cluster upgrade.
// +optional
MachineImageRef *corev1.ObjectReference `json:"machineImageRef,omitempty"`
MachineImageRef *corev1.ObjectReference `json:"machineImageRef"`
}

type MachineImageDetails struct {
// Version is the version of the Kubernetes image to build
Version string `json:"version,omitempty"`

// ID is the unique name or another identifier of the image that was built.
ID string `json:"id"`
}

//nolint:lll // This is generated code.
//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
//+kubebuilder:resource:categories=cluster-api
//+kubebuilder:printcolumn:name="Cluster Name",type="string",JSONPath=`.spec.clusterName`
//+kubebuilder:printcolumn:name="Version Range",type="string",JSONPath=`.spec.versionRange`
//+kubebuilder:printcolumn:name="Latest Found Version",type="string",JSONPath=`.status.latestFoundVersion`
//+kubebuilder:printcolumn:name="Latest Set Version",type="string",JSONPath=`.status.latestSetVersion`
//+kubebuilder:printcolumn:name="Latest Version",type="string",JSONPath=`.status.machineImageDetails.version`

// Plan is the Schema for the plans API.
type Plan struct {
Expand Down
3 changes: 3 additions & 0 deletions api/v1alpha1/webhook_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ var _ = BeforeSuite(func() {
err = (&MachineImageSyncer{}).SetupWebhookWithManager(mgr)
Expect(err).NotTo(HaveOccurred())

err = (&ClusterClassClusterUpgrader{}).SetupWebhookWithManager(mgr)
Expect(err).NotTo(HaveOccurred())

//+kubebuilder:scaffold:webhook

go func() {
Expand Down
Loading

0 comments on commit 58883d4

Please sign in to comment.