This repository has been archived by the owner on Nov 18, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 132
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add script to scaffold go operator (#145)
This commit adds a script which can be used to generate a runnable memcached operator sample with webhook implementation. Motivation: Automate the process of updating SDK samples.
- Loading branch information
1 parent
1bf226e
commit 90e8d09
Showing
100 changed files
with
1,409 additions
and
3,286 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,329 @@ | ||
#!/usr/bin/env bash | ||
|
||
NO_COLOR=${NO_COLOR:-""} | ||
if [ -z "$NO_COLOR" ]; then | ||
header_color=$'\e[1;33m' | ||
reset_color=$'\e[0m' | ||
else | ||
header_color='' | ||
reset_color='' | ||
fi | ||
|
||
operIMG=quay.io/example/memcached-operator:v0.0.1 | ||
bundleIMG=quay.io/example-bundle/memcached-operator:v0.0.1 | ||
|
||
MEMCACHE_CONTROLLER_OLD_RECONCILER='kubebuilder' | ||
MEMCACHE_CONTROLLER_NEW_RECONCILER='// +kubebuilder:rbac:groups=cache.example.com,resources=memcacheds,verbs=get;list;watch;create;update;patch;delete \ | ||
// +kubebuilder:rbac:groups=cache.example.com,resources=memcacheds/status,verbs=get;update;patch \ | ||
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete \ | ||
// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list; \ | ||
\nfunc (r *MemcachedReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { \ | ||
ctx := context.Background() \ | ||
log := r.Log.WithValues("memcached", req.NamespacedName) \ | ||
// Fetch the Memcached instance \ | ||
memcached := &cachev1alpha1.Memcached{} \ | ||
err := r.Get(ctx, req.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 \ | ||
log.Info("Memcached resource not found. Ignoring since object must be deleted") \ | ||
return ctrl.Result{}, nil \ | ||
} \ | ||
// Error reading the object - requeue the request. \ | ||
log.Error(err, "Failed to get Memcached") \ | ||
return ctrl.Result{}, err \ | ||
} \ | ||
\n\t// Check if the deployment already exists, if not create a new one \ | ||
found := &appsv1.Deployment{} \ | ||
err = r.Get(ctx, types.NamespacedName{Name: memcached.Name, Namespace: memcached.Namespace}, found) \ | ||
if err != nil && errors.IsNotFound(err) { \ | ||
// Define a new deployment \ | ||
dep := r.deploymentForMemcached(memcached) \ | ||
log.Info("Creating a new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) \ | ||
err = r.Create(ctx, dep) \ | ||
if err != nil { \ | ||
log.Error(err, "Failed to create new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) \ | ||
return ctrl.Result{}, err \ | ||
} \ | ||
// Deployment created successfully - return and requeue \ | ||
return ctrl.Result{Requeue: true}, nil \ | ||
} else if err != nil { \ | ||
log.Error(err, "Failed to get Deployment") \ | ||
return ctrl.Result{}, err \ | ||
} \ | ||
\n\t// 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.Update(ctx, found) \ | ||
if err != nil { \ | ||
log.Error(err, "Failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name) \ | ||
return ctrl.Result{}, err \ | ||
} \ | ||
// Spec updated - return and requeue \ | ||
return ctrl.Result{Requeue: true}, nil \ | ||
} \ | ||
\n\t// Update the Memcached status with the pod names \ | ||
// List the pods for this memcached'\''s deployment \ | ||
podList := &corev1.PodList{} \ | ||
listOpts := []client.ListOption{ \ | ||
client.InNamespace(memcached.Namespace), \ | ||
client.MatchingLabels(labelsForMemcached(memcached.Name)), \ | ||
} \ | ||
if err = r.List(ctx, podList, listOpts...); err != nil { \ | ||
log.Error(err, "Failed to list pods", "Memcached.Namespace", memcached.Namespace, "Memcached.Name", memcached.Name) \ | ||
return ctrl.Result{}, err \ | ||
} \ | ||
podNames := getPodNames(podList.Items) \ | ||
\n// Update status.Nodes if needed \ | ||
if !reflect.DeepEqual(podNames, memcached.Status.Nodes) { \ | ||
memcached.Status.Nodes = podNames \ | ||
err := r.Status().Update(ctx, memcached) \ | ||
if err != nil { \ | ||
log.Error(err, "Failed to update Memcached status") \ | ||
return ctrl.Result{}, err \ | ||
} \ | ||
} \ | ||
\nreturn ctrl.Result{}, nil \ | ||
} \ | ||
\n\n// deploymentForMemcached returns a memcached Deployment object \ | ||
func (r *MemcachedReconciler) deploymentForMemcached(m *cachev1alpha1.Memcached) *appsv1.Deployment { \ | ||
ls := labelsForMemcached(m.Name) \ | ||
replicas := m.Spec.Size \ | ||
\ndep := &appsv1.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 \ | ||
ctrl.SetControllerReference(m, dep, r.Scheme) \ | ||
return dep \ | ||
} \ | ||
\n// 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} \ | ||
} \ | ||
\n// 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 \ | ||
} \ | ||
\nfunc (r *MemcachedReconciler) SetupWithManager(mgr ctrl.Manager) error { \ | ||
return ctrl.NewControllerManagedBy(mgr). \ | ||
For(&cachev1alpha1.Memcached{}). \ | ||
Owns(&appsv1.Deployment{}). \ | ||
Complete(r) \ | ||
}' | ||
|
||
MAKEFILE_UNDEPLOY_TARGET='$a \ | ||
\n# UnDeploy controller from the configured Kubernetes cluster in ~/.kube/config \ | ||
# Note that it was added for we are allowed to uninstall the files. However, it will be present by default in the future \ | ||
# versions. \ | ||
undeploy: \ | ||
$(KUSTOMIZE) build config\/default | kubectl delete -f -' | ||
|
||
MAKEFILE_OLD_TEST_TARGET='go test .\/... -coverprofile cover.out' | ||
|
||
MAKEFILE_NEW_TEST_TARGET='mkdir -p ${ENVTEST_ASSETS_DIR} \ | ||
test -f ${ENVTEST_ASSETS_DIR}\/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}\/setup-envtest.sh https:\/\/raw.githubusercontent.com\/kubernetes-sigs\/controller-runtime\/master\/hack\/setup-envtest.sh \ | ||
source ${ENVTEST_ASSETS_DIR}\/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test .\/... -coverprofile cover.out' | ||
|
||
MAKEFILE_PACKAGE_MANIFEST_TARGET='$a \ | ||
\n \ | ||
# Options for "packagemanifests". \ | ||
ifneq ($(origin CHANNEL), undefined) \ | ||
PKG_CHANNELS := --channel=$(CHANNEL) \ | ||
endif \ | ||
ifeq ($(IS_CHANNEL_DEFAULT), 1) \ | ||
PKG_IS_DEFAULT_CHANNEL := --default-channel \ | ||
endif \ | ||
PKG_MAN_OPTS ?= $(PKG_CHANNELS) $(PKG_IS_DEFAULT_CHANNEL) \ | ||
# Generate package manifests. \ | ||
packagemanifests: kustomize manifests \ | ||
operator-sdk generate kustomize manifests -q --interactive=false \ | ||
cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) \ | ||
$(KUSTOMIZE) build config\/manifests | operator-sdk generate packagemanifests -q --version $(VERSION) $(PKG_MAN_OPTS)' | ||
|
||
function header_text { | ||
echo "$header_color$*$reset_color" | ||
} | ||
|
||
function update_memcached_types() { | ||
FILENAME=api/v1alpha1/memcached_types.go | ||
|
||
sed -i 's@Foo string `json:"foo,omitempty"`@\ | ||
Size int32 `json:"size"`@' $FILENAME | ||
|
||
sed -i '/type MemcachedStatus struct {/ a\ | ||
Nodes []string `json:"nodes"`' $FILENAME | ||
|
||
sed -i '/\/\/ Foo is an example field of Memcached. Edit Memcached_types.go to remove\/update/d' $FILENAME | ||
} | ||
|
||
function update_memcached_controllers() { | ||
FILENAME=controllers/memcached_controller.go | ||
|
||
# Updating imports | ||
sed -i '/context/ a\ | ||
"reflect"' $FILENAME | ||
|
||
sed -i '/github.com\/go-logr\/logr/ a\ | ||
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"' $FILENAME | ||
|
||
sed -i '/apimachinery\/pkg\/runtime/ a\ | ||
"k8s.io/apimachinery/pkg/types"' $FILENAME | ||
|
||
sed -i '/'"$MEMCACHE_CONTROLLER_OLD_RECONCILER"'/,$c\'"$MEMCACHE_CONTROLLER_NEW_RECONCILER" $FILENAME | ||
} | ||
|
||
function update_memcached_webhook() { | ||
FILENAME=api/v1alpha1/memcached_webhook.go | ||
|
||
# Update imports | ||
sed -i '/import (/ a\ | ||
"errors"' $FILENAME | ||
|
||
# Updating validation logic | ||
sed -i 's/\/\/ TODO(user): fill in your defaulting logic./ if r.Spec.Size == 0 { \ | ||
r.Spec.Size = 3 \ | ||
}/' $FILENAME | ||
|
||
sed -i '/return nil/d' $FILENAME | ||
|
||
sed -i 's/\/\/ TODO(user): fill in your validation logic upon object creation./ return validateOdd(r.Spec.Size)/' $FILENAME | ||
|
||
sed -i 's/\/\/ TODO(user): fill in your validation logic upon object update./ return validateOdd(r.Spec.Size)/' $FILENAME | ||
|
||
sed -i 's/\/\/ TODO(user): fill in your validation logic upon object deletion./ return nil/' $FILENAME | ||
|
||
|
||
sed -i -e '$afunc validateOdd(n int32) error { \ | ||
if n%2 == 0 { \ | ||
return errors.New("Cluster size must be an odd number") \ | ||
} \ | ||
return nil \ | ||
}' $FILENAME | ||
} | ||
|
||
function update_default_kustomization() { | ||
FILENAME=config/default/kustomization.yaml | ||
|
||
# Uncommenting lines with [WEBHOOK] prefix in | ||
# config/default/kustomization.yaml to enable Webhooks | ||
sed -i '45,70 s/^#//' $FILENAME | ||
sed -i '21 s/^#//' $FILENAME | ||
sed -i '23 s/^#//' $FILENAME | ||
sed -i '35 s/^#//' $FILENAME | ||
sed -i '40 s/^#//' $FILENAME | ||
} | ||
|
||
function update_cache_v1alpha1_memcached_sample() { | ||
sed -i 's/foo: bar/size: 3/' config/samples/cache_v1alpha1_memcached.yaml | ||
} | ||
|
||
function update_Makefile() { | ||
FILENAME=Makefile | ||
|
||
# Add undeploy target | ||
sed -i "$MAKEFILE_UNDEPLOY_TARGET" $FILENAME | ||
|
||
# Modify the test target | ||
sed -i 's/'"$MAKEFILE_OLD_TEST_TARGET"'/'"$MAKEFILE_NEW_TEST_TARGET"/ $FILENAME | ||
|
||
# Add Envtest variable | ||
sed -i '/# Run tests/a ENVTEST_ASSETS_DIR=$(shell pwd)\/testbin' $FILENAME | ||
|
||
# Add packagemanifest target | ||
sed -i "$MAKEFILE_PACKAGE_MANIFEST_TARGET" $FILENAME | ||
} | ||
|
||
function scaffold_go_project() { | ||
|
||
header_text "Removing the existing example project" | ||
rm -rf memcached-operator | ||
|
||
header_text "Creating project directory" | ||
mkdir memcached-operator | ||
cd memcached-operator | ||
|
||
header_text "Initializing new project and creating API" | ||
operator-sdk init --repo github.com/example/memcached-operator --domain example.com | ||
operator-sdk create api --group cache --version v1alpha1 --kind Memcached --controller --resource | ||
|
||
# Update memcached_types.go | ||
update_memcached_types | ||
|
||
# Update memcached_controllers.go | ||
update_memcached_controllers | ||
|
||
header_text "Updating the generated code for specific resource" | ||
make generate | ||
|
||
header_text "Generating manifests" | ||
make manifests | ||
|
||
header_text "Scaffolding webhook" | ||
operator-sdk create webhook --group cache --version v1alpha1 --kind Memcached --defaulting --programmatic-validation | ||
|
||
# Update memcached_webhook.go | ||
update_memcached_webhook | ||
|
||
# Update config/default/kustomization.yaml | ||
update_default_kustomization | ||
|
||
# Update config/samples/cache_v1alpha1_memcached.yaml | ||
update_cache_v1alpha1_memcached_sample | ||
|
||
# Add Undeploy target to Makefile | ||
update_Makefile | ||
|
||
header_text "integrating with OLM ..." | ||
sed -i".bak" -E -e 's/operator-sdk generate kustomize manifests/operator-sdk generate kustomize manifests --interactive=false/g' Makefile; rm -f Makefile.bak | ||
|
||
# OLM Integration | ||
operIMG=quay.io/example/memcached-operator:v0.0.1 | ||
bundleIMG=quay.io/example-bundle/memcached-operator:v0.0.1 | ||
|
||
header_text "generating bundle and building the image ..." | ||
make bundle IMG=$operIMG | ||
make bundle-build BUNDLE_IMG=$bundleIMG | ||
|
||
make packagemanifests | ||
} | ||
|
||
scaffold_go_project | ||
|
||
# Run go fmt against code | ||
go fmt ./... |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.