Skip to content

Commit ca6f53b

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 acb5dbe commit ca6f53b

File tree

9 files changed

+111
-30
lines changed

9 files changed

+111
-30
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ func upLocalAnsible() {
161161
}
162162

163163
// start the operator
164-
go ansibleOperator.Run(done, mgr, "./"+ansibleScaffold.WatchesYamlFile, time.Minute)
164+
go ansibleOperator.Run(done, mgr, "./"+ansibleScaffold.WatchesYamlFile, time.Minute, true)
165165

166166
// wait for either to finish
167167
err = <-done

pkg/ansible/controller/controller.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type Options struct {
4242
Runner runner.Runner
4343
GVK schema.GroupVersionKind
4444
ReconcilePeriod time.Duration
45+
ManagedStatus bool
4546
}
4647

4748
// Add - Creates a new ansible operator controller and adds it to the manager
@@ -58,6 +59,7 @@ func Add(mgr manager.Manager, options Options) {
5859
Runner: options.Runner,
5960
EventHandlers: eventHandlers,
6061
ReconcilePeriod: options.ReconcilePeriod,
62+
ManagedStatus: options.ManagedStatus,
6163
}
6264

6365
// 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.
@@ -112,28 +113,9 @@ func (r *AnsibleOperatorReconciler) Reconcile(request reconcile.Request) (reconc
112113
return reconcileResult, err
113114
}
114115
}
115-
statusInterface := u.Object["status"]
116-
statusMap, _ := statusInterface.(map[string]interface{})
117-
crStatus := ansiblestatus.CreateFromMap(statusMap)
118-
119-
// If there is no current status add that we are working on this resource.
120-
errCond := ansiblestatus.GetCondition(crStatus, ansiblestatus.FailureConditionType)
121-
succCond := ansiblestatus.GetCondition(crStatus, ansiblestatus.RunningConditionType)
122116

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

210234
if !runSuccessful {
@@ -233,9 +257,8 @@ func (r *AnsibleOperatorReconciler) Reconcile(request reconcile.Request) (reconc
233257
}
234258
// This needs the status subresource to be enabled by default.
235259
u.Object["status"] = crStatus
236-
err = r.Client.Update(context.TODO(), u)
237-
return reconcileResult, err
238260

261+
return r.Client.Update(context.TODO(), u)
239262
}
240263

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

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
logf.Log.WithName("manager").Error(err, "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

@@ -43,6 +44,7 @@ type Runner interface {
4344
Run(string, *unstructured.Unstructured, string) (*RunResult, error)
4445
GetFinalizer() (string, bool)
4546
GetReconcilePeriod() (time.Duration, bool)
47+
GetManagedStatus() (bool, bool)
4648
}
4749

4850
// watch holds data used to create a mapping of GVK to ansible playbook or role.
@@ -54,6 +56,7 @@ type watch struct {
5456
Playbook string `yaml:"playbook"`
5557
Role string `yaml:"role"`
5658
ReconcilePeriod string `yaml:"reconcilePeriod"`
59+
ManagedStatus string `yaml:"managedStatus"`
5760
Finalizer *Finalizer `yaml:"finalizer"`
5861
}
5962

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

98109
// Check if schema is a duplicate
99110
if _, ok := m[s]; ok {
100111
return nil, fmt.Errorf("duplicate GVK: %v", s.String())
101112
}
102113
switch {
103114
case w.Playbook != "":
104-
r, err := NewForPlaybook(w.Playbook, s, w.Finalizer, reconcilePeriod)
115+
r, err := NewForPlaybook(w.Playbook, s, w.Finalizer, reconcilePeriod, managedStatus)
105116
if err != nil {
106117
return nil, err
107118
}
108119
m[s] = r
109120
case w.Role != "":
110-
r, err := NewForRole(w.Role, s, w.Finalizer, reconcilePeriod)
121+
r, err := NewForRole(w.Role, s, w.Finalizer, reconcilePeriod, managedStatus)
111122
if err != nil {
112123
return nil, err
113124
}
@@ -120,7 +131,7 @@ func NewFromWatches(path string) (map[schema.GroupVersionKind]Runner, error) {
120131
}
121132

122133
// NewForPlaybook returns a new Runner based on the path to an ansible playbook.
123-
func NewForPlaybook(path string, gvk schema.GroupVersionKind, finalizer *Finalizer, reconcilePeriod *time.Duration) (Runner, error) {
134+
func NewForPlaybook(path string, gvk schema.GroupVersionKind, finalizer *Finalizer, reconcilePeriod *time.Duration, managedStatus *bool) (Runner, error) {
124135
if !filepath.IsAbs(path) {
125136
return nil, fmt.Errorf("playbook path must be absolute for %v", gvk)
126137
}
@@ -134,6 +145,7 @@ func NewForPlaybook(path string, gvk schema.GroupVersionKind, finalizer *Finaliz
134145
return exec.Command("ansible-runner", "-vv", "-p", path, "-i", ident, "run", inputDirPath)
135146
},
136147
reconcilePeriod: reconcilePeriod,
148+
managedStatus: managedStatus,
137149
}
138150
err := r.addFinalizer(finalizer)
139151
if err != nil {
@@ -143,7 +155,7 @@ func NewForPlaybook(path string, gvk schema.GroupVersionKind, finalizer *Finaliz
143155
}
144156

145157
// NewForRole returns a new Runner based on the path to an ansible role.
146-
func NewForRole(path string, gvk schema.GroupVersionKind, finalizer *Finalizer, reconcilePeriod *time.Duration) (Runner, error) {
158+
func NewForRole(path string, gvk schema.GroupVersionKind, finalizer *Finalizer, reconcilePeriod *time.Duration, managedStatus *bool) (Runner, error) {
147159
if !filepath.IsAbs(path) {
148160
return nil, fmt.Errorf("role path must be absolute for %v", gvk)
149161
}
@@ -159,6 +171,7 @@ func NewForRole(path string, gvk schema.GroupVersionKind, finalizer *Finalizer,
159171
return exec.Command("ansible-runner", "-vv", "--role", roleName, "--roles-path", rolePath, "--hosts", "localhost", "-i", ident, "run", inputDirPath)
160172
},
161173
reconcilePeriod: reconcilePeriod,
174+
managedStatus: managedStatus,
162175
}
163176
err := r.addFinalizer(finalizer)
164177
if err != nil {
@@ -175,6 +188,7 @@ type runner struct {
175188
cmdFunc func(ident, inputDirPath string) *exec.Cmd // returns a Cmd that runs ansible-runner
176189
finalizerCmdFunc func(ident, inputDirPath string) *exec.Cmd
177190
reconcilePeriod *time.Duration
191+
managedStatus *bool
178192
}
179193

180194
func (r *runner) Run(ident string, u *unstructured.Unstructured, kubeconfig string) (*RunResult, error) {
@@ -257,6 +271,14 @@ func (r *runner) GetReconcilePeriod() (time.Duration, bool) {
257271
return *r.reconcilePeriod, true
258272
}
259273

274+
// GetManagedStatus - return runner's managedStatus if defined
275+
func (r *runner) GetManagedStatus() (bool, bool) {
276+
if r.managedStatus == nil {
277+
return false, false
278+
}
279+
return *r.managedStatus, true
280+
}
281+
260282
func (r *runner) GetFinalizer() (string, bool) {
261283
if r.Finalizer != nil {
262284
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)