memcache operator template syntax highlighting ( source: https://github.com/operator-framework/operator-sdk/blob/master/example/memcached-operator/memcached_controller.go.tmpl )
package memcached | |
import ( | |
"context" | |
"reflect" | |
cachev1alpha1 "github.com/example-inc/memcached-operator/pkg/apis/cache/v1alpha1" | |
appsv1 "k8s.io/api/apps/v1" | |
corev1 "k8s.io/api/core/v1" | |
"k8s.io/apimachinery/pkg/api/errors" | |
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | |
"k8s.io/apimachinery/pkg/labels" | |
"k8s.io/apimachinery/pkg/runtime" | |
"k8s.io/apimachinery/pkg/types" | |
"sigs.k8s.io/controller-runtime/pkg/client" | |
"sigs.k8s.io/controller-runtime/pkg/controller" | |
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" | |
"sigs.k8s.io/controller-runtime/pkg/handler" | |
"sigs.k8s.io/controller-runtime/pkg/manager" | |
"sigs.k8s.io/controller-runtime/pkg/reconcile" | |
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" | |
"sigs.k8s.io/controller-runtime/pkg/source" | |
) | |
var log = logf.Log.WithName("controller_memcached") | |
/** | |
* USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller | |
* business logic. Delete these comments after modifying this file.* | |
*/ | |
// Add creates a new Memcached Controller and adds it to the Manager. The Manager will set fields on the Controller | |
// and Start it when the Manager is Started. | |
func Add(mgr manager.Manager) error { | |
return add(mgr, newReconciler(mgr)) | |
} | |
// newReconciler returns a new reconcile.Reconciler | |
func newReconciler(mgr manager.Manager) reconcile.Reconciler { | |
return &ReconcileMemcached{client: mgr.GetClient(), scheme: mgr.GetScheme()} | |
} | |
// add adds a new Controller to mgr with r as the reconcile.Reconciler | |
func add(mgr manager.Manager, r reconcile.Reconciler) error { | |
// Create a new controller | |
c, err := controller.New("memcached-controller", mgr, controller.Options{Reconciler: r}) | |
if err != nil { | |
return err | |
} | |
// Watch for changes to primary resource Memcached | |
err = c.Watch(&source.Kind{Type: &cachev1alpha1.Memcached{}}, &handler.EnqueueRequestForObject{}) | |
if err != nil { | |
return err | |
} | |
// TODO(user): Modify this to be the types you create that are owned by the primary resource | |
// Watch for changes to secondary resource Pods and requeue the owner Memcached | |
err = c.Watch(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForOwner{ | |
IsController: true, | |
OwnerType: &cachev1alpha1.Memcached{}, | |
}) | |
if err != nil { | |
return err | |
} | |
return nil | |
} | |
var _ reconcile.Reconciler = &ReconcileMemcached{} | |
// ReconcileMemcached reconciles a Memcached object | |
type ReconcileMemcached struct { | |
// TODO: Clarify the split client | |
// This client, initialized using mgr.Client() above, is a split client | |
// that reads objects from the cache and writes to the apiserver | |
client client.Client | |
scheme *runtime.Scheme | |
} | |
// Reconcile reads that state of the cluster for a Memcached object and makes changes based on the state read | |
// and what is in the Memcached.Spec | |
// TODO(user): Modify this Reconcile function to implement your Controller logic. This example creates | |
// a Memcached Deployment for each Memcached CR | |
// Note: | |
// The Controller will requeue the Request to be processed again if the returned error is non-nil or | |
// Result.Requeue is true, otherwise upon completion it will remove the work from the queue. | |
func (r *ReconcileMemcached) Reconcile(request reconcile.Request) (reconcile.Result, error) { | |
reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) | |
reqLogger.Info("Reconciling Memcached") | |
// Fetch the Memcached instance | |
memcached := &cachev1alpha1.Memcached{} | |
err := r.client.Get(context.TODO(), request.NamespacedName, memcached) | |
if err != nil { | |
if errors.IsNotFound(err) { | |
// Request object not found, could have been deleted after reconcile request. | |
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. | |
// Return and don't requeue | |
reqLogger.Info("Memcached resource not found. Ignoring since object must be deleted") | |
return reconcile.Result{}, nil | |
} | |
// Error reading the object - requeue the request. | |
reqLogger.Error(err, "failed to get Memcached") | |
return reconcile.Result{}, err | |
} | |
// Check if the deployment already exists, if not create a new one | |
found := &appsv1.Deployment{} | |
err = r.client.Get(context.TODO(), types.NamespacedName{Name: memcached.Name, Namespace: memcached.Namespace}, found) | |
if err != nil && errors.IsNotFound(err) { | |
// Define a new deployment | |
dep := r.deploymentForMemcached(memcached) | |
reqLogger.Info("Creating a new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) | |
err = r.client.Create(context.TODO(), dep) | |
if err != nil { | |
reqLogger.Error(err, "failed to create new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) | |
return reconcile.Result{}, err | |
} | |
// Deployment created successfully - return and requeue | |
return reconcile.Result{Requeue: true}, nil | |
} else if err != nil { | |
reqLogger.Error(err, "failed to get Deployment") | |
return reconcile.Result{}, err | |
} | |
// Ensure the deployment size is the same as the spec | |
size := memcached.Spec.Size | |
if *found.Spec.Replicas != size { | |
found.Spec.Replicas = &size | |
err = r.client.Update(context.TODO(), found) | |
if err != nil { | |
reqLogger.Error(err, "failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name) | |
return reconcile.Result{}, err | |
} | |
// Spec updated - return and requeue | |
return reconcile.Result{Requeue: true}, nil | |
} | |
// Update the Memcached status with the pod names | |
// List the pods for this memcached's deployment | |
podList := &corev1.PodList{} | |
labelSelector := labels.SelectorFromSet(labelsForMemcached(memcached.Name)) | |
listOps := &client.ListOptions{Namespace: memcached.Namespace, LabelSelector: labelSelector} | |
err = r.client.List(context.TODO(), listOps, podList) | |
if err != nil { | |
reqLogger.Error(err, "failed to list pods", "Memcached.Namespace", memcached.Namespace, "Memcached.Name", memcached.Name) | |
return reconcile.Result{}, err | |
} | |
podNames := getPodNames(podList.Items) | |
// Update status.Nodes if needed | |
if !reflect.DeepEqual(podNames, memcached.Status.Nodes) { | |
memcached.Status.Nodes = podNames | |
err := r.client.Update(context.TODO(), memcached) | |
if err != nil { | |
reqLogger.Error(err, "failed to update Memcached status") | |
return reconcile.Result{}, err | |
} | |
} | |
return reconcile.Result{}, nil | |
} | |
// deploymentForMemcached returns a memcached Deployment object | |
func (r *ReconcileMemcached) deploymentForMemcached(m *cachev1alpha1.Memcached) *appsv1.Deployment { | |
ls := labelsForMemcached(m.Name) | |
replicas := m.Spec.Size | |
dep := &appsv1.Deployment{ | |
TypeMeta: metav1.TypeMeta{ | |
APIVersion: "apps/v1", | |
Kind: "Deployment", | |
}, | |
ObjectMeta: metav1.ObjectMeta{ | |
Name: m.Name, | |
Namespace: m.Namespace, | |
}, | |
Spec: appsv1.DeploymentSpec{ | |
Replicas: &replicas, | |
Selector: &metav1.LabelSelector{ | |
MatchLabels: ls, | |
}, | |
Template: corev1.PodTemplateSpec{ | |
ObjectMeta: metav1.ObjectMeta{ | |
Labels: ls, | |
}, | |
Spec: corev1.PodSpec{ | |
Containers: []corev1.Container{{ | |
Image: "memcached:1.4.36-alpine", | |
Name: "memcached", | |
Command: []string{"memcached", "-m=64", "-o", "modern", "-v"}, | |
Ports: []corev1.ContainerPort{{ | |
ContainerPort: 11211, | |
Name: "memcached", | |
}}, | |
}}, | |
}, | |
}, | |
}, | |
} | |
// Set Memcached instance as the owner and controller | |
controllerutil.SetControllerReference(m, dep, r.scheme) | |
return dep | |
} | |
// labelsForMemcached returns the labels for selecting the resources | |
// belonging to the given memcached CR name. | |
func labelsForMemcached(name string) map[string]string { | |
return map[string]string{"app": "memcached", "memcached_cr": name} | |
} | |
// getPodNames returns the pod names of the array of pods passed in | |
func getPodNames(pods []corev1.Pod) []string { | |
var podNames []string | |
for _, pod := range pods { | |
podNames = append(podNames, pod.Name) | |
} | |
return podNames | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment