Skip to content

Commit 2609258

Browse files
committed
internal/pkg/scaffold/,pkg/status/: add status helpers
1 parent ead640a commit 2609258

File tree

7 files changed

+895
-18
lines changed

7 files changed

+895
-18
lines changed

internal/pkg/scaffold/controller_kind.go

+52-8
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@ const controllerKindTemplate = `package {{ .Resource.LowerKind }}
4343
4444
import (
4545
"context"
46+
"fmt"
4647
4748
{{ .Resource.GoImportGroup}}{{ .Resource.Version }} "{{ .Repo }}/pkg/apis/{{ .Resource.GoImportGroup}}/{{ .Resource.Version }}"
4849
50+
"github.com/operator-framework/operator-sdk/pkg/status"
4951
corev1 "k8s.io/api/core/v1"
5052
"k8s.io/apimachinery/pkg/api/errors"
5153
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -146,7 +148,8 @@ func (r *Reconcile{{ .Resource.Kind }}) Reconcile(request reconcile.Request) (re
146148
147149
// Set {{ .Resource.Kind }} instance as the owner and controller
148150
if err := controllerutil.SetControllerReference(instance, pod, r.scheme); err != nil {
149-
return reconcile.Result{}, err
151+
// Failed to set owner reference on pod - set status not ready and requeue
152+
return r.setStatusNotReady(instance, err)
150153
}
151154
152155
// Check if this Pod already exists
@@ -156,18 +159,32 @@ func (r *Reconcile{{ .Resource.Kind }}) Reconcile(request reconcile.Request) (re
156159
reqLogger.Info("Creating a new Pod", "Pod.Namespace", pod.Namespace, "Pod.Name", pod.Name)
157160
err = r.client.Create(context.TODO(), pod)
158161
if err != nil {
159-
return reconcile.Result{}, err
162+
// Failed to create pod - set status not ready and requeue
163+
return r.setStatusNotReady(instance, err)
160164
}
161165
162-
// Pod created successfully - don't requeue
163-
return reconcile.Result{}, nil
166+
// Pod created successfully - set status ready
167+
return r.setStatusReady(instance)
164168
} else if err != nil {
165-
return reconcile.Result{}, err
169+
// Failed to get pod - set status not ready and requeue
170+
return r.setStatusNotReady(instance, err)
166171
}
167172
168-
// Pod already exists - don't requeue
169-
reqLogger.Info("Skip reconcile: Pod already exists", "Pod.Namespace", found.Namespace, "Pod.Name", found.Name)
170-
return reconcile.Result{}, nil
173+
// If Pod is not yet running, don't set Ready condition to true
174+
if found.Status.Phase != corev1.PodRunning {
175+
notRunningErr := fmt.Errorf("pod %s not running", found.Name)
176+
res, err := r.setStatusNotReady(instance, notRunningErr)
177+
if err != notRunningErr {
178+
return res, err
179+
}
180+
181+
reqLogger.Info("Pod already exists, but is not yet running", "Pod.Namespace", found.Namespace, "Pod.Name", found.Name, "Pod.Phase", found.Status.Phase)
182+
return reconcile.Result{}, nil
183+
}
184+
185+
// Pod already exists and is running - set status ready
186+
reqLogger.Info("Skip reconcile: Pod already exists", "Pod.Namespace", found.Namespace, "Pod.Name", found.Name, "Pod.Phase", found.Status.Phase)
187+
return r.setStatusReady(instance)
171188
}
172189
173190
// newPodForCR returns a busybox pod with the same name/namespace as the cr
@@ -192,4 +209,31 @@ func newPodForCR(cr *{{ .Resource.GoImportGroup}}{{ .Resource.Version }}.{{ .Res
192209
},
193210
}
194211
}
212+
213+
func (r *Reconcile{{ .Resource.Kind }}) setStatusReady(instance *{{ .Resource.GoImportGroup}}{{ .Resource.Version }}.{{ .Resource.Kind }}) (reconcile.Result, error) {
214+
if setErr := instance.Status.Conditions.SetCondition(&status.BasicCondition{
215+
Type: status.ConditionType("Ready"),
216+
Status: corev1.ConditionTrue,
217+
}); setErr != nil {
218+
return reconcile.Result{}, setErr
219+
}
220+
if updateErr := r.client.Status().Update(context.TODO(), instance); updateErr != nil {
221+
return reconcile.Result{}, updateErr
222+
}
223+
return reconcile.Result{}, nil
224+
}
225+
226+
func (r *Reconcile{{ .Resource.Kind }}) setStatusNotReady(instance *{{ .Resource.GoImportGroup}}{{ .Resource.Version }}.{{ .Resource.Kind }}, origErr error) (reconcile.Result, error) {
227+
if setErr := instance.Status.Conditions.SetCondition(&status.BasicCondition{
228+
Type: status.ConditionType("Ready"),
229+
Status: corev1.ConditionFalse,
230+
Message: origErr.Error(),
231+
}); setErr != nil {
232+
return reconcile.Result{}, setErr
233+
}
234+
if updateErr := r.client.Status().Update(context.TODO(), instance); updateErr != nil {
235+
return reconcile.Result{}, updateErr
236+
}
237+
return reconcile.Result{}, origErr
238+
}
195239
`

internal/pkg/scaffold/controller_kind_test.go

+52-8
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,11 @@ const controllerKindExp = `package appservice
4141
4242
import (
4343
"context"
44+
"fmt"
4445
4546
appv1alpha1 "github.com/example-inc/app-operator/pkg/apis/app/v1alpha1"
4647
48+
"github.com/operator-framework/operator-sdk/pkg/status"
4749
corev1 "k8s.io/api/core/v1"
4850
"k8s.io/apimachinery/pkg/api/errors"
4951
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -144,7 +146,8 @@ func (r *ReconcileAppService) Reconcile(request reconcile.Request) (reconcile.Re
144146
145147
// Set AppService instance as the owner and controller
146148
if err := controllerutil.SetControllerReference(instance, pod, r.scheme); err != nil {
147-
return reconcile.Result{}, err
149+
// Failed to set owner reference on pod - set status not ready and requeue
150+
return r.setStatusNotReady(instance, err)
148151
}
149152
150153
// Check if this Pod already exists
@@ -154,18 +157,32 @@ func (r *ReconcileAppService) Reconcile(request reconcile.Request) (reconcile.Re
154157
reqLogger.Info("Creating a new Pod", "Pod.Namespace", pod.Namespace, "Pod.Name", pod.Name)
155158
err = r.client.Create(context.TODO(), pod)
156159
if err != nil {
157-
return reconcile.Result{}, err
160+
// Failed to create pod - set status not ready and requeue
161+
return r.setStatusNotReady(instance, err)
158162
}
159163
160-
// Pod created successfully - don't requeue
161-
return reconcile.Result{}, nil
164+
// Pod created successfully - set status ready
165+
return r.setStatusReady(instance)
162166
} else if err != nil {
163-
return reconcile.Result{}, err
167+
// Failed to get pod - set status not ready and requeue
168+
return r.setStatusNotReady(instance, err)
164169
}
165170
166-
// Pod already exists - don't requeue
167-
reqLogger.Info("Skip reconcile: Pod already exists", "Pod.Namespace", found.Namespace, "Pod.Name", found.Name)
168-
return reconcile.Result{}, nil
171+
// If Pod is not yet running, don't set Ready condition to true
172+
if found.Status.Phase != corev1.PodRunning {
173+
notRunningErr := fmt.Errorf("pod %s not running", found.Name)
174+
res, err := r.setStatusNotReady(instance, notRunningErr)
175+
if err != notRunningErr {
176+
return res, err
177+
}
178+
179+
reqLogger.Info("Pod already exists, but is not yet running", "Pod.Namespace", found.Namespace, "Pod.Name", found.Name, "Pod.Phase", found.Status.Phase)
180+
return reconcile.Result{}, nil
181+
}
182+
183+
// Pod already exists and is running - set status ready
184+
reqLogger.Info("Skip reconcile: Pod already exists", "Pod.Namespace", found.Namespace, "Pod.Name", found.Name, "Pod.Phase", found.Status.Phase)
185+
return r.setStatusReady(instance)
169186
}
170187
171188
// newPodForCR returns a busybox pod with the same name/namespace as the cr
@@ -190,4 +207,31 @@ func newPodForCR(cr *appv1alpha1.AppService) *corev1.Pod {
190207
},
191208
}
192209
}
210+
211+
func (r *ReconcileAppService) setStatusReady(instance *appv1alpha1.AppService) (reconcile.Result, error) {
212+
if setErr := instance.Status.Conditions.SetCondition(&status.BasicCondition{
213+
Type: status.ConditionType("Ready"),
214+
Status: corev1.ConditionTrue,
215+
}); setErr != nil {
216+
return reconcile.Result{}, setErr
217+
}
218+
if updateErr := r.client.Status().Update(context.TODO(), instance); updateErr != nil {
219+
return reconcile.Result{}, updateErr
220+
}
221+
return reconcile.Result{}, nil
222+
}
223+
224+
func (r *ReconcileAppService) setStatusNotReady(instance *appv1alpha1.AppService, origErr error) (reconcile.Result, error) {
225+
if setErr := instance.Status.Conditions.SetCondition(&status.BasicCondition{
226+
Type: status.ConditionType("Ready"),
227+
Status: corev1.ConditionFalse,
228+
Message: origErr.Error(),
229+
}); setErr != nil {
230+
return reconcile.Result{}, setErr
231+
}
232+
if updateErr := r.client.Status().Update(context.TODO(), instance); updateErr != nil {
233+
return reconcile.Result{}, updateErr
234+
}
235+
return reconcile.Result{}, origErr
236+
}
193237
`

internal/pkg/scaffold/types.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ const typesTemplate = `package {{ .Resource.Version }}
4646
4747
import (
4848
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
49+
50+
"github.com/operator-framework/operator-sdk/pkg/status"
4951
)
5052
5153
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
@@ -62,9 +64,16 @@ type {{.Resource.Kind}}Spec struct {
6264
// {{.Resource.Kind}}Status defines the observed state of {{.Resource.Kind}}
6365
// +k8s:openapi-gen=true
6466
type {{.Resource.Kind}}Status struct {
65-
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
67+
// The status.Conditions type provides helpers for managing status conditions for the custom resource,
68+
// such as adding and removing them and checking their status. Conditions are serialized as an array to
69+
// align with Kubernetes conventions.
70+
//
71+
Conditions status.Conditions ` + "`" + `json:"conditions"` + "`" + `
72+
73+
// INSERT ADDITIONAL STATUS FIELDS - define observed state of cluster
6674
// Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file
6775
// Add custom validation using kubebuilder tags: https://book.kubebuilder.io/beyond_basics/generating_crd.html
76+
// For status conventions, see https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties
6877
}
6978
7079
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

internal/pkg/scaffold/types_test.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ const typesExp = `package v1alpha1
4141
4242
import (
4343
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
44+
45+
"github.com/operator-framework/operator-sdk/pkg/status"
4446
)
4547
4648
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
@@ -57,9 +59,16 @@ type AppServiceSpec struct {
5759
// AppServiceStatus defines the observed state of AppService
5860
// +k8s:openapi-gen=true
5961
type AppServiceStatus struct {
60-
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
62+
// The status.Conditions type provides helpers for managing status conditions for the custom resource,
63+
// such as adding and removing them and checking their status. Conditions are serialized as an array to
64+
// align with Kubernetes conventions.
65+
//
66+
Conditions status.Conditions ` + "`" + `json:"conditions"` + "`" + `
67+
68+
// INSERT ADDITIONAL STATUS FIELDS - define observed state of cluster
6169
// Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file
6270
// Add custom validation using kubebuilder tags: https://book.kubebuilder.io/beyond_basics/generating_crd.html
71+
// For status conventions, see https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties
6372
}
6473
6574
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

pkg/status/basic_condition.go

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright 2019 The Operator-SDK Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package status
16+
17+
import (
18+
corev1 "k8s.io/api/core/v1"
19+
)
20+
21+
var (
22+
_ Condition = &BasicCondition{}
23+
)
24+
25+
// BasicCondition contains the base set of fields for all condition types. It
26+
// can be used directly with the Conditions type or it can be embedded in a
27+
// custom condition type. Since it implements the Condition interface, structs
28+
// that embed BasicCondition will also implement the Condition interface.
29+
type BasicCondition struct {
30+
Type ConditionType `json:"type"`
31+
Status corev1.ConditionStatus `json:"status"`
32+
Reason ConditionReason `json:"reason,omitempty"`
33+
Message string `json:"message,omitempty"`
34+
}
35+
36+
// GetType returns the type of the condition.
37+
func (c BasicCondition) GetType() ConditionType {
38+
return c.Type
39+
}
40+
41+
// GetStatus returns the status of the condition.
42+
func (c BasicCondition) GetStatus() corev1.ConditionStatus {
43+
return c.Status
44+
}
45+
46+
// IsTrue returns whether the condition status is "True".
47+
func (c BasicCondition) IsTrue() bool {
48+
return c.Status == corev1.ConditionTrue
49+
}
50+
51+
// IsFalse returns whether the condition status is "False".
52+
func (c BasicCondition) IsFalse() bool {
53+
return c.Status == corev1.ConditionFalse
54+
}
55+
56+
// IsUnknown returns whether the condition status is "Unknown".
57+
func (c BasicCondition) IsUnknown() bool {
58+
return c.Status == corev1.ConditionUnknown
59+
}
60+
61+
// GetReason returns the reason of the condition. If the condition does not
62+
// include a reason, GetReason returns an empty ConditionReason.
63+
func (c BasicCondition) GetReason() ConditionReason {
64+
return c.Reason
65+
}
66+
67+
// GetMessage returns the message of the condition. If the condition does not
68+
// include a message, GetMessage returns an empty string.
69+
func (c BasicCondition) GetMessage() string {
70+
return c.Message
71+
}
72+
73+
// SetStatus sets the status of the condition. Valid values are "True",
74+
// "False", and "Unknown".
75+
func (c *BasicCondition) SetStatus(s corev1.ConditionStatus) {
76+
c.Status = s
77+
}
78+
79+
// SetReason sets the reason of the condition.
80+
func (c *BasicCondition) SetReason(r ConditionReason) {
81+
c.Reason = r
82+
}
83+
84+
// SetMessage sets the message of the condition.
85+
func (c *BasicCondition) SetMessage(m string) {
86+
c.Message = m
87+
}

0 commit comments

Comments
 (0)