Skip to content

Commit 693adb7

Browse files
add code for updating controller status
1 parent 68c51e9 commit 693adb7

File tree

17 files changed

+912
-30
lines changed

17 files changed

+912
-30
lines changed

pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/api/types.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ func (f *Types) SetTemplateDefaults() error {
6060
return nil
6161
}
6262

63+
//nolint:lll
6364
const typesTemplate = `{{ .Boilerplate }}
6465
6566
package {{ .Resource.Version }}
@@ -92,8 +93,16 @@ type {{ .Resource.Kind }}Spec struct {
9293
9394
// {{ .Resource.Kind }}Status defines the observed state of {{ .Resource.Kind }}
9495
type {{ .Resource.Kind }}Status struct {
95-
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
96-
// Important: Run "make" to regenerate code after modifying this file
96+
// Represents the observations of a {{ .Resource.Kind }}'s current state.
97+
// {{ .Resource.Kind }}.status.conditions.type are: "Available", "Progressing", and "Degraded"
98+
// {{ .Resource.Kind }}.status.conditions.status are one of True, False, Unknown.
99+
// {{ .Resource.Kind }}.status.conditions.reason the value should be a CamelCase string and producers of specific
100+
// condition types may define expected values and meanings for this field, and whether the values
101+
// are considered a guaranteed API.
102+
// {{ .Resource.Kind }}.status.conditions.Message is a human readable message indicating details about the transition.
103+
// For further information see: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties
104+
105+
Conditions []metav1.Condition ` + "`" + `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` + "`" + `
97106
}
98107
99108
//+kubebuilder:object:root=true

pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/controllers/controller.go

Lines changed: 108 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ import (
7575
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
7676
"k8s.io/apimachinery/pkg/types"
7777
"k8s.io/apimachinery/pkg/runtime"
78+
"k8s.io/apimachinery/pkg/api/meta"
7879
"k8s.io/client-go/tools/record"
7980
ctrl "sigs.k8s.io/controller-runtime"
8081
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -88,6 +89,14 @@ import (
8889
8990
const {{ lower .Resource.Kind }}Finalizer = "{{ .Resource.Group }}.{{ .Resource.Domain }}/finalizer"
9091
92+
// Definitions to manage status conditions
93+
const (
94+
// typeAvailable{{ .Resource.Kind }} represents the status of the Deployment reconciliation
95+
typeAvailable{{ .Resource.Kind }} = "Available"
96+
// typeDegraded{{ .Resource.Kind }} represents the status used when the custom resource is deleted and the finalizer operations are must to occur.
97+
typeDegraded{{ .Resource.Kind }} = "Degraded"
98+
)
99+
91100
// {{ .Resource.Kind }}Reconciler reconciles a {{ .Resource.Kind }} object
92101
type {{ .Resource.Kind }}Reconciler struct {
93102
client.Client
@@ -138,6 +147,25 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl
138147
return ctrl.Result{}, err
139148
}
140149
150+
// Let's just set the status as Unknown when no status are available
151+
if {{ lower .Resource.Kind }}.Status.Conditions == nil || len({{ lower .Resource.Kind }}.Status.Conditions) == 0 {
152+
meta.SetStatusCondition(&{{ lower .Resource.Kind }}.Status.Conditions, metav1.Condition{Type: typeAvailable{{ .Resource.Kind }}, Status: metav1.ConditionUnknown, Reason: "Reconciling", Message: "Starting reconciliation"})
153+
if err = r.Status().Update(ctx, {{ lower .Resource.Kind }}); err != nil {
154+
log.Error(err, "Failed to update {{ .Resource.Kind }} status")
155+
return ctrl.Result{}, err
156+
}
157+
158+
// Let's re-fetch the {{ lower .Resource.Kind }} Custom Resource after update the status
159+
// so that we have the latest state of the resource on the cluster and we will avoid
160+
// raise the issue "the object has been modified, please apply
161+
// your changes to the latest version and try again" which would re-trigger the reconciliation
162+
// if we try to update it again in the following operations
163+
if err := r.Get(ctx, req.NamespacedName, {{ lower .Resource.Kind }}); err != nil {
164+
log.Error(err, "Failed to re-fetch {{ lower .Resource.Kind }}")
165+
return ctrl.Result{}, err
166+
}
167+
}
168+
141169
// Let's add a finalizer. Then, we can define some operations which should
142170
// occurs before the custom resource to be deleted.
143171
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers
@@ -160,8 +188,43 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl
160188
if is{{ .Resource.Kind }}MarkedToBeDeleted {
161189
if controllerutil.ContainsFinalizer({{ lower .Resource.Kind }}, {{ lower .Resource.Kind }}Finalizer) {
162190
log.Info("Performing Finalizer Operations for {{ .Resource.Kind }} before delete CR")
191+
192+
// Let's add here an status "Downgrade" to define that this resource begin its process to be terminated.
193+
meta.SetStatusCondition(&{{ lower .Resource.Kind }}.Status.Conditions, metav1.Condition{Type: typeDegraded{{ .Resource.Kind }},
194+
Status: metav1.ConditionUnknown, Reason: "Finalizing",
195+
Message: fmt.Sprintf("Performing finalizer operations for the custom resource: %s ", {{ lower .Resource.Kind }}.Name)})
196+
197+
if err := r.Status().Update(ctx, {{ lower .Resource.Kind }}); err != nil {
198+
log.Error(err, "Failed to update {{ .Resource.Kind }} status")
199+
return ctrl.Result{}, err
200+
}
201+
202+
// Perform all operations required before remove the finalizer and allow
203+
// the Kubernetes API to remove the custom custom resource.
163204
r.doFinalizerOperationsFor{{ .Resource.Kind }}({{ lower .Resource.Kind }})
164205
206+
// TODO(user): If you add operations to the doFinalizerOperationsFor{{ .Resource.Kind }} method
207+
// then you need to ensure that all worked fine before deleting and updating the Downgrade status
208+
// otherwise, you should requeue here.
209+
210+
// Re-fetch the {{ lower .Resource.Kind }} Custom Resource before update the status
211+
// so that we have the latest state of the resource on the cluster and we will avoid
212+
// raise the issue "the object has been modified, please apply
213+
// your changes to the latest version and try again" which would re-trigger the reconciliation
214+
if err := r.Get(ctx, req.NamespacedName, {{ lower .Resource.Kind }}); err != nil {
215+
log.Error(err, "Failed to re-fetch {{ lower .Resource.Kind }}")
216+
return ctrl.Result{}, err
217+
}
218+
219+
meta.SetStatusCondition(&{{ lower .Resource.Kind }}.Status.Conditions, metav1.Condition{Type: typeDegraded{{ .Resource.Kind }},
220+
Status: metav1.ConditionTrue, Reason: "Finalizing",
221+
Message: fmt.Sprintf("Finalizer operations for custom resource %s name were successfully accomplished", {{ lower .Resource.Kind }}.Name)})
222+
223+
if err := r.Status().Update(ctx, {{ lower .Resource.Kind }}); err != nil {
224+
log.Error(err, "Failed to update {{ .Resource.Kind }} status")
225+
return ctrl.Result{}, err
226+
}
227+
165228
log.Info("Removing Finalizer for {{ .Resource.Kind }} after successfully perform the operations")
166229
if ok:= controllerutil.RemoveFinalizer({{ lower .Resource.Kind }}, {{ lower .Resource.Kind }}Finalizer); !ok{
167230
log.Error(err, "Failed to remove finalizer for {{ .Resource.Kind }}")
@@ -184,6 +247,17 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl
184247
dep, err := r.deploymentFor{{ .Resource.Kind }}({{ lower .Resource.Kind }})
185248
if err != nil {
186249
log.Error(err, "Failed to define new Deployment resource for {{ .Resource.Kind }}")
250+
251+
// The following implementation will update the status
252+
meta.SetStatusCondition(&{{ lower .Resource.Kind }}.Status.Conditions, metav1.Condition{Type: typeAvailable{{ .Resource.Kind }},
253+
Status: metav1.ConditionFalse, Reason: "Reconciling",
254+
Message: fmt.Sprintf("Failed to create Deployment for the custom resource (%s): (%s)", {{ lower .Resource.Kind }}.Name, err)})
255+
256+
if err := r.Status().Update(ctx, {{ lower .Resource.Kind }}); err != nil {
257+
log.Error(err, "Failed to update {{ .Resource.Kind }} status")
258+
return ctrl.Result{}, err
259+
}
260+
187261
return ctrl.Result{}, err
188262
}
189263
@@ -215,15 +289,45 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl
215289
if err = r.Update(ctx, found); err != nil {
216290
log.Error(err, "Failed to update Deployment",
217291
"Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name)
292+
293+
// Re-fetch the {{ lower .Resource.Kind }} Custom Resource before update the status
294+
// so that we have the latest state of the resource on the cluster and we will avoid
295+
// raise the issue "the object has been modified, please apply
296+
// your changes to the latest version and try again" which would re-trigger the reconciliation
297+
if err := r.Get(ctx, req.NamespacedName, {{ lower .Resource.Kind }}); err != nil {
298+
log.Error(err, "Failed to re-fetch {{ lower .Resource.Kind }}")
299+
return ctrl.Result{}, err
300+
}
301+
302+
// The following implementation will update the status
303+
meta.SetStatusCondition(&{{ lower .Resource.Kind }}.Status.Conditions, metav1.Condition{Type: typeAvailable{{ .Resource.Kind }},
304+
Status: metav1.ConditionFalse, Reason: "Resizing",
305+
Message: fmt.Sprintf("Failed to update the size for the custom resource (%s): (%s)", {{ lower .Resource.Kind }}.Name, err)})
306+
307+
if err := r.Status().Update(ctx, {{ lower .Resource.Kind }}); err != nil {
308+
log.Error(err, "Failed to update {{ .Resource.Kind }} status")
309+
return ctrl.Result{}, err
310+
}
311+
218312
return ctrl.Result{}, err
219313
}
220314
221-
// Since it fails we want to re-queue the reconciliation
222-
// The reconciliation will only stop when we be able to ensure
223-
// the desired state on the cluster
315+
// Now, that we update the size we want to requeue the reconciliation
316+
// so that we can ensure that we have the latest state of the resource before
317+
// update. Also, it will help ensure the desired state on the cluster
224318
return ctrl.Result{Requeue: true}, nil
225319
}
226320
321+
// The following implementation will update the status
322+
meta.SetStatusCondition(&{{ lower .Resource.Kind }}.Status.Conditions, metav1.Condition{Type: typeAvailable{{ .Resource.Kind }},
323+
Status: metav1.ConditionTrue, Reason: "Reconciling",
324+
Message: fmt.Sprintf("Deployment for custom resource (%s) with %d replicas created successfully", {{ lower .Resource.Kind }}.Name, size)})
325+
326+
if err := r.Status().Update(ctx, {{ lower .Resource.Kind }}); err != nil {
327+
log.Error(err, "Failed to update {{ .Resource.Kind }} status")
328+
return ctrl.Result{}, err
329+
}
330+
227331
return ctrl.Result{}, nil
228332
}
229333
@@ -244,7 +348,7 @@ func (r *{{ .Resource.Kind }}Reconciler) doFinalizerOperationsFor{{ .Resource.Ki
244348
r.Recorder.Event(cr, "Warning", "Deleting",
245349
fmt.Sprintf("Custom Resource %s is being deleted from the namespace %s",
246350
cr.Name,
247-
cr.Namespace))
351+
cr.Namespace),)
248352
}
249353
250354
// deploymentFor{{ .Resource.Kind }} returns a {{ .Resource.Kind }} Deployment object

test/e2e/deployimage/plugin_cluster_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package deployimage
1818

1919
import (
20+
"errors"
2021
"fmt"
2122
"os/exec"
2223
"path/filepath"
@@ -263,6 +264,20 @@ func Run(kbc *utils.TestContext) {
263264
}
264265
EventuallyWithOffset(1, getMemcachedPodStatus, time.Minute, time.Second).Should(Succeed())
265266

267+
By("validating that the status of the custom resource created is updated or not")
268+
var status string
269+
getStatus := func() error {
270+
status, err = kbc.Kubectl.Get(true, strings.ToLower(kbc.Kind),
271+
strings.ToLower(kbc.Kind)+"-sample",
272+
"-o", "jsonpath={.status.conditions}")
273+
ExpectWithOffset(2, err).NotTo(HaveOccurred())
274+
if !strings.Contains(status, "Available") {
275+
return errors.New(`status condition with type "Available" should be set`)
276+
}
277+
return nil
278+
}
279+
Eventually(getStatus, time.Minute, time.Second).Should(Succeed())
280+
266281
//Testing the finalizer
267282
EventuallyWithOffset(1, func() error {
268283
_, err = kbc.Kubectl.Delete(true, "-f", sampleFilePath)

testdata/project-v3-with-deploy-image/api/v1alpha1/busybox_types.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,16 @@ type BusyboxSpec struct {
3939

4040
// BusyboxStatus defines the observed state of Busybox
4141
type BusyboxStatus struct {
42-
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
43-
// Important: Run "make" to regenerate code after modifying this file
42+
// Represents the observations of a Busybox's current state.
43+
// Busybox.status.conditions.type are: "Available", "Progressing", and "Degraded"
44+
// Busybox.status.conditions.status are one of True, False, Unknown.
45+
// Busybox.status.conditions.reason the value should be a CamelCase string and producers of specific
46+
// condition types may define expected values and meanings for this field, and whether the values
47+
// are considered a guaranteed API.
48+
// Busybox.status.conditions.Message is a human readable message indicating details about the transition.
49+
// For further information see: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties
50+
51+
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"`
4452
}
4553

4654
//+kubebuilder:object:root=true

testdata/project-v3-with-deploy-image/api/v1alpha1/memcached_types.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,16 @@ type MemcachedSpec struct {
4242

4343
// MemcachedStatus defines the observed state of Memcached
4444
type MemcachedStatus struct {
45-
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
46-
// Important: Run "make" to regenerate code after modifying this file
45+
// Represents the observations of a Memcached's current state.
46+
// Memcached.status.conditions.type are: "Available", "Progressing", and "Degraded"
47+
// Memcached.status.conditions.status are one of True, False, Unknown.
48+
// Memcached.status.conditions.reason the value should be a CamelCase string and producers of specific
49+
// condition types may define expected values and meanings for this field, and whether the values
50+
// are considered a guaranteed API.
51+
// Memcached.status.conditions.Message is a human readable message indicating details about the transition.
52+
// For further information see: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties
53+
54+
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"`
4755
}
4856

4957
//+kubebuilder:object:root=true

testdata/project-v3-with-deploy-image/api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 17 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

testdata/project-v3-with-deploy-image/config/crd/bases/example.com.testproject.org_busyboxes.yaml

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,75 @@ spec:
4646
type: object
4747
status:
4848
description: BusyboxStatus defines the observed state of Busybox
49+
properties:
50+
conditions:
51+
items:
52+
description: "Condition contains details for one aspect of the current
53+
state of this API Resource. --- This struct is intended for direct
54+
use as an array at the field path .status.conditions. For example,
55+
type FooStatus struct{ // Represents the observations of a foo's
56+
current state. // Known .status.conditions.type are: \"Available\",
57+
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
58+
// +listType=map // +listMapKey=type Conditions []metav1.Condition
59+
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
60+
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
61+
properties:
62+
lastTransitionTime:
63+
description: lastTransitionTime is the last time the condition
64+
transitioned from one status to another. This should be when
65+
the underlying condition changed. If that is not known, then
66+
using the time when the API field changed is acceptable.
67+
format: date-time
68+
type: string
69+
message:
70+
description: message is a human readable message indicating
71+
details about the transition. This may be an empty string.
72+
maxLength: 32768
73+
type: string
74+
observedGeneration:
75+
description: observedGeneration represents the .metadata.generation
76+
that the condition was set based upon. For instance, if .metadata.generation
77+
is currently 12, but the .status.conditions[x].observedGeneration
78+
is 9, the condition is out of date with respect to the current
79+
state of the instance.
80+
format: int64
81+
minimum: 0
82+
type: integer
83+
reason:
84+
description: reason contains a programmatic identifier indicating
85+
the reason for the condition's last transition. Producers
86+
of specific condition types may define expected values and
87+
meanings for this field, and whether the values are considered
88+
a guaranteed API. The value should be a CamelCase string.
89+
This field may not be empty.
90+
maxLength: 1024
91+
minLength: 1
92+
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
93+
type: string
94+
status:
95+
description: status of the condition, one of True, False, Unknown.
96+
enum:
97+
- "True"
98+
- "False"
99+
- Unknown
100+
type: string
101+
type:
102+
description: type of condition in CamelCase or in foo.example.com/CamelCase.
103+
--- Many .condition.type values are consistent across resources
104+
like Available, but because arbitrary conditions can be useful
105+
(see .node.status.conditions), the ability to deconflict is
106+
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
107+
maxLength: 316
108+
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
109+
type: string
110+
required:
111+
- lastTransitionTime
112+
- message
113+
- reason
114+
- status
115+
- type
116+
type: object
117+
type: array
49118
type: object
50119
type: object
51120
served: true

0 commit comments

Comments
 (0)