From c1e3a9efbe1f42621d9941cc3ccf36120e908e06 Mon Sep 17 00:00:00 2001 From: Mengqi Yu Date: Fri, 8 Mar 2019 10:08:24 -0800 Subject: [PATCH] :running: add an example for CRD --- examples/crd/main.go | 139 ++++++++++++++++++++++ examples/crd/pkg/doc.go | 23 ++++ examples/crd/pkg/resource.go | 119 ++++++++++++++++++ examples/crd/pkg/zz_generated.deepcopy.go | 121 +++++++++++++++++++ hack/check-everything.sh | 5 +- hack/verify.sh | 2 +- 6 files changed, 406 insertions(+), 3 deletions(-) create mode 100644 examples/crd/main.go create mode 100644 examples/crd/pkg/doc.go create mode 100644 examples/crd/pkg/resource.go create mode 100644 examples/crd/pkg/zz_generated.deepcopy.go diff --git a/examples/crd/main.go b/examples/crd/main.go new file mode 100644 index 0000000000..908858d52b --- /dev/null +++ b/examples/crd/main.go @@ -0,0 +1,139 @@ +/* +Copyright 2019 The Kubernetes Authors. + +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 main + +import ( + "context" + "math/rand" + "os" + "time" + + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + ctrl "sigs.k8s.io/controller-runtime" + api "sigs.k8s.io/controller-runtime/examples/crd/pkg" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +var ( + setupLog = ctrl.Log.WithName("setup") + recLog = ctrl.Log.WithName("reconciler") +) + +type reconciler struct { + client.Client + scheme *runtime.Scheme +} + +func (r *reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { + log := recLog.WithValues("chaospod", req.NamespacedName) + log.V(1).Info("reconciling chaos pod") + ctx := context.Background() + + var chaosctl api.ChaosPod + if err := r.Get(ctx, req.NamespacedName, &chaosctl); err != nil { + log.Error(err, "unable to get chaosctl") + return ctrl.Result{}, err + } + + var pod corev1.Pod + podFound := true + if err := r.Get(ctx, req.NamespacedName, &pod); err != nil { + if !apierrors.IsNotFound(err) { + log.Error(err, "unable to get pod") + return ctrl.Result{}, err + } + podFound = false + } + + if podFound { + shouldStop := chaosctl.Spec.NextStop.Time.Before(time.Now()) + if !shouldStop { + return ctrl.Result{RequeueAfter: chaosctl.Spec.NextStop.Sub(time.Now()) + 1*time.Second}, nil + } + + if err := r.Delete(ctx, &pod); err != nil { + log.Error(err, "unable to delete pod") + return ctrl.Result{}, err + } + + return ctrl.Result{Requeue: true}, nil + } + + templ := chaosctl.Spec.Template.DeepCopy() + pod.ObjectMeta = templ.ObjectMeta + pod.Name = req.Name + pod.Namespace = req.Namespace + pod.Spec = templ.Spec + + if err := ctrl.SetControllerReference(&chaosctl, &pod, r.scheme); err != nil { + log.Error(err, "unable to set pod's owner reference") + return ctrl.Result{}, err + } + + if err := r.Create(ctx, &pod); err != nil { + log.Error(err, "unable to create pod") + return ctrl.Result{}, err + } + + chaosctl.Spec.NextStop.Time = time.Now().Add(time.Duration(10*(rand.Int63n(2)+1)) * time.Second) + chaosctl.Status.LastRun = pod.CreationTimestamp + if err := r.Update(ctx, &chaosctl); err != nil { + log.Error(err, "unable to update chaosctl status") + return ctrl.Result{}, err + } + return ctrl.Result{}, nil +} + +func main() { + ctrl.SetLogger(zap.Logger(true)) + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{}) + if err != nil { + setupLog.Error(err, "unable to start manager") + os.Exit(1) + } + + // in a real controller, we'd create a new scheme for this + err = api.AddToScheme(mgr.GetScheme()) + if err != nil { + setupLog.Error(err, "unable to add scheme") + os.Exit(1) + } + + err = ctrl.NewControllerManagedBy(mgr). + For(&api.ChaosPod{}). + Owns(&corev1.Pod{}). + Complete(&reconciler{ + Client: mgr.GetClient(), + scheme: mgr.GetScheme(), + }) + + if err != nil { + setupLog.Error(err, "unable to create controller") + os.Exit(1) + } + + setupLog.Info("starting manager") + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + setupLog.Error(err, "problem running manager") + os.Exit(1) + } +} diff --git a/examples/crd/pkg/doc.go b/examples/crd/pkg/doc.go new file mode 100644 index 0000000000..ac12c3fb7b --- /dev/null +++ b/examples/crd/pkg/doc.go @@ -0,0 +1,23 @@ +/* +Copyright 2018 The Kubernetes authors. + +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 pkg + +import ( + logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" +) + +var log = logf.Log.WithName("chaospod-resource") diff --git a/examples/crd/pkg/resource.go b/examples/crd/pkg/resource.go new file mode 100644 index 0000000000..5e7c398039 --- /dev/null +++ b/examples/crd/pkg/resource.go @@ -0,0 +1,119 @@ +/* +Copyright 2018 The Kubernetes Authors. + +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 pkg + +import ( + "fmt" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" + "sigs.k8s.io/controller-runtime/pkg/webhook" +) + +// ChaosPodSpec defines the desired state of ChaosPod +type ChaosPodSpec struct { + Template corev1.PodTemplateSpec `json:"template"` + // +optional + NextStop metav1.Time `json:"nextStop,omitempty"` +} + +// ChaosPodStatus defines the observed state of ChaosPod. +// It should always be reconstructable from the state of the cluster and/or outside world. +type ChaosPodStatus struct { + LastRun metav1.Time `json:"lastRun,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ChaosPod is the Schema for the randomjobs API +// +kubebuilder:printcolumn:name="next stop",type="string",JSONPath=".spec.nextStop",format="date" +// +kubebuilder:printcolumn:name="last run",type="string",JSONPath=".status.lastRun",format="date" +// +k8s:openapi-gen=true +type ChaosPod struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ChaosPodSpec `json:"spec,omitempty"` + Status ChaosPodStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ChaosPodList contains a list of ChaosPod +type ChaosPodList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ChaosPod `json:"items"` +} + +// ValidateCreate implements webhookutil.validator so a webhook will be registered for the type +func (c *ChaosPod) ValidateCreate() error { + log.Info("validate create", "name", c.Name) + + if c.Spec.NextStop.Before(&metav1.Time{Time: time.Now()}) { + return fmt.Errorf(".spec.nextStop must be later than current time") + } + return nil +} + +// ValidateUpdate implements webhookutil.validator so a webhook will be registered for the type +func (c *ChaosPod) ValidateUpdate(old runtime.Object) error { + log.Info("validate update", "name", c.Name) + + if c.Spec.NextStop.Before(&metav1.Time{Time: time.Now()}) { + return fmt.Errorf(".spec.nextStop must be later than current time") + } + + oldC, ok := old.(*ChaosPod) + if !ok { + return fmt.Errorf("expect old object to be a %T instead of %T", oldC, old) + } + if c.Spec.NextStop.After(oldC.Spec.NextStop.Add(time.Hour)) { + return fmt.Errorf("it is not allowed to delay.spec.nextStop for more than 1 hour") + } + return nil +} + +var _ webhook.Defaulter = &ChaosPod{} + +// Default implements webhookutil.defaulter so a webhook will be registered for the type +func (c *ChaosPod) Default() { + log.Info("default", "name", c.Name) + + if c.Spec.NextStop.Before(&metav1.Time{Time: time.Now()}) { + c.Spec.NextStop = metav1.Time{Time: time.Now().Add(time.Minute)} + } +} + +func init() { + SchemeBuilder.Register(&ChaosPod{}, &ChaosPodList{}) +} + +var ( + // SchemeGroupVersion is group version used to register these objects + SchemeGroupVersion = schema.GroupVersion{Group: "chaosapps.metamagical.io", Version: "v1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} + + // AddToScheme is required by pkg/client/... + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/examples/crd/pkg/zz_generated.deepcopy.go b/examples/crd/pkg/zz_generated.deepcopy.go new file mode 100644 index 0000000000..cd506a87c0 --- /dev/null +++ b/examples/crd/pkg/zz_generated.deepcopy.go @@ -0,0 +1,121 @@ +// +build !ignore_autogenerated + +/* +Copyright 2019 The Kubernetes Authors. + +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. +*/ + +// Code generated by main. DO NOT EDIT. + +package pkg + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (c *ChaosPod) DeepCopyInto(out *ChaosPod) { + *out = *c + out.TypeMeta = c.TypeMeta + c.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + c.Spec.DeepCopyInto(&out.Spec) + c.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChaosPod. +func (c *ChaosPod) DeepCopy() *ChaosPod { + if c == nil { + return nil + } + out := new(ChaosPod) + c.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (c *ChaosPod) DeepCopyObject() runtime.Object { + if c := c.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ChaosPodList) DeepCopyInto(out *ChaosPodList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ChaosPod, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChaosPodList. +func (in *ChaosPodList) DeepCopy() *ChaosPodList { + if in == nil { + return nil + } + out := new(ChaosPodList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ChaosPodList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ChaosPodSpec) DeepCopyInto(out *ChaosPodSpec) { + *out = *in + in.Template.DeepCopyInto(&out.Template) + in.NextStop.DeepCopyInto(&out.NextStop) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChaosPodSpec. +func (in *ChaosPodSpec) DeepCopy() *ChaosPodSpec { + if in == nil { + return nil + } + out := new(ChaosPodSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ChaosPodStatus) DeepCopyInto(out *ChaosPodStatus) { + *out = *in + in.LastRun.DeepCopyInto(&out.LastRun) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChaosPodStatus. +func (in *ChaosPodStatus) DeepCopy() *ChaosPodStatus { + if in == nil { + return nil + } + out := new(ChaosPodStatus) + in.DeepCopyInto(out) + return out +} diff --git a/hack/check-everything.sh b/hack/check-everything.sh index 50d0c50d08..da4ac475ac 100755 --- a/hack/check-everything.sh +++ b/hack/check-everything.sh @@ -73,8 +73,9 @@ setup_envs ${hack_dir}/verify.sh ${hack_dir}/test-all.sh -header_text "confirming example compiles (via go install)" -go install ./example +header_text "confirming examples compile (via go install)" +go install ./examples/builtins +go install ./examples/crd echo "passed" exit 0 diff --git a/hack/verify.sh b/hack/verify.sh index 4e9b760495..b8977904a9 100755 --- a/hack/verify.sh +++ b/hack/verify.sh @@ -50,7 +50,7 @@ gometalinter.v2 --disable-all \ --enable=dupl \ --skip=atomic \ --enable=goimports \ - ./pkg/... ./example/... . + ./pkg/... ./examples/... . # TODO: Enable these as we fix them to make them pass # --enable=gosec \ # --enable=maligned \