@@ -14,59 +14,226 @@ See the License for the specific language governing permissions and
14
14
limitations under the License.
15
15
*/
16
16
17
-
18
17
package foo
19
18
20
19
import (
21
- "log "
20
+ "fmt "
22
21
22
+ "github.com/golang/glog"
23
23
"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"
24
26
"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"
25
35
26
- samplecontrollerv1alpha1client "samplecontroller/pkg/client/clientset/versioned/typed/samplecontroller/v1alpha1"
27
- samplecontrollerv1alpha1lister "samplecontroller/pkg/client/listers/samplecontroller/v1alpha1"
28
36
samplecontrollerv1alpha1 "samplecontroller/pkg/apis/samplecontroller/v1alpha1"
29
- samplecontrollerv1alpha1informer "samplecontroller/pkg/client/informers/externalversions/samplecontroller/v1alpha1 "
37
+ samplescheme "samplecontroller/pkg/client/clientset/versioned/scheme "
30
38
"samplecontroller/pkg/inject/args"
31
39
)
32
40
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"
36
49
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.
37
61
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 )
40
129
return nil
41
130
}
42
131
132
+ // FooController is the controller implementation for Foo resources
43
133
// +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
44
136
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
48
142
}
49
143
50
144
// ProvideController provides a controller that will be run at startup. Kubebuilder will use codegeneration
51
145
// 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
+
54
149
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 ),
57
152
}
58
153
59
154
// Create a new controller that will call FooController.Reconcile on changes to Foos
60
155
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 ,
64
159
}
160
+
161
+ glog .Info ("Setting up event handlers" )
65
162
if err := gc .Watch (& samplecontrollerv1alpha1.Foo {}); err != nil {
66
163
return gc , err
67
164
}
68
165
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
+
71
177
return gc , nil
72
178
}
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