Skip to content

Commit

Permalink
add an example for CRD
Browse files Browse the repository at this point in the history
  • Loading branch information
Mengqi Yu committed Feb 22, 2019
1 parent 5f14019 commit 5264408
Show file tree
Hide file tree
Showing 8 changed files with 496 additions and 0 deletions.
17 changes: 17 additions & 0 deletions crdexample/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Build the manager binary
FROM golang:1.11.5 as builder

# Copy in the go src
WORKDIR /go/src/sigs.k8s.io/controller-runtime/
COPY pkg/ pkg/
COPY crdexample/ crdexample/
COPY vendor/ vendor/

# Build
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o manager ./crdexample

# Copy the controller-manager into a thin image
FROM ubuntu:latest
WORKDIR /
COPY --from=builder /go/src/sigs.k8s.io/controller-runtime/manager .
ENTRYPOINT ["/manager"]
5 changes: 5 additions & 0 deletions crdexample/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
build docker image

```bash
docker build -f ./crdexample/Dockerfile .
```
91 changes: 91 additions & 0 deletions crdexample/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
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 main

import (
"context"

"github.com/go-logr/logr"

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/runtime"
"sigs.k8s.io/controller-runtime/crdexample/logutil"
"sigs.k8s.io/controller-runtime/crdexample/pkg"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

var fmLog = logutil.Log.WithName("firstmate-reconciler")

// FirstMateController reconciles ReplicaSets
type FirstMateController struct {
// client can be used to retrieve objects from the APIServer.
client client.Client
}

func (i *FirstMateController) InjectClient(c client.Client) error {
i.client = c
return nil
}

// Implement reconcile.Reconciler so the controller can reconcile objects
var _ reconcile.Reconciler = &FirstMateController{}

func (r *FirstMateController) Reconcile(request reconcile.Request) (reconcile.Result, error) {
// set up a entryLog object so we don't have to type request over and over again
log := fmLog.WithValues("request", request)
ctx := context.Background()

// Fetch the firstMate from the cache
fm := &pkg.FirstMate{}
if err := r.client.Get(ctx, request.NamespacedName, fm); errors.IsNotFound(err) {
return reconcile.Result{}, nil
} else if err != nil {
log.Error(err, "could not fetch firstMate")
return reconcile.Result{}, err
}

dep := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: request.Name, Namespace: request.Namespace}}
updateFn := (&createOrUpdateDeployment{firstMate: fm, log: fmLog}).do

_, err := controllerutil.CreateOrUpdate(ctx, r.client, dep, updateFn)
if err != nil {
return reconcile.Result{}, err
}

return reconcile.Result{}, nil
}

type createOrUpdateDeployment struct {
firstMate *pkg.FirstMate
log logr.Logger
}

func (r *createOrUpdateDeployment) do(existing runtime.Object) error {
r.log.Info("creating or updating deployment")
dep := existing.(*appsv1.Deployment)
dep.Labels = r.firstMate.Labels
dep.Spec.Replicas = &r.firstMate.Spec.Crew
dep.Spec.Template.Labels = r.firstMate.Labels
dep.Spec.Selector.MatchLabels = r.firstMate.Labels
dep.Spec.Template.Spec.Containers = []corev1.Container{{Name: "nginx", Image: "nginx"}}
return nil
}
30 changes: 30 additions & 0 deletions crdexample/logutil/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
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 logutil

import (
"github.com/go-logr/logr"

logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
)

var Log logr.Logger

func init() {
logf.SetLogger(logf.ZapLogger(true))
Log = logf.Log.WithName("crew-controller")
}
65 changes: 65 additions & 0 deletions crdexample/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
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 main

import (
"flag"
"os"

appsv1 "k8s.io/api/apps/v1"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
"sigs.k8s.io/controller-runtime/crdexample/logutil"
"sigs.k8s.io/controller-runtime/crdexample/pkg"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/runtime/signals"
)

func main() {
flag.Parse()
entryLog := logutil.Log.WithName("entrypoint")

entryLog.Info("setting up manager")
mgr, err := manager.New(config.GetConfigOrDie(), manager.Options{Port: 12789})
if err != nil {
entryLog.Error(err, "unable to set up overall controller manager")
os.Exit(1)
}

entryLog.Info("setting up scheme")
if err := pkg.AddToScheme(mgr.GetScheme()); err != nil {
entryLog.Error(err, "unable add APIs to scheme")
os.Exit(1)
}

entryLog.Info("setting up controllers")
err = builder.ControllerManagedBy(mgr).
For(&pkg.FirstMate{}).
Owns(&appsv1.Deployment{}).
Complete(&FirstMateController{})
if err != nil {
entryLog.Error(err, "unable to set up controllers")
os.Exit(1)
}

entryLog.Info("starting manager")
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
entryLog.Error(err, "unable to run manager")
os.Exit(1)
}
}
23 changes: 23 additions & 0 deletions crdexample/pkg/doc.go
Original file line number Diff line number Diff line change
@@ -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 v1 contains API Schema definitions for the crew v1 API group
// +k8s:openapi-gen=true
// +k8s:deepcopy-gen=package,register
// +k8s:conversion-gen=sigs.k8s.io/kubebuilder/test/project/pkg/apis/crew
// +k8s:defaulter-gen=TypeMeta
// +groupName=crew.example.com
package pkg
138 changes: 138 additions & 0 deletions crdexample/pkg/resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
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"

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/crdexample/logutil"
"sigs.k8s.io/controller-runtime/pkg/scheme"
"sigs.k8s.io/controller-runtime/pkg/webhook"
)

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

var log = logutil.Log.WithName("firstmate-resource")

// firstMate is the Schema for the firstmates API
// +k8s:openapi-gen=true
type FirstMate struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec FirstMateSpec `json:"spec,omitempty"`
Status FirstMateStatus `json:"status,omitempty"`
}

// FirstMateSpec defines the desired state of firstMate
type FirstMateSpec struct {
Height *int `json:"height,omitempty"`
Experience int `json:"experience,omitempty"`
Crew int32 `json:"crew,omitempty"`
}

// FirstMateStatus defines the observed state of firstMate
type FirstMateStatus struct {
Location string
}

var _ webhook.Validator = &FirstMate{}

// ValidateCreate implements webhookutil.validator so a webhook will be registered for the type
func (f *FirstMate) ValidateCreate() error {
log.Info("validate create", "name", f.Name)

if f.Spec.Crew <= 0 {
return fmt.Errorf("crew must be greater than 0")
}
return nil
}

// ValidateUpdate implements webhookutil.validator so a webhook will be registered for the type
func (f *FirstMate) ValidateUpdate(old runtime.Object) error {
log.Info("validate update", "name", f.Name)

if f.Spec.Crew <= 0 {
return fmt.Errorf("crew must be greater than 0")
}

oldF, ok := old.(*FirstMate)
if !ok {
return fmt.Errorf("expect old object to be a %T instead of %T", oldF, old)
}
if f.Spec.Crew-oldF.Spec.Crew > 1 || oldF.Spec.Crew-f.Spec.Crew > 1 {
return fmt.Errorf("crew must be greater than 0")
}
return nil
}

var _ webhook.Defaulter = &FirstMate{}

// Default implements webhookutil.defaulter so a webhook will be registered for the type
func (f *FirstMate) Default() {
log.Info("default", "name", f.Name)

if *f.Spec.Height == 0 {
height := 10
f.Spec.Height = &height
}
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

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

func init() {
SchemeBuilder.Register(&FirstMate{}, &FirstMateList{})
}

var (
// SchemeGroupVersion is group version used to register these objects
SchemeGroupVersion = schema.GroupVersion{Group: "crew.example.com", 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
)

// Resource is required by pkg/client/listers/...
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}

// TODO: figure out should we encourage the users to do a wrapper for a core type object.
type MyPod struct {
corev1.Pod
}

var _ webhook.Defaulter = &MyPod{}

func (p *MyPod) Default() {
log.Info("default", *p)
}
Loading

0 comments on commit 5264408

Please sign in to comment.