Skip to content

Commit a0ce0bd

Browse files
committed
Add samplecontroller business logic
1 parent b972355 commit a0ce0bd

File tree

2 files changed

+193
-32
lines changed

2 files changed

+193
-32
lines changed

samples/full/controller/src/samplecontroller/pkg/apis/samplecontroller/v1alpha1/foo_types.go

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
/*
32
Copyright 2017 The Kubernetes Authors.
43
@@ -15,32 +14,27 @@ See the License for the specific language governing permissions and
1514
limitations under the License.
1615
*/
1716

18-
1917
package v1alpha1
2018

2119
import (
2220
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2321
)
2422

25-
// EDIT THIS FILE!
26-
// Created by "kubebuilder create resource" for you to implement the Foo resource schema definition
27-
// as a go struct.
28-
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
29-
3023
// FooSpec defines the desired state of Foo
3124
type FooSpec struct {
32-
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
25+
DeploymentName string `json:"deploymentName"`
26+
Replicas *int32 `json:"replicas"`
3327
}
3428

3529
// FooStatus defines the observed state of Foo
3630
type FooStatus struct {
37-
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
31+
AvailableReplicas int32 `json:"availableReplicas"`
3832
}
3933

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

43-
// Foo
37+
// Foo is a specification for a Foo resource
4438
// +k8s:openapi-gen=true
4539
// +resource:path=foos
4640
type Foo struct {

samples/full/controller/src/samplecontroller/pkg/controller/foo/controller.go

Lines changed: 189 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,59 +14,226 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
1817
package foo
1918

2019
import (
21-
"log"
20+
"fmt"
2221

22+
"github.com/golang/glog"
2323
"github.com/kubernetes-sigs/kubebuilder/pkg/controller"
24+
"github.com/kubernetes-sigs/kubebuilder/pkg/controller/eventhandlers"
25+
"github.com/kubernetes-sigs/kubebuilder/pkg/controller/predicates"
2426
"github.com/kubernetes-sigs/kubebuilder/pkg/controller/types"
27+
appsv1 "k8s.io/api/apps/v1"
28+
corev1 "k8s.io/api/core/v1"
29+
"k8s.io/apimachinery/pkg/api/errors"
30+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31+
"k8s.io/apimachinery/pkg/runtime/schema"
32+
"k8s.io/apimachinery/pkg/util/runtime"
33+
"k8s.io/client-go/kubernetes/scheme"
34+
"k8s.io/client-go/tools/record"
2535

26-
samplecontrollerv1alpha1client "samplecontroller/pkg/client/clientset/versioned/typed/samplecontroller/v1alpha1"
27-
samplecontrollerv1alpha1lister "samplecontroller/pkg/client/listers/samplecontroller/v1alpha1"
2836
samplecontrollerv1alpha1 "samplecontroller/pkg/apis/samplecontroller/v1alpha1"
29-
samplecontrollerv1alpha1informer "samplecontroller/pkg/client/informers/externalversions/samplecontroller/v1alpha1"
37+
samplescheme "samplecontroller/pkg/client/clientset/versioned/scheme"
3038
"samplecontroller/pkg/inject/args"
3139
)
3240

33-
// EDIT THIS FILE
34-
// This files was created by "kubebuilder create resource" for you to edit.
35-
// Controller implementation logic for Foo resources goes here.
41+
const controllerAgentName = "sample-controller"
42+
43+
const (
44+
// SuccessSynced is used as part of the Event 'reason' when a Foo is synced
45+
SuccessSynced = "Synced"
46+
// ErrResourceExists is used as part of the Event 'reason' when a Foo fails
47+
// to sync due to a Deployment of the same name already existing.
48+
ErrResourceExists = "ErrResourceExists"
3649

50+
// MessageResourceExists is the message used for Events when a resource
51+
// fails to sync due to a Deployment already existing
52+
MessageResourceExists = "Resource %q already exists and is not managed by Foo"
53+
// MessageResourceSynced is the message used for an Event fired when a Foo
54+
// is synced successfully
55+
MessageResourceSynced = "Foo synced successfully"
56+
)
57+
58+
// Reconcile compares the actual state with the desired, and attempts to
59+
// converge the two. It then updates the Status block of the Foo resource
60+
// with the current status of the resource.
3761
func (bc *FooController) Reconcile(k types.ReconcileKey) error {
38-
// INSERT YOUR CODE HERE
39-
log.Printf("Implement the Reconcile function on foo.FooController to reconcile %s\n", k.Name)
62+
namespace, name := k.Namespace, k.Name
63+
foo, err := bc.Informers.Samplecontroller().V1alpha1().Foos().Lister().Foos(namespace).Get(name)
64+
if err != nil {
65+
// The Foo resource may no longer exist, in which case we stop
66+
// processing.
67+
if errors.IsNotFound(err) {
68+
runtime.HandleError(fmt.Errorf("foo '%s' in work queue no longer exists", k))
69+
return nil
70+
}
71+
72+
return err
73+
}
74+
75+
deploymentName := foo.Spec.DeploymentName
76+
if deploymentName == "" {
77+
// We choose to absorb the error here as the worker would requeue the
78+
// resource otherwise. Instead, the next time the resource is updated
79+
// the resource will be queued again.
80+
runtime.HandleError(fmt.Errorf("%s: deployment name must be specified", k))
81+
return nil
82+
}
83+
84+
// Get the deployment with the name specified in Foo.spec
85+
deployment, err := bc.KubernetesInformers.Apps().V1().Deployments().Lister().Deployments(foo.Namespace).Get(deploymentName)
86+
// If the resource doesn't exist, we'll create it
87+
if errors.IsNotFound(err) {
88+
deployment, err = bc.KubernetesClientSet.AppsV1().Deployments(foo.Namespace).Create(newDeployment(foo))
89+
}
90+
91+
// If an error occurs during Get/Create, we'll requeue the item so we can
92+
// attempt processing again later. This could have been caused by a
93+
// temporary network failure, or any other transient reason.
94+
if err != nil {
95+
return err
96+
}
97+
98+
// If the Deployment is not controlled by this Foo resource, we should log
99+
// a warning to the event recorder and ret
100+
if !metav1.IsControlledBy(deployment, foo) {
101+
msg := fmt.Sprintf(MessageResourceExists, deployment.Name)
102+
bc.recorder.Event(foo, corev1.EventTypeWarning, ErrResourceExists, msg)
103+
return fmt.Errorf(msg)
104+
}
105+
106+
// If this number of the replicas on the Foo resource is specified, and the
107+
// number does not equal the current desired replicas on the Deployment, we
108+
// should update the Deployment resource.
109+
if foo.Spec.Replicas != nil && *foo.Spec.Replicas != *deployment.Spec.Replicas {
110+
glog.V(4).Infof("Foo %s replicas: %d, deployment replicas: %d", name, *foo.Spec.Replicas, *deployment.Spec.Replicas)
111+
deployment, err = bc.KubernetesClientSet.AppsV1().Deployments(foo.Namespace).Update(newDeployment(foo))
112+
}
113+
114+
// If an error occurs during Update, we'll requeue the item so we can
115+
// attempt processing again later. THis could have been caused by a
116+
// temporary network failure, or any other transient reason.
117+
if err != nil {
118+
return err
119+
}
120+
121+
// Finally, we update the status block of the Foo resource to reflect the
122+
// current state of the world
123+
err = bc.updateFooStatus(foo, deployment)
124+
if err != nil {
125+
return err
126+
}
127+
128+
bc.recorder.Event(foo, corev1.EventTypeNormal, SuccessSynced, MessageResourceSynced)
40129
return nil
41130
}
42131

132+
// FooController is the controller implementation for Foo resources
43133
// +controller:group=samplecontroller,version=v1alpha1,kind=Foo,resource=foos
134+
// +informers:group=apps,version=v1,kind=Deployment
135+
// +rbac:rbac:groups=apps,resources=Deployment,verbs=get;list;watch;create;update;patch;delete
44136
type FooController struct {
45-
// INSERT ADDITIONAL FIELDS HERE
46-
fooLister samplecontrollerv1alpha1lister.FooLister
47-
fooclient samplecontrollerv1alpha1client.SamplecontrollerV1alpha1Interface
137+
args.InjectArgs
138+
139+
// recorder is an event recorder for recording Event resources to the
140+
// Kubernetes API.
141+
recorder record.EventRecorder
48142
}
49143

50144
// ProvideController provides a controller that will be run at startup. Kubebuilder will use codegeneration
51145
// to automatically register this controller in the inject package
52-
func ProvideController(arguments args.InjectArgs) (*controller.GenericController, error) {
53-
// INSERT INITIALIZATIONS FOR ADDITIONAL FIELDS HERE
146+
func ProvideController(iargs args.InjectArgs) (*controller.GenericController, error) {
147+
samplescheme.AddToScheme(scheme.Scheme)
148+
54149
bc := &FooController{
55-
fooLister: arguments.ControllerManager.GetInformerProvider(&samplecontrollerv1alpha1.Foo{}).(samplecontrollerv1alpha1informer.FooInformer).Lister(),
56-
fooclient: arguments.Clientset.SamplecontrollerV1alpha1(),
150+
InjectArgs: iargs,
151+
recorder: iargs.CreateRecorder(controllerAgentName),
57152
}
58153

59154
// Create a new controller that will call FooController.Reconcile on changes to Foos
60155
gc := &controller.GenericController{
61-
Name: "FooController",
62-
Reconcile: bc.Reconcile,
63-
InformerRegistry: arguments.ControllerManager,
156+
Name: controllerAgentName,
157+
Reconcile: bc.Reconcile,
158+
InformerRegistry: iargs.ControllerManager,
64159
}
160+
161+
glog.Info("Setting up event handlers")
65162
if err := gc.Watch(&samplecontrollerv1alpha1.Foo{}); err != nil {
66163
return gc, err
67164
}
68165

69-
// INSERT ADDITIONAL WATCHES HERE BY CALLING gc.Watch.*() FUNCTIONS
70-
// NOTE: Informers for Kubernetes resources *MUST* be registered in the pkg/inject package so that they are started.
166+
// Set up an event handler for when Deployment resources change. This
167+
// handler will lookup the owner of the given Deployment, and if it is
168+
// owned by a Foo resource will enqueue that Foo resource for
169+
// processing. This way, we don't need to implement custom logic for
170+
// handling Deployment resources. More info on this pattern:
171+
// https://github.com/kubernetes/community/blob/8cafef897a22026d42f5e5bb3f104febe7e29830/contributors/devel/controllers.md
172+
if err := gc.WatchControllerOf(&appsv1.Deployment{}, eventhandlers.Path{bc.LookupFoo},
173+
predicates.ResourceVersionChanged); err != nil {
174+
return gc, err
175+
}
176+
71177
return gc, nil
72178
}
179+
180+
// LookupFoo looksup a Foo from the lister
181+
func (bc FooController) LookupFoo(r types.ReconcileKey) (interface{}, error) {
182+
return bc.Informers.Samplecontroller().V1alpha1().Foos().Lister().Foos(r.Namespace).Get(r.Name)
183+
}
184+
185+
func (bc *FooController) updateFooStatus(foo *samplecontrollerv1alpha1.Foo, deployment *appsv1.Deployment) error {
186+
// NEVER modify objects from the store. It's a read-only, local cache.
187+
// You can use DeepCopy() to make a deep copy of original object and modify this copy
188+
// Or create a copy manually for better performance
189+
fooCopy := foo.DeepCopy()
190+
fooCopy.Status.AvailableReplicas = deployment.Status.AvailableReplicas
191+
// Until #38113 is merged, we must use Update instead of UpdateStatus to
192+
// update the Status block of the Foo resource. UpdateStatus will not
193+
// allow changes to the Spec of the resource, which is ideal for ensuring
194+
// nothing other than resource status has been updated.
195+
_, err := bc.Clientset.SamplecontrollerV1alpha1().Foos(foo.Namespace).Update(fooCopy)
196+
return err
197+
}
198+
199+
// newDeployment creates a new Deployment for a Foo resource. It also sets
200+
// the appropriate OwnerReferences on the resource so handleObject can discover
201+
// the Foo resource that 'owns' it.
202+
func newDeployment(foo *samplecontrollerv1alpha1.Foo) *appsv1.Deployment {
203+
labels := map[string]string{
204+
"app": "nginx",
205+
"controller": foo.Name,
206+
}
207+
return &appsv1.Deployment{
208+
ObjectMeta: metav1.ObjectMeta{
209+
Name: foo.Spec.DeploymentName,
210+
Namespace: foo.Namespace,
211+
OwnerReferences: []metav1.OwnerReference{
212+
*metav1.NewControllerRef(foo, schema.GroupVersionKind{
213+
Group: samplecontrollerv1alpha1.SchemeGroupVersion.Group,
214+
Version: samplecontrollerv1alpha1.SchemeGroupVersion.Version,
215+
Kind: "Foo",
216+
}),
217+
},
218+
},
219+
Spec: appsv1.DeploymentSpec{
220+
Replicas: foo.Spec.Replicas,
221+
Selector: &metav1.LabelSelector{
222+
MatchLabels: labels,
223+
},
224+
Template: corev1.PodTemplateSpec{
225+
ObjectMeta: metav1.ObjectMeta{
226+
Labels: labels,
227+
},
228+
Spec: corev1.PodSpec{
229+
Containers: []corev1.Container{
230+
{
231+
Name: "nginx",
232+
Image: "nginx:latest",
233+
},
234+
},
235+
},
236+
},
237+
},
238+
}
239+
}

0 commit comments

Comments
 (0)