Skip to content

Commit 73b89c4

Browse files
committed
ansible: introduce managedStatus flag
- Adds new bool flag to ansible operator `managedStatus`. When True (expected default) the ansible operator manages the status. When false the ansible operator does not touch the status of the CR. - Adds new field to `watches.yaml`, for each GVK, you can now set `managedStatus: bool` see https://golang.org/pkg/strconv/#ParseBool - Update reconciler to only update the status of the CR when `managedStatus` Also move constants out of `status/utils.go` into `status/types.go` to prevent confusion.
1 parent 675fb6e commit 73b89c4

File tree

11 files changed

+129
-48
lines changed

11 files changed

+129
-48
lines changed

commands/operator-sdk/cmd/up/local.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ func upLocalAnsible() {
148148
}
149149

150150
// start the operator
151-
go ansibleOperator.Run(done, mgr, "./"+ansibleScaffold.WatchesYamlFile, time.Minute)
151+
go ansibleOperator.Run(done, mgr, "./"+ansibleScaffold.WatchesYamlFile, time.Minute, true)
152152

153153
// wait for either to finish
154154
err = <-done

pkg/ansible/controller/controller.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ type Options struct {
4040
Runner runner.Runner
4141
GVK schema.GroupVersionKind
4242
ReconcilePeriod time.Duration
43+
ManagedStatus bool
4344
}
4445

4546
// Add - Creates a new ansible operator controller and adds it to the manager
@@ -56,6 +57,7 @@ func Add(mgr manager.Manager, options Options) {
5657
Runner: options.Runner,
5758
EventHandlers: eventHandlers,
5859
ReconcilePeriod: options.ReconcilePeriod,
60+
ManagedStatus: options.ManagedStatus,
5961
}
6062

6163
// Register the GVK with the schema

pkg/ansible/controller/reconcile.go

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ type AnsibleOperatorReconciler struct {
5454
Client client.Client
5555
EventHandlers []events.EventHandler
5656
ReconcilePeriod time.Duration
57+
ManagedStatus bool
5758
}
5859

5960
// Reconcile - handle the event.
@@ -113,28 +114,9 @@ func (r *AnsibleOperatorReconciler) Reconcile(request reconcile.Request) (reconc
113114
return reconcileResult, err
114115
}
115116
}
116-
statusInterface := u.Object["status"]
117-
statusMap, _ := statusInterface.(map[string]interface{})
118-
crStatus := ansiblestatus.CreateFromMap(statusMap)
119-
120-
// If there is no current status add that we are working on this resource.
121-
errCond := ansiblestatus.GetCondition(crStatus, ansiblestatus.FailureConditionType)
122-
succCond := ansiblestatus.GetCondition(crStatus, ansiblestatus.RunningConditionType)
123117

124-
// If the condition is currently running, making sure that the values are correct.
125-
// If they are the same a no-op, if they are different then it is a good thing we
126-
// are updating it.
127-
if (errCond == nil && succCond == nil) || (succCond != nil && succCond.Reason != ansiblestatus.SuccessfulReason) {
128-
c := ansiblestatus.NewCondition(
129-
ansiblestatus.RunningConditionType,
130-
v1.ConditionTrue,
131-
nil,
132-
ansiblestatus.RunningReason,
133-
ansiblestatus.RunningMessage,
134-
)
135-
ansiblestatus.SetCondition(&crStatus, *c)
136-
u.Object["status"] = crStatus
137-
err = r.Client.Update(context.TODO(), u)
118+
if r.ManagedStatus {
119+
err = r.markRunning(u)
138120
if err != nil {
139121
return reconcileResult, err
140122
}
@@ -207,6 +189,48 @@ func (r *AnsibleOperatorReconciler) Reconcile(request reconcile.Request) (reconc
207189
return reconcileResult, err
208190
}
209191
}
192+
if r.ManagedStatus {
193+
err = r.markDone(u, statusEvent, failureMessages)
194+
}
195+
return reconcileResult, err
196+
}
197+
198+
func (r *AnsibleOperatorReconciler) markRunning(u *unstructured.Unstructured) error {
199+
statusInterface := u.Object["status"]
200+
statusMap, _ := statusInterface.(map[string]interface{})
201+
crStatus := ansiblestatus.CreateFromMap(statusMap)
202+
203+
// If there is no current status add that we are working on this resource.
204+
errCond := ansiblestatus.GetCondition(crStatus, ansiblestatus.FailureConditionType)
205+
succCond := ansiblestatus.GetCondition(crStatus, ansiblestatus.RunningConditionType)
206+
207+
// If the condition is currently running, making sure that the values are correct.
208+
// If they are the same a no-op, if they are different then it is a good thing we
209+
// are updating it.
210+
if (errCond == nil && succCond == nil) || (succCond != nil && succCond.Reason != ansiblestatus.SuccessfulReason) {
211+
c := ansiblestatus.NewCondition(
212+
ansiblestatus.RunningConditionType,
213+
v1.ConditionTrue,
214+
nil,
215+
ansiblestatus.RunningReason,
216+
ansiblestatus.RunningMessage,
217+
)
218+
ansiblestatus.SetCondition(&crStatus, *c)
219+
u.Object["status"] = crStatus
220+
err := r.Client.Update(context.TODO(), u)
221+
if err != nil {
222+
return err
223+
}
224+
}
225+
return nil
226+
}
227+
228+
func (r *AnsibleOperatorReconciler) markDone(u *unstructured.Unstructured, statusEvent eventapi.StatusJobEvent, failureMessages eventapi.FailureMessages) error {
229+
statusInterface := u.Object["status"]
230+
statusMap, _ := statusInterface.(map[string]interface{})
231+
crStatus := ansiblestatus.CreateFromMap(statusMap)
232+
233+
runSuccessful := len(failureMessages) == 0
210234
ansibleStatus := ansiblestatus.NewAnsibleResultFromStatusJobEvent(statusEvent)
211235

212236
if !runSuccessful {
@@ -235,9 +259,8 @@ func (r *AnsibleOperatorReconciler) Reconcile(request reconcile.Request) (reconc
235259
}
236260
// This needs the status subresource to be enabled by default.
237261
u.Object["status"] = crStatus
238-
err = r.Client.Update(context.TODO(), u)
239-
return reconcileResult, err
240262

263+
return r.Client.Update(context.TODO(), u)
241264
}
242265

243266
func contains(l []string, s string) bool {

pkg/ansible/controller/status/types.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,24 @@ func NewAnsibleResultFromMap(sm map[string]interface{}) *AnsibleResult {
7979
return a
8080
}
8181

82+
const (
83+
// RunningReason - Condition is running
84+
RunningReason = "Running"
85+
// SuccessfulReason - Condition is running due to reconcile being successful
86+
SuccessfulReason = "Successful"
87+
// FailedReason - Condition is failed due to ansible failure
88+
FailedReason = "Failed"
89+
// UnknownFailedReason - Condition is unknown
90+
UnknownFailedReason = "Unknown"
91+
)
92+
93+
const (
94+
// RunningMessage - message for running reason.
95+
RunningMessage = "Running reconciliation"
96+
// SuccessfulMessage - message for successful reason.
97+
SuccessfulMessage = "Awaiting next reconciliation"
98+
)
99+
82100
// ConditionType - type of condition
83101
type ConditionType string
84102

pkg/ansible/controller/status/utils.go

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,6 @@ import (
1919
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2020
)
2121

22-
const (
23-
// RunningReason - Condition is running
24-
RunningReason = "Running"
25-
// SuccessfulReason - Condition is running due to reconcile being successful
26-
SuccessfulReason = "Successful"
27-
// FailedReason - Condition is failed due to ansible failure
28-
FailedReason = "Failed"
29-
// UnknownFailedReason - Condition is unknown
30-
UnknownFailedReason = "Unknown"
31-
)
32-
33-
const (
34-
// RunningMessage - message for running reason.
35-
RunningMessage = "Running reconciliation"
36-
// SuccessfulMessage - message for successful reason.
37-
SuccessfulMessage = "Awaiting next reconciliation"
38-
)
39-
4022
// NewCondition - condition
4123
func NewCondition(condType ConditionType, status v1.ConditionStatus, ansibleResult *AnsibleResult, reason, message string) *Condition {
4224
return &Condition{

pkg/ansible/operator/operator.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import (
2929
// Run - A blocking function which starts a controller-runtime manager
3030
// It starts an Operator by reading in the values in `./watches.yaml`, adds a controller
3131
// to the manager, and finally running the manager.
32-
func Run(done chan error, mgr manager.Manager, watchesPath string, reconcilePeriod time.Duration) {
32+
func Run(done chan error, mgr manager.Manager, watchesPath string, reconcilePeriod time.Duration, managedStatus bool) {
3333
watches, err := runner.NewFromWatches(watchesPath)
3434
if err != nil {
3535
logrus.Error("Failed to get watches")
@@ -49,6 +49,10 @@ func Run(done chan error, mgr manager.Manager, watchesPath string, reconcilePeri
4949
if ok {
5050
o.ReconcilePeriod = d
5151
}
52+
s, ok := runner.GetManagedStatus()
53+
if ok {
54+
o.ManagedStatus = s
55+
}
5256
controller.Add(mgr, o)
5357
}
5458
done <- mgr.Start(c)

pkg/ansible/runner/runner.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"os"
2323
"os/exec"
2424
"path/filepath"
25+
"strconv"
2526
"strings"
2627
"time"
2728

@@ -40,6 +41,7 @@ type Runner interface {
4041
Run(string, *unstructured.Unstructured, string) (*RunResult, error)
4142
GetFinalizer() (string, bool)
4243
GetReconcilePeriod() (time.Duration, bool)
44+
GetManagedStatus() (bool, bool)
4345
}
4446

4547
// watch holds data used to create a mapping of GVK to ansible playbook or role.
@@ -51,6 +53,7 @@ type watch struct {
5153
Playbook string `yaml:"playbook"`
5254
Role string `yaml:"role"`
5355
ReconcilePeriod string `yaml:"reconcilePeriod"`
56+
ManagedStatus string `yaml:"managedStatus"`
5457
Finalizer *Finalizer `yaml:"finalizer"`
5558
}
5659

@@ -91,20 +94,28 @@ func NewFromWatches(path string) (map[schema.GroupVersionKind]Runner, error) {
9194
}
9295
reconcilePeriod = &d
9396
}
97+
var managedStatus *bool
98+
if w.ManagedStatus != "" {
99+
stat, err := strconv.ParseBool(w.ManagedStatus)
100+
if err != nil {
101+
return nil, fmt.Errorf("unable to parse managedStatus: %v - %v, setting to default", w.ManagedStatus, err)
102+
}
103+
managedStatus = &stat
104+
}
94105

95106
// Check if schema is a duplicate
96107
if _, ok := m[s]; ok {
97108
return nil, fmt.Errorf("duplicate GVK: %v", s.String())
98109
}
99110
switch {
100111
case w.Playbook != "":
101-
r, err := NewForPlaybook(w.Playbook, s, w.Finalizer, reconcilePeriod)
112+
r, err := NewForPlaybook(w.Playbook, s, w.Finalizer, reconcilePeriod, managedStatus)
102113
if err != nil {
103114
return nil, err
104115
}
105116
m[s] = r
106117
case w.Role != "":
107-
r, err := NewForRole(w.Role, s, w.Finalizer, reconcilePeriod)
118+
r, err := NewForRole(w.Role, s, w.Finalizer, reconcilePeriod, managedStatus)
108119
if err != nil {
109120
return nil, err
110121
}
@@ -117,7 +128,7 @@ func NewFromWatches(path string) (map[schema.GroupVersionKind]Runner, error) {
117128
}
118129

119130
// NewForPlaybook returns a new Runner based on the path to an ansible playbook.
120-
func NewForPlaybook(path string, gvk schema.GroupVersionKind, finalizer *Finalizer, reconcilePeriod *time.Duration) (Runner, error) {
131+
func NewForPlaybook(path string, gvk schema.GroupVersionKind, finalizer *Finalizer, reconcilePeriod *time.Duration, managedStatus *bool) (Runner, error) {
121132
if !filepath.IsAbs(path) {
122133
return nil, fmt.Errorf("playbook path must be absolute for %v", gvk)
123134
}
@@ -131,6 +142,7 @@ func NewForPlaybook(path string, gvk schema.GroupVersionKind, finalizer *Finaliz
131142
return exec.Command("ansible-runner", "-vv", "-p", path, "-i", ident, "run", inputDirPath)
132143
},
133144
reconcilePeriod: reconcilePeriod,
145+
managedStatus: managedStatus,
134146
}
135147
err := r.addFinalizer(finalizer)
136148
if err != nil {
@@ -140,7 +152,7 @@ func NewForPlaybook(path string, gvk schema.GroupVersionKind, finalizer *Finaliz
140152
}
141153

142154
// NewForRole returns a new Runner based on the path to an ansible role.
143-
func NewForRole(path string, gvk schema.GroupVersionKind, finalizer *Finalizer, reconcilePeriod *time.Duration) (Runner, error) {
155+
func NewForRole(path string, gvk schema.GroupVersionKind, finalizer *Finalizer, reconcilePeriod *time.Duration, managedStatus *bool) (Runner, error) {
144156
if !filepath.IsAbs(path) {
145157
return nil, fmt.Errorf("role path must be absolute for %v", gvk)
146158
}
@@ -156,6 +168,7 @@ func NewForRole(path string, gvk schema.GroupVersionKind, finalizer *Finalizer,
156168
return exec.Command("ansible-runner", "-vv", "--role", roleName, "--roles-path", rolePath, "--hosts", "localhost", "-i", ident, "run", inputDirPath)
157169
},
158170
reconcilePeriod: reconcilePeriod,
171+
managedStatus: managedStatus,
159172
}
160173
err := r.addFinalizer(finalizer)
161174
if err != nil {
@@ -172,6 +185,7 @@ type runner struct {
172185
cmdFunc func(ident, inputDirPath string) *exec.Cmd // returns a Cmd that runs ansible-runner
173186
finalizerCmdFunc func(ident, inputDirPath string) *exec.Cmd
174187
reconcilePeriod *time.Duration
188+
managedStatus *bool
175189
}
176190

177191
func (r *runner) Run(ident string, u *unstructured.Unstructured, kubeconfig string) (*RunResult, error) {
@@ -254,6 +268,14 @@ func (r *runner) GetReconcilePeriod() (time.Duration, bool) {
254268
return *r.reconcilePeriod, true
255269
}
256270

271+
// GetManagedStatus - return runner's managedStatus if defined
272+
func (r *runner) GetManagedStatus() (bool, bool) {
273+
if r.managedStatus == nil {
274+
return false, false
275+
}
276+
return *r.managedStatus, true
277+
}
278+
257279
func (r *runner) GetFinalizer() (string, bool) {
258280
if r.Finalizer != nil {
259281
return r.Finalizer.Name, true

pkg/ansible/runner/runner_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ func TestNewFromWatches(t *testing.T) {
5555

5656
zeroSeconds := time.Duration(0)
5757
twoSeconds := time.Second * 2
58+
noManagedStatus := false
5859
testCases := []struct {
5960
name string
6061
path string
@@ -101,6 +102,11 @@ func TestNewFromWatches(t *testing.T) {
101102
path: "testdata/invalid_duration.yaml",
102103
shouldError: true,
103104
},
105+
{
106+
name: "error invalid status",
107+
path: "testdata/invalid_status.yaml",
108+
shouldError: true,
109+
},
104110
{
105111
name: "valid watches file",
106112
path: "testdata/valid.yaml",
@@ -148,6 +154,19 @@ func TestNewFromWatches(t *testing.T) {
148154
Path: validTemplate.ValidPlaybook,
149155
reconcilePeriod: &zeroSeconds,
150156
},
157+
schema.GroupVersionKind{
158+
Version: "v1alpha1",
159+
Group: "app.example.com",
160+
Kind: "DisableStatus",
161+
}: runner{
162+
GVK: schema.GroupVersionKind{
163+
Version: "v1alpha1",
164+
Group: "app.example.com",
165+
Kind: "DisableStatus",
166+
},
167+
Path: validTemplate.ValidPlaybook,
168+
managedStatus: &noManagedStatus,
169+
},
151170
schema.GroupVersionKind{
152171
Version: "v1alpha1",
153172
Group: "app.example.com",
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
- version: v1alpha1
3+
group: app.example.com
4+
kind: Database
5+
playbook: /opt/ansible/playbook.yaml
6+
watches: invalid

pkg/ansible/runner/testdata/valid.yaml.tmpl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
kind: NoReconcile
1919
playbook: {{ .ValidPlaybook }}
2020
reconcilePeriod: 0s
21+
- version: v1alpha1
22+
group: app.example.com
23+
kind: DisableStatus
24+
playbook: {{ .ValidPlaybook }}
25+
managedStatus: False
2126
- version: v1alpha1
2227
group: app.example.com
2328
kind: Role

test/ansible-operator/cmd/ansible-operator/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func main() {
6767
}
6868

6969
// start the operator
70-
go operator.Run(done, mgr, "/opt/ansible/watches.yaml", time.Minute)
70+
go operator.Run(done, mgr, "/opt/ansible/watches.yaml", time.Minute, true)
7171

7272
// wait for either to finish
7373
err = <-done

0 commit comments

Comments
 (0)